From 429d325a10ffb2e8ba55e397d07006c4c4062ea5 Mon Sep 17 00:00:00 2001 From: Belen Pruvost Date: Tue, 19 Apr 2022 17:07:14 +0200 Subject: [PATCH] SONAR-16261 - Endpoint to search for Projects a user can scan --- .../server/project/ws/ProjectFinder.java | 120 ++++++++++++ .../server/project/ws/ProjectsWsModule.java | 2 + .../ws/SearchMyScannableProjectsAction.java | 74 ++++++++ .../search-my-scannable-projects-example.json | 12 ++ .../server/project/ws/ProjectFinderTest.java | 140 ++++++++++++++ .../project/ws/ProjectsWsModuleTest.java | 2 +- .../SearchMyScannableProjectsActionTest.java | 179 ++++++++++++++++++ .../main/protobuf/ws-projectanalyses.proto | 1 + sonar-ws/src/main/protobuf/ws-projects.proto | 10 + 9 files changed, 539 insertions(+), 1 deletion(-) create mode 100644 server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectFinder.java create mode 100644 server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/SearchMyScannableProjectsAction.java create mode 100644 server/sonar-webserver-webapi/src/main/resources/org/sonar/server/project/ws/search-my-scannable-projects-example.json create mode 100644 server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/ProjectFinderTest.java create mode 100644 server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/SearchMyScannableProjectsActionTest.java diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectFinder.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectFinder.java new file mode 100644 index 00000000000..971aa7a4381 --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectFinder.java @@ -0,0 +1,120 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.project.ws; + +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.sonar.api.server.ServerSide; +import org.sonar.api.web.UserRole; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.permission.GlobalPermission; +import org.sonar.db.project.ProjectDto; +import org.sonar.server.user.UserSession; + +import static java.util.Comparator.comparing; +import static java.util.Comparator.nullsFirst; +import static java.util.Locale.ENGLISH; +import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; + +@ServerSide +public class ProjectFinder { + + private final DbClient dbClient; + private final UserSession userSession; + + public ProjectFinder(DbClient dbClient, UserSession userSession) { + this.dbClient = dbClient; + this.userSession = userSession; + } + + public SearchResult search(DbSession dbSession, @Nullable String searchQuery) { + List allProjects = dbClient.projectDao().selectProjects(dbSession); + + Set projectsUserHasAccessTo = userSession.keepAuthorizedProjects(UserRole.SCAN, allProjects) + .stream() + .map(ProjectDto::getKey) + .collect(toSet()); + + applyQueryAndPermissionFilter(searchQuery, allProjects, projectsUserHasAccessTo); + + List resultProjects = allProjects.stream() + .sorted(comparing(ProjectDto::getName, nullsFirst(String.CASE_INSENSITIVE_ORDER))) + .map(p -> new Project(p.getKey(), p.getName())) + .collect(toList()); + return new SearchResult(resultProjects); + } + + private void applyQueryAndPermissionFilter(@Nullable String searchQuery, final List projects, + Set projectsUserHasAccessTo) { + Iterator projectIterator = projects.iterator(); + while (projectIterator.hasNext()) { + ProjectDto project = projectIterator.next(); + if (isFilteredByQuery(searchQuery, project.getName()) || !hasPermission(projectsUserHasAccessTo, project.getKey())) { + projectIterator.remove(); + } + } + } + + private static boolean isFilteredByQuery(@Nullable String query, String projectName) { + return query != null && !projectName.toLowerCase(ENGLISH).contains(query.toLowerCase(ENGLISH)); + } + + private boolean hasPermission(Set projectsUserHasAccessTo, String projectKey) { + return userSession.hasPermission(GlobalPermission.SCAN) || projectsUserHasAccessTo.contains(projectKey); + } + + public static class SearchResult { + private final List projects; + + public SearchResult(List projects) { + this.projects = projects; + } + + public List getProjects() { + return projects; + } + } + + public static class Project { + private final String key; + private final String name; + + public Project(String key, @Nullable String name) { + this.key = requireNonNull(key, "Key cant be null"); + this.name = name; + } + + public String getKey() { + return key; + } + + @CheckForNull + public String getName() { + return name; + } + + } +} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java index f452466feae..aac5be28426 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java @@ -33,6 +33,7 @@ public class ProjectsWsModule extends Module { protected void configureModule() { add( ProjectDefaultVisibility.class, + ProjectFinder.class, ProjectLifeCycleListenersImpl.class, ProjectsWs.class, CreateAction.class, @@ -41,6 +42,7 @@ public class ProjectsWsModule extends Module { UpdateKeyAction.class, SearchMyProjectsAction.class, SearchAction.class, + SearchMyScannableProjectsAction.class, UpdateVisibilityAction.class, UpdateDefaultVisibilityAction.class); } diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/SearchMyScannableProjectsAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/SearchMyScannableProjectsAction.java new file mode 100644 index 00000000000..917da7a58f7 --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/project/ws/SearchMyScannableProjectsAction.java @@ -0,0 +1,74 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.project.ws; + +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService.NewAction; +import org.sonar.api.server.ws.WebService.NewController; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonarqube.ws.Projects.SearchMyScannableProjectsResponse.Project; + +import static org.sonar.api.server.ws.WebService.Param.TEXT_QUERY; +import static org.sonar.server.project.ws.ProjectFinder.SearchResult; +import static org.sonar.server.ws.WsUtils.writeProtobuf; +import static org.sonarqube.ws.Projects.SearchMyScannableProjectsResponse; + +public class SearchMyScannableProjectsAction implements ProjectsWsAction { + + private final DbClient dbClient; + private final ProjectFinder projectFinder; + + public SearchMyScannableProjectsAction(DbClient dbClient, ProjectFinder projectFinder) { + this.dbClient = dbClient; + this.projectFinder = projectFinder; + } + + @Override + public void define(NewController controller) { + NewAction action = controller.createAction("search_my_scannable_projects") + .setDescription("List projects that a user can scan.") + .setSince("9.5") + .setInternal(true) + .setResponseExample(getClass().getResource("search-my-scannable-projects-example.json")) + .setHandler(this); + + action.createSearchQuery("project", "project names"); + } + + @Override + public void handle(Request request, Response response) { + try (DbSession dbSession = dbClient.openSession(false)) { + String query = request.param(TEXT_QUERY); + + SearchResult searchResult = projectFinder.search(dbSession, query); + + SearchMyScannableProjectsResponse.Builder searchProjects = SearchMyScannableProjectsResponse.newBuilder(); + searchResult.getProjects().stream() + .map(p -> Project.newBuilder() + .setKey(p.getKey()) + .setName(p.getName())) + .forEach(searchProjects::addProjects); + writeProtobuf(searchProjects.build(), request, response); + } + } + +} diff --git a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/project/ws/search-my-scannable-projects-example.json b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/project/ws/search-my-scannable-projects-example.json new file mode 100644 index 00000000000..4a2dbc338c0 --- /dev/null +++ b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/project/ws/search-my-scannable-projects-example.json @@ -0,0 +1,12 @@ +{ + "projects": [ + { + "key": "project-key-1", + "name": "Project 1" + }, + { + "key": "project-key-2", + "name": "Project 2" + } + ] +} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/ProjectFinderTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/ProjectFinderTest.java new file mode 100644 index 00000000000..7fa18411245 --- /dev/null +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/ProjectFinderTest.java @@ -0,0 +1,140 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.project.ws; + +import java.util.List; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.db.DbTester; +import org.sonar.db.permission.GlobalPermission; +import org.sonar.db.project.ProjectDto; +import org.sonar.server.project.ws.ProjectFinder.Project; +import org.sonar.server.tester.UserSessionRule; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; +import static org.sonar.api.web.UserRole.SCAN; +import static org.sonar.server.project.ws.ProjectFinder.SearchResult; + +public class ProjectFinderTest { + @Rule + public DbTester db = DbTester.create(); + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + + private final ProjectFinder underTest = new ProjectFinder(db.getDbClient(), userSession); + + @Test + public void selected_projects() { + ProjectDto project1 = db.components().insertPrivateProjectDto(); + ProjectDto project2 = db.components().insertPrivateProjectDto(); + ProjectDto project3 = db.components().insertPrivateProjectDto(); + userSession.addProjectPermission(SCAN, project1, project2); + List projects = underTest.search(db.getSession(), "").getProjects(); + + assertThat(projects) + .extracting(Project::getKey, Project::getName) + .containsExactlyInAnyOrder( + tuple(project1.getKey(), project1.getName()), + tuple(project2.getKey(), project2.getName())); + } + + @Test + public void sort_project_by_name() { + ProjectDto project1 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:one").setName("Projet Un")); + ProjectDto project2 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:two").setName("Projet Deux")); + ProjectDto project3 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:three").setName("Projet Trois")); + ProjectDto project4 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:four").setName("Projet Quatre")); + userSession.addProjectPermission(SCAN, project1, project2, project3, project4); + + assertThat(underTest.search(db.getSession(), "projet") + .getProjects()) + .extracting(Project::getName) + .containsExactly("Projet Deux", "Projet Quatre", "Projet Trois", "Projet Un"); + } + + @Test + public void projects_are_filtered_by_permissions() { + ProjectDto project1 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:one").setName("Projet Un")); + ProjectDto project2 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:two").setName("Projet Deux")); + ProjectDto project3 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:three").setName("Projet Trois")); + ProjectDto project4 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:four").setName("Projet Quatre")); + db.components().insertPrivateProjectDto(p -> p.setDbKey("project:five").setName("Projet Cinq")); + + userSession.addProjectPermission(SCAN, project1, project2, project3, project4); + + SearchResult result = underTest.search(db.getSession(), null); + + assertThat(result.getProjects()) + .extracting(Project::getKey, Project::getName) + .containsExactlyInAnyOrder( + tuple("project:one", "Projet Un"), + tuple("project:two", "Projet Deux"), + tuple("project:three", "Projet Trois"), + tuple("project:four", "Projet Quatre")); + } + + @Test + public void projects_are_not_filtered_due_to_global_scan_permission() { + db.components().insertPrivateProjectDto(p -> p.setDbKey("project:one").setName("Projet Un")); + db.components().insertPrivateProjectDto(p -> p.setDbKey("project:two").setName("Projet Deux")); + db.components().insertPrivateProjectDto(p -> p.setDbKey("project:three").setName("Projet Trois")); + db.components().insertPrivateProjectDto(p -> p.setDbKey("project:four").setName("Projet Quatre")); + db.components().insertPrivateProjectDto(p -> p.setDbKey("project:five").setName("Projet Cinq")); + + userSession.addPermission(GlobalPermission.SCAN); + + SearchResult result = underTest.search(db.getSession(), null); + + assertThat(result.getProjects()) + .extracting(Project::getKey, Project::getName) + .containsExactlyInAnyOrder( + tuple("project:one", "Projet Un"), + tuple("project:two", "Projet Deux"), + tuple("project:three", "Projet Trois"), + tuple("project:four", "Projet Quatre"), + tuple("project:five", "Projet Cinq")); + } + + @Test + public void search_by_query_on_name_case_insensitive() { + ProjectDto project1 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:one").setName("Projet Un")); + ProjectDto project2 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:two").setName("Projet Deux")); + ProjectDto project3 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:three").setName("Projet Trois")); + ProjectDto project4 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:four").setName("Projet Quatre")); + + userSession.addProjectPermission(SCAN, project1, project2, project3, project4); + + assertThat(underTest.search(db.getSession(), "projet") + .getProjects()) + .extracting(Project::getKey) + .containsExactlyInAnyOrder("project:one", "project:two", "project:three", "project:four"); + + assertThat(underTest.search(db.getSession(), "un") + .getProjects()) + .extracting(Project::getKey) + .containsExactlyInAnyOrder("project:one"); + + assertThat(underTest.search(db.getSession(), "TROIS") + .getProjects()) + .extracting(Project::getKey) + .containsExactlyInAnyOrder("project:three"); + } +} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java index ec6d1c4283b..169267e7aba 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java @@ -29,6 +29,6 @@ public class ProjectsWsModuleTest { public void verify_count_of_added_components_on_SonarQube() { ListContainer container = new ListContainer(); new ProjectsWsModule().configure(container); - assertThat(container.getAddedObjects()).hasSize(11); + assertThat(container.getAddedObjects()).hasSize(13); } } diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/SearchMyScannableProjectsActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/SearchMyScannableProjectsActionTest.java new file mode 100644 index 00000000000..1201b6197b0 --- /dev/null +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/project/ws/SearchMyScannableProjectsActionTest.java @@ -0,0 +1,179 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.project.ws; + +import java.util.List; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.server.ws.WebService; +import org.sonar.api.server.ws.WebService.Param; +import org.sonar.db.DbTester; +import org.sonar.db.permission.GlobalPermission; +import org.sonar.db.project.ProjectDto; +import org.sonar.server.tester.UserSessionRule; +import org.sonar.server.ws.WsActionTester; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; +import static org.sonar.api.server.ws.WebService.Param.TEXT_QUERY; +import static org.sonar.api.web.UserRole.SCAN; +import static org.sonar.test.JsonAssert.assertJson; +import static org.sonarqube.ws.Projects.SearchMyScannableProjectsResponse; +import static org.sonarqube.ws.Projects.SearchMyScannableProjectsResponse.Project; + +public class SearchMyScannableProjectsActionTest { + + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + + @Rule + public DbTester db = DbTester.create(); + + private final WsActionTester ws = new WsActionTester( + new SearchMyScannableProjectsAction(db.getDbClient(), new ProjectFinder(db.getDbClient(), userSession))); + + @Test + public void projects_filtered_by_query() { + ProjectDto project1 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:one").setName("Projet Un")); + ProjectDto project2 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:two").setName("Projet Deux")); + ProjectDto project3 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:three").setName("Project Three")); + ProjectDto project4 = db.components().insertPublicProjectDto(p -> p.setDbKey("project:four").setName("Project Four")); + userSession.addProjectPermission(SCAN, project1, project2, project3, project4); + + List result = ws.newRequest() + .setParam(TEXT_QUERY, "project") + .executeProtobuf(SearchMyScannableProjectsResponse.class) + .getProjectsList(); + + assertThat(result) + .extracting(Project::getKey, Project::getName) + .containsExactlyInAnyOrder( + tuple("project:three", "Project Three"), + tuple("project:four", "Project Four")); + } + + @Test + public void projects_not_filtered_by_empty_query() { + ProjectDto project1 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:one").setName("Projet Un")); + ProjectDto project2 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:two").setName("Projet Deux")); + ProjectDto project3 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:three").setName("Project Three")); + ProjectDto project4 = db.components().insertPublicProjectDto(p -> p.setDbKey("project:four").setName("Project Four")); + userSession.addProjectPermission(SCAN, project1, project2, project3, project4); + + List result = ws.newRequest() + .setParam(TEXT_QUERY, "") + .executeProtobuf(SearchMyScannableProjectsResponse.class) + .getProjectsList(); + + assertThat(result) + .extracting(Project::getKey, Project::getName) + .containsExactlyInAnyOrder( + tuple("project:one", "Projet Un"), + tuple("project:two", "Projet Deux"), + tuple("project:three", "Project Three"), + tuple("project:four", "Project Four")); + } + + @Test + public void projects_filtered_by_scan_permission() { + db.components().insertPrivateProjectDto(p -> p.setDbKey("project:one").setName("Projet Un")); + db.components().insertPrivateProjectDto(p -> p.setDbKey("project:two").setName("Projet Deux")); + db.components().insertPrivateProjectDto(p -> p.setDbKey("project:three").setName("Project Three")); + db.components().insertPublicProjectDto(p -> p.setDbKey("project:four").setName("Project Four")); + + List result = ws.newRequest() + .executeProtobuf(SearchMyScannableProjectsResponse.class) + .getProjectsList(); + + assertThat(result).isEmpty(); + } + + @Test + public void projects_filtered_for_anonymous_user() { + ProjectDto project1 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:one").setName("Projet Un")); + ProjectDto project2 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:two").setName("Projet Deux")); + ProjectDto project3 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project:three").setName("Project Three")); + ProjectDto project4 = db.components().insertPublicProjectDto(p -> p.setDbKey("project:four").setName("Project Four")); + userSession.addProjectPermission(SCAN, project1, project2, project3, project4); + + WsActionTester ws = new WsActionTester( + new SearchMyScannableProjectsAction(db.getDbClient(), new ProjectFinder(db.getDbClient(), userSession.anonymous()))); + + List result = ws.newRequest() + .executeProtobuf(SearchMyScannableProjectsResponse.class) + .getProjectsList(); + + assertThat(result).isEmpty(); + } + + @Test + public void projects_not_filtered_due_to_global_scan_permission() { + db.components().insertPrivateProjectDto(p -> p.setDbKey("project:one").setName("Projet Un")); + db.components().insertPrivateProjectDto(p -> p.setDbKey("project:two").setName("Projet Deux")); + db.components().insertPrivateProjectDto(p -> p.setDbKey("project:three").setName("Project Three")); + db.components().insertPublicProjectDto(p -> p.setDbKey("project:four").setName("Project Four")); + userSession.addPermission(GlobalPermission.SCAN); + + List result = ws.newRequest() + .executeProtobuf(SearchMyScannableProjectsResponse.class) + .getProjectsList(); + + assertThat(result) + .extracting(Project::getKey, Project::getName) + .containsExactlyInAnyOrder( + tuple("project:one", "Projet Un"), + tuple("project:two", "Projet Deux"), + tuple("project:three", "Project Three"), + tuple("project:four", "Project Four")); + } + + @Test + public void json_example() { + ProjectDto project1 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project-key-1").setName("Project 1")); + ProjectDto project2 = db.components().insertPrivateProjectDto(p -> p.setDbKey("project-key-2").setName("Project 2")); + ProjectDto project3 = db.components().insertPublicProjectDto(p -> p.setDbKey("public-project-without-scan-permissions") + .setName("Public Project with Scan Permissions")); + userSession.addProjectPermission(SCAN, project1, project2); + userSession.registerProjects(project3); + + String result = ws.newRequest() + .execute().getInput(); + + assertJson(result).isSimilarTo(ws.getDef().responseExampleAsString()); + assertJson(ws.getDef().responseExampleAsString()).isSimilarTo(result); + } + + @Test + public void definition() { + WebService.Action definition = ws.getDef(); + + assertThat(definition.key()).isEqualTo("search_my_scannable_projects"); + assertThat(definition.since()).isEqualTo("9.5"); + assertThat(definition.isInternal()).isTrue(); + assertThat(definition.isPost()).isFalse(); + assertThat(definition.responseExampleAsString()).isNotEmpty(); + + assertThat(definition.params()) + .extracting(Param::key, Param::isRequired) + .containsExactlyInAnyOrder( + tuple("q", false)); + } + +} diff --git a/sonar-ws/src/main/protobuf/ws-projectanalyses.proto b/sonar-ws/src/main/protobuf/ws-projectanalyses.proto index 7a8ab2e5dd3..c435380d59a 100644 --- a/sonar-ws/src/main/protobuf/ws-projectanalyses.proto +++ b/sonar-ws/src/main/protobuf/ws-projectanalyses.proto @@ -87,3 +87,4 @@ message Project { optional string oldBranch = 5; optional string newBranch = 6; } + diff --git a/sonar-ws/src/main/protobuf/ws-projects.proto b/sonar-ws/src/main/protobuf/ws-projects.proto index d20ba59fb2d..db29fdb2a85 100644 --- a/sonar-ws/src/main/protobuf/ws-projects.proto +++ b/sonar-ws/src/main/protobuf/ws-projects.proto @@ -73,3 +73,13 @@ message SearchWsResponse { optional string revision = 8; } } + +// WS api/projects/search_my_scannable_projects +message SearchMyScannableProjectsResponse { + message Project { + optional string key = 1; + optional string name = 2; + } + + repeated Project projects = 1; +} -- 2.39.5