]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9516 add integration tests on api/projects/update_key
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Tue, 18 Jul 2017 11:16:09 +0000 (13:16 +0200)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Fri, 21 Jul 2017 22:31:16 +0000 (00:31 +0200)
including ES resiliency tests

27 files changed:
server/sonar-db-dao/src/main/java/org/sonar/db/component/ComponentKeyUpdaterDao.java
server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentKeyUpdaterMapper.xml
server/sonar-db-dao/src/main/resources/org/sonar/db/component/ComponentMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/component/ComponentKeyUpdaterDaoTest.java
server/sonar-server/src/main/java/org/sonar/server/component/ComponentCleanerService.java
server/sonar-server/src/main/java/org/sonar/server/component/ComponentService.java
server/sonar-server/src/main/java/org/sonar/server/component/ComponentUpdater.java
server/sonar-server/src/main/java/org/sonar/server/es/ProjectIndexers.java
server/sonar-server/src/main/java/org/sonar/server/es/ProjectIndexersImpl.java
server/sonar-server/src/main/java/org/sonar/server/permission/PermissionTemplateService.java
server/sonar-server/src/main/java/org/sonar/server/permission/PermissionUpdater.java
server/sonar-server/src/main/java/org/sonar/server/project/ws/BulkUpdateKeyAction.java
server/sonar-server/src/main/java/org/sonar/server/project/ws/UpdateKeyAction.java
server/sonar-server/src/main/java/org/sonar/server/project/ws/UpdateVisibilityAction.java
server/sonar-server/src/main/java/org/sonar/server/projecttag/ws/SetAction.java
server/sonar-server/src/test/java/org/sonar/server/component/ComponentServiceTest.java
server/sonar-server/src/test/java/org/sonar/server/component/ComponentServiceUpdateKeyTest.java
server/sonar-server/src/test/java/org/sonar/server/component/index/ComponentIndexerTest.java
server/sonar-server/src/test/java/org/sonar/server/es/ProjectIndexersImplTest.java
server/sonar-server/src/test/java/org/sonar/server/es/ProjectIndexersTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/es/TestProjectIndexers.java
server/sonar-server/src/test/java/org/sonar/server/project/ws/BulkUpdateKeyActionTest.java
tests/src/test/java/org/sonarqube/tests/Category6Suite.java
tests/src/test/java/org/sonarqube/tests/Elasticsearch.java
tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectKeyPageTest.java [deleted file]
tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectKeyUpdatePageTest.java [new file with mode: 0644]
tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectKeyUpdateTest.java [new file with mode: 0644]

index 357732fa08cd71381805b13d758bc104a5622dcf..b954a67e27f0b97eba2e004c4210cd87711daf55 100644 (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) {
index b47e6f46b4447c5131423bc1f2f26132e9bb0b0d..d7189fdb1ec65e96fd36eb424e91173b0d5a93d9 100644 (file)
 
   <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">
index f9a99818714983dd3bcf28d05c238c9c38bd736f..fecf8cf87158ed1f1d85f5f3e8fb42d6f39f6108 100644 (file)
       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>
 
index 8dd4bbcadce26bd72e0cd2169b4eb1ce582aaf29..f8305553ee96997727b4ef93685810fcf0c438af 100644 (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)
index 33d357e3a75378df5fafb0eca3ae42a66829778c..8c840c4749d302886dda156914a62ec5fbeb4b2a 100644 (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) {
index 1cf8b58feffc9b91decbddad123488eb92d8ec08..6c015fb12590a2ea61423fbb34886edb947d0e31 100644 (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) {
index 115a7e28f4a4d1bdfba7cd26a5afb7ae345489dd..fdb5dddf2e3349737da9936c53ebcb64c013265d 100644 (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;
   }
 
index 51a9b34bd769e9ca8b4f848ca692d15d19b36cac..c582dfb73b3ffc7675c63765bc929b3ce7c69fee 100644 (file)
 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);
+  }
 }
