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.

ChangesOnMyIssuesEmailTemplateTest.java 42KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2020 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.notification;
  21. import com.google.common.collect.ImmutableSet;
  22. import com.tngtech.java.junit.dataprovider.DataProvider;
  23. import com.tngtech.java.junit.dataprovider.DataProviderRunner;
  24. import com.tngtech.java.junit.dataprovider.UseDataProvider;
  25. import java.util.Arrays;
  26. import java.util.Collections;
  27. import java.util.List;
  28. import java.util.Locale;
  29. import java.util.Random;
  30. import java.util.Set;
  31. import java.util.function.Function;
  32. import java.util.stream.IntStream;
  33. import java.util.stream.Stream;
  34. import org.elasticsearch.common.util.set.Sets;
  35. import org.junit.Test;
  36. import org.junit.rules.ExpectedException;
  37. import org.junit.runner.RunWith;
  38. import org.sonar.api.config.EmailSettings;
  39. import org.sonar.api.notifications.Notification;
  40. import org.sonar.api.rules.RuleType;
  41. import org.sonar.core.i18n.I18n;
  42. import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.AnalysisChange;
  43. import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.Change;
  44. import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.ChangedIssue;
  45. import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.Project;
  46. import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.Rule;
  47. import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.UserChange;
  48. import org.sonar.test.html.HtmlFragmentAssert;
  49. import org.sonar.test.html.HtmlListAssert;
  50. import org.sonar.test.html.HtmlParagraphAssert;
  51. import static java.util.stream.Collectors.joining;
  52. import static java.util.stream.Collectors.toList;
  53. import static java.util.stream.Collectors.toSet;
  54. import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
  55. import static org.assertj.core.api.Assertions.assertThat;
  56. import static org.mockito.Mockito.mock;
  57. import static org.mockito.Mockito.when;
  58. import static org.sonar.api.issue.Issue.STATUS_CLOSED;
  59. import static org.sonar.api.issue.Issue.STATUS_CONFIRMED;
  60. import static org.sonar.api.issue.Issue.STATUS_OPEN;
  61. import static org.sonar.api.issue.Issue.STATUS_REOPENED;
  62. import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
  63. import static org.sonar.api.issue.Issue.STATUS_REVIEWED;
  64. import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW;
  65. import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
  66. import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.newAnalysisChange;
  67. import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.newBranch;
  68. import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.newChangedIssue;
  69. import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.newProject;
  70. import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.newRandomNotAHotspotRule;
  71. import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.newRule;
  72. import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.newSecurityHotspotRule;
  73. import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.newUserChange;
  74. import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.randomRuleTypeHotspotExcluded;
  75. @RunWith(DataProviderRunner.class)
  76. public class ChangesOnMyIssuesEmailTemplateTest {
  77. private static final String[] ISSUE_STATUSES = {STATUS_OPEN, STATUS_RESOLVED, STATUS_CONFIRMED, STATUS_REOPENED, STATUS_CLOSED};
  78. private static final String[] SECURITY_HOTSPOTS_STATUSES = {STATUS_TO_REVIEW, STATUS_REVIEWED};
  79. @org.junit.Rule
  80. public ExpectedException expectedException = ExpectedException.none();
  81. private I18n i18n = mock(I18n.class);
  82. private EmailSettings emailSettings = mock(EmailSettings.class);
  83. private ChangesOnMyIssuesEmailTemplate underTest = new ChangesOnMyIssuesEmailTemplate(i18n, emailSettings);
  84. @Test
  85. public void format_returns_null_on_Notification() {
  86. EmailMessage emailMessage = underTest.format(mock(Notification.class));
  87. assertThat(emailMessage).isNull();
  88. }
  89. @Test
  90. public void formats_fails_with_ISE_if_change_from_Analysis_and_no_issue() {
  91. AnalysisChange analysisChange = newAnalysisChange();
  92. expectedException.expect(IllegalStateException.class);
  93. expectedException.expectMessage("changedIssues can't be empty");
  94. underTest.format(new ChangesOnMyIssuesNotification(analysisChange, Collections.emptySet()));
  95. }
  96. @Test
  97. public void format_sets_message_id_with_project_key_of_first_issue_in_set_when_change_from_Analysis() {
  98. Set<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(4))
  99. .mapToObj(i -> newChangedIssue(i + "", randomValidStatus(), newProject("prj_" + i), newRandomNotAHotspotRule("rule_" + i)))
  100. .collect(toSet());
  101. AnalysisChange analysisChange = newAnalysisChange();
  102. EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(analysisChange, changedIssues));
  103. assertThat(emailMessage.getMessageId()).isEqualTo("changes-on-my-issues/" + changedIssues.iterator().next().getProject().getKey());
  104. }
  105. @Test
  106. public void format_sets_subject_with_project_name_of_first_issue_in_set_when_change_from_Analysis() {
  107. Set<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(4))
  108. .mapToObj(i -> newChangedIssue(i + "", randomValidStatus(), newProject("prj_" + i), newRandomNotAHotspotRule("rule_" + i)))
  109. .collect(toSet());
  110. AnalysisChange analysisChange = IssuesChangesNotificationBuilderTesting.newAnalysisChange();
  111. EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(analysisChange, changedIssues));
  112. Project project = changedIssues.iterator().next().getProject();
  113. assertThat(emailMessage.getSubject()).isEqualTo("Analysis has changed some of your issues in " + project.getProjectName());
  114. }
  115. @Test
  116. public void format_sets_subject_with_project_name_and_branch_name_of_first_issue_in_set_when_change_from_Analysis() {
  117. Set<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(4))
  118. .mapToObj(i -> newChangedIssue(i + "", randomValidStatus(), newBranch("prj_" + i, "br_" + i), newRandomNotAHotspotRule("rule_" + i)))
  119. .collect(toSet());
  120. AnalysisChange analysisChange = newAnalysisChange();
  121. EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(analysisChange, changedIssues));
  122. Project project = changedIssues.iterator().next().getProject();
  123. assertThat(emailMessage.getSubject()).isEqualTo("Analysis has changed some of your issues in " + project.getProjectName() + ", " + project.getBranchName().get());
  124. }
  125. @Test
  126. public void format_set_html_message_with_header_dealing_with_plural_when_change_from_Analysis() {
  127. Set<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(4))
  128. .mapToObj(i -> newChangedIssue(i + "", randomValidStatus(), newProject("prj_" + i), newRandomNotAHotspotRule("rule_" + i)))
  129. .collect(toSet());
  130. AnalysisChange analysisChange = newAnalysisChange();
  131. EmailMessage singleIssueMessage = underTest.format(new ChangesOnMyIssuesNotification(analysisChange, changedIssues.stream().limit(1).collect(toSet())));
  132. EmailMessage multiIssueMessage = underTest.format(new ChangesOnMyIssuesNotification(analysisChange, changedIssues));
  133. HtmlFragmentAssert.assertThat(singleIssueMessage.getMessage())
  134. .hasParagraph("Hi,")
  135. .hasParagraph("An analysis has updated an issue assigned to you:");
  136. HtmlFragmentAssert.assertThat(multiIssueMessage.getMessage())
  137. .hasParagraph("Hi,")
  138. .hasParagraph("An analysis has updated issues assigned to you:");
  139. }
  140. @Test
  141. public void format_sets_static_message_id_when_change_from_User() {
  142. Set<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(4))
  143. .mapToObj(i -> newChangedIssue(i + "", randomValidStatus(), newProject("prj_" + i), newRandomNotAHotspotRule("rule_" + i)))
  144. .collect(toSet());
  145. UserChange userChange = newUserChange();
  146. EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(userChange, changedIssues));
  147. assertThat(emailMessage.getMessageId()).isEqualTo("changes-on-my-issues");
  148. }
  149. @Test
  150. public void format_sets_static_subject_when_change_from_User() {
  151. Set<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(4))
  152. .mapToObj(i -> newChangedIssue(i + "", randomValidStatus(), newProject("prj_" + i), newRandomNotAHotspotRule("rule_" + i)))
  153. .collect(toSet());
  154. UserChange userChange = newUserChange();
  155. EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(userChange, changedIssues));
  156. assertThat(emailMessage.getSubject()).isEqualTo("A manual update has changed some of your issues/hotspots");
  157. }
  158. @Test
  159. public void format_set_html_message_with_header_dealing_with_plural_issues_when_change_from_User() {
  160. Set<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(4))
  161. .mapToObj(i -> newChangedIssue(i + "", randomValidStatus(), newProject("prj_" + i), newRandomNotAHotspotRule("rule_" + i)))
  162. .collect(toSet());
  163. UserChange userChange = newUserChange();
  164. EmailMessage singleIssueMessage = underTest.format(new ChangesOnMyIssuesNotification(
  165. userChange, changedIssues.stream().limit(1).collect(toSet())));
  166. EmailMessage multiIssueMessage = underTest.format(new ChangesOnMyIssuesNotification(userChange, changedIssues));
  167. HtmlFragmentAssert.assertThat(singleIssueMessage.getMessage())
  168. .hasParagraph("Hi,")
  169. .withoutLink()
  170. .hasParagraph("A manual change has updated an issue assigned to you:")
  171. .withoutLink();
  172. HtmlFragmentAssert.assertThat(multiIssueMessage.getMessage())
  173. .hasParagraph("Hi,")
  174. .withoutLink()
  175. .hasParagraph("A manual change has updated issues assigned to you:")
  176. .withoutLink();
  177. }
  178. @Test
  179. public void format_set_html_message_with_header_dealing_with_plural_security_hotspots_when_change_from_User() {
  180. Set<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(4))
  181. .mapToObj(i -> newChangedIssue(i + "", randomValidStatus(), newProject("prj_" + i), newSecurityHotspotRule("rule_" + i)))
  182. .collect(toSet());
  183. UserChange userChange = newUserChange();
  184. EmailMessage singleIssueMessage = underTest.format(new ChangesOnMyIssuesNotification(
  185. userChange, changedIssues.stream().limit(1).collect(toSet())));
  186. EmailMessage multiIssueMessage = underTest.format(new ChangesOnMyIssuesNotification(userChange, changedIssues));
  187. HtmlFragmentAssert.assertThat(singleIssueMessage.getMessage())
  188. .hasParagraph("Hi,")
  189. .withoutLink()
  190. .hasParagraph("A manual change has updated a hotspot assigned to you:")
  191. .withoutLink();
  192. HtmlFragmentAssert.assertThat(multiIssueMessage.getMessage())
  193. .hasParagraph("Hi,")
  194. .withoutLink()
  195. .hasParagraph("A manual change has updated hotspots assigned to you:")
  196. .withoutLink();
  197. }
  198. @Test
  199. public void format_set_html_message_with_header_dealing_with_plural_security_hotspots_and_issues_when_change_from_User() {
  200. Set<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(4))
  201. .mapToObj(i -> newChangedIssue(i + "", randomValidStatus(), newProject("prj_" + i), newRandomNotAHotspotRule("rule_" + i)))
  202. .collect(toSet());
  203. Set<ChangedIssue> changedHotspots = IntStream.range(0, 2 + new Random().nextInt(4))
  204. .mapToObj(i -> newChangedIssue(i + "", randomValidStatus(), newProject("prj_" + i), newSecurityHotspotRule("rule_" + i)))
  205. .collect(toSet());
  206. Set<ChangedIssue> issuesAndHotspots = Sets.union(changedIssues, changedHotspots);
  207. UserChange userChange = newUserChange();
  208. EmailMessage multiIssueMessage = underTest.format(new ChangesOnMyIssuesNotification(userChange, issuesAndHotspots));
  209. HtmlFragmentAssert.assertThat(multiIssueMessage.getMessage())
  210. .hasParagraph("Hi,")
  211. .withoutLink()
  212. .hasParagraph("A manual change has updated issues/hotspots assigned to you:")
  213. .withoutLink();
  214. }
  215. @Test
  216. @UseDataProvider("issueStatuses")
  217. public void format_set_html_message_with_footer_when_issue_change_from_user(String issueStatus) {
  218. UserChange userChange = newUserChange();
  219. format_set_html_message_with_footer(userChange, issueStatus, c -> c
  220. // skip content
  221. .hasParagraph() // skip project header
  222. .hasList(), // rule list,
  223. randomRuleTypeHotspotExcluded());
  224. }
  225. @Test
  226. @UseDataProvider("issueStatuses")
  227. public void format_set_html_message_with_footer_when_issue_change_from_analysis(String issueStatus) {
  228. AnalysisChange analysisChange = newAnalysisChange();
  229. format_set_html_message_with_footer(analysisChange, issueStatus, c -> c
  230. .hasParagraph() // status
  231. .hasList(), // rule list,
  232. randomRuleTypeHotspotExcluded());
  233. }
  234. @Test
  235. @UseDataProvider("securityHotspotsStatuses")
  236. public void format_set_html_message_with_footer_when_security_hotspot_change_from_analysis(String securityHotspotStatus) {
  237. AnalysisChange analysisChange = newAnalysisChange();
  238. format_set_html_message_with_footer(analysisChange, securityHotspotStatus, c -> c
  239. .hasParagraph()
  240. .hasList(), // rule list
  241. SECURITY_HOTSPOT);
  242. }
  243. @Test
  244. @UseDataProvider("securityHotspotsStatuses")
  245. public void format_set_html_message_with_footer_when_security_hotspot_change_from_user(String securityHotspotStatus) {
  246. UserChange userChange = newUserChange();
  247. format_set_html_message_with_footer(userChange, securityHotspotStatus, c -> c
  248. .hasParagraph()
  249. .hasList(), // rule list
  250. SECURITY_HOTSPOT);
  251. }
  252. @DataProvider
  253. public static Object[][] issueStatuses() {
  254. return Arrays.stream(ISSUE_STATUSES)
  255. .map(t -> new Object[] {t})
  256. .toArray(Object[][]::new);
  257. }
  258. @DataProvider
  259. public static Object[][] securityHotspotsStatuses() {
  260. return Arrays.stream(SECURITY_HOTSPOTS_STATUSES)
  261. .map(t -> new Object[] {t})
  262. .toArray(Object[][]::new);
  263. }
  264. private void format_set_html_message_with_footer(Change change, String issueStatus, Function<HtmlParagraphAssert, HtmlListAssert> skipContent, RuleType ruleType) {
  265. String wordingNotification = randomAlphabetic(20);
  266. String host = randomAlphabetic(15);
  267. when(i18n.message(Locale.ENGLISH, "notification.dispatcher.ChangesOnMyIssue", "notification.dispatcher.ChangesOnMyIssue"))
  268. .thenReturn(wordingNotification);
  269. when(emailSettings.getServerBaseURL()).thenReturn(host);
  270. Project project = newProject("foo");
  271. Rule rule = newRule("bar", ruleType);
  272. Set<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(4))
  273. .mapToObj(i -> newChangedIssue(i + "", issueStatus, project, rule))
  274. .collect(toSet());
  275. EmailMessage singleIssueMessage = underTest.format(new ChangesOnMyIssuesNotification(
  276. change, changedIssues.stream().limit(1).collect(toSet())));
  277. EmailMessage multiIssueMessage = underTest.format(new ChangesOnMyIssuesNotification(change, changedIssues));
  278. Stream.of(singleIssueMessage, multiIssueMessage)
  279. .forEach(issueMessage -> {
  280. HtmlParagraphAssert htmlAssert = HtmlFragmentAssert.assertThat(issueMessage.getMessage())
  281. .hasParagraph().hasParagraph(); // skip header
  282. // skip content
  283. HtmlListAssert htmlListAssert = skipContent.apply(htmlAssert);
  284. String footerText = "You received this email because you are subscribed to \"" + wordingNotification + "\" notifications from SonarQube."
  285. + " Click here to edit your email preferences.";
  286. htmlListAssert.hasEmptyParagraph()
  287. .hasParagraph(footerText)
  288. .withSmallOn(footerText)
  289. .withLink("here", host + "/account/notifications")
  290. .noMoreBlock();
  291. });
  292. }
  293. @Test
  294. public void format_set_html_message_with_issues_grouped_by_status_closed_or_any_other_when_change_from_analysis() {
  295. Project project = newProject("foo");
  296. Rule rule = newRandomNotAHotspotRule("bar");
  297. Set<ChangedIssue> changedIssues = Arrays.stream(ISSUE_STATUSES)
  298. .map(status -> newChangedIssue(status + "", status, project, rule))
  299. .collect(toSet());
  300. AnalysisChange analysisChange = newAnalysisChange();
  301. EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(analysisChange, changedIssues));
  302. HtmlListAssert htmlListAssert = HtmlFragmentAssert.assertThat(emailMessage.getMessage())
  303. .hasParagraph().hasParagraph() // skip header
  304. .hasParagraph("Closed issue:")
  305. .withoutLink()
  306. .hasList("Rule " + rule.getName() + " - See the single issue")
  307. .withLinkOn("See the single issue")
  308. .hasParagraph("Open issues:")
  309. .withoutLink()
  310. .hasList("Rule " + rule.getName() + " - See all " + (ISSUE_STATUSES.length - 1) + " issues")
  311. .withLinkOn("See all " + (ISSUE_STATUSES.length - 1) + " issues");
  312. verifyEnd(htmlListAssert);
  313. }
  314. @Test
  315. public void format_set_html_message_with_issue_status_title_handles_plural_when_change_from_analysis() {
  316. Project project = newProject("foo");
  317. Rule rule = newRandomNotAHotspotRule("bar");
  318. Set<ChangedIssue> closedIssues = IntStream.range(0, 2 + new Random().nextInt(5))
  319. .mapToObj(status -> newChangedIssue(status + "", STATUS_CLOSED, project, rule))
  320. .collect(toSet());
  321. Set<ChangedIssue> openIssues = IntStream.range(0, 2 + new Random().nextInt(5))
  322. .mapToObj(status -> newChangedIssue(status + "", STATUS_OPEN, project, rule))
  323. .collect(toSet());
  324. AnalysisChange analysisChange = newAnalysisChange();
  325. EmailMessage closedIssuesMessage = underTest.format(new ChangesOnMyIssuesNotification(analysisChange, closedIssues));
  326. EmailMessage openIssuesMessage = underTest.format(new ChangesOnMyIssuesNotification(analysisChange, openIssues));
  327. HtmlListAssert htmlListAssert = HtmlFragmentAssert.assertThat(closedIssuesMessage.getMessage())
  328. .hasParagraph().hasParagraph() // skip header
  329. .hasParagraph("Closed issues:")
  330. .hasList();
  331. verifyEnd(htmlListAssert);
  332. htmlListAssert = HtmlFragmentAssert.assertThat(openIssuesMessage.getMessage())
  333. .hasParagraph().hasParagraph() // skip header
  334. .hasParagraph("Open issues:")
  335. .hasList();
  336. verifyEnd(htmlListAssert);
  337. }
  338. @Test
  339. public void formats_returns_html_message_for_single_issue_on_master_when_analysis_change() {
  340. Project project = newProject("1");
  341. String ruleName = randomAlphabetic(8);
  342. String host = randomAlphabetic(15);
  343. ChangedIssue changedIssue = newChangedIssue("key", randomValidStatus(), project, ruleName, randomRuleTypeHotspotExcluded());
  344. AnalysisChange analysisChange = newAnalysisChange();
  345. when(emailSettings.getServerBaseURL()).thenReturn(host);
  346. EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(analysisChange, ImmutableSet.of(changedIssue)));
  347. HtmlFragmentAssert.assertThat(emailMessage.getMessage())
  348. .hasParagraph().hasParagraph() // skip header
  349. .hasParagraph()// skip title based on status
  350. .hasList("Rule " + ruleName + " - See the single issue")
  351. .withLink("See the single issue", host + "/project/issues?id=" + project.getKey() + "&issues=" + changedIssue.getKey() + "&open=" + changedIssue.getKey())
  352. .hasParagraph().hasParagraph() // skip footer
  353. .noMoreBlock();
  354. }
  355. @Test
  356. public void formats_returns_html_message_for_single_issue_on_master_when_user_change() {
  357. Project project = newProject("1");
  358. String ruleName = randomAlphabetic(8);
  359. String host = randomAlphabetic(15);
  360. ChangedIssue changedIssue = newChangedIssue("key", randomValidStatus(), project, ruleName, randomRuleTypeHotspotExcluded());
  361. UserChange userChange = newUserChange();
  362. when(emailSettings.getServerBaseURL()).thenReturn(host);
  363. EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(userChange, ImmutableSet.of(changedIssue)));
  364. HtmlFragmentAssert.assertThat(emailMessage.getMessage())
  365. .hasParagraph().hasParagraph() // skip header
  366. .hasParagraph(project.getProjectName())
  367. .hasList("Rule " + ruleName + " - See the single issue")
  368. .withLink("See the single issue", host + "/project/issues?id=" + project.getKey() + "&issues=" + changedIssue.getKey() + "&open=" + changedIssue.getKey())
  369. .hasParagraph().hasParagraph() // skip footer
  370. .noMoreBlock();
  371. }
  372. @Test
  373. public void formats_returns_html_message_for_single_issue_on_branch_when_analysis_change() {
  374. String branchName = randomAlphabetic(6);
  375. Project project = newBranch("1", branchName);
  376. String ruleName = randomAlphabetic(8);
  377. String host = randomAlphabetic(15);
  378. String key = "key";
  379. ChangedIssue changedIssue = newChangedIssue(key, randomValidStatus(), project, ruleName, randomRuleTypeHotspotExcluded());
  380. AnalysisChange analysisChange = newAnalysisChange();
  381. when(emailSettings.getServerBaseURL()).thenReturn(host);
  382. EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(analysisChange, ImmutableSet.of(changedIssue)));
  383. HtmlFragmentAssert.assertThat(emailMessage.getMessage())
  384. .hasParagraph().hasParagraph() // skip header
  385. .hasParagraph()// skip title based on status
  386. .hasList("Rule " + ruleName + " - See the single issue")
  387. .withLink("See the single issue",
  388. host + "/project/issues?id=" + project.getKey() + "&branch=" + branchName + "&issues=" + changedIssue.getKey() + "&open=" + changedIssue.getKey())
  389. .hasParagraph().hasParagraph() // skip footer
  390. .noMoreBlock();
  391. }
  392. @Test
  393. public void formats_returns_html_message_for_single_issue_on_branch_when_user_change() {
  394. String branchName = randomAlphabetic(6);
  395. Project project = newBranch("1", branchName);
  396. String ruleName = randomAlphabetic(8);
  397. String host = randomAlphabetic(15);
  398. String key = "key";
  399. ChangedIssue changedIssue = newChangedIssue(key, randomValidStatus(), project, ruleName, randomRuleTypeHotspotExcluded());
  400. UserChange userChange = newUserChange();
  401. when(emailSettings.getServerBaseURL()).thenReturn(host);
  402. EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(userChange, ImmutableSet.of(changedIssue)));
  403. HtmlFragmentAssert.assertThat(emailMessage.getMessage())
  404. .hasParagraph().hasParagraph() // skip header
  405. .hasParagraph(project.getProjectName() + ", " + branchName)
  406. .hasList("Rule " + ruleName + " - See the single issue")
  407. .withLink("See the single issue",
  408. host + "/project/issues?id=" + project.getKey() + "&branch=" + branchName + "&issues=" + changedIssue.getKey() + "&open=" + changedIssue.getKey())
  409. .hasParagraph().hasParagraph() // skip footer
  410. .noMoreBlock();
  411. }
  412. @Test
  413. public void formats_returns_html_message_for_multiple_issues_of_same_rule_on_same_project_on_master_when_analysis_change() {
  414. Project project = newProject("1");
  415. String ruleName = randomAlphabetic(8);
  416. String host = randomAlphabetic(15);
  417. Rule rule = newRule(ruleName, randomRuleTypeHotspotExcluded());
  418. String issueStatus = randomValidStatus();
  419. List<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(5))
  420. .mapToObj(i -> newChangedIssue("issue_" + i, issueStatus, project, rule))
  421. .collect(toList());
  422. AnalysisChange analysisChange = newAnalysisChange();
  423. when(emailSettings.getServerBaseURL()).thenReturn(host);
  424. EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(analysisChange, ImmutableSet.copyOf(changedIssues)));
  425. String expectedHref = host + "/project/issues?id=" + project.getKey()
  426. + "&issues=" + changedIssues.stream().map(ChangedIssue::getKey).collect(joining("%2C"));
  427. String expectedLinkText = "See all " + changedIssues.size() + " issues";
  428. HtmlFragmentAssert.assertThat(emailMessage.getMessage())
  429. .hasParagraph().hasParagraph() // skip header
  430. .hasParagraph() // skip title based on status
  431. .hasList("Rule " + ruleName + " - " + expectedLinkText)
  432. .withLink(expectedLinkText, expectedHref)
  433. .hasParagraph().hasParagraph() // skip footer
  434. .noMoreBlock();
  435. }
  436. @Test
  437. public void formats_returns_html_message_for_multiple_issues_of_same_rule_on_same_project_on_master_when_user_change() {
  438. Project project = newProject("1");
  439. String ruleName = randomAlphabetic(8);
  440. String host = randomAlphabetic(15);
  441. Rule rule = newRule(ruleName, randomRuleTypeHotspotExcluded());
  442. List<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(5))
  443. .mapToObj(i -> newChangedIssue("issue_" + i, randomValidStatus(), project, rule))
  444. .collect(toList());
  445. UserChange userChange = newUserChange();
  446. when(emailSettings.getServerBaseURL()).thenReturn(host);
  447. EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(userChange, ImmutableSet.copyOf(changedIssues)));
  448. String expectedHref = host + "/project/issues?id=" + project.getKey()
  449. + "&issues=" + changedIssues.stream().map(ChangedIssue::getKey).collect(joining("%2C"));
  450. String expectedLinkText = "See all " + changedIssues.size() + " issues";
  451. HtmlFragmentAssert.assertThat(emailMessage.getMessage())
  452. .hasParagraph().hasParagraph() // skip header
  453. .hasParagraph(project.getProjectName())
  454. .hasList("Rule " + ruleName + " - " + expectedLinkText)
  455. .withLink(expectedLinkText, expectedHref)
  456. .hasParagraph().hasParagraph() // skip footer
  457. .noMoreBlock();
  458. }
  459. @Test
  460. public void formats_returns_html_message_for_multiple_issues_of_same_rule_on_same_project_on_branch_when_analysis_change() {
  461. String branchName = randomAlphabetic(19);
  462. Project project = newBranch("1", branchName);
  463. String ruleName = randomAlphabetic(8);
  464. String host = randomAlphabetic(15);
  465. Rule rule = newRule(ruleName, randomRuleTypeHotspotExcluded());
  466. String status = randomValidStatus();
  467. List<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(5))
  468. .mapToObj(i -> newChangedIssue("issue_" + i, status, project, rule))
  469. .collect(toList());
  470. AnalysisChange analysisChange = newAnalysisChange();
  471. when(emailSettings.getServerBaseURL()).thenReturn(host);
  472. EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(analysisChange, ImmutableSet.copyOf(changedIssues)));
  473. String expectedHref = host + "/project/issues?id=" + project.getKey() + "&branch=" + branchName
  474. + "&issues=" + changedIssues.stream().map(ChangedIssue::getKey).collect(joining("%2C"));
  475. String expectedLinkText = "See all " + changedIssues.size() + " issues";
  476. HtmlFragmentAssert.assertThat(emailMessage.getMessage())
  477. .hasParagraph().hasParagraph() // skip header
  478. .hasParagraph()// skip title based on status
  479. .hasList("Rule " + ruleName + " - " + expectedLinkText)
  480. .withLink(expectedLinkText, expectedHref)
  481. .hasParagraph().hasParagraph() // skip footer
  482. .noMoreBlock();
  483. }
  484. @Test
  485. public void formats_returns_html_message_for_multiple_issues_of_same_rule_on_same_project_on_branch_when_user_change() {
  486. String branchName = randomAlphabetic(19);
  487. Project project = newBranch("1", branchName);
  488. String ruleName = randomAlphabetic(8);
  489. String host = randomAlphabetic(15);
  490. Rule rule = newRandomNotAHotspotRule(ruleName);
  491. List<ChangedIssue> changedIssues = IntStream.range(0, 2 + new Random().nextInt(5))
  492. .mapToObj(i -> newChangedIssue("issue_" + i, randomValidStatus(), project, rule))
  493. .collect(toList());
  494. UserChange userChange = newUserChange();
  495. when(emailSettings.getServerBaseURL()).thenReturn(host);
  496. EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(userChange, ImmutableSet.copyOf(changedIssues)));
  497. String expectedHref = host + "/project/issues?id=" + project.getKey() + "&branch=" + branchName
  498. + "&issues=" + changedIssues.stream().map(ChangedIssue::getKey).collect(joining("%2C"));
  499. String expectedLinkText = "See all " + changedIssues.size() + " issues";
  500. HtmlFragmentAssert.assertThat(emailMessage.getMessage())
  501. .hasParagraph().hasParagraph() // skip header
  502. .hasParagraph(project.getProjectName() + ", " + branchName)
  503. .hasList("Rule " + ruleName + " - " + expectedLinkText)
  504. .withLink(expectedLinkText, expectedHref)
  505. .hasParagraph().hasParagraph() // skip footer
  506. .noMoreBlock();
  507. }
  508. @Test
  509. public void formats_returns_html_message_with_projects_ordered_by_name_when_user_change() {
  510. Project project1 = newProject("1");
  511. Project project1Branch1 = newBranch("1", "a");
  512. Project project1Branch2 = newBranch("1", "b");
  513. Project project2 = newProject("B");
  514. Project project2Branch1 = newBranch("B", "a");
  515. Project project3 = newProject("C");
  516. String host = randomAlphabetic(15);
  517. List<ChangedIssue> changedIssues = Stream.of(project1, project1Branch1, project1Branch2, project2, project2Branch1, project3)
  518. .map(project -> newChangedIssue("issue_" + project.getUuid(), randomValidStatus(), project, newRule(randomAlphabetic(2), randomRuleTypeHotspotExcluded())))
  519. .collect(toList());
  520. Collections.shuffle(changedIssues);
  521. UserChange userChange = newUserChange();
  522. when(emailSettings.getServerBaseURL()).thenReturn(host);
  523. EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(userChange, ImmutableSet.copyOf(changedIssues)));
  524. HtmlFragmentAssert.assertThat(emailMessage.getMessage())
  525. .hasParagraph().hasParagraph() // skip header
  526. .hasParagraph(project1.getProjectName())
  527. .hasList()
  528. .hasParagraph(project1Branch1.getProjectName() + ", " + project1Branch1.getBranchName().get())
  529. .hasList()
  530. .hasParagraph(project1Branch2.getProjectName() + ", " + project1Branch2.getBranchName().get())
  531. .hasList()
  532. .hasParagraph(project2.getProjectName())
  533. .hasList()
  534. .hasParagraph(project2Branch1.getProjectName() + ", " + project2Branch1.getBranchName().get())
  535. .hasList()
  536. .hasParagraph(project3.getProjectName())
  537. .hasList()
  538. .hasParagraph().hasParagraph() // skip footer
  539. .noMoreBlock();
  540. }
  541. @Test
  542. public void formats_returns_html_message_with_rules_ordered_by_name_when_analysis_change() {
  543. Project project = newProject("1");
  544. Rule rule1 = newRandomNotAHotspotRule("1");
  545. Rule rule2 = newRandomNotAHotspotRule("a");
  546. Rule rule3 = newRandomNotAHotspotRule("b");
  547. Rule rule4 = newRandomNotAHotspotRule("X");
  548. String host = randomAlphabetic(15);
  549. String issueStatus = randomValidStatus();
  550. List<ChangedIssue> changedIssues = Stream.of(rule1, rule2, rule3, rule4)
  551. .map(rule -> newChangedIssue("issue_" + rule.getName(), issueStatus, project, rule))
  552. .collect(toList());
  553. Collections.shuffle(changedIssues);
  554. AnalysisChange analysisChange = newAnalysisChange();
  555. when(emailSettings.getServerBaseURL()).thenReturn(host);
  556. EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(analysisChange, ImmutableSet.copyOf(changedIssues)));
  557. HtmlFragmentAssert.assertThat(emailMessage.getMessage())
  558. .hasParagraph().hasParagraph() // skip header
  559. .hasParagraph()// skip title based on status
  560. .hasList(
  561. "Rule " + rule1.getName() + " - See the single issue",
  562. "Rule " + rule2.getName() + " - See the single issue",
  563. "Rule " + rule3.getName() + " - See the single issue",
  564. "Rule " + rule4.getName() + " - See the single issue")
  565. .hasParagraph().hasParagraph() // skip footer
  566. .noMoreBlock();
  567. }
  568. @Test
  569. public void formats_returns_html_message_with_rules_ordered_by_name_user_change() {
  570. Project project = newProject("1");
  571. Rule rule1 = newRandomNotAHotspotRule("1");
  572. Rule rule2 = newRandomNotAHotspotRule("a");
  573. Rule rule3 = newRandomNotAHotspotRule("b");
  574. Rule rule4 = newRandomNotAHotspotRule("X");
  575. Rule hotspot1 = newSecurityHotspotRule("S");
  576. Rule hotspot2 = newSecurityHotspotRule("Z");
  577. Rule hotspot3 = newSecurityHotspotRule("N");
  578. Rule hotspot4 = newSecurityHotspotRule("M");
  579. String host = randomAlphabetic(15);
  580. List<ChangedIssue> changedIssues = Stream.of(rule1, rule2, rule3, rule4, hotspot1, hotspot2, hotspot3, hotspot4)
  581. .map(rule -> newChangedIssue("issue_" + rule.getName(), randomValidStatus(), project, rule))
  582. .collect(toList());
  583. Collections.shuffle(changedIssues);
  584. UserChange userChange = newUserChange();
  585. when(emailSettings.getServerBaseURL()).thenReturn(host);
  586. EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(userChange, ImmutableSet.copyOf(changedIssues)));
  587. HtmlFragmentAssert.assertThat(emailMessage.getMessage())
  588. .hasParagraph()
  589. .hasParagraph()
  590. .hasParagraph() // skip project name
  591. .hasList(
  592. "Rule " + rule1.getName() + " - See the single issue",
  593. "Rule " + rule2.getName() + " - See the single issue",
  594. "Rule " + rule3.getName() + " - See the single issue",
  595. "Rule " + rule4.getName() + " - See the single issue")
  596. .hasEmptyParagraph()
  597. .hasList(
  598. "Rule " + hotspot1.getName() + " - See the single hotspot",
  599. "Rule " + hotspot2.getName() + " - See the single hotspot",
  600. "Rule " + hotspot3.getName() + " - See the single hotspot",
  601. "Rule " + hotspot4.getName() + " - See the single hotspot")
  602. .hasParagraph().hasParagraph() // skip footer
  603. .noMoreBlock();
  604. }
  605. @Test
  606. public void formats_returns_html_message_with_multiple_links_by_rule_of_groups_of_up_to_40_issues_when_analysis_change() {
  607. Project project1 = newProject("1");
  608. Rule rule1 = newRandomNotAHotspotRule("1");
  609. Rule rule2 = newRandomNotAHotspotRule("a");
  610. String host = randomAlphabetic(15);
  611. String issueStatusClosed = STATUS_CLOSED;
  612. String otherIssueStatus = STATUS_RESOLVED;
  613. List<ChangedIssue> changedIssues = Stream.of(
  614. IntStream.range(0, 39).mapToObj(i -> newChangedIssue("39_" + i, issueStatusClosed, project1, rule1)),
  615. IntStream.range(0, 40).mapToObj(i -> newChangedIssue("40_" + i, issueStatusClosed, project1, rule2)),
  616. IntStream.range(0, 81).mapToObj(i -> newChangedIssue("1-40_41-80_1_" + i, otherIssueStatus, project1, rule2)),
  617. IntStream.range(0, 6).mapToObj(i -> newChangedIssue("6_" + i, otherIssueStatus, project1, rule1)))
  618. .flatMap(t -> t)
  619. .collect(toList());
  620. Collections.shuffle(changedIssues);
  621. AnalysisChange analysisChange = newAnalysisChange();
  622. when(emailSettings.getServerBaseURL()).thenReturn(host);
  623. EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(analysisChange, ImmutableSet.copyOf(changedIssues)));
  624. HtmlFragmentAssert.assertThat(emailMessage.getMessage())
  625. .hasParagraph().hasParagraph() // skip header
  626. .hasParagraph("Closed issues:") // skip title based on status
  627. .hasList(
  628. "Rule " + rule1.getName() + " - See all 39 issues",
  629. "Rule " + rule2.getName() + " - See all 40 issues")
  630. .withLink("See all 39 issues",
  631. host + "/project/issues?id=" + project1.getKey()
  632. + "&issues=" + IntStream.range(0, 39).mapToObj(i -> "39_" + i).sorted().collect(joining("%2C")))
  633. .withLink("See all 40 issues",
  634. host + "/project/issues?id=" + project1.getKey()
  635. + "&issues=" + IntStream.range(0, 40).mapToObj(i -> "40_" + i).sorted().collect(joining("%2C")))
  636. .hasParagraph("Open issues:")
  637. .hasList(
  638. "Rule " + rule2.getName() + " - See issues 1-40 41-80 81",
  639. "Rule " + rule1.getName() + " - See all 6 issues")
  640. .withLink("1-40",
  641. host + "/project/issues?id=" + project1.getKey()
  642. + "&issues=" + IntStream.range(0, 81).mapToObj(i -> "1-40_41-80_1_" + i).sorted().limit(40).collect(joining("%2C")))
  643. .withLink("41-80",
  644. host + "/project/issues?id=" + project1.getKey()
  645. + "&issues=" + IntStream.range(0, 81).mapToObj(i -> "1-40_41-80_1_" + i).sorted().skip(40).limit(40).collect(joining("%2C")))
  646. .withLink("81",
  647. host + "/project/issues?id=" + project1.getKey()
  648. + "&issues=" + "1-40_41-80_1_9" + "&open=" + "1-40_41-80_1_9")
  649. .withLink("See all 6 issues",
  650. host + "/project/issues?id=" + project1.getKey()
  651. + "&issues=" + IntStream.range(0, 6).mapToObj(i -> "6_" + i).sorted().collect(joining("%2C")))
  652. .hasParagraph().hasParagraph() // skip footer
  653. .noMoreBlock();
  654. }
  655. @Test
  656. public void formats_returns_html_message_with_multiple_links_by_rule_of_groups_of_up_to_40_issues_and_hotspots_when_user_change() {
  657. Project project1 = newProject("1");
  658. Project project2 = newProject("V");
  659. Project project2Branch = newBranch("V", "AB");
  660. Rule rule1 = newRule("1", randomRuleTypeHotspotExcluded());
  661. Rule rule2 = newRule("a", randomRuleTypeHotspotExcluded());
  662. Rule hotspot1 = newSecurityHotspotRule("h1");
  663. Rule hotspot2 = newSecurityHotspotRule("h2");
  664. String status = randomValidStatus();
  665. String host = randomAlphabetic(15);
  666. List<ChangedIssue> changedIssues = Stream.of(
  667. IntStream.range(0, 39).mapToObj(i -> newChangedIssue("39_" + i, status, project1, rule1)),
  668. IntStream.range(0, 40).mapToObj(i -> newChangedIssue("40_" + i, status, project1, rule2)),
  669. IntStream.range(0, 81).mapToObj(i -> newChangedIssue("1-40_41-80_1_" + i, status, project2, rule2)),
  670. IntStream.range(0, 6).mapToObj(i -> newChangedIssue("6_" + i, status, project2Branch, rule1)),
  671. IntStream.range(0, 39).mapToObj(i -> newChangedIssue("39_" + i, STATUS_REVIEWED, project1, hotspot1)),
  672. IntStream.range(0, 40).mapToObj(i -> newChangedIssue("40_" + i, STATUS_REVIEWED, project1, hotspot2)),
  673. IntStream.range(0, 81).mapToObj(i -> newChangedIssue("1-40_41-80_1_" + i, STATUS_TO_REVIEW, project2, hotspot2)),
  674. IntStream.range(0, 6).mapToObj(i -> newChangedIssue("6_" + i, STATUS_TO_REVIEW, project2Branch, hotspot1)))
  675. .flatMap(t -> t)
  676. .collect(toList());
  677. Collections.shuffle(changedIssues);
  678. UserChange userChange = newUserChange();
  679. when(emailSettings.getServerBaseURL()).thenReturn(host);
  680. EmailMessage emailMessage = underTest.format(new ChangesOnMyIssuesNotification(userChange, ImmutableSet.copyOf(changedIssues)));
  681. HtmlFragmentAssert.assertThat(emailMessage.getMessage())
  682. .hasParagraph().hasParagraph() // skip header
  683. .hasParagraph(project1.getProjectName())
  684. .hasList()
  685. .withItemTexts(
  686. "Rule " + rule1.getName() + " - See all 39 issues",
  687. "Rule " + rule2.getName() + " - See all 40 issues")
  688. .withLink("See all 39 issues",
  689. host + "/project/issues?id=" + project1.getKey()
  690. + "&issues=" + IntStream.range(0, 39).mapToObj(i -> "39_" + i).sorted().collect(joining("%2C")))
  691. .withLink("See all 40 issues",
  692. host + "/project/issues?id=" + project1.getKey()
  693. + "&issues=" + IntStream.range(0, 40).mapToObj(i -> "40_" + i).sorted().collect(joining("%2C")))
  694. .hasEmptyParagraph()
  695. .hasList()
  696. .withItemTexts(
  697. "Rule " + hotspot1.getName() + " - See all 39 hotspots",
  698. "Rule " + hotspot2.getName() + " - See all 40 hotspots")
  699. .withLink("See all 39 hotspots",
  700. host + "/security_hotspots?id=" + project1.getKey()
  701. + "&hotspots=" + IntStream.range(0, 39).mapToObj(i -> "39_" + i).sorted().collect(joining("%2C")))
  702. .withLink("See all 40 hotspots",
  703. host + "/security_hotspots?id=" + project1.getKey()
  704. + "&hotspots=" + IntStream.range(0, 40).mapToObj(i -> "40_" + i).sorted().collect(joining("%2C")))
  705. .hasParagraph(project2.getProjectName())
  706. .hasList(
  707. "Rule " + rule2.getName() + " - See issues 1-40 41-80 81")
  708. .withLink("1-40",
  709. host + "/project/issues?id=" + project2.getKey()
  710. + "&issues=" + IntStream.range(0, 81).mapToObj(i -> "1-40_41-80_1_" + i).sorted().limit(40).collect(joining("%2C")))
  711. .withLink("41-80",
  712. host + "/project/issues?id=" + project2.getKey()
  713. + "&issues=" + IntStream.range(0, 81).mapToObj(i -> "1-40_41-80_1_" + i).sorted().skip(40).limit(40).collect(joining("%2C")))
  714. .withLink("81",
  715. host + "/project/issues?id=" + project2.getKey()
  716. + "&issues=" + "1-40_41-80_1_9" + "&open=" + "1-40_41-80_1_9")
  717. .hasEmptyParagraph()
  718. .hasList("Rule " + hotspot2.getName() + " - See hotspots 1-40 41-80 81")
  719. .withLink("1-40",
  720. host + "/security_hotspots?id=" + project2.getKey()
  721. + "&hotspots=" + IntStream.range(0, 81).mapToObj(i -> "1-40_41-80_1_" + i).sorted().limit(40).collect(joining("%2C")))
  722. .withLink("41-80",
  723. host + "/security_hotspots?id=" + project2.getKey()
  724. + "&hotspots=" + IntStream.range(0, 81).mapToObj(i -> "1-40_41-80_1_" + i).sorted().skip(40).limit(40).collect(joining("%2C")))
  725. .withLink("81",
  726. host + "/security_hotspots?id=" + project2.getKey()
  727. + "&hotspots=" + "1-40_41-80_1_9")
  728. .hasParagraph(project2Branch.getProjectName() + ", " + project2Branch.getBranchName().get())
  729. .hasList(
  730. "Rule " + rule1.getName() + " - See all 6 issues")
  731. .withLink("See all 6 issues",
  732. host + "/project/issues?id=" + project2Branch.getKey() + "&branch=" + project2Branch.getBranchName().get()
  733. + "&issues=" + IntStream.range(0, 6).mapToObj(i -> "6_" + i).sorted().collect(joining("%2C")))
  734. .hasEmptyParagraph()
  735. .hasList("Rule " + hotspot1.getName() + " - See all 6 hotspots")
  736. .withLink("See all 6 hotspots",
  737. host + "/security_hotspots?id=" + project2Branch.getKey() + "&branch=" + project2Branch.getBranchName().get()
  738. + "&hotspots=" + IntStream.range(0, 6).mapToObj(i -> "6_" + i).sorted().collect(joining("%2C")))
  739. .hasParagraph().hasParagraph() // skip footer
  740. .noMoreBlock();
  741. }
  742. private static String randomValidStatus() {
  743. return ISSUE_STATUSES[new Random().nextInt(ISSUE_STATUSES.length)];
  744. }
  745. private void verifyEnd(HtmlListAssert htmlListAssert) {
  746. htmlListAssert
  747. .hasEmptyParagraph()
  748. .hasParagraph()
  749. .noMoreBlock();
  750. }
  751. }