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 26KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2021 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.Date;
  23. import java.util.Map;
  24. import java.util.TimeZone;
  25. import org.elasticsearch.action.search.SearchResponse;
  26. import org.junit.Rule;
  27. import org.junit.Test;
  28. import org.junit.rules.ExpectedException;
  29. import org.sonar.api.impl.utils.TestSystem2;
  30. import org.sonar.api.rules.RuleType;
  31. import org.sonar.api.utils.System2;
  32. import org.sonar.db.DbTester;
  33. import org.sonar.db.component.ComponentDto;
  34. import org.sonar.db.rule.RuleDefinitionDto;
  35. import org.sonar.server.es.EsTester;
  36. import org.sonar.server.es.Facets;
  37. import org.sonar.server.es.SearchOptions;
  38. import org.sonar.server.permission.index.IndexPermissions;
  39. import org.sonar.server.permission.index.PermissionIndexerTester;
  40. import org.sonar.server.permission.index.WebAuthorizationTypeSupport;
  41. import org.sonar.server.security.SecurityStandards.SQCategory;
  42. import org.sonar.server.tester.UserSessionRule;
  43. import static java.util.Arrays.asList;
  44. import static java.util.Arrays.stream;
  45. import static java.util.Collections.singletonList;
  46. import static java.util.TimeZone.getTimeZone;
  47. import static java.util.stream.Collectors.toList;
  48. import static java.util.stream.IntStream.rangeClosed;
  49. import static org.assertj.core.api.Assertions.assertThat;
  50. import static org.assertj.core.api.Assertions.entry;
  51. import static org.junit.rules.ExpectedException.none;
  52. import static org.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE;
  53. import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
  54. import static org.sonar.api.issue.Issue.RESOLUTION_REMOVED;
  55. import static org.sonar.api.issue.Issue.RESOLUTION_WONT_FIX;
  56. import static org.sonar.api.issue.Issue.STATUS_CLOSED;
  57. import static org.sonar.api.issue.Issue.STATUS_CONFIRMED;
  58. import static org.sonar.api.issue.Issue.STATUS_OPEN;
  59. import static org.sonar.api.issue.Issue.STATUS_REOPENED;
  60. import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
  61. import static org.sonar.api.resources.Qualifiers.PROJECT;
  62. import static org.sonar.api.rule.Severity.BLOCKER;
  63. import static org.sonar.api.rule.Severity.CRITICAL;
  64. import static org.sonar.api.rule.Severity.INFO;
  65. import static org.sonar.api.rule.Severity.MAJOR;
  66. import static org.sonar.api.rule.Severity.MINOR;
  67. import static org.sonar.api.utils.DateUtils.parseDateTime;
  68. import static org.sonar.db.component.ComponentTesting.newDirectory;
  69. import static org.sonar.db.component.ComponentTesting.newFileDto;
  70. import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto;
  71. import static org.sonar.db.rule.RuleTesting.newRule;
  72. import static org.sonar.server.issue.IssueDocTesting.newDoc;
  73. public class IssueIndexFacetsTest {
  74. @Rule
  75. public EsTester es = EsTester.create();
  76. @Rule
  77. public UserSessionRule userSessionRule = UserSessionRule.standalone();
  78. @Rule
  79. public ExpectedException expectedException = none();
  80. private final TimeZone defaultTimezone = getTimeZone("GMT-01:00");
  81. private System2 system2 = new TestSystem2().setNow(1_500_000_000_000L).setDefaultTimeZone(defaultTimezone);
  82. @Rule
  83. public DbTester db = DbTester.create(system2);
  84. private IssueIndexer issueIndexer = new IssueIndexer(es.client(), db.getDbClient(), new IssueIteratorFactory(db.getDbClient()), null);
  85. private PermissionIndexerTester authorizationIndexer = new PermissionIndexerTester(es, issueIndexer);
  86. private IssueIndex underTest = new IssueIndex(es.client(), system2, userSessionRule, new WebAuthorizationTypeSupport(userSessionRule));
  87. @Test
  88. public void facet_on_projectUuids() {
  89. ComponentDto project = newPrivateProjectDto("ABCD");
  90. ComponentDto project2 = newPrivateProjectDto("EFGH");
  91. indexIssues(
  92. newDoc("I1", newFileDto(project, null)),
  93. newDoc("I2", newFileDto(project, null)),
  94. newDoc("I3", newFileDto(project2, null)));
  95. assertThatFacetHasExactly(IssueQuery.builder(), "projects", entry("ABCD", 2L), entry("EFGH", 1L));
  96. }
  97. @Test
  98. public void facet_on_projectUuids_return_100_entries_plus_selected_values() {
  99. indexIssues(rangeClosed(1, 110).mapToObj(i -> newDoc(newPrivateProjectDto("a" + i))).toArray(IssueDoc[]::new));
  100. IssueDoc issue1 = newDoc(newPrivateProjectDto("project1"));
  101. IssueDoc issue2 = newDoc(newPrivateProjectDto("project2"));
  102. indexIssues(issue1, issue2);
  103. assertThatFacetHasSize(IssueQuery.builder().build(), "projects", 100);
  104. assertThatFacetHasSize(IssueQuery.builder().projectUuids(asList(issue1.projectUuid(), issue2.projectUuid())).build(), "projects", 102);
  105. }
  106. @Test
  107. public void facets_on_files() {
  108. ComponentDto project = newPrivateProjectDto("A");
  109. ComponentDto dir = newDirectory(project, "src");
  110. ComponentDto file1 = newFileDto(project, dir, "ABCD");
  111. ComponentDto file2 = newFileDto(project, dir, "BCDE");
  112. ComponentDto file3 = newFileDto(project, dir, "CDEF");
  113. indexIssues(
  114. newDoc("I1", project),
  115. newDoc("I2", file1),
  116. newDoc("I3", file2),
  117. newDoc("I4", file2),
  118. newDoc("I5", file3));
  119. assertThatFacetHasOnly(IssueQuery.builder(), "files", entry("src/NAME_ABCD", 1L), entry("src/NAME_BCDE", 2L), entry("src/NAME_CDEF", 1L));
  120. }
  121. @Test
  122. public void facet_on_files_return_100_entries_plus_selected_values() {
  123. ComponentDto project = newPrivateProjectDto();
  124. indexIssues(rangeClosed(1, 110).mapToObj(i -> newDoc(newFileDto(project, null, "a" + i))).toArray(IssueDoc[]::new));
  125. IssueDoc issue1 = newDoc(newFileDto(project, null, "file1"));
  126. IssueDoc issue2 = newDoc(newFileDto(project, null, "file2"));
  127. indexIssues(issue1, issue2);
  128. assertThatFacetHasSize(IssueQuery.builder().build(), "files", 100);
  129. assertThatFacetHasSize(IssueQuery.builder().files(asList(issue1.filePath(), issue2.filePath())).build(), "files", 102);
  130. }
  131. @Test
  132. public void facets_on_directories() {
  133. ComponentDto project = newPrivateProjectDto();
  134. ComponentDto file1 = newFileDto(project, null).setPath("src/main/xoo/F1.xoo");
  135. ComponentDto file2 = newFileDto(project, null).setPath("F2.xoo");
  136. indexIssues(
  137. newDoc("I1", file1).setDirectoryPath("/src/main/xoo"),
  138. newDoc("I2", file2).setDirectoryPath("/"));
  139. assertThatFacetHasOnly(IssueQuery.builder(), "directories", entry("/src/main/xoo", 1L), entry("/", 1L));
  140. }
  141. @Test
  142. public void facet_on_directories_return_100_entries_plus_selected_values() {
  143. ComponentDto project = newPrivateProjectDto();
  144. indexIssues(rangeClosed(1, 110).mapToObj(i -> newDoc(newFileDto(project, newDirectory(project, "dir" + i))).setDirectoryPath("a" + i)).toArray(IssueDoc[]::new));
  145. IssueDoc issue1 = newDoc(newFileDto(project, newDirectory(project, "path1"))).setDirectoryPath("directory1");
  146. IssueDoc issue2 = newDoc(newFileDto(project, newDirectory(project, "path2"))).setDirectoryPath("directory2");
  147. indexIssues(issue1, issue2);
  148. assertThatFacetHasSize(IssueQuery.builder().build(), "directories", 100);
  149. assertThatFacetHasSize(IssueQuery.builder().directories(asList(issue1.directoryPath(), issue2.directoryPath())).build(), "directories", 102);
  150. }
  151. @Test
  152. public void facets_on_cwe() {
  153. ComponentDto project = newPrivateProjectDto();
  154. ComponentDto file = newFileDto(project, null);
  155. indexIssues(
  156. newDoc("I1", file).setType(RuleType.VULNERABILITY).setCwe(asList("20", "564", "89", "943")),
  157. newDoc("I2", file).setType(RuleType.VULNERABILITY).setCwe(asList("943")),
  158. newDoc("I3", file));
  159. assertThatFacetHasOnly(IssueQuery.builder(), "cwe",
  160. entry("943", 2L),
  161. entry("20", 1L),
  162. entry("564", 1L),
  163. entry("89", 1L));
  164. }
  165. @Test
  166. public void facets_on_owaspTop10() {
  167. ComponentDto project = newPrivateProjectDto();
  168. ComponentDto file = newFileDto(project, null);
  169. indexIssues(
  170. newDoc("I1", file).setType(RuleType.VULNERABILITY).setOwaspTop10(asList("a1", "a2")),
  171. newDoc("I2", file).setType(RuleType.VULNERABILITY).setOwaspTop10(singletonList("a3")),
  172. newDoc("I3", file));
  173. assertThatFacetHasOnly(IssueQuery.builder(), "owaspTop10",
  174. entry("a1", 1L),
  175. entry("a2", 1L),
  176. entry("a3", 1L));
  177. }
  178. @Test
  179. public void facets_on_sansTop25() {
  180. ComponentDto project = newPrivateProjectDto();
  181. ComponentDto file = newFileDto(project, null);
  182. indexIssues(
  183. newDoc("I1", file).setType(RuleType.VULNERABILITY).setSansTop25(asList("porous-defenses", "risky-resource", "insecure-interaction")),
  184. newDoc("I2", file).setType(RuleType.VULNERABILITY).setSansTop25(singletonList("porous-defenses")),
  185. newDoc("I3", file));
  186. assertThatFacetHasOnly(IssueQuery.builder(), "sansTop25",
  187. entry("insecure-interaction", 1L),
  188. entry("porous-defenses", 2L),
  189. entry("risky-resource", 1L));
  190. }
  191. @Test
  192. public void facets_on_sonarSourceSecurity() {
  193. ComponentDto project = newPrivateProjectDto();
  194. ComponentDto file = newFileDto(project, null);
  195. indexIssues(
  196. newDoc("I1", file).setType(RuleType.VULNERABILITY).setSonarSourceSecurityCategory(SQCategory.BUFFER_OVERFLOW),
  197. newDoc("I2", file).setType(RuleType.VULNERABILITY).setSonarSourceSecurityCategory(SQCategory.DOS),
  198. newDoc("I3", file));
  199. assertThatFacetHasOnly(IssueQuery.builder(), "sonarsourceSecurity",
  200. entry("buffer-overflow", 1L),
  201. entry("dos", 1L));
  202. }
  203. @Test
  204. public void facets_on_severities() {
  205. ComponentDto project = newPrivateProjectDto();
  206. ComponentDto file = newFileDto(project, null);
  207. indexIssues(
  208. newDoc("I1", file).setSeverity(INFO),
  209. newDoc("I2", file).setSeverity(INFO),
  210. newDoc("I3", file).setSeverity(MAJOR));
  211. assertThatFacetHasOnly(IssueQuery.builder(), "severities", entry("INFO", 2L), entry("MAJOR", 1L));
  212. }
  213. @Test
  214. public void facet_on_severities_return_5_entries_max() {
  215. ComponentDto project = newPrivateProjectDto();
  216. ComponentDto file = newFileDto(project, null);
  217. indexIssues(
  218. newDoc("I2", file).setSeverity(INFO),
  219. newDoc("I1", file).setSeverity(MINOR),
  220. newDoc("I3", file).setSeverity(MAJOR),
  221. newDoc("I4", file).setSeverity(CRITICAL),
  222. newDoc("I5", file).setSeverity(BLOCKER),
  223. newDoc("I6", file).setSeverity(MAJOR));
  224. assertThatFacetHasSize(IssueQuery.builder().build(), "severities", 5);
  225. }
  226. @Test
  227. public void facets_on_statuses() {
  228. ComponentDto project = newPrivateProjectDto();
  229. ComponentDto file = newFileDto(project, null);
  230. indexIssues(
  231. newDoc("I1", file).setStatus(STATUS_CLOSED),
  232. newDoc("I2", file).setStatus(STATUS_CLOSED),
  233. newDoc("I3", file).setStatus(STATUS_OPEN));
  234. assertThatFacetHasOnly(IssueQuery.builder(), "statuses", entry("CLOSED", 2L), entry("OPEN", 1L));
  235. }
  236. @Test
  237. public void facet_on_statuses_return_5_entries_max() {
  238. ComponentDto project = newPrivateProjectDto();
  239. ComponentDto file = newFileDto(project, null);
  240. indexIssues(
  241. newDoc("I1", file).setStatus(STATUS_OPEN),
  242. newDoc("I2", file).setStatus(STATUS_CONFIRMED),
  243. newDoc("I3", file).setStatus(STATUS_REOPENED),
  244. newDoc("I4", file).setStatus(STATUS_RESOLVED),
  245. newDoc("I5", file).setStatus(STATUS_CLOSED),
  246. newDoc("I6", file).setStatus(STATUS_OPEN));
  247. assertThatFacetHasSize(IssueQuery.builder().build(), "statuses", 5);
  248. }
  249. @Test
  250. public void facets_on_resolutions() {
  251. ComponentDto project = newPrivateProjectDto();
  252. ComponentDto file = newFileDto(project, null);
  253. indexIssues(
  254. newDoc("I1", file).setResolution(RESOLUTION_FALSE_POSITIVE),
  255. newDoc("I2", file).setResolution(RESOLUTION_FALSE_POSITIVE),
  256. newDoc("I3", file).setResolution(RESOLUTION_FIXED));
  257. assertThatFacetHasOnly(IssueQuery.builder(), "resolutions", entry("FALSE-POSITIVE", 2L), entry("FIXED", 1L));
  258. }
  259. @Test
  260. public void facets_on_resolutions_return_5_entries_max() {
  261. ComponentDto project = newPrivateProjectDto();
  262. ComponentDto file = newFileDto(project, null);
  263. indexIssues(
  264. newDoc("I1", file).setResolution(RESOLUTION_FIXED),
  265. newDoc("I2", file).setResolution(RESOLUTION_FALSE_POSITIVE),
  266. newDoc("I3", file).setResolution(RESOLUTION_REMOVED),
  267. newDoc("I4", file).setResolution(RESOLUTION_WONT_FIX),
  268. newDoc("I5", file).setResolution(null));
  269. assertThatFacetHasSize(IssueQuery.builder().build(), "resolutions", 5);
  270. }
  271. @Test
  272. public void facets_on_languages() {
  273. ComponentDto project = newPrivateProjectDto();
  274. ComponentDto file = newFileDto(project, null);
  275. RuleDefinitionDto ruleDefinitionDto = newRule();
  276. db.rules().insert(ruleDefinitionDto);
  277. indexIssues(newDoc("I1", file).setRuleUuid(ruleDefinitionDto.getUuid()).setLanguage("xoo"));
  278. assertThatFacetHasOnly(IssueQuery.builder(), "languages", entry("xoo", 1L));
  279. }
  280. @Test
  281. public void facets_on_languages_return_100_entries_plus_selected_values() {
  282. ComponentDto project = newPrivateProjectDto();
  283. indexIssues(rangeClosed(1, 100).mapToObj(i -> newDoc(newFileDto(project, null)).setLanguage("a" + i)).toArray(IssueDoc[]::new));
  284. IssueDoc issue1 = newDoc(newFileDto(project, null)).setLanguage("language1");
  285. IssueDoc issue2 = newDoc(newFileDto(project, null)).setLanguage("language2");
  286. indexIssues(issue1, issue2);
  287. assertThatFacetHasSize(IssueQuery.builder().build(), "languages", 100);
  288. assertThatFacetHasSize(IssueQuery.builder().languages(asList(issue1.language(), issue2.language())).build(), "languages", 102);
  289. }
  290. @Test
  291. public void facets_on_assignees() {
  292. ComponentDto project = newPrivateProjectDto();
  293. ComponentDto file = newFileDto(project, null);
  294. indexIssues(
  295. newDoc("I1", file).setAssigneeUuid("steph-uuid"),
  296. newDoc("I2", file).setAssigneeUuid("marcel-uuid"),
  297. newDoc("I3", file).setAssigneeUuid("marcel-uuid"),
  298. newDoc("I4", file).setAssigneeUuid(null));
  299. assertThatFacetHasOnly(IssueQuery.builder(), "assignees", entry("steph-uuid", 1L), entry("marcel-uuid", 2L), entry("", 1L));
  300. }
  301. @Test
  302. public void facets_on_assignees_return_only_100_entries_plus_selected_values() {
  303. ComponentDto project = newPrivateProjectDto();
  304. indexIssues(rangeClosed(1, 110).mapToObj(i -> newDoc(newFileDto(project, null)).setAssigneeUuid("a" + i)).toArray(IssueDoc[]::new));
  305. IssueDoc issue1 = newDoc(newFileDto(project, null)).setAssigneeUuid("user1");
  306. IssueDoc issue2 = newDoc(newFileDto(project, null)).setAssigneeUuid("user2");
  307. indexIssues(issue1, issue2);
  308. assertThatFacetHasSize(IssueQuery.builder().build(), "assignees", 100);
  309. assertThatFacetHasSize(IssueQuery.builder().assigneeUuids(asList(issue1.assigneeUuid(), issue2.assigneeUuid())).build(), "assignees", 102);
  310. }
  311. @Test
  312. public void facets_on_assignees_supports_dashes() {
  313. ComponentDto project = newPrivateProjectDto();
  314. ComponentDto file = newFileDto(project, null);
  315. indexIssues(
  316. newDoc("I1", file).setAssigneeUuid("j-b-uuid"),
  317. newDoc("I2", file).setAssigneeUuid("marcel-uuid"),
  318. newDoc("I3", file).setAssigneeUuid("marcel-uuid"),
  319. newDoc("I4", file).setAssigneeUuid(null));
  320. assertThatFacetHasOnly(IssueQuery.builder().assigneeUuids(singletonList("j-b")),
  321. "assignees", entry("j-b-uuid", 1L), entry("marcel-uuid", 2L), entry("", 1L));
  322. }
  323. @Test
  324. public void facets_on_author() {
  325. ComponentDto project = newPrivateProjectDto();
  326. ComponentDto file = newFileDto(project, null);
  327. indexIssues(
  328. newDoc("I1", file).setAuthorLogin("steph"),
  329. newDoc("I2", file).setAuthorLogin("marcel"),
  330. newDoc("I3", file).setAuthorLogin("marcel"),
  331. newDoc("I4", file).setAuthorLogin(null));
  332. assertThatFacetHasOnly(IssueQuery.builder(), "author", entry("steph", 1L), entry("marcel", 2L));
  333. }
  334. @Test
  335. public void facets_on_authors_return_100_entries_plus_selected_values() {
  336. ComponentDto project = newPrivateProjectDto();
  337. indexIssues(rangeClosed(1, 110).mapToObj(i -> newDoc(newFileDto(project, null)).setAuthorLogin("a" + i)).toArray(IssueDoc[]::new));
  338. IssueDoc issue1 = newDoc(newFileDto(project, null)).setAuthorLogin("user1");
  339. IssueDoc issue2 = newDoc(newFileDto(project, null)).setAuthorLogin("user2");
  340. indexIssues(issue1, issue2);
  341. assertThatFacetHasSize(IssueQuery.builder().build(), "author", 100);
  342. assertThatFacetHasSize(IssueQuery.builder().authors(asList(issue1.authorLogin(), issue2.authorLogin())).build(), "author", 102);
  343. }
  344. @Test
  345. public void facet_on_created_at_with_less_than_20_days_use_system_timezone_by_default() {
  346. SearchOptions options = fixtureForCreatedAtFacet();
  347. IssueQuery query = IssueQuery.builder()
  348. .createdAfter(parseDateTime("2014-09-01T00:00:00+0100"))
  349. .createdBefore(parseDateTime("2014-09-08T00:00:00+0100"))
  350. .build();
  351. SearchResponse result = underTest.search(query, options);
  352. Map<String, Long> buckets = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt");
  353. assertThat(buckets).containsOnly(
  354. entry("2014-08-31", 0L),
  355. entry("2014-09-01", 2L),
  356. entry("2014-09-02", 1L),
  357. entry("2014-09-03", 0L),
  358. entry("2014-09-04", 0L),
  359. entry("2014-09-05", 1L),
  360. entry("2014-09-06", 0L),
  361. entry("2014-09-07", 0L));
  362. }
  363. @Test
  364. public void facet_on_created_at_with_less_than_20_days_use_user_timezone_if_provided() {
  365. // Use timezones very far from each other in order to see some issues moving to a different calendar day
  366. final ZoneId plus14 = ZoneId.of("Pacific/Kiritimati");
  367. final ZoneId minus11 = ZoneId.of("Pacific/Pago_Pago");
  368. SearchOptions options = fixtureForCreatedAtFacet();
  369. final Date startDate = parseDateTime("2014-09-01T00:00:00+0000");
  370. final Date endDate = parseDateTime("2014-09-08T00:00:00+0000");
  371. IssueQuery queryPlus14 = IssueQuery.builder()
  372. .createdAfter(startDate)
  373. .createdBefore(endDate)
  374. .timeZone(plus14)
  375. .build();
  376. SearchResponse resultPlus14 = underTest.search(queryPlus14, options);
  377. Map<String, Long> bucketsPlus14 = new Facets(resultPlus14, plus14).get("createdAt");
  378. assertThat(bucketsPlus14).containsOnly(
  379. entry("2014-09-01", 0L),
  380. entry("2014-09-02", 2L),
  381. entry("2014-09-03", 1L),
  382. entry("2014-09-04", 0L),
  383. entry("2014-09-05", 0L),
  384. entry("2014-09-06", 1L),
  385. entry("2014-09-07", 0L),
  386. entry("2014-09-08", 0L));
  387. IssueQuery queryMinus11 = IssueQuery.builder()
  388. .createdAfter(startDate)
  389. .createdBefore(endDate)
  390. .timeZone(minus11)
  391. .build();
  392. SearchResponse resultMinus11 = underTest.search(queryMinus11, options);
  393. Map<String, Long> bucketsMinus11 = new Facets(resultMinus11, minus11).get("createdAt");
  394. assertThat(bucketsMinus11).containsOnly(
  395. entry("2014-08-31", 1L),
  396. entry("2014-09-01", 1L),
  397. entry("2014-09-02", 1L),
  398. entry("2014-09-03", 0L),
  399. entry("2014-09-04", 0L),
  400. entry("2014-09-05", 1L),
  401. entry("2014-09-06", 0L),
  402. entry("2014-09-07", 0L));
  403. }
  404. @Test
  405. public void facet_on_created_at_with_less_than_20_weeks() {
  406. SearchOptions options = fixtureForCreatedAtFacet();
  407. SearchResponse result = underTest.search(IssueQuery.builder()
  408. .createdAfter(parseDateTime("2014-09-01T00:00:00+0100"))
  409. .createdBefore(parseDateTime("2014-09-21T00:00:00+0100")).build(),
  410. options);
  411. Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt");
  412. assertThat(createdAt).containsOnly(
  413. entry("2014-08-25", 0L),
  414. entry("2014-09-01", 4L),
  415. entry("2014-09-08", 0L),
  416. entry("2014-09-15", 1L));
  417. }
  418. @Test
  419. public void facet_on_created_at_with_less_than_20_months() {
  420. SearchOptions options = fixtureForCreatedAtFacet();
  421. SearchResponse result = underTest.search(IssueQuery.builder()
  422. .createdAfter(parseDateTime("2014-09-01T00:00:00+0100"))
  423. .createdBefore(parseDateTime("2015-01-19T00:00:00+0100")).build(),
  424. options);
  425. Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt");
  426. assertThat(createdAt).containsOnly(
  427. entry("2014-08-01", 0L),
  428. entry("2014-09-01", 5L),
  429. entry("2014-10-01", 0L),
  430. entry("2014-11-01", 0L),
  431. entry("2014-12-01", 0L),
  432. entry("2015-01-01", 1L));
  433. }
  434. @Test
  435. public void facet_on_created_at_with_more_than_20_months() {
  436. SearchOptions options = fixtureForCreatedAtFacet();
  437. SearchResponse result = underTest.search(IssueQuery.builder()
  438. .createdAfter(parseDateTime("2011-01-01T00:00:00+0100"))
  439. .createdBefore(parseDateTime("2016-01-01T00:00:00+0100")).build(),
  440. options);
  441. Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt");
  442. assertThat(createdAt).containsOnly(
  443. entry("2010-01-01", 0L),
  444. entry("2011-01-01", 1L),
  445. entry("2012-01-01", 0L),
  446. entry("2013-01-01", 0L),
  447. entry("2014-01-01", 5L),
  448. entry("2015-01-01", 1L));
  449. }
  450. @Test
  451. public void facet_on_created_at_with_one_day() {
  452. SearchOptions options = fixtureForCreatedAtFacet();
  453. SearchResponse result = underTest.search(IssueQuery.builder()
  454. .createdAfter(parseDateTime("2014-09-01T00:00:00-0100"))
  455. .createdBefore(parseDateTime("2014-09-02T00:00:00-0100")).build(),
  456. options);
  457. Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt");
  458. assertThat(createdAt).containsOnly(
  459. entry("2014-09-01", 2L));
  460. }
  461. @Test
  462. public void facet_on_created_at_with_bounds_outside_of_data() {
  463. SearchOptions options = fixtureForCreatedAtFacet();
  464. SearchResponse result = underTest.search(IssueQuery.builder()
  465. .createdAfter(parseDateTime("2009-01-01T00:00:00+0100"))
  466. .createdBefore(parseDateTime("2016-01-01T00:00:00+0100"))
  467. .build(), options);
  468. Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt");
  469. assertThat(createdAt).containsOnly(
  470. entry("2008-01-01", 0L),
  471. entry("2009-01-01", 0L),
  472. entry("2010-01-01", 0L),
  473. entry("2011-01-01", 1L),
  474. entry("2012-01-01", 0L),
  475. entry("2013-01-01", 0L),
  476. entry("2014-01-01", 5L),
  477. entry("2015-01-01", 1L));
  478. }
  479. @Test
  480. public void facet_on_created_at_without_start_bound() {
  481. SearchOptions searchOptions = fixtureForCreatedAtFacet();
  482. SearchResponse result = underTest.search(IssueQuery.builder()
  483. .createdBefore(parseDateTime("2016-01-01T00:00:00+0100")).build(),
  484. searchOptions);
  485. Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt");
  486. assertThat(createdAt).containsOnly(
  487. entry("2011-01-01", 1L),
  488. entry("2012-01-01", 0L),
  489. entry("2013-01-01", 0L),
  490. entry("2014-01-01", 5L),
  491. entry("2015-01-01", 1L));
  492. }
  493. @Test
  494. public void facet_on_created_at_without_issues() {
  495. SearchOptions searchOptions = new SearchOptions().addFacets("createdAt");
  496. SearchResponse result = underTest.search(IssueQuery.builder().build(), searchOptions);
  497. Map<String, Long> createdAt = new Facets(result, system2.getDefaultTimeZone().toZoneId()).get("createdAt");
  498. assertThat(createdAt).isNull();
  499. }
  500. private SearchOptions fixtureForCreatedAtFacet() {
  501. ComponentDto project = newPrivateProjectDto();
  502. ComponentDto file = newFileDto(project, null);
  503. IssueDoc issue0 = newDoc("ISSUE0", file).setFuncCreationDate(parseDateTime("2011-04-25T00:05:13+0000"));
  504. IssueDoc issue1 = newDoc("I1", file).setFuncCreationDate(parseDateTime("2014-09-01T10:34:56+0000"));
  505. IssueDoc issue2 = newDoc("I2", file).setFuncCreationDate(parseDateTime("2014-09-01T22:46:00+0000"));
  506. IssueDoc issue3 = newDoc("I3", file).setFuncCreationDate(parseDateTime("2014-09-02T11:34:56+0000"));
  507. IssueDoc issue4 = newDoc("I4", file).setFuncCreationDate(parseDateTime("2014-09-05T11:34:56+0000"));
  508. IssueDoc issue5 = newDoc("I5", file).setFuncCreationDate(parseDateTime("2014-09-20T11:34:56+0000"));
  509. IssueDoc issue6 = newDoc("I6", file).setFuncCreationDate(parseDateTime("2015-01-18T11:34:56+0000"));
  510. indexIssues(issue0, issue1, issue2, issue3, issue4, issue5, issue6);
  511. return new SearchOptions().addFacets("createdAt");
  512. }
  513. private void indexIssues(IssueDoc... issues) {
  514. issueIndexer.index(asList(issues).iterator());
  515. authorizationIndexer.allow(stream(issues).map(issue -> new IndexPermissions(issue.projectUuid(), PROJECT).allowAnyone()).collect(toList()));
  516. }
  517. @SafeVarargs
  518. private final void assertThatFacetHasExactly(IssueQuery.Builder query, String facet, Map.Entry<String, Long>... expectedEntries) {
  519. SearchResponse result = underTest.search(query.build(), new SearchOptions().addFacets(singletonList(facet)));
  520. Facets facets = new Facets(result, system2.getDefaultTimeZone().toZoneId());
  521. assertThat(facets.getNames()).containsOnly(facet, "effort");
  522. assertThat(facets.get(facet)).containsExactly(expectedEntries);
  523. }
  524. @SafeVarargs
  525. private final void assertThatFacetHasOnly(IssueQuery.Builder query, String facet, Map.Entry<String, Long>... expectedEntries) {
  526. SearchResponse result = underTest.search(query.build(), new SearchOptions().addFacets(singletonList(facet)));
  527. Facets facets = new Facets(result, system2.getDefaultTimeZone().toZoneId());
  528. assertThat(facets.getNames()).containsOnly(facet, "effort");
  529. assertThat(facets.get(facet)).containsOnly(expectedEntries);
  530. }
  531. private void assertThatFacetHasSize(IssueQuery issueQuery, String facet, int expectedSize) {
  532. SearchResponse result = underTest.search(issueQuery, new SearchOptions().addFacets(singletonList(facet)));
  533. Facets facets = new Facets(result, system2.getDefaultTimeZone().toZoneId());
  534. assertThat(facets.get(facet)).hasSize(expectedSize);
  535. }
  536. }