index d26ba078d9ccf26278681064a4dea78d5dc93204..553622ead1b1ede556f7127b23cd765b5589e42b 100644 (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();
index 82735ce115f6dee7912ce299ec4ab10573e46ccb..ddfcaabc81b9ad064bb5d0e929ba138743d13d35 100644 (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);
   }
 
   /**
index 9238fd5e36e7e10e6674654de65178bb248241dc..126ba94a36c46a9dff2fa2d447f6674c2063ea51 100644 (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) {
index 890571e70ba5471edbb8d40e97863d06b6d14328..106a9db3a6cdb8d3068aa2b288b3e119f4d7a61f 100644 (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) {
index d845d695d89e9e4331c9c8f34edd627035207900..4364f3c1cac561bdc318f0eb58c0a385e1fddee1 100644 (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();
     }
   }
 
index 990839e226cff20bdf79f3fd924f63f68ed496c6..95fb26c20e3f9923df20c280ef9b8ee2b82e995f 100644 (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);
       }
     }
   }
index 71fab7c61143223334fe5e6c166d218ca23432db..b46278cde7cb4193dd73d5b0343d7855600beda0 100644 (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();
index b8928a8419fc42f44150ac36739fdce8f9c2b0f5..7e63c6ec8731eba27e25118a9a4f58a58bf45d09 100644 (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");
index b42087054889142b1bedb6bde584109865e0a3bf..22334b55a5895d6baa7dbe22c0cfaeddd75cfedb 100644 (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");
index 959ec117e9e24ec31b188e53ef2387200fc1d65e..86874bb8fcc5f29bb0dc62dc442baee8df0960a7 100644 (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());
index 2d9567780315d5b8d3b85fb74ff2dc54f827d81c..8ba6ddd9e8127f6bf4328668f042e40a5fce8e87 100644 (file)
  */
  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);
     }
   }
 }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/es/ProjectIndexersTest.java b/server/sonar-server/src/test/java/org/sonar/server/es/ProjectIndexersTest.java
new file mode 100644 (file)
index 0000000..9ec6968
--- /dev/null
@@ -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();
+    }
+  }
+}
index 957612a0588a478dd973008eaae7fceecd45cace..173ced760da1213cdfdb8e651f73cdbd00b1ceaa 100644 (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));
 
