diff options
author | Simon Brandhof <simon.brandhof@sonarsource.com> | 2017-07-18 13:16:09 +0200 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@sonarsource.com> | 2017-07-22 00:31:16 +0200 |
commit | b18e284c549f4e46d0ad806cfbcc76e348027bd7 (patch) | |
tree | 6307bd9576529584bb45e4510471ad626b448ad0 /tests | |
parent | 7e87812f0c2a3000368e76b2965360c476f8ec10 (diff) | |
download | sonarqube-b18e284c549f4e46d0ad806cfbcc76e348027bd7.tar.gz sonarqube-b18e284c549f4e46d0ad806cfbcc76e348027bd7.zip |
SONAR-9516 add integration tests on api/projects/update_key
including ES resiliency tests
Diffstat (limited to 'tests')
-rw-r--r-- | tests/src/test/java/org/sonarqube/tests/Category6Suite.java | 4 | ||||
-rw-r--r-- | tests/src/test/java/org/sonarqube/tests/Elasticsearch.java | 19 | ||||
-rw-r--r-- | tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectKeyUpdatePageTest.java (renamed from tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectKeyPageTest.java) | 2 | ||||
-rw-r--r-- | tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectKeyUpdateTest.java | 254 |
4 files changed, 277 insertions, 2 deletions
diff --git a/tests/src/test/java/org/sonarqube/tests/Category6Suite.java b/tests/src/test/java/org/sonarqube/tests/Category6Suite.java index cf02b593415..ab947ec17e7 100644 --- a/tests/src/test/java/org/sonarqube/tests/Category6Suite.java +++ b/tests/src/test/java/org/sonarqube/tests/Category6Suite.java @@ -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 { diff --git a/tests/src/test/java/org/sonarqube/tests/Elasticsearch.java b/tests/src/test/java/org/sonarqube/tests/Elasticsearch.java index 60df943b9b1..a19d9708a92 100644 --- a/tests/src/test/java/org/sonarqube/tests/Elasticsearch.java +++ b/tests/src/test/java/org/sonarqube/tests/Elasticsearch.java @@ -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/ProjectKeyUpdatePageTest.java index a1d7f61aba1..fb9572d3748 100644 --- a/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectKeyPageTest.java +++ b/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectKeyUpdatePageTest.java @@ -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; 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 index 00000000000..275552cf3ec --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/projectAdministration/ProjectKeyUpdateTest.java @@ -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()); + } +} |