Browse Source

SONAR-9516 add integration tests on api/projects/update_key

including ES resiliency tests
tags/6.6-RC1
Simon Brandhof 6 years ago
parent
commit
b18e284c54
26 changed files with 443 additions and 85 deletions
  1. 3
    5
      server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentKeyUpdaterDao.java
  2. 4
    4
      server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentKeyUpdaterMapper.xml
  3. 1
    1
      server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml
  4. 2
    1
      server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentKeyUpdaterDaoTest.java
  5. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/component/ComponentCleanerService.java
  6. 8
    8
      server/sonar-server/src/main/java/org/sonar/server/component/ComponentService.java
  7. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/component/ComponentUpdater.java
  8. 12
    1
      server/sonar-server/src/main/java/org/sonar/server/es/ProjectIndexers.java
  9. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/es/ProjectIndexersImpl.java
  10. 1
    2
      server/sonar-server/src/main/java/org/sonar/server/permission/PermissionTemplateService.java
  11. 2
    2
      server/sonar-server/src/main/java/org/sonar/server/permission/PermissionUpdater.java
  12. 1
    2
      server/sonar-server/src/main/java/org/sonar/server/project/ws/BulkUpdateKeyAction.java
  13. 0
    1
      server/sonar-server/src/main/java/org/sonar/server/project/ws/UpdateKeyAction.java
  14. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/project/ws/UpdateVisibilityAction.java
  15. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/projecttag/ws/SetAction.java
  16. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/component/ComponentServiceTest.java
  17. 5
    5
      server/sonar-server/src/test/java/org/sonar/server/component/ComponentServiceUpdateKeyTest.java
  18. 1
    0
      server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexerTest.java
  19. 25
    42
      server/sonar-server/src/test/java/org/sonar/server/es/ProjectIndexersImplTest.java
  20. 92
    0
      server/sonar-server/src/test/java/org/sonar/server/es/ProjectIndexersTest.java
  21. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/es/TestProjectIndexers.java
  22. 2
    2
      server/sonar-server/src/test/java/org/sonar/server/project/ws/BulkUpdateKeyActionTest.java
  23. 3
    1
      tests/src/test/java/org/sonarqube/tests/Category6Suite.java
  24. 19
    0
      tests/src/test/java/org/sonarqube/tests/Elasticsearch.java
  25. 1
    1
      tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectKeyUpdatePageTest.java
  26. 254
    0
      tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectKeyUpdateTest.java

+ 3
- 5
server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentKeyUpdaterDao.java View File

