You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ProjectKeyUpdateTest.java 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2018 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonarqube.tests.project;
  21. import com.sonar.orchestrator.Orchestrator;
  22. import com.sonar.orchestrator.build.SonarScanner;
  23. import java.util.Collection;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.stream.Collectors;
  27. import javax.annotation.CheckForNull;
  28. import org.junit.After;
  29. import org.junit.ClassRule;
  30. import org.junit.Rule;
  31. import org.junit.Test;
  32. import org.junit.rules.DisableOnDebug;
  33. import org.junit.rules.TestRule;
  34. import org.junit.rules.Timeout;
  35. import org.sonarqube.qa.util.Tester;
  36. import org.sonarqube.ws.Components;
  37. import org.sonarqube.ws.Organizations;
  38. import org.sonarqube.ws.Projects;
  39. import org.sonarqube.ws.client.GetRequest;
  40. import org.sonarqube.ws.client.WsResponse;
  41. import org.sonarqube.ws.client.components.SearchProjectsRequest;
  42. import org.sonarqube.ws.client.components.ShowRequest;
  43. import org.sonarqube.ws.client.projects.UpdateKeyRequest;
  44. import org.sonarqube.ws.client.projects.CreateRequest;
  45. import util.ItUtils;
  46. import static org.assertj.core.api.Assertions.assertThat;
  47. import static util.ItUtils.projectDir;
  48. public class ProjectKeyUpdateTest {
  49. private static final String PROJECT_KEY = "sample";
  50. @ClassRule
  51. public static final Orchestrator orchestrator = ProjectSuite.ORCHESTRATOR;
  52. @Rule
  53. public TestRule safeguard = new DisableOnDebug(Timeout.seconds(300));
  54. @Rule
  55. public Tester tester = new Tester(orchestrator).setElasticsearchHttpPort(ProjectSuite.SEARCH_HTTP_PORT);
  56. @After
  57. public void tearDown() throws Exception {
  58. unlockWritesOnProjectIndices();
  59. }
  60. @Test
  61. public void update_key() {
  62. analyzeXooSample();
  63. String newProjectKey = "another_project_key";
  64. Components.Component project = tester.wsClient().components().show(new ShowRequest().setComponent(PROJECT_KEY)).getComponent();
  65. assertThat(project.getKey()).isEqualTo(PROJECT_KEY);
  66. tester.wsClient().projects().updateKey(new UpdateKeyRequest()
  67. .setFrom(PROJECT_KEY)
  68. .setTo(newProjectKey));
  69. assertThat(tester.wsClient().components().show(new ShowRequest().setComponentId(project.getId())).getComponent().getKey()).isEqualTo(newProjectKey);
  70. }
  71. @Test
  72. public void update_key_of_provisioned_project() {
  73. Organizations.Organization organization = tester.organizations().generate();
  74. Projects.CreateWsResponse.Project project = createProject(organization, "one", "Foo");
  75. updateKey(project, "two");
  76. assertThat(isProjectInDatabase("one")).isFalse();
  77. assertThat(isProjectInDatabase("two")).isTrue();
  78. assertThat(isComponentInDatabase("one")).isFalse();
  79. assertThat(isComponentInDatabase("two")).isTrue();
  80. assertThat(keyInComponentSearchProjects("Foo")).isEqualTo("two");
  81. assertThat(keysInComponentSuggestions("Foo")).containsExactly("two");
  82. }
  83. @Test
  84. public void recover_indexing_errors_when_updating_key_of_provisioned_project() throws Exception {
  85. Organizations.Organization organization = tester.organizations().generate();
  86. Projects.CreateWsResponse.Project project = createProject(organization, "one", "Foo");
  87. lockWritesOnProjectIndices();
  88. updateKey(project, "two");
  89. assertThat(isProjectInDatabase("one")).isFalse();
  90. // WS gets the list of projects from ES then reloads projects from db.
  91. // That's why keys in WS responses are correct.
  92. assertThat(isProjectInDatabase("one")).isFalse();
  93. assertThat(isProjectInDatabase("two")).isTrue();
  94. assertThat(keyInComponentSearchProjects("Foo")).isEqualTo("two");
  95. assertThat(keysInComponentSuggestions("Foo")).containsExactly("two");
  96. // however searching by key is inconsistent
  97. assertThat(keyInComponentSearchProjects("one")).isEqualTo("two");
  98. assertThat(keysInComponentSuggestions("one")).containsExactly("two");
  99. assertThat(keyInComponentSearchProjects("two")).isNull();
  100. assertThat(keysInComponentSuggestions("two")).isEmpty();
  101. unlockWritesOnProjectIndices();
  102. boolean recovered = false;
  103. while (!recovered) {
  104. // recovery daemon runs every second, see Category6Suite
  105. Thread.sleep(1_000L);
  106. recovered = keyInComponentSearchProjects("one") == null &&
  107. keysInComponentSuggestions("one").isEmpty() &&
  108. "two".equals(keyInComponentSearchProjects("two")) &&
  109. keysInComponentSuggestions("two").contains("two");
  110. }
  111. }
  112. @Test
  113. public void update_key_of_module() {
  114. Organizations.Organization organization = tester.organizations().generate();
  115. orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-multi-modules-sample"),
  116. "sonar.organization", organization.getKey(),
  117. "sonar.login", "admin", "sonar.password", "admin"));
  118. String initialKey = "com.sonarsource.it.samples:multi-modules-sample:module_a";
  119. String newKey = "com.sonarsource.it.samples:multi-modules-sample:module_c";
  120. updateKey(initialKey, newKey);
  121. assertThat(isComponentInDatabase(initialKey)).isFalse();
  122. assertThat(isComponentInDatabase(newKey)).isTrue();
  123. // suggestions engine ignores one-character words, so we can't search for "Module A"
  124. assertThat(keysInComponentSuggestions("Module"))
  125. .contains(newKey)
  126. .doesNotContain(initialKey);
  127. assertThat(keysInComponentSuggestions(newKey))
  128. .contains(newKey)
  129. .doesNotContain(initialKey);
  130. assertThat(keysInComponentSuggestions(initialKey)).isEmpty();
  131. }
  132. @Test
  133. public void recover_indexing_errors_when_updating_key_of_module() throws Exception {
  134. Organizations.Organization organization = tester.organizations().generate();
  135. orchestrator.executeBuild(SonarScanner.create(projectDir("shared/xoo-multi-modules-sample"),
  136. "sonar.organization", organization.getKey(),
  137. "sonar.login", "admin", "sonar.password", "admin"));
  138. String initialKey = "com.sonarsource.it.samples:multi-modules-sample:module_a";
  139. String newKey = "com.sonarsource.it.samples:multi-modules-sample:module_c";
  140. lockWritesOnProjectIndices();
  141. updateKey(initialKey, newKey);
  142. // api/components/search loads keys from db, so results are consistent
  143. assertThat(isComponentInDatabase(initialKey)).isFalse();
  144. assertThat(isComponentInDatabase(newKey)).isTrue();
  145. // key in result of suggestion engine is loaded from db, so results are ok when searching for unchanged name
  146. assertThat(keysInComponentSuggestions("Module"))
  147. .contains(newKey)
  148. .doesNotContain(initialKey);
  149. // but searching for new key does not work
  150. assertThat(keysInComponentSuggestions(newKey)).isEmpty();
  151. assertThat(keysInComponentSuggestions(initialKey))
  152. .isNotEmpty()
  153. .contains(newKey /* the returned key is loaded from db, so it's correct */);
  154. unlockWritesOnProjectIndices();
  155. boolean recovered = false;
  156. while (!recovered) {
  157. // recovery daemon runs every second, see Category6Suite
  158. Thread.sleep(1_000L);
  159. recovered = keysInComponentSuggestions(newKey).contains(newKey) && keysInComponentSuggestions(initialKey).isEmpty();
  160. }
  161. }
  162. private void lockWritesOnProjectIndices() throws Exception {
  163. tester.elasticsearch().lockWrites("components");
  164. tester.elasticsearch().lockWrites("projectmeasures");
  165. }
  166. private void unlockWritesOnProjectIndices() throws Exception {
  167. tester.elasticsearch().unlockWrites("components");
  168. tester.elasticsearch().unlockWrites("projectmeasures");
  169. }
  170. private void updateKey(Projects.CreateWsResponse.Project project, String newKey) {
  171. tester.wsClient().projects().updateKey(new UpdateKeyRequest().setFrom(project.getKey()).setTo(newKey));
  172. }
  173. private void updateKey(String initialKey, String newKey) {
  174. tester.wsClient().projects().updateKey(new UpdateKeyRequest().setFrom(initialKey).setTo(newKey));
  175. }
  176. private Projects.CreateWsResponse.Project createProject(Organizations.Organization organization, String key, String name) {
  177. CreateRequest createRequest = new CreateRequest().setProject(key).setName(name).setOrganization(organization.getKey());
  178. return tester.wsClient().projects().create(createRequest).getProject();
  179. }
  180. private boolean isProjectInDatabase(String projectKey) {
  181. return orchestrator.getDatabase().countSql(String.format("select count(id) from projects where qualifier='TRK' and kee='%s'", projectKey)) == 1L;
  182. }
  183. private boolean isComponentInDatabase(String componentKey) {
  184. return orchestrator.getDatabase().countSql(String.format("select count(id) from projects where kee='%s'", componentKey)) == 1L;
  185. }
  186. /**
  187. * Projects page - api/components/search_projects - uses ES + DB
  188. */
  189. @CheckForNull
  190. private String keyInComponentSearchProjects(String name) {
  191. Components.SearchProjectsWsResponse response = tester.wsClient().components().searchProjects(
  192. new SearchProjectsRequest().setFilter("query=\"" + name + "\""));
  193. if (response.getComponentsCount() > 0) {
  194. return response.getComponents(0).getKey();
  195. }
  196. return null;
  197. }
  198. /**
  199. * Top-right search engine - api/components/suggestions - uses ES + DB
  200. */
  201. private List<String> keysInComponentSuggestions(String name) {
  202. GetRequest request = new GetRequest("api/components/suggestions").setParam("s", name);
  203. WsResponse response = tester.wsClient().wsConnector().call(request);
  204. Map<String, Object> json = ItUtils.jsonToMap(response.content());
  205. Collection<Map<String, Object>> results = (Collection<Map<String, Object>>) json.get("results");
  206. return results.stream()
  207. .filter(map -> "TRK".equals(map.get("q")) || "BRC".equals(map.get("q")))
  208. .flatMap(map -> ((Collection<Map<String, Object>>) map.get("items")).stream())
  209. .map(map -> (String) map.get("key"))
  210. .collect(Collectors.toList());
  211. }
  212. private void analyzeXooSample() {
  213. SonarScanner build = SonarScanner.create(projectDir("shared/xoo-sample"));
  214. orchestrator.executeBuild(build);
  215. }
  216. }