/*
* 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 javax.annotation.Nullable;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.config.MapSettings;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.System2;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.DbTester;
import org.sonar.db.component.ComponentDbTester;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.component.ComponentTesting;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.server.component.ComponentFinder;
import org.sonar.server.component.ComponentService;
import org.sonar.server.component.index.ComponentIndexDefinition;
import org.sonar.server.es.EsTester;
import org.sonar.server.exceptions.BadRequestException;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.measure.index.ProjectMeasuresIndexDefinition;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.ws.TestRequest;
import org.sonar.server.ws.WsActionTester;
import org.sonarqube.ws.WsProjects.BulkUpdateKeyWsResponse;
import org.sonarqube.ws.WsProjects.BulkUpdateKeyWsResponse.Key;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.sonar.db.component.ComponentTesting.newFileDto;
import static org.sonar.db.component.ComponentTesting.newModuleDto;
import static org.sonar.test.JsonAssert.assertJson;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_DRY_RUN;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_FROM;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_PROJECT;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_PROJECT_ID;
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_TO;
public class BulkUpdateKeyActionTest {
private static final String MY_PROJECT_KEY = "my_project";
private static final String FROM = "my_";
private static final String TO = "your_";
private System2 system2 = System2.INSTANCE;
@Rule
public ExpectedException expectedException = ExpectedException.none();
@Rule
public UserSessionRule userSession = UserSessionRule.standalone();
@Rule
public EsTester es = new EsTester(new ProjectMeasuresIndexDefinition(new MapSettings()),
new ComponentIndexDefinition(new MapSettings()));
@Rule
public DbTester db = DbTester.create(system2);
private ComponentDbTester componentDb = new ComponentDbTester(db);
private DbClient dbClient = db.getDbClient();
private ComponentFinder componentFinder = new ComponentFinder(dbClient);
private ComponentService componentService = mock(ComponentService.class);
private WsActionTester ws = new WsActionTester(
new BulkUpdateKeyAction(dbClient, componentFinder, componentService, userSession));
@Before
public void setUp() {
userSession.logIn().setRoot();
}
@Test
public void json_example() {
OrganizationDto organizationDto = db.organizations().insert();
ComponentDto project = componentDb.insertComponent(ComponentTesting.newPrivateProjectDto(organizationDto).setKey("my_project"));
componentDb.insertComponent(newModuleDto(project).setKey("my_project:module_1"));
ComponentDto anotherProject = componentDb.insertComponent(ComponentTesting.newPrivateProjectDto(organizationDto).setKey("another_project"));
componentDb.insertComponent(newModuleDto(anotherProject).setKey("my_new_project:module_1"));
ComponentDto module2 = componentDb.insertComponent(newModuleDto(project).setKey("my_project:module_2"));
componentDb.insertComponent(newFileDto(module2, null));
String result = ws.newRequest()
.setParam(PARAM_PROJECT, "my_project")
.setParam(PARAM_FROM, "my_")
.setParam(PARAM_TO, "my_new_")
.setParam(PARAM_DRY_RUN, String.valueOf(true))
.execute().getInput();
assertJson(result).withStrictArrayOrder().isSimilarTo(getClass().getResource("bulk_update_key-example.json"));
}
@Test
public void dry_run_by_key() {
insertMyProject();
BulkUpdateKeyWsResponse result = callDryRunByKey(MY_PROJECT_KEY, FROM, TO);
assertThat(result.getKeysCount()).isEqualTo(1);
assertThat(result.getKeys(0).getNewKey()).isEqualTo("your_project");
}
@Test
public void bulk_update_project_key() {
ComponentDto project = insertMyProject();
ComponentDto module = componentDb.insertComponent(newModuleDto(project).setKey("my_project:root:module"));
ComponentDto inactiveModule = componentDb.insertComponent(newModuleDto(project).setKey("my_project:root:inactive_module").setEnabled(false));
ComponentDto file = componentDb.insertComponent(newFileDto(module, null).setKey("my_project:root:module:src/File.xoo"));
ComponentDto inactiveFile = componentDb.insertComponent(newFileDto(module, null).setKey("my_project:root:module:src/InactiveFile.xoo").setEnabled(false));
BulkUpdateKeyWsResponse result = callByUuid(project.uuid(), FROM, TO);
assertThat(result.getKeysCount()).isEqualTo(2);
assertThat(result.getKeysList()).extracting(Key::getKey, Key::getNewKey, Key::getDuplicate)
.containsExactly(
tuple(project.key(), "your_project", false),
tuple(module.key(), "your_project:root:module", false));
verify(componentService).bulkUpdateKey(any(DbSession.class), eq(project.uuid()), eq(FROM), eq(TO));
}
@Test
public void bulk_update_provisioned_project_key() {
String newKey = "provisionedProject2";
ComponentDto provisionedProject = componentDb.insertPrivateProject();
callByKey(provisionedProject.key(), provisionedProject.getKey(), newKey);
verify(componentService).bulkUpdateKey(any(DbSession.class), eq(provisionedProject.uuid()), eq(provisionedProject.getKey()), eq(newKey));
}
@Test
public void fail_to_bulk_if_a_component_already_exists_with_the_same_key() {
componentDb.insertComponent(ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization()).setKey("my_project"));
componentDb.insertComponent(ComponentTesting.newPrivateProjectDto(db.getDefaultOrganization()).setKey("your_project"));
expectedException.expect(BadRequestException.class);
expectedException.expectMessage("Impossible to update key: a component with key \"your_project\" already exists.");
callByKey("my_project", "my_", "your_");
}
@Test
public void fail_to_bulk_update_with_invalid_new_key() {
insertMyProject();
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Malformed key for 'my?project'. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.");
callByKey(MY_PROJECT_KEY, FROM, "my?");
}
@Test
public void fail_to_dry_bulk_update_with_invalid_new_key() {
insertMyProject();
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Malformed key for 'my?project'. Allowed characters are alphanumeric, '-', '_', '.' and ':', with at least one non-digit.");
callDryRunByKey(MY_PROJECT_KEY, FROM, "my?");
}
@Test
public void fail_to_bulk_update_if_not_project_or_module() {
ComponentDto project = insertMyProject();
ComponentDto file = componentDb.insertComponent(newFileDto(project, null));
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("Component updated must be a module or a key");
callByKey(file.key(), FROM, TO);
}
@Test
public void fail_if_from_string_is_not_provided() {
expectedException.expect(IllegalArgumentException.class);
ComponentDto project = insertMyProject();
callDryRunByKey(project.key(), null, TO);
}
@Test
public void fail_if_to_string_is_not_provided() {
expectedException.expect(IllegalArgumentException.class);
ComponentDto project = insertMyProject();
callDryRunByKey(project.key(), FROM, null);
}
@Test
public void fail_if_uuid_nor_key_provided() {
expectedException.expect(IllegalArgumentException.class);
call(null, null, FROM, TO, false);
}
@Test
public void fail_if_uuid_and_key_provided() {
expectedException.expect(IllegalArgumentException.class);
ComponentDto project = insertMyProject();
call(project.uuid(), project.key(), FROM, TO, false);
}
@Test
public void fail_if_project_does_not_exist() {
expectedException.expect(NotFoundException.class);
callDryRunByUuid("UNKNOWN_UUID", FROM, TO);
}
@Test
public void throw_ForbiddenException_if_not_project_administrator() {
userSession.logIn();
ComponentDto project = insertMyProject();
expectedException.expect(ForbiddenException.class);
callDryRunByUuid(project.uuid(), FROM, TO);
}
@Test
public void api_definition() {
WebService.Action definition = ws.getDef();
assertThat(definition.isPost()).isTrue();
assertThat(definition.since()).isEqualTo("6.1");
assertThat(definition.key()).isEqualTo("bulk_update_key");
assertThat(definition.params())
.hasSize(5)
.extracting(WebService.Param::key)
.containsOnlyOnce("projectId", "project", "from", "to", "dryRun");
}
private ComponentDto insertMyProject() {
return componentDb.insertComponent(ComponentTesting.newPrivateProjectDto(db.organizations().insert()).setKey(MY_PROJECT_KEY));
}
private BulkUpdateKeyWsResponse callDryRunByUuid(@Nullable String uuid, @Nullable String from, @Nullable String to) {
return call(uuid, null, from, to, true);
}
private BulkUpdateKeyWsResponse callDryRunByKey(@Nullable String key, @Nullable String from, @Nullable String to) {
return call(null, key, from, to, true);
}
private BulkUpdateKeyWsResponse callByUuid(@Nullable String uuid, @Nullable String from, @Nullable String to) {
return call(uuid, null, from, to, false);
}
private BulkUpdateKeyWsResponse callByKey(@Nullable String key, @Nullable String from, @Nullable String to) {
return call(null, key, from, to, false);
}
private BulkUpdateKeyWsResponse call(@Nullable String uuid, @Nullable String key, @Nullable String from, @Nullable String to, @Nullable Boolean dryRun) {
TestRequest request = ws.newRequest();
if (uuid != null) {
request.setParam(PARAM_PROJECT_ID, uuid);
}
if (key != null) {
request.setParam(PARAM_PROJECT, key);
}
if (from != null) {
request.setParam(PARAM_FROM, from);
}
if (to != null) {
request.setParam(PARAM_TO, to);
}
if (dryRun != null) {
request.setParam(PARAM_DRY_RUN, String.valueOf(dryRun));
}
return request.executeProtobuf(BulkUpdateKeyWsResponse.class);
}
}