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.

IssueDaoIT.java 53KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2024 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.db.issue;
  21. import java.security.SecureRandom;
  22. import java.sql.SQLException;
  23. import java.util.Collection;
  24. import java.util.Collections;
  25. import java.util.Date;
  26. import java.util.Iterator;
  27. import java.util.List;
  28. import java.util.Optional;
  29. import java.util.Random;
  30. import java.util.Set;
  31. import java.util.stream.IntStream;
  32. import java.util.stream.Stream;
  33. import javax.annotation.Nullable;
  34. import org.apache.ibatis.cursor.Cursor;
  35. import org.jetbrains.annotations.NotNull;
  36. import org.junit.jupiter.api.BeforeEach;
  37. import org.junit.jupiter.api.Test;
  38. import org.junit.jupiter.api.extension.RegisterExtension;
  39. import org.junit.jupiter.params.ParameterizedTest;
  40. import org.junit.jupiter.params.provider.ValueSource;
  41. import org.sonar.api.issue.Issue;
  42. import org.sonar.api.issue.impact.Severity;
  43. import org.sonar.api.issue.impact.SoftwareQuality;
  44. import org.sonar.api.rule.RuleKey;
  45. import org.sonar.api.rules.RuleType;
  46. import org.sonar.api.utils.System2;
  47. import org.sonar.db.DbSession;
  48. import org.sonar.db.DbTester;
  49. import org.sonar.db.Pagination;
  50. import org.sonar.db.RowNotFoundException;
  51. import org.sonar.db.component.BranchDto;
  52. import org.sonar.db.component.BranchType;
  53. import org.sonar.db.component.ComponentDto;
  54. import org.sonar.db.component.ComponentTesting;
  55. import org.sonar.db.protobuf.DbIssues;
  56. import org.sonar.db.rule.RuleDto;
  57. import org.sonar.db.rule.RuleTesting;
  58. import org.sonar.db.user.UserDto;
  59. import static java.util.Arrays.asList;
  60. import static java.util.Collections.emptyList;
  61. import static org.assertj.core.api.Assertions.assertThat;
  62. import static org.assertj.core.api.Assertions.assertThatThrownBy;
  63. import static org.assertj.core.api.Assertions.fail;
  64. import static org.assertj.core.api.AssertionsForClassTypes.tuple;
  65. import static org.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE;
  66. import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
  67. import static org.sonar.api.issue.Issue.RESOLUTION_WONT_FIX;
  68. import static org.sonar.api.issue.Issue.STATUS_CLOSED;
  69. import static org.sonar.api.issue.Issue.STATUS_CONFIRMED;
  70. import static org.sonar.api.issue.Issue.STATUS_OPEN;
  71. import static org.sonar.api.issue.Issue.STATUS_REOPENED;
  72. import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
  73. import static org.sonar.api.issue.Issue.STATUS_REVIEWED;
  74. import static org.sonar.api.issue.impact.Severity.HIGH;
  75. import static org.sonar.api.issue.impact.Severity.LOW;
  76. import static org.sonar.api.issue.impact.Severity.MEDIUM;
  77. import static org.sonar.api.issue.impact.SoftwareQuality.MAINTAINABILITY;
  78. import static org.sonar.api.issue.impact.SoftwareQuality.RELIABILITY;
  79. import static org.sonar.api.issue.impact.SoftwareQuality.SECURITY;
  80. import static org.sonar.db.component.BranchType.BRANCH;
  81. import static org.sonar.db.component.BranchType.PULL_REQUEST;
  82. import static org.sonar.db.component.ComponentTesting.newFileDto;
  83. import static org.sonar.db.issue.IssueListQuery.IssueListQueryBuilder.newIssueListQueryBuilder;
  84. import static org.sonar.db.issue.IssueTesting.generateIssues;
  85. import static org.sonar.db.issue.IssueTesting.newCodeReferenceIssue;
  86. import static org.sonar.db.protobuf.DbIssues.MessageFormattingType.CODE;
  87. class IssueDaoIT {
  88. private static final String PROJECT_UUID = "prj_uuid";
  89. private static final String PROJECT_KEY = "prj_key";
  90. private static final String FILE_UUID = "file_uuid";
  91. private static final String FILE_KEY = "file_key";
  92. private static final RuleDto RULE = RuleTesting.newXooX1();
  93. private static final String ISSUE_KEY1 = "I1";
  94. private static final String ISSUE_KEY2 = "I2";
  95. private static final String TEST_CONTEXT_KEY = "test_context_key";
  96. private static final String USER_LOGIN = "user_login";
  97. private static final Random RANDOM = new SecureRandom();
  98. private static final RuleType[] RULE_TYPES_EXCEPT_HOTSPOT = Stream.of(RuleType.values())
  99. .filter(r -> r != RuleType.SECURITY_HOTSPOT)
  100. .toArray(RuleType[]::new);
  101. private static final DbIssues.MessageFormattings MESSAGE_FORMATTING = DbIssues.MessageFormattings.newBuilder()
  102. .addMessageFormatting(DbIssues.MessageFormatting.newBuilder()
  103. .setStart(0)
  104. .setEnd(4)
  105. .setType(CODE)
  106. .build())
  107. .build();
  108. @RegisterExtension
  109. private final DbTester db = DbTester.create(System2.INSTANCE);
  110. private final IssueDao underTest = db.getDbClient().issueDao();
  111. private ComponentDto projectDto;
  112. private UserDto userDto;
  113. @BeforeEach
  114. void setup() {
  115. int i = db.countSql(db.getSession(), "select count(1) from rules_default_impacts");
  116. db.rules().insert(RULE.setIsExternal(true));
  117. projectDto = db.components().insertPrivateProject(t -> t.setUuid(PROJECT_UUID).setKey(PROJECT_KEY)).getMainBranchComponent();
  118. db.components().insertComponent(newFileDto(projectDto).setUuid(FILE_UUID).setKey(FILE_KEY));
  119. userDto = db.users().insertUser(USER_LOGIN);
  120. }
  121. @Test
  122. void selectByKeyOrFail() {
  123. prepareTables();
  124. IssueDto expected = new IssueDto()
  125. .setKee(ISSUE_KEY1)
  126. .setComponentUuid(FILE_UUID)
  127. .setProjectUuid(PROJECT_UUID)
  128. .setRuleUuid(RULE.getUuid())
  129. .setLanguage(Optional.ofNullable(RULE.getLanguage()).orElseGet(() -> fail("Rule language should not be null here")))
  130. .setSeverity("BLOCKER")
  131. .setType(2)
  132. .setManualSeverity(false)
  133. .setMessage("the message")
  134. .setRuleDescriptionContextKey(TEST_CONTEXT_KEY)
  135. .setRuleCleanCodeAttribute(RULE.getCleanCodeAttribute())
  136. .setLine(500)
  137. .setEffort(10L)
  138. .setGap(3.14)
  139. .setStatus("RESOLVED")
  140. .setResolution("FIXED")
  141. .setChecksum("123456789")
  142. .setAuthorLogin("morgan")
  143. .setAssigneeUuid(userDto.getUuid())
  144. .setAssigneeLogin(USER_LOGIN)
  145. .setCreatedAt(1_440_000_000_000L)
  146. .setUpdatedAt(1_440_000_000_000L)
  147. .setRule(RULE)
  148. .setComponentKey(FILE_KEY)
  149. .setProjectKey(PROJECT_KEY)
  150. .setExternal(true)
  151. .setTags(List.of("tag1", "tag2"))
  152. .setCodeVariants(List.of("variant1", "variant2"))
  153. .setQuickFixAvailable(false)
  154. .setMessageFormattings(MESSAGE_FORMATTING);
  155. IssueDto issue = underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY1);
  156. assertThat(issue).usingRecursiveComparison()
  157. .ignoringFields("filePath", "issueCreationDate", "issueUpdateDate", "issueCloseDate", "cleanCodeAttribute", "impacts",
  158. "ruleDefaultImpacts")
  159. .isEqualTo(expected);
  160. assertThat(issue.parseMessageFormattings()).isEqualTo(MESSAGE_FORMATTING);
  161. assertThat(issue.getIssueCreationDate()).isNotNull();
  162. assertThat(issue.getIssueUpdateDate()).isNotNull();
  163. assertThat(issue.getIssueCloseDate()).isNotNull();
  164. assertThat(issue.getRuleRepo()).isEqualTo(RULE.getRepositoryKey());
  165. assertThat(issue.getRule()).isEqualTo(RULE.getRuleKey());
  166. assertThat(issue.getEffectiveCleanCodeAttribute()).isEqualTo(RULE.getCleanCodeAttribute());
  167. assertThat(issue.parseLocations()).isNull();
  168. assertThat(issue.getImpacts())
  169. .extracting(ImpactDto::getSeverity, ImpactDto::getSoftwareQuality)
  170. .containsExactlyInAnyOrder(
  171. tuple(MEDIUM, RELIABILITY),
  172. tuple(LOW, SECURITY));
  173. assertThat(issue.getRuleDefaultImpacts())
  174. .extracting(ImpactDto::getSeverity, ImpactDto::getSoftwareQuality)
  175. .containsExactlyInAnyOrder(tuple(HIGH, MAINTAINABILITY));
  176. }
  177. @Test
  178. void selectByKeyOrFail_fails_if_key_not_found() {
  179. prepareTables();
  180. DbSession session = db.getSession();
  181. assertThatThrownBy(() -> underTest.selectOrFailByKey(session, "DOES_NOT_EXIST"))
  182. .isInstanceOf(RowNotFoundException.class)
  183. .hasMessage("Issue with key 'DOES_NOT_EXIST' does not exist");
  184. }
  185. @Test
  186. void selectByKeys() {
  187. // contains I1 and I2
  188. prepareTables();
  189. List<IssueDto> issues = underTest.selectByKeys(db.getSession(), asList("I1", "I2", "I3"));
  190. assertThat(issues).extracting(IssueDto::getKey).containsExactlyInAnyOrder("I1", "I2");
  191. assertThat(issues).filteredOn(issueDto -> issueDto.getKey().equals("I1"))
  192. .extracting(IssueDto::getAssigneeLogin)
  193. .containsExactly(USER_LOGIN);
  194. assertThat(issues).filteredOn(issueDto -> issueDto.getKey().equals("I1"))
  195. .extracting(IssueDto::getImpacts)
  196. .flatMap(issueImpactDtos -> issueImpactDtos)
  197. .extracting(ImpactDto::getSeverity, ImpactDto::getSoftwareQuality)
  198. .containsExactlyInAnyOrder(
  199. tuple(MEDIUM, RELIABILITY),
  200. tuple(LOW, SECURITY));
  201. }
  202. @Test
  203. void scrollIndexationIssues_shouldReturnDto() throws SQLException {
  204. ComponentDto project = db.components().insertPrivateProject().getMainBranchComponent();
  205. RuleDto rule = db.rules().insert(r -> r.setRepositoryKey("java").setLanguage("java")
  206. .replaceAllDefaultImpacts(List.of(new ImpactDto()
  207. .setSoftwareQuality(MAINTAINABILITY)
  208. .setSeverity(MEDIUM),
  209. new ImpactDto()
  210. .setSoftwareQuality(RELIABILITY)
  211. .setSeverity(LOW))));
  212. ComponentDto branchA = db.components().insertProjectBranch(project, b -> b.setKey("branchA"));
  213. ComponentDto fileA = db.components().insertComponent(newFileDto(branchA));
  214. IntStream.range(0, 100).forEach(i -> insertBranchIssue(branchA, fileA, rule, "A" + i, STATUS_OPEN, 1_340_000_000_000L));
  215. Cursor<IndexedIssueDto> issues = underTest.scrollIssuesForIndexation(db.getSession(), null, null);
  216. Iterator<IndexedIssueDto> iterator = issues.iterator();
  217. int issueCount = 0;
  218. while (iterator.hasNext()) {
  219. IndexedIssueDto next = iterator.next();
  220. assertThat(next.getRuleDefaultImpacts()).hasSize(2)
  221. .extracting(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity)
  222. .containsExactlyInAnyOrder(
  223. tuple(RELIABILITY, LOW),
  224. tuple(MAINTAINABILITY, MEDIUM));
  225. assertThat(next.getImpacts())
  226. .extracting(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity)
  227. .containsExactlyInAnyOrder(
  228. tuple(MAINTAINABILITY, HIGH));
  229. issueCount++;
  230. }
  231. assertThat(issueCount).isEqualTo(100);
  232. }
  233. @Test
  234. void selectIssueKeysByComponentUuid() {
  235. // contains I1 and I2
  236. prepareTables();
  237. Set<String> issues = underTest.selectIssueKeysByComponentUuid(db.getSession(), PROJECT_UUID);
  238. // results are not ordered, so do not use "containsExactly"
  239. assertThat(issues).containsOnly("I1", "I2");
  240. }
  241. @Test
  242. void selectIssueKeysByComponentUuidFiltersAccordingly() {
  243. // contains I1 and I2
  244. prepareTables();
  245. // adds I3
  246. underTest.insert(db.getSession(), newIssueDto("I3")
  247. .setMessage("the message")
  248. .setRuleUuid(RULE.getUuid())
  249. .setComponentUuid(FILE_UUID)
  250. .setStatus("OPEN")
  251. .setProjectUuid(PROJECT_UUID));
  252. // Filter by including repositories
  253. Set<String> issues = underTest.selectIssueKeysByComponentUuid(db.getSession(), PROJECT_UUID, List.of("xoo"),
  254. emptyList(), emptyList(), 1);
  255. // results are not ordered, so do not use "containsExactly"
  256. assertThat(issues).containsOnly("I1", "I3");
  257. // Filter by excluding repositories
  258. issues = underTest.selectIssueKeysByComponentUuid(db.getSession(), PROJECT_UUID, emptyList(), List.of("xoo"),
  259. emptyList(), 1);
  260. assertThat(issues).isEmpty();
  261. // Filter by language
  262. issues = underTest.selectIssueKeysByComponentUuid(db.getSession(), PROJECT_UUID, emptyList(), emptyList(), List.of("xoo"), 1);
  263. assertThat(issues).containsOnly("I1", "I3");
  264. }
  265. @Test
  266. void selectIssueKeysByComponentUuidAndChangedSinceFiltersAccordingly() {
  267. long t1 = 1_340_000_000_000L;
  268. long t2 = 1_400_000_000_000L;
  269. // contains I1 and I2
  270. prepareTables();
  271. // Insert I3, I4, where t1 < t2
  272. IntStream.range(3, 5).forEach(i -> underTest.insert(db.getSession(), newIssueDto("I" + i).setUpdatedAt(t1)));
  273. // Filter by including repositories
  274. Set<String> issues = underTest.selectIssueKeysByComponentUuidAndChangedSinceDate(db.getSession(), PROJECT_UUID, t2, List.of("xoo"),
  275. emptyList(), emptyList(), 1);
  276. // results are not ordered, so do not use "containsExactly"
  277. assertThat(issues).contains("I1");
  278. // Filter by excluding repositories
  279. issues = underTest.selectIssueKeysByComponentUuidAndChangedSinceDate(db.getSession(), PROJECT_UUID, t2,
  280. emptyList(), List.of("xoo"), emptyList(), 1);
  281. assertThat(issues).isEmpty();
  282. // Filter by language
  283. issues = underTest.selectIssueKeysByComponentUuidAndChangedSinceDate(db.getSession(), PROJECT_UUID, t2, emptyList(),
  284. emptyList(), List.of("xoo"), 1);
  285. assertThat(issues).contains("I1");
  286. }
  287. @Test
  288. void selectByBranch() {
  289. long updatedAt = 1_340_000_000_000L;
  290. long changedSince = 1_000_000_000_000L;
  291. ComponentDto project = db.components().insertPrivateProject().getMainBranchComponent();
  292. RuleDto rule = db.rules().insert(r -> r.setRepositoryKey("java").setLanguage("java"));
  293. ComponentDto branchA = db.components().insertProjectBranch(project, b -> b.setKey("branchA"));
  294. ComponentDto fileA = db.components().insertComponent(newFileDto(branchA));
  295. List<String> statusesA = List.of(STATUS_OPEN, STATUS_REVIEWED, STATUS_CLOSED, STATUS_RESOLVED);
  296. IntStream.range(0, statusesA.size()).forEach(i -> insertBranchIssue(branchA, fileA, rule, "A" + i, statusesA.get(i), updatedAt));
  297. insertBranchIssue(branchA, fileA, rule, "WithResolution", STATUS_RESOLVED, RESOLUTION_FIXED, updatedAt);
  298. ComponentDto branchB = db.components().insertProjectBranch(project, b -> b.setKey("branchB"));
  299. ComponentDto fileB = db.components().insertComponent(newFileDto(branchB));
  300. List<String> statusesB = List.of(STATUS_OPEN, STATUS_RESOLVED);
  301. IntStream.range(0, statusesB.size()).forEach(i -> insertBranchIssue(branchB, fileB, rule, "B" + i, statusesB.get(i), updatedAt));
  302. List<IssueDto> branchAIssuesA1 = underTest.selectByBranch(db.getSession(), Set.of("issueA0", "issueA1", "issueA3",
  303. "issueWithResolution"),
  304. buildSelectByBranchQuery(branchA, false, changedSince));
  305. assertThat(branchAIssuesA1)
  306. .extracting(IssueDto::getKey, IssueDto::getStatus, IssueDto::getResolution)
  307. .containsExactlyInAnyOrder(
  308. tuple("issueA0", STATUS_OPEN, null),
  309. tuple("issueA1", STATUS_REVIEWED, null),
  310. tuple("issueA3", STATUS_RESOLVED, null),
  311. tuple("issueWithResolution", STATUS_RESOLVED, RESOLUTION_FIXED));
  312. assertThat(branchAIssuesA1.get(0))
  313. .extracting(IssueDto::getMessage, IssueDto::parseMessageFormattings)
  314. .containsOnly("message", MESSAGE_FORMATTING);
  315. List<IssueDto> branchAIssuesA2 = underTest.selectByBranch(db.getSession(), Set.of("issueA0", "issueA1", "issueA3"),
  316. buildSelectByBranchQuery(branchA, true, changedSince));
  317. assertThat(branchAIssuesA2)
  318. .extracting(IssueDto::getKey, IssueDto::getStatus)
  319. .containsExactlyInAnyOrder(tuple("issueA0", STATUS_OPEN),
  320. tuple("issueA1", STATUS_REVIEWED),
  321. tuple("issueA3", STATUS_RESOLVED));
  322. List<IssueDto> branchBIssuesB1 = underTest.selectByBranch(db.getSession(), Set.of("issueB0", "issueB1"),
  323. buildSelectByBranchQuery(branchB, false, changedSince));
  324. assertThat(branchBIssuesB1)
  325. .extracting(IssueDto::getKey, IssueDto::getStatus)
  326. .containsExactlyInAnyOrder(
  327. tuple("issueB0", STATUS_OPEN),
  328. tuple("issueB1", STATUS_RESOLVED));
  329. List<IssueDto> branchBIssuesB2 = underTest.selectByBranch(db.getSession(), Set.of("issueB0", "issueB1"),
  330. buildSelectByBranchQuery(branchB, true, changedSince));
  331. assertThat(branchBIssuesB2)
  332. .extracting(IssueDto::getKey, IssueDto::getStatus)
  333. .containsExactlyInAnyOrder(tuple("issueB0", STATUS_OPEN),
  334. tuple("issueB1", STATUS_RESOLVED));
  335. }
  336. @Test
  337. void selectOpenByComponentUuid() {
  338. RuleDto rule = db.rules().insert();
  339. ComponentDto project = db.components().insertPublicProject().getMainBranchComponent();
  340. ComponentDto projectBranch = db.components().insertProjectBranch(project,
  341. b -> b.setKey("feature/foo")
  342. .setBranchType(BranchType.BRANCH));
  343. ComponentDto file = db.components().insertComponent(newFileDto(projectBranch));
  344. IssueDto openIssue = db.issues().insert(rule, projectBranch, file, i -> i.setStatus(STATUS_OPEN).setResolution(null));
  345. db.issues().insert(rule, projectBranch, file, i -> i.setStatus(STATUS_CLOSED).setResolution(RESOLUTION_FIXED));
  346. IssueDto reopenedIssue = db.issues().insert(rule, projectBranch, file, i -> i.setStatus(STATUS_REOPENED).setResolution(null));
  347. IssueDto confirmedIssue = db.issues().insert(rule, projectBranch, file, i -> i.setStatus(STATUS_CONFIRMED).setResolution(null));
  348. IssueDto wontfixIssue = db.issues().insert(rule, projectBranch, file,
  349. i -> i.setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_WONT_FIX));
  350. IssueDto fpIssue = db.issues().insert(rule, projectBranch, file,
  351. i -> i.setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_FALSE_POSITIVE));
  352. assertThat(underTest.selectOpenByComponentUuids(db.getSession(), Collections.singletonList(file.uuid())))
  353. .extracting("kee")
  354. .containsOnly(openIssue.getKey(), reopenedIssue.getKey(), confirmedIssue.getKey(), wontfixIssue.getKey(), fpIssue.getKey());
  355. }
  356. @Test
  357. void selectOpenByComponentUuid_should_correctly_map_required_fields() {
  358. RuleDto rule = db.rules().insert();
  359. ComponentDto project = db.components().insertPublicProject().getMainBranchComponent();
  360. ComponentDto projectBranch = db.components().insertProjectBranch(project,
  361. b -> b.setKey("feature/foo")
  362. .setBranchType(BranchType.BRANCH));
  363. ComponentDto file = db.components().insertComponent(newFileDto(projectBranch));
  364. IssueDto fpIssue = db.issues().insert(rule, projectBranch, file, i -> i.setStatus("RESOLVED").setResolution("FALSE-POSITIVE"));
  365. PrIssueDto fp = underTest.selectOpenByComponentUuids(db.getSession(), Collections.singletonList(file.uuid())).get(0);
  366. assertThat(fp.getLine()).isEqualTo(fpIssue.getLine());
  367. assertThat(fp.getMessage()).isEqualTo(fpIssue.getMessage());
  368. assertThat(fp.getChecksum()).isEqualTo(fpIssue.getChecksum());
  369. assertThat(fp.getRuleKey()).isEqualTo(fpIssue.getRuleKey());
  370. assertThat(fp.getStatus()).isEqualTo(fpIssue.getStatus());
  371. assertThat(fp.getLine()).isNotNull();
  372. assertThat(fp.getLine()).isNotZero();
  373. assertThat(fp.getMessage()).isNotNull();
  374. assertThat(fp.getChecksum()).isNotNull();
  375. assertThat(fp.getChecksum()).isNotEmpty();
  376. assertThat(fp.getRuleKey()).isNotNull();
  377. assertThat(fp.getStatus()).isNotNull();
  378. assertThat(fp.getBranchKey()).isEqualTo("feature/foo");
  379. assertThat(fp.getIssueUpdateDate()).isNotNull();
  380. }
  381. @Test
  382. void test_selectIssueGroupsByComponent_on_component_without_issues() {
  383. ComponentDto project = db.components().insertPublicProject().getMainBranchComponent();
  384. ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
  385. Collection<IssueGroupDto> groups = underTest.selectIssueGroupsByComponent(db.getSession(), file, 1_000L);
  386. assertThat(groups).isEmpty();
  387. }
  388. @Test
  389. void selectByKey_givenOneIssueWithQuickFix_selectOneIssueWithQuickFix() {
  390. underTest.insert(db.getSession(), newIssueDto(ISSUE_KEY1)
  391. .setMessage("the message")
  392. .setRuleUuid(RULE.getUuid())
  393. .setComponentUuid(FILE_UUID)
  394. .setProjectUuid(PROJECT_UUID)
  395. .setQuickFixAvailable(true));
  396. IssueDto issue = underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY1);
  397. assertThat(issue.getKee()).isEqualTo(ISSUE_KEY1);
  398. assertThat(issue.isQuickFixAvailable()).isTrue();
  399. }
  400. @Test
  401. void selectByKey_givenOneIssueWithoutQuickFix_selectOneIssueWithoutQuickFix() {
  402. underTest.insert(db.getSession(), createIssueWithKey(ISSUE_KEY1));
  403. IssueDto issue = underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY1);
  404. assertThat(issue.getKee()).isEqualTo(ISSUE_KEY1);
  405. assertThat(issue.isQuickFixAvailable()).isFalse();
  406. }
  407. @Test
  408. void selectIssueGroupsByComponent_on_file() {
  409. ComponentDto project = db.components().insertPublicProject().getMainBranchComponent();
  410. ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
  411. RuleDto rule = db.rules().insert();
  412. db.issues().insert(rule, project, file,
  413. i -> i.setStatus("RESOLVED").setResolution("FALSE-POSITIVE").setSeverity("MAJOR").setType(RuleType.BUG).setIssueCreationTime(1_500L));
  414. db.issues().insert(rule, project, file,
  415. i -> i.setStatus("OPEN").setResolution(null).setSeverity("CRITICAL").setType(RuleType.BUG).setIssueCreationTime(1_600L));
  416. IssueDto criticalBug2 = db.issues().insert(rule, project, file,
  417. i -> i.setStatus("OPEN").setResolution(null).setSeverity("CRITICAL").setType(RuleType.BUG).setIssueCreationTime(1_700L));
  418. // closed issues are ignored
  419. db.issues().insert(rule, project, file,
  420. i -> i.setStatus("CLOSED").setResolution("REMOVED").setSeverity("CRITICAL").setType(RuleType.BUG).setIssueCreationTime(1_700L));
  421. Collection<IssueGroupDto> result = underTest.selectIssueGroupsByComponent(db.getSession(), file, 1_000L);
  422. assertThat(result.stream().mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(3);
  423. assertThat(result.stream().filter(g -> g.getRuleType() == RuleType.BUG.getDbConstant()).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(3);
  424. assertThat(result.stream().filter(g -> g.getRuleType() == RuleType.CODE_SMELL.getDbConstant()).mapToLong(IssueGroupDto::getCount).sum()).isZero();
  425. assertThat(result.stream().filter(g -> g.getRuleType() == RuleType.VULNERABILITY.getDbConstant()).mapToLong(IssueGroupDto::getCount).sum()).isZero();
  426. assertThat(result.stream().filter(g -> g.getSeverity().equals("CRITICAL")).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(2);
  427. assertThat(result.stream().filter(g -> g.getSeverity().equals("MAJOR")).mapToLong(IssueGroupDto::getCount).sum()).isOne();
  428. assertThat(result.stream().filter(g -> g.getSeverity().equals("MINOR")).mapToLong(IssueGroupDto::getCount).sum()).isZero();
  429. assertThat(result.stream().filter(g -> g.getStatus().equals("OPEN")).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(2);
  430. assertThat(result.stream().filter(g -> g.getStatus().equals("RESOLVED")).mapToLong(IssueGroupDto::getCount).sum()).isOne();
  431. assertThat(result.stream().filter(g -> g.getStatus().equals("CLOSED")).mapToLong(IssueGroupDto::getCount).sum()).isZero();
  432. assertThat(result.stream().filter(g -> "FALSE-POSITIVE".equals(g.getResolution())).mapToLong(IssueGroupDto::getCount).sum()).isOne();
  433. assertThat(result.stream().filter(g -> g.getResolution() == null).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(2);
  434. assertThat(result.stream().filter(IssueGroupDto::isInLeak).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(3);
  435. assertThat(result.stream().filter(g -> !g.isInLeak()).mapToLong(IssueGroupDto::getCount).sum()).isZero();
  436. // test leak
  437. result = underTest.selectIssueGroupsByComponent(db.getSession(), file, 999_999_999L);
  438. assertThat(result.stream().filter(IssueGroupDto::isInLeak).mapToLong(IssueGroupDto::getCount).sum()).isZero();
  439. assertThat(result.stream().filter(g -> !g.isInLeak()).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(3);
  440. // test leak using exact creation time of criticalBug2 issue
  441. result = underTest.selectIssueGroupsByComponent(db.getSession(), file, criticalBug2.getIssueCreationTime());
  442. assertThat(result.stream().filter(IssueGroupDto::isInLeak).mapToLong(IssueGroupDto::getCount).sum()).isZero();
  443. assertThat(result.stream().filter(g -> !g.isInLeak()).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(3);
  444. }
  445. @Test
  446. void selectGroupsOfComponentTreeOnLeak_on_file_new_code_reference_branch() {
  447. ComponentDto project = db.components().insertPublicProject().getMainBranchComponent();
  448. ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
  449. RuleDto rule = db.rules().insert();
  450. IssueDto fpBug = db.issues().insert(rule, project, file,
  451. i -> i.setStatus("RESOLVED").setResolution("FALSE-POSITIVE").setSeverity("MAJOR").setType(RuleType.BUG));
  452. IssueDto criticalBug1 = db.issues().insert(rule, project, file,
  453. i -> i.setStatus("OPEN").setResolution(null).setSeverity("CRITICAL").setType(RuleType.BUG));
  454. IssueDto criticalBug2 = db.issues().insert(rule, project, file,
  455. i -> i.setStatus("OPEN").setResolution(null).setSeverity("CRITICAL").setType(RuleType.BUG));
  456. db.issues().insert(rule, project, file,
  457. i -> i.setStatus("OPEN").setResolution(null).setSeverity("CRITICAL").setType(RuleType.BUG));
  458. // two issues part of new code period on reference branch
  459. db.issues().insertNewCodeReferenceIssue(fpBug);
  460. db.issues().insertNewCodeReferenceIssue(criticalBug1);
  461. db.issues().insertNewCodeReferenceIssue(criticalBug2);
  462. Collection<IssueGroupDto> result = underTest.selectIssueGroupsByComponent(db.getSession(), file, -1);
  463. assertThat(result.stream().mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(4);
  464. assertThat(result.stream().filter(g -> g.getRuleType() == RuleType.BUG.getDbConstant()).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(4);
  465. assertThat(result.stream().filter(g -> g.getRuleType() == RuleType.CODE_SMELL.getDbConstant()).mapToLong(IssueGroupDto::getCount).sum()).isZero();
  466. assertThat(result.stream().filter(g -> g.getRuleType() == RuleType.VULNERABILITY.getDbConstant()).mapToLong(IssueGroupDto::getCount).sum()).isZero();
  467. assertThat(result.stream().filter(g -> g.getSeverity().equals("CRITICAL")).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(3);
  468. assertThat(result.stream().filter(g -> g.getSeverity().equals("MAJOR")).mapToLong(IssueGroupDto::getCount).sum()).isOne();
  469. assertThat(result.stream().filter(g -> g.getSeverity().equals("MINOR")).mapToLong(IssueGroupDto::getCount).sum()).isZero();
  470. assertThat(result.stream().filter(g -> g.getStatus().equals("OPEN")).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(3);
  471. assertThat(result.stream().filter(g -> g.getStatus().equals("RESOLVED")).mapToLong(IssueGroupDto::getCount).sum()).isOne();
  472. assertThat(result.stream().filter(g -> g.getStatus().equals("CLOSED")).mapToLong(IssueGroupDto::getCount).sum()).isZero();
  473. assertThat(result.stream().filter(g -> "FALSE-POSITIVE".equals(g.getResolution())).mapToLong(IssueGroupDto::getCount).sum()).isOne();
  474. assertThat(result.stream().filter(g -> g.getResolution() == null).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(3);
  475. assertThat(result.stream().filter(IssueGroupDto::isInLeak).mapToLong(IssueGroupDto::getCount).sum()).isEqualTo(3);
  476. assertThat(result.stream().filter(g -> !g.isInLeak()).mapToLong(IssueGroupDto::getCount).sum()).isOne();
  477. }
  478. @ParameterizedTest
  479. @ValueSource(booleans = {true, false})
  480. void selectIssueImpactGroupsByComponent_shouldReturnImpactGroups(boolean inLeak) {
  481. ComponentDto project = db.components().insertPublicProject().getMainBranchComponent();
  482. ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
  483. RuleDto rule = db.rules().insert();
  484. db.issues().insert(rule, project, file,
  485. i -> i.setStatus(STATUS_OPEN).replaceAllImpacts(List.of(createImpact(SECURITY, HIGH), createImpact(MAINTAINABILITY, LOW))));
  486. db.issues().insert(rule, project, file,
  487. i -> i.setStatus(STATUS_OPEN).replaceAllImpacts(List.of(createImpact(SECURITY, HIGH), createImpact(MAINTAINABILITY, HIGH))));
  488. db.issues().insert(rule, project, file,
  489. i -> i.setStatus(STATUS_REOPENED).replaceAllImpacts(List.of(createImpact(SECURITY, HIGH))));
  490. db.issues().insert(rule, project, file,
  491. i -> i.setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_WONT_FIX).replaceAllImpacts(List.of(createImpact(SECURITY, HIGH))));
  492. // issues in ignored status
  493. db.issues().insert(rule, project, file,
  494. i -> i.setStatus(Issue.STATUS_CLOSED).replaceAllImpacts(List.of(createImpact(SECURITY, HIGH))));
  495. db.issues().insert(rule, project, file,
  496. i -> i.setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_FALSE_POSITIVE).replaceAllImpacts(List.of(createImpact(SECURITY, HIGH))));
  497. Collection<IssueImpactGroupDto> result = underTest.selectIssueImpactGroupsByComponent(db.getSession(), file, inLeak ? 1L : Long.MAX_VALUE);
  498. assertThat(result).hasSize(5);
  499. assertThat(result.stream().filter(IssueImpactGroupDto::isInLeak)).hasSize(inLeak ? 5 : 0);
  500. assertThat(result.stream().mapToLong(IssueImpactGroupDto::getCount).sum()).isEqualTo(6);
  501. assertThat(result.stream().filter(g -> g.getSoftwareQuality() == SECURITY).mapToLong(IssueImpactGroupDto::getCount).sum()).isEqualTo(4);
  502. assertThat(result.stream().filter(g -> g.getSoftwareQuality() == MAINTAINABILITY).mapToLong(IssueImpactGroupDto::getCount).sum()).isEqualTo(2);
  503. assertThat(result.stream().filter(g -> g.getSeverity() == HIGH).mapToLong(IssueImpactGroupDto::getCount).sum()).isEqualTo(5);
  504. assertThat(result.stream().filter(g -> g.getSeverity() == LOW).mapToLong(IssueImpactGroupDto::getCount).sum()).isOne();
  505. assertThat(result.stream().filter(g -> STATUS_OPEN.equals(g.getStatus())).mapToLong(IssueImpactGroupDto::getCount).sum()).isEqualTo(4);
  506. assertThat(result.stream().filter(g -> STATUS_REOPENED.equals(g.getStatus())).mapToLong(IssueImpactGroupDto::getCount).sum()).isOne();
  507. assertThat(result.stream().filter(g -> STATUS_RESOLVED.equals(g.getStatus())).mapToLong(IssueImpactGroupDto::getCount).sum()).isOne();
  508. assertThat(result.stream().filter(g -> RESOLUTION_WONT_FIX.equals(g.getResolution())).mapToLong(IssueImpactGroupDto::getCount).sum()).isOne();
  509. assertThat(result.stream().filter(g -> g.getResolution() == null).mapToLong(IssueImpactGroupDto::getCount).sum()).isEqualTo(5);
  510. assertThat(result.stream().noneMatch(g -> STATUS_CLOSED.equals(g.getResolution()))).isTrue();
  511. assertThat(result.stream().noneMatch(g -> RESOLUTION_FALSE_POSITIVE.equals(g.getResolution()))).isTrue();
  512. assertThat(result.stream().noneMatch(g -> RELIABILITY == g.getSoftwareQuality())).isTrue();
  513. assertThat(result.stream().noneMatch(g -> MEDIUM == g.getSeverity())).isTrue();
  514. }
  515. @Test
  516. void selectIssueImpactGroupsByComponent_whenNewCodeFromReferenceBranch_shouldReturnImpactGroups() {
  517. ComponentDto project = db.components().insertPublicProject().getMainBranchComponent();
  518. ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
  519. RuleDto rule = db.rules().insert();
  520. IssueDto issueInNewCodePeriod = db.issues().insert(rule, project, file,
  521. i -> i.setStatus(STATUS_OPEN).replaceAllImpacts(List.of(createImpact(SECURITY, HIGH), createImpact(MAINTAINABILITY, LOW))));
  522. db.issues().insert(rule, project, file,
  523. i -> i.setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_WONT_FIX).replaceAllImpacts(List.of(createImpact(SECURITY, HIGH))));
  524. db.issues().insertNewCodeReferenceIssue(issueInNewCodePeriod);
  525. Collection<IssueImpactGroupDto> result = underTest.selectIssueImpactGroupsByComponent(db.getSession(), file, -1L);
  526. assertThat(result).hasSize(3);
  527. assertThat(result.stream().filter(IssueImpactGroupDto::isInLeak)).hasSize(2);
  528. assertThat(result.stream().mapToLong(IssueImpactGroupDto::getCount).sum()).isEqualTo(3);
  529. assertThat(result.stream().filter(g -> g.getSoftwareQuality() == SECURITY).mapToLong(IssueImpactGroupDto::getCount).sum()).isEqualTo(2);
  530. assertThat(result.stream().filter(g -> g.getSoftwareQuality() == MAINTAINABILITY).mapToLong(IssueImpactGroupDto::getCount).sum()).isEqualTo(1);
  531. assertThat(result.stream().filter(g -> g.getSeverity() == HIGH).mapToLong(IssueImpactGroupDto::getCount).sum()).isEqualTo(2);
  532. assertThat(result.stream().filter(g -> g.getSeverity() == LOW).mapToLong(IssueImpactGroupDto::getCount).sum()).isOne();
  533. assertThat(result.stream().filter(g -> STATUS_OPEN.equals(g.getStatus())).mapToLong(IssueImpactGroupDto::getCount).sum()).isEqualTo(2);
  534. assertThat(result.stream().filter(g -> STATUS_REOPENED.equals(g.getStatus())).mapToLong(IssueImpactGroupDto::getCount).sum()).isZero();
  535. assertThat(result.stream().filter(g -> STATUS_RESOLVED.equals(g.getStatus())).mapToLong(IssueImpactGroupDto::getCount).sum()).isOne();
  536. assertThat(result.stream().filter(g -> RESOLUTION_WONT_FIX.equals(g.getResolution())).mapToLong(IssueImpactGroupDto::getCount).sum()).isOne();
  537. assertThat(result.stream().filter(IssueImpactGroupDto::isInLeak).mapToLong(IssueImpactGroupDto::getCount).sum()).isEqualTo(2);
  538. assertThat(result.stream().filter(g -> g.isInLeak() && g.getSoftwareQuality() == SECURITY).mapToLong(IssueImpactGroupDto::getCount).sum()).isEqualTo(1);
  539. assertThat(result.stream().filter(g -> g.isInLeak() && g.getSoftwareQuality() == MAINTAINABILITY).mapToLong(IssueImpactGroupDto::getCount).sum()).isEqualTo(1);
  540. }
  541. @NotNull
  542. private static ImpactDto createImpact(SoftwareQuality softwareQuality, Severity high) {
  543. return new ImpactDto().setSoftwareQuality(softwareQuality).setSeverity(high);
  544. }
  545. @Test
  546. void selectIssueImpactGroupsByComponent_whenComponentWithNoIssues_shouldReturnEmpty() {
  547. ComponentDto project = db.components().insertPublicProject().getMainBranchComponent();
  548. ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(project));
  549. Collection<IssueImpactGroupDto> groups = underTest.selectIssueImpactGroupsByComponent(db.getSession(), file, 1_000L);
  550. assertThat(groups).isEmpty();
  551. }
  552. @Test
  553. void selectByKey_givenOneIssueNewOnReferenceBranch_selectOneIssueWithNewOnReferenceBranch() {
  554. underTest.insert(db.getSession(), newIssueDto(ISSUE_KEY1)
  555. .setMessage("the message")
  556. .setRuleUuid(RULE.getUuid())
  557. .setComponentUuid(FILE_UUID)
  558. .setProjectUuid(PROJECT_UUID)
  559. .setQuickFixAvailable(true));
  560. underTest.insert(db.getSession(), newIssueDto(ISSUE_KEY2)
  561. .setMessage("the message")
  562. .setRuleUuid(RULE.getUuid())
  563. .setComponentUuid(FILE_UUID)
  564. .setProjectUuid(PROJECT_UUID)
  565. .setQuickFixAvailable(true));
  566. IssueDto issue1 = underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY1);
  567. IssueDto issue2 = underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY2);
  568. assertThat(issue1.isNewCodeReferenceIssue()).isFalse();
  569. assertThat(issue2.isNewCodeReferenceIssue()).isFalse();
  570. underTest.insertAsNewCodeOnReferenceBranch(db.getSession(), newCodeReferenceIssue(issue1));
  571. assertThat(underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY1).isNewCodeReferenceIssue()).isTrue();
  572. assertThat(underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY2).isNewCodeReferenceIssue()).isFalse();
  573. underTest.deleteAsNewCodeOnReferenceBranch(db.getSession(), ISSUE_KEY1);
  574. assertThat(underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY1).isNewCodeReferenceIssue()).isFalse();
  575. }
  576. @Test
  577. void selectByKey_givenOneIssueWithoutRuleDescriptionContextKey_returnsEmptyOptional() {
  578. underTest.insert(db.getSession(), createIssueWithKey(ISSUE_KEY1)
  579. .setRuleDescriptionContextKey(null));
  580. IssueDto issue1 = underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY1);
  581. assertThat(issue1.getOptionalRuleDescriptionContextKey()).isEmpty();
  582. }
  583. @Test
  584. void selectByKey_givenOneIssueWithRuleDescriptionContextKey_returnsContextKey() {
  585. underTest.insert(db.getSession(), createIssueWithKey(ISSUE_KEY1)
  586. .setRuleDescriptionContextKey(TEST_CONTEXT_KEY));
  587. IssueDto issue1 = underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY1);
  588. assertThat(issue1.getOptionalRuleDescriptionContextKey()).contains(TEST_CONTEXT_KEY);
  589. }
  590. @Test
  591. void insert_shouldInsertBatchIssuesWithImpacts() {
  592. ImpactDto impact1 = new ImpactDto()
  593. .setSoftwareQuality(MAINTAINABILITY)
  594. .setSeverity(HIGH);
  595. ImpactDto impact2 = new ImpactDto()
  596. .setSoftwareQuality(SECURITY)
  597. .setSeverity(LOW);
  598. IssueDto issue1 = createIssueWithKey(ISSUE_KEY1)
  599. .addImpact(impact1)
  600. .addImpact(impact2);
  601. IssueDto issue2 = createIssueWithKey(ISSUE_KEY2);
  602. underTest.insert(db.getSession(), issue1, issue2);
  603. List<IssueDto> issueDtos = underTest.selectByKeys(db.getSession(), Set.of(ISSUE_KEY1, ISSUE_KEY2));
  604. assertThat(issueDtos)
  605. .extracting(IssueDto::getKey)
  606. .containsExactlyInAnyOrder(ISSUE_KEY1, ISSUE_KEY2);
  607. assertThat(issueDtos).filteredOn(issueDto -> issueDto.getKey().equals(ISSUE_KEY1))
  608. .flatExtracting(IssueDto::getImpacts)
  609. .containsExactlyInAnyOrder(impact1, impact2)
  610. .doesNotContainNull();
  611. }
  612. @Test
  613. void deleteIssueImpacts_shouldDeleteOnlyImpactsOfIssue() {
  614. ImpactDto impact1 = new ImpactDto()
  615. .setSoftwareQuality(MAINTAINABILITY)
  616. .setSeverity(HIGH);
  617. ImpactDto impact2 = new ImpactDto()
  618. .setSoftwareQuality(SECURITY)
  619. .setSeverity(LOW);
  620. IssueDto issue1 = createIssueWithKey(ISSUE_KEY1)
  621. .addImpact(impact1)
  622. .addImpact(impact2);
  623. underTest.insert(db.getSession(), issue1);
  624. underTest.deleteIssueImpacts(db.getSession(), issue1);
  625. Optional<IssueDto> issueDto = underTest.selectByKey(db.getSession(), ISSUE_KEY1);
  626. assertThat(issueDto).isPresent()
  627. .get()
  628. .extracting(IssueDto::getImpacts)
  629. .satisfies(impactDtos -> assertThat(impactDtos).isEmpty());
  630. }
  631. @Test
  632. void updateWithoutIssueImpacts_shouldNotReplaceIssueImpacts() {
  633. ImpactDto impact1 = new ImpactDto()
  634. .setSoftwareQuality(MAINTAINABILITY)
  635. .setSeverity(HIGH);
  636. ImpactDto impact2 = new ImpactDto()
  637. .setSoftwareQuality(SECURITY)
  638. .setSeverity(LOW);
  639. IssueDto issue1 = createIssueWithKey(ISSUE_KEY1)
  640. .addImpact(impact1)
  641. .addImpact(impact2);
  642. underTest.insert(db.getSession(), issue1);
  643. issue1.setResolution(RESOLUTION_FALSE_POSITIVE);
  644. underTest.updateWithoutIssueImpacts(db.getSession(), issue1);
  645. Optional<IssueDto> issueDto = underTest.selectByKey(db.getSession(), ISSUE_KEY1);
  646. assertThat(issueDto).isPresent()
  647. .get()
  648. .satisfies(i -> assertThat(i.getResolution()).isEqualTo(RESOLUTION_FALSE_POSITIVE))
  649. .extracting(IssueDto::getImpacts)
  650. .satisfies(impactDtos -> assertThat(impactDtos).extracting(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity)
  651. .containsExactlyInAnyOrder(tuple(MAINTAINABILITY, HIGH), tuple(SECURITY, LOW)));
  652. }
  653. @Test
  654. void update_whenUpdatingRuleDescriptionContextKeyToNull_returnsEmptyContextKey() {
  655. IssueDto issue = createIssueWithKey(ISSUE_KEY1).setRuleDescriptionContextKey(TEST_CONTEXT_KEY);
  656. underTest.insert(db.getSession(), issue);
  657. issue.setRuleDescriptionContextKey(null);
  658. underTest.update(db.getSession(), issue);
  659. issue = underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY1);
  660. assertThat(issue.getOptionalRuleDescriptionContextKey()).isEmpty();
  661. }
  662. @Test
  663. void update_whenUpdatingRuleDescriptionContextKeyToNotNull_returnsContextKey() {
  664. IssueDto issue = createIssueWithKey(ISSUE_KEY1).setRuleDescriptionContextKey(null);
  665. underTest.insert(db.getSession(), issue);
  666. issue.setRuleDescriptionContextKey(TEST_CONTEXT_KEY);
  667. underTest.update(db.getSession(), issue);
  668. issue = underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY1);
  669. assertThat(issue.getOptionalRuleDescriptionContextKey()).contains(TEST_CONTEXT_KEY);
  670. }
  671. @Test
  672. void update_givenOneIssueWithoutRuleDescriptionContextKey_returnsContextKey() {
  673. IssueDto issue = createIssueWithKey(ISSUE_KEY1).setRuleDescriptionContextKey(TEST_CONTEXT_KEY);
  674. underTest.insert(db.getSession(), issue);
  675. issue.setRuleDescriptionContextKey(null);
  676. underTest.update(db.getSession(), issue);
  677. issue = underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY1);
  678. assertThat(issue.getOptionalRuleDescriptionContextKey()).isEmpty();
  679. issue.setRuleDescriptionContextKey(TEST_CONTEXT_KEY);
  680. underTest.update(db.getSession(), issue);
  681. issue = underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY1);
  682. assertThat(issue.getOptionalRuleDescriptionContextKey()).contains(TEST_CONTEXT_KEY);
  683. }
  684. @Test
  685. void selectIssueKeysByQuery_shouldBePaginated() {
  686. List<IssueDto> issues = generateIssues(10, i -> createIssueWithKey("i-" + i));
  687. issues.forEach(issue -> underTest.insert(db.getSession(), issue));
  688. List<String> results = underTest.selectIssueKeysByQuery(
  689. db.getSession(),
  690. newIssueListQueryBuilder().project(PROJECT_KEY).build(),
  691. Pagination.forPage(2).andSize(3));
  692. assertThat(results.stream().toList()).hasSize(3);
  693. }
  694. @Test
  695. void selectIssueKeysByQuery_whenFilteredByBranch_shouldGetOnlyBranchIssues() {
  696. BranchDto branchDto = ComponentTesting.newBranchDto(PROJECT_UUID, BRANCH);
  697. ComponentDto branch = db.components().insertProjectBranch(projectDto, branchDto);
  698. ComponentDto branchFile = db.components().insertComponent(newFileDto(branch));
  699. List<IssueDto> mainBranchIssues = generateIssues(3, i -> createIssueWithKey("i-" + i));
  700. List<IssueDto> otherBranchIssues = generateIssues(3, i -> createIssueWithKey("branch-" + i, branch.uuid(), branchFile.uuid()));
  701. Stream.concat(mainBranchIssues.stream(), otherBranchIssues.stream())
  702. .forEach(issue -> underTest.insert(db.getSession(), issue));
  703. List<String> results = underTest.selectIssueKeysByQuery(
  704. db.getSession(),
  705. newIssueListQueryBuilder().project(PROJECT_KEY).branch(branchDto.getKey()).build(),
  706. Pagination.forPage(1).andSize(6));
  707. List<String> expectedKeys = List.of("branch-0", "branch-1", "branch-2");
  708. assertThat(results.stream().toList()).containsExactlyInAnyOrderElementsOf(expectedKeys);
  709. }
  710. @Test
  711. void selectIssueKeysByQuery_whenFilteredByPullRequest_shouldGetOnlyPRIssues() {
  712. BranchDto pullRequestDto = ComponentTesting.newBranchDto(PROJECT_UUID, PULL_REQUEST);
  713. ComponentDto branch = db.components().insertProjectBranch(projectDto, pullRequestDto);
  714. ComponentDto branchFile = db.components().insertComponent(newFileDto(branch));
  715. List<IssueDto> mainBranchIssues = generateIssues(3, i -> createIssueWithKey("i-" + i));
  716. List<IssueDto> otherBranchIssues = generateIssues(3, i -> createIssueWithKey("pr-" + i, branch.uuid(), branchFile.uuid()));
  717. Stream.concat(mainBranchIssues.stream(), otherBranchIssues.stream())
  718. .forEach(issue -> underTest.insert(db.getSession(), issue));
  719. List<String> results = underTest.selectIssueKeysByQuery(
  720. db.getSession(),
  721. newIssueListQueryBuilder().project(PROJECT_KEY).pullRequest(pullRequestDto.getKey()).build(),
  722. Pagination.forPage(1).andSize(6));
  723. List<String> expectedKeys = List.of("pr-0", "pr-1", "pr-2");
  724. assertThat(results.stream().toList()).containsExactlyInAnyOrderElementsOf(expectedKeys);
  725. }
  726. @Test
  727. void selectIssueKeysByQuery_whenFilteredByTypes_shouldGetIssuesWithSpecifiedTypes() {
  728. List<IssueDto> bugs = generateIssues(3, i -> createIssueWithKey("bug-" + i).setType(RuleType.BUG));
  729. List<IssueDto> codeSmells = generateIssues(3, i -> createIssueWithKey("codesmell-" + i).setType(RuleType.CODE_SMELL));
  730. Stream.of(bugs, codeSmells)
  731. .flatMap(Collection::stream)
  732. .forEach(issue -> underTest.insert(db.getSession(), issue));
  733. List<String> results = underTest.selectIssueKeysByQuery(
  734. db.getSession(),
  735. newIssueListQueryBuilder().project(PROJECT_KEY).types(List.of(RuleType.BUG.getDbConstant())).build(),
  736. Pagination.forPage(1).andSize(10));
  737. List<String> expectedKeys = List.of("bug-0", "bug-1", "bug-2");
  738. assertThat(results.stream().toList()).containsExactlyInAnyOrderElementsOf(expectedKeys);
  739. }
  740. @Test
  741. void selectIssueKeysByQuery_whenFilteredByFilteredStatuses_shouldGetIssuesWithoutSpecifiedStatuses() {
  742. List<IssueDto> openIssues = generateIssues(3, i -> createIssueWithKey("open-" + i).setStatus("OPEN"));
  743. List<IssueDto> closedIssues = generateIssues(3, i -> createIssueWithKey("closed-" + i).setStatus("CLOSED"));
  744. List<IssueDto> resolvedIssues = generateIssues(3, i -> createIssueWithKey("resolved-" + i).setStatus("RESOLVED"));
  745. Stream.of(openIssues, closedIssues, resolvedIssues)
  746. .flatMap(Collection::stream)
  747. .forEach(issue -> underTest.insert(db.getSession(), issue));
  748. List<String> results = underTest.selectIssueKeysByQuery(
  749. db.getSession(),
  750. newIssueListQueryBuilder().project(PROJECT_KEY).statuses(List.of("OPEN")).build(),
  751. Pagination.forPage(1).andSize(10));
  752. List<String> expectedKeys = List.of("open-0", "open-1", "open-2");
  753. assertThat(results.stream().toList()).containsExactlyInAnyOrderElementsOf(expectedKeys);
  754. }
  755. @Test
  756. void selectIssueKeysByQuery_whenFilteredByFilteredResolutions_shouldGetIssuesWithoutSpecifiedResolution() {
  757. List<IssueDto> unresolvedIssues = generateIssues(3, i -> createIssueWithKey("open-" + i).setResolution(null));
  758. List<IssueDto> wontfixIssues = generateIssues(3, i -> createIssueWithKey("wf-" + i).setResolution("WONTFIX"));
  759. List<IssueDto> falsePositiveIssues = generateIssues(3, i -> createIssueWithKey("fp-" + i).setResolution("FALSE-POSITIVE"));
  760. Stream.of(unresolvedIssues, wontfixIssues, falsePositiveIssues)
  761. .flatMap(Collection::stream)
  762. .forEach(issue -> underTest.insert(db.getSession(), issue));
  763. List<String> results = underTest.selectIssueKeysByQuery(
  764. db.getSession(),
  765. newIssueListQueryBuilder().project(PROJECT_KEY).resolutions(List.of("WONTFIX")).build(),
  766. Pagination.forPage(1).andSize(10));
  767. List<String> expectedKeys = List.of("wf-0", "wf-1", "wf-2");
  768. assertThat(results.stream().toList()).containsExactlyInAnyOrderElementsOf(expectedKeys);
  769. }
  770. @Test
  771. void selectIssueKeysByQuery_whenFilteredByFileComponent_shouldGetIssuesWithinFileOnly() {
  772. ComponentDto otherFileDto = db.components().insertComponent(newFileDto(projectDto).setUuid("OTHER_UUID").setKey("OTHER_KEY"));
  773. List<IssueDto> fromFileIssues = generateIssues(3, i -> createIssueWithKey("file-" + i));
  774. List<IssueDto> fromOtherFileIssues = generateIssues(3, i -> createIssueWithKey("otherfile-" + i, PROJECT_UUID, otherFileDto.uuid()));
  775. Stream.of(fromFileIssues, fromOtherFileIssues)
  776. .flatMap(Collection::stream)
  777. .forEach(issue -> underTest.insert(db.getSession(), issue));
  778. List<String> results = underTest.selectIssueKeysByQuery(
  779. db.getSession(),
  780. newIssueListQueryBuilder().component(otherFileDto.getKey()).build(),
  781. Pagination.forPage(1).andSize(10));
  782. List<String> expectedKeys = List.of("otherfile-0", "otherfile-1", "otherfile-2");
  783. assertThat(results.stream().toList()).containsExactlyInAnyOrderElementsOf(expectedKeys);
  784. }
  785. @Test
  786. void selectIssueKeysByQuery_whenFilteredWithInNewCodeReference_shouldGetNewCodeReferenceIssues() {
  787. List<IssueDto> issues = generateIssues(3, i -> createIssueWithKey("i-" + i));
  788. List<IssueDto> newCodeRefIssues = generateIssues(3, i -> createIssueWithKey("newCodeRef-" + i));
  789. Stream.of(issues, newCodeRefIssues)
  790. .flatMap(Collection::stream)
  791. .forEach(issue -> underTest.insert(db.getSession(), issue));
  792. newCodeRefIssues.forEach(issue -> db.issues().insertNewCodeReferenceIssue(issue));
  793. List<String> results = underTest.selectIssueKeysByQuery(
  794. db.getSession(),
  795. newIssueListQueryBuilder().project(PROJECT_KEY).newCodeOnReference(true).build(),
  796. Pagination.forPage(1).andSize(10));
  797. List<String> expectedKeys = List.of("newCodeRef-0", "newCodeRef-1", "newCodeRef-2");
  798. assertThat(results.stream().toList()).containsExactlyInAnyOrderElementsOf(expectedKeys);
  799. }
  800. @Test
  801. void selectIssueKeysByQuery_whenFilteredWithCreatedAfter_shouldGetIssuesCreatedAfterDate() {
  802. List<IssueDto> createdBeforeIssues = generateIssues(3,
  803. i -> createIssueWithKey("createdBefore-" + i).setResolution(null).setIssueCreationDate(new Date(1_400_000_000_000L)));
  804. List<IssueDto> createdAfterIssues = generateIssues(3,
  805. i -> createIssueWithKey("createdAfter-" + i).setResolution(null).setIssueCreationDate(new Date(1_420_000_000_000L)));
  806. Stream.of(createdBeforeIssues, createdAfterIssues)
  807. .flatMap(Collection::stream)
  808. .forEach(issue -> underTest.insert(db.getSession(), issue));
  809. List<String> results = underTest.selectIssueKeysByQuery(
  810. db.getSession(),
  811. newIssueListQueryBuilder().project(PROJECT_KEY).createdAfter(1_410_000_000_000L).build(),
  812. Pagination.forPage(1).andSize(10));
  813. List<String> expectedKeys = List.of("createdAfter-0", "createdAfter-1", "createdAfter-2");
  814. assertThat(results.stream().toList()).containsExactlyInAnyOrderElementsOf(expectedKeys);
  815. }
  816. @Test
  817. void selectIssueKeysByQuery_whenFilteredWithSoftwareQualities_shouldGetThoseIssuesOnly() {
  818. prepareTables(); // One of the issues has software quality impact: SECURITY
  819. List<String> results = underTest.selectIssueKeysByQuery(
  820. db.getSession(),
  821. newIssueListQueryBuilder().project(PROJECT_KEY).softwareQualities(List.of("SECURITY")).build(),
  822. Pagination.forPage(1).andSize(10));
  823. List<String> expectedKeys = List.of("I1");
  824. assertThat(results.stream().toList()).containsExactlyInAnyOrderElementsOf(expectedKeys);
  825. }
  826. @Test
  827. void updateIfBeforeSelectedDate_whenCalledWithBeforeSelectDate_shouldUpdateImpacts() {
  828. prepareTables();
  829. IssueDto issueDto = underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY1)
  830. .setSelectedAt(1_440_000_000_000L);
  831. boolean isUpdated = underTest.updateIfBeforeSelectedDate(db.getSession(), issueDto);
  832. assertThat(isUpdated).isTrue();
  833. }
  834. @Test
  835. void updateIfBeforeSelectedDate_whenCalledWithAfterSelectDate_shouldNotUpdateImpacts() {
  836. prepareTables();
  837. IssueDto issueDto = underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY1)
  838. .setSelectedAt(1_400_000_000_000L)
  839. .replaceAllImpacts(List.of(createImpact(RELIABILITY, LOW)));
  840. underTest.updateIfBeforeSelectedDate(db.getSession(), issueDto);
  841. assertThat(underTest.selectOrFailByKey(db.getSession(), ISSUE_KEY1).getImpacts()).extracting(i -> i.getSoftwareQuality(),
  842. i -> i.getSeverity())
  843. .containsExactlyInAnyOrder(tuple(RELIABILITY, MEDIUM), tuple(SECURITY, LOW));
  844. }
  845. private static IssueDto createIssueWithKey(String issueKey) {
  846. return createIssueWithKey(issueKey, PROJECT_UUID, FILE_UUID);
  847. }
  848. private static IssueDto createIssueWithKey(String issueKey, String branchUuid, String fileUuid) {
  849. return newIssueDto(issueKey)
  850. .setMessage("the message")
  851. .setRuleUuid(RULE.getUuid())
  852. .setComponentUuid(fileUuid)
  853. .setProjectUuid(branchUuid)
  854. .setQuickFixAvailable(false);
  855. }
  856. private static IssueDto newIssueDto(String key) {
  857. IssueDto dto = new IssueDto();
  858. dto.setComponent(new ComponentDto().setKey("struts:Action").setUuid("component-uuid"));
  859. dto.setProject(new ComponentDto().setKey("struts").setUuid("project-uuid"));
  860. dto.setRule(RuleTesting.newRule(RuleKey.of("java", "S001")).setUuid("uuid-200"));
  861. dto.setKee(key);
  862. dto.setType(2);
  863. dto.setLine(500);
  864. dto.setGap(3.14);
  865. dto.setEffort(10L);
  866. dto.setResolution("FIXED");
  867. dto.setStatus("RESOLVED");
  868. dto.setSeverity("BLOCKER");
  869. dto.setAuthorLogin("morgan");
  870. dto.setAssigneeUuid("karadoc");
  871. dto.setChecksum("123456789");
  872. dto.setMessage("the message");
  873. dto.setMessageFormattings(MESSAGE_FORMATTING);
  874. dto.setRuleDescriptionContextKey(TEST_CONTEXT_KEY);
  875. dto.setCreatedAt(1_440_000_000_000L);
  876. dto.setUpdatedAt(1_440_000_000_000L);
  877. dto.setIssueCreationTime(1_450_000_000_000L);
  878. dto.setIssueUpdateTime(1_450_000_000_000L);
  879. dto.setIssueCloseTime(1_450_000_000_000L);
  880. dto.setTags(Set.of("tag1", "tag2"));
  881. dto.setCodeVariants(Set.of("variant1", "variant2"));
  882. return dto;
  883. }
  884. private void prepareTables() {
  885. underTest.insert(db.getSession(), newIssueDto(ISSUE_KEY1)
  886. .setMessage("the message")
  887. .setRuleUuid(RULE.getUuid())
  888. .setComponentUuid(FILE_UUID)
  889. .setProjectUuid(PROJECT_UUID)
  890. .setAssigneeUuid(userDto.getUuid())
  891. .addImpact(newIssueImpact(RELIABILITY, MEDIUM))
  892. .addImpact(newIssueImpact(SECURITY, LOW)));
  893. underTest.insert(db.getSession(), newIssueDto(ISSUE_KEY2)
  894. .setRuleUuid(RULE.getUuid())
  895. .setComponentUuid(FILE_UUID)
  896. .setStatus("CLOSED")
  897. .setProjectUuid(PROJECT_UUID));
  898. db.getSession().commit();
  899. }
  900. private static ImpactDto newIssueImpact(SoftwareQuality softwareQuality, Severity severity) {
  901. return new ImpactDto()
  902. .setSoftwareQuality(softwareQuality)
  903. .setSeverity(severity);
  904. }
  905. private static RuleType randomRuleTypeExceptHotspot() {
  906. return RULE_TYPES_EXCEPT_HOTSPOT[RANDOM.nextInt(RULE_TYPES_EXCEPT_HOTSPOT.length)];
  907. }
  908. private void insertBranchIssue(ComponentDto branch, ComponentDto file, RuleDto rule, String id, String status,
  909. @Nullable String resolution, Long updateAt) {
  910. db.issues().insert(rule, branch, file, i -> i.setKee("issue" + id)
  911. .setStatus(status)
  912. .setResolution(resolution)
  913. .setUpdatedAt(updateAt)
  914. .setType(randomRuleTypeExceptHotspot())
  915. .setMessage("message")
  916. .setMessageFormattings(MESSAGE_FORMATTING));
  917. }
  918. private void insertBranchIssue(ComponentDto branch, ComponentDto file, RuleDto rule, String id, String status, Long updateAt) {
  919. insertBranchIssue(branch, file, rule, id, status, null, updateAt);
  920. }
  921. private static IssueQueryParams buildSelectByBranchQuery(ComponentDto branch, boolean resolvedOnly, Long changedSince) {
  922. return new IssueQueryParams(branch.uuid(), List.of("java"), List.of(), List.of(), resolvedOnly, changedSince);
  923. }
  924. }