diff options
author | Sébastien Lesaint <sebastien.lesaint@sonarsource.com> | 2017-02-17 16:19:57 +0100 |
---|---|---|
committer | Sébastien Lesaint <sebastien.lesaint@sonarsource.com> | 2017-02-17 16:23:24 +0100 |
commit | fa327b38613d18ec445831242f84d500bf284ac3 (patch) | |
tree | 940811204fae4b23fd53a067a72b64d1cfd902c3 | |
parent | f4039bbb34726f62ee61e45503c2bfa6c87dc3fb (diff) | |
parent | 3b673b37f88fc38b84244bd541d20ee9ac510486 (diff) | |
download | sonarqube-fa327b38613d18ec445831242f84d500bf284ac3.tar.gz sonarqube-fa327b38613d18ec445831242f84d500bf284ac3.zip |
Merge branch 'branch-6.3'
26 files changed, 1128 insertions, 53 deletions
diff --git a/it/it-tests/src/test/java/it/Category1Suite.java b/it/it-tests/src/test/java/it/Category1Suite.java index 067db2094d2..af6812ff25a 100644 --- a/it/it-tests/src/test/java/it/Category1Suite.java +++ b/it/it-tests/src/test/java/it/Category1Suite.java @@ -20,6 +20,7 @@ package it; import com.sonar.orchestrator.Orchestrator; +import it.administration.ProjectsAdministrationTest; import it.administration.UsersPageTest; import it.authorisation.ExecuteAnalysisPermissionTest; import it.authorisation.IssuePermissionTest; @@ -62,6 +63,7 @@ import static util.ItUtils.xooPlugin; @Suite.SuiteClasses({ // administration UsersPageTest.class, + ProjectsAdministrationTest.class, // project administration BulkDeletionTest.class, ProjectAdministrationTest.class, diff --git a/it/it-tests/src/test/java/it/administration/ProjectsAdministrationTest.java b/it/it-tests/src/test/java/it/administration/ProjectsAdministrationTest.java new file mode 100644 index 00000000000..c6258e7b886 --- /dev/null +++ b/it/it-tests/src/test/java/it/administration/ProjectsAdministrationTest.java @@ -0,0 +1,66 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 it.administration; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import it.Category1Suite; +import java.sql.SQLException; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.ws.client.permission.RemoveGroupWsRequest; +import pageobjects.Navigation; + +import static com.codeborne.selenide.Condition.text; +import static com.codeborne.selenide.Selenide.$; +import static util.ItUtils.newAdminWsClient; +import static util.ItUtils.projectDir; + +public class ProjectsAdministrationTest { + + @ClassRule + public static Orchestrator orchestrator = Category1Suite.ORCHESTRATOR; + + @Rule + public Navigation nav = Navigation.get(orchestrator); + + @Before + public void deleteAnalysisData() throws SQLException { + orchestrator.resetData(); + } + + @Test + public void return_all_projects_even_when_no_permission() throws Exception { + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample")).setProperties("sonar.projectKey", "sample1")); + orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-sample")).setProperties("sonar.projectKey", "sample2")); + // Remove 'Browse' permission from anyone and 'Admin' permission for admin group on project 2 -> No one can access or admin this + // project, expect System Admin + newAdminWsClient(orchestrator).permissions().removeGroup(new RemoveGroupWsRequest().setProjectKey("sample2").setGroupName("Anyone").setPermission("user")); + newAdminWsClient(orchestrator).permissions().removeGroup(new RemoveGroupWsRequest().setProjectKey("sample2").setGroupName("sonar-administrators").setPermission("admin")); + + nav.logIn().asAdmin().open("/projects_admin"); + $(".data.zebra") + .shouldHave(text("sample1")) + .shouldHave(text("sample2")); + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationCreation.java b/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationCreation.java index 4f542a461e3..72ed9b83392 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationCreation.java +++ b/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationCreation.java @@ -23,6 +23,7 @@ import java.util.Optional; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.sonar.api.web.UserRole; +import org.sonar.core.permission.GlobalPermissions; import org.sonar.db.DbSession; import org.sonar.db.organization.OrganizationDto; import org.sonar.db.user.UserDto; @@ -32,6 +33,7 @@ import static java.util.Objects.requireNonNull; public interface OrganizationCreation { String OWNERS_GROUP_NAME = "Owners"; String OWNERS_GROUP_DESCRIPTION_PATTERN = "Owners of organization %s"; + String PERM_TEMPLATE_NAME = "Default template"; String PERM_TEMPLATE_DESCRIPTION_PATTERN = "Default permission template of organization %s"; String PERSONAL_ORGANIZATION_DESCRIPTION_PATTERN = "%s's personal organization"; @@ -42,16 +44,21 @@ public interface OrganizationCreation { * This method does several operations at once: * <ol> * <li>create an ungarded organization with the specified details</li> - * <li>create a group called {@link #OWNERS_GROUP_NAME Owners} with Administer Organization permission</li> + * <li>create a group called {@link #OWNERS_GROUP_NAME Owners} with all organization wide permissions</li> * <li>make the specified user a member of this group</li> - * <li>create a default template for the organization (which name and description will follow patterns - * {@link #OWNERS_GROUP_NAME} and {@link #OWNERS_GROUP_DESCRIPTION_PATTERN} based on the organization name)</li> - * <li>this group defines the specified permissions (which effectively makes projects public): + * <li>create a default template for the organization + * <ul> + * <li>name is {@link #PERM_TEMPLATE_NAME Default template}</li> + * <li>description follows pattern {@link #PERM_TEMPLATE_DESCRIPTION_PATTERN} based on the organization name</li> + * </ul> + * </li> + * <li>this permission template defines the specified permissions (which effectively makes projects public): * <ul> * <li>group {@link #OWNERS_GROUP_NAME Owners} : {@link UserRole#ADMIN ADMIN}</li> * <li>group {@link #OWNERS_GROUP_NAME Owners} : {@link UserRole#ISSUE_ADMIN ISSUE_ADMIN}</li> - * <li>any one : {@link UserRole#USER USER}</li> - * <li>any one : {@link UserRole#CODEVIEWER CODEVIEWER}</li> + * <li>group {@link #OWNERS_GROUP_NAME Owners} : {@link GlobalPermissions#SCAN_EXECUTION SCAN_EXECUTION}</li> + * <li>anyone : {@link UserRole#USER USER}</li> + * <li>anyone : {@link UserRole#CODEVIEWER CODEVIEWER}</li> * </ul> * </li> * </ol> @@ -67,16 +74,36 @@ public interface OrganizationCreation { /** * Create a new guarded organization which details are based on the login of the specified User. * <p> - * This method create the organization and its associated elements in exactly the same was as - * {@link #create(DbSession, long, NewOrganization)} with the organization's details computed from the - * user's login: - * <ul> - * <li>key: generated from the user's login</li> - * <li>name: the user's name if set, otherwise the user's login</li> - * <li>description: {@link #PERSONAL_ORGANIZATION_DESCRIPTION_PATTERN "[name]'s personal organization"} where name - * is user name (when non null and non empty) or login</li> - * <li>url and avatar: null</li> - * </ul> + * This method does several operations at once: + * <ol> + * <li> + * create a guarded organization with the details computed from user's details: + * <ul> + * <li>key: generated from the user's login</li> + * <li>name: the user's name if set, otherwise the user's login</li> + * <li>description: {@link #PERSONAL_ORGANIZATION_DESCRIPTION_PATTERN "[name]'s personal organization"} where name + * is user name (when non null and non empty) or login</li> + * <li>url and avatar: null</li> + * </ul> + * </li> + * <li>give all organization wide permissions to the user</li> + * <li>create a default template for the organization + * <ul> + * <li>name is {@link #PERM_TEMPLATE_NAME Default template}</li> + * <li>description follows pattern {@link #PERM_TEMPLATE_DESCRIPTION_PATTERN} based on the organization name</li> + * </ul> + * </li> + * <li>this permission template defines the specified permissions (which effectively makes projects public and + * automatically adds new projects to the user's favorites): + * <ul> + * <li>project creator : {@link UserRole#ADMIN ADMIN}</li> + * <li>project creator : {@link UserRole#ISSUE_ADMIN ISSUE_ADMIN}</li> + * <li>project creator : {@link GlobalPermissions#SCAN_EXECUTION SCAN_EXECUTION}</li> + * <li>anyone : {@link UserRole#USER USER}</li> + * <li>anyone : {@link UserRole#CODEVIEWER CODEVIEWER}</li> + * </ul> + * </li> + * </ol> * </p> * * @return the created organization or empty if feature is disabled diff --git a/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationCreationImpl.java b/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationCreationImpl.java index e342fa7ea65..81de1e7cc40 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationCreationImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/organization/OrganizationCreationImpl.java @@ -34,6 +34,8 @@ import org.sonar.db.DbSession; import org.sonar.db.organization.DefaultTemplates; import org.sonar.db.organization.OrganizationDto; import org.sonar.db.permission.GroupPermissionDto; +import org.sonar.db.permission.UserPermissionDto; +import org.sonar.db.permission.template.PermissionTemplateCharacteristicDto; import org.sonar.db.permission.template.PermissionTemplateDto; import org.sonar.db.user.GroupDto; import org.sonar.db.user.UserDto; @@ -99,9 +101,8 @@ public class OrganizationCreationImpl implements OrganizationCreation { OrganizationDto organization = insertOrganization(dbSession, newOrganization, dto -> dto.setGuarded(true).setUserId(newUser.getId())); - GroupDto group = insertOwnersGroup(dbSession, organization); - insertDefaultTemplate(dbSession, organization, group); - addCurrentUserToGroup(dbSession, group, newUser.getId()); + GlobalPermissions.ALL.forEach(permission -> insertUserPermissions(dbSession, newUser, organization, permission)); + insertPersonalOrgDefaultTemplate(dbSession, organization); dbSession.commit(); @@ -160,13 +161,39 @@ public class OrganizationCreationImpl implements OrganizationCreation { new PermissionTemplateDto() .setOrganizationUuid(organizationDto.getUuid()) .setUuid(uuidFactory.create()) - .setName("Default template") + .setName(PERM_TEMPLATE_NAME) .setDescription(format(PERM_TEMPLATE_DESCRIPTION_PATTERN, organizationDto.getName())) .setCreatedAt(now) .setUpdatedAt(now)); insertGroupPermission(dbSession, permissionTemplateDto, UserRole.ADMIN, group); insertGroupPermission(dbSession, permissionTemplateDto, UserRole.ISSUE_ADMIN, group); + insertGroupPermission(dbSession, permissionTemplateDto, GlobalPermissions.SCAN_EXECUTION, group); + insertGroupPermission(dbSession, permissionTemplateDto, UserRole.USER, null); + insertGroupPermission(dbSession, permissionTemplateDto, UserRole.CODEVIEWER, null); + + dbClient.organizationDao().setDefaultTemplates( + dbSession, + organizationDto.getUuid(), + new DefaultTemplates().setProjectUuid(permissionTemplateDto.getUuid())); + } + + private void insertPersonalOrgDefaultTemplate(DbSession dbSession, OrganizationDto organizationDto) { + long now = system2.now(); + Date dateNow = new Date(now); + PermissionTemplateDto permissionTemplateDto = dbClient.permissionTemplateDao().insert( + dbSession, + new PermissionTemplateDto() + .setOrganizationUuid(organizationDto.getUuid()) + .setUuid(uuidFactory.create()) + .setName("Default template") + .setDescription(format(PERM_TEMPLATE_DESCRIPTION_PATTERN, organizationDto.getName())) + .setCreatedAt(dateNow) + .setUpdatedAt(dateNow)); + + insertProjectCreatorPermission(dbSession, permissionTemplateDto, UserRole.ADMIN, now); + insertProjectCreatorPermission(dbSession, permissionTemplateDto, UserRole.ISSUE_ADMIN, now); + insertProjectCreatorPermission(dbSession, permissionTemplateDto, GlobalPermissions.SCAN_EXECUTION, now); insertGroupPermission(dbSession, permissionTemplateDto, UserRole.USER, null); insertGroupPermission(dbSession, permissionTemplateDto, UserRole.CODEVIEWER, null); @@ -176,6 +203,17 @@ public class OrganizationCreationImpl implements OrganizationCreation { new DefaultTemplates().setProjectUuid(permissionTemplateDto.getUuid())); } + private void insertProjectCreatorPermission(DbSession dbSession, PermissionTemplateDto permissionTemplateDto, String permission, long now) { + dbClient.permissionTemplateCharacteristicDao().insert( + dbSession, + new PermissionTemplateCharacteristicDto() + .setTemplateId(permissionTemplateDto.getId()) + .setWithProjectCreator(true) + .setPermission(permission) + .setCreatedAt(now) + .setUpdatedAt(now)); + } + private void insertGroupPermission(DbSession dbSession, PermissionTemplateDto template, String permission, @Nullable GroupDto group) { dbClient.permissionTemplateDao().insertGroupPermission(dbSession, template.getId(), group == null ? null : group.getId(), permission); } @@ -201,6 +239,12 @@ public class OrganizationCreationImpl implements OrganizationCreation { .setRole(permission)); } + private void insertUserPermissions(DbSession dbSession, UserDto userDto, OrganizationDto organization, String permission) { + dbClient.userPermissionDao().insert( + dbSession, + new UserPermissionDto(organization.getUuid(), permission, userDto.getId(), null)); + } + private void addCurrentUserToGroup(DbSession dbSession, GroupDto group, int createUserId) { dbClient.userGroupDao().insert( dbSession, diff --git a/server/sonar-server/src/main/java/org/sonar/server/permission/index/AuthorizationTypeSupport.java b/server/sonar-server/src/main/java/org/sonar/server/permission/index/AuthorizationTypeSupport.java index 3ca3359473c..6d4c1c33b6b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/permission/index/AuthorizationTypeSupport.java +++ b/server/sonar-server/src/main/java/org/sonar/server/permission/index/AuthorizationTypeSupport.java @@ -85,6 +85,10 @@ public class AuthorizationTypeSupport { * user has read access. */ public QueryBuilder createQueryFilter() { + if (userSession.isRoot()) { + return QueryBuilders.matchAllQuery(); + } + Integer userId = userSession.getUserId(); BoolQueryBuilder filter = boolQuery(); diff --git a/server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java b/server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java index ee421e8213c..d3c00cf99ed 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java +++ b/server/sonar-server/src/main/java/org/sonar/server/project/ws/ProjectsWsModule.java @@ -34,6 +34,7 @@ public class ProjectsWsModule extends Module { GhostsAction.class, ProvisionedAction.class, SearchMyProjectsAction.class, - SearchMyProjectsDataLoader.class); + SearchMyProjectsDataLoader.class, + SearchAction.class); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/project/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/project/ws/SearchAction.java new file mode 100644 index 00000000000..3ca4f103486 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/project/ws/SearchAction.java @@ -0,0 +1,154 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.api.server.ws.WebService.Param; +import org.sonar.api.utils.Paging; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.ComponentQuery; +import org.sonar.db.organization.OrganizationDto; +import org.sonar.server.organization.DefaultOrganizationProvider; +import org.sonar.server.user.UserSession; +import org.sonarqube.ws.WsProjects.SearchWsResponse; +import org.sonarqube.ws.client.project.SearchWsRequest; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Optional.ofNullable; +import static org.sonar.api.resources.Qualifiers.PROJECT; +import static org.sonar.api.resources.Qualifiers.VIEW; +import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN; +import static org.sonar.server.ws.WsUtils.writeProtobuf; +import static org.sonarqube.ws.WsProjects.SearchWsResponse.Component; +import static org.sonarqube.ws.WsProjects.SearchWsResponse.newBuilder; +import static org.sonarqube.ws.client.project.ProjectsWsParameters.ACTION_SEARCH; +import static org.sonarqube.ws.client.project.ProjectsWsParameters.MAX_PAGE_SIZE; +import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_ORGANIZATION; +import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_QUALIFIERS; + +public class SearchAction implements ProjectsWsAction { + + private final DbClient dbClient; + private final UserSession userSession; + private final DefaultOrganizationProvider defaultOrganizationProvider; + private final ProjectsWsSupport support; + + public SearchAction(DbClient dbClient, UserSession userSession, DefaultOrganizationProvider defaultOrganizationProvider, ProjectsWsSupport support) { + this.dbClient = dbClient; + this.userSession = userSession; + this.defaultOrganizationProvider = defaultOrganizationProvider; + this.support = support; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction(ACTION_SEARCH) + .setSince("6.3") + .setDescription("Search for projects or views.<br>" + + "Requires 'System Administrator' permission") + .setInternal(true) + .addPagingParams(100, MAX_PAGE_SIZE) + .addSearchQuery("sona", "component names", "component keys") + .setResponseExample(getClass().getResource("search-example.json")) + .setHandler(this); + action.createParam(PARAM_QUALIFIERS) + .setDescription("Comma-separated list of component qualifiers. Filter the results with the specified qualifiers") + .setPossibleValues(PROJECT, VIEW) + .setDefaultValue(PROJECT); + support.addOrganizationParam(action); + } + + @Override + public void handle(Request wsRequest, Response wsResponse) throws Exception { + SearchWsResponse searchWsResponse = doHandle(toSearchWsRequest(wsRequest)); + writeProtobuf(searchWsResponse, wsRequest, wsResponse); + } + + private static SearchWsRequest toSearchWsRequest(Request request) { + return SearchWsRequest.builder() + .setOrganization(request.param(PARAM_ORGANIZATION)) + .setQualifiers(request.mandatoryParamAsStrings(PARAM_QUALIFIERS)) + .setQuery(request.param(Param.TEXT_QUERY)) + .setPage(request.mandatoryParamAsInt(Param.PAGE)) + .setPageSize(request.mandatoryParamAsInt(Param.PAGE_SIZE)).build(); + } + + private SearchWsResponse doHandle(SearchWsRequest request) { + try (DbSession dbSession = dbClient.openSession(false)) { + OrganizationDto organization = support.getOrganization(dbSession, ofNullable(request.getOrganization()).orElseGet(defaultOrganizationProvider.get()::getKey)); + userSession.checkOrganizationPermission(organization.getUuid(), SYSTEM_ADMIN); + + ComponentQuery query = buildQuery(request); + Paging paging = buildPaging(dbSession, request, organization, query); + List<ComponentDto> components = dbClient.componentDao().selectByQuery(dbSession, organization.getUuid(), query, paging.offset(), paging.pageSize()); + return buildResponse(components, organization, paging); + } + } + + private static ComponentQuery buildQuery(SearchWsRequest request) { + List<String> qualifiers = request.getQualifiers(); + return ComponentQuery.builder() + .setNameOrKeyQuery(request.getQuery()) + .setQualifiers(qualifiers.toArray(new String[qualifiers.size()])) + .build(); + } + + private Paging buildPaging(DbSession dbSession, SearchWsRequest request, OrganizationDto organization, ComponentQuery query) { + int total = dbClient.componentDao().countByQuery(dbSession, organization.getUuid(), query); + return Paging.forPageIndex(request.getPage()) + .withPageSize(request.getPageSize()) + .andTotal(total); + } + + private static SearchWsResponse buildResponse(List<ComponentDto> components, OrganizationDto organization, Paging paging) { + SearchWsResponse.Builder responseBuilder = newBuilder(); + responseBuilder.getPagingBuilder() + .setPageIndex(paging.pageIndex()) + .setPageSize(paging.pageSize()) + .setTotal(paging.total()) + .build(); + + components.stream() + .map(dto -> dtoToProject(organization, dto)) + .forEach(responseBuilder::addComponents); + return responseBuilder.build(); + } + + private static Component dtoToProject(OrganizationDto organization, ComponentDto dto) { + checkArgument( + organization.getUuid().equals(dto.getOrganizationUuid()), + "No Organization found for uuid '%s'", + dto.getOrganizationUuid()); + + Component.Builder builder = Component.newBuilder() + .setOrganization(organization.getKey()) + .setId(dto.uuid()) + .setKey(dto.key()) + .setName(dto.name()) + .setQualifier(dto.qualifier()); + return builder.build(); + } + +} diff --git a/server/sonar-server/src/main/resources/org/sonar/server/project/ws/search-example.json b/server/sonar-server/src/main/resources/org/sonar/server/project/ws/search-example.json new file mode 100644 index 00000000000..fca854523c0 --- /dev/null +++ b/server/sonar-server/src/main/resources/org/sonar/server/project/ws/search-example.json @@ -0,0 +1,23 @@ +{ + "paging": { + "pageIndex": 1, + "pageSize": 100, + "total": 2 + }, + "components": [ + { + "organization": "my-org-1", + "id": "project-uuid-1", + "key": "project-key-1", + "name": "Project Name 1", + "qualifier": "TRK" + }, + { + "organization": "my-org-1", + "id": "project-uuid-2", + "key": "project-key-2", + "name": "Project Name 1", + "qualifier": "TRK" + } + ] +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexLoginTest.java b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexLoginTest.java index b294e4ea4e5..c5b30d5db51 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexLoginTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexLoginTest.java @@ -68,4 +68,14 @@ public class ComponentIndexLoginTest extends ComponentIndexTest { authorizationIndexerTester.allowOnlyGroup(project, group); assertSearchResults("sonarqube", project); } + + @Test + public void do_not_check_permissions_when_logged_in_user_is_root() { + userSession.logIn().setRoot(); + ComponentDto project = newProject("sonarqube", "Quality Product"); + indexer.index(project); + // do not give any permissions to that project + + assertSearchResults("sonarqube", project); + } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java b/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java index a27cf1661be..a0b31e5f8e5 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/issue/index/IssueIndexTest.java @@ -1218,6 +1218,15 @@ public class IssueIndexTest { } @Test + public void root_user_is_authorized_to_access_all_issues() { + ComponentDto project = newProjectDto(newOrganizationDto()); + indexIssue(IssueDocTesting.newDoc("I1", project)); + userSessionRule.logIn().setRoot(); + + assertThat(underTest.search(IssueQuery.builder().build(), new SearchOptions()).getDocs()).hasSize(1); + } + + @Test public void search_issues_for_batch_return_needed_fields() { ComponentDto project = newProjectDto(newOrganizationDto(), "PROJECT"); ComponentDto file = newFileDto(project, null).setPath("src/File.xoo"); diff --git a/server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java b/server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java index c6ba98edeb4..435ae83e8b8 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexTest.java @@ -313,6 +313,15 @@ public class ProjectMeasuresIndexTest { } @Test + public void root_user_can_access_all_projects() { + indexForUser(USER1, newDoc(PROJECT1)); + // connecting with a root but not USER1 + userSession.logIn().setRoot(); + + assertResults(new ProjectMeasuresQuery(), PROJECT1); + } + + @Test public void does_not_return_facet_when_no_facets_in_options() throws Exception { index( newDoc(PROJECT1, NCLOC, 10d, COVERAGE_KEY, 30d, MAINTAINABILITY_RATING, 3d) diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/OrganizationCreationImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/OrganizationCreationImplTest.java index 3ca80415832..fa34f3ae17c 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/organization/OrganizationCreationImplTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/organization/OrganizationCreationImplTest.java @@ -19,8 +19,8 @@ */ package org.sonar.server.organization; +import java.util.Collections; import java.util.List; -import java.util.Optional; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -35,6 +35,7 @@ import org.sonar.db.DbSession; import org.sonar.db.DbTester; import org.sonar.db.organization.DefaultTemplates; import org.sonar.db.organization.OrganizationDto; +import org.sonar.db.permission.template.PermissionTemplateCharacteristicDto; import org.sonar.db.permission.template.PermissionTemplateDto; import org.sonar.db.permission.template.PermissionTemplateGroupDto; import org.sonar.db.user.GroupDto; @@ -57,6 +58,7 @@ public class OrganizationCreationImplTest { private static final String SLUG_OF_A_LOGIN = "slug-of-a-login"; private static final String STRING_64_CHARS = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; private static final String A_NAME = "a name"; + private static final int ANYONE_GROUP_ID = 0; private OrganizationCreation.NewOrganization FULL_POPULATED_NEW_ORGANIZATION = newOrganizationBuilder() .setName("a-name") @@ -194,7 +196,19 @@ public class OrganizationCreationImplTest { underTest.create(dbSession, SOME_USER_ID, FULL_POPULATED_NEW_ORGANIZATION); - verifyDefaultTemplate(FULL_POPULATED_NEW_ORGANIZATION.getKey(), FULL_POPULATED_NEW_ORGANIZATION.getName()); + OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, FULL_POPULATED_NEW_ORGANIZATION.getKey()).get(); + GroupDto ownersGroup = dbClient.groupDao().selectByName(dbSession, organization.getUuid(), "Owners").get(); + PermissionTemplateDto defaultTemplate = dbClient.permissionTemplateDao().selectByName(dbSession, organization.getUuid(), "default template"); + assertThat(defaultTemplate.getName()).isEqualTo("Default template"); + assertThat(defaultTemplate.getDescription()).isEqualTo("Default permission template of organization " + FULL_POPULATED_NEW_ORGANIZATION.getName()); + DefaultTemplates defaultTemplates = dbClient.organizationDao().getDefaultTemplates(dbSession, organization.getUuid()).get(); + assertThat(defaultTemplates.getProjectUuid()).isEqualTo(defaultTemplate.getUuid()); + assertThat(defaultTemplates.getViewUuid()).isNull(); + assertThat(dbClient.permissionTemplateDao().selectGroupPermissionsByTemplateId(dbSession, defaultTemplate.getId())) + .extracting(PermissionTemplateGroupDto::getGroupId, PermissionTemplateGroupDto::getPermission) + .containsOnly( + tuple(ownersGroup.getId(), UserRole.ADMIN), tuple(ownersGroup.getId(), UserRole.ISSUE_ADMIN), tuple(ownersGroup.getId(), GlobalPermissions.SCAN_EXECUTION), + tuple(ANYONE_GROUP_ID, UserRole.USER), tuple(ANYONE_GROUP_ID, UserRole.CODEVIEWER)); } @Test @@ -264,7 +278,21 @@ public class OrganizationCreationImplTest { } @Test - public void createForUser_creates_owners_group_with_all_permissions_for_new_organization_and_add_current_user_to_it() throws OrganizationCreation.KeyConflictException { + public void createForUser_gives_all_permissions_for_new_organization_to_current_user() throws OrganizationCreation.KeyConflictException { + UserDto user = dbTester.users().insertUser(dto -> dto.setLogin(A_LOGIN).setName(A_NAME)); + when(organizationValidation.generateKeyFrom(A_LOGIN)).thenReturn(SLUG_OF_A_LOGIN); + mockForSuccessfulInsert(SOME_UUID, SOME_DATE); + enableCreatePersonalOrg(true); + + underTest.createForUser(dbSession, user); + + OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, SLUG_OF_A_LOGIN).get(); + assertThat(dbClient.userPermissionDao().selectGlobalPermissionsOfUser(dbSession, user.getId(), organization.getUuid())) + .containsOnly(GlobalPermissions.ALL.toArray(new String[GlobalPermissions.ALL.size()])); + } + + @Test + public void createForUser_does_not_create_any_group() throws OrganizationCreation.KeyConflictException { UserDto user = dbTester.users().insertUser(dto -> dto.setLogin(A_LOGIN).setName(A_NAME)); when(organizationValidation.generateKeyFrom(A_LOGIN)).thenReturn(SLUG_OF_A_LOGIN); mockForSuccessfulInsert(SOME_UUID, SOME_DATE); @@ -272,14 +300,17 @@ public class OrganizationCreationImplTest { underTest.createForUser(dbSession, user); - verifyGroupOwners(user, SLUG_OF_A_LOGIN, A_NAME); + OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, SLUG_OF_A_LOGIN).get(); + assertThat(dbClient.groupDao().selectByOrganizationUuid(dbSession, organization.getUuid())).isEmpty(); } private void verifyGroupOwners(UserDto user, String organizationKey, String organizationName) { OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, organizationKey).get(); - Optional<GroupDto> groupDtoOptional = dbClient.groupDao().selectByName(dbSession, organization.getUuid(), "Owners"); - assertThat(groupDtoOptional).isNotEmpty(); - GroupDto groupDto = groupDtoOptional.get(); + List<GroupDto> groups = dbClient.groupDao().selectByOrganizationUuid(dbSession, organization.getUuid()); + assertThat(groups) + .extracting(GroupDto::getName) + .containsOnly("Owners"); + GroupDto groupDto = groups.iterator().next(); assertThat(groupDto.getDescription()).isEqualTo("Owners of organization " + organizationName); assertThat(dbClient.groupPermissionDao().selectGlobalPermissionsOfGroup(dbSession, groupDto.getOrganizationUuid(), groupDto.getId())) .containsOnly(GlobalPermissions.ALL.toArray(new String[GlobalPermissions.ALL.size()])); @@ -300,23 +331,20 @@ public class OrganizationCreationImplTest { underTest.createForUser(dbSession, user); - verifyDefaultTemplate(SLUG_OF_A_LOGIN, A_NAME); - } - - private void verifyDefaultTemplate(String organizationKey, String organizationName) { - OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, organizationKey).get(); - GroupDto ownersGroup = dbClient.groupDao().selectByName(dbSession, organization.getUuid(), "Owners").get(); + OrganizationDto organization = dbClient.organizationDao().selectByKey(dbSession, SLUG_OF_A_LOGIN).get(); PermissionTemplateDto defaultTemplate = dbClient.permissionTemplateDao().selectByName(dbSession, organization.getUuid(), "default template"); assertThat(defaultTemplate.getName()).isEqualTo("Default template"); - assertThat(defaultTemplate.getDescription()).isEqualTo("Default permission template of organization " + organizationName); + assertThat(defaultTemplate.getDescription()).isEqualTo("Default permission template of organization " + A_NAME); DefaultTemplates defaultTemplates = dbClient.organizationDao().getDefaultTemplates(dbSession, organization.getUuid()).get(); assertThat(defaultTemplates.getProjectUuid()).isEqualTo(defaultTemplate.getUuid()); assertThat(defaultTemplates.getViewUuid()).isNull(); assertThat(dbClient.permissionTemplateDao().selectGroupPermissionsByTemplateId(dbSession, defaultTemplate.getId())) .extracting(PermissionTemplateGroupDto::getGroupId, PermissionTemplateGroupDto::getPermission) + .containsOnly(tuple(ANYONE_GROUP_ID, UserRole.USER), tuple(ANYONE_GROUP_ID, UserRole.CODEVIEWER)); + assertThat(dbClient.permissionTemplateCharacteristicDao().selectByTemplateIds(dbSession, Collections.singletonList(defaultTemplate.getId()))) + .extracting(PermissionTemplateCharacteristicDto::getWithProjectCreator, PermissionTemplateCharacteristicDto::getPermission) .containsOnly( - tuple(ownersGroup.getId(), UserRole.ADMIN), tuple(ownersGroup.getId(), UserRole.ISSUE_ADMIN), - tuple(0, UserRole.USER), tuple(0, UserRole.CODEVIEWER)); + tuple(true, UserRole.ADMIN), tuple(true, UserRole.ISSUE_ADMIN), tuple(true, GlobalPermissions.SCAN_EXECUTION)); } @Test diff --git a/server/sonar-server/src/test/java/org/sonar/server/organization/ws/CreateActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/organization/ws/CreateActionTest.java index 50ba6976f19..f56fa73f83d 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/organization/ws/CreateActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/organization/ws/CreateActionTest.java @@ -91,7 +91,8 @@ public class CreateActionTest { private OrganizationValidation organizationValidation = new OrganizationValidationImpl(); private OrganizationCreation organizationCreation = new OrganizationCreationImpl(dbClient, system2, uuidFactory, organizationValidation, settings); private TestOrganizationFlags organizationFlags = TestOrganizationFlags.standalone().setEnabled(true); - private CreateAction underTest = new CreateAction(settings, userSession, dbClient, new OrganizationsWsSupport(organizationValidation), organizationValidation, organizationCreation, organizationFlags); + private CreateAction underTest = new CreateAction(settings, userSession, dbClient, new OrganizationsWsSupport(organizationValidation), organizationValidation, + organizationCreation, organizationFlags); private WsActionTester wsTester = new WsActionTester(underTest); @Test @@ -486,7 +487,7 @@ public class CreateActionTest { assertThat(dbClient.permissionTemplateDao().selectGroupPermissionsByTemplateId(dbSession, defaultTemplate.getId())) .extracting(PermissionTemplateGroupDto::getGroupId, PermissionTemplateGroupDto::getPermission) .containsOnly( - tuple(ownersGroup.getId(), UserRole.ADMIN), tuple(ownersGroup.getId(), UserRole.ISSUE_ADMIN), + tuple(ownersGroup.getId(), UserRole.ADMIN), tuple(ownersGroup.getId(), UserRole.ISSUE_ADMIN), tuple(ownersGroup.getId(), GlobalPermissions.SCAN_EXECUTION), tuple(0, UserRole.USER), tuple(0, UserRole.CODEVIEWER)); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/permission/index/AuthorizationTypeSupportTest.java b/server/sonar-server/src/test/java/org/sonar/server/permission/index/AuthorizationTypeSupportTest.java new file mode 100644 index 00000000000..6b1423222c2 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/permission/index/AuthorizationTypeSupportTest.java @@ -0,0 +1,153 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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.permission.index; + +import org.elasticsearch.index.query.HasParentQueryBuilder; +import org.elasticsearch.index.query.MatchAllQueryBuilder; +import org.elasticsearch.index.query.QueryBuilder; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.db.user.GroupDto; +import org.sonar.db.user.GroupTesting; +import org.sonar.server.tester.UserSessionRule; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.test.JsonAssert.assertJson; + +public class AuthorizationTypeSupportTest { + + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + + private AuthorizationTypeSupport underTest = new AuthorizationTypeSupport(userSession); + + @Test + public void createQueryFilter_does_not_include_permission_filters_if_user_is_flagged_as_root() { + userSession.logIn().setRoot(); + + QueryBuilder filter = underTest.createQueryFilter(); + + assertThat(filter).isInstanceOf(MatchAllQueryBuilder.class); + } + + @Test + public void createQueryFilter_sets_filter_on_anyone_group_if_user_is_anonymous() { + userSession.anonymous(); + + HasParentQueryBuilder filter = (HasParentQueryBuilder) underTest.createQueryFilter(); + + assertJson(filter.toString()).isSimilarTo("{" + + " \"has_parent\" : {" + + " \"query\" : {" + + " \"bool\" : {" + + " \"filter\" : {" + + " \"bool\" : {" + + " \"should\" : {" + + " \"term\" : {" + + " \"allowAnyone\" : true" + + " }" + + " }" + + " }" + + " }" + + " }" + + " }," + + " \"parent_type\" : \"authorization\"" + + " }" + + "}"); + } + + @Test + public void createQueryFilter_sets_filter_on_anyone_and_user_id_if_user_is_logged_in_but_has_no_groups() { + userSession.logIn().setUserId(1234); + + HasParentQueryBuilder filter = (HasParentQueryBuilder) underTest.createQueryFilter(); + + assertJson(filter.toString()).isSimilarTo("{" + + " \"has_parent\": {" + + " \"query\": {" + + " \"bool\": {" + + " \"filter\": {" + + " \"bool\": {" + + " \"should\": [" + + " {" + + " \"term\": {" + + " \"allowAnyone\": true" + + " }" + + " }," + + " {" + + " \"term\": {" + + " \"userIds\": 1234" + + " }" + + " }" + + " ]" + + " }" + + " }" + + " }" + + " }," + + " \"parent_type\": \"authorization\"" + + " }" + + "}"); + } + + @Test + public void createQueryFilter_sets_filter_on_anyone_and_user_id_and_group_ids_if_user_is_logged_in_and_has_groups() { + GroupDto group1 = GroupTesting.newGroupDto().setId(10); + GroupDto group2 = GroupTesting.newGroupDto().setId(11); + userSession.logIn().setUserId(1234).setGroups(group1, group2); + + HasParentQueryBuilder filter = (HasParentQueryBuilder) underTest.createQueryFilter(); + + assertJson(filter.toString()).isSimilarTo("{" + + " \"has_parent\": {" + + " \"query\": {" + + " \"bool\": {" + + " \"filter\": {" + + " \"bool\": {" + + " \"should\": [" + + " {" + + " \"term\": {" + + " \"allowAnyone\": true" + + " }" + + " }," + + " {" + + " \"term\": {" + + " \"userIds\": 1234" + + " }" + + " }," + + " {" + + " \"term\": {" + + " \"groupIds\": 10" + + " }" + + " }," + + " {" + + " \"term\": {" + + " \"groupIds\": 11" + + " }" + + " }" + + " ]" + + " }" + + " }" + + " }" + + " }," + + " \"parent_type\": \"authorization\"" + + " }" + + "}"); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java index e6c03e41ebf..abd0abd5105 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/project/ws/ProjectsWsModuleTest.java @@ -30,6 +30,6 @@ public class ProjectsWsModuleTest { public void verify_count_of_added_components() { ComponentContainer container = new ComponentContainer(); new ProjectsWsModule().configure(container); - assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 10); + assertThat(container.size()).isEqualTo(COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER + 11); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/project/ws/SearchActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/project/ws/SearchActionTest.java new file mode 100644 index 00000000000..1aa03aa609f --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/project/ws/SearchActionTest.java @@ -0,0 +1,290 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 com.google.common.base.Joiner; +import com.google.common.base.Throwables; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.server.ws.WebService; +import org.sonar.db.DbTester; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.organization.OrganizationDto; +import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.organization.DefaultOrganizationProvider; +import org.sonar.server.organization.TestDefaultOrganizationProvider; +import org.sonar.server.tester.UserSessionRule; +import org.sonar.server.ws.TestRequest; +import org.sonar.server.ws.WsActionTester; +import org.sonarqube.ws.MediaTypes; +import org.sonarqube.ws.WsProjects.SearchWsResponse; +import org.sonarqube.ws.WsProjects.SearchWsResponse.Component; +import org.sonarqube.ws.client.component.ComponentsWsParameters; +import org.sonarqube.ws.client.project.SearchWsRequest; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.api.server.ws.WebService.Param.PAGE; +import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE; +import static org.sonar.api.server.ws.WebService.Param.TEXT_QUERY; +import static org.sonar.core.permission.GlobalPermissions.QUALITY_PROFILE_ADMIN; +import static org.sonar.core.permission.GlobalPermissions.SYSTEM_ADMIN; +import static org.sonar.core.util.Protobuf.setNullable; +import static org.sonar.db.component.ComponentTesting.newDirectory; +import static org.sonar.db.component.ComponentTesting.newFileDto; +import static org.sonar.db.component.ComponentTesting.newModuleDto; +import static org.sonar.db.component.ComponentTesting.newProjectDto; +import static org.sonar.db.component.ComponentTesting.newView; +import static org.sonar.test.JsonAssert.assertJson; +import static org.sonarqube.ws.MediaTypes.PROTOBUF; +import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_ORGANIZATION; + +public class SearchActionTest { + + private static final String PROJECT_KEY_1 = "project1"; + private static final String PROJECT_KEY_2 = "project2"; + private static final String PROJECT_KEY_3 = "project3"; + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Rule + public UserSessionRule userSession = UserSessionRule.standalone(); + + @Rule + public DbTester db = DbTester.create(); + + private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db); + + private WsActionTester ws = new WsActionTester(new SearchAction(db.getDbClient(), userSession, defaultOrganizationProvider, new ProjectsWsSupport(db.getDbClient()))); + + @Test + public void search_by_key_query() throws IOException { + userSession.addOrganizationPermission(db.getDefaultOrganization(), SYSTEM_ADMIN); + db.components().insertComponents( + newProjectDto(db.getDefaultOrganization()).setKey("project-_%-key"), + newProjectDto(db.getDefaultOrganization()).setKey("project-key-without-escaped-characters")); + + SearchWsResponse response = call(SearchWsRequest.builder().setQuery("project-_%-key").build()); + + assertThat(response.getComponentsList()).extracting(Component::getKey).containsOnly("project-_%-key"); + } + + @Test + public void search_projects_when_no_qualifier_set() throws IOException { + userSession.addOrganizationPermission(db.getDefaultOrganization(), SYSTEM_ADMIN); + db.components().insertComponents( + newProjectDto(db.getDefaultOrganization()).setKey(PROJECT_KEY_1), + newView(db.getDefaultOrganization())); + + SearchWsResponse response = call(SearchWsRequest.builder().build()); + + assertThat(response.getComponentsList()).extracting(Component::getKey).containsOnly(PROJECT_KEY_1); + } + + @Test + public void search_projects() throws IOException { + userSession.addOrganizationPermission(db.getDefaultOrganization(), SYSTEM_ADMIN); + ComponentDto project = newProjectDto(db.getDefaultOrganization()).setKey(PROJECT_KEY_1); + ComponentDto module = newModuleDto(project); + ComponentDto directory = newDirectory(module, "dir"); + ComponentDto file = newFileDto(directory); + db.components().insertComponents( + project, module, directory, file, + newProjectDto(db.getDefaultOrganization()).setKey(PROJECT_KEY_2), + newView(db.getDefaultOrganization())); + + SearchWsResponse response = call(SearchWsRequest.builder().setQualifiers(singletonList("TRK")).build()); + + assertThat(response.getComponentsList()).extracting(Component::getKey).containsOnly(PROJECT_KEY_1, PROJECT_KEY_2); + } + + @Test + public void search_views() throws IOException { + userSession.addOrganizationPermission(db.getDefaultOrganization(), SYSTEM_ADMIN); + db.components().insertComponents( + newProjectDto(db.getDefaultOrganization()).setKey(PROJECT_KEY_1), + newView(db.getDefaultOrganization()).setKey("view1")); + + SearchWsResponse response = call(SearchWsRequest.builder().setQualifiers(singletonList("VW")).build()); + + assertThat(response.getComponentsList()).extracting(Component::getKey).containsOnly("view1"); + } + + @Test + public void search_projects_and_views() throws IOException { + userSession.addOrganizationPermission(db.getDefaultOrganization(), SYSTEM_ADMIN); + db.components().insertComponents( + newProjectDto(db.getDefaultOrganization()).setKey(PROJECT_KEY_1), + newView(db.getDefaultOrganization()).setKey("view1")); + + SearchWsResponse response = call(SearchWsRequest.builder().setQualifiers(asList("TRK", "VW")).build()); + + assertThat(response.getComponentsList()).extracting(Component::getKey).containsOnly(PROJECT_KEY_1, "view1"); + } + + @Test + public void search_on_default_organization_when_no_organization_set() throws IOException { + userSession.addOrganizationPermission(db.getDefaultOrganization(), SYSTEM_ADMIN); + OrganizationDto otherOrganization = db.organizations().insert(); + db.components().insertComponents( + newProjectDto(db.getDefaultOrganization()).setKey(PROJECT_KEY_1), + newProjectDto(db.getDefaultOrganization()).setKey(PROJECT_KEY_2), + newProjectDto(otherOrganization).setKey(PROJECT_KEY_3)); + + SearchWsResponse response = call(SearchWsRequest.builder().build()); + + assertThat(response.getComponentsList()).extracting(Component::getKey).containsOnly(PROJECT_KEY_1, PROJECT_KEY_2); + } + + @Test + public void search_for_projects_on_given_organization() throws IOException { + OrganizationDto organization1 = db.organizations().insert(); + OrganizationDto organization2 = db.organizations().insert(); + userSession.addOrganizationPermission(organization1, SYSTEM_ADMIN); + ComponentDto project1 = newProjectDto(organization1); + ComponentDto project2 = newProjectDto(organization1); + ComponentDto project3 = newProjectDto(organization2); + db.components().insertComponents(project1, project2, project3); + + SearchWsResponse response = call(SearchWsRequest.builder().setOrganization(organization1.getKey()).build()); + + assertThat(response.getComponentsList()).extracting(Component::getKey).containsOnly(project1.key(), project2.key()); + } + + @Test + public void result_is_paginated() throws IOException { + userSession.addOrganizationPermission(db.getDefaultOrganization(), SYSTEM_ADMIN); + List<ComponentDto> componentDtoList = new ArrayList<>(); + for (int i = 1; i <= 9; i++) { + componentDtoList.add(newProjectDto(db.getDefaultOrganization(), "project-uuid-" + i).setKey("project-key-" + i).setName("Project Name " + i)); + } + db.components().insertComponents(componentDtoList.toArray(new ComponentDto[] {})); + + SearchWsResponse response = call(SearchWsRequest.builder().setPage(2).setPageSize(3).build()); + + assertThat(response.getComponentsList()).extracting(Component::getKey).containsExactly("project-key-4", "project-key-5", "project-key-6"); + } + + @Test + public void fail_when_not_system_admin() throws Exception { + userSession.addOrganizationPermission(db.getDefaultOrganization(), QUALITY_PROFILE_ADMIN); + expectedException.expect(ForbiddenException.class); + + call(SearchWsRequest.builder().build()); + } + + @Test + public void fail_on_unknown_organization() throws Exception { + expectedException.expect(NotFoundException.class); + + call(SearchWsRequest.builder().setOrganization("unknown").build()); + } + + @Test + public void fail_on_invalid_qualifier() throws Exception { + userSession.addOrganizationPermission(db.getDefaultOrganization(), QUALITY_PROFILE_ADMIN); + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Value of parameter 'qualifiers' (BRC) must be one of: [TRK, VW]"); + + call(SearchWsRequest.builder().setQualifiers(singletonList("BRC")).build()); + } + + @Test + public void verify_define() { + WebService.Action action = ws.getDef(); + assertThat(action.key()).isEqualTo("search"); + assertThat(action.isPost()).isFalse(); + assertThat(action.description()).isEqualTo("Search for projects or views.<br>Requires 'System Administrator' permission"); + assertThat(action.isInternal()).isTrue(); + assertThat(action.since()).isEqualTo("6.3"); + assertThat(action.handler()).isEqualTo(ws.getDef().handler()); + assertThat(action.params()).hasSize(5); + assertThat(action.responseExample()).isEqualTo(getClass().getResource("search-example.json")); + + WebService.Param organization = action.param("organization"); + Assertions.assertThat(organization.description()).isEqualTo("The key of the organization"); + Assertions.assertThat(organization.isInternal()).isTrue(); + Assertions.assertThat(organization.isRequired()).isFalse(); + Assertions.assertThat(organization.since()).isEqualTo("6.3"); + + WebService.Param qParam = action.param("q"); + assertThat(qParam.isRequired()).isFalse(); + assertThat(qParam.description()).isEqualTo("Limit search to component names or component keys that contain the supplied string."); + + WebService.Param qualifierParam = action.param("qualifiers"); + assertThat(qualifierParam.isRequired()).isFalse(); + assertThat(qualifierParam.description()).isEqualTo("Comma-separated list of component qualifiers. Filter the results with the specified qualifiers"); + assertThat(qualifierParam.possibleValues()).containsOnly("TRK", "VW"); + assertThat(qualifierParam.defaultValue()).isEqualTo("TRK"); + + WebService.Param pParam = action.param("p"); + assertThat(pParam.isRequired()).isFalse(); + assertThat(pParam.defaultValue()).isEqualTo("1"); + assertThat(pParam.description()).isEqualTo("1-based page number"); + + WebService.Param psParam = action.param("ps"); + assertThat(psParam.isRequired()).isFalse(); + assertThat(psParam.defaultValue()).isEqualTo("100"); + assertThat(psParam.description()).isEqualTo("Page size. Must be greater than 0 and less than 500"); + } + + @Test + public void verify_response_example() throws URISyntaxException, IOException { + OrganizationDto organizationDto = db.organizations().insertForKey("my-org-1"); + userSession.addOrganizationPermission(organizationDto, SYSTEM_ADMIN); + db.components().insertComponents( + newProjectDto(organizationDto, "project-uuid-1").setName("Project Name 1").setKey("project-key-1"), + newProjectDto(organizationDto, "project-uuid-2").setName("Project Name 1").setKey("project-key-2")); + + String response = ws.newRequest() + .setMediaType(MediaTypes.JSON) + .setParam(PARAM_ORGANIZATION, organizationDto.getKey()) + .execute().getInput(); + assertJson(response).isSimilarTo(ws.getDef().responseExampleAsString()); + } + + private SearchWsResponse call(SearchWsRequest wsRequest) { + TestRequest request = ws.newRequest() + .setMediaType(PROTOBUF); + setNullable(wsRequest.getOrganization(), organization -> request.setParam(PARAM_ORGANIZATION, organization)); + List<String> qualifiers = wsRequest.getQualifiers(); + if (!qualifiers.isEmpty()) { + request.setParam(ComponentsWsParameters.PARAM_QUALIFIERS, Joiner.on(",").join(qualifiers)); + } + setNullable(wsRequest.getQuery(), query -> request.setParam(TEXT_QUERY, query)); + setNullable(wsRequest.getPage(), page -> request.setParam(PAGE, String.valueOf(page))); + setNullable(wsRequest.getPageSize(), pageSize -> request.setParam(PAGE_SIZE, String.valueOf(pageSize))); + try { + return SearchWsResponse.parseFrom(request.execute().getInputStream()); + } catch (IOException e) { + throw Throwables.propagate(e); + } + } + +} diff --git a/server/sonar-web/src/main/js/api/components.js b/server/sonar-web/src/main/js/api/components.js index e47820b33f7..53007d59a45 100644 --- a/server/sonar-web/src/main/js/api/components.js +++ b/server/sonar-web/src/main/js/api/components.js @@ -21,7 +21,7 @@ import { getJSON, postJSON, post } from '../helpers/request'; export function getComponents (data?: Object) { - const url = '/api/components/search'; + const url = '/api/projects/search'; return getJSON(url, data); } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/utils/Paging.java b/sonar-plugin-api/src/main/java/org/sonar/api/utils/Paging.java index 4cc2da8bb21..2f650bffe61 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/utils/Paging.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/utils/Paging.java @@ -19,6 +19,8 @@ */ package org.sonar.api.utils; +import static com.google.common.base.Preconditions.checkArgument; + /** * @since 3.6 */ @@ -29,16 +31,9 @@ public class Paging { private final int total; private Paging(int pageSize, int pageIndex, int total) { - if (pageSize < 1) { - throw new IllegalArgumentException("Page size must be strictly positive. Got " + pageSize); - } - if (pageIndex < 1) { - throw new IllegalArgumentException("Page index must be strictly positive. Got " + pageIndex); - } - if (total < 0) { - throw new IllegalArgumentException("Total items must be positive. Got " + total); - } - + checkArgument(pageSize >= 1, "Page size must be strictly positive. Got %s", pageSize); + checkArgument(pageIndex >= 1, "Page index must be strictly positive. Got %s", pageIndex); + checkArgument(total >= 0, "Total items must be positive. Got %s", total); this.pageSize = pageSize; this.pageIndex = pageIndex; this.total = total; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java index 7bbb1247828..332a976d76c 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectScanContainer.java @@ -238,6 +238,10 @@ public class ProjectScanContainer extends ComponentContainer { if (StringUtils.isNotEmpty(organization)) { LOG.info("Organization key: {}", organization); } + String branch = tree.root().definition().getBranch(); + if (branch != null) { + LOG.info("Branch key: {}", branch); + } LOG.debug("Start recursive analysis of project modules"); scanRecursively(tree, tree.root()); diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/fs/FileSystemMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/fs/FileSystemMediumTest.java index 64a3018c6a3..96e8df3f64d 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/fs/FileSystemMediumTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/fs/FileSystemMediumTest.java @@ -135,6 +135,7 @@ public class FileSystemMediumTest { public void logProjectKeyAndOrganizationKey() throws IOException { builder = createBuilder(); builder.put("sonar.organization", "my org"); + builder.put("sonar.branch", ""); File srcDir = new File(baseDir, "src"); srcDir.mkdir(); @@ -149,8 +150,29 @@ public class FileSystemMediumTest { assertThat(logs.getAllAsString()).contains("Project key: com.foo.project"); assertThat(logs.getAllAsString()).contains("Organization key: my org"); + assertThat(logs.getAllAsString()).doesNotContain("Branch key"); } - + + @Test + public void logBranchKey() throws IOException { + builder = createBuilder(); + builder.put("sonar.branch", "my-branch"); + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + File xooFile = new File(srcDir, "sample.xoo"); + FileUtils.write(xooFile, "Sample xoo\ncontent"); + + tester.newTask() + .properties(builder + .put("sonar.sources", "src") + .build()) + .start(); + + assertThat(logs.getAllAsString()).contains("Project key: com.foo.project"); + assertThat(logs.getAllAsString()).contains("Branch key: my-branch"); + } + @Test public void dontLogInvalidOrganization() throws IOException { builder = createBuilder(); @@ -168,6 +190,7 @@ public class FileSystemMediumTest { assertThat(logs.getAllAsString()).contains("Project key: com.foo.project"); assertThat(logs.getAllAsString()).doesNotContain("Organization key"); + assertThat(logs.getAllAsString()).doesNotContain("Branch key"); } @Test diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsService.java index e3142f248bd..b2646fa7ccc 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsService.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsService.java @@ -19,16 +19,23 @@ */ package org.sonarqube.ws.client.project; +import com.google.common.base.Joiner; +import org.sonarqube.ws.WsProjects; import org.sonarqube.ws.WsProjects.CreateWsResponse; import org.sonarqube.ws.client.BaseService; +import org.sonarqube.ws.client.GetRequest; import org.sonarqube.ws.client.PostRequest; import org.sonarqube.ws.client.WsConnector; +import static org.sonar.api.server.ws.WebService.Param.*; import static org.sonarqube.ws.client.project.ProjectsWsParameters.ACTION_CREATE; +import static org.sonarqube.ws.client.project.ProjectsWsParameters.ACTION_SEARCH; import static org.sonarqube.ws.client.project.ProjectsWsParameters.CONTROLLER; import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_BRANCH; import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NAME; +import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_ORGANIZATION; import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_PROJECT; +import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_QUALIFIERS; /** * Maps web service {@code api/projects}. @@ -47,7 +54,7 @@ public class ProjectsService extends BaseService { */ public CreateWsResponse create(CreateRequest project) { PostRequest request = new PostRequest(path(ACTION_CREATE)) - .setParam("organization", project.getOrganization()) + .setParam(PARAM_ORGANIZATION, project.getOrganization()) .setParam(PARAM_PROJECT, project.getKey()) .setParam(PARAM_NAME, project.getName()) .setParam(PARAM_BRANCH, project.getBranch()); @@ -62,4 +69,14 @@ public class ProjectsService extends BaseService { .setParam("id", request.getId()) .setParam("key", request.getKey())); } + + public WsProjects.SearchWsResponse search(SearchWsRequest request) { + GetRequest get = new GetRequest(path(ACTION_SEARCH)) + .setParam(PARAM_ORGANIZATION, request.getOrganization()) + .setParam(PARAM_QUALIFIERS, Joiner.on(",").join(request.getQualifiers())) + .setParam(TEXT_QUERY, request.getQuery()) + .setParam(PAGE, request.getPage()) + .setParam(PAGE_SIZE, request.getPageSize()); + return call(get, WsProjects.SearchWsResponse.parser()); + } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsWsParameters.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsWsParameters.java index 713618cbdba..b735334df8f 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsWsParameters.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/project/ProjectsWsParameters.java @@ -21,15 +21,20 @@ package org.sonarqube.ws.client.project; public class ProjectsWsParameters { + public static final int MAX_PAGE_SIZE = 500; + public static final String CONTROLLER = "api/projects"; public static final String ACTION_CREATE = "create"; public static final String ACTION_INDEX = "index"; + public static final String ACTION_SEARCH = "search"; public static final String PARAM_PROJECT = "project"; public static final String PARAM_PROJECT_ID = "projectId"; public static final String PARAM_NAME = "name"; public static final String PARAM_BRANCH = "branch"; + public static final String PARAM_ORGANIZATION = "organization"; + public static final String PARAM_QUALIFIERS = "qualifiers"; private ProjectsWsParameters() { // static utils only diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/project/SearchWsRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/project/SearchWsRequest.java new file mode 100644 index 00000000000..0bb627a9c61 --- /dev/null +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/project/SearchWsRequest.java @@ -0,0 +1,113 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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.sonarqube.ws.client.project; + +import java.util.ArrayList; +import java.util.List; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Objects.requireNonNull; +import static org.sonarqube.ws.client.project.ProjectsWsParameters.MAX_PAGE_SIZE; + +public class SearchWsRequest { + + private final String organization; + private final String query; + private final List<String> qualifiers; + private final Integer page; + private final Integer pageSize; + + public SearchWsRequest(Builder builder) { + this.organization = builder.organization; + this.query = builder.query; + this.qualifiers = builder.qualifiers; + this.page = builder.page; + this.pageSize = builder.pageSize; + } + + @CheckForNull + public String getOrganization() { + return organization; + } + + public List<String> getQualifiers() { + return qualifiers; + } + + @CheckForNull + public Integer getPage() { + return page; + } + + @CheckForNull + public Integer getPageSize() { + return pageSize; + } + + @CheckForNull + public String getQuery() { + return query; + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private String organization; + private List<String> qualifiers = new ArrayList<>(); + private Integer page; + private Integer pageSize; + private String query; + + public Builder setOrganization(@Nullable String organization) { + this.organization = organization; + return this; + } + + public Builder setQualifiers(List<String> qualifiers) { + this.qualifiers = requireNonNull(qualifiers, "Qualifiers cannot be null"); + return this; + } + + public Builder setPage(@Nullable Integer page) { + this.page = page; + return this; + } + + public Builder setPageSize(@Nullable Integer pageSize) { + this.pageSize = pageSize; + return this; + } + + public Builder setQuery(@Nullable String query) { + this.query = query; + return this; + } + + public SearchWsRequest build() { + checkArgument(pageSize == null || pageSize <= MAX_PAGE_SIZE, "Page size must not be greater than %s", MAX_PAGE_SIZE); + return new SearchWsRequest(this); + } + } + +} diff --git a/sonar-ws/src/main/protobuf/ws-projects.proto b/sonar-ws/src/main/protobuf/ws-projects.proto index 64fb78379ac..7dcb6260857 100644 --- a/sonar-ws/src/main/protobuf/ws-projects.proto +++ b/sonar-ws/src/main/protobuf/ws-projects.proto @@ -57,3 +57,17 @@ message CreateWsResponse { } } +// WS api/projects/search +message SearchWsResponse { + optional sonarqube.ws.commons.Paging paging = 1; + repeated Component components = 2; + + message Component { + optional string organization = 1; + optional string id = 2; + optional string key = 3; + optional string name = 4; + optional string qualifier = 5; + } +} + diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/project/ProjectsServiceTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/project/ProjectsServiceTest.java index b2fca0a05ba..2576eb3eb09 100644 --- a/sonar-ws/src/test/java/org/sonarqube/ws/client/project/ProjectsServiceTest.java +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/project/ProjectsServiceTest.java @@ -25,9 +25,12 @@ import org.sonarqube.ws.WsProjects; import org.sonarqube.ws.client.ServiceTester; import org.sonarqube.ws.client.WsConnector; +import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.data.MapEntry.entry; import static org.mockito.Mockito.mock; +import static org.sonar.api.server.ws.WebService.Param.PAGE; +import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE; public class ProjectsServiceTest { @@ -96,4 +99,24 @@ public class ProjectsServiceTest { assertThat(serviceTester.getPostRequest().getPath()).isEqualTo("api/projects/delete"); assertThat(serviceTester.getPostRequest().getParams()).containsOnly(entry("key", "project_key")); } + + @Test + public void search() { + underTest.search(SearchWsRequest.builder() + .setOrganization("default") + .setQuery("project") + .setQualifiers(asList("TRK", "VW")) + .setPage(3) + .setPageSize(10) + .build()); + + serviceTester.assertThat(serviceTester.getGetRequest()) + .hasPath("search") + .hasParam("organization", "default") + .hasParam("q", "project") + .hasParam("qualifiers", "TRK,VW") + .hasParam(PAGE, 3) + .hasParam(PAGE_SIZE, 10) + .andNoOtherParam(); + } } diff --git a/sonar-ws/src/test/java/org/sonarqube/ws/client/project/SearchWsRequestTest.java b/sonar-ws/src/test/java/org/sonarqube/ws/client/project/SearchWsRequestTest.java new file mode 100644 index 00000000000..89f27374b0a --- /dev/null +++ b/sonar-ws/src/test/java/org/sonarqube/ws/client/project/SearchWsRequestTest.java @@ -0,0 +1,60 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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.sonarqube.ws.client.project; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; + +public class SearchWsRequestTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void create_request() throws Exception { + SearchWsRequest underTest = SearchWsRequest.builder() + .setOrganization("orga") + .setQuery("project") + .setQualifiers(asList("TRK", "VW")) + .setPage(5) + .setPageSize(10) + .build(); + + assertThat(underTest.getOrganization()).isEqualTo("orga"); + assertThat(underTest.getQuery()).isEqualTo("project"); + assertThat(underTest.getQualifiers()).containsOnly("TRK", "VW"); + assertThat(underTest.getPage()).isEqualTo(5); + assertThat(underTest.getPageSize()).isEqualTo(10); + } + + @Test + public void fail_when_page_size_is_greather_then_500() throws Exception { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Page size must not be greater than 500"); + + SearchWsRequest.builder() + .setPageSize(10000) + .build(); + } +} |