diff options
author | Duarte Meneses <duarte.meneses@sonarsource.com> | 2020-04-13 15:37:35 -0500 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2020-04-17 20:03:43 +0000 |
commit | b922a7648b5a6d4a26a659900ceb8644cbc5c10b (patch) | |
tree | a8d5088c04085eeff2a1776d560c82d5af0e14e9 | |
parent | 2ba726630087bb0baae61e05e1c20943ec9e6931 (diff) | |
download | sonarqube-b922a7648b5a6d4a26a659900ceb8644cbc5c10b.tar.gz sonarqube-b922a7648b5a6d4a26a659900ceb8644cbc5c10b.zip |
SONAR-13196 Search issues since leak period returns all issues if no leak period exists
10 files changed, 388 insertions, 67 deletions
diff --git a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java index d9b1ecadc96..58c00a5fa8d 100644 --- a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java +++ b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueIndex.java @@ -638,7 +638,7 @@ public class IssueIndex { } private void validateCreationDateBounds(@Nullable Date createdBefore, @Nullable Date createdAfter) { - Preconditions.checkArgument(createdAfter == null || createdAfter.before(new Date(system.now())), + Preconditions.checkArgument(createdAfter == null || createdAfter.compareTo(new Date(system.now())) <= 0, "Start bound cannot be in the future"); Preconditions.checkArgument(createdAfter == null || createdBefore == null || createdAfter.before(createdBefore), "Start bound cannot be larger or equal to end bound"); diff --git a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java index 7dd6221826f..d6c7f69d8c3 100644 --- a/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java +++ b/server/sonar-webserver-es/src/main/java/org/sonar/server/issue/index/IssueQueryFactory.java @@ -95,7 +95,7 @@ public class IssueQueryFactory { .map(Enum::name) .collect(MoreCollectors.toSet(RuleType.values().length - 1)); private static final ComponentDto UNKNOWN_COMPONENT = new ComponentDto().setUuid(UNKNOWN).setProjectUuid(UNKNOWN); - + private static final Set<String> QUALIFIERS_WITHOUT_LEAK_PERIOD = new HashSet<>(Arrays.asList(Qualifiers.APP, Qualifiers.VIEW, Qualifiers.SUBVIEW)); private final DbClient dbClient; private final Clock clock; private final UserSession userSession; @@ -145,8 +145,6 @@ public class IssueQueryFactory { } private void setCreatedAfterFromDates(IssueQuery.Builder builder, @Nullable Date createdAfter, @Nullable String createdInLast, boolean createdAfterInclusive) { - checkArgument(createdAfter == null || createdInLast == null, format("Parameters %s and %s cannot be set simultaneously", PARAM_CREATED_AFTER, PARAM_CREATED_IN_LAST)); - Date actualCreatedAfter = createdAfter; if (createdInLast != null) { actualCreatedAfter = Date.from( @@ -171,20 +169,26 @@ public class IssueQueryFactory { String createdInLast = request.getCreatedInLast(); if (request.getSinceLeakPeriod() == null || !request.getSinceLeakPeriod()) { + checkArgument(createdAfter == null || createdInLast == null, format("Parameters %s and %s cannot be set simultaneously", PARAM_CREATED_AFTER, PARAM_CREATED_IN_LAST)); setCreatedAfterFromDates(builder, createdAfter, createdInLast, true); } else { checkArgument(createdAfter == null, "Parameters '%s' and '%s' cannot be set simultaneously", PARAM_CREATED_AFTER, PARAM_SINCE_LEAK_PERIOD); + checkArgument(createdInLast == null, format("Parameters %s and %s cannot be set simultaneously", PARAM_CREATED_IN_LAST, PARAM_SINCE_LEAK_PERIOD)); + checkArgument(componentUuids.size() == 1, "One and only one component must be provided when searching since leak period"); ComponentDto component = componentUuids.iterator().next(); - Date createdAfterFromSnapshot = findCreatedAfterFromComponentUuid(dbSession, component); - setCreatedAfterFromDates(builder, createdAfterFromSnapshot, createdInLast, false); + + if (!QUALIFIERS_WITHOUT_LEAK_PERIOD.contains(component.qualifier()) && request.getPullRequest() == null) { + Date createdAfterFromSnapshot = findCreatedAfterFromComponentUuid(dbSession, component); + setCreatedAfterFromDates(builder, createdAfterFromSnapshot, null, false); + } } } - @CheckForNull private Date findCreatedAfterFromComponentUuid(DbSession dbSession, ComponentDto component) { Optional<SnapshotDto> snapshot = dbClient.snapshotDao().selectLastAnalysisByComponentUuid(dbSession, component.uuid()); - return snapshot.map(s -> longToDate(s.getPeriodDate())).orElse(null); + // if last analysis has no period date, then no issue should be considered new. + return snapshot.map(s -> longToDate(s.getPeriodDate())).orElseGet(() -> new Date(clock.millis())); } private boolean mergeDeprecatedComponentParameters(DbSession session, SearchRequest request, List<ComponentDto> allComponents) { @@ -319,7 +323,7 @@ public class IssueQueryFactory { } private void addCreatedAfterByProjects(IssueQuery.Builder builder, DbSession dbSession, SearchRequest request, Set<String> applicationUuids) { - if (request.getSinceLeakPeriod() == null || !request.getSinceLeakPeriod()) { + if (request.getSinceLeakPeriod() == null || !request.getSinceLeakPeriod() || request.getPullRequest() != null) { return; } @@ -331,6 +335,7 @@ public class IssueQueryFactory { .stream() .filter(s -> s.getPeriodDate() != null) .collect(uniqueIndex(SnapshotDto::getComponentUuid, s -> new PeriodStart(longToDate(s.getPeriodDate()), false))); + builder.createdAfterByProjectUuids(leakByProjects); } diff --git a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryFactoryTest.java b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryFactoryTest.java index 050eb283ae7..c86fb938571 100644 --- a/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryFactoryTest.java +++ b/server/sonar-webserver-es/src/test/java/org/sonar/server/issue/index/IssueQueryFactoryTest.java @@ -24,6 +24,7 @@ import java.time.ZoneOffset; import java.util.ArrayList; import java.util.Collections; import java.util.Date; +import java.util.Map; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -43,6 +44,7 @@ import org.sonar.server.tester.UserSessionRule; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -276,6 +278,7 @@ public class IssueQueryFactoryTest { @Test public void application_search_project_issues_on_leak() { Date now = new Date(); + when(clock.millis()).thenReturn(now.getTime()); ComponentDto project1 = db.components().insertPublicProject(); SnapshotDto analysis1 = db.components().insertSnapshot(project1, s -> s.setPeriodDate(addDays(now, -14).getTime())); ComponentDto project2 = db.components().insertPublicProject(); @@ -292,8 +295,8 @@ public class IssueQueryFactoryTest { .setSinceLeakPeriod(true)); assertThat(result.createdAfterByProjectUuids()).hasSize(1); - assertThat(result.createdAfterByProjectUuids().get(project1.uuid()).date().getTime()).isEqualTo(analysis1.getPeriodDate()); - assertThat(result.createdAfterByProjectUuids().get(project1.uuid()).inclusive()).isFalse(); + assertThat(result.createdAfterByProjectUuids().entrySet()).extracting(Map.Entry::getKey, e -> e.getValue().date(), e -> e.getValue().inclusive()).containsOnly( + tuple(project1.uuid(), new Date(analysis1.getPeriodDate()), false)); assertThat(result.viewUuids()).containsExactlyInAnyOrder(application.uuid()); } @@ -392,14 +395,14 @@ public class IssueQueryFactoryTest { assertThat(underTest.create(new SearchRequest() .setProjectKeys(singletonList(branch.getKey())) .setBranch(branch.getBranch()))) - .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch) - .containsOnly(branch.uuid(), singletonList(project.uuid()), false); + .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch) + .containsOnly(branch.uuid(), singletonList(project.uuid()), false); assertThat(underTest.create(new SearchRequest() .setComponentKeys(singletonList(branch.getKey())) .setBranch(branch.getBranch()))) - .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch) - .containsOnly(branch.uuid(), singletonList(project.uuid()), false); + .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch) + .containsOnly(branch.uuid(), singletonList(project.uuid()), false); } @Test @@ -411,22 +414,22 @@ public class IssueQueryFactoryTest { assertThat(underTest.create(new SearchRequest() .setComponentKeys(singletonList(file.getKey())) .setBranch(branch.getBranch()))) - .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.fileUuids()), IssueQuery::isMainBranch) - .containsOnly(branch.uuid(), singletonList(file.uuid()), false); + .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.fileUuids()), IssueQuery::isMainBranch) + .containsOnly(branch.uuid(), singletonList(file.uuid()), false); assertThat(underTest.create(new SearchRequest() .setComponentKeys(singletonList(branch.getKey())) .setFileUuids(singletonList(file.uuid())) .setBranch(branch.getBranch()))) - .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.fileUuids()), IssueQuery::isMainBranch) - .containsOnly(branch.uuid(), singletonList(file.uuid()), false); + .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.fileUuids()), IssueQuery::isMainBranch) + .containsOnly(branch.uuid(), singletonList(file.uuid()), false); assertThat(underTest.create(new SearchRequest() .setProjectKeys(singletonList(branch.getKey())) .setFileUuids(singletonList(file.uuid())) .setBranch(branch.getBranch()))) - .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.fileUuids()), IssueQuery::isMainBranch) - .containsOnly(branch.uuid(), singletonList(file.uuid()), false); + .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.fileUuids()), IssueQuery::isMainBranch) + .containsOnly(branch.uuid(), singletonList(file.uuid()), false); } @Test @@ -439,8 +442,8 @@ public class IssueQueryFactoryTest { .setComponentKeys(singletonList(file.getKey())) .setBranch(branch.getBranch()) .setOnComponentOnly(true))) - .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.componentUuids()), IssueQuery::isMainBranch) - .containsOnly(branch.uuid(), singletonList(file.uuid()), false); + .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.componentUuids()), IssueQuery::isMainBranch) + .containsOnly(branch.uuid(), singletonList(file.uuid()), false); } @Test @@ -451,13 +454,13 @@ public class IssueQueryFactoryTest { assertThat(underTest.create(new SearchRequest() .setProjectKeys(singletonList(project.getKey())) .setBranch("master"))) - .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch) - .containsOnly(project.uuid(), singletonList(project.uuid()), true); + .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch) + .containsOnly(project.uuid(), singletonList(project.uuid()), true); assertThat(underTest.create(new SearchRequest() .setComponentKeys(singletonList(project.getKey())) .setBranch("master"))) - .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch) - .containsOnly(project.uuid(), singletonList(project.uuid()), true); + .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch) + .containsOnly(project.uuid(), singletonList(project.uuid()), true); } @Test @@ -492,16 +495,16 @@ public class IssueQueryFactoryTest { assertThat(underTest.create(new SearchRequest() .setComponentKeys(singletonList(applicationBranch1.getKey())) .setBranch(applicationBranch1.getBranch()))) - .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch) - .containsOnly(applicationBranch1.uuid(), Collections.emptyList(), false); + .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch) + .containsOnly(applicationBranch1.uuid(), Collections.emptyList(), false); // Search on project1Branch1 assertThat(underTest.create(new SearchRequest() .setComponentKeys(singletonList(applicationBranch1.getKey())) .setProjectKeys(singletonList(project1.getKey())) .setBranch(applicationBranch1.getBranch()))) - .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch) - .containsOnly(applicationBranch1.uuid(), singletonList(project1.uuid()), false); + .extracting(IssueQuery::branchUuid, query -> new ArrayList<>(query.projectUuids()), IssueQuery::isMainBranch) + .containsOnly(applicationBranch1.uuid(), singletonList(project1.uuid()), false); } @Test diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/SearchAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/SearchAction.java index 3676f4abfa4..1a18e72dcde 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/SearchAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/SearchAction.java @@ -24,6 +24,7 @@ import com.google.common.collect.ImmutableSet; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -44,6 +45,7 @@ import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.Paging; +import org.sonar.api.utils.System2; import org.sonar.api.web.UserRole; import org.sonar.core.util.stream.MoreCollectors; import org.sonar.db.DbClient; @@ -101,13 +103,14 @@ public class SearchAction implements HotspotsWsAction { private final UserSession userSession; private final IssueIndex issueIndex; private final HotspotWsResponseFormatter responseFormatter; + private System2 system2; - public SearchAction(DbClient dbClient, UserSession userSession, IssueIndex issueIndex, - HotspotWsResponseFormatter responseFormatter) { + public SearchAction(DbClient dbClient, UserSession userSession, IssueIndex issueIndex, HotspotWsResponseFormatter responseFormatter, System2 system2) { this.dbClient = dbClient; this.userSession = userSession; this.issueIndex = issueIndex; this.responseFormatter = responseFormatter; + this.system2 = system2; } @Override @@ -292,10 +295,11 @@ public class SearchAction implements HotspotsWsAction { builder.mainBranch(false); } - if (wsRequest.isSinceLeakPeriod()) { - dbClient.snapshotDao().selectLastAnalysisByComponentUuid(dbSession, p.uuid()) + if (wsRequest.isSinceLeakPeriod() && !wsRequest.getPullRequest().isPresent()) { + Date sinceDate = dbClient.snapshotDao().selectLastAnalysisByComponentUuid(dbSession, p.uuid()) .map(s -> longToDate(s.getPeriodDate())) - .ifPresent(d -> builder.createdAfter(d, false)); + .orElseGet(() -> new Date(system2.now())); + builder.createdAfter(sinceDate, false); } }); if (!hotspotKeys.isEmpty()) { diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java index 83a3ed8dc03..36e261558a8 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchAction.java @@ -307,7 +307,7 @@ public class SearchAction implements IssuesWsAction { .setExampleValue("1m2w (1 month 2 weeks)"); action.createParam(PARAM_SINCE_LEAK_PERIOD) .setDescription("To retrieve issues created since the leak period.<br>" + - "If this parameter is set to a truthy value, createdAfter must not be set and one component id or key must be provided.") + "If this parameter is set to a truthy value, createdAfter must not be set and one component uuid or key must be provided.") .setBooleanPossibleValues() .setDefaultValue("false"); } diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/SearchActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/SearchActionTest.java index dfa747882ce..c4f2c6a6cc5 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/SearchActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/SearchActionTest.java @@ -40,6 +40,7 @@ import javax.annotation.Nullable; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.sonar.api.impl.utils.TestSystem2; import org.sonar.api.issue.Issue; import org.sonar.api.rules.RuleType; import org.sonar.api.utils.System2; @@ -50,7 +51,6 @@ import org.sonar.db.component.BranchType; import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ComponentTesting; import org.sonar.db.issue.IssueDto; -import org.sonar.db.organization.OrganizationDto; import org.sonar.db.rule.RuleDefinitionDto; import org.sonar.db.rule.RuleTesting; import org.sonar.server.es.EsTester; @@ -104,6 +104,7 @@ public class SearchActionTest { @Rule public UserSessionRule userSessionRule = UserSessionRule.standalone(); + private TestSystem2 system2 = new TestSystem2(); private DbClient dbClient = dbTester.getDbClient(); private TestDefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(dbTester); @@ -113,7 +114,7 @@ public class SearchActionTest { private PermissionIndexer permissionIndexer = new PermissionIndexer(dbClient, es.client(), issueIndexer); private HotspotWsResponseFormatter responseFormatter = new HotspotWsResponseFormatter(defaultOrganizationProvider); - private SearchAction underTest = new SearchAction(dbClient, userSessionRule, issueIndex, responseFormatter); + private SearchAction underTest = new SearchAction(dbClient, userSessionRule, issueIndex, responseFormatter, system2); private WsActionTester actionTester = new WsActionTester(underTest); @Test @@ -724,8 +725,8 @@ public class SearchActionTest { .setParam("hotspots", IntStream.range(2, 10).mapToObj(String::valueOf).collect(joining(","))) .setParam("onlyMine", "true") .execute()) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("Parameter 'onlyMine' can be used with parameter 'projectKey' only"); + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Parameter 'onlyMine' can be used with parameter 'projectKey' only"); } @Test @@ -738,8 +739,8 @@ public class SearchActionTest { .setParam("projectKey", project.getKey()) .setParam("onlyMine", "true") .execute()) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("Parameter 'onlyMine' requires user to be logged in"); + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Parameter 'onlyMine' requires user to be logged in"); } @Test @@ -1340,7 +1341,7 @@ public class SearchActionTest { SearchWsResponse responseOnLeak = newRequest(project, t -> t.setParam("sinceLeakPeriod", "true")) - .executeProtobuf(SearchWsResponse.class); + .executeProtobuf(SearchWsResponse.class); assertThat(responseOnLeak.getHotspotsList()) .extracting(SearchWsResponse.Hotspot::getKey) .containsExactlyInAnyOrder(Stream.concat( @@ -1351,6 +1352,69 @@ public class SearchActionTest { } @Test + public void returns_nothing_when_sinceLeakPeriod_is_true_and_no_period_exists() { + long referenceDate = 800_996_999_332L; + + system2.setNow(referenceDate + 10_000); + ComponentDto project = dbTester.components().insertPublicProject(); + userSessionRule.registerComponents(project); + indexPermissions(); + ComponentDto file = dbTester.components().insertComponent(newFileDto(project)); + dbTester.components().insertSnapshot(project, t -> t.setPeriodDate(referenceDate).setLast(false)); + dbTester.components().insertSnapshot(project, t -> t.setPeriodDate(null).setLast(true)); + RuleDefinitionDto rule = newRule(SECURITY_HOTSPOT); + IssueDto afterRef = dbTester.issues().insertHotspot(rule, project, file, t -> t.setIssueCreationTime(referenceDate + 1000)); + IssueDto atRef = dbTester.issues().insertHotspot(rule, project, file, t -> t.setType(SECURITY_HOTSPOT).setIssueCreationTime(referenceDate)); + IssueDto beforeRef = dbTester.issues().insertHotspot(rule, project, file, t -> t.setIssueCreationTime(referenceDate - 1000)); + indexIssues(); + + SearchWsResponse responseAll = newRequest(project) + .executeProtobuf(SearchWsResponse.class); + assertThat(responseAll.getHotspotsList()) + .extracting(SearchWsResponse.Hotspot::getKey) + .containsExactlyInAnyOrder(Stream.of(afterRef, atRef, beforeRef) + .map(IssueDto::getKey) + .toArray(String[]::new)); + + SearchWsResponse responseOnLeak = newRequest(project, + t -> t.setParam("sinceLeakPeriod", "true")) + .executeProtobuf(SearchWsResponse.class); + assertThat(responseOnLeak.getHotspotsList()).isEmpty(); + } + + @Test + public void returnsall_issues_when_sinceLeakPeriod_is_true_and_is_pr() { + long referenceDate = 800_996_999_332L; + + system2.setNow(referenceDate + 10_000); + ComponentDto project = dbTester.components().insertPublicProject(); + ComponentDto pr = dbTester.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.PULL_REQUEST).setKey("pr")); + userSessionRule.registerComponents(project); + indexPermissions(); + ComponentDto file = dbTester.components().insertComponent(newFileDto(pr)); + dbTester.components().insertSnapshot(project, t -> t.setPeriodDate(referenceDate).setLast(true)); + dbTester.components().insertSnapshot(pr, t -> t.setPeriodDate(null).setLast(true)); + RuleDefinitionDto rule = newRule(SECURITY_HOTSPOT); + IssueDto afterRef = dbTester.issues().insertHotspot(rule, pr, file, t -> t.setIssueCreationTime(referenceDate + 1000)); + IssueDto atRef = dbTester.issues().insertHotspot(rule, pr, file, t -> t.setType(SECURITY_HOTSPOT).setIssueCreationTime(referenceDate)); + IssueDto beforeRef = dbTester.issues().insertHotspot(rule, pr, file, t -> t.setIssueCreationTime(referenceDate - 1000)); + indexIssues(); + + SearchWsResponse responseAll = newRequest(project).setParam("pullRequest", "pr") + .executeProtobuf(SearchWsResponse.class); + assertThat(responseAll.getHotspotsList()) + .extracting(SearchWsResponse.Hotspot::getKey) + .containsExactlyInAnyOrder(Stream.of(afterRef, atRef, beforeRef) + .map(IssueDto::getKey) + .toArray(String[]::new)); + + SearchWsResponse responseOnLeak = newRequest(project, + t -> t.setParam("sinceLeakPeriod", "true").setParam("pullRequest", "pr")) + .executeProtobuf(SearchWsResponse.class); + assertThat(responseOnLeak.getHotspotsList()).hasSize(3); + } + + @Test public void verify_response_example() { ComponentDto project = dbTester.components().insertPublicProject(componentDto -> componentDto .setName("test-project") @@ -1375,7 +1439,7 @@ public class SearchActionTest { return insertHotspot(rule, project, fileWithHotspot, issueDto -> issueDto.setKee("hotspot-" + i) .setAssigneeUuid("assignee-uuid") .setAuthorLogin("joe") - .setMessage("message-" +i) + .setMessage("message-" + i) .setLine(10 + i) .setIssueCreationTime(time) .setIssueUpdateTime(time) diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsTest.java index e61776b8b6a..1d6314dcb93 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionComponentsTest.java @@ -168,12 +168,12 @@ public class SearchActionComponentsTest { assertThat(ws.newRequest() .setParam(PARAM_COMPONENT_KEYS, module1.getKey()) .executeProtobuf(SearchWsResponse.class).getIssuesList()).extracting(Issue::getKey) - .containsExactlyInAnyOrder(issue1.getKey()); + .containsExactlyInAnyOrder(issue1.getKey()); assertThat(ws.newRequest() .setParam(PARAM_MODULE_UUIDS, module1.uuid()) .executeProtobuf(SearchWsResponse.class).getIssuesList()).extracting(Issue::getKey) - .containsExactlyInAnyOrder(issue1.getKey()); + .containsExactlyInAnyOrder(issue1.getKey()); } @Test @@ -484,11 +484,11 @@ public class SearchActionComponentsTest { .setParam(PARAM_COMPONENT_KEYS, applicationBranch1.getKey()) .setParam(PARAM_BRANCH, applicationBranch1.getBranch()) .executeProtobuf(SearchWsResponse.class).getIssuesList()) - .extracting(Issue::getKey, Issue::getComponent, Issue::getProject, Issue::getBranch, Issue::hasBranch) - .containsExactlyInAnyOrder( - tuple(issueOnProject1Branch1.getKey(), project1Branch1.getKey(), project1Branch1.getKey(), project1Branch1.getBranch(), true), - tuple(issueOnFileOnProject1Branch1.getKey(), fileOnProject1Branch1.getKey(), project1Branch1.getKey(), project1Branch1.getBranch(), true), - tuple(issueOnProject2.getKey(), project2.getKey(), project2.getKey(), "", false)); + .extracting(Issue::getKey, Issue::getComponent, Issue::getProject, Issue::getBranch, Issue::hasBranch) + .containsExactlyInAnyOrder( + tuple(issueOnProject1Branch1.getKey(), project1Branch1.getKey(), project1Branch1.getKey(), project1Branch1.getBranch(), true), + tuple(issueOnFileOnProject1Branch1.getKey(), fileOnProject1Branch1.getKey(), project1Branch1.getKey(), project1Branch1.getBranch(), true), + tuple(issueOnProject2.getKey(), project2.getKey(), project2.getKey(), "", false)); // Issues on project1Branch1 assertThat(ws.newRequest() @@ -496,10 +496,10 @@ public class SearchActionComponentsTest { .setParam(PARAM_PROJECTS, project1.getKey()) .setParam(PARAM_BRANCH, applicationBranch1.getBranch()) .executeProtobuf(SearchWsResponse.class).getIssuesList()) - .extracting(Issue::getKey, Issue::getComponent, Issue::getBranch) - .containsExactlyInAnyOrder( - tuple(issueOnProject1Branch1.getKey(), project1Branch1.getKey(), project1Branch1.getBranch()), - tuple(issueOnFileOnProject1Branch1.getKey(), fileOnProject1Branch1.getKey(), project1Branch1.getBranch())); + .extracting(Issue::getKey, Issue::getComponent, Issue::getBranch) + .containsExactlyInAnyOrder( + tuple(issueOnProject1Branch1.getKey(), project1Branch1.getKey(), project1Branch1.getBranch()), + tuple(issueOnFileOnProject1Branch1.getKey(), fileOnProject1Branch1.getKey(), project1Branch1.getBranch())); } @Test @@ -670,24 +670,24 @@ public class SearchActionComponentsTest { .setParam(PARAM_COMPONENT_KEYS, project.getKey()) .setParam(PARAM_BRANCH, branch.getBranch()) .executeProtobuf(SearchWsResponse.class).getIssuesList()) - .extracting(Issue::getKey, Issue::getComponent, Issue::getBranch) - .containsExactlyInAnyOrder(tuple(branchIssue.getKey(), branchFile.getKey(), branchFile.getBranch())); + .extracting(Issue::getKey, Issue::getComponent, Issue::getBranch) + .containsExactlyInAnyOrder(tuple(branchIssue.getKey(), branchFile.getKey(), branchFile.getBranch())); // On project key + branch assertThat(ws.newRequest() .setParam(PARAM_PROJECT_KEYS, project.getKey()) .setParam(PARAM_BRANCH, branch.getBranch()) .executeProtobuf(SearchWsResponse.class).getIssuesList()) - .extracting(Issue::getKey, Issue::getComponent, Issue::getBranch) - .containsExactlyInAnyOrder(tuple(branchIssue.getKey(), branchFile.getKey(), branchFile.getBranch())); + .extracting(Issue::getKey, Issue::getComponent, Issue::getBranch) + .containsExactlyInAnyOrder(tuple(branchIssue.getKey(), branchFile.getKey(), branchFile.getBranch())); // On file key + branch assertThat(ws.newRequest() .setParam(PARAM_COMPONENT_KEYS, branchFile.getKey()) .setParam(PARAM_BRANCH, branch.getBranch()) .executeProtobuf(SearchWsResponse.class).getIssuesList()) - .extracting(Issue::getKey, Issue::getComponent, Issue::getBranch) - .containsExactlyInAnyOrder(tuple(branchIssue.getKey(), branchFile.getKey(), branchFile.getBranch())); + .extracting(Issue::getKey, Issue::getComponent, Issue::getBranch) + .containsExactlyInAnyOrder(tuple(branchIssue.getKey(), branchFile.getKey(), branchFile.getBranch())); } @Test diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java index 68ba8cc8037..ed1c329999c 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java @@ -44,8 +44,10 @@ import org.sonar.api.utils.System2; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.DbTester; +import org.sonar.db.component.BranchType; import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ComponentTesting; +import org.sonar.db.component.SnapshotDto; import org.sonar.db.issue.IssueChangeDto; import org.sonar.db.issue.IssueDto; import org.sonar.db.organization.OrganizationDto; @@ -94,6 +96,7 @@ import static org.sonar.api.utils.DateUtils.formatDateTime; import static org.sonar.api.utils.DateUtils.parseDate; import static org.sonar.api.utils.DateUtils.parseDateTime; import static org.sonar.api.web.UserRole.ISSUE_ADMIN; +import static org.sonar.db.component.ComponentDto.PULL_REQUEST_SEPARATOR; import static org.sonar.db.component.ComponentTesting.newFileDto; import static org.sonar.db.issue.IssueTesting.newDto; import static org.sonar.server.tester.UserSessionRule.standalone; @@ -106,7 +109,9 @@ import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_ASSIGNEES; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_COMPONENT_KEYS; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_CREATED_AFTER; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_HIDE_COMMENTS; +import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_PULL_REQUEST; import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_RULES; +import static org.sonarqube.ws.client.issue.IssuesWsParameters.PARAM_SINCE_LEAK_PERIOD; public class SearchActionTest { @@ -509,6 +514,126 @@ public class SearchActionTest { } @Test + public void filter_by_leak_period() { + UserDto john = db.users().insertUser(u -> u.setLogin("john").setName("John").setEmail("john@email.com")); + UserDto alice = db.users().insertUser(u -> u.setLogin("alice").setName("Alice").setEmail("alice@email.com")); + OrganizationDto organization = db.organizations().insert(); + ComponentDto project = db.components().insertComponent(ComponentTesting.newPublicProjectDto(organization, "PROJECT_ID").setDbKey("PROJECT_KEY")); + SnapshotDto snapshotDto = db.components().insertSnapshot(project, s -> s.setLast(true).setPeriodDate(parseDateTime("2014-09-05T00:00:00+0100").getTime())); + indexPermissions(); + + ComponentDto file = db.components().insertComponent(newFileDto(project, null, "FILE_ID").setDbKey("FILE_KEY")); + RuleDto rule = newIssueRule(); + IssueDto issue1 = newDto(rule, file, project) + .setIssueCreationDate(parseDateTime("2014-09-04T00:00:00+0100")) + .setIssueUpdateDate(parseDateTime("2017-12-04T00:00:00+0100")) + .setEffort(10L) + .setStatus("OPEN") + .setKee("82fd47d4-b650-4037-80bc-7b112bd4eac2") + .setSeverity("MAJOR") + .setAssigneeUuid(john.getUuid()); + IssueDto issue2 = newDto(rule, file, project) + .setIssueCreationDate(parseDateTime("2014-09-06T00:00:00+0100")) + .setIssueUpdateDate(parseDateTime("2017-12-04T00:00:00+0100")) + .setEffort(10L) + .setStatus("OPEN") + .setKee("7b112bd4-b650-4037-80bc-82fd47d4eac2") + .setSeverity("MAJOR") + .setAssigneeUuid(alice.getUuid()); + dbClient.issueDao().insert(session, issue1, issue2); + session.commit(); + indexIssues(); + + userSession.logIn(john); + + ws.newRequest() + .setParam(PARAM_SINCE_LEAK_PERIOD, "true") + .setParam(PARAM_COMPONENT_KEYS, "PROJECT_KEY") + .execute() + .assertJson(this.getClass(), "filter_by_leak_period.json"); + } + + @Test + public void filter_by_leak_period_without_a_period() { + UserDto john = db.users().insertUser(u -> u.setLogin("john").setName("John").setEmail("john@email.com")); + UserDto alice = db.users().insertUser(u -> u.setLogin("alice").setName("Alice").setEmail("alice@email.com")); + OrganizationDto organization = db.organizations().insert(); + ComponentDto project = db.components().insertComponent(ComponentTesting.newPublicProjectDto(organization, "PROJECT_ID").setDbKey("PROJECT_KEY")); + SnapshotDto snapshotDto = db.components().insertSnapshot(project); + indexPermissions(); + ComponentDto file = db.components().insertComponent(newFileDto(project, null, "FILE_ID").setDbKey("FILE_KEY")); + RuleDto rule = newIssueRule(); + IssueDto issue1 = newDto(rule, file, project) + .setIssueCreationDate(parseDateTime("2014-09-04T00:00:00+0100")) + .setIssueUpdateDate(parseDateTime("2017-12-04T00:00:00+0100")) + .setEffort(10L) + .setStatus("OPEN") + .setKee("82fd47d4-b650-4037-80bc-7b112bd4eac2") + .setSeverity("MAJOR") + .setAssigneeUuid(john.getUuid()); + IssueDto issue2 = newDto(rule, file, project) + .setIssueCreationDate(parseDateTime("2014-09-04T00:00:00+0100")) + .setIssueUpdateDate(parseDateTime("2017-12-04T00:00:00+0100")) + .setEffort(10L) + .setStatus("OPEN") + .setKee("7b112bd4-b650-4037-80bc-82fd47d4eac2") + .setSeverity("MAJOR") + .setAssigneeUuid(alice.getUuid()); + dbClient.issueDao().insert(session, issue1, issue2); + session.commit(); + indexIssues(); + + userSession.logIn(john); + + ws.newRequest() + .setParam(PARAM_COMPONENT_KEYS, "PROJECT_KEY") + .setParam(PARAM_SINCE_LEAK_PERIOD, "true") + .execute() + .assertJson(this.getClass(), "empty_result.json"); + } + + @Test + public void filter_by_leak_period_has_no_effect_on_prs() { + UserDto john = db.users().insertUser(u -> u.setLogin("john").setName("John").setEmail("john@email.com")); + UserDto alice = db.users().insertUser(u -> u.setLogin("alice").setName("Alice").setEmail("alice@email.com")); + OrganizationDto organization = db.organizations().insert(); + ComponentDto project = db.components().insertPublicProject(organization, c -> c.setUuid("PROJECT_ID").setDbKey("PROJECT_KEY")); + ComponentDto pr = db.components().insertProjectBranch(project, b -> b.setBranchType(BranchType.PULL_REQUEST).setKey("pr")); + SnapshotDto snapshotDto = db.components().insertSnapshot(pr); + indexPermissions(); + ComponentDto file = db.components().insertComponent(newFileDto(pr, null, "FILE_ID").setDbKey("FILE_KEY" + PULL_REQUEST_SEPARATOR + "pr")); + RuleDto rule = newIssueRule(); + IssueDto issue1 = newDto(rule, file, pr) + .setIssueCreationDate(parseDateTime("2014-09-04T00:00:00+0100")) + .setIssueUpdateDate(parseDateTime("2017-12-04T00:00:00+0100")) + .setEffort(10L) + .setStatus("OPEN") + .setKee("82fd47d4-b650-4037-80bc-7b112bd4eac2") + .setSeverity("MAJOR") + .setAssigneeUuid(john.getUuid()); + IssueDto issue2 = newDto(rule, file, pr) + .setIssueCreationDate(parseDateTime("2014-09-04T00:00:00+0100")) + .setIssueUpdateDate(parseDateTime("2017-12-04T00:00:00+0100")) + .setEffort(10L) + .setStatus("OPEN") + .setKee("7b112bd4-b650-4037-80bc-82fd47d4eac2") + .setSeverity("MAJOR") + .setAssigneeUuid(alice.getUuid()); + dbClient.issueDao().insert(session, issue1, issue2); + session.commit(); + indexIssues(); + + userSession.logIn(john); + + ws.newRequest() + .setParam(PARAM_COMPONENT_KEYS, "PROJECT_KEY") + .setParam(PARAM_PULL_REQUEST, "pr") + .setParam(PARAM_SINCE_LEAK_PERIOD, "true") + .execute() + .assertJson(this.getClass(), "filter_by_leak_period_has_no_effect_on_prs.json"); + } + + @Test public void return_empty_when_login_is_unknown() { UserDto john = db.users().insertUser(u -> u.setLogin("john").setName("John").setEmail("john@email.com")); UserDto alice = db.users().insertUser(u -> u.setLogin("alice").setName("Alice").setEmail("alice@email.com")); @@ -620,7 +745,7 @@ public class SearchActionTest { assertThat(ws.newRequest() .setMultiParam("author", singletonList("unknown")) .executeProtobuf(SearchWsResponse.class).getIssuesList()) - .isEmpty(); + .isEmpty(); } @Test @@ -652,8 +777,8 @@ public class SearchActionTest { // This parameter will be ignored .setParam("authors", "leia") .executeProtobuf(SearchWsResponse.class).getIssuesList()) - .extracting(Issue::getKey) - .containsExactlyInAnyOrder(issue2.getKey()); + .extracting(Issue::getKey) + .containsExactlyInAnyOrder(issue2.getKey()); } @Test @@ -829,8 +954,8 @@ public class SearchActionTest { assertThatThrownBy(() -> ws.newRequest() .setParam("types", RuleType.SECURITY_HOTSPOT.toString()) .execute()) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("Value of parameter 'types' (SECURITY_HOTSPOT) must be one of: [CODE_SMELL, BUG, VULNERABILITY]"); + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Value of parameter 'types' (SECURITY_HOTSPOT) must be one of: [CODE_SMELL, BUG, VULNERABILITY]"); } @Test diff --git a/server/sonar-webserver-webapi/src/test/resources/org/sonar/server/issue/ws/SearchActionTest/filter_by_leak_period.json b/server/sonar-webserver-webapi/src/test/resources/org/sonar/server/issue/ws/SearchActionTest/filter_by_leak_period.json new file mode 100644 index 00000000000..0b868a72643 --- /dev/null +++ b/server/sonar-webserver-webapi/src/test/resources/org/sonar/server/issue/ws/SearchActionTest/filter_by_leak_period.json @@ -0,0 +1,51 @@ +{ + "total": 1, + "p": 1, + "ps": 100, + "paging": { + "pageIndex": 1, + "pageSize": 100, + "total": 1 + }, + "effortTotal": 10, + "debtTotal": 10, + "issues": [ + { + "key": "7b112bd4-b650-4037-80bc-82fd47d4eac2", + "rule": "xoo:x1", + "severity": "MAJOR", + "component": "FILE_KEY", + "project": "PROJECT_KEY", + "flows": [], + "status": "OPEN", + "message": "", + "effort": "10min", + "debt": "10min", + "assignee": "alice", + "tags": [], + "creationDate": "2014-09-06T00:00:00+0100", + "updateDate": "2017-12-04T00:00:00+0100", + "type": "CODE_SMELL", + } + ], + "components": [ + { + "key": "FILE_KEY", + "uuid": "FILE_ID", + "enabled": true, + "qualifier": "FIL", + "name": "NAME_FILE_ID", + "longName": "null/NAME_FILE_ID", + "path": "null/NAME_FILE_ID" + }, + { + "key": "PROJECT_KEY", + "uuid": "PROJECT_ID", + "enabled": true, + "qualifier": "TRK", + "name": "NAME_PROJECT_ID", + "longName": "LONG_NAME_PROJECT_ID" + } + ], + "facets": [] +} diff --git a/server/sonar-webserver-webapi/src/test/resources/org/sonar/server/issue/ws/SearchActionTest/filter_by_leak_period_has_no_effect_on_prs.json b/server/sonar-webserver-webapi/src/test/resources/org/sonar/server/issue/ws/SearchActionTest/filter_by_leak_period_has_no_effect_on_prs.json new file mode 100644 index 00000000000..75400d0b309 --- /dev/null +++ b/server/sonar-webserver-webapi/src/test/resources/org/sonar/server/issue/ws/SearchActionTest/filter_by_leak_period_has_no_effect_on_prs.json @@ -0,0 +1,69 @@ +{ + "total": 2, + "p": 1, + "ps": 100, + "paging": { + "pageIndex": 1, + "pageSize": 100, + "total": 2 + }, + "effortTotal": 20, + "debtTotal": 20, + "issues": [ + { + "key": "7b112bd4-b650-4037-80bc-82fd47d4eac2", + "rule": "xoo:x1", + "severity": "MAJOR", + "component": "FILE_KEY", + "project": "PROJECT_KEY", + "flows": [], + "status": "OPEN", + "message": "", + "effort": "10min", + "debt": "10min", + "assignee": "alice", + "tags": [], + "creationDate": "2014-09-04T00:00:00+0100", + "updateDate": "2017-12-04T00:00:00+0100", + "type": "CODE_SMELL", + "pullRequest": "pr" + }, + { + "key": "82fd47d4-b650-4037-80bc-7b112bd4eac2", + "rule": "xoo:x1", + "severity": "MAJOR", + "component": "FILE_KEY", + "project": "PROJECT_KEY", + "flows": [], + "status": "OPEN", + "message": "", + "effort": "10min", + "debt": "10min", + "assignee": "john", + "tags": [], + "creationDate": "2014-09-04T00:00:00+0100", + "updateDate": "2017-12-04T00:00:00+0100", + "type": "CODE_SMELL", + "pullRequest": "pr" + } + ], + "components": [ + { + "key": "FILE_KEY", + "uuid": "FILE_ID", + "enabled": true, + "qualifier": "FIL", + "name": "NAME_FILE_ID", + "longName": "null/NAME_FILE_ID", + "path": "null/NAME_FILE_ID", + "pullRequest": "pr" + }, + { + "key": "PROJECT_KEY", + "enabled": true, + "qualifier": "TRK", + "pullRequest": "pr" + } + ], + "facets": [] +} |