/*
* 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.component.ws;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.io.IOException;
import java.util.Date;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.apache.commons.io.IOUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.Mockito;
import org.sonar.api.i18n.I18n;
import org.sonar.api.resources.Qualifiers;
import org.sonar.api.server.ws.WebService.Param;
import org.sonar.api.utils.System2;
import org.sonar.api.web.UserRole;
import org.sonar.db.DbClient;
import org.sonar.db.DbTester;
import org.sonar.db.component.ComponentDbTester;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.component.ComponentTesting;
import org.sonar.db.component.ResourceTypesRule;
import org.sonar.db.component.SnapshotDto;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.server.component.ComponentFinder;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.WsActionTester;
import org.sonar.test.JsonAssert;
import org.sonarqube.ws.WsComponents.TreeWsResponse;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.db.component.ComponentTesting.newDirectory;
import static org.sonar.db.component.ComponentTesting.newModuleDto;
import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
import static org.sonar.db.component.ComponentTesting.newProjectCopy;
import static org.sonar.db.component.ComponentTesting.newSubView;
import static org.sonar.db.component.ComponentTesting.newView;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_COMPONENT_ID;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_QUALIFIERS;
import static org.sonarqube.ws.client.component.ComponentsWsParameters.PARAM_STRATEGY;
public class TreeActionTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Rule
public UserSessionRule userSession = UserSessionRule.standalone();
@Rule
public DbTester db = DbTester.create(System2.INSTANCE);
private ResourceTypesRule resourceTypes = new ResourceTypesRule();
private ComponentDbTester componentDb = new ComponentDbTester(db);
private DbClient dbClient = db.getDbClient();
private WsActionTester ws;
@Before
public void setUp() {
ws = new WsActionTester(new TreeAction(dbClient, new ComponentFinder(dbClient), resourceTypes, userSession, Mockito.mock(I18n.class)));
resourceTypes.setChildrenQualifiers(Qualifiers.MODULE, Qualifiers.FILE, Qualifiers.DIRECTORY);
resourceTypes.setLeavesQualifiers(Qualifiers.FILE, Qualifiers.UNIT_TEST_FILE);
}
@Test
public void json_example() throws IOException {
ComponentDto project = initJsonExampleComponents();
logInWithBrowsePermission(project);
String response = ws.newRequest()
.setParam(PARAM_COMPONENT_ID, project.uuid())
.execute().getInput();
JsonAssert.assertJson(response)
.withStrictArrayOrder()
.isSimilarTo(getClass().getResource("tree-example.json"));
}
@Test
public void return_children() throws IOException {
ComponentDto project = newPrivateProjectDto(db.organizations().insert(), "project-uuid");
componentDb.insertProjectAndSnapshot(project);
ComponentDto module = newModuleDto("module-uuid-1", project);
componentDb.insertComponent(module);
componentDb.insertComponent(newFileDto(project, 1));
for (int i = 2; i <= 9; i++) {
componentDb.insertComponent(newFileDto(module, i));
}
ComponentDto directory = newDirectory(module, "directory-path-1");
componentDb.insertComponent(directory);
componentDb.insertComponent(newFileDto(module, directory, 10));
db.commit();
logInWithBrowsePermission(project);
TreeWsResponse response = ws.newRequest()
.setParam(PARAM_STRATEGY, "children")
.setParam(PARAM_COMPONENT_ID, "module-uuid-1")
.setParam(Param.PAGE, "2")
.setParam(Param.PAGE_SIZE, "3")
.setParam(Param.TEXT_QUERY, "file-name")
.setParam(Param.ASCENDING, "false")
.setParam(Param.SORT, "name").executeProtobuf(TreeWsResponse.class);
assertThat(response.getComponentsCount()).isEqualTo(3);
assertThat(response.getPaging().getTotal()).isEqualTo(8);
assertThat(response.getComponentsList()).extracting("id").containsExactly("file-uuid-6", "file-uuid-5", "file-uuid-4");
}
@Test
public void return_descendants() throws IOException {
ComponentDto project = newPrivateProjectDto(db.getDefaultOrganization(), "project-uuid");
SnapshotDto projectSnapshot = componentDb.insertProjectAndSnapshot(project);
ComponentDto module = newModuleDto("module-uuid-1", project);
componentDb.insertComponent(module);
componentDb.insertComponent(newFileDto(project, 10));
for (int i = 2; i <= 9; i++) {
componentDb.insertComponent(newFileDto(module, i));
}
ComponentDto directory = newDirectory(module, "directory-path-1");
componentDb.insertComponent(directory);
componentDb.insertComponent(newFileDto(module, directory, 1));
db.commit();
logInWithBrowsePermission(project);
TreeWsResponse response = ws.newRequest()
.setParam(PARAM_STRATEGY, "all")
.setParam(PARAM_COMPONENT_ID, "module-uuid-1")
.setParam(Param.PAGE, "2")
.setParam(Param.PAGE_SIZE, "3")
.setParam(Param.TEXT_QUERY, "file-name")
.setParam(Param.ASCENDING, "true")
.setParam(Param.SORT, "path").executeProtobuf(TreeWsResponse.class);
assertThat(response.getComponentsCount()).isEqualTo(3);
assertThat(response.getPaging().getTotal()).isEqualTo(9);
assertThat(response.getComponentsList()).extracting("id").containsExactly("file-uuid-4", "file-uuid-5", "file-uuid-6");
}
@Test
public void filter_descendants_by_qualifier() throws IOException {
ComponentDto project = newPrivateProjectDto(db.organizations().insert(), "project-uuid");
componentDb.insertProjectAndSnapshot(project);
componentDb.insertComponent(newFileDto(project, 1));
componentDb.insertComponent(newFileDto(project, 2));
componentDb.insertComponent(newModuleDto("module-uuid-1", project));
db.commit();
logInWithBrowsePermission(project);
TreeWsResponse response = ws.newRequest()
.setParam(PARAM_STRATEGY, "all")
.setParam(PARAM_QUALIFIERS, Qualifiers.FILE)
.setParam(PARAM_COMPONENT_ID, "project-uuid").executeProtobuf(TreeWsResponse.class);
assertThat(response.getComponentsList()).extracting("id").containsExactly("file-uuid-1", "file-uuid-2");
}
@Test
public void return_leaves() throws IOException {
ComponentDto project = newPrivateProjectDto(db.getDefaultOrganization(), "project-uuid");
componentDb.insertProjectAndSnapshot(project);
ComponentDto module = newModuleDto("module-uuid-1", project);
componentDb.insertComponent(module);
componentDb.insertComponent(newFileDto(project, 1));
componentDb.insertComponent(newFileDto(module, 2));
ComponentDto directory = newDirectory(project, "directory-path-1");
componentDb.insertComponent(directory);
componentDb.insertComponent(newFileDto(module, directory, 3));
db.commit();
logInWithBrowsePermission(project);
TreeWsResponse response = ws.newRequest()
.setParam(PARAM_STRATEGY, "leaves")
.setParam(PARAM_COMPONENT_ID, "project-uuid")
.setParam(PARAM_QUALIFIERS, Qualifiers.FILE).executeProtobuf(TreeWsResponse.class);
assertThat(response.getComponentsCount()).isEqualTo(3);
assertThat(response.getPaging().getTotal()).isEqualTo(3);
assertThat(response.getComponentsList()).extracting("id").containsExactly("file-uuid-1", "file-uuid-2", "file-uuid-3");
}
@Test
public void sort_descendants_by_qualifier() throws IOException {
ComponentDto project = newPrivateProjectDto(db.organizations().insert(), "project-uuid");
componentDb.insertProjectAndSnapshot(project);
componentDb.insertComponent(newFileDto(project, 1));
componentDb.insertComponent(newFileDto(project, 2));
ComponentDto module = newModuleDto("module-uuid-1", project);
componentDb.insertComponent(module);
componentDb.insertComponent(newDirectory(project, "path/directory/", "directory-uuid-1"));
db.commit();
logInWithBrowsePermission(project);
TreeWsResponse response = ws.newRequest()
.setParam(PARAM_STRATEGY, "all")
.setParam(Param.SORT, "qualifier, name")
.setParam(PARAM_COMPONENT_ID, "project-uuid").executeProtobuf(TreeWsResponse.class);
assertThat(response.getComponentsList()).extracting("id").containsExactly("module-uuid-1", "path/directory/", "file-uuid-1", "file-uuid-2");
}
@Test
public void return_children_of_a_view() {
OrganizationDto organizationDto = db.organizations().insert();
ComponentDto view = newView(organizationDto, "view-uuid");
componentDb.insertViewAndSnapshot(view);
ComponentDto project = newPrivateProjectDto(organizationDto, "project-uuid-1").setName("project-name").setKey("project-key-1");
componentDb.insertProjectAndSnapshot(project);
componentDb.insertComponent(newProjectCopy("project-uuid-1-copy", project, view));
componentDb.insertComponent(newSubView(view, "sub-view-uuid", "sub-view-key").setName("sub-view-name"));
db.commit();
userSession.logIn()
.registerComponents(view, project);
TreeWsResponse response = ws.newRequest()
.setParam(PARAM_STRATEGY, "children")
.setParam(PARAM_COMPONENT_ID, "view-uuid")
.setParam(Param.TEXT_QUERY, "name").executeProtobuf(TreeWsResponse.class);
assertThat(response.getComponentsList()).extracting("id").containsExactly("project-uuid-1-copy", "sub-view-uuid");
assertThat(response.getComponentsList()).extracting("refId").containsExactly("project-uuid-1", "");
assertThat(response.getComponentsList()).extracting("refKey").containsExactly("project-key-1", "");
}
@Test
public void response_is_empty_on_provisioned_projects() {
ComponentDto project = componentDb.insertComponent(newPrivateProjectDto(db.getDefaultOrganization(), "project-uuid"));
logInWithBrowsePermission(project);
TreeWsResponse response = ws.newRequest()
.setParam(PARAM_COMPONENT_ID, "project-uuid").executeProtobuf(TreeWsResponse.class);
assertThat(response.getBaseComponent().getId()).isEqualTo("project-uuid");
assertThat(response.getComponentsList()).isEmpty();
assertThat(response.getPaging().getTotal()).isEqualTo(0);
assertThat(response.getPaging().getPageSize()).isEqualTo(100);
assertThat(response.getPaging().getPageIndex()).isEqualTo(1);
}
@Test
public void return_projects_composing_a_view() {
ComponentDto project = newPrivateProjectDto(db.organizations().insert(), "project-uuid");
componentDb.insertProjectAndSnapshot(project);
ComponentDto view = newView(db.getDefaultOrganization(), "view-uuid");
componentDb.insertViewAndSnapshot(view);
componentDb.insertComponent(newProjectCopy("project-copy-uuid", project, view));
userSession.logIn()
.registerComponents(project, view);
TreeWsResponse response = ws.newRequest().setParam(PARAM_COMPONENT_ID, view.uuid()).executeProtobuf(TreeWsResponse.class);
assertThat(response.getBaseComponent().getId()).isEqualTo(view.uuid());
assertThat(response.getComponentsCount()).isEqualTo(1);
assertThat(response.getComponents(0).getId()).isEqualTo("project-copy-uuid");
assertThat(response.getComponents(0).getRefId()).isEqualTo("project-uuid");
}
@Test
public void fail_when_not_enough_privileges() {
ComponentDto project = componentDb.insertComponent(newPrivateProjectDto(db.organizations().insert(), "project-uuid"));
userSession.logIn()
.addProjectPermission(UserRole.CODEVIEWER, project);
db.commit();
expectedException.expect(ForbiddenException.class);
ws.newRequest()
.setParam(PARAM_COMPONENT_ID, "project-uuid")
.execute();
}
@Test
public void fail_when_page_size_above_500() {
expectedException.expect(BadRequestException.class);
expectedException.expectMessage("The 'ps' parameter must be less than 500");
componentDb.insertComponent(newPrivateProjectDto(db.getDefaultOrganization(), "project-uuid"));
db.commit();
ws.newRequest()
.setParam(PARAM_COMPONENT_ID, "project-uuid")
.setParam(Param.PAGE_SIZE, "501")
.execute();
}
@Test
public void fail_when_search_query_has_less_than_3_characters() {
expectedException.expect(BadRequestException.class);
expectedException.expectMessage("The 'q' parameter must have at least 3 characters");
componentDb.insertComponent(newPrivateProjectDto(db.organizations().insert(), "project-uuid"));
db.commit();
ws.newRequest()
.setParam(PARAM_COMPONENT_ID, "project-uuid")
.setParam(Param.TEXT_QUERY, "fi")
.execute();
}
@Test
public void fail_when_sort_is_unknown() {
expectedException.expect(IllegalArgumentException.class);
componentDb.insertComponent(newPrivateProjectDto(db.getDefaultOrganization(), "project-uuid"));
db.commit();
ws.newRequest()
.setParam(PARAM_COMPONENT_ID, "project-uuid")
.setParam(Param.SORT, "unknown-sort")
.execute();
}
@Test
public void fail_when_strategy_is_unknown() {
expectedException.expect(IllegalArgumentException.class);
componentDb.insertComponent(newPrivateProjectDto(db.organizations().insert(), "project-uuid"));
db.commit();
ws.newRequest()
.setParam(PARAM_COMPONENT_ID, "project-uuid")
.setParam(PARAM_STRATEGY, "unknown-strategy")
.execute();
}
@Test
public void fail_when_base_component_not_found() {
expectedException.expect(NotFoundException.class);
ws.newRequest()
.setParam(PARAM_COMPONENT_ID, "project-uuid")
.execute();
}
@Test
public void fail_when_no_base_component_parameter() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Either 'componentId' or 'component' must be provided, not both");
ws.newRequest().execute();
}
private static ComponentDto newFileDto(ComponentDto moduleOrProject, @Nullable ComponentDto directory, int i) {
return ComponentTesting.newFileDto(moduleOrProject, directory, "file-uuid-" + i)
.setName("file-name-" + i)
.setKey("file-key-" + i)
.setPath("file-path-" + i);
}
private static ComponentDto newFileDto(ComponentDto moduleOrProject, int i) {
return newFileDto(moduleOrProject, null, i);
}
private ComponentDto initJsonExampleComponents() throws IOException {
OrganizationDto organizationDto = db.organizations().insertForKey("my-org-1");
ComponentDto project = newPrivateProjectDto(organizationDto, "MY_PROJECT_ID")
.setKey("MY_PROJECT_KEY")
.setName("Project Name");
componentDb.insertProjectAndSnapshot(project);
Date now = new Date();
JsonParser jsonParser = new JsonParser();
JsonElement jsonTree = jsonParser.parse(IOUtils.toString(getClass().getResource("tree-example.json"), UTF_8));
JsonArray components = jsonTree.getAsJsonObject().getAsJsonArray("components");
for (JsonElement componentAsJsonElement : components) {
JsonObject componentAsJsonObject = componentAsJsonElement.getAsJsonObject();
String uuid = getJsonField(componentAsJsonObject, "id");
componentDb.insertComponent(ComponentTesting.newChildComponent(uuid, project, project)
.setKey(getJsonField(componentAsJsonObject, "key"))
.setName(getJsonField(componentAsJsonObject, "name"))
.setLanguage(getJsonField(componentAsJsonObject, "language"))
.setPath(getJsonField(componentAsJsonObject, "path"))
.setQualifier(getJsonField(componentAsJsonObject, "qualifier"))
.setDescription(getJsonField(componentAsJsonObject, "description"))
.setEnabled(true)
.setCreatedAt(now));
}
db.commit();
return project;
}
@CheckForNull
private static String getJsonField(JsonObject jsonObject, String field) {
JsonElement jsonElement = jsonObject.get(field);
return jsonElement == null ? null : jsonElement.getAsString();
}
private void logInWithBrowsePermission(ComponentDto project) {
userSession.logIn().addProjectPermission(UserRole.USER, project);
}
}