@@ -48,22 +48,20 @@ public class ComponentKeyUpdaterDao implements Dao {

private static final Set<String> PROJECT_OR_MODULE_QUALIFIERS = ImmutableSet.of(Qualifiers.PROJECT, Qualifiers.MODULE);

public void updateKey(DbSession dbSession, String projectUuid, String newKey) {
public void updateKey(DbSession dbSession, String projectOrModuleUuid, String newKey) {
ComponentKeyUpdaterMapper mapper = dbSession.getMapper(ComponentKeyUpdaterMapper.class);
if (mapper.countResourceByKey(newKey) > 0) {
throw new IllegalArgumentException("Impossible to update key: a component with key \"" + newKey + "\" already exists.");
}

// must SELECT first everything
ResourceDto project = mapper.selectProject(projectUuid);
ResourceDto project = mapper.selectProject(projectOrModuleUuid);
String projectOldKey = project.getKey();
List<ResourceDto> resources = mapper.selectProjectResources(projectUuid);
List<ResourceDto> resources = mapper.selectProjectResources(projectOrModuleUuid);
resources.add(project);

// and then proceed with the batch UPDATE at once
runBatchUpdateForAllResources(resources, projectOldKey, newKey, mapper);

dbSession.commit();
}

public static void checkIsProjectOrModule(ComponentDto component) {

+ 4
- 4
server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentKeyUpdaterMapper.xml View File

@@ -21,15 +21,15 @@

<select id="selectProject" parameterType="String" resultMap="resourceResultMap">
select * from projects
where uuid=#{uuid,jdbcType=VARCHAR}
where uuid = #{uuid,jdbcType=VARCHAR}
</select>

<select id="selectProjectResources" parameterType="String" resultMap="resourceResultMap">
select * from projects
where
root_uuid=#{rootUuid,jdbcType=VARCHAR}
and scope!='PRJ'
and enabled=${_true}
root_uuid = #{rootUuid,jdbcType=VARCHAR}
and scope != 'PRJ'
and enabled = ${_true}
</select>

<select id="selectDescendantProjects" parameterType="String" resultMap="resourceResultMap">

+ 1
- 1
server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml View File

@@ -425,7 +425,7 @@
p.enabled=${_true}
and p.copy_component_uuid is null
<if test="projectUuid != null">
and p.project_uuid=#{projectUuid,jdbcType=VARCHAR}
and p.project_uuid = #{projectUuid,jdbcType=VARCHAR}
</if>
</select>


+ 2
- 1
server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentKeyUpdaterDaoTest.java View File

@@ -56,6 +56,7 @@ public class ComponentKeyUpdaterDaoTest {
db.prepareDbUnit(getClass(), "shared.xml");

underTest.updateKey(dbSession, "B", "struts:core");
dbSession.commit();

db.assertDbUnit(getClass(), "shouldUpdateKey-result.xml", "projects");
}
@@ -70,7 +71,7 @@ public class ComponentKeyUpdaterDaoTest {
db.components().insertComponent(newFileDto(project, inactiveDirectory).setKey("my_project:inactive_directory/file").setEnabled(false));

underTest.updateKey(dbSession, "A", "your_project");
db.commit();
dbSession.commit();

List<ComponentDto> result = dbClient.componentDao().selectAllComponentsFromProjectKey(dbSession, "your_project");
assertThat(result).hasSize(5).extracting(ComponentDto::getKey)

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/component/ComponentCleanerService.java View File

@@ -56,7 +56,7 @@ public class ComponentCleanerService {
throw new IllegalArgumentException("Only projects can be deleted");
}
dbClient.purgeDao().deleteRootComponent(dbSession, project.uuid());
projectIndexers.commitAndIndex(dbSession, singletonList(project.uuid()), ProjectIndexer.Cause.PROJECT_DELETION);
projectIndexers.commitAndIndex(dbSession, singletonList(project), ProjectIndexer.Cause.PROJECT_DELETION);
}

private static boolean hasNotProjectScope(ComponentDto project) {

+ 8
- 8
server/sonar-server/src/main/java/org/sonar/server/component/ComponentService.java View File

@@ -46,18 +46,18 @@ public class ComponentService {
}

// TODO should be moved to UpdateKeyAction
public void updateKey(DbSession dbSession, ComponentDto component, String newKey) {
userSession.checkComponentPermission(UserRole.ADMIN, component);
checkIsProjectOrModule(component);
public void updateKey(DbSession dbSession, ComponentDto projectOrModule, String newKey) {
userSession.checkComponentPermission(UserRole.ADMIN, projectOrModule);
checkIsProjectOrModule(projectOrModule);
checkProjectOrModuleKeyFormat(newKey);
dbClient.componentKeyUpdaterDao().updateKey(dbSession, component.uuid(), newKey);
projectIndexers.commitAndIndex(dbSession, singletonList(component.uuid()), ProjectIndexer.Cause.PROJECT_KEY_UPDATE);
dbClient.componentKeyUpdaterDao().updateKey(dbSession, projectOrModule.uuid(), newKey);
projectIndexers.commitAndIndex(dbSession, singletonList(projectOrModule), ProjectIndexer.Cause.PROJECT_KEY_UPDATE);
}

// TODO should be moved to BulkUpdateKeyAction
public void bulkUpdateKey(DbSession dbSession, String projectUuid, String stringToReplace, String replacementString) {
dbClient.componentKeyUpdaterDao().bulkUpdateKey(dbSession, projectUuid, stringToReplace, replacementString);
projectIndexers.commitAndIndex(dbSession, singletonList(projectUuid), ProjectIndexer.Cause.PROJECT_KEY_UPDATE);
public void bulkUpdateKey(DbSession dbSession, ComponentDto projectOrModule, String stringToReplace, String replacementString) {
dbClient.componentKeyUpdaterDao().bulkUpdateKey(dbSession, projectOrModule.uuid(), stringToReplace, replacementString);
projectIndexers.commitAndIndex(dbSession, singletonList(projectOrModule), ProjectIndexer.Cause.PROJECT_KEY_UPDATE);
}

private static void checkProjectOrModuleKeyFormat(String key) {

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/component/ComponentUpdater.java View File

@@ -72,7 +72,7 @@ public class ComponentUpdater {
ComponentDto componentDto = createRootComponent(dbSession, newComponent);
removeDuplicatedProjects(dbSession, componentDto.getKey());
handlePermissionTemplate(dbSession, componentDto, newComponent.getOrganizationUuid(), userId);
projectIndexers.commitAndIndex(dbSession, singletonList(componentDto.uuid()), Cause.PROJECT_CREATION);
projectIndexers.commitAndIndex(dbSession, singletonList(componentDto), Cause.PROJECT_CREATION);
return componentDto;
}


+ 12
- 1
server/sonar-server/src/main/java/org/sonar/server/es/ProjectIndexers.java View File

@@ -20,13 +20,24 @@
package org.sonar.server.es;

import java.util.Collection;
import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.db.DbSession;
import org.sonar.db.component.ComponentDto;

public interface ProjectIndexers {

/**
* Commits the DB transaction and indexes the specified projects, if needed (according to
* "cause" parameter).
* IMPORTANT - UUIDs must relate to projects only. Modules, directories and files are forbidden
* and will lead to lack of indexing.
*/
void commitAndIndex(DbSession dbSession, Collection<String> projectUuid, ProjectIndexer.Cause cause);
void commitAndIndexByProjectUuids(DbSession dbSession, Collection<String> projectUuids, ProjectIndexer.Cause cause);

default void commitAndIndex(DbSession dbSession, Collection<ComponentDto> projectOrModules, ProjectIndexer.Cause cause) {
Collection<String> projectUuids = projectOrModules.stream()
.map(ComponentDto::projectUuid)
.collect(MoreCollectors.toSet(projectOrModules.size()));
commitAndIndexByProjectUuids(dbSession, projectUuids, cause);
}
}

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/es/ProjectIndexersImpl.java View File

@@ -38,7 +38,7 @@ public class ProjectIndexersImpl implements ProjectIndexers {
}

@Override
public void commitAndIndex(DbSession dbSession, Collection<String> projectUuids, ProjectIndexer.Cause cause) {
public void commitAndIndexByProjectUuids(DbSession dbSession, Collection<String> projectUuids, ProjectIndexer.Cause cause) {
Map<ProjectIndexer, Collection<EsQueueDto>> itemsByIndexer = new IdentityHashMap<>();
indexers.forEach(i -> itemsByIndexer.put(i, i.prepareForRecovery(dbSession, projectUuids, cause)));
dbSession.commit();

+ 1
- 2
server/sonar-server/src/main/java/org/sonar/server/permission/PermissionTemplateService.java View File

@@ -32,7 +32,6 @@ import org.sonar.api.resources.Qualifiers;
import org.sonar.api.server.ServerSide;
import org.sonar.core.component.ComponentKeys;
import org.sonar.core.permission.ProjectPermissions;
import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.component.ComponentDto;
@@ -104,7 +103,7 @@ public class PermissionTemplateService {
for (ComponentDto project : projects) {
copyPermissions(dbSession, template, project, null);
}
projectIndexers.commitAndIndex(dbSession, projects.stream().map(ComponentDto::uuid).collect(MoreCollectors.toList()), ProjectIndexer.Cause.PERMISSION_CHANGE);
projectIndexers.commitAndIndex(dbSession, projects, ProjectIndexer.Cause.PERMISSION_CHANGE);
}

/**

+ 2
- 2
server/sonar-server/src/main/java/org/sonar/server/permission/PermissionUpdater.java View File

@@ -43,7 +43,7 @@ public class PermissionUpdater {
private final GroupPermissionChanger groupPermissionChanger;

public PermissionUpdater(DbClient dbClient, ProjectIndexers projectIndexers,
UserPermissionChanger userPermissionChanger, GroupPermissionChanger groupPermissionChanger) {
UserPermissionChanger userPermissionChanger, GroupPermissionChanger groupPermissionChanger) {
this.dbClient = dbClient;
this.projectIndexers = projectIndexers;
this.userPermissionChanger = userPermissionChanger;
@@ -65,7 +65,7 @@ public class PermissionUpdater {
dbClient.resourceDao().updateAuthorizationDate(projectId, dbSession);
}

projectIndexers.commitAndIndex(dbSession, projectOrViewUuids, ProjectIndexer.Cause.PERMISSION_CHANGE);
projectIndexers.commitAndIndexByProjectUuids(dbSession, projectOrViewUuids, ProjectIndexer.Cause.PERMISSION_CHANGE);
}

private boolean doApply(DbSession dbSession, PermissionChange change) {

+ 1
- 2
server/sonar-server/src/main/java/org/sonar/server/project/ws/BulkUpdateKeyAction.java View File

@@ -154,8 +154,7 @@ public class BulkUpdateKeyAction implements ProjectsWsAction {
}

private void bulkUpdateKey(DbSession dbSession, BulkUpdateKeyWsRequest request, ComponentDto projectOrModule) {
componentService.bulkUpdateKey(dbSession, projectOrModule.uuid(), request.getFrom(), request.getTo());
dbSession.commit();
componentService.bulkUpdateKey(dbSession, projectOrModule, request.getFrom(), request.getTo());
}

private static BulkUpdateKeyWsResponse buildResponse(Map<String, String> newKeysByOldKeys, Map<String, Boolean> newKeysWithDuplicateMap) {

+ 0
- 1
server/sonar-server/src/main/java/org/sonar/server/project/ws/UpdateKeyAction.java View File

@@ -101,7 +101,6 @@ public class UpdateKeyAction implements ProjectsWsAction {
try (DbSession dbSession = dbClient.openSession(false)) {
ComponentDto projectOrModule = componentFinder.getByUuidOrKey(dbSession, request.getId(), request.getKey(), ParamNames.PROJECT_ID_AND_FROM);
componentService.updateKey(dbSession, projectOrModule, request.getNewKey());
dbSession.commit();
}
}


+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/project/ws/UpdateVisibilityAction.java View File

@@ -109,7 +109,7 @@ public class UpdateVisibilityAction implements ProjectsWsAction {
} else {
updatePermissionsToPublic(dbSession, component);
}
projectIndexers.commitAndIndex(dbSession, singletonList(component.uuid()), ProjectIndexer.Cause.PERMISSION_CHANGE);
projectIndexers.commitAndIndex(dbSession, singletonList(component), ProjectIndexer.Cause.PERMISSION_CHANGE);
}
}
}

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/projecttag/ws/SetAction.java View File

@@ -99,7 +99,7 @@ public class SetAction implements ProjectTagsWsAction {

project.setTags(tags);
dbClient.componentDao().updateTags(dbSession, project);
projectIndexers.commitAndIndex(dbSession, singletonList(project.uuid()), PROJECT_TAGS_UPDATE);
projectIndexers.commitAndIndex(dbSession, singletonList(project), PROJECT_TAGS_UPDATE);
}

response.noContent();

+ 1
- 1
server/sonar-server/src/test/java/org/sonar/server/component/ComponentServiceTest.java View File

@@ -60,7 +60,7 @@ public class ComponentServiceTest {
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));

underTest.bulkUpdateKey(dbSession, project.uuid(), "my_", "your_");
underTest.bulkUpdateKey(dbSession, project, "my_", "your_");

assertComponentKeyUpdated(project.key(), "your_project");
assertComponentKeyUpdated(module.key(), "your_project:root:module");

+ 5
- 5
server/sonar-server/src/test/java/org/sonar/server/component/ComponentServiceUpdateKeyTest.java View File

@@ -86,10 +86,9 @@ public class ComponentServiceUpdateKeyTest {
public void update_module_key() {
ComponentDto project = insertSampleRootProject();
ComponentDto module = ComponentTesting.newModuleDto(project).setKey("sample:root:module");
dbClient.componentDao().insert(dbSession, module);
db.components().insertComponent(module);
ComponentDto file = ComponentTesting.newFileDto(module, null).setKey("sample:root:module:src/File.xoo");
dbClient.componentDao().insert(dbSession, file);
dbSession.commit();
db.components().insertComponent(file);
logInAsProjectAdministrator(project);

underTest.updateKey(dbSession, module, "sample:root2:module");
@@ -99,7 +98,8 @@ public class ComponentServiceUpdateKeyTest {
assertComponentKeyHasBeenUpdated(module.key(), "sample:root2:module");
assertComponentKeyHasBeenUpdated(file.key(), "sample:root2:module:src/File.xoo");

org.assertj.core.api.Assertions.assertThat(projectIndexers.hasBeenCalled(module.uuid(), ProjectIndexer.Cause.PROJECT_KEY_UPDATE)).isTrue();
// do not index the module but the project
org.assertj.core.api.Assertions.assertThat(projectIndexers.hasBeenCalled(project.uuid(), ProjectIndexer.Cause.PROJECT_KEY_UPDATE)).isTrue();
}

@Test
@@ -180,7 +180,7 @@ public class ComponentServiceUpdateKeyTest {
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));

underTest.bulkUpdateKey(dbSession, project.uuid(), "my_", "your_");
underTest.bulkUpdateKey(dbSession, project, "my_", "your_");

assertComponentKeyUpdated(project.key(), "your_project");
assertComponentKeyUpdated(module.key(), "your_project:root:module");

+ 1
- 0
server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexerTest.java View File

@@ -184,6 +184,7 @@ public class ComponentIndexerTest {
ComponentDto project = db.components().insertPrivateProject();
ComponentDto file = db.components().insertComponent(newFileDto(project));
indexProject(project, PROJECT_CREATION);
assertThatIndexHasSize(2);

db.getDbClient().componentDao().delete(db.getSession(), project.getId());
db.getDbClient().componentDao().delete(db.getSession(), file.getId());

+ 25
- 42
server/sonar-server/src/test/java/org/sonar/server/es/ProjectIndexersImplTest.java View File

@@ -19,14 +19,17 @@
*/
package org.sonar.server.es;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import org.junit.Test;
import org.sonar.db.DbSession;
import org.sonar.db.es.EsQueueDto;
import org.sonar.db.component.ComponentDto;
import org.sonar.db.component.ComponentTesting;
import org.sonar.db.organization.OrganizationDto;
import org.sonar.db.organization.OrganizationTesting;
import org.sonar.server.es.ProjectIndexer.Cause;

import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@@ -34,54 +37,34 @@ import static org.mockito.Mockito.mock;
public class ProjectIndexersImplTest {

@Test
public void commitAndIndex_calls_indexer_with_only_its_supported_items() {
EsQueueDto item1a = EsQueueDto.create("fake/fake1", "P1");
EsQueueDto item1b = EsQueueDto.create("fake/fake1", "P1");
EsQueueDto item2 = EsQueueDto.create("fake/fake2", "P1");
FakeIndexer indexer1 = new FakeIndexer(asList(item1a, item1b));
FakeIndexer indexer2 = new FakeIndexer(singletonList(item2));
DbSession dbSession = mock(DbSession.class);
public void commitAndIndex_indexes_project() {
OrganizationDto organization = OrganizationTesting.newOrganizationDto();
ComponentDto project = ComponentTesting.newPublicProjectDto(organization);

ProjectIndexersImpl underTest = new ProjectIndexersImpl(indexer1, indexer2);
underTest.commitAndIndex(dbSession, singletonList("P1"), ProjectIndexer.Cause.PROJECT_CREATION);
FakeIndexers underTest = new FakeIndexers();
underTest.commitAndIndex(mock(DbSession.class), singletonList(project), Cause.PROJECT_CREATION);

assertThat(indexer1.calledItems).containsExactlyInAnyOrder(item1a, item1b);
assertThat(indexer2.calledItems).containsExactlyInAnyOrder(item2);
assertThat(underTest.calls).containsExactly(project.uuid());
}

private static class FakeIndexer implements ProjectIndexer {

private final List<EsQueueDto> items;
private Collection<EsQueueDto> calledItems;

private FakeIndexer(List<EsQueueDto> items) {
this.items = items;
}

@Override
public void indexOnStartup(Set<IndexType> uninitializedIndexTypes) {
throw new UnsupportedOperationException();
}
@Test
public void commitAndIndex_of_module_indexes_the_project() {
OrganizationDto organization = OrganizationTesting.newOrganizationDto();
ComponentDto project = ComponentTesting.newPublicProjectDto(organization);
ComponentDto module = ComponentTesting.newModuleDto(project);

@Override
public Set<IndexType> getIndexTypes() {
throw new UnsupportedOperationException();
}
FakeIndexers underTest = new FakeIndexers();
underTest.commitAndIndex(mock(DbSession.class), singletonList(module), Cause.PROJECT_CREATION);

@Override
public Collection<EsQueueDto> prepareForRecovery(DbSession dbSession, Collection<String> projectUuids, Cause cause) {
return items;
}
assertThat(underTest.calls).containsExactly(project.uuid());
}

@Override
public IndexingResult index(DbSession dbSession, Collection<EsQueueDto> items) {
this.calledItems = items;
return new IndexingResult();
}
private static class FakeIndexers implements ProjectIndexers {
private final List<String> calls = new ArrayList<>();

@Override
public void indexOnAnalysis(String projectUuid) {
throw new UnsupportedOperationException();
public void commitAndIndexByProjectUuids(DbSession dbSession, Collection<String> projectUuids, Cause cause) {
calls.addAll(projectUuids);
}
}
}

+ 92
- 0
server/sonar-server/src/test/java/org/sonar/server/es/ProjectIndexersTest.java View File

@@ -0,0 +1,92 @@
/*
* 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.es;

import java.util.Collection;
import java.util.List;
import java.util.Set;
import org.junit.Test;
import org.sonar.db.DbSession;
import org.sonar.db.es.EsQueueDto;

import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;

public class ProjectIndexersTest {

@Test
public void commitAndIndexByProjectUuids_calls_indexer_with_only_its_supported_items() {
EsQueueDto item1a = EsQueueDto.create("fake/fake1", "P1");
EsQueueDto item1b = EsQueueDto.create("fake/fake1", "P1");
EsQueueDto item2 = EsQueueDto.create("fake/fake2", "P1");
FakeIndexer indexer1 = new FakeIndexer(asList(item1a, item1b));
FakeIndexer indexer2 = new FakeIndexer(singletonList(item2));
DbSession dbSession = mock(DbSession.class);

ProjectIndexersImpl underTest = new ProjectIndexersImpl(indexer1, indexer2);
underTest.commitAndIndexByProjectUuids(dbSession, singletonList("P1"), ProjectIndexer.Cause.PROJECT_CREATION);

assertThat(indexer1.calledItems).containsExactlyInAnyOrder(item1a, item1b);
assertThat(indexer2.calledItems).containsExactlyInAnyOrder(item2);
}

@Test
public void commitAndIndex_restricts_indexing_to_projects() {
// TODO
}

private static class FakeIndexer implements ProjectIndexer {

private final List<EsQueueDto> items;
private Collection<EsQueueDto> calledItems;

private FakeIndexer(List<EsQueueDto> items) {
this.items = items;
}

@Override
public void indexOnStartup(Set<IndexType> uninitializedIndexTypes) {
throw new UnsupportedOperationException();
}

@Override
public Set<IndexType> getIndexTypes() {
throw new UnsupportedOperationException();
}

@Override
public Collection<EsQueueDto> prepareForRecovery(DbSession dbSession, Collection<String> projectUuids, Cause cause) {
return items;
}

@Override
public IndexingResult index(DbSession dbSession, Collection<EsQueueDto> items) {
this.calledItems = items;
return new IndexingResult();
}

@Override
public void indexOnAnalysis(String projectUuid) {
throw new UnsupportedOperationException();
}
}
}

+ 1
- 1
server/sonar-server/src/test/java/org/sonar/server/es/TestProjectIndexers.java View File

@@ -29,7 +29,7 @@ public class TestProjectIndexers implements ProjectIndexers {
private final ListMultimap<String, ProjectIndexer.Cause> calls = ArrayListMultimap.create();

@Override
public void commitAndIndex(DbSession dbSession, Collection<String> projectUuids, ProjectIndexer.Cause cause) {
public void commitAndIndexByProjectUuids(DbSession dbSession, Collection<String> projectUuids, ProjectIndexer.Cause cause) {
dbSession.commit();
projectUuids.forEach(projectUuid -> calls.put(projectUuid, cause));


+ 2
- 2
server/sonar-server/src/test/java/org/sonar/server/project/ws/BulkUpdateKeyActionTest.java View File

@@ -140,7 +140,7 @@ public class BulkUpdateKeyActionTest {
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));
verify(componentService).bulkUpdateKey(any(DbSession.class), eq(project), eq(FROM), eq(TO));
}

@Test
@@ -150,7 +150,7 @@ public class BulkUpdateKeyActionTest {

callByKey(provisionedProject.key(), provisionedProject.getKey(), newKey);

verify(componentService).bulkUpdateKey(any(DbSession.class), eq(provisionedProject.uuid()), eq(provisionedProject.getKey()), eq(newKey));
verify(componentService).bulkUpdateKey(any(DbSession.class), eq(provisionedProject), eq(provisionedProject.getKey()), eq(newKey));
}

@Test

+ 3
- 1
tests/src/test/java/org/sonarqube/tests/Category6Suite.java View File

@@ -34,6 +34,7 @@ import org.sonarqube.tests.organization.OrganizationTest;
import org.sonarqube.tests.organization.PersonalOrganizationTest;
import org.sonarqube.tests.organization.RootUserOnOrganizationTest;
import org.sonarqube.tests.projectAdministration.ProjectDeletionTest;
import org.sonarqube.tests.projectAdministration.ProjectKeyUpdateTest;
import org.sonarqube.tests.projectAdministration.ProjectProvisioningTest;
import org.sonarqube.tests.projectSearch.LeakProjectsPageTest;
import org.sonarqube.tests.projectSearch.SearchProjectsTest;
@@ -71,7 +72,8 @@ import static util.ItUtils.xooPlugin;
SearchProjectsTest.class,
RulesWsTest.class,
ProjectDeletionTest.class,
ProjectProvisioningTest.class
ProjectProvisioningTest.class,
ProjectKeyUpdateTest.class
})
public class Category6Suite {


+ 19
- 0
tests/src/test/java/org/sonarqube/tests/Elasticsearch.java View File

@@ -1,3 +1,22 @@
/*
* 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.tests;

import java.net.InetAddress;

tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectKeyPageTest.java → tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectKeyUpdatePageTest.java View File

@@ -38,7 +38,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static util.ItUtils.newAdminWsClient;
import static util.ItUtils.projectDir;

public class ProjectKeyPageTest {
public class ProjectKeyUpdatePageTest {

@ClassRule
public static Orchestrator ORCHESTRATOR = Category1Suite.ORCHESTRATOR;

+ 254
- 0
tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectKeyUpdateTest.java View File

@@ -0,0 +1,254 @@
/*
* 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.tests.projectAdministration;

import com.sonar.orchestrator.Orchestrator;
import com.sonar.orchestrator.build.SonarScanner;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.CheckForNull;
import org.junit.After;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.DisableOnDebug;
import org.junit.rules.TestRule;
import org.junit.rules.Timeout;
import org.sonarqube.tests.Category6Suite;
import org.sonarqube.tests.Tester;
import org.sonarqube.ws.Organizations;
import org.sonarqube.ws.WsComponents;
import org.sonarqube.ws.WsProjects;
import org.sonarqube.ws.client.GetRequest;
import org.sonarqube.ws.client.WsResponse;
import org.sonarqube.ws.client.component.SearchProjectsRequest;
import org.sonarqube.ws.client.project.CreateRequest;
import org.sonarqube.ws.client.project.SearchWsRequest;
import org.sonarqube.ws.client.project.UpdateKeyWsRequest;
import util.ItUtils;

import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static util.ItUtils.projectDir;

public class ProjectKeyUpdateTest {

@ClassRule
public static final Orchestrator orchestrator = Category6Suite.ORCHESTRATOR;

@Rule
public TestRule safeguard = new DisableOnDebug(Timeout.seconds(300));
@Rule
public Tester tester = new Tester(orchestrator)
.setElasticsearchHttpPort(Category6Suite.SEARCH_HTTP_PORT);

@After
public void tearDown() throws Exception {
unlockWritesOnProjectIndices();
}

@Test
public void update_key_of_provisioned_project() {
Organizations.Organization organization = tester.organizations().generate();
WsProjects.CreateWsResponse.Project project = createProject(organization, "one", "Foo");

updateKey(project, "two");

assertThat(isInProjectsSearch(organization, "one")).isFalse();
assertThat(isInProjectsSearch(organization, "two")).isTrue();
assertThat(isInComponentSearch(organization, "one")).isFalse();
assertThat(isInComponentSearch(organization, "two")).isTrue();
assertThat(keyInComponentSearchProjects("Foo")).isEqualTo("two");
assertThat(keysInComponentSuggestions("Foo")).containsExactly("two");
}

@Test
public void recover_indexing_errors_when_updating_key_of_provisioned_project() throws Exception {
Organizations.Organization organization = tester.organizations().generate();
WsProjects.CreateWsResponse.Project project = createProject(organization, "one", "Foo");

lockWritesOnProjectIndices();

updateKey(project, "two");

assertThat(isInProjectsSearch(organization, "one")).isFalse();

// WS gets the list of projects from ES then reloads projects from db.
// That's why keys in WS responses are correct.
assertThat(isInProjectsSearch(organization, "one")).isFalse();
assertThat(isInProjectsSearch(organization, "two")).isTrue();
assertThat(keyInComponentSearchProjects("Foo")).isEqualTo("two");
assertThat(keysInComponentSuggestions("Foo")).containsExactly("two");

// however searching by key is inconsistent
assertThat(keyInComponentSearchProjects("one")).isEqualTo("two");
assertThat(keysInComponentSuggestions("one")).containsExactly("two");
assertThat(keyInComponentSearchProjects("two")).isNull();
assertThat(keysInComponentSuggestions("two")).isEmpty();

unlockWritesOnProjectIndices();

boolean recovered = false;
while (!recovered) {
// recovery daemon runs every second, see Category6Suite
Thread.sleep(1_000L);
recovered = keyInComponentSearchProjects("one") == null &&
keysInComponentSuggestions("one").isEmpty() &&
"two".equals(keyInComponentSearchProjects("two")) &&
keysInComponentSuggestions("two").contains("two");
}
}

@Test
public void update_key_of_module() {
Organizations.Organization organization = tester.organizations().generate();
orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-multi-modules-sample"),
"sonar.organization", organization.getKey(),
"sonar.login", "admin", "sonar.password", "admin"));

String initialKey = "com.sonarsource.it.samples:multi-modules-sample:module_a";
String newKey = "com.sonarsource.it.samples:multi-modules-sample:module_c";

updateKey(initialKey, newKey);

assertThat(isInComponentSearch(organization, initialKey)).isFalse();
assertThat(isInComponentSearch(organization, newKey)).isTrue();
// suggestions engine ignores one-character words, so we can't search for "Module A"
assertThat(keysInComponentSuggestions("Module"))
.contains(newKey)
.doesNotContain(initialKey);
assertThat(keysInComponentSuggestions(newKey))
.contains(newKey)
.doesNotContain(initialKey);
assertThat(keysInComponentSuggestions(initialKey)).isEmpty();

}

@Test
public void recover_indexing_errors_when_updating_key_of_module() throws Exception {
Organizations.Organization organization = tester.organizations().generate();
orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-multi-modules-sample"),
"sonar.organization", organization.getKey(),
"sonar.login", "admin", "sonar.password", "admin"));

String initialKey = "com.sonarsource.it.samples:multi-modules-sample:module_a";
String newKey = "com.sonarsource.it.samples:multi-modules-sample:module_c";

lockWritesOnProjectIndices();
updateKey(initialKey, newKey);

// api/components/search loads keys from db, so results are consistent
assertThat(isInComponentSearch(organization, initialKey)).isFalse();
assertThat(isInComponentSearch(organization, newKey)).isTrue();

// key in result of suggestion engine is loaded from db, so results are ok when searching for unchanged name
assertThat(keysInComponentSuggestions("Module"))
.contains(newKey)
.doesNotContain(initialKey);

// but searching for new key does not work
assertThat(keysInComponentSuggestions(newKey)).isEmpty();
assertThat(keysInComponentSuggestions(initialKey))
.isNotEmpty()
.contains(newKey /* the returned key is loaded from db, so it's correct */);

unlockWritesOnProjectIndices();

boolean recovered = false;
while (!recovered) {
// recovery daemon runs every second, see Category6Suite
Thread.sleep(1_000L);
recovered = keysInComponentSuggestions(newKey).contains(newKey) && keysInComponentSuggestions(initialKey).isEmpty();
}

}

private void lockWritesOnProjectIndices() throws Exception {
tester.elasticsearch().lockWrites("components");
tester.elasticsearch().lockWrites("projectmeasures");
}


private void unlockWritesOnProjectIndices() throws Exception {
tester.elasticsearch().unlockWrites("components");
tester.elasticsearch().unlockWrites("projectmeasures");
}

private void updateKey(WsProjects.CreateWsResponse.Project project, String newKey) {
tester.wsClient().projects().updateKey(UpdateKeyWsRequest.builder().setKey(project.getKey()).setNewKey(newKey).build());
}

private void updateKey(String initialKey, String newKey) {
tester.wsClient().projects().updateKey(UpdateKeyWsRequest.builder().setKey(initialKey).setNewKey(newKey).build());
}

private WsProjects.CreateWsResponse.Project createProject(Organizations.Organization organization, String key, String name) {
CreateRequest createRequest = CreateRequest.builder().setKey(key).setName(name).setOrganization(organization.getKey()).build();
return tester.wsClient().projects().create(createRequest).getProject();
}

/**
* Projects administration page - uses database
*/
private boolean isInProjectsSearch(Organizations.Organization organization, String key) {
WsProjects.SearchWsResponse response = tester.wsClient().projects().search(
SearchWsRequest.builder().setOrganization(organization.getKey()).setQuery(key).setQualifiers(singletonList("TRK")).build());
return response.getComponentsCount() > 0;
}

private boolean isInComponentSearch(Organizations.Organization organization, String key) {
org.sonarqube.ws.client.component.SearchWsRequest request = new org.sonarqube.ws.client.component.SearchWsRequest()
.setQualifiers(asList("TRK", "BRC"))
.setQuery(key)
.setOrganization(organization.getKey());
return tester.wsClient().components().search(request).getComponentsCount() == 1L;
}

/**
* Projects page - api/components/search_projects - uses ES + DB
*/
@CheckForNull
private String keyInComponentSearchProjects(String name) {
WsComponents.SearchProjectsWsResponse response = tester.wsClient().components().searchProjects(
SearchProjectsRequest.builder().setFilter("query=\"" + name + "\"").build());
if (response.getComponentsCount() > 0) {
return response.getComponents(0).getKey();
}
return null;
}

/**
* Top-right search engine - api/components/suggestions - uses ES + DB
*/
private List<String> keysInComponentSuggestions(String name) {
GetRequest request = new GetRequest("api/components/suggestions").setParam("s", name);
WsResponse response = tester.wsClient().wsConnector().call(request);
Map<String, Object> json = ItUtils.jsonToMap(response.content());
Collection<Map<String, Object>> results = (Collection<Map<String, Object>>) json.get("results");
return results.stream()
.filter(map -> "TRK".equals(map.get("q")) || "BRC".equals(map.get("q")))
.flatMap(map -> ((Collection<Map<String, Object>>) map.get("items")).stream())
.map(map -> (String) map.get("key"))
.collect(Collectors.toList());
}
}

Loading…
Cancel
Save