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.

IssueIndexerIT.java 29KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2023 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.sonar.server.issue.index;
  21. import java.util.Arrays;
  22. import java.util.Collection;
  23. import java.util.Date;
  24. import java.util.HashSet;
  25. import java.util.List;
  26. import java.util.Map;
  27. import java.util.Set;
  28. import java.util.function.Predicate;
  29. import org.assertj.core.api.Assertions;
  30. import org.elasticsearch.search.SearchHit;
  31. import org.junit.Rule;
  32. import org.junit.Test;
  33. import org.slf4j.event.Level;
  34. import org.sonar.api.issue.impact.Severity;
  35. import org.sonar.api.issue.impact.SoftwareQuality;
  36. import org.sonar.api.resources.Qualifiers;
  37. import org.sonar.api.testfixtures.log.LogTester;
  38. import org.sonar.db.DbSession;
  39. import org.sonar.db.DbTester;
  40. import org.sonar.db.component.BranchDto;
  41. import org.sonar.db.component.ComponentDto;
  42. import org.sonar.db.component.ComponentTesting;
  43. import org.sonar.db.component.ProjectData;
  44. import org.sonar.db.es.EsQueueDto;
  45. import org.sonar.db.issue.IssueDto;
  46. import org.sonar.db.issue.IssueTesting;
  47. import org.sonar.db.project.ProjectDto;
  48. import org.sonar.db.rule.RuleDto;
  49. import org.sonar.server.es.EsTester;
  50. import org.sonar.server.es.IndexType;
  51. import org.sonar.server.es.Indexers;
  52. import org.sonar.server.es.Indexers.EntityEvent;
  53. import org.sonar.server.es.IndexingResult;
  54. import org.sonar.server.es.StartupIndexer;
  55. import org.sonar.server.permission.index.AuthorizationScope;
  56. import org.sonar.server.permission.index.IndexPermissions;
  57. import org.sonar.server.security.SecurityStandards;
  58. import org.sonar.server.security.SecurityStandards.SQCategory;
  59. import org.sonar.server.security.SecurityStandards.VulnerabilityProbability;
  60. import static java.util.Arrays.asList;
  61. import static java.util.Collections.emptyList;
  62. import static java.util.Collections.emptySet;
  63. import static java.util.Collections.singletonList;
  64. import static org.assertj.core.api.Assertions.assertThat;
  65. import static org.assertj.core.api.Assertions.assertThatThrownBy;
  66. import static org.assertj.core.groups.Tuple.tuple;
  67. import static org.sonar.db.component.ComponentTesting.newFileDto;
  68. import static org.sonar.server.es.Indexers.BranchEvent.DELETION;
  69. import static org.sonar.server.es.Indexers.EntityEvent.PROJECT_KEY_UPDATE;
  70. import static org.sonar.server.es.Indexers.EntityEvent.PROJECT_TAGS_UPDATE;
  71. import static org.sonar.server.issue.IssueDocTesting.newDoc;
  72. import static org.sonar.server.issue.index.IssueIndexDefinition.SUB_FIELD_SEVERITY;
  73. import static org.sonar.server.issue.index.IssueIndexDefinition.SUB_FIELD_SOFTWARE_QUALITY;
  74. import static org.sonar.server.issue.index.IssueIndexDefinition.TYPE_ISSUE;
  75. import static org.sonar.server.permission.index.IndexAuthorizationConstants.TYPE_AUTHORIZATION;
  76. import static org.sonar.server.security.SecurityStandards.SANS_TOP_25_POROUS_DEFENSES;
  77. public class IssueIndexerIT {
  78. @Rule
  79. public EsTester es = EsTester.create();
  80. @Rule
  81. public DbTester db = DbTester.create();
  82. @Rule
  83. public LogTester logTester = new LogTester();
  84. private final IssueIndexer underTest = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()), null);
  85. @Test
  86. public void getIndexTypes_shouldReturnTypeIssue() {
  87. assertThat(underTest.getIndexTypes()).containsExactly(TYPE_ISSUE);
  88. }
  89. @Test
  90. public void getAuthorizationScope_shouldBeProject() {
  91. AuthorizationScope scope = underTest.getAuthorizationScope();
  92. assertThat(scope.getIndexType().getIndex()).isEqualTo(IssueIndexDefinition.DESCRIPTOR);
  93. assertThat(scope.getIndexType().getType()).isEqualTo(TYPE_AUTHORIZATION);
  94. Predicate<IndexPermissions> projectPredicate = scope.getEntityPredicate();
  95. IndexPermissions project = new IndexPermissions("P1", Qualifiers.PROJECT);
  96. IndexPermissions file = new IndexPermissions("F1", Qualifiers.FILE);
  97. assertThat(projectPredicate.test(project)).isTrue();
  98. assertThat(projectPredicate.test(file)).isFalse();
  99. }
  100. @Test
  101. public void indexAllIssues_shouldIndexAllIssues() {
  102. IssueDto issue1 = db.issues().insert();
  103. IssueDto issue2 = db.issues().insert();
  104. underTest.indexAllIssues();
  105. assertThatIndexHasOnly(issue1, issue2);
  106. }
  107. @Test
  108. public void indexAllIssues_shouldIndexAllIssueFields() {
  109. RuleDto rule = db.rules().insert();
  110. ProjectData projectData = db.components().insertPrivateProject();
  111. ComponentDto project = projectData.getMainBranchComponent();
  112. ComponentDto dir = db.components().insertComponent(ComponentTesting.newDirectory(project, "src/main/java/foo"));
  113. ComponentDto file = db.components().insertComponent(newFileDto(project, dir, "F1"));
  114. IssueDto issue = db.issues().insert(rule, project, file);
  115. underTest.indexAllIssues();
  116. IssueDoc doc = es.getDocuments(TYPE_ISSUE, IssueDoc.class).get(0);
  117. assertThat(doc.getId()).isEqualTo(issue.getKey());
  118. assertThat(doc.assigneeUuid()).isEqualTo(issue.getAssigneeUuid());
  119. assertThat(doc.authorLogin()).isEqualTo(issue.getAuthorLogin());
  120. assertThat(doc.componentUuid()).isEqualTo(file.uuid());
  121. assertThat(doc.projectUuid()).isEqualTo(projectData.projectUuid());
  122. assertThat(doc.branchUuid()).isEqualTo(project.uuid());
  123. assertThat(doc.isMainBranch()).isTrue();
  124. assertThat(doc.closeDate()).isEqualTo(issue.getIssueCloseDate());
  125. assertThat(doc.creationDate()).isEqualToIgnoringMillis(issue.getIssueCreationDate());
  126. assertThat(doc.directoryPath()).isEqualTo(dir.path());
  127. assertThat(doc.filePath()).isEqualTo(file.path());
  128. assertThat(doc.scope()).isEqualTo(IssueScope.MAIN);
  129. assertThat(doc.language()).isEqualTo(issue.getLanguage());
  130. assertThat(doc.line()).isEqualTo(issue.getLine());
  131. // functional date
  132. assertThat(doc.updateDate()).isEqualToIgnoringMillis(new Date(issue.getIssueUpdateTime()));
  133. assertThat(doc.getCwe()).containsExactlyInAnyOrder(SecurityStandards.UNKNOWN_STANDARD);
  134. assertThat(doc.getOwaspTop10()).isEmpty();
  135. assertThat(doc.getSansTop25()).isEmpty();
  136. assertThat(doc.getSonarSourceSecurityCategory()).isEqualTo(SQCategory.OTHERS);
  137. assertThat(doc.getVulnerabilityProbability()).isEqualTo(VulnerabilityProbability.LOW);
  138. assertThat(doc.impacts())
  139. .containsExactlyInAnyOrder(Map.of(
  140. SUB_FIELD_SOFTWARE_QUALITY, SoftwareQuality.MAINTAINABILITY.name(),
  141. SUB_FIELD_SEVERITY, Severity.HIGH.name()));
  142. assertThat(doc.issueStatus()).isEqualTo(issue.getIssueStatus().name());
  143. }
  144. @Test
  145. public void indexAllIssues_shouldIndexSecurityStandards() {
  146. RuleDto rule = db.rules().insert(r -> r.setSecurityStandards(new HashSet<>(Arrays.asList("cwe:123", "owaspTop10:a3", "cwe:863", "owaspAsvs-4.0:2.1.1"))));
  147. ComponentDto project = db.components().insertPrivateProject().getMainBranchComponent();
  148. ComponentDto dir = db.components().insertComponent(ComponentTesting.newDirectory(project, "src/main/java/foo"));
  149. ComponentDto file = db.components().insertComponent(newFileDto(project, dir, "F1"));
  150. db.issues().insert(rule, project, file);
  151. underTest.indexAllIssues();
  152. IssueDoc doc = es.getDocuments(TYPE_ISSUE, IssueDoc.class).get(0);
  153. assertThat(doc.getCwe()).containsExactlyInAnyOrder("123", "863");
  154. assertThat(doc.getOwaspTop10()).containsExactlyInAnyOrder("a3");
  155. assertThat(doc.getOwaspAsvs40()).containsExactlyInAnyOrder("2.1.1");
  156. assertThat(doc.getSansTop25()).containsExactlyInAnyOrder(SANS_TOP_25_POROUS_DEFENSES);
  157. }
  158. @Test
  159. public void indexOnStartup_does_not_fail_on_errors_and_does_enable_recovery_mode() {
  160. es.lockWrites(TYPE_ISSUE);
  161. db.issues().insert();
  162. Set<IndexType> uninitializedIndexTypes = emptySet();
  163. assertThatThrownBy(() -> underTest.indexOnStartup(uninitializedIndexTypes))
  164. .isInstanceOf(IllegalStateException.class)
  165. .hasMessage("SYNCHRONOUS StartupIndexer must implement indexOnStartup");
  166. assertThatIndexHasSize(0);
  167. assertThatEsQueueTableHasSize(0);
  168. es.unlockWrites(TYPE_ISSUE);
  169. }
  170. @Test
  171. public void indexOnAnalysis_indexes_the_issues_of_project() {
  172. RuleDto rule = db.rules().insert();
  173. ComponentDto branchComponent = db.components().insertPrivateProject().getMainBranchComponent();
  174. ComponentDto file = db.components().insertComponent(newFileDto(branchComponent));
  175. IssueDto issue = db.issues().insert(rule, branchComponent, file);
  176. ComponentDto otherProject = db.components().insertPrivateProject().getMainBranchComponent();
  177. db.components().insertComponent(newFileDto(otherProject));
  178. underTest.indexOnAnalysis(branchComponent.uuid());
  179. assertThatIndexHasOnly(issue);
  180. }
  181. @Test
  182. public void indexOnAnalysis_does_not_delete_orphan_docs() {
  183. RuleDto rule = db.rules().insert();
  184. ProjectData projectData = db.components().insertPrivateProject();
  185. ComponentDto file = db.components().insertComponent(newFileDto(projectData.getMainBranchComponent()));
  186. IssueDto issue = db.issues().insert(rule, projectData.getMainBranchComponent(), file);
  187. // orphan in the project
  188. addIssueToIndex(projectData.projectUuid(), projectData.getMainBranchComponent().uuid(), "orphan");
  189. underTest.indexOnAnalysis(projectData.getMainBranchComponent().uuid());
  190. assertThat(es.getDocuments(TYPE_ISSUE))
  191. .extracting(SearchHit::getId)
  192. .containsExactlyInAnyOrder(issue.getKey(), "orphan");
  193. }
  194. /**
  195. * Indexing recovery is handled by Compute Engine, without using
  196. * the table es_queue
  197. */
  198. @Test
  199. public void indexOnAnalysis_does_not_fail_on_errors_and_does_not_enable_recovery_mode() {
  200. es.lockWrites(TYPE_ISSUE);
  201. IssueDto issue = db.issues().insert();
  202. String projectUuid = issue.getProjectUuid();
  203. assertThatThrownBy(() -> underTest.indexOnAnalysis(projectUuid))
  204. .isInstanceOf(IllegalStateException.class)
  205. .hasMessage("Unrecoverable indexation failures: 1 errors among 1 requests. Check Elasticsearch logs for further details.");
  206. assertThatIndexHasSize(0);
  207. assertThatEsQueueTableHasSize(0);
  208. es.unlockWrites(TYPE_ISSUE);
  209. }
  210. @Test
  211. public void index_is_not_updated_when_updating_project_key() {
  212. // issue is inserted to verify that indexing of project is not triggered
  213. IssueDto issue = db.issues().insert();
  214. IndexingResult result = indexProject(issue.getProjectUuid(), PROJECT_KEY_UPDATE);
  215. assertThat(result.getTotal()).isZero();
  216. assertThatIndexHasSize(0);
  217. }
  218. @Test
  219. public void index_is_not_updated_when_updating_tags() {
  220. // issue is inserted to verify that indexing of project is not triggered
  221. IssueDto issue = db.issues().insert();
  222. IndexingResult result = indexProject(issue.getProjectUuid(), PROJECT_TAGS_UPDATE);
  223. assertThat(result.getTotal()).isZero();
  224. assertThatIndexHasSize(0);
  225. }
  226. @Test
  227. public void index_is_updated_when_deleting_branch() {
  228. ProjectDto project = db.components().insertPublicProject().getProjectDto();
  229. BranchDto branch1 = db.components().insertProjectBranch(project);
  230. BranchDto branch2 = db.components().insertProjectBranch(project);
  231. addIssueToIndex(project.getUuid(), branch1.getUuid(), "I1");
  232. addIssueToIndex(project.getUuid(), branch1.getUuid(), "I2");
  233. addIssueToIndex(project.getUuid(), branch2.getUuid(), "I3");
  234. assertThatIndexHasSize(3);
  235. IndexingResult result = indexBranches(List.of(branch1.getUuid()), DELETION);
  236. assertThat(result.getTotal()).isEqualTo(2);
  237. assertThat(result.getSuccess()).isEqualTo(2);
  238. assertThatIndexHasOnly("I3");
  239. }
  240. @Test
  241. public void index_is_updated_when_deleting_project() {
  242. BranchDto branch = db.components().insertPrivateProject().getMainBranchDto();
  243. addIssueToIndex(branch.getProjectUuid(), branch.getUuid(), "I1");
  244. assertThatIndexHasSize(1);
  245. IndexingResult result = indexProject(branch.getProjectUuid(), EntityEvent.DELETION);
  246. assertThat(result.getTotal()).isOne();
  247. assertThat(result.getSuccess()).isOne();
  248. assertThatIndexHasSize(0);
  249. }
  250. @Test
  251. public void errors_during_project_deletion_are_recovered() {
  252. addIssueToIndex("P1", "B1", "I1");
  253. addIssueToIndex("P1", "B2", "I2");
  254. assertThatIndexHasSize(2);
  255. es.lockWrites(TYPE_ISSUE);
  256. IndexingResult result = indexProject("P1", EntityEvent.DELETION);
  257. assertThat(result.getTotal()).isEqualTo(2);
  258. assertThat(result.getFailures()).isEqualTo(2);
  259. // index is still read-only, fail to recover
  260. result = recover();
  261. assertThat(result.getTotal()).isEqualTo(2);
  262. assertThat(result.getFailures()).isEqualTo(2);
  263. assertThatIndexHasSize(2);
  264. es.unlockWrites(TYPE_ISSUE);
  265. result = recover();
  266. assertThat(result.getTotal()).isEqualTo(2);
  267. assertThat(result.getFailures()).isZero();
  268. assertThatIndexHasSize(0);
  269. }
  270. @Test
  271. public void commitAndIndexIssues_commits_db_transaction_and_adds_issues_to_index() {
  272. RuleDto rule = db.rules().insert();
  273. ComponentDto project = db.components().insertPrivateProject().getMainBranchComponent();
  274. ComponentDto file = db.components().insertComponent(newFileDto(project));
  275. // insert issues in db without committing
  276. IssueDto issue1 = IssueTesting.newIssue(rule, project, file);
  277. IssueDto issue2 = IssueTesting.newIssue(rule, project, file);
  278. db.getDbClient().issueDao().insert(db.getSession(), issue1, issue2);
  279. underTest.commitAndIndexIssues(db.getSession(), asList(issue1, issue2));
  280. // issues are persisted and indexed
  281. assertThatIndexHasOnly(issue1, issue2);
  282. assertThatDbHasOnly(issue1, issue2);
  283. assertThatEsQueueTableHasSize(0);
  284. }
  285. @Test
  286. public void commitAndIndexIssues_removes_issue_from_index_if_it_does_not_exist_in_db() {
  287. IssueDto issue1 = new IssueDto().setKee("I1").setProjectUuid("B1");
  288. addIssueToIndex("B1", issue1.getProjectUuid(), issue1.getKey());
  289. IssueDto issue2 = db.issues().insert();
  290. underTest.commitAndIndexIssues(db.getSession(), asList(issue1, issue2));
  291. // issue1 is removed from index, issue2 is persisted and indexed
  292. assertThatIndexHasOnly(issue2);
  293. assertThatDbHasOnly(issue2);
  294. assertThatEsQueueTableHasSize(0);
  295. }
  296. @Test
  297. public void indexing_errors_during_commitAndIndexIssues_are_recovered() {
  298. RuleDto rule = db.rules().insert();
  299. ComponentDto project = db.components().insertPrivateProject().getMainBranchComponent();
  300. ComponentDto file = db.components().insertComponent(newFileDto(project));
  301. // insert issues in db without committing
  302. IssueDto issue1 = IssueTesting.newIssue(rule, project, file);
  303. IssueDto issue2 = IssueTesting.newIssue(rule, project, file);
  304. db.getDbClient().issueDao().insert(db.getSession(), issue1, issue2);
  305. // index is read-only
  306. es.lockWrites(TYPE_ISSUE);
  307. underTest.commitAndIndexIssues(db.getSession(), asList(issue1, issue2));
  308. // issues are persisted but not indexed
  309. assertThatIndexHasSize(0);
  310. assertThatDbHasOnly(issue1, issue2);
  311. assertThatEsQueueTableHasSize(2);
  312. // re-enable write on index
  313. es.unlockWrites(TYPE_ISSUE);
  314. // emulate the recovery daemon
  315. IndexingResult result = recover();
  316. assertThatEsQueueTableHasSize(0);
  317. assertThatIndexHasOnly(issue1, issue2);
  318. assertThat(result.isSuccess()).isTrue();
  319. assertThat(result.getTotal()).isEqualTo(2L);
  320. }
  321. @Test
  322. public void recovery_does_not_fail_if_unsupported_docIdType() {
  323. EsQueueDto item = EsQueueDto.create(TYPE_ISSUE.format(), "I1", "unknown", "P1");
  324. db.getDbClient().esQueueDao().insert(db.getSession(), item);
  325. db.commit();
  326. recover();
  327. assertThat(logTester.logs(Level.ERROR))
  328. .filteredOn(l -> l.contains("Unsupported es_queue.doc_id_type for issues. Manual fix is required: "))
  329. .hasSize(1);
  330. assertThatEsQueueTableHasSize(1);
  331. }
  332. @Test
  333. public void indexing_recovers_multiple_errors_on_the_same_issue() {
  334. es.lockWrites(TYPE_ISSUE);
  335. IssueDto issue = db.issues().insert();
  336. // three changes on the same issue
  337. underTest.commitAndIndexIssues(db.getSession(), singletonList(issue));
  338. underTest.commitAndIndexIssues(db.getSession(), singletonList(issue));
  339. underTest.commitAndIndexIssues(db.getSession(), singletonList(issue));
  340. assertThatIndexHasSize(0);
  341. // three attempts of indexing are stored in es_queue recovery table
  342. assertThatEsQueueTableHasSize(3);
  343. es.unlockWrites(TYPE_ISSUE);
  344. recover();
  345. assertThatIndexHasOnly(issue);
  346. assertThatEsQueueTableHasSize(0);
  347. }
  348. @Test
  349. public void indexing_recovers_multiple_errors_on_the_same_project() {
  350. RuleDto rule = db.rules().insert();
  351. ComponentDto project = db.components().insertPrivateProject().getMainBranchComponent();
  352. ComponentDto file = db.components().insertComponent(newFileDto(project));
  353. db.issues().insert(rule, project, file);
  354. db.issues().insert(rule, project, file);
  355. es.lockWrites(TYPE_ISSUE);
  356. IndexingResult result = indexBranches(List.of(project.uuid()), DELETION);
  357. assertThat(result.getTotal()).isEqualTo(2L);
  358. assertThat(result.getFailures()).isEqualTo(2L);
  359. // index is still read-only, fail to recover
  360. result = recover();
  361. assertThat(result.getTotal()).isEqualTo(2L);
  362. assertThat(result.getFailures()).isEqualTo(2L);
  363. assertThatIndexHasSize(0);
  364. es.unlockWrites(TYPE_ISSUE);
  365. result = recover();
  366. assertThat(result.getTotal()).isEqualTo(2L);
  367. assertThat(result.getFailures()).isZero();
  368. assertThatIndexHasSize(2);
  369. assertThatEsQueueTableHasSize(0);
  370. }
  371. private IndexingResult indexBranches(List<String> branchUuids, Indexers.BranchEvent cause) {
  372. Collection<EsQueueDto> items = underTest.prepareForRecoveryOnBranchEvent(db.getSession(), branchUuids, cause);
  373. db.commit();
  374. return underTest.index(db.getSession(), items);
  375. }
  376. private IndexingResult indexProject(String projectUuid, EntityEvent cause) {
  377. Collection<EsQueueDto> items = underTest.prepareForRecoveryOnEntityEvent(db.getSession(), singletonList(projectUuid), cause);
  378. db.commit();
  379. return underTest.index(db.getSession(), items);
  380. }
  381. @Test
  382. public void deleteByKeys_shouldDeleteDocsByKeys() {
  383. addIssueToIndex("P1", "B1", "Issue1");
  384. addIssueToIndex("P1", "B1", "Issue2");
  385. addIssueToIndex("P1", "B1", "Issue3");
  386. addIssueToIndex("P2", "B2", "Issue4");
  387. assertThatIndexHasOnly("Issue1", "Issue2", "Issue3", "Issue4");
  388. underTest.deleteByKeys("P1", asList("Issue1", "Issue2"));
  389. assertThatIndexHasOnly("Issue3", "Issue4");
  390. }
  391. @Test
  392. public void deleteByKeys_shouldNotRecoverFromErrors() {
  393. addIssueToIndex("P1", "B1", "Issue1");
  394. es.lockWrites(TYPE_ISSUE);
  395. List<String> issues = List.of("Issue1");
  396. assertThatThrownBy(() -> underTest.deleteByKeys("P1", issues))
  397. .isInstanceOf(IllegalStateException.class)
  398. .hasMessage("Unrecoverable indexation failures: 1 errors among 1 requests. Check Elasticsearch logs for further details.");
  399. assertThatIndexHasOnly("Issue1");
  400. assertThatEsQueueTableHasSize(0);
  401. es.unlockWrites(TYPE_ISSUE);
  402. }
  403. @Test
  404. public void deleteByKeys_whenEmptyList_shouldDoNothing() {
  405. addIssueToIndex("P1", "B1", "Issue1");
  406. addIssueToIndex("P1", "B1", "Issue2");
  407. addIssueToIndex("P1", "B1", "Issue3");
  408. underTest.deleteByKeys("B1", emptyList());
  409. assertThatIndexHasOnly("Issue1", "Issue2", "Issue3");
  410. }
  411. /**
  412. * This is a technical constraint, to ensure, that the indexers can be called in any order, during startup.
  413. */
  414. @Test
  415. public void parent_child_relationship_does_not_require_ordering_of_index_requests() {
  416. IssueDoc issueDoc = new IssueDoc();
  417. issueDoc.setKey("key");
  418. issueDoc.setProjectUuid("parent-does-not-exist");
  419. new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()), null)
  420. .index(singletonList(issueDoc).iterator());
  421. assertThat(es.countDocuments(TYPE_ISSUE)).isOne();
  422. }
  423. @Test
  424. public void index_issue_in_non_main_branch() {
  425. RuleDto rule = db.rules().insert();
  426. ComponentDto project = db.components().insertPrivateProject().getMainBranchComponent();
  427. ComponentDto branch = db.components().insertProjectBranch(project, b -> b.setKey("feature/foo"));
  428. BranchDto branchDto = db.getDbClient().branchDao().selectByUuid(db.getSession(), branch.uuid()).orElseThrow();
  429. ComponentDto dir = db.components().insertComponent(ComponentTesting.newDirectory(branch, "src/main/java/foo"));
  430. ComponentDto file = db.components().insertComponent(newFileDto(branch, dir, "F1"));
  431. IssueDto issue = db.issues().insert(rule, branch, file);
  432. underTest.indexAllIssues();
  433. IssueDoc doc = es.getDocuments(TYPE_ISSUE, IssueDoc.class).get(0);
  434. assertThat(doc.getId()).isEqualTo(issue.getKey());
  435. assertThat(doc.componentUuid()).isEqualTo(file.uuid());
  436. assertThat(doc.projectUuid()).isEqualTo(branchDto.getProjectUuid());
  437. assertThat(doc.branchUuid()).isEqualTo(branch.uuid());
  438. assertThat(doc.isMainBranch()).isFalse();
  439. assertThat(doc.scope()).isEqualTo(IssueScope.MAIN);
  440. }
  441. @Test
  442. public void indexIssue_whenSwitchMainBranch_shouldIndexIsMainBranch() {
  443. RuleDto rule = db.rules().insert();
  444. ProjectData projectData = db.components().insertPrivateProject();
  445. BranchDto mainBranchDto = projectData.getMainBranchDto();
  446. ComponentDto mainBranchComponent = projectData.getMainBranchComponent();
  447. BranchDto newMainBranchDto = db.components().insertProjectBranch(projectData.getProjectDto(), b -> b.setKey("newMainBranch"));
  448. ComponentDto newMainBranchComponent = db.components().getComponentDto(newMainBranchDto);
  449. IssueDto issue1 = createIssue(rule, mainBranchComponent);
  450. IssueDto issue2 = createIssue(rule, newMainBranchComponent);
  451. underTest.indexAllIssues();
  452. assertThat(es.getDocuments(TYPE_ISSUE, IssueDoc.class)).extracting(IssueDoc::branchUuid, IssueDoc::isMainBranch)
  453. .containsExactlyInAnyOrder(tuple(issue1.getProjectUuid(), true), tuple(issue2.getProjectUuid(), false));
  454. db.getDbClient().branchDao().updateIsMain(db.getSession(), projectData.getMainBranchDto().getUuid(), false);
  455. db.getDbClient().branchDao().updateIsMain(db.getSession(), newMainBranchDto.getUuid(), true);
  456. indexBranches(List.of(mainBranchDto.getUuid(), newMainBranchDto.getUuid()), Indexers.BranchEvent.SWITCH_OF_MAIN_BRANCH);
  457. assertThat(es.getDocuments(TYPE_ISSUE, IssueDoc.class)).extracting(IssueDoc::branchUuid, IssueDoc::isMainBranch)
  458. .containsExactlyInAnyOrder(tuple(issue1.getProjectUuid(), false), tuple(issue2.getProjectUuid(), true));
  459. }
  460. private IssueDto createIssue(RuleDto rule, ComponentDto branch) {
  461. ComponentDto dir2 = db.components().insertComponent(ComponentTesting.newDirectory(branch, "src/main/java/foo"));
  462. ComponentDto file2 = db.components().insertComponent(newFileDto(branch, dir2));
  463. return db.issues().insert(rule, branch, file2);
  464. }
  465. @Test
  466. public void issue_on_test_file_has_test_scope() {
  467. RuleDto rule = db.rules().insert();
  468. ComponentDto project = db.components().insertPrivateProject().getMainBranchComponent();
  469. ComponentDto dir = db.components().insertComponent(ComponentTesting.newDirectory(project, "src/main/java/foo"));
  470. ComponentDto file = db.components().insertComponent(newFileDto(project, dir, "F1").setQualifier("UTS"));
  471. IssueDto issue = db.issues().insert(rule, project, file);
  472. underTest.indexAllIssues();
  473. IssueDoc doc = es.getDocuments(TYPE_ISSUE, IssueDoc.class).get(0);
  474. assertThat(doc.getId()).isEqualTo(issue.getKey());
  475. assertThat(doc.componentUuid()).isEqualTo(file.uuid());
  476. assertThat(doc.scope()).isEqualTo(IssueScope.TEST);
  477. }
  478. @Test
  479. public void issue_on_directory_has_main_code_scope() {
  480. RuleDto rule = db.rules().insert();
  481. ComponentDto project = db.components().insertPrivateProject().getMainBranchComponent();
  482. ComponentDto dir = db.components().insertComponent(ComponentTesting.newDirectory(project, "src/main/java/foo"));
  483. IssueDto issue = db.issues().insert(rule, project, dir);
  484. underTest.indexAllIssues();
  485. IssueDoc doc = es.getDocuments(TYPE_ISSUE, IssueDoc.class).get(0);
  486. assertThat(doc.getId()).isEqualTo(issue.getKey());
  487. assertThat(doc.componentUuid()).isEqualTo(dir.uuid());
  488. assertThat(doc.scope()).isEqualTo(IssueScope.MAIN);
  489. }
  490. @Test
  491. public void issue_on_project_has_main_code_scope() {
  492. RuleDto rule = db.rules().insert();
  493. ComponentDto mainBranchComponent = db.components().insertPrivateProject().getMainBranchComponent();
  494. IssueDto issue = db.issues().insert(rule, mainBranchComponent, mainBranchComponent);
  495. underTest.indexAllIssues();
  496. IssueDoc doc = es.getDocuments(TYPE_ISSUE, IssueDoc.class).get(0);
  497. assertThat(doc.getId()).isEqualTo(issue.getKey());
  498. assertThat(doc.componentUuid()).isEqualTo(mainBranchComponent.uuid());
  499. assertThat(doc.scope()).isEqualTo(IssueScope.MAIN);
  500. }
  501. @Test
  502. public void getType() {
  503. Assertions.assertThat(underTest.getType()).isEqualTo(StartupIndexer.Type.ASYNCHRONOUS);
  504. }
  505. @Test
  506. public void indexOnAnalysis_whenChangedComponents_shouldReindexOnlyChangedComponents() {
  507. RuleDto rule = db.rules().insert();
  508. ComponentDto mainBranchComponent = db.components().insertPrivateProject().getMainBranchComponent();
  509. ComponentDto changedComponent1 = db.components().insertComponent(newFileDto(mainBranchComponent));
  510. ComponentDto unchangedComponent = db.components().insertComponent(newFileDto(mainBranchComponent));
  511. ComponentDto ChangedComponent2 = db.components().insertComponent(newFileDto(mainBranchComponent));
  512. IssueDto changedIssue1 = db.issues().insert(rule, mainBranchComponent, changedComponent1);
  513. IssueDto changedIssue2 = db.issues().insert(rule, mainBranchComponent, changedComponent1);
  514. IssueDto changedIssue3 = db.issues().insert(rule, mainBranchComponent, ChangedComponent2);
  515. db.issues().insert(rule, mainBranchComponent, unchangedComponent);
  516. db.issues().insert(rule, mainBranchComponent, unchangedComponent);
  517. underTest.indexOnAnalysis(mainBranchComponent.uuid(), Set.of(unchangedComponent.uuid()));
  518. assertThatIndexHasOnly(changedIssue1, changedIssue2, changedIssue3);
  519. }
  520. @Test
  521. public void indexOnAnalysis_whenEmptyUnchangedComponents_shouldReindexEverything() {
  522. RuleDto rule = db.rules().insert();
  523. ComponentDto mainBranchComponent = db.components().insertPrivateProject().getMainBranchComponent();
  524. ComponentDto changedComponent = db.components().insertComponent(newFileDto(mainBranchComponent));
  525. IssueDto changedIssue1 = db.issues().insert(rule, mainBranchComponent, changedComponent);
  526. IssueDto changedIssue2 = db.issues().insert(rule, mainBranchComponent, changedComponent);
  527. underTest.indexOnAnalysis(mainBranchComponent.uuid(), Set.of());
  528. assertThatIndexHasOnly(changedIssue1, changedIssue2);
  529. }
  530. @Test
  531. public void indexOnAnalysis_whenChangedComponentWithoutIssue_shouldReindexNothing() {
  532. db.rules().insert();
  533. ComponentDto mainBranchComponent = db.components().insertPrivateProject().getMainBranchComponent();
  534. db.components().insertComponent(newFileDto(mainBranchComponent));
  535. underTest.indexOnAnalysis(mainBranchComponent.uuid(), Set.of());
  536. assertThat(es.getDocuments(TYPE_ISSUE)).isEmpty();
  537. }
  538. private void addIssueToIndex(String projectUuid, String branchUuid, String issueKey) {
  539. es.putDocuments(TYPE_ISSUE,
  540. newDoc().setKey(issueKey).setProjectUuid(projectUuid).setBranchUuid(branchUuid));
  541. }
  542. private void assertThatIndexHasSize(long expectedSize) {
  543. assertThat(es.countDocuments(TYPE_ISSUE)).isEqualTo(expectedSize);
  544. }
  545. private void assertThatIndexHasOnly(IssueDto... expectedIssues) {
  546. assertThat(es.getDocuments(TYPE_ISSUE))
  547. .extracting(SearchHit::getId)
  548. .containsExactlyInAnyOrder(Arrays.stream(expectedIssues).map(IssueDto::getKey).toArray(String[]::new));
  549. }
  550. private void assertThatIndexHasOnly(String... expectedKeys) {
  551. List<IssueDoc> issues = es.getDocuments(TYPE_ISSUE, IssueDoc.class);
  552. assertThat(issues).extracting(IssueDoc::key).containsOnly(expectedKeys);
  553. }
  554. private void assertThatEsQueueTableHasSize(int expectedSize) {
  555. assertThat(db.countRowsOfTable("es_queue")).isEqualTo(expectedSize);
  556. }
  557. private void assertThatDbHasOnly(IssueDto... expectedIssues) {
  558. try (DbSession otherSession = db.getDbClient().openSession(false)) {
  559. List<String> keys = Arrays.stream(expectedIssues).map(IssueDto::getKey).toList();
  560. assertThat(db.getDbClient().issueDao().selectByKeys(otherSession, keys)).hasSize(expectedIssues.length);
  561. }
  562. }
  563. private IndexingResult recover() {
  564. Collection<EsQueueDto> items = db.getDbClient().esQueueDao().selectForRecovery(db.getSession(), System.currentTimeMillis() + 1_000L, 10);
  565. return underTest.index(db.getSession(), items);
  566. }
  567. }