]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-12726 api/hotspots/search supports Applications
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Tue, 17 Dec 2019 16:40:41 +0000 (17:40 +0100)
committerSonarTech <sonartech@sonarsource.com>
Mon, 13 Jan 2020 19:46:30 +0000 (20:46 +0100)
server/sonar-db-dao/src/testFixtures/java/org/sonar/db/component/ComponentDbTester.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/hotspot/ws/SearchAction.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/SearchActionTest.java

index 70099cb0a8733db99e32d220572c8e4d7634d4fa..b60725718571734f178fc359842a9c270a134026 100644 (file)
@@ -166,16 +166,31 @@ public class ComponentDbTester {
     return insertComponentImpl(ComponentTesting.newView(organization).setPrivate(false), false, dtoPopulators);
   }
 
+  @SafeVarargs
+  public final ComponentDto insertPrivatePortfolio(Consumer<ComponentDto>... dtoPopulators) {
+    return insertComponentImpl(ComponentTesting.newView(db.getDefaultOrganization()).setPrivate(true), true, dtoPopulators);
+  }
+
   @SafeVarargs
   public final ComponentDto insertPrivatePortfolio(OrganizationDto organization, Consumer<ComponentDto>... dtoPopulators) {
     return insertComponentImpl(ComponentTesting.newView(organization).setPrivate(true), true, dtoPopulators);
   }
 
+  @SafeVarargs
+  public final ComponentDto insertPublicApplication(Consumer<ComponentDto>... dtoPopulators) {
+    return insertComponentImpl(ComponentTesting.newApplication(db.getDefaultOrganization()).setPrivate(false), false, dtoPopulators);
+  }
+
   @SafeVarargs
   public final ComponentDto insertPublicApplication(OrganizationDto organization, Consumer<ComponentDto>... dtoPopulators) {
     return insertComponentImpl(ComponentTesting.newApplication(organization).setPrivate(false), false, dtoPopulators);
   }
 
