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.

IssueIndexFacetsTest.java 37KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860
  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.time.ZoneId;
  22. import java.util.Collections;
  23. import java.util.Date;
  24. import java.util.Map;
  25. import java.util.Set;
  26. import org.elasticsearch.action.search.SearchResponse;
  27. import org.junit.Test;
  28. import org.sonar.api.issue.impact.Severity;
  29. import org.sonar.api.rules.RuleType;
  30. import org.sonar.api.server.rule.RulesDefinition.OwaspAsvsVersion;
  31. import org.sonar.db.component.ComponentDto;
  32. import org.sonar.db.rule.RuleDto;
  33. import org.sonar.server.es.Facets;
  34. import org.sonar.server.es.SearchOptions;
  35. import org.sonar.server.security.SecurityStandards.SQCategory;
  36. import static java.util.Arrays.asList;
  37. import static java.util.Collections.singletonList;
  38. import static java.util.stream.IntStream.rangeClosed;
  39. import static org.assertj.core.api.Assertions.assertThat;
  40. import static org.assertj.core.api.Assertions.entry;
  41. import static org.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE;
  42. import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
  43. import static org.sonar.api.issue.Issue.RESOLUTION_REMOVED;
  44. import static org.sonar.api.issue.Issue.RESOLUTION_WONT_FIX;
  45. import static org.sonar.api.issue.Issue.STATUS_CLOSED;
  46. import static org.sonar.api.issue.Issue.STATUS_CONFIRMED;
  47. import static org.sonar.api.issue.Issue.STATUS_OPEN;
  48. import static org.sonar.api.issue.Issue.STATUS_REOPENED;
  49. import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
  50. import static org.sonar.api.issue.impact.SoftwareQuality.MAINTAINABILITY;
  51. import static org.sonar.api.issue.impact.SoftwareQuality.RELIABILITY;
  52. import static org.sonar.api.rule.Severity.BLOCKER;
  53. import static org.sonar.api.rule.Severity.CRITICAL;
  54. import static org.sonar.api.rule.Severity.INFO;
  55. import static org.sonar.api.rule.Severity.MAJOR;
  56. import static org.sonar.api.rule.Severity.MINOR;
  57. import static org.sonar.api.rules.CleanCodeAttributeCategory.ADAPTABLE;
  58. import static org.sonar.api.rules.CleanCodeAttributeCategory.CONSISTENT;
  59. import static org.sonar.api.rules.CleanCodeAttributeCategory.INTENTIONAL;
  60. import static org.sonar.api.rules.CleanCodeAttributeCategory.RESPONSIBLE;
  61. import static org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version.Y2017;
  62. import static org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version.Y2021;
  63. import static org.sonar.api.server.rule.RulesDefinition.PciDssVersion.V3_2;
  64. import static org.sonar.api.server.rule.RulesDefinition.PciDssVersion.V4_0;
  65. import static org.sonar.api.utils.DateUtils.parseDateTime;
  66. import static org.sonar.db.component.ComponentTesting.newDirectory;
  67. import static org.sonar.db.component.ComponentTesting.newFileDto;
  68. import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
  69. import static org.sonar.db.rule.RuleTesting.newRule;
  70. import static org.sonar.server.issue.IssueDocTesting.newDoc;
  71. import static org.sonar.server.issue.IssueDocTesting.newDocForProject;
  72. public class IssueIndexFacetsTest extends IssueIndexTestCommon {
  73. @Test
  74. public void facet_on_projectUuids() {
  75. ComponentDto project = newPrivateProjectDto("ABCD");
  76. ComponentDto project2 = newPrivateProjectDto("EFGH");
  77. indexIssues(
  78. newDoc("I1", project.uuid(), newFileDto(project)),
  79. newDoc("I2", project.uuid(), newFileDto(project)),
  80. newDoc("I3", project2.uuid(), newFileDto(project2)));
  81. assertThatFacetHasExactly(IssueQuery.builder(), "projects", entry("ABCD", 2L), entry("EFGH", 1L));
  82. }
  83. @Test
  84. public void facet_on_projectUuids_return_100_entries_plus_selected_values() {
  85. indexIssues(rangeClosed(1, 110).mapToObj(i -> newDocForProject(newPrivateProjectDto("a" + i))).toArray(IssueDoc[]::new));
  86. IssueDoc issue1 = newDocForProject(newPrivateProjectDto("project1"));
  87. IssueDoc issue2 = newDocForProject(newPrivateProjectDto("project2"));
  88. indexIssues(issue1, issue2);
  89. assertThatFacetHasSize(IssueQuery.builder().build(), "projects", 100);
  90. assertThatFacetHasSize(IssueQuery.builder().projectUuids(asList(issue1.projectUuid(), issue2.projectUuid())).build(), "projects", 102);
  91. }
  92. @Test
  93. public void facets_on_files() {
  94. ComponentDto project = newPrivateProjectDto("A");
  95. ComponentDto dir = newDirectory(project, "src");
  96. ComponentDto file1 = newFileDto(project, dir, "ABCD");
  97. ComponentDto file2 = newFileDto(project, dir, "BCDE");
  98. ComponentDto file3 = newFileDto(project, dir, "CDEF");
  99. indexIssues(
  100. newDocForProject("I1", project),
  101. newDoc("I2", project.uuid(), file1),
  102. newDoc("I3", project.uuid(), file2),
  103. newDoc("I4", project.uuid(), file2),
  104. newDoc("I5", project.uuid(), file3));
  105. assertThatFacetHasOnly(IssueQuery.builder(), "files", entry("src/NAME_ABCD", 1L), entry("src/NAME_BCDE", 2L), entry("src/NAME_CDEF", 1L));
  106. }
  107. @Test
  108. public void facet_on_files_return_100_entries_plus_selected_values() {
  109. ComponentDto project = newPrivateProjectDto();
  110. indexIssues(rangeClosed(1, 110).mapToObj(i -> newDoc(newFileDto(project, null, "a" + i), project.uuid())).toArray(IssueDoc[]::new));
  111. IssueDoc issue1 = newDoc(newFileDto(project, null, "file1"), project.uuid());
  112. IssueDoc issue2 = newDoc(newFileDto(project, null, "file2"), project.uuid());
  113. indexIssues(issue1, issue2);
  114. assertThatFacetHasSize(IssueQuery.builder().build(), "files", 100);
  115. assertThatFacetHasSize(IssueQuery.builder().files(asList(issue1.filePath(), issue2.filePath())).build(), "files", 102);
  116. }
  117. @Test
  118. public void facets_on_directories() {
  119. ComponentDto project = newPrivateProjectDto();
  120. ComponentDto file1 = newFileDto(project).setPath("src/main/xoo/F1.xoo");
  121. ComponentDto file2 = newFileDto(project).setPath("F2.xoo");
  122. indexIssues(
  123. newDoc("I1", project.uuid(), file1).setDirectoryPath("/src/main/xoo"),
  124. newDoc("I2", project.uuid(), file2).setDirectoryPath("/"));
  125. assertThatFacetHasOnly(IssueQuery.builder(), "directories", entry("/src/main/xoo", 1L), entry("/", 1L));
  126. }
  127. @Test
  128. public void facet_on_directories_return_100_entries_plus_selected_values() {
  129. ComponentDto project = newPrivateProjectDto();
  130. indexIssues(
  131. rangeClosed(1, 110).mapToObj(i -> newDoc(newFileDto(project, newDirectory(project, "dir" + i)), project.uuid()).setDirectoryPath("a" + i)).toArray(IssueDoc[]::new));
  132. IssueDoc issue1 = newDoc(newFileDto(project, newDirectory(project, "path1")), project.uuid()).setDirectoryPath("directory1");
  133. IssueDoc issue2 = newDoc(newFileDto(project, newDirectory(project, "path2")), project.uuid()).setDirectoryPath("directory2");
  134. indexIssues(issue1, issue2);
  135. assertThatFacetHasSize(IssueQuery.builder().build(), "directories", 100);
  136. assertThatFacetHasSize(IssueQuery.builder().directories(asList(issue1.directoryPath(), issue2.directoryPath())).build(), "directories", 102);
  137. }
  138. @Test
  139. public void facets_on_cwe() {
  140. ComponentDto project = newPrivateProjectDto();
  141. ComponentDto file = newFileDto(project);
  142. indexIssues(
  143. newDoc("I1", project.uuid(), file).setType(RuleType.VULNERABILITY).setCwe(asList("20", "564", "89", "943")),
  144. newDoc("I2", project.uuid(), file).setType(RuleType.VULNERABILITY).setCwe(asList("943")),
  145. newDoc("I3", project.uuid(), file));
  146. assertThatFacetHasOnly(IssueQuery.builder(), "cwe",
  147. entry("943", 2L),
  148. entry("20", 1L),
  149. entry("564", 1L),
  150. entry("89", 1L));
  151. }
  152. @Test
  153. public void facets_on_pciDss32() {
  154. ComponentDto project = newPrivateProjectDto();
  155. ComponentDto file = newFileDto(project);
  156. indexIssues(
  157. newDoc("I1", project.uuid(), file).setType(RuleType.VULNERABILITY).setPciDss32(asList("1", "2")),
  158. newDoc("I2", project.uuid(), file).setType(RuleType.VULNERABILITY).setPciDss32(singletonList("3")),
  159. newDoc("I3", project.uuid(), file));
  160. assertThatFacetHasOnly(IssueQuery.builder(), V3_2.prefix(),
  161. entry("1", 1L),
  162. entry("2", 1L),
  163. entry("3", 1L));
  164. }
  165. @Test
  166. public void facets_on_pciDss40() {
  167. ComponentDto project = newPrivateProjectDto();
  168. ComponentDto file = newFileDto(project);
  169. indexIssues(
  170. newDoc("I1", project.uuid(), file).setType(RuleType.VULNERABILITY).setPciDss40(asList("1", "2")),
  171. newDoc("I2", project.uuid(), file).setType(RuleType.VULNERABILITY).setPciDss40(singletonList("3")),
  172. newDoc("I3", project.uuid(), file));
  173. assertThatFacetHasOnly(IssueQuery.builder(), V4_0.prefix(),
  174. entry("1", 1L),
  175. entry("2", 1L),
  176. entry("3", 1L));
  177. }
  178. @Test
  179. public void facets_on_owaspAsvs40() {
  180. ComponentDto project = newPrivateProjectDto();
  181. ComponentDto file = newFileDto(project);
  182. indexIssues(
  183. newDoc("I1", project.uuid(), file).setType(RuleType.VULNERABILITY).setOwaspAsvs40(asList("1", "2")),
  184. newDoc("I2", project.uuid(), file).setType(RuleType.VULNERABILITY).setOwaspAsvs40(singletonList("3")),
  185. newDoc("I3", project.uuid(), file));
  186. assertThatFacetHasOnly(IssueQuery.builder(), OwaspAsvsVersion.V4_0.prefix(),
  187. entry("1", 1L),
  188. entry("2", 1L),
  189. entry("3", 1L));
  190. }
  191. @Test
  192. public void facets_on_owaspTop10() {
  193. ComponentDto project = newPrivateProjectDto();
  194. ComponentDto file = newFileDto(project);
  195. indexIssues(
  196. newDoc("I1", project.uuid(), file).setType(RuleType.VULNERABILITY).setOwaspTop10(asList("a1", "a2")),
  197. newDoc("I2", project.uuid(), file).setType(RuleType.VULNERABILITY).setOwaspTop10(singletonList("a3")),
  198. newDoc("I3", project.uuid(), file));
  199. assertThatFacetHasOnly(IssueQuery.builder(), Y2017.prefix(),
  200. entry("a1", 1L),
  201. entry("a2", 1L),
  202. entry("a3", 1L));
  203. }
  204. @Test
  205. public void facets_on_owaspTop10_2021() {
  206. ComponentDto project = newPrivateProjectDto();
  207. ComponentDto file = newFileDto(project);
  208. indexIssues(
  209. newDoc("I1", project.uuid(), file).setType(RuleType.VULNERABILITY).setOwaspTop10For2021(asList("a1", "a2")),
  210. newDoc("I2", project.uuid(), file).setType(RuleType.VULNERABILITY).setOwaspTop10For2021(singletonList("a3")),
  211. newDoc("I3", project.uuid(), file));
  212. assertThatFacetHasExactly(IssueQuery.builder(), Y2021.prefix(),
  213. entry("a1", 1L),
  214. entry("a2", 1L),
  215. entry("a3", 1L));
  216. }
  217. @Test
  218. public void facets_on_owaspTop10_2021_stay_ordered() {
  219. ComponentDto project = newPrivateProjectDto();
  220. ComponentDto file = newFileDto(project);
  221. indexIssues(
  222. newDoc("I1", project.uuid(), file).setType(RuleType.VULNERABILITY).setOwaspTop10For2021(asList("a1", "a2")),
  223. newDoc("I2", project.uuid(), file).setType(RuleType.VULNERABILITY).setOwaspTop10For2021(singletonList("a3")),
  224. newDoc("I3", project.uuid(), file));
  225. assertThatFacetHasExactly(IssueQuery.builder().owaspTop10For2021(Collections.singletonList("a3")), Y2021.prefix(),
  226. entry("a1", 1L),
  227. entry("a2", 1L),
  228. entry("a3", 1L));
  229. }
  230. @Test
  231. public void facets_on_sansTop25() {
  232. ComponentDto project = newPrivateProjectDto();
  233. ComponentDto file = newFileDto(project);
  234. indexIssues(
  235. newDoc("I1", project.uuid(), file).setType(RuleType.VULNERABILITY).setSansTop25(asList("porous-defenses", "risky-resource", "insecure-interaction")),
  236. newDoc("I2", project.uuid(), file).setType(RuleType.VULNERABILITY).setSansTop25(singletonList("porous-defenses")),
  237. newDoc("I3", project.uuid(), file));
  238. assertThatFacetHasOnly(IssueQuery.builder(), "sansTop25",
  239. entry("insecure-interaction", 1L),
  240. entry("porous-defenses", 2L),
  241. entry("risky-resource", 1L));
  242. }
  243. @Test
  244. public void facets_on_sonarSourceSecurity() {
  245. ComponentDto project = newPrivateProjectDto();
  246. ComponentDto file = newFileDto(project);
  247. indexIssues(
  248. newDoc("I1", project.uuid(), file).setType(RuleType.VULNERABILITY).setSonarSourceSecurityCategory(SQCategory.BUFFER_OVERFLOW),
  249. newDoc("I2", project.uuid(), file).setType(RuleType.VULNERABILITY).setSonarSourceSecurityCategory(SQCategory.DOS),
  250. newDoc("I3", project.uuid(), file));
  251. assertThatFacetHasOnly(IssueQuery.builder(), "sonarsourceSecurity",
  252. entry("buffer-overflow", 1L),
  253. entry("dos", 1L));
  254. }
  255. @Test
  256. public void facets_on_severities() {
  257. ComponentDto project = newPrivateProjectDto();
  258. ComponentDto file = newFileDto(project);
  259. indexIssues(
  260. newDoc("I1", project.uuid(), file).setSeverity(INFO),
  261. newDoc("I2", project.uuid(), file).setSeverity(INFO),
  262. newDoc("I3", project.uuid(), file).setSeverity(MAJOR));
  263. assertThatFacetHasOnly(IssueQuery.builder(), "severities", entry("INFO", 2L), entry("MAJOR", 1L));
  264. }
  265. @Test
  266. public void facet_on_severities_return_5_entries_max() {
  267. ComponentDto project = newPrivateProjectDto();
  268. ComponentDto file = newFileDto(project);
  269. indexIssues(
  270. newDoc("I2", project.uuid(), file).setSeverity(INFO),
  271. newDoc("I1", project.uuid(), file).setSeverity(MINOR),
  272. newDoc("I3", project.uuid(), file).setSeverity(MAJOR),
  273. newDoc("I4", project.uuid(), file).setSeverity(CRITICAL),
  274. newDoc("I5", project.uuid(), file).setSeverity(BLOCKER),
  275. newDoc("I6", project.uuid(), file).setSeverity(MAJOR));
  276. assertThatFacetHasSize(IssueQuery.builder().build(), "severities", 5);
  277. }
  278. @Test
  279. public void facets_on_statuses() {
  280. ComponentDto project = newPrivateProjectDto();
  281. ComponentDto file = newFileDto(project);
  282. indexIssues(
  283. newDoc("I1", project.uuid(), file).setStatus(STATUS_CLOSED),
  284. newDoc("I2", project.uuid(), file).setStatus(STATUS_CLOSED),
  285. newDoc("I3", project.uuid(), file).setStatus(STATUS_OPEN));
  286. assertThatFacetHasOnly(IssueQuery.builder(), "statuses", entry("CLOSED", 2L), entry("OPEN", 1L));
  287. }
  288. @Test
  289. public void facet_on_statuses_return_5_entries_max() {
  290. ComponentDto project = newPrivateProjectDto();
  291. ComponentDto file = newFileDto(project);
  292. indexIssues(
  293. newDoc("I1", project.uuid(), file).setStatus(STATUS_OPEN),
  294. newDoc("I2", project.uuid(), file).setStatus(STATUS_CONFIRMED),
  295. newDoc("I3", project.uuid(), file).setStatus(STATUS_REOPENED),
  296. newDoc("I4", project.uuid(), file).setStatus(STATUS_RESOLVED),
  297. newDoc("I5", project.uuid(), file).setStatus(STATUS_CLOSED),
  298. newDoc("I6", project.uuid(), file).setStatus(STATUS_OPEN));
  299. assertThatFacetHasSize(IssueQuery.builder().build(), "statuses", 5);
  300. }
  301. @Test
  302. public void facets_on_resolutions() {
  303. ComponentDto project = newPrivateProjectDto();
  304. ComponentDto file = newFileDto(project);
  305. indexIssues(
  306. newDoc("I1", project.uuid(), file).setResolution(RESOLUTION_FALSE_POSITIVE),
  307. newDoc("I2", project.uuid(), file).setResolution(RESOLUTION_FALSE_POSITIVE),
  308. newDoc("I3", project.uuid(), file).setResolution(RESOLUTION_FIXED));
  309. assertThatFacetHasOnly(IssueQuery.builder(), "resolutions", entry("FALSE-POSITIVE", 2L), entry("FIXED", 1L));
  310. }
  311. @Test
  312. public void facets_on_resolutions_return_5_entries_max() {
  313. ComponentDto project = newPrivateProjectDto();
  314. ComponentDto file = newFileDto(project);
  315. indexIssues(
  316. newDoc("I1", project.uuid(), file).setResolution(RESOLUTION_FIXED),
  317. newDoc("I2", project.uuid(), file).setResolution(RESOLUTION_FALSE_POSITIVE),
  318. newDoc("I3", project.uuid(), file).setResolution(RESOLUTION_REMOVED),
  319. newDoc("I4", project.uuid(), file).setResolution(RESOLUTION_WONT_FIX),
  320. newDoc("I5", project.uuid(), file).setResolution(null));
  321. assertThatFacetHasSize(IssueQuery.builder().build(), "resolutions", 5);
  322. }
  323. @Test
  324. public void facets_on_languages() {
  325. ComponentDto project = newPrivateProjectDto();
  326. ComponentDto file = newFileDto(project);
  327. RuleDto ruleDefinitionDto = newRule();
  328. db.rules().insert(ruleDefinitionDto);
  329. indexIssues(newDoc("I1", project.uuid(), file).setRuleUuid(ruleDefinitionDto.getUuid()).setLanguage("xoo"));
  330. assertThatFacetHasOnly(IssueQuery.builder(), "languages", entry("xoo", 1L));
  331. }
  332. @Test
  333. public void facets_on_languages_return_100_entries_plus_selected_values() {
  334. ComponentDto project = newPrivateProjectDto();
  335. indexIssues(rangeClosed(1, 100).mapToObj(i -> newDoc(newFileDto(project), project.uuid()).setLanguage("a" + i)).toArray(IssueDoc[]::new));
  336. IssueDoc issue1 = newDoc(newFileDto(project), project.uuid()).setLanguage("language1");
  337. IssueDoc issue2 = newDoc(newFileDto(project), project.uuid()).setLanguage("language2");
  338. indexIssues(issue1, issue2);
  339. assertThatFacetHasSize(IssueQuery.builder().build(), "languages", 100);
  340. assertThatFacetHasSize(IssueQuery.builder().languages(asList(issue1.language(), issue2.language())).build(), "languages", 102);
  341. }
  342. @Test
  343. public void facets_on_assignees() {
  344. ComponentDto project = newPrivateProjectDto();
  345. ComponentDto file = newFileDto(project);
  346. indexIssues(
  347. newDoc("I1", project.uuid(), file).setAssigneeUuid("steph-uuid"),
  348. newDoc("I2", project.uuid(), file).setAssigneeUuid("marcel-uuid"),
  349. newDoc("I3", project.uuid(), file).setAssigneeUuid("marcel-uuid"),
  350. newDoc("I4", project.uuid(), file).setAssigneeUuid(null));
  351. assertThatFacetHasOnly(IssueQuery.builder(), "assignees", entry("steph-uuid", 1L), entry("marcel-uuid", 2L), entry("", 1L));
  352. }
  353. @Test
  354. public void facets_on_assignees_return_only_100_entries_plus_selected_values() {
  355. ComponentDto project = newPrivateProjectDto();
  356. indexIssues(rangeClosed(1, 110).mapToObj(i -> newDoc(newFileDto(project), project.uuid()).setAssigneeUuid("a" + i)).toArray(IssueDoc[]::new));
  357. IssueDoc issue1 = newDoc(newFileDto(project), project.uuid()).setAssigneeUuid("user1");
  358. IssueDoc issue2 = newDoc(newFileDto(project), project.uuid()).setAssigneeUuid("user2");
  359. indexIssues(issue1, issue2);
  360. assertThatFacetHasSize(IssueQuery.builder().build(), "assignees", 100);
  361. assertThatFacetHasSize(IssueQuery.builder().assigneeUuids(asList(issue1.assigneeUuid(), issue2.assigneeUuid())).build(), "assignees", 102);
  362. }
  363. @Test
  364. public void facets_on_assignees_supports_dashes() {
  365. ComponentDto project = newPrivateProjectDto();
  366. ComponentDto file = newFileDto(project);
  367. indexIssues(
  368. newDoc("I1", project.uuid(), file).setAssigneeUuid("j-b-uuid"),
  369. newDoc("I2", project.uuid(), file).setAssigneeUuid("marcel-uuid"),
  370. newDoc("I3", project.uuid(), file).setAssigneeUuid("marcel-uuid"),
  371. newDoc("I4", project.uuid(), file).setAssigneeUuid(null));
  372. assertThatFacetHasOnly(IssueQuery.builder().assigneeUuids(singletonList("j-b")),
  373. "assignees", entry("j-b-uuid", 1L), entry("marcel-uuid", 2L), entry("", 1L));
  374. }
  375. @Test
  376. public void facets_on_author() {
  377. ComponentDto project = newPrivateProjectDto();
  378. ComponentDto file = newFileDto(project);
  379. indexIssues(
  380. newDoc("I1", project.uuid(), file).setAuthorLogin("steph"),
  381. newDoc("I2", project.uuid(), file).setAuthorLogin("marcel"),
  382. newDoc("I3", project.uuid(), file).setAuthorLogin("marcel"),
  383. newDoc("I4", project.uuid(), file).setAuthorLogin(null));
  384. assertThatFacetHasOnly(IssueQuery.builder(), "author", entry("steph", 1L), entry("marcel", 2L));
  385. }
  386. @Test
  387. public void facets_on_authors_return_100_entries_plus_selected_values() {
  388. ComponentDto project = newPrivateProjectDto();
  389. indexIssues(rangeClosed(1, 110).mapToObj(i -> newDoc(newFileDto(project), project.uuid()).setAuthorLogin("a" + i)).toArray(IssueDoc[]::new));
  390. IssueDoc issue1 = newDoc(newFileDto(project), project.uuid()).setAuthorLogin("user1");
  391. IssueDoc issue2 = newDoc(newFileDto(project), project.uuid()).setAuthorLogin("user2");
  392. indexIssues(issue1, issue2);
  393. assertThatFacetHasSize(IssueQuery.builder().build(), "author", 100);
  394. assertThatFacetHasSize(IssueQuery.builder().authors(asList(issue1.authorLogin(), issue2.authorLogin())).build(), "author", 102);
  395. }
  396. @Test
  397. public void facet_on_created_at_with_less_than_20_days_use_system_timezone_by_default() {
  398. SearchOptions options = fixtureForCreatedAtFacet();
  399. IssueQuery query = IssueQuery.builder()
  400. .createdAfter(parseDateTime("2014-09-01T00:00:00+0100"))
  401. .createdBefore(parseDateTime("2014-09-08T00:00:00+0100"))
  402. .build();
  403. SearchResponse result = underTest.search(query, options);
  404. Map<String, Long> buckets = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt");
  405. assertThat(buckets).containsOnly(
  406. entry("2014-08-31", 0L),
  407. entry("2014-09-01", 2L),
  408. entry("2014-09-02", 1L),
  409. entry("2014-09-03", 0L),
  410. entry("2014-09-04", 0L),
  411. entry("2014-09-05", 1L),
  412. entry("2014-09-06", 0L),
  413. entry("2014-09-07", 0L));
  414. }
  415. @Test
  416. public void facet_on_created_at_with_less_than_20_days_use_user_timezone_if_provided() {
  417. // Use timezones very far from each other in order to see some issues moving to a different calendar day
  418. final ZoneId plus14 = ZoneId.of("Pacific/Kiritimati");
  419. final ZoneId minus11 = ZoneId.of("Pacific/Pago_Pago");
  420. SearchOptions options = fixtureForCreatedAtFacet();
  421. final Date startDate = parseDateTime("2014-09-01T00:00:00+0000");
  422. final Date endDate = parseDateTime("2014-09-08T00:00:00+0000");
  423. IssueQuery queryPlus14 = IssueQuery.builder()
  424. .createdAfter(startDate)
  425. .createdBefore(endDate)
  426. .timeZone(plus14)
  427. .build();
  428. SearchResponse resultPlus14 = underTest.search(queryPlus14, options);
  429. Map<String, Long> bucketsPlus14 = new Facets(resultPlus14, plus14).get("createdAt");
  430. assertThat(bucketsPlus14).containsOnly(
  431. entry("2014-09-01", 0L),
  432. entry("2014-09-02", 2L),
  433. entry("2014-09-03", 1L),
  434. entry("2014-09-04", 0L),
  435. entry("2014-09-05", 0L),
  436. entry("2014-09-06", 1L),
  437. entry("2014-09-07", 0L),
  438. entry("2014-09-08", 0L));
  439. IssueQuery queryMinus11 = IssueQuery.builder()
  440. .createdAfter(startDate)
  441. .createdBefore(endDate)
  442. .timeZone(minus11)
  443. .build();
  444. SearchResponse resultMinus11 = underTest.search(queryMinus11, options);
  445. Map<String, Long> bucketsMinus11 = new Facets(resultMinus11, minus11).get("createdAt");
  446. assertThat(bucketsMinus11).containsOnly(
  447. entry("2014-08-31", 1L),
  448. entry("2014-09-01", 1L),
  449. entry("2014-09-02", 1L),
  450. entry("2014-09-03", 0L),
  451. entry("2014-09-04", 0L),
  452. entry("2014-09-05", 1L),
  453. entry("2014-09-06", 0L),
  454. entry("2014-09-07", 0L));
  455. }
  456. @Test
  457. public void facet_on_created_at_with_less_than_20_weeks() {
  458. SearchOptions options = fixtureForCreatedAtFacet();
  459. SearchResponse result = underTest.search(IssueQuery.builder()
  460. .createdAfter(parseDateTime("2014-09-01T00:00:00+0100"))
  461. .createdBefore(parseDateTime("2014-09-21T00:00:00+0100")).build(),
  462. options);
  463. Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt");
  464. assertThat(createdAt).containsOnly(
  465. entry("2014-08-25", 0L),
  466. entry("2014-09-01", 4L),
  467. entry("2014-09-08", 0L),
  468. entry("2014-09-15", 1L));
  469. }
  470. @Test
  471. public void facet_on_created_at_with_less_than_20_months() {
  472. SearchOptions options = fixtureForCreatedAtFacet();
  473. SearchResponse result = underTest.search(IssueQuery.builder()
  474. .createdAfter(parseDateTime("2014-09-01T00:00:00+0100"))
  475. .createdBefore(parseDateTime("2015-01-19T00:00:00+0100")).build(),
  476. options);
  477. Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt");
  478. assertThat(createdAt).containsOnly(
  479. entry("2014-08-01", 0L),
  480. entry("2014-09-01", 5L),
  481. entry("2014-10-01", 0L),
  482. entry("2014-11-01", 0L),
  483. entry("2014-12-01", 0L),
  484. entry("2015-01-01", 1L));
  485. }
  486. @Test
  487. public void facet_on_created_at_with_more_than_20_months() {
  488. SearchOptions options = fixtureForCreatedAtFacet();
  489. SearchResponse result = underTest.search(IssueQuery.builder()
  490. .createdAfter(parseDateTime("2011-01-01T00:00:00+0100"))
  491. .createdBefore(parseDateTime("2016-01-01T00:00:00+0100")).build(),
  492. options);
  493. Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt");
  494. assertThat(createdAt).containsOnly(
  495. entry("2010-01-01", 0L),
  496. entry("2011-01-01", 1L),
  497. entry("2012-01-01", 0L),
  498. entry("2013-01-01", 0L),
  499. entry("2014-01-01", 5L),
  500. entry("2015-01-01", 1L));
  501. }
  502. @Test
  503. public void facet_on_created_at_with_one_day() {
  504. SearchOptions options = fixtureForCreatedAtFacet();
  505. SearchResponse result = underTest.search(IssueQuery.builder()
  506. .createdAfter(parseDateTime("2014-09-01T00:00:00-0100"))
  507. .createdBefore(parseDateTime("2014-09-02T00:00:00-0100")).build(),
  508. options);
  509. Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt");
  510. assertThat(createdAt).containsOnly(
  511. entry("2014-09-01", 2L));
  512. }
  513. @Test
  514. public void facet_on_created_at_with_bounds_outside_of_data() {
  515. SearchOptions options = fixtureForCreatedAtFacet();
  516. SearchResponse result = underTest.search(IssueQuery.builder()
  517. .createdAfter(parseDateTime("2009-01-01T00:00:00+0100"))
  518. .createdBefore(parseDateTime("2016-01-01T00:00:00+0100"))
  519. .build(), options);
  520. Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt");
  521. assertThat(createdAt).containsOnly(
  522. entry("2008-01-01", 0L),
  523. entry("2009-01-01", 0L),
  524. entry("2010-01-01", 0L),
  525. entry("2011-01-01", 1L),
  526. entry("2012-01-01", 0L),
  527. entry("2013-01-01", 0L),
  528. entry("2014-01-01", 5L),
  529. entry("2015-01-01", 1L));
  530. }
  531. @Test
  532. public void facet_on_created_at_without_start_bound() {
  533. SearchOptions searchOptions = fixtureForCreatedAtFacet();
  534. SearchResponse result = underTest.search(IssueQuery.builder()
  535. .createdBefore(parseDateTime("2016-01-01T00:00:00+0100")).build(),
  536. searchOptions);
  537. Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt");
  538. assertThat(createdAt).containsOnly(
  539. entry("2011-01-01", 1L),
  540. entry("2012-01-01", 0L),
  541. entry("2013-01-01", 0L),
  542. entry("2014-01-01", 5L),
  543. entry("2015-01-01", 1L));
  544. }
  545. @Test
  546. public void facet_on_created_at_without_issues() {
  547. SearchOptions searchOptions = new SearchOptions().addFacets("createdAt");
  548. SearchResponse result = underTest.search(IssueQuery.builder().build(), searchOptions);
  549. Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt");
  550. assertThat(createdAt).isNull();
  551. }
  552. @Test
  553. public void search_shouldReturnCodeVariantsFacet() {
  554. ComponentDto project = newPrivateProjectDto();
  555. ComponentDto file = newFileDto(project);
  556. indexIssues(
  557. newDoc("I1", project.uuid(), file).setCodeVariants(asList("variant1", "variant2")),
  558. newDoc("I2", project.uuid(), file).setCodeVariants(singletonList("variant2")),
  559. newDoc("I3", project.uuid(), file).setCodeVariants(singletonList("variant3")),
  560. newDoc("I4", project.uuid(), file));
  561. assertThatFacetHasOnly(IssueQuery.builder(), "codeVariants",
  562. entry("variant1", 1L),
  563. entry("variant2", 2L),
  564. entry("variant3", 1L));
  565. }
  566. @Test
  567. public void search_shouldReturnSoftwareQualityFacet() {
  568. ComponentDto project = newPrivateProjectDto();
  569. ComponentDto file = newFileDto(project);
  570. indexIssues(
  571. newDoc("I1", project.uuid(), file).setImpacts(Map.of(
  572. MAINTAINABILITY, org.sonar.api.issue.impact.Severity.HIGH,
  573. RELIABILITY, org.sonar.api.issue.impact.Severity.MEDIUM)),
  574. newDoc("I2", project.uuid(), file).setImpacts(Map.of(
  575. MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW)),
  576. newDoc("I3", project.uuid(), file).setImpacts(Map.of(
  577. RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH)),
  578. newDoc("I4", project.uuid(), file).setImpacts(Map.of(
  579. MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW)));
  580. assertThatFacetHasOnly(IssueQuery.builder(), "softwareQualities",
  581. entry("MAINTAINABILITY", 3L),
  582. entry("RELIABILITY", 2L),
  583. entry("SECURITY", 0L));
  584. }
  585. @Test
  586. public void search_whenFilteredOnSeverity_shouldReturnSoftwareQualityFacet() {
  587. ComponentDto project = newPrivateProjectDto();
  588. ComponentDto file = newFileDto(project);
  589. indexIssues(
  590. newDoc("I1", project.uuid(), file).setImpacts(Map.of(
  591. MAINTAINABILITY, org.sonar.api.issue.impact.Severity.HIGH,
  592. RELIABILITY, org.sonar.api.issue.impact.Severity.MEDIUM))
  593. .setTags(singletonList("my-tag")),
  594. newDoc("I2", project.uuid(), file).setImpacts(Map.of(
  595. MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW)),
  596. newDoc("I3", project.uuid(), file).setImpacts(Map.of(
  597. RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH)),
  598. newDoc("I4", project.uuid(), file).setImpacts(Map.of(
  599. MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW)));
  600. assertThatFacetHasOnly(IssueQuery.builder().impactSoftwareQualities(Set.of(MAINTAINABILITY.name())).impactSeverities(Set.of(org.sonar.api.issue.impact.Severity.LOW.name())),
  601. "softwareQualities",
  602. entry("MAINTAINABILITY", 2L),
  603. entry("RELIABILITY", 0L),
  604. entry("SECURITY", 0L));
  605. assertThatFacetHasOnly(IssueQuery.builder().impactSeverities(Set.of(Severity.MEDIUM.name())), "softwareQualities",
  606. entry("MAINTAINABILITY", 0L),
  607. entry("RELIABILITY", 1L),
  608. entry("SECURITY", 0L));
  609. assertThatFacetHasOnly(IssueQuery.builder().impactSeverities(Set.of(org.sonar.api.issue.impact.Severity.HIGH.name())), "softwareQualities",
  610. entry("MAINTAINABILITY", 1L),
  611. entry("RELIABILITY", 1L),
  612. entry("SECURITY", 0L));
  613. assertThatFacetHasOnly(IssueQuery.builder()
  614. .tags(singletonList("my-tag"))
  615. .impactSeverities(Set.of(org.sonar.api.issue.impact.Severity.HIGH.name())), "softwareQualities",
  616. entry("MAINTAINABILITY", 1L),
  617. entry("RELIABILITY", 0L),
  618. entry("SECURITY", 0L));
  619. }
  620. @Test
  621. public void search_shouldReturnSoftwareQualitySeverityFacet() {
  622. ComponentDto project = newPrivateProjectDto();
  623. ComponentDto file = newFileDto(project);
  624. indexIssues(
  625. newDoc("I1", project.uuid(), file).setImpacts(Map.of(
  626. MAINTAINABILITY, org.sonar.api.issue.impact.Severity.HIGH,
  627. RELIABILITY, org.sonar.api.issue.impact.Severity.MEDIUM)),
  628. newDoc("I2", project.uuid(), file).setImpacts(Map.of(
  629. MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW)),
  630. newDoc("I3", project.uuid(), file).setImpacts(Map.of(
  631. RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH)),
  632. newDoc("I4", project.uuid(), file).setImpacts(Map.of(
  633. MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW)));
  634. assertThatFacetHasOnly(IssueQuery.builder(), "softwareQualitiesSeverities",
  635. entry("HIGH", 2L),
  636. entry("MEDIUM", 1L),
  637. entry("LOW", 2L));
  638. }
  639. @Test
  640. public void search_whenFilteredOnSoftwareQuality_shouldReturnSoftwareQualitySeverityFacet() {
  641. ComponentDto project = newPrivateProjectDto();
  642. ComponentDto file = newFileDto(project);
  643. indexIssues(
  644. newDoc("I1", project.uuid(), file).setImpacts(Map.of(
  645. MAINTAINABILITY, org.sonar.api.issue.impact.Severity.HIGH,
  646. RELIABILITY, org.sonar.api.issue.impact.Severity.MEDIUM)),
  647. newDoc("I2", project.uuid(), file).setImpacts(Map.of(
  648. MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW)),
  649. newDoc("I3", project.uuid(), file).setImpacts(Map.of(
  650. RELIABILITY, org.sonar.api.issue.impact.Severity.HIGH)),
  651. newDoc("I4", project.uuid(), file).setImpacts(Map.of(
  652. MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW)));
  653. assertThatFacetHasOnly(IssueQuery.builder().impactSoftwareQualities(Set.of(MAINTAINABILITY.name())), "softwareQualitiesSeverities",
  654. entry("HIGH", 1L),
  655. entry("MEDIUM", 0L),
  656. entry("LOW", 2L));
  657. }
  658. @Test
  659. public void search_shouldReturnCleanCodeAttributeCategoryFacet() {
  660. ComponentDto project = newPrivateProjectDto();
  661. ComponentDto file = newFileDto(project);
  662. indexIssues(
  663. newDoc("I1", project.uuid(), file).setCleanCodeAttributeCategory(ADAPTABLE.name()),
  664. newDoc("I2", project.uuid(), file).setCleanCodeAttributeCategory(ADAPTABLE.name()),
  665. newDoc("I3", project.uuid(), file).setCleanCodeAttributeCategory(CONSISTENT.name()),
  666. newDoc("I4", project.uuid(), file).setCleanCodeAttributeCategory(INTENTIONAL.name()),
  667. newDoc("I5", project.uuid(), file).setCleanCodeAttributeCategory(INTENTIONAL.name()),
  668. newDoc("I6", project.uuid(), file).setCleanCodeAttributeCategory(INTENTIONAL.name()),
  669. newDoc("I7", project.uuid(), file).setCleanCodeAttributeCategory(INTENTIONAL.name()),
  670. newDoc("I8", project.uuid(), file).setCleanCodeAttributeCategory(RESPONSIBLE.name()));
  671. assertThatFacetHasOnly(IssueQuery.builder(), "cleanCodeAttributeCategories",
  672. entry("INTENTIONAL", 4L),
  673. entry("ADAPTABLE", 2L),
  674. entry("CONSISTENT", 1L),
  675. entry("RESPONSIBLE", 1L));
  676. }
  677. @Test
  678. public void search_whenFilteredByTags_shouldReturnCleanCodeAttributeCategoryFacet() {
  679. ComponentDto project = newPrivateProjectDto();
  680. ComponentDto file = newFileDto(project);
  681. indexIssues(
  682. newDoc("I1", project.uuid(), file).setCleanCodeAttributeCategory(ADAPTABLE.name()).setTags(singletonList("tag-1")),
  683. newDoc("I2", project.uuid(), file).setCleanCodeAttributeCategory(ADAPTABLE.name()).setTags(singletonList("tag-1")),
  684. newDoc("I3", project.uuid(), file).setCleanCodeAttributeCategory(CONSISTENT.name()),
  685. newDoc("I4", project.uuid(), file).setCleanCodeAttributeCategory(INTENTIONAL.name()).setTags(singletonList("tag-1")),
  686. newDoc("I5", project.uuid(), file).setCleanCodeAttributeCategory(INTENTIONAL.name()),
  687. newDoc("I6", project.uuid(), file).setCleanCodeAttributeCategory(INTENTIONAL.name()),
  688. newDoc("I7", project.uuid(), file).setCleanCodeAttributeCategory(INTENTIONAL.name()),
  689. newDoc("I8", project.uuid(), file).setCleanCodeAttributeCategory(RESPONSIBLE.name()).setTags(singletonList("tag-3")));
  690. assertThatFacetHasOnly(IssueQuery.builder().tags(singletonList("tag-1")), "cleanCodeAttributeCategories",
  691. entry("INTENTIONAL", 1L),
  692. entry("ADAPTABLE", 2L));
  693. }
  694. private SearchOptions fixtureForCreatedAtFacet() {
  695. ComponentDto project = newPrivateProjectDto();
  696. ComponentDto file = newFileDto(project);
  697. IssueDoc issue0 = newDoc("ISSUE0", project.uuid(), file).setFuncCreationDate(parseDateTime("2011-04-25T00:05:13+0000"));
  698. IssueDoc issue1 = newDoc("I1", project.uuid(), file).setFuncCreationDate(parseDateTime("2014-09-01T10:34:56+0000"));
  699. IssueDoc issue2 = newDoc("I2", project.uuid(), file).setFuncCreationDate(parseDateTime("2014-09-01T22:46:00+0000"));
  700. IssueDoc issue3 = newDoc("I3", project.uuid(), file).setFuncCreationDate(parseDateTime("2014-09-02T11:34:56+0000"));
  701. IssueDoc issue4 = newDoc("I4", project.uuid(), file).setFuncCreationDate(parseDateTime("2014-09-05T11:34:56+0000"));
  702. IssueDoc issue5 = newDoc("I5", project.uuid(), file).setFuncCreationDate(parseDateTime("2014-09-20T11:34:56+0000"));
  703. IssueDoc issue6 = newDoc("I6", project.uuid(), file).setFuncCreationDate(parseDateTime("2015-01-18T11:34:56+0000"));
  704. indexIssues(issue0, issue1, issue2, issue3, issue4, issue5, issue6);
  705. return new SearchOptions().addFacets("createdAt");
  706. }
  707. @SafeVarargs
  708. private final void assertThatFacetHasExactly(IssueQuery.Builder query, String facet, Map.Entry<String, Long>... expectedEntries) {
  709. SearchResponse result = underTest.search(query.build(), new SearchOptions().addFacets(singletonList(facet)));
  710. Facets facets = new Facets(result, system2.getDefaultTimeZone().toZoneId());
  711. assertThat(facets.getNames()).containsOnly(facet, "effort");
  712. assertThat(facets.get(facet)).containsExactly(expectedEntries);
  713. }
  714. @SafeVarargs
  715. private final void assertThatFacetHasOnly(IssueQuery.Builder query, String facet, Map.Entry<String, Long>... expectedEntries) {
  716. SearchResponse result = underTest.search(query.build(), new SearchOptions().addFacets(singletonList(facet)));
  717. Facets facets = new Facets(result, system2.getDefaultTimeZone().toZoneId());
  718. assertThat(facets.getNames()).containsOnly(facet, "effort");
  719. assertThat(facets.get(facet)).containsOnly(expectedEntries);
  720. }
  721. private void assertThatFacetHasSize(IssueQuery issueQuery, String facet, int expectedSize) {
  722. SearchResponse result = underTest.search(issueQuery, new SearchOptions().addFacets(singletonList(facet)));
  723. Facets facets = new Facets(result, system2.getDefaultTimeZone().toZoneId());
  724. assertThat(facets.get(facet)).hasSize(expectedSize);
  725. }
  726. }