From: Julien Lancelot Date: Fri, 18 Apr 2014 12:06:42 +0000 (+0200) Subject: SONAR-5218 Once a module has been turned into a project, its issues are no more visib... X-Git-Tag: 4.3~6 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=6334cb7d7397e1455099611b3b1259ad050706e6;p=sonarqube.git SONAR-5218 Once a module has been turned into a project, its issues are no more visible in the UI --- diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTrackingDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTrackingDecorator.java index 65b2a5a31a6..1838dc5311e 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTrackingDecorator.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTrackingDecorator.java @@ -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); } } diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingDecoratorTest.java index 05aa5bdb2d7..121b473d2d8 100644 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingDecoratorTest.java +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingDecoratorTest.java @@ -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 diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/DefaultIssuable.java b/sonar-batch/src/main/java/org/sonar/batch/issue/DefaultIssuable.java index 62b44e7d69e..578bb2bb49e 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/issue/DefaultIssuable.java +++ b/sonar-batch/src/main/java/org/sonar/batch/issue/DefaultIssuable.java @@ -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 diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/IssuableFactory.java b/sonar-batch/src/main/java/org/sonar/batch/issue/IssuableFactory.java index 147ab7c7c15..7491bbb7814 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/issue/IssuableFactory.java +++ b/sonar-batch/src/main/java/org/sonar/batch/issue/IssuableFactory.java @@ -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 { 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 { 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; } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/ModuleIssues.java b/sonar-batch/src/main/java/org/sonar/batch/issue/ModuleIssues.java index c0a49d68671..6f46e2cc7a9 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/issue/ModuleIssues.java +++ b/sonar-batch/src/main/java/org/sonar/batch/issue/ModuleIssues.java @@ -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()) diff --git a/sonar-batch/src/main/java/org/sonar/batch/issue/ScanIssueStorage.java b/sonar-batch/src/main/java/org/sonar/batch/issue/ScanIssueStorage.java index 23dab80cde1..723082eb335 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/issue/ScanIssueStorage.java +++ b/sonar-batch/src/main/java/org/sonar/batch/issue/ScanIssueStorage.java @@ -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(); } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/DefaultIssuableTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/DefaultIssuableTest.java index 1f557c64ad1..90721058222 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/issue/DefaultIssuableTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/issue/DefaultIssuableTest.java @@ -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 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 issues = perspective.resolvedIssues(); assertThat(issues).containsOnly(resolved); diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/IssuableFactoryTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/IssuableFactoryTest.java index d11d0a72111..4753023ccbc 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/issue/IssuableFactoryTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/issue/IssuableFactoryTest.java @@ -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); diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/ModuleIssuesTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/ModuleIssuesTest.java index 3df87485b74..bacfe55be16 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/issue/ModuleIssuesTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/issue/ModuleIssuesTest.java @@ -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 diff --git a/sonar-batch/src/test/java/org/sonar/batch/issue/ScanIssueStorageTest.java b/sonar-batch/src/test/java/org/sonar/batch/issue/ScanIssueStorageTest.java index 7875ae97f25..8866851b3da 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/issue/ScanIssueStorageTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/issue/ScanIssueStorageTest.java @@ -19,13 +19,19 @@ */ 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 diff --git a/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueBuilder.java b/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueBuilder.java index 68284cb07ce..859ad804ad2 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueBuilder.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueBuilder.java @@ -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); diff --git a/sonar-core/src/main/java/org/sonar/core/issue/IssueUpdater.java b/sonar-core/src/main/java/org/sonar/core/issue/IssueUpdater.java index cdd3717f699..9ac381eeffd 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/IssueUpdater.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/IssueUpdater.java @@ -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); + } + } diff --git a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueDto.java b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueDto.java index 3b7aa30ccc4..be0d275f1cd 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueDto.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueDto.java @@ -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()) diff --git a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueMapper.java b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueMapper.java index 3c3a8784891..600b5e4f8c2 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueMapper.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueMapper.java @@ -31,8 +31,6 @@ public interface IssueMapper { IssueDto selectByKey(String key); - List 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. diff --git a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueStorage.java b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueStorage.java index fad9862d7be..a52f728a5df 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/db/IssueStorage.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/db/IssueStorage.java @@ -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); diff --git a/sonar-core/src/main/java/org/sonar/core/issue/db/UpdateConflictResolver.java b/sonar-core/src/main/java/org/sonar/core/issue/db/UpdateConflictResolver.java index 536667cae3f..73370a7face 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/db/UpdateConflictResolver.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/db/UpdateConflictResolver.java @@ -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())); } } diff --git a/sonar-core/src/main/resources/org/sonar/core/issue/db/IssueMapper.xml b/sonar-core/src/main/resources/org/sonar/core/issue/db/IssueMapper.xml index a3e15378041..bd85afc9313 100644 --- a/sonar-core/src/main/resources/org/sonar/core/issue/db/IssueMapper.xml +++ b/sonar-core/src/main/resources/org/sonar/core/issue/db/IssueMapper.xml @@ -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}, @@ -115,6 +116,7 @@ reporter=#{reporter}, assignee=#{assignee}, author_login=#{authorLogin}, + root_component_id=#{rootComponentId}, issue_attributes=#{issueAttributes}, issue_creation_date=#{issueCreationDate}, issue_update_date=#{issueUpdateDate}, @@ -163,10 +165,12 @@ 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 <> '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 and i.status <> 'CLOSED' diff --git a/sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueBuilderTest.java b/sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueBuilderTest.java index 916be3ea6e1..30b34609c00 100644 --- a/sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueBuilderTest.java +++ b/sonar-core/src/test/java/org/sonar/core/issue/DefaultIssueBuilderTest.java @@ -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(); diff --git a/sonar-core/src/test/java/org/sonar/core/issue/IssueUpdaterTest.java b/sonar-core/src/test/java/org/sonar/core/issue/IssueUpdaterTest.java index c0dfa62ec32..5373ebef6eb 100644 --- a/sonar-core/src/test/java/org/sonar/core/issue/IssueUpdaterTest.java +++ b/sonar-core/src/test/java/org/sonar/core/issue/IssueUpdaterTest.java @@ -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"); + } + } diff --git a/sonar-core/src/test/java/org/sonar/core/issue/db/IssueDaoTest.java b/sonar-core/src/test/java/org/sonar/core/issue/db/IssueDaoTest.java index a8087a3d4d8..45790762a26 100644 --- a/sonar-core/src/test/java/org/sonar/core/issue/db/IssueDaoTest.java +++ b/sonar-core/src/test/java/org/sonar/core/issue/db/IssueDaoTest.java @@ -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 diff --git a/sonar-core/src/test/java/org/sonar/core/issue/db/IssueMapperTest.java b/sonar-core/src/test/java/org/sonar/core/issue/db/IssueMapperTest.java index e3e074562ee..705c5358ea8 100644 --- a/sonar-core/src/test/java/org/sonar/core/issue/db/IssueMapperTest.java +++ b/sonar-core/src/test/java/org/sonar/core/issue/db/IssueMapperTest.java @@ -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 index 00000000000..bb4aaf5abbf --- /dev/null +++ b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueDaoTest/should_select_non_closed_issues_by_module_on_removed_project.xml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + diff --git a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueMapperTest/testUpdate-result.xml b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueMapperTest/testUpdate-result.xml index 8016cfac576..a923dfa54e0 100644 --- a/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueMapperTest/testUpdate-result.xml +++ b/sonar-core/src/test/resources/org/sonar/core/issue/db/IssueMapperTest/testUpdate-result.xml @@ -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]" diff --git a/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java b/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java index bef324ee95f..d0362da17d0 100644 --- a/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java +++ b/sonar-server/src/main/java/org/sonar/server/issue/InternalRubyIssueService.java @@ -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); } diff --git a/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java b/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java index db1664a919c..7db9d49daca 100644 --- a/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java +++ b/sonar-server/src/main/java/org/sonar/server/issue/IssueService.java @@ -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"); diff --git a/sonar-server/src/test/java/org/sonar/server/issue/IssueServiceTest.java b/sonar-server/src/test/java/org/sonar/server/issue/IssueServiceTest.java index 7d27a4bb842..3f842e788f4 100644 --- a/sonar-server/src/test/java/org/sonar/server/issue/IssueServiceTest.java +++ b/sonar-server/src/test/java/org/sonar/server/issue/IssueServiceTest.java @@ -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.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 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");