index ddf60c516891c6bd132d1c1ddbba8a8562fc8724..632813f071f1ef929ef39b7e1a12f9ae08d26809 100644 (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
index cf02b5934158cfa2e9e960cc8788809eee0ac1fd..ab947ec17e7c059771c938e70d51ccd999799d81 100644 (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 {
 
index 60df943b9b18f6f2b219909d7fffb89767af784c..a19d9708a929184cf4d8f2d9f80fe1294148c494 100644 (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;
diff --git a/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectKeyPageTest.java b/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectKeyPageTest.java
deleted file mode 100644 (file)
index a1d7f61..0000000
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * 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 org.sonarqube.tests.Category1Suite;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.sonarqube.ws.client.PostRequest;
-import org.sonarqube.ws.client.WsClient;
-import org.sonarqube.pageobjects.Navigation;
-import org.sonarqube.pageobjects.ProjectKeyPage;
-
-import static com.codeborne.selenide.Condition.visible;
-import static com.codeborne.selenide.Selenide.$;
-import static com.codeborne.selenide.WebDriverRunner.url;
-import static org.assertj.core.api.Assertions.assertThat;
-import static util.ItUtils.newAdminWsClient;
-import static util.ItUtils.projectDir;
-
-public class ProjectKeyPageTest {
-
-  @ClassRule
-  public static Orchestrator ORCHESTRATOR = Category1Suite.ORCHESTRATOR;
-
-  private static WsClient wsClient;
-
-  @BeforeClass
-  public static void setUp() {
-    wsClient = newAdminWsClient(ORCHESTRATOR);
-  }
-
-  @Before
-  public void cleanUp() {
-    ORCHESTRATOR.resetData();
-  }
-
-  private Navigation nav = Navigation.create(ORCHESTRATOR);
-
-  @Test
-  public void change_key_when_no_modules() {
-    createProject("sample");
-
-    ProjectKeyPage page = openPage("sample");
-    page.assertSimpleUpdate().trySimpleUpdate("another");
-
-    assertThat(url()).endsWith("/project/key?id=another");
-  }
-
-  @Test
-  public void fail_to_change_key_when_no_modules() {
-    createProject("sample");
-    createProject("another");
-
-    ProjectKeyPage page = openPage("sample");
-    page.assertSimpleUpdate().trySimpleUpdate("another");
-
-    $(".alert.alert-danger").shouldBe(visible);
-    assertThat(url()).endsWith("/project/key?id=sample");
-  }
-
-  @Test
-  public void change_key_of_multi_modules_project() {
-    analyzeProject("shared/xoo-multi-modules-sample", "sample");
-
-    ProjectKeyPage page = openPage("sample");
-    page.openFineGrainedUpdate().tryFineGrainedUpdate("sample", "another");
-
-    assertThat(url()).endsWith("/project/key?id=another");
-  }
-
-  @Test
-  public void fail_to_change_key_of_multi_modules_project() {
-    analyzeProject("shared/xoo-multi-modules-sample", "sample");
-    createProject("another");
-
-    ProjectKeyPage page = openPage("sample");
-    page.openFineGrainedUpdate().tryFineGrainedUpdate("sample", "another");
-
-    $(".alert.alert-danger").shouldBe(visible);
-    assertThat(url()).endsWith("/project/key?id=sample");
-  }
-
-  @Test
-  public void change_key_of_module_of_multi_modules_project() {
-    analyzeProject("shared/xoo-multi-modules-sample", "sample");
-
-    ProjectKeyPage page = openPage("sample");
-    page.openFineGrainedUpdate().tryFineGrainedUpdate("sample:module_a:module_a1", "another");
-
-    $("#update-key-confirmation-form").shouldNotBe(visible);
-
-    nav.openProjectKey("another");
-    assertThat(url()).endsWith("/project/key?id=another");
-  }
-
-  @Test
-  public void fail_to_change_key_of_module_of_multi_modules_project() {
-    analyzeProject("shared/xoo-multi-modules-sample", "sample");
-    createProject("another");
-
-    ProjectKeyPage page = openPage("sample");
-    page.openFineGrainedUpdate().tryFineGrainedUpdate("sample:module_a:module_a1", "another");
-
-    $(".alert.alert-danger").shouldBe(visible);
-  }
-
-  @Test
-  public void bulk_change() {
-    analyzeProject("shared/xoo-multi-modules-sample", "sample");
-
-    ProjectKeyPage page = openPage("sample");
-    page.assertBulkChange().simulateBulkChange("sample", "another");
-
-    $("#bulk-update-results").shouldBe(visible);
-    page.assertBulkChangeSimulationResult("sample", "another")
-      .assertBulkChangeSimulationResult("sample:module_a:module_a1", "another:module_a:module_a1");
-
-    page.confirmBulkUpdate().assertSuccessfulBulkUpdate();
-  }
-
-  @Test
-  public void fail_to_bulk_change_because_no_changed_key() {
-    analyzeProject("shared/xoo-multi-modules-sample", "sample");
-
-    ProjectKeyPage page = openPage("sample");
-    page.assertBulkChange().simulateBulkChange("random", "another");
-
-    $("#bulk-update-nothing").shouldBe(visible);
-    $("#bulk-update-results").shouldNotBe(visible);
-  }
-
-  @Test
-  public void fail_to_bulk_change_because_of_duplications() {
-    analyzeProject("shared/xoo-multi-modules-sample", "sample");
-
-    ProjectKeyPage page = openPage("sample");
-    page.assertBulkChange().simulateBulkChange("module_a1", "module_a2");
-
-    $("#bulk-update-duplicate").shouldBe(visible);
-    $("#bulk-update-results").shouldBe(visible);
-
-    page.assertBulkChangeSimulationResult("sample:module_a:module_a1", "sample:module_a:module_a2")
-      .assertDuplicated("sample:module_a:module_a1");
-  }
-
-  private ProjectKeyPage openPage(String projectKey) {
-    nav.logIn().submitCredentials("admin", "admin");
-    return nav.openProjectKey(projectKey);
-  }
-
-  private static void createProject(String projectKey) {
-    wsClient.wsConnector().call(new PostRequest("api/projects/create")
-      .setParam("key", projectKey)
-      .setParam("name", projectKey));
-  }
-
-  private static void analyzeProject(String path, String projectKey) {
-    ORCHESTRATOR.executeBuild(SonarScanner.create(projectDir(path))
-      .setProjectKey(projectKey));
-  }
-}
diff --git a/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectKeyUpdatePageTest.java b/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectKeyUpdatePageTest.java
new file mode 100644 (file)
index 0000000..fb9572d
--- /dev/null
@@ -0,0 +1,182 @@
+/*
+ * 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 org.sonarqube.tests.Category1Suite;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.sonarqube.ws.client.PostRequest;
+import org.sonarqube.ws.client.WsClient;
+import org.sonarqube.pageobjects.Navigation;
+import org.sonarqube.pageobjects.ProjectKeyPage;
+
+import static com.codeborne.selenide.Condition.visible;
+import static com.codeborne.selenide.Selenide.$;
+import static com.codeborne.selenide.WebDriverRunner.url;
+import static org.assertj.core.api.Assertions.assertThat;
+import static util.ItUtils.newAdminWsClient;
+import static util.ItUtils.projectDir;
+
+public class ProjectKeyUpdatePageTest {
+
+  @ClassRule
+  public static Orchestrator ORCHESTRATOR = Category1Suite.ORCHESTRATOR;
+
+  private static WsClient wsClient;
+
+  @BeforeClass
+  public static void setUp() {
+    wsClient = newAdminWsClient(ORCHESTRATOR);
+  }
+
+  @Before
+  public void cleanUp() {
+    ORCHESTRATOR.resetData();
+  }
+
+  private Navigation nav = Navigation.create(ORCHESTRATOR);
+
+  @Test
+  public void change_key_when_no_modules() {
+    createProject("sample");
+
+    ProjectKeyPage page = openPage("sample");
+    page.assertSimpleUpdate().trySimpleUpdate("another");
+
+    assertThat(url()).endsWith("/project/key?id=another");
+  }
+
+  @Test
+  public void fail_to_change_key_when_no_modules() {
+    createProject("sample");
+    createProject("another");
+
+    ProjectKeyPage page = openPage("sample");
+    page.assertSimpleUpdate().trySimpleUpdate("another");
+
+    $(".alert.alert-danger").shouldBe(visible);
+    assertThat(url()).endsWith("/project/key?id=sample");
+  }
+
+  @Test
+  public void change_key_of_multi_modules_project() {
+    analyzeProject("shared/xoo-multi-modules-sample", "sample");
+
+    ProjectKeyPage page = openPage("sample");
+    page.openFineGrainedUpdate().tryFineGrainedUpdate("sample", "another");
+
+    assertThat(url()).endsWith("/project/key?id=another");
+  }
+
+  @Test
+  public void fail_to_change_key_of_multi_modules_project() {
+    analyzeProject("shared/xoo-multi-modules-sample", "sample");
+    createProject("another");
+
+    ProjectKeyPage page = openPage("sample");
+    page.openFineGrainedUpdate().tryFineGrainedUpdate("sample", "another");
+
+    $(".alert.alert-danger").shouldBe(visible);
+    assertThat(url()).endsWith("/project/key?id=sample");
+  }
+
+  @Test
+  public void change_key_of_module_of_multi_modules_project() {
+    analyzeProject("shared/xoo-multi-modules-sample", "sample");
+
+    ProjectKeyPage page = openPage("sample");
+    page.openFineGrainedUpdate().tryFineGrainedUpdate("sample:module_a:module_a1", "another");
+
+    $("#update-key-confirmation-form").shouldNotBe(visible);
+
+    nav.openProjectKey("another");
+    assertThat(url()).endsWith("/project/key?id=another");
+  }
+
+  @Test
+  public void fail_to_change_key_of_module_of_multi_modules_project() {
+    analyzeProject("shared/xoo-multi-modules-sample", "sample");
+    createProject("another");
+
+    ProjectKeyPage page = openPage("sample");
+    page.openFineGrainedUpdate().tryFineGrainedUpdate("sample:module_a:module_a1", "another");
+
+    $(".alert.alert-danger").shouldBe(visible);
+  }
+
+  @Test
+  public void bulk_change() {
+    analyzeProject("shared/xoo-multi-modules-sample", "sample");
+
+    ProjectKeyPage page = openPage("sample");
+    page.assertBulkChange().simulateBulkChange("sample", "another");
+
+    $("#bulk-update-results").shouldBe(visible);
+    page.assertBulkChangeSimulationResult("sample", "another")
+      .assertBulkChangeSimulationResult("sample:module_a:module_a1", "another:module_a:module_a1");
+
+    page.confirmBulkUpdate().assertSuccessfulBulkUpdate();
+  }
+
+  @Test
+  public void fail_to_bulk_change_because_no_changed_key() {
+    analyzeProject("shared/xoo-multi-modules-sample", "sample");
+
+    ProjectKeyPage page = openPage("sample");
+    page.assertBulkChange().simulateBulkChange("random", "another");
+
+    $("#bulk-update-nothing").shouldBe(visible);
+    $("#bulk-update-results").shouldNotBe(visible);
+  }
+
+  @Test
+  public void fail_to_bulk_change_because_of_duplications() {
+    analyzeProject("shared/xoo-multi-modules-sample", "sample");
+
+    ProjectKeyPage page = openPage("sample");
+    page.assertBulkChange().simulateBulkChange("module_a1", "module_a2");
+
+    $("#bulk-update-duplicate").shouldBe(visible);
+    $("#bulk-update-results").shouldBe(visible);
+
+    page.assertBulkChangeSimulationResult("sample:module_a:module_a1", "sample:module_a:module_a2")
+      .assertDuplicated("sample:module_a:module_a1");
+  }
+
+  private ProjectKeyPage openPage(String projectKey) {
+    nav.logIn().submitCredentials("admin", "admin");
+    return nav.openProjectKey(projectKey);
+  }
+
+  private static void createProject(String projectKey) {
+    wsClient.wsConnector().call(new PostRequest("api/projects/create")
+      .setParam("key", projectKey)
+      .setParam("name", projectKey));
+  }
+
+  private static void analyzeProject(String path, String projectKey) {
+    ORCHESTRATOR.executeBuild(SonarScanner.create(projectDir(path))
+      .setProjectKey(projectKey));
+  }
+}
diff --git a/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectKeyUpdateTest.java b/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectKeyUpdateTest.java
new file mode 100644 (file)
index 0000000..275552c
--- /dev/null
@@ -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());
+  }
+}