]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5218 Once a module has been turned into a project, its issues are no more visib...
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Fri, 18 Apr 2014 12:06:42 +0000 (14:06 +0200)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Fri, 18 Apr 2014 12:06:42 +0000 (14:06 +0200)
26 files changed:
plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTrackingDecorator.java
plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingDecoratorTest.java
sonar-batch/src/main/java/org/sonar/batch/issue/DefaultIssuable.java
sonar-batch/src/main/java/org/sonar/batch/issue/IssuableFactory.java
sonar-batch/src/main/java/org/sonar/batch/issue/ModuleIssues.java
sonar-batch/src/main/java/org/sonar/batch/issue/ScanIssueStorage.java
sonar-batch/src/test/java/org/sonar/batch/issue/DefaultIssuableTest.java
sonar-batch/src/test/java/org/sonar/batch/issue/IssuableFactoryTest.java
sonar-batch/src/test/java/org/sonar/batch/issue/ModuleIssuesTest.java
sonar-batch/src/test/java/org/sonar/batch/issue/ScanIssueStorageTest.java
sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueBuilder.java
sonar-core/src/main/java/org/sonar/core/issue/IssueUpdater.java
sonar-core/src/main/java/org/sonar/core/issue/db/IssueDto.java
sonar-core/src/main/java/org/sonar/core/issue/db/IssueMapper.java
sonar-core/src/main/java/org/sonar/core/issue/db/IssueStorage.java
sonar-core/src/main/java/org/sonar/core/issue/db/UpdateConflictResolver.java
sonar-core/src/main/resources/org/sonar/core/issue/db/IssueMapper.xml
sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueBuilderTest.java
sonar-core/src/test/java/org/sonar/core/issue/IssueUpdaterTest.java
sonar-core/src/test/java/org/sonar/core/issue/db/IssueDaoTest.java
sonar-core/src/test/java/org/sonar/core/issue/db/IssueMapperTest.java
sonar-core/src/test/resources/org/sonar/core/issue/db/IssueDaoTest/should_select_non_closed_issues_by_module_on_removed_project.xml [new file with mode: 0644]
sonar-core/src/test/resources/org/sonar/core/issue/db/IssueMapperTest/testUpdate-result.xml
sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java
sonar-server/src/main/java/org/sonar/server/issue/IssueService.java
sonar-server/src/test/java/org/sonar/server/issue/IssueServiceTest.java

index 65b2a5a31a67ae63f4a9e2e7a45e25f5419f1364..1838dc5311edd340903ab0d6ae67b669960e39d0 100644 (file)
@@ -157,6 +157,7 @@ public class IssueTrackingDecorator implements Decorator {
       issue.setStatus(ref.getStatus());
       issue.setAssignee(ref.getAssignee());
       issue.setAuthorLogin(ref.getAuthorLogin());
+
       if (ref.getIssueAttributes() != null) {
         issue.setAttributes(KeyValueFormat.parse(ref.getIssueAttributes()));
       }
@@ -180,6 +181,7 @@ public class IssueTrackingDecorator implements Decorator {
       Long debtInMinutes = ref.getDebt();
       Duration previousTechnicalDebt = debtInMinutes != null ? Duration.create(debtInMinutes) : null;
       updater.setPastTechnicalDebt(issue, previousTechnicalDebt, changeContext);
+      updater.setPastProject(issue, ref.getRootComponentKey(), changeContext);
     }
   }
 
index 05aa5bdb2d7e23bbe930ee8deaa1de835a6191d9..121b473d2d88d127d190e66b850bcb1d1df8a9e5 100644 (file)
@@ -508,7 +508,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
   @Test
   public void merge_matched_issue() throws Exception {
     IssueDto previousIssue = new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey_unit_test_only("squid", "AvoidCycle")
-      .setLine(10).setSeverity("MAJOR").setMessage("Message").setEffortToFix(1.5).setDebt(1L);
+      .setLine(10).setSeverity("MAJOR").setMessage("Message").setEffortToFix(1.5).setDebt(1L).setRootComponentKey_unit_test_only("sample");
     DefaultIssue issue = new DefaultIssue();
 
     IssueTrackingResult trackingResult = mock(IssueTrackingResult.class);
@@ -521,6 +521,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
     verify(updater).setPastMessage(eq(issue), eq("Message"), any(IssueChangeContext.class));
     verify(updater).setPastEffortToFix(eq(issue), eq(1.5), any(IssueChangeContext.class));
     verify(updater).setPastTechnicalDebt(eq(issue), eq(Duration.create(1L)), any(IssueChangeContext.class));
+    verify(updater).setPastProject(eq(issue), eq("sample"), any(IssueChangeContext.class));
   }
 
   @Test
index 62b44e7d69e3f92888db81e474a4b0f91f6aba0d..578bb2bb49ef97af435c0b7ee2985ae9f47d534f 100644 (file)
@@ -24,6 +24,7 @@ import org.sonar.api.component.Component;
 import org.sonar.api.issue.Issuable;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.resources.Project;
 import org.sonar.core.issue.DefaultIssueBuilder;
 
 import java.util.List;