+  @SafeVarargs
+  public final ComponentDto insertPrivateApplication(Consumer<ComponentDto>... dtoPopulators) {
+    return insertComponentImpl(ComponentTesting.newApplication(db.getDefaultOrganization()).setPrivate(true), true, dtoPopulators);
+  }
+
   @SafeVarargs
   public final ComponentDto insertPrivateApplication(OrganizationDto organization, Consumer<ComponentDto>... dtoPopulators) {
     return insertComponentImpl(ComponentTesting.newApplication(organization).setPrivate(true), true, dtoPopulators);
index 8609c927d792499bbc5fdb7b3de05225247cb434..d55096ccb7d8717f987f2b4230c66890e19a5fc1 100644 (file)
@@ -60,6 +60,7 @@ import org.sonarqube.ws.Common;
 import org.sonarqube.ws.Hotspots;
 import org.sonarqube.ws.Hotspots.SearchWsResponse;
 
+import static com.google.common.base.MoreObjects.firstNonNull;
 import static com.google.common.base.Preconditions.checkArgument;
 import static java.lang.String.format;
 import static java.util.Collections.singleton;
@@ -84,6 +85,7 @@ import static org.sonar.server.ws.WsUtils.writeProtobuf;
 import static org.sonarqube.ws.WsUtils.nullToEmpty;
 
 public class SearchAction implements HotspotsWsAction {
+  private static final Set<String> SUPPORTED_QUALIFIERS = ImmutableSet.of(Qualifiers.PROJECT, Qualifiers.APP);
   private static final String PARAM_PROJECT_KEY = "projectKey";
   private static final String PARAM_STATUS = "status";
   private static final String PARAM_RESOLUTION = "resolution";
@@ -118,7 +120,7 @@ public class SearchAction implements HotspotsWsAction {
     action.addPagingParams(100);
     action.createParam(PARAM_PROJECT_KEY)
       .setDescription(format(
-        "Key of the project. This parameter is required unless %s is provided.",
+        "Key of the project or application. This parameter is required unless %s is provided.",
         PARAM_HOTSPOTS))
       .setExampleValue(KEY_PROJECT_EXAMPLE_001);
     action.createParam(PARAM_BRANCH)
@@ -162,7 +164,7 @@ public class SearchAction implements HotspotsWsAction {
     WsRequest wsRequest = toWsRequest(request);
     validateParameters(wsRequest);
     try (DbSession dbSession = dbClient.openSession(false)) {
-      Optional<ComponentDto> project = getAndValidateProject(dbSession, wsRequest);
+      Optional<ComponentDto> project = getAndValidateProjectOrApplication(dbSession, wsRequest);
 
       SearchResponseData searchResponseData = searchHotspots(wsRequest, dbSession, project, wsRequest.getHotspotKeys());
       loadComponents(dbSession, searchResponseData);
@@ -222,10 +224,10 @@ public class SearchAction implements HotspotsWsAction {
     }
   }
 
-  private Optional<ComponentDto> getAndValidateProject(DbSession dbSession, WsRequest wsRequest) {
+  private Optional<ComponentDto> getAndValidateProjectOrApplication(DbSession dbSession, WsRequest wsRequest) {
     return wsRequest.getProjectKey().map(projectKey -> {
       ComponentDto project = getProject(dbSession, projectKey, wsRequest.getBranch(), wsRequest.getPullRequest())
-        .filter(t -> Scopes.PROJECT.equals(t.scope()) && Qualifiers.PROJECT.equals(t.qualifier()))
+        .filter(t -> Scopes.PROJECT.equals(t.scope()) && SUPPORTED_QUALIFIERS.contains(t.qualifier()))
         .filter(ComponentDto::isEnabled)
         .orElseThrow(() -> new NotFoundException(format("Project '%s' not found", projectKey)));
       userSession.checkComponentPermission(UserRole.USER, project);
@@ -274,13 +276,20 @@ public class SearchAction implements HotspotsWsAction {
     project.ifPresent(p -> {
       builder.organizationUuid(p.getOrganizationUuid());
 
+      String projectUuid = firstNonNull(p.getMainBranchProjectUuid(), p.uuid());
+      if (Qualifiers.APP.equals(p.qualifier())) {
+        builder.viewUuids(singletonList(projectUuid));
+      } else {
+        builder.projectUuids(singletonList(projectUuid));
+      }
+
       if (p.getMainBranchProjectUuid() == null) {
         builder.mainBranch(true);
-        builder.projectUuids(singletonList(p.uuid()));
       } else {
-        builder.branchUuid(p.projectUuid());
+        builder.branchUuid(p.uuid());
         builder.mainBranch(false);
       }
+
       if (wsRequest.isSinceLeakPeriod()) {
         dbClient.snapshotDao().selectLastAnalysisByComponentUuid(dbSession, p.uuid())
           .map(s -> longToDate(s.getPeriodDate()))
index 2e9672211565803bb2c26bcc9ab2d4c523df9613..e61ee0238fa337f7c22fead8c0f3d334704e7cf2 100644 (file)
@@ -52,7 +52,6 @@ import org.sonar.db.issue.IssueDto;
 import org.sonar.db.rule.RuleDefinitionDto;
 import org.sonar.db.rule.RuleTesting;
 import org.sonar.server.es.EsTester;
-import org.sonar.server.es.StartupIndexer;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.issue.index.IssueIndex;
@@ -64,6 +63,7 @@ import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
 import org.sonar.server.security.SecurityStandards;
 import org.sonar.server.security.SecurityStandards.SQCategory;
 import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.view.index.ViewIndexer;
 import org.sonar.server.ws.TestRequest;
 import org.sonar.server.ws.WsActionTester;
 import org.sonarqube.ws.Hotspots;
@@ -105,7 +105,8 @@ public class SearchActionTest {
 
   private IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSessionRule, new WebAuthorizationTypeSupport(userSessionRule));
   private IssueIndexer issueIndexer = new IssueIndexer(es.client(), dbClient, new IssueIteratorFactory(dbClient));
-  private StartupIndexer permissionIndexer = new PermissionIndexer(dbClient, es.client(), issueIndexer);
+  private ViewIndexer viewIndexer = new ViewIndexer(dbClient, es.client());
+  private PermissionIndexer permissionIndexer = new PermissionIndexer(dbClient, es.client(), issueIndexer);
   private HotspotWsResponseFormatter responseFormatter = new HotspotWsResponseFormatter(defaultOrganizationProvider);
 
   private SearchAction underTest = new SearchAction(dbClient, userSessionRule, issueIndex, responseFormatter);
@@ -268,15 +269,14 @@ public class SearchActionTest {
   }
 
   @Test
-  public void fails_with_NotFoundException_if_project_is_not_a_project() {
+  public void fails_with_NotFoundException_if_project_is_neither_a_project_nor_an_application() {
     ComponentDto project = dbTester.components().insertPrivateProject();
     ComponentDto directory = dbTester.components().insertComponent(ComponentTesting.newDirectory(project, "foo"));
     ComponentDto file = dbTester.components().insertComponent(ComponentTesting.newFileDto(project));
-    ComponentDto portfolio = dbTester.components().insertPrivatePortfolio(dbTester.getDefaultOrganization());
-    ComponentDto application = dbTester.components().insertPrivateApplication(dbTester.getDefaultOrganization());
+    ComponentDto portfolio = dbTester.components().insertPrivatePortfolio();
     TestRequest request = actionTester.newRequest();
 
-    for (ComponentDto component : Arrays.asList(directory, file, portfolio, application)) {
+    for (ComponentDto component : Arrays.asList(directory, file, portfolio)) {
       request.setParam("projectKey", component.getKey());
 
       assertThatThrownBy(request::execute)
@@ -296,6 +296,17 @@ public class SearchActionTest {
       .hasMessage("Insufficient privileges");
   }
 
+  @Test
+  public void fails_with_ForbiddenException_if_application_is_private_and_not_allowed() {
+    ComponentDto application = dbTester.components().insertPrivateApplication();
+    userSessionRule.registerComponents(application);
+    TestRequest request = newRequest(application);
+
+    assertThatThrownBy(request::execute)
+      .isInstanceOf(ForbiddenException.class)
+      .hasMessage("Insufficient privileges");
+  }
+
   @Test
   public void succeeds_on_public_project() {
     ComponentDto project = dbTester.components().insertPublicProject();
@@ -308,6 +319,18 @@ public class SearchActionTest {
     assertThat(response.getComponentsList()).isEmpty();
   }
 
+  @Test
+  public void succeeds_on_public_application() {
+    ComponentDto application = dbTester.components().insertPublicApplication();
+    userSessionRule.registerComponents(application);
+
+    SearchWsResponse response = newRequest(application)
+      .executeProtobuf(SearchWsResponse.class);
+
+    assertThat(response.getHotspotsList()).isEmpty();
+    assertThat(response.getComponentsList()).isEmpty();
+  }
+
   @Test
   public void succeeds_on_private_project_with_permission() {
     ComponentDto project = dbTester.components().insertPrivateProject();
@@ -321,6 +344,19 @@ public class SearchActionTest {
     assertThat(response.getComponentsList()).isEmpty();
   }
 
+  @Test
+  public void succeeds_on_private_application_with_permission() {
+    ComponentDto application = dbTester.components().insertPrivateApplication();
+    userSessionRule.registerComponents(application);
+    userSessionRule.logIn().addProjectPermission(UserRole.USER, application);
+
+    SearchWsResponse response = newRequest(application)
+      .executeProtobuf(SearchWsResponse.class);
+
+    assertThat(response.getHotspotsList()).isEmpty();
+    assertThat(response.getComponentsList()).isEmpty();
+  }
+
   @Test
   public void does_not_fail_if_rule_of_hotspot_does_not_exist_in_DB() {
     ComponentDto project = dbTester.components().insertPublicProject();
@@ -462,6 +498,93 @@ public class SearchActionTest {
       .containsOnly(project2.getKey(), file2.getKey());
   }
 
+  @Test
+  public void returns_hotspots_of_specified_application() {
+    ComponentDto application1 = dbTester.components().insertPublicApplication();
+    ComponentDto application2 = dbTester.components().insertPublicApplication();
+    ComponentDto project1 = dbTester.components().insertPublicProject();
+    ComponentDto project2 = dbTester.components().insertPublicProject();
+    dbTester.components().insertComponent(ComponentTesting.newProjectCopy(project1, application1));
+    dbTester.components().insertComponent(ComponentTesting.newProjectCopy(project2, application2));
+    indexViews();
+    userSessionRule.registerComponents(application1, application2, project1, project2);
+    indexPermissions();
+    ComponentDto file1 = dbTester.components().insertComponent(newFileDto(project1));
+    ComponentDto file2 = dbTester.components().insertComponent(newFileDto(project2));
+    IssueDto[] hotspots2 = IntStream.range(0, 1 + RANDOM.nextInt(10))
+      .mapToObj(i -> {
+        RuleDefinitionDto rule = newRule(SECURITY_HOTSPOT);
+        insertHotspot(project1, file1, rule);
+        return insertHotspot(project2, file2, rule);
+      })
+      .toArray(IssueDto[]::new);
+    indexIssues();
+
+    SearchWsResponse responseApplication1 = newRequest(application1)
+      .executeProtobuf(SearchWsResponse.class);
+
+    assertThat(responseApplication1.getHotspotsList())
+      .extracting(SearchWsResponse.Hotspot::getKey)
+      .doesNotContainAnyElementsOf(Arrays.stream(hotspots2).map(IssueDto::getKey).collect(toList()));
+    assertThat(responseApplication1.getComponentsList())
+      .extracting(Component::getKey)
+      .containsOnly(project1.getKey(), file1.getKey());
+
+    SearchWsResponse responseApplication2 = newRequest(application2)
+      .executeProtobuf(SearchWsResponse.class);
+
+    assertThat(responseApplication2.getHotspotsList())
+      .extracting(SearchWsResponse.Hotspot::getKey)
+      .containsOnly(Arrays.stream(hotspots2).map(IssueDto::getKey).toArray(String[]::new));
+    assertThat(responseApplication2.getComponentsList())
+      .extracting(Component::getKey)
+      .containsOnly(project2.getKey(), file2.getKey());
+  }
+
+  @Test
+  public void returns_hotspots_of_specified_application_branch() {
+    ComponentDto application = dbTester.components().insertPublicApplication();
+    ComponentDto applicationBranch = dbTester.components().insertProjectBranch(application);
+    ComponentDto project1 = dbTester.components().insertPublicProject();
+    ComponentDto project2 = dbTester.components().insertPublicProject();
+    dbTester.components().insertComponent(ComponentTesting.newProjectCopy(project1, application));
+    dbTester.components().insertComponent(ComponentTesting.newProjectCopy(project2, applicationBranch));
+    indexViews();
+    userSessionRule.registerComponents(application, applicationBranch, project1, project2);
+    indexPermissions();
+    ComponentDto file1 = dbTester.components().insertComponent(newFileDto(project1));
+    ComponentDto file2 = dbTester.components().insertComponent(newFileDto(project2));
+    IssueDto[] hotspots2 = IntStream.range(0, 1 + RANDOM.nextInt(10))
+      .mapToObj(i -> {
+        RuleDefinitionDto rule = newRule(SECURITY_HOTSPOT);
+        insertHotspot(project1, file1, rule);
+        return insertHotspot(project2, file2, rule);
+      })
+      .toArray(IssueDto[]::new);
+    indexIssues();
+
+    SearchWsResponse responseApplication = newRequest(application)
+      .executeProtobuf(SearchWsResponse.class);
+
+    assertThat(responseApplication.getHotspotsList())
+      .extracting(SearchWsResponse.Hotspot::getKey)
+      .doesNotContainAnyElementsOf(Arrays.stream(hotspots2).map(IssueDto::getKey).collect(toList()));
+    assertThat(responseApplication.getComponentsList())
+      .extracting(Component::getKey)
+      .containsOnly(project1.getKey(), file1.getKey());
+
+    SearchWsResponse responseApplicationBranch = newRequest(applicationBranch)
+      .executeProtobuf(SearchWsResponse.class);
+
+    assertThat(responseApplicationBranch.getHotspotsList())
+      .extracting(SearchWsResponse.Hotspot::getKey)
+      .containsOnly(Arrays.stream(hotspots2).map(IssueDto::getKey).toArray(String[]::new));
+    assertThat(responseApplicationBranch.getComponentsList())
+      .extracting(Component::getKey)
+      .containsOnly(project2.getKey(), file2.getKey());
+
+  }
+
   @Test
   public void returns_hotspot_of_branch_or_pullRequest() {
     ComponentDto project = dbTester.components().insertPublicProject();
@@ -1261,6 +1384,10 @@ public class SearchActionTest {
     issueIndexer.indexOnStartup(issueIndexer.getIndexTypes());
   }
 
+  private void indexViews() {
+    viewIndexer.indexOnStartup(viewIndexer.getIndexTypes());
+  }
+
   private RuleDefinitionDto newRule(RuleType ruleType) {
     return newRule(ruleType, t -> {
     });