@@ -36,16 +37,18 @@ public class DefaultIssuable implements Issuable {
   private final ModuleIssues moduleIssues;
   private final IssueCache cache;
   private final Component component;
+  private final Project project;
 
-  DefaultIssuable(Component component, ModuleIssues moduleIssues, IssueCache cache) {
+  DefaultIssuable(Component component, Project project, ModuleIssues moduleIssues, IssueCache cache) {
     this.component = component;
+    this.project = project;
     this.moduleIssues = moduleIssues;
     this.cache = cache;
   }
 
   @Override
   public IssueBuilder newIssueBuilder() {
-    return new DefaultIssueBuilder().componentKey(component.key());
+    return new DefaultIssueBuilder().componentKey(component.key()).projectKey(project.getKey());
   }
 
   @Override
index 147ab7c7c155e66503d93912618aa82b3ef4f8f4..7491bbb78144e523232b3ae4923da67cf8ad08b4 100644 (file)
@@ -22,6 +22,7 @@ package org.sonar.batch.issue;
 import org.sonar.api.component.Component;
 import org.sonar.api.issue.Issuable;
 import org.sonar.api.resources.Scopes;
+import org.sonar.batch.ProjectTree;
 import org.sonar.core.component.PerspectiveBuilder;
 import org.sonar.core.component.ResourceComponent;
 
@@ -35,11 +36,13 @@ public class IssuableFactory extends PerspectiveBuilder<Issuable> {
 
   private final ModuleIssues moduleIssues;
   private final IssueCache cache;
+  private final ProjectTree projectTree;
 
-  public IssuableFactory(ModuleIssues moduleIssues, IssueCache cache) {
+  public IssuableFactory(ModuleIssues moduleIssues, IssueCache cache, ProjectTree projectTree) {
     super(Issuable.class);
     this.moduleIssues = moduleIssues;
     this.cache = cache;
+    this.projectTree = projectTree;
   }
 
   @CheckForNull
@@ -49,6 +52,6 @@ public class IssuableFactory extends PerspectiveBuilder<Issuable> {
     if (component instanceof ResourceComponent) {
       supported = Scopes.isHigherThanOrEquals(((ResourceComponent) component).scope(), Scopes.FILE);
     }
-    return supported ? new DefaultIssuable(component, moduleIssues, cache) : null;
+    return supported ? new DefaultIssuable(component, projectTree.getRootProject(), moduleIssues, cache) : null;
   }
 }
index c0a49d68671e6e43b617a40d3aa4dd5dbb8c14ec..6f46e2cc7a9bcda27cc86d06ecbebc87d3c02f0e 100644 (file)
@@ -67,6 +67,7 @@ public class ModuleIssues {
   private DefaultIssue newIssue(Violation violation) {
     return (DefaultIssue) new DefaultIssueBuilder()
       .componentKey(violation.getResource().getEffectiveKey())
+      .projectKey(project.getRoot().getEffectiveKey())
       .ruleKey(RuleKey.of(violation.getRule().getRepositoryKey(), violation.getRule().getKey()))
       .effortToFix(violation.getCost())
       .line(violation.getLineId())
index 23dab80cde165b3d4b8de2da77eb7fd06b3fed52..723082eb3355e6e99fa089b72dc5a9ce7181096b 100644 (file)
@@ -23,6 +23,7 @@ import org.sonar.api.BatchComponent;
 import org.sonar.api.database.model.Snapshot;
 import org.sonar.api.issue.internal.DefaultIssue;
 import org.sonar.api.rules.RuleFinder;
+import org.sonar.batch.ProjectTree;
 import org.sonar.batch.index.SnapshotCache;
 import org.sonar.core.issue.db.IssueStorage;
 import org.sonar.core.persistence.MyBatis;
@@ -34,16 +35,18 @@ public class ScanIssueStorage extends IssueStorage implements BatchComponent {
 
   private final SnapshotCache snapshotCache;
   private final ResourceDao resourceDao;
+  private final ProjectTree projectTree;
 
-  public ScanIssueStorage(MyBatis mybatis, RuleFinder ruleFinder, SnapshotCache snapshotCache, ResourceDao resourceDao) {
+  public ScanIssueStorage(MyBatis mybatis, RuleFinder ruleFinder, SnapshotCache snapshotCache, ResourceDao resourceDao, ProjectTree projectTree) {
     super(mybatis, ruleFinder);
     this.snapshotCache = snapshotCache;
     this.resourceDao = resourceDao;
+    this.projectTree = projectTree;
   }
 
   @Override
   protected long componentId(DefaultIssue issue) {
-    Snapshot snapshot = getSnapshot(issue);
+    Snapshot snapshot = snapshotCache.get(issue.componentKey());
     if (snapshot != null) {
       return snapshot.getResourceId();
     }
@@ -58,19 +61,7 @@ public class ScanIssueStorage extends IssueStorage implements BatchComponent {
 
   @Override
   protected long projectId(DefaultIssue issue) {
-    Snapshot snapshot = getSnapshot(issue);
-    if (snapshot != null) {
-      return snapshot.getRootProjectId();
-    }
-    throw new IllegalStateException("Project id not found for: " + issue.componentKey());
-  }
-
-  private Snapshot getSnapshot(DefaultIssue issue) {
-    Snapshot snapshot = snapshotCache.get(issue.componentKey());
-    if (snapshot != null) {
-      return snapshot;
-    }
-    return null;
+    return projectTree.getRootProject().getId();
   }
 
 }
index 1f557c64ad12c732be718d08c3c1c74e091555b2..90721058222c99a0aa063002cb9f876b4b186376 100644 (file)
@@ -23,6 +23,7 @@ import org.junit.Test;
 import org.sonar.api.component.Component;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.resources.Project;
 
 import java.util.Arrays;
 import java.util.List;
@@ -35,6 +36,7 @@ public class DefaultIssuableTest {
 
   ModuleIssues moduleIssues = mock(ModuleIssues.class);
   IssueCache cache = mock(IssueCache.class);
+  Project project = mock(Project.class);
   Component component = mock(Component.class);
 
   @Test
@@ -44,7 +46,7 @@ public class DefaultIssuableTest {
     DefaultIssue unresolved = new DefaultIssue();
     when(cache.byComponent("struts:org.apache.Action")).thenReturn(Arrays.asList(resolved, unresolved));
 
-    DefaultIssuable perspective = new DefaultIssuable(component, moduleIssues, cache);
+    DefaultIssuable perspective = new DefaultIssuable(component, project, moduleIssues, cache);
 
     List<Issue> issues = perspective.issues();
     assertThat(issues).containsOnly(unresolved);
@@ -57,7 +59,7 @@ public class DefaultIssuableTest {
     DefaultIssue unresolved = new DefaultIssue();
     when(cache.byComponent("struts:org.apache.Action")).thenReturn(Arrays.asList(resolved, unresolved));
 
-    DefaultIssuable perspective = new DefaultIssuable(component, moduleIssues, cache);
+    DefaultIssuable perspective = new DefaultIssuable(component, project, moduleIssues, cache);
 
     List<Issue> issues = perspective.resolvedIssues();
     assertThat(issues).containsOnly(resolved);
index d11d0a7211160ed6fb020aff10802e7b9d01e6d5..4753023ccbc64702cb541b1d2884ddac76ac59bc 100644 (file)
@@ -25,6 +25,7 @@ import org.sonar.api.component.Component;
 import org.sonar.api.issue.Issuable;
 import org.sonar.api.resources.File;
 import org.sonar.api.resources.Project;
+import org.sonar.batch.ProjectTree;
 import org.sonar.core.component.ResourceComponent;
 
 import static org.fest.assertions.Assertions.assertThat;
@@ -34,10 +35,11 @@ public class IssuableFactoryTest {
 
   ModuleIssues moduleIssues = mock(ModuleIssues.class);
   IssueCache cache = mock(IssueCache.class, Mockito.RETURNS_MOCKS);
+  ProjectTree projectTree = mock(ProjectTree.class);
 
   @Test
   public void file_should_be_issuable() throws Exception {
-    IssuableFactory factory = new IssuableFactory(moduleIssues, cache);
+    IssuableFactory factory = new IssuableFactory(moduleIssues, cache, projectTree);
     Component component = new ResourceComponent(new File("foo/bar.c").setEffectiveKey("foo/bar.c"));
     Issuable issuable = factory.loadPerspective(Issuable.class, component);
 
@@ -48,7 +50,7 @@ public class IssuableFactoryTest {
 
   @Test
   public void project_should_be_issuable() throws Exception {
-    IssuableFactory factory = new IssuableFactory(moduleIssues, cache);
+    IssuableFactory factory = new IssuableFactory(moduleIssues, cache, projectTree);
     Component component = new ResourceComponent(new Project("Foo").setEffectiveKey("foo"));
     Issuable issuable = factory.loadPerspective(Issuable.class, component);
 
index 3df87485b74fe1ff80d6eaa3b6c716a10d792c19..bacfe55be16fb3f0945700cf93f467cd403df25a 100644 (file)
@@ -73,6 +73,7 @@ public class ModuleIssuesTest {
   public void setUp() {
     when(project.getAnalysisDate()).thenReturn(new Date());
     when(project.getEffectiveKey()).thenReturn("org.apache:struts-core");
+    when(project.getRoot()).thenReturn(project);
   }
 
   @Test
@@ -225,6 +226,7 @@ public class ModuleIssuesTest {
     assertThat(issue.key()).isNotEmpty();
     assertThat(issue.ruleKey().toString()).isEqualTo("squid:AvoidCycle");
     assertThat(issue.componentKey()).isEqualTo("struts:src/org/struts/Action.java");
+    assertThat(issue.projectKey()).isEqualTo("org.apache:struts-core");
   }
 
   @Test
index 7875ae97f25bdddd194308a35fccbfcba85ed235..8866851b3da2cea818fa6678ca53cd53b9ffc0b0 100644 (file)
  */
 package org.sonar.batch.issue;
 
+import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
 import org.sonar.api.database.model.Snapshot;
 import org.sonar.api.issue.internal.DefaultIssue;
+import org.sonar.api.resources.Project;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.rules.Rule;
 import org.sonar.api.rules.RuleFinder;
 import org.sonar.api.rules.RuleQuery;
+import org.sonar.batch.ProjectTree;
 import org.sonar.batch.index.SnapshotCache;
 import org.sonar.core.persistence.AbstractDaoTestCase;
 import org.sonar.core.resource.ResourceDao;
@@ -34,16 +40,28 @@ import java.util.Collection;
 
 import static org.fest.assertions.Assertions.assertThat;
 import static org.fest.assertions.Fail.fail;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+@RunWith(MockitoJUnitRunner.class)
 public class ScanIssueStorageTest extends AbstractDaoTestCase {
+
+  @Mock
+  SnapshotCache snapshotCache;
+
+  @Mock
+  ProjectTree projectTree;
+
+  ScanIssueStorage storage;
+
+  @Before
+  public void setUp() throws Exception {
+    storage = new ScanIssueStorage(getMyBatis(), new FakeRuleFinder(), snapshotCache, new ResourceDao(getMyBatis()), projectTree);
+  }
+
   @Test
   public void should_load_component_id_from_cache() throws Exception {
-    SnapshotCache snapshotCache = mock(SnapshotCache.class);
     when(snapshotCache.get("struts:Action.java")).thenReturn(new Snapshot().setResourceId(123));
 
-    ScanIssueStorage storage = new ScanIssueStorage(getMyBatis(), new FakeRuleFinder(), snapshotCache, new ResourceDao(getMyBatis()));
     long componentId = storage.componentId(new DefaultIssue().setComponentKey("struts:Action.java"));
 
     assertThat(componentId).isEqualTo(123);
@@ -52,10 +70,8 @@ public class ScanIssueStorageTest extends AbstractDaoTestCase {
   @Test
   public void should_load_component_id_from_db() throws Exception {
     setupData("should_load_component_id_from_db");
-    SnapshotCache snapshotCache = mock(SnapshotCache.class);
     when(snapshotCache.get("struts:Action.java")).thenReturn(null);
 
-    ScanIssueStorage storage = new ScanIssueStorage(getMyBatis(), new FakeRuleFinder(), snapshotCache, new ResourceDao(getMyBatis()));
     long componentId = storage.componentId(new DefaultIssue().setComponentKey("struts:Action.java"));
 
     assertThat(componentId).isEqualTo(123);
@@ -64,10 +80,8 @@ public class ScanIssueStorageTest extends AbstractDaoTestCase {
   @Test
   public void should_fail_to_load_component_id_if_unknown_component() throws Exception {
     setupData("should_fail_to_load_component_id_if_unknown_component");
-    SnapshotCache snapshotCache = mock(SnapshotCache.class);
     when(snapshotCache.get("struts:Action.java")).thenReturn(null);
 
-    ScanIssueStorage storage = new ScanIssueStorage(getMyBatis(), new FakeRuleFinder(), snapshotCache, new ResourceDao(getMyBatis()));
     try {
       storage.componentId(new DefaultIssue().setComponentKey("struts:Action.java"));
       fail();
@@ -78,29 +92,13 @@ public class ScanIssueStorageTest extends AbstractDaoTestCase {
 
   @Test
   public void should_load_project_id() throws Exception {
-    SnapshotCache snapshotCache = mock(SnapshotCache.class);
-    when(snapshotCache.get("struts:Action.java")).thenReturn(new Snapshot().setResourceId(123).setRootProjectId(100));
+    when(projectTree.getRootProject()).thenReturn((Project) new Project("struts").setId(100));
 
-    ScanIssueStorage storage = new ScanIssueStorage(getMyBatis(), new FakeRuleFinder(), snapshotCache, new ResourceDao(getMyBatis()));
     long projectId = storage.projectId(new DefaultIssue().setComponentKey("struts:Action.java"));
 
     assertThat(projectId).isEqualTo(100);
   }
 
-  @Test
-  public void should_fail_to_load_project_id_if_unknown_component() throws Exception {
-    SnapshotCache snapshotCache = mock(SnapshotCache.class);
-    when(snapshotCache.get("struts:Action.java")).thenReturn(null);
-
-    ScanIssueStorage storage = new ScanIssueStorage(getMyBatis(), new FakeRuleFinder(), snapshotCache, new ResourceDao(getMyBatis()));
-    try {
-      storage.projectId(new DefaultIssue().setComponentKey("struts:Action.java"));
-      fail();
-    } catch (Exception e) {
-      assertThat(e).hasMessage("Project id not found for: struts:Action.java");
-    }
-  }
-
   static class FakeRuleFinder implements RuleFinder {
 
     @Override
index 68284cb07ce190b744a0eba8496d8656a2ce3ba7..859ad804ad234819b5ade20c97e8dd7bc4f360fe 100644 (file)
@@ -35,6 +35,7 @@ import java.util.UUID;
 public class DefaultIssueBuilder implements Issuable.IssueBuilder {
 
   private String componentKey;
+  private String projectKey;
   private RuleKey ruleKey;
   private Integer line;
   private String message;
@@ -52,6 +53,12 @@ public class DefaultIssueBuilder implements Issuable.IssueBuilder {
     return this;
   }
 
+
+  public DefaultIssueBuilder projectKey(String projectKey) {
+    this.projectKey = projectKey;
+    return this;
+  }
+
   @Override
   public Issuable.IssueBuilder ruleKey(RuleKey ruleKey) {
     this.ruleKey = ruleKey;
@@ -99,6 +106,7 @@ public class DefaultIssueBuilder implements Issuable.IssueBuilder {
 
   @Override
   public DefaultIssue build() {
+    Preconditions.checkNotNull(projectKey, "Project key must be set");
     Preconditions.checkNotNull(componentKey, "Component key must be set");
     Preconditions.checkNotNull(ruleKey, "Rule key must be set");
 
@@ -107,6 +115,7 @@ public class DefaultIssueBuilder implements Issuable.IssueBuilder {
     Preconditions.checkState(!Strings.isNullOrEmpty(key), "Fail to generate issue key");
     issue.setKey(key);
     issue.setComponentKey(componentKey);
+    issue.setProjectKey(projectKey);
     issue.setRuleKey(ruleKey);
     issue.setMessage(message);
     issue.setSeverity(severity);
index cdd3717f699aa91bbc0a19ed97e7643d88a88d11..9ac381eeffddf25601252f106aa0e7082f7609a2 100644 (file)
@@ -247,4 +247,20 @@ public class IssueUpdater implements BatchComponent, ServerComponent {
     return false;
   }
 
+  public boolean setProject(DefaultIssue issue, String key, IssueChangeContext context) {
+    if (!Objects.equal(key, issue.projectKey())) {
+      issue.setProjectKey(key);
+      issue.setUpdateDate(context.date());
+      issue.setChanged(true);
+      return true;
+    }
+    return false;
+  }
+
+  public boolean setPastProject(DefaultIssue issue, String previousKey, IssueChangeContext context) {
+    String currentProjectKey = issue.projectKey();
+    issue.setProjectKey(previousKey);
+    return setProject(issue, currentProjectKey, context);
+  }
+
 }
index 3b7aa30ccc490e61c45eee61180b85e24c120141..be0d275f1cd00981152ce2df66a6f9ed737514a4 100644 (file)
@@ -390,7 +390,7 @@ public final class IssueDto implements Serializable {
       .setUpdatedAt(now);
   }
 
-  public static IssueDto toDtoForUpdate(DefaultIssue issue, Date now) {
+  public static IssueDto toDtoForUpdate(DefaultIssue issue, Long rootComponentId, Date now) {
     // Invariant fields, like key and rule, can't be updated
     return new IssueDto()
       .setKee(issue.key())
@@ -408,6 +408,7 @@ public final class IssueDto implements Serializable {
       .setActionPlanKey(issue.actionPlanKey())
       .setIssueAttributes(KeyValueFormat.format(issue.attributes()))
       .setAuthorLogin(issue.authorLogin())
+      .setRootComponentId(rootComponentId)
       .setIssueCreationDate(issue.creationDate())
       .setIssueCloseDate(issue.closeDate())
       .setIssueUpdateDate(issue.updateDate())
index 3c3a878489130381d9daa4d0bc08a2cd68b5ab96..600b5e4f8c2e0dab00d605a4df6b2c01944e2e32 100644 (file)
@@ -31,8 +31,6 @@ public interface IssueMapper {
 
   IssueDto selectByKey(String key);
 
-  List<IssueDto> selectNonClosedIssuesByModule(int rootComponentId);
-
   /**
    * Return a paginated list of authorized issue ids for a user.
    * If the role is null, then the authorisation check is disabled.
index fad9862d7bec7519f05fde6e0a6add1992576584..a52f728a5dfc0154ff23df1144cf42a8f80cd01b 100644 (file)
@@ -119,7 +119,7 @@ public abstract class IssueStorage {
   }
 
   private void update(IssueMapper issueMapper, Date now, DefaultIssue issue) {
-    IssueDto dto = IssueDto.toDtoForUpdate(issue, now);
+    IssueDto dto = IssueDto.toDtoForUpdate(issue, projectId(issue), now);
     if (Issue.STATUS_CLOSED.equals(issue.status()) || issue.selectedAt() == null) {
       // Issue is closed by scan or changed by end-user
       issueMapper.update(dto);
index 536667cae3f8d00c998c1825d026b719b6f443e7..73370a7face4a717d06ab85d52ad44c0be1799c0 100644 (file)
@@ -41,7 +41,7 @@ class UpdateConflictResolver {
     IssueDto dbIssue = mapper.selectByKey(issue.key());
     if (dbIssue != null) {
       mergeFields(dbIssue, issue);
-      mapper.update(IssueDto.toDtoForUpdate(issue, new Date()));
+      mapper.update(IssueDto.toDtoForUpdate(issue, dbIssue.getRootComponentId(), new Date()));
     }
   }
 
index a3e153780414aef4d968e6158a0407995db82a4e..bd85afc93136c9d4ea01fa75a6cf60de827277ca 100644 (file)
@@ -89,6 +89,7 @@
     reporter=#{reporter},
     assignee=#{assignee},
     author_login=#{authorLogin},
+    root_component_id=#{rootComponentId},
     issue_attributes=#{issueAttributes},
     issue_creation_date=#{issueCreationDate},
     issue_update_date=#{issueUpdateDate},
     reporter=#{reporter},
     assignee=#{assignee},
     author_login=#{authorLogin},
+    root_component_id=#{rootComponentId},
     issue_attributes=#{issueAttributes},
     issue_creation_date=#{issueCreationDate},
     issue_update_date=#{issueUpdateDate},
       i.updated_at as updatedAt,
       r.plugin_rule_key as ruleKey,
       r.plugin_name as ruleRepo,
-      p.kee as componentKey
+      p.kee as componentKey,
+      root.kee as rootComponentKey
     from issues i
-    inner join rules r on r.id=i.rule_id
     inner join (select p.id,p.kee from projects p where (p.root_id=#{id} and p.qualifier &lt;&gt; 'BRC') or (p.id=#{id})) p on p.id=i.component_id
+    inner join rules r on r.id=i.rule_id
+    left outer join projects root on root.id=i.root_component_id
     <where>
       and i.status &lt;&gt; 'CLOSED'
     </where>
index 916be3ea6e1dde892fd6dd66dca910655296941e..30b34609c003a3e0c7d1a71f0937f6aaf0507493 100644 (file)
@@ -32,8 +32,10 @@ public class DefaultIssueBuilderTest {
   @Test
   public void build_new_issue() throws Exception {
     String componentKey = "org.apache.struts:struts-core:Action.java";
+    String projectKey = "org.apache.struts";
     DefaultIssue issue = (DefaultIssue) new DefaultIssueBuilder()
       .componentKey(componentKey)
+      .projectKey(projectKey)
       .message("the message")
       .line(123)
       .effortToFix(10000.0)
@@ -47,6 +49,7 @@ public class DefaultIssueBuilderTest {
     assertThat(issue.key()).isNotNull();
     assertThat(issue.effortToFix()).isEqualTo(10000.0);
     assertThat(issue.componentKey()).isEqualTo(componentKey);
+    assertThat(issue.projectKey()).isEqualTo(projectKey);
     assertThat(issue.message()).isEqualTo("the message");
     assertThat(issue.line()).isEqualTo(123);
     assertThat(issue.ruleKey().repository()).isEqualTo("squid");
@@ -65,6 +68,7 @@ public class DefaultIssueBuilderTest {
   public void not_set_default_severity() {
     DefaultIssue issue = (DefaultIssue) new DefaultIssueBuilder()
       .componentKey("Action.java")
+      .projectKey("org.apache.struts")
       .ruleKey(RuleKey.of("squid", "NullDereference"))
       .build();
 
index c0dfa62ec32703d8790cd70b1fe2d5f7deb08127..5373ebef6ebc138970039bd6c04a4a99f1659d24 100644 (file)
@@ -447,4 +447,35 @@ public class IssueUpdaterTest {
     assertThat(issue.mustSendNotifications()).isFalse();
   }
 
+  @Test
+  public void set_project() throws Exception {
+    boolean updated = updater.setProject(issue, "sample", context);
+    assertThat(updated).isTrue();
+    assertThat(issue.projectKey()).isEqualTo("sample");
+
+    // do not save change
+    assertThat(issue.currentChange()).isNull();
+    assertThat(issue.mustSendNotifications()).isFalse();
+  }
+
+  @Test
+  public void set_past_project() throws Exception {
+    issue.setProjectKey("new project key");
+    boolean updated = updater.setPastProject(issue, "past project key", context);
+    assertThat(updated).isTrue();
+    assertThat(issue.projectKey()).isEqualTo("new project key");
+
+    // do not save change
+    assertThat(issue.currentChange()).isNull();
+    assertThat(issue.mustSendNotifications()).isFalse();
+  }
+
+  @Test
+  public void not_set_past_project_if_no_change() throws Exception {
+    issue.setProjectKey("key");
+    boolean updated = updater.setPastProject(issue, "key", context);
+    assertThat(updated).isFalse();
+    assertThat(issue.projectKey()).isEqualTo("key");
+  }
+
 }
index a8087a3d4d8bbeff6cfe3ad4e7de328fb54a8fc1..45790762a26466bd376eed3cad04b9b8d9b5cd39 100644 (file)
@@ -310,7 +310,7 @@ public class IssueDaoTest extends AbstractDaoTestCase {
   }
 
   @Test
-  public void should_select_non_closed_issues_by_module() {
+  public void select_non_closed_issues_by_module() {
     setupData("shared", "should_select_non_closed_issues_by_module");
 
     // 400 is a non-root module, we should find 2 issues from classes and one on itself
@@ -322,11 +322,37 @@ public class IssueDaoTest extends AbstractDaoTestCase {
     assertThat(issue.getRuleRepo()).isNotNull();
     assertThat(issue.getRule()).isNotNull();
     assertThat(issue.getComponentKey()).isNotNull();
+    assertThat(issue.getRootComponentKey()).isEqualTo("struts");
 
     // 399 is the root module, we should only find 1 issue on itself
     handler = new DefaultResultHandler();
     dao.selectNonClosedIssuesByModule(399, handler);
     assertThat(handler.getResultList()).hasSize(1);
+
+    issue = (IssueDto) handler.getResultList().get(0);
+    assertThat(issue.getComponentKey()).isEqualTo("struts");
+    assertThat(issue.getRootComponentKey()).isEqualTo("struts");
+  }
+
+  /**
+   * SONAR-5218
+   */
+  @Test
+  public void select_non_closed_issues_by_module_on_removed_project() {
+    // All issues are linked on a project that is not existing anymore
+
+    setupData("shared", "should_select_non_closed_issues_by_module_on_removed_project");
+
+    // 400 is a non-root module, we should find 2 issues from classes and one on itself
+    DefaultResultHandler handler = new DefaultResultHandler();
+    dao.selectNonClosedIssuesByModule(400, handler);
+    assertThat(handler.getResultList()).hasSize(3);
+
+    IssueDto issue = (IssueDto) handler.getResultList().get(0);
+    assertThat(issue.getRuleRepo()).isNotNull();
+    assertThat(issue.getRule()).isNotNull();
+    assertThat(issue.getComponentKey()).isNotNull();
+    assertThat(issue.getRootComponentKey()).isNull();
   }
 
   @Test
index e3e074562ee2ac98fc2529cb54e8bbe0781a966c..705c5358ea81df4912d448a591fcb729a9414d09 100644 (file)
@@ -84,7 +84,7 @@ public class IssueMapperTest extends AbstractDaoTestCase {
 
     IssueDto dto = new IssueDto();
     dto.setComponentId(123l);
-    dto.setRootComponentId(100l);
+    dto.setRootComponentId(101l);
     dto.setRuleId(200);
     dto.setKee("ABCDE");
     dto.setLine(500);
@@ -119,7 +119,7 @@ public class IssueMapperTest extends AbstractDaoTestCase {
 
     IssueDto dto = new IssueDto();
     dto.setComponentId(123l);
-    dto.setRootComponentId(100l);
+    dto.setRootComponentId(101l);
     dto.setRuleId(200);
     dto.setKee("ABCDE");
     dto.setLine(500);
@@ -157,7 +157,7 @@ public class IssueMapperTest extends AbstractDaoTestCase {
 
     IssueDto dto = new IssueDto();
     dto.setComponentId(123l);
-    dto.setRootComponentId(100l);
+    dto.setRootComponentId(101l);
     dto.setRuleId(200);
     dto.setKee("ABCDE");
     dto.setLine(500);
diff --git a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueDaoTest/should_select_non_closed_issues_by_module_on_removed_project.xml b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueDaoTest/should_select_non_closed_issues_by_module_on_removed_project.xml
new file mode 100644 (file)
index 0000000..bb4aaf5
--- /dev/null
@@ -0,0 +1,127 @@
+<!--
+  ~ Sonar, open source software quality management tool.
+  ~ Copyright (C) 2008-2012 SonarSource
+  ~ mailto:contact AT sonarsource DOT com
+  ~
+  ~ Sonar is free software; you can redistribute it and/or
+  ~ modify it under the terms of the GNU Lesser General Public
+  ~ License as published by the Free Software Foundation; either
+  ~ version 3 of the License, or (at your option) any later version.
+  ~
+  ~ Sonar is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  ~ Lesser General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU Lesser General Public
+  ~ License along with Sonar; if not, write to the Free Software
+  ~ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+  -->
+
+<dataset>
+
+  <!-- Open Issue on a file -->
+  <issues
+      id="100"
+      kee="100"
+      component_id="401"
+      root_component_id="111"
+      rule_id="500"
+      severity="BLOCKER"
+      manual_severity="[false]"
+      message="[null]"
+      line="200"
+      effort_to_fix="[null]"
+      status="OPEN"
+      resolution="[null]"
+      checksum="[null]"
+      reporter="user"
+      assignee="user"
+      author_login="[null]"
+      issue_attributes="[null]"
+      issue_creation_date="2013-04-16"
+      issue_update_date="2013-04-16"
+      issue_close_date="2013-04-16"
+      created_at="2013-04-16"
+      updated_at="[null]"
+      />
+
+  <!-- Open Issue on a file -->
+  <issues
+      id="101"
+      kee="101"
+      component_id="402"
+      root_component_id="111"
+      rule_id="501"
+      severity="MAJOR"
+      manual_severity="[false]"
+      message="[null]"
+      line="120"
+      effort_to_fix="[null]"
+      status="OPEN"
+      resolution="[null]"
+      checksum="[null]"
+      reporter="[null]"
+      assignee="user"
+      author_login="[null]"
+      issue_attributes="[null]"
+      issue_creation_date="2013-04-16"
+      issue_update_date="2013-04-16"
+      issue_close_date="2013-04-16"
+      created_at="2013-04-10"
+      updated_at="[null]"
+      />
+
+  <!-- Closed Issue on a file -->
+  <issues
+      id="102"
+      kee="102"
+      component_id="402"
+      root_component_id="111"
+      rule_id="501"
+      severity="MAJOR"
+      manual_severity="[false]"
+      message="[null]"
+      line="120"
+      effort_to_fix="[null]"
+      status="CLOSED"
+      resolution="FIXED"
+      checksum="[null]"
+      reporter="[null]"
+      assignee="user"
+      author_login="[null]"
+      issue_attributes="[null]"
+      issue_creation_date="2013-04-16"
+      issue_update_date="2013-04-16"
+      issue_close_date="2013-04-16"
+      created_at="2013-04-10"
+      updated_at="[null]"
+      />
+
+  <!-- Open Issue on a sub module -->
+  <issues
+      id="103"
+      kee="103"
+      component_id="400"
+      root_component_id="111"
+      rule_id="501"
+      severity="MAJOR"
+      manual_severity="[false]"
+      message="[null]"
+      line="[null]"
+      effort_to_fix="[null]"
+      status="OPEN"
+      resolution="[null]"
+      checksum="[null]"
+      reporter="[null]"
+      assignee="user"
+      author_login="[null]"
+      issue_attributes="[null]"
+      issue_creation_date="2013-04-16"
+      issue_update_date="2013-04-16"
+      issue_close_date="2013-04-16"
+      created_at="2013-04-10"
+      updated_at="[null]"
+      />
+
+</dataset>
index 8016cfac576d926ec1f9b962ca6c63570ec43fc4..a923dfa54e0de4c9b31ae68d516c7e54dc089ee5 100644 (file)
@@ -3,7 +3,7 @@
       id="100"
       kee="ABCDE"
       component_id="123"
-      root_component_id="100"
+      root_component_id="101"
       rule_id="200"
       severity="BLOCKER"
       manual_severity="[false]"
index bef324ee95fc35f17b3869b158cb7fe5a5d54ca7..d0362da17d0746f85517b555c516ee62875567db 100644 (file)
@@ -21,7 +21,6 @@ package org.sonar.server.issue;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
-import com.google.common.base.Objects;
 import com.google.common.base.Predicate;
 import com.google.common.base.Strings;
 import com.google.common.collect.Iterables;
@@ -36,11 +35,9 @@ import org.sonar.api.issue.action.Action;
 import org.sonar.api.issue.internal.DefaultIssue;
 import org.sonar.api.issue.internal.FieldDiffs;
 import org.sonar.api.rule.RuleKey;
-import org.sonar.api.rule.Severity;
 import org.sonar.api.utils.SonarException;
 import org.sonar.core.issue.ActionPlanStats;
 import org.sonar.core.issue.DefaultActionPlan;
-import org.sonar.core.issue.DefaultIssueBuilder;
 import org.sonar.core.issue.DefaultIssueFilter;
 import org.sonar.core.issue.workflow.Transition;
 import org.sonar.core.resource.ResourceDao;
@@ -236,16 +233,8 @@ public class InternalRubyIssueService implements ServerComponent {
       }
 
       if (result.ok()) {
-        DefaultIssue issue = (DefaultIssue) new DefaultIssueBuilder()
-          .componentKey(componentKey)
-          .line(RubyUtils.toInteger(params.get("line")))
-          .message(params.get("message"))
-          .severity(Objects.firstNonNull(params.get("severity"), Severity.MAJOR))
-          .effortToFix(RubyUtils.toDouble(params.get("effortToFix")))
-          .ruleKey(ruleKey)
-          .reporter(UserSession.get().login())
-          .build();
-        issue = issueService.createManualIssue(issue, UserSession.get());
+        DefaultIssue issue = issueService.createManualIssue(componentKey, ruleKey, RubyUtils.toInteger(params.get("line")), params.get("message"), params.get("severity"),
+          RubyUtils.toDouble(params.get("effortToFix")), UserSession.get());
         result.set(issue);
       }
 
index db1664a919caf2c697f3438a76851423e7b85de2..7db9d49daca97da01dfc3ee4922b9088ab0abb4a 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.issue;
 
+import com.google.common.base.Objects;
 import com.google.common.base.Strings;
 import org.apache.commons.lang.StringUtils;
 import org.sonar.api.ServerComponent;
@@ -28,11 +29,14 @@ import org.sonar.api.issue.IssueQuery;
 import org.sonar.api.issue.IssueQueryResult;
 import org.sonar.api.issue.internal.DefaultIssue;
 import org.sonar.api.issue.internal.IssueChangeContext;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rule.Severity;
 import org.sonar.api.rules.Rule;
 import org.sonar.api.rules.RuleFinder;
 import org.sonar.api.user.User;
 import org.sonar.api.user.UserFinder;
 import org.sonar.api.web.UserRole;
+import org.sonar.core.issue.DefaultIssueBuilder;
 import org.sonar.core.issue.IssueNotifications;
 import org.sonar.core.issue.IssueUpdater;
 import org.sonar.core.issue.db.IssueStorage;
@@ -202,15 +206,26 @@ public class IssueService implements ServerComponent {
     return issue;
   }
 
-  public DefaultIssue createManualIssue(DefaultIssue issue, UserSession userSession) {
+  public DefaultIssue createManualIssue(String componentKey, RuleKey ruleKey, @Nullable Integer line, @Nullable String message, @Nullable String severity,
+                                        @Nullable Double effortToFix, UserSession userSession) {
     verifyLoggedIn(userSession);
-    ResourceDto resourceDto = resourceDao.getResource(ResourceQuery.create().setKey(issue.componentKey()));
-    if (resourceDto == null) {
-      throw new IllegalArgumentException("Unknown component: " + issue.componentKey());
+    ResourceDto resourceDto = resourceDao.getResource(ResourceQuery.create().setKey(componentKey));
+    ResourceDto project = resourceDao.getRootProjectByComponentKey(componentKey);
+    if (resourceDto == null || project == null) {
+      throw new IllegalArgumentException("Unknown component: " + componentKey);
     }
-    // Force use of correct key in case deprecated key is used
-    issue.setComponentKey(resourceDto.getKey());
-    issue.setComponentId(resourceDto.getId());
+
+    DefaultIssue issue = (DefaultIssue) new DefaultIssueBuilder()
+      .componentKey(resourceDto.getKey())
+      .projectKey(project.getKey())
+      .line(line)
+      .message(message)
+      .severity(Objects.firstNonNull(severity, Severity.MAJOR))
+      .effortToFix(effortToFix)
+      .ruleKey(ruleKey)
+      .reporter(UserSession.get().login())
+      .build();
+
     if (!authorizationDao.isAuthorizedComponentKey(resourceDto.getKey(), userSession.userId(), UserRole.USER)) {
       // TODO throw unauthorized
       throw new IllegalStateException("User does not have the required role");
index 7d27a4bb842c2d6c4816a1488bd69e54f6a0fed0..3f842e788f4788f5e82a0c9d4583f2109fc4612c 100644 (file)
@@ -22,7 +22,10 @@ package org.sonar.server.issue;
 
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
 import org.sonar.api.issue.ActionPlan;
 import org.sonar.api.issue.Issue;
 import org.sonar.api.issue.IssueQuery;
@@ -61,23 +64,56 @@ import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.*;
 
+@RunWith(MockitoJUnitRunner.class)
 public class IssueServiceTest {
 
-  private DefaultIssueFinder finder = mock(DefaultIssueFinder.class);
-  private IssueWorkflow workflow = mock(IssueWorkflow.class);
-  private IssueUpdater issueUpdater = mock(IssueUpdater.class);
-  private IssueStorage issueStorage = mock(IssueStorage.class);
-  private IssueNotifications issueNotifications = mock(IssueNotifications.class);
-  private ActionPlanService actionPlanService = mock(ActionPlanService.class);
-  private RuleFinder ruleFinder = mock(RuleFinder.class);
-  private ResourceDao resourceDao = mock(ResourceDao.class);
-  private AuthorizationDao authorizationDao = mock(AuthorizationDao.class);
-  private UserFinder userFinder = mock(UserFinder.class);
-  private UserSession userSession = mock(UserSession.class);
-  private Transition transition = Transition.create("reopen", Issue.STATUS_RESOLVED, Issue.STATUS_REOPENED);
-  private IssueQueryResult issueQueryResult = mock(IssueQueryResult.class);
-  private DefaultIssue issue = new DefaultIssue().setKey("ABCD");
-  private IssueService issueService;
+  @Mock
+  DefaultIssueFinder finder;
+
+  @Mock
+  IssueWorkflow workflow;
+
+  @Mock
+  IssueUpdater issueUpdater;
+
+  @Mock
+  IssueStorage issueStorage;
+
+  @Mock
+  IssueNotifications issueNotifications;
+
+  @Mock
+  ActionPlanService actionPlanService;
+
+  @Mock
+  RuleFinder ruleFinder;
+
+  @Mock
+  ResourceDao resourceDao;
+
+  @Mock
+  AuthorizationDao authorizationDao;
+
+  @Mock
+  UserFinder userFinder;
+
+  @Mock
+  UserSession userSession;
+
+  @Mock
+  IssueQueryResult issueQueryResult;
+
+  @Mock
+  ResourceDto resource;
+
+  @Mock
+  ResourceDto project;
+
+  Transition transition = Transition.create("reopen", Issue.STATUS_RESOLVED, Issue.STATUS_REOPENED);
+
+  DefaultIssue issue = new DefaultIssue().setKey("ABCD");
+
+  IssueService issueService;
 
   @Before
   public void before() {
@@ -90,18 +126,21 @@ public class IssueServiceTest {
     when(issueQueryResult.issues()).thenReturn(newArrayList((Issue) issue));
     when(issueQueryResult.first()).thenReturn(issue);
 
+    when(resource.getKey()).thenReturn("org.sonar.Sample");
+    when(project.getKey()).thenReturn("Sample");
+
     issueService = new IssueService(finder, workflow, issueStorage, issueUpdater, issueNotifications, actionPlanService, ruleFinder, resourceDao, authorizationDao, userFinder,
       mock(PreviewCache.class));
   }
 
   @Test
-  public void should_load_issue() {
+  public void load_issue() {
     IssueQueryResult result = issueService.loadIssue("ABCD");
     assertThat(result).isEqualTo(issueQueryResult);
   }
 
   @Test
-  public void should_fail_to_load_issue() {
+  public void fail_to_load_issue() {
     when(issueQueryResult.issues()).thenReturn(Collections.<Issue>emptyList());
     when(finder.find(any(IssueQuery.class))).thenReturn(issueQueryResult);
 
@@ -114,13 +153,13 @@ public class IssueServiceTest {
   }
 
   @Test
-  public void should_list_status() {
+  public void list_status() {
     issueService.listStatus();
     verify(workflow).statusKeys();
   }
 
   @Test
-  public void should_list_transitions() {
+  public void list_transitions() {
     List<Transition> transitions = newArrayList(transition);
     when(workflow.outTransitions(issue)).thenReturn(transitions);
 
@@ -130,7 +169,7 @@ public class IssueServiceTest {
   }
 
   @Test
-  public void should_return_no_transition() {
+  public void return_no_transition() {
     when(issueQueryResult.first()).thenReturn(null);
     when(issueQueryResult.issues()).thenReturn(newArrayList((Issue) new DefaultIssue()));
 
@@ -139,7 +178,7 @@ public class IssueServiceTest {
   }
 
   @Test
-  public void should_do_transition() {
+  public void do_transition() {
     when(workflow.doTransition(eq(issue), eq(transition.key()), any(IssueChangeContext.class))).thenReturn(true);
 
     Issue result = issueService.doTransition("ABCD", transition.key(), userSession);
@@ -157,7 +196,7 @@ public class IssueServiceTest {
   }
 
   @Test
-  public void should_not_do_transition() {
+  public void not_do_transition() {
     when(workflow.doTransition(eq(issue), eq(transition.key()), any(IssueChangeContext.class))).thenReturn(false);
 
     Issue result = issueService.doTransition("ABCD", transition.key(), userSession);
@@ -168,7 +207,7 @@ public class IssueServiceTest {
   }
 
   @Test
-  public void should_fail_do_transition_if_not_logged() {
+  public void fail_do_transition_if_not_logged() {
     when(userSession.isLoggedIn()).thenReturn(false);
     try {
       issueService.doTransition("ABCD", transition.key(), userSession);
@@ -180,7 +219,7 @@ public class IssueServiceTest {
   }
 
   @Test
-  public void should_assign() {
+  public void assign() {
     String assignee = "perceval";
     User user = new DefaultUser();
 
@@ -202,7 +241,7 @@ public class IssueServiceTest {
   }
 
   @Test
-  public void should_unassign() {
+  public void unassign() {
     when(issueUpdater.assign(eq(issue), eq((User) null), any(IssueChangeContext.class))).thenReturn(true);
 
     Issue result = issueService.assign("ABCD", null, userSession);
@@ -221,7 +260,7 @@ public class IssueServiceTest {
   }
 
   @Test
-  public void should_not_assign() {
+  public void not_assign() {
     String assignee = "perceval";
     User user = new DefaultUser();
 
@@ -237,7 +276,7 @@ public class IssueServiceTest {
   }
 
   @Test
-  public void should_fail_assign_if_assignee_not_found() {
+  public void fail_assign_if_assignee_not_found() {
     String assignee = "perceval";
 
     when(userFinder.findByLogin(assignee)).thenReturn(null);
@@ -255,7 +294,7 @@ public class IssueServiceTest {
   }
 
   @Test
-  public void should_plan() {
+  public void plan() {
     String actionPlanKey = "EFGH";
 
     ActionPlan actionPlan = new DefaultActionPlan();
@@ -278,7 +317,7 @@ public class IssueServiceTest {
   }
 
   @Test
-  public void should_unplan() {
+  public void unplan() {
     when(issueUpdater.plan(eq(issue), eq((ActionPlan) null), any(IssueChangeContext.class))).thenReturn(true);
 
     Issue result = issueService.plan("ABCD", null, userSession);
@@ -297,7 +336,7 @@ public class IssueServiceTest {
   }
 
   @Test
-  public void should_not_plan() {
+  public void not_plan() {
     String actionPlanKey = "EFGH";
 
     ActionPlan actionPlan = new DefaultActionPlan();
@@ -313,7 +352,7 @@ public class IssueServiceTest {
   }
 
   @Test
-  public void should_fail_plan_if_action_plan_not_found() {
+  public void fail_plan_if_action_plan_not_found() {
     String actionPlanKey = "EFGH";
 
     when(actionPlanService.findByKey(actionPlanKey, userSession)).thenReturn(null);
@@ -330,7 +369,7 @@ public class IssueServiceTest {
   }
 
   @Test
-  public void should_set_severity() {
+  public void set_severity() {
     String severity = "MINOR";
     when(issueUpdater.setManualSeverity(eq(issue), eq(severity), any(IssueChangeContext.class))).thenReturn(true);
 
@@ -349,7 +388,7 @@ public class IssueServiceTest {
   }
 
   @Test
-  public void should_not_set_severity() {
+  public void not_set_severity() {
     String severity = "MINOR";
     when(issueUpdater.setManualSeverity(eq(issue), eq(severity), any(IssueChangeContext.class))).thenReturn(false);
 
@@ -363,47 +402,47 @@ public class IssueServiceTest {
   @Test
   public void create_manual_issue() {
     RuleKey ruleKey = RuleKey.of("manual", "manualRuleKey");
-    DefaultIssue manualIssue = new DefaultIssue().setKey("GHIJ").setRuleKey(RuleKey.of("manual", "manualRuleKey")).setComponentKey("org.sonar.Sample").setMessage("Fix it");
     when(ruleFinder.findByKey(ruleKey)).thenReturn(Rule.create("manual", "manualRuleKey"));
-    when(resourceDao.getResource(any(ResourceQuery.class))).thenReturn(mock(ResourceDto.class));
+    when(resourceDao.getResource(any(ResourceQuery.class))).thenReturn(resource);
+    when(resourceDao.getRootProjectByComponentKey(anyString())).thenReturn(project);
 
-    Issue result = issueService.createManualIssue(manualIssue, userSession);
+    Issue result = issueService.createManualIssue("org.sonar.Sample", RuleKey.of("manual", "manualRuleKey"), null, "Fix it", null, null, userSession);
     assertThat(result).isNotNull();
     assertThat(result.message()).isEqualTo("Fix it");
     assertThat(result.creationDate()).isNotNull();
     assertThat(result.updateDate()).isNotNull();
 
-    verify(issueStorage).save(manualIssue);
+    verify(issueStorage).save(any(DefaultIssue.class));
     verify(authorizationDao).isAuthorizedComponentKey(anyString(), anyInt(), eq(UserRole.USER));
   }
 
   @Test
   public void create_manual_issue_use_rule_name_if_no_message() {
     RuleKey ruleKey = RuleKey.of("manual", "manualRuleKey");
-    DefaultIssue manualIssue = new DefaultIssue().setKey("GHIJ").setRuleKey(RuleKey.of("manual", "manualRuleKey")).setComponentKey("org.sonar.Sample").setMessage("");
     when(ruleFinder.findByKey(ruleKey)).thenReturn(Rule.create("manual", "manualRuleKey").setName("Manual Rule"));
-    when(resourceDao.getResource(any(ResourceQuery.class))).thenReturn(mock(ResourceDto.class));
+    when(resourceDao.getResource(any(ResourceQuery.class))).thenReturn(resource);
+    when(resourceDao.getRootProjectByComponentKey(anyString())).thenReturn(project);
 
-    Issue result = issueService.createManualIssue(manualIssue, userSession);
+    Issue result = issueService.createManualIssue("org.sonar.Sample", RuleKey.of("manual", "manualRuleKey"), null, "", null, null, userSession);
     assertThat(result).isNotNull();
     assertThat(result.message()).isEqualTo("Manual Rule");
     assertThat(result.creationDate()).isNotNull();
     assertThat(result.updateDate()).isNotNull();
 
-    verify(issueStorage).save(manualIssue);
+    verify(issueStorage).save(any(DefaultIssue.class));
     verify(authorizationDao).isAuthorizedComponentKey(anyString(), anyInt(), eq(UserRole.USER));
   }
 
   @Test
-  public void should_fail_create_manual_issue_if_not_having_required_role() {
+  public void fail_create_manual_issue_if_not_having_required_role() {
     RuleKey ruleKey = RuleKey.of("manual", "manualRuleKey");
-    when(resourceDao.getResource(any(ResourceQuery.class))).thenReturn(mock(ResourceDto.class));
+    when(resourceDao.getResource(any(ResourceQuery.class))).thenReturn(resource);
+    when(resourceDao.getRootProjectByComponentKey(anyString())).thenReturn(project);
     when(ruleFinder.findByKey(ruleKey)).thenReturn(Rule.create("manual", "manualRuleKey"));
     when(authorizationDao.isAuthorizedComponentKey(anyString(), eq(10), anyString())).thenReturn(false);
 
-    DefaultIssue manualIssue = new DefaultIssue().setKey("GHIJ").setRuleKey(ruleKey).setComponentKey("org.sonar.Sample");
     try {
-      issueService.createManualIssue(manualIssue, userSession);
+      issueService.createManualIssue("org.sonar.Sample", ruleKey, null, null, null, null, userSession);
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("User does not have the required role");
@@ -411,13 +450,13 @@ public class IssueServiceTest {
   }
 
   @Test
-  public void should_fail_create_manual_issue_if_not_manual_rule() {
-    when(resourceDao.getResource(any(ResourceQuery.class))).thenReturn(mock(ResourceDto.class));
+  public void fail_create_manual_issue_if_not_manual_rule() {
+    when(resourceDao.getResource(any(ResourceQuery.class))).thenReturn(resource);
+    when(resourceDao.getRootProjectByComponentKey(anyString())).thenReturn(project);
 
     RuleKey ruleKey = RuleKey.of("squid", "s100");
-    DefaultIssue manualIssue = new DefaultIssue().setKey("GHIJ").setRuleKey(ruleKey).setComponentKey("org.sonar.Sample");
     try {
-      issueService.createManualIssue(manualIssue, userSession);
+      issueService.createManualIssue("org.sonar.Sample", ruleKey, null, null, null, null, userSession);
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("Issues can be created only on rules marked as 'manual': squid:s100");
@@ -426,14 +465,14 @@ public class IssueServiceTest {
   }
 
   @Test
-  public void should_fail_create_manual_issue_if_rule_not_found() {
-    when(resourceDao.getResource(any(ResourceQuery.class))).thenReturn(mock(ResourceDto.class));
+  public void fail_create_manual_issue_if_rule_not_found() {
+    when(resourceDao.getResource(any(ResourceQuery.class))).thenReturn(resource);
+    when(resourceDao.getRootProjectByComponentKey(anyString())).thenReturn(project);
 
     RuleKey ruleKey = RuleKey.of("manual", "manualRuleKey");
-    DefaultIssue manualIssue = new DefaultIssue().setKey("GHIJ").setRuleKey(RuleKey.of("manual", "manualRuleKey")).setComponentKey("org.sonar.Sample");
     when(ruleFinder.findByKey(ruleKey)).thenReturn(null);
     try {
-      issueService.createManualIssue(manualIssue, userSession);
+      issueService.createManualIssue("org.sonar.Sample", RuleKey.of("manual", "manualRuleKey"), null, null, null, null, userSession);
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("Unknown rule: manual:manualRuleKey");
@@ -442,13 +481,12 @@ public class IssueServiceTest {
   }
 
   @Test
-  public void should_fail_create_manual_issue_if_component_not_found() {
+  public void fail_create_manual_issue_if_component_not_found() {
     RuleKey ruleKey = RuleKey.of("manual", "manualRuleKey");
-    DefaultIssue manualIssue = new DefaultIssue().setKey("GHIJ").setRuleKey(RuleKey.of("manual", "manualRuleKey")).setComponentKey("org.sonar.Sample");
     when(ruleFinder.findByKey(ruleKey)).thenReturn(Rule.create("manual", "manualRuleKey"));
     when(resourceDao.getResource(any(ResourceQuery.class))).thenReturn(null);
     try {
-      issueService.createManualIssue(manualIssue, userSession);
+      issueService.createManualIssue("org.sonar.Sample", RuleKey.of("manual", "manualRuleKey"), null, null, null, null, userSession);
       fail();
     } catch (Exception e) {
       assertThat(e).isInstanceOf(IllegalArgumentException.class).hasMessage("Unknown component: org.sonar.Sample");