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.

FPOrWontFixNotificationHandlerTest.java 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  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.google.common.collect.ListMultimap;
  23. import com.tngtech.java.junit.dataprovider.DataProvider;
  24. import com.tngtech.java.junit.dataprovider.DataProviderRunner;
  25. import com.tngtech.java.junit.dataprovider.UseDataProvider;
  26. import java.util.Random;
  27. import java.util.Set;
  28. import java.util.function.Consumer;
  29. import java.util.stream.IntStream;
  30. import java.util.stream.Stream;
  31. import org.junit.Test;
  32. import org.junit.runner.RunWith;
  33. import org.mockito.ArgumentCaptor;
  34. import org.mockito.Mockito;
  35. import org.sonar.api.issue.Issue;
  36. import org.sonar.server.issue.notification.FPOrWontFixNotification.FpOrWontFix;
  37. import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.Change;
  38. import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.ChangedIssue;
  39. import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.Project;
  40. import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.User;
  41. import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.UserChange;
  42. import org.sonar.server.notification.NotificationDispatcherMetadata;
  43. import org.sonar.server.notification.NotificationManager;
  44. import org.sonar.server.notification.email.EmailNotificationChannel;
  45. import org.sonar.server.notification.email.EmailNotificationChannel.EmailDeliveryRequest;
  46. import static java.util.Collections.singleton;
  47. import static java.util.stream.Collectors.toSet;
  48. import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
  49. import static org.assertj.core.api.Assertions.assertThat;
  50. import static org.junit.Assert.fail;
  51. import static org.mockito.ArgumentMatchers.any;
  52. import static org.mockito.ArgumentMatchers.anySet;
  53. import static org.mockito.Mockito.mock;
  54. import static org.mockito.Mockito.reset;
  55. import static org.mockito.Mockito.spy;
  56. import static org.mockito.Mockito.times;
  57. import static org.mockito.Mockito.verify;
  58. import static org.mockito.Mockito.verifyNoMoreInteractions;
  59. import static org.mockito.Mockito.verifyZeroInteractions;
  60. import static org.mockito.Mockito.when;
  61. import static org.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE;
  62. import static org.sonar.api.issue.Issue.RESOLUTION_WONT_FIX;
  63. import static org.sonar.core.util.stream.MoreCollectors.index;
  64. import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.newProject;
  65. import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.newRandomNotAHotspotRule;
  66. import static org.sonar.server.notification.NotificationDispatcherMetadata.GLOBAL_NOTIFICATION;
  67. import static org.sonar.server.notification.NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION;
  68. import static org.sonar.server.notification.NotificationManager.SubscriberPermissionsOnProject.ALL_MUST_HAVE_ROLE_USER;
  69. @RunWith(DataProviderRunner.class)
  70. public class FPOrWontFixNotificationHandlerTest {
  71. private static final String DO_NOT_FIX_ISSUE_CHANGE_DISPATCHER_KEY = "NewFalsePositiveIssue";
  72. private NotificationManager notificationManager = mock(NotificationManager.class);
  73. private EmailNotificationChannel emailNotificationChannel = mock(EmailNotificationChannel.class);
  74. private IssuesChangesNotificationSerializer serializerMock = mock(IssuesChangesNotificationSerializer.class);
  75. private IssuesChangesNotificationSerializer serializer = spy(new IssuesChangesNotificationSerializer());
  76. private Class<Set<EmailDeliveryRequest>> requestSetType = (Class<Set<EmailDeliveryRequest>>) (Class<?>) Set.class;
  77. private FPOrWontFixNotificationHandler underTest = new FPOrWontFixNotificationHandler(notificationManager, emailNotificationChannel, serializer);
  78. @Test
  79. public void getMetadata_returns_same_instance_as_static_method() {
  80. assertThat(underTest.getMetadata().get()).isSameAs(FPOrWontFixNotificationHandler.newMetadata());
  81. }
  82. @Test
  83. public void verify_fpOrWontFixIssues_notification_dispatcher_key() {
  84. NotificationDispatcherMetadata metadata = FPOrWontFixNotificationHandler.newMetadata();
  85. assertThat(metadata.getDispatcherKey()).isEqualTo(DO_NOT_FIX_ISSUE_CHANGE_DISPATCHER_KEY);
  86. }
  87. @Test
  88. public void fpOrWontFixIssues_notification_is_disabled_at_global_level() {
  89. NotificationDispatcherMetadata metadata = FPOrWontFixNotificationHandler.newMetadata();
  90. assertThat(metadata.getProperty(GLOBAL_NOTIFICATION)).isEqualTo("false");
  91. }
  92. @Test
  93. public void fpOrWontFixIssues_notification_is_enable_at_project_level() {
  94. NotificationDispatcherMetadata metadata = FPOrWontFixNotificationHandler.newMetadata();
  95. assertThat(metadata.getProperty(PER_PROJECT_NOTIFICATION)).isEqualTo("true");
  96. }
  97. @Test
  98. public void getNotificationClass_is_IssueChangeNotification() {
  99. assertThat(underTest.getNotificationClass()).isEqualTo(IssuesChangesNotification.class);
  100. }
  101. @Test
  102. public void deliver_has_no_effect_if_emailNotificationChannel_is_disabled() {
  103. when(emailNotificationChannel.isActivated()).thenReturn(false);
  104. Set<IssuesChangesNotification> notifications = IntStream.range(0, 1 + new Random().nextInt(10))
  105. .mapToObj(i -> mock(IssuesChangesNotification.class))
  106. .collect(toSet());
  107. int deliver = underTest.deliver(notifications);
  108. assertThat(deliver).isZero();
  109. verifyZeroInteractions(notificationManager);
  110. verify(emailNotificationChannel).isActivated();
  111. verifyNoMoreInteractions(emailNotificationChannel);
  112. notifications.forEach(Mockito::verifyZeroInteractions);
  113. }
  114. @Test
  115. public void deliver_parses_every_notification_in_order() {
  116. Set<IssuesChangesNotification> notifications = IntStream.range(0, 5 + new Random().nextInt(10))
  117. .mapToObj(i -> mock(IssuesChangesNotification.class))
  118. .collect(toSet());
  119. when(emailNotificationChannel.isActivated()).thenReturn(true);
  120. when(serializerMock.from(any(IssuesChangesNotification.class))).thenReturn(mock(IssuesChangesNotificationBuilder.class));
  121. FPOrWontFixNotificationHandler underTest = new FPOrWontFixNotificationHandler(notificationManager, emailNotificationChannel, serializerMock);
  122. underTest.deliver(notifications);
  123. notifications.forEach(notification -> verify(serializerMock).from(notification));
  124. }
  125. @Test
  126. public void deliver_fails_with_IAE_if_serializer_throws_IAE() {
  127. Set<IssuesChangesNotification> notifications = IntStream.range(0, 3 + new Random().nextInt(10))
  128. .mapToObj(i -> mock(IssuesChangesNotification.class))
  129. .collect(toSet());
  130. when(emailNotificationChannel.isActivated()).thenReturn(true);
  131. IllegalArgumentException expected = new IllegalArgumentException("faking serializer#from throwing a IllegalArgumentException");
  132. when(serializerMock.from(any(IssuesChangesNotification.class)))
  133. .thenReturn(mock(IssuesChangesNotificationBuilder.class))
  134. .thenReturn(mock(IssuesChangesNotificationBuilder.class))
  135. .thenThrow(expected);
  136. FPOrWontFixNotificationHandler underTest = new FPOrWontFixNotificationHandler(notificationManager, emailNotificationChannel, serializerMock);
  137. try {
  138. underTest.deliver(notifications);
  139. fail("should have throws IAE");
  140. } catch (IllegalArgumentException e) {
  141. verify(serializerMock, times(3)).from(any(IssuesChangesNotification.class));
  142. assertThat(e).isSameAs(expected);
  143. }
  144. }
  145. @Test
  146. public void deliver_has_no_effect_if_no_issue_has_new_resolution() {
  147. when(emailNotificationChannel.isActivated()).thenReturn(true);
  148. Change changeMock = mock(Change.class);
  149. Set<IssuesChangesNotification> notifications = IntStream.range(0, 2 + new Random().nextInt(5))
  150. .mapToObj(j -> new IssuesChangesNotificationBuilder(randomIssues(t -> t.setNewResolution(null)).collect(toSet()), changeMock))
  151. .map(serializer::serialize)
  152. .collect(toSet());
  153. reset(serializer);
  154. int deliver = underTest.deliver(notifications);
  155. assertThat(deliver).isZero();
  156. verify(serializer, times(notifications.size())).from(any(IssuesChangesNotification.class));
  157. verifyZeroInteractions(changeMock);
  158. verifyNoMoreInteractions(serializer);
  159. verifyZeroInteractions(notificationManager);
  160. verify(emailNotificationChannel).isActivated();
  161. verifyNoMoreInteractions(emailNotificationChannel);
  162. }
  163. @Test
  164. @UseDataProvider("notFPorWontFixResolution")
  165. public void deliver_has_no_effect_if_no_issue_has_FP_or_wontfix_resolution(String newResolution) {
  166. when(emailNotificationChannel.isActivated()).thenReturn(true);
  167. Change changeMock = mock(Change.class);
  168. Set<IssuesChangesNotification> notifications = IntStream.range(0, 2 + new Random().nextInt(5))
  169. .mapToObj(j -> new IssuesChangesNotificationBuilder(randomIssues(t -> t.setNewResolution(newResolution)).collect(toSet()), changeMock))
  170. .map(serializer::serialize)
  171. .collect(toSet());
  172. reset(serializer);
  173. int deliver = underTest.deliver(notifications);
  174. assertThat(deliver).isZero();
  175. verify(serializer, times(notifications.size())).from(any(IssuesChangesNotification.class));
  176. verifyZeroInteractions(changeMock);
  177. verifyNoMoreInteractions(serializer);
  178. verifyZeroInteractions(notificationManager);
  179. verify(emailNotificationChannel).isActivated();
  180. verifyNoMoreInteractions(emailNotificationChannel);
  181. }
  182. @DataProvider
  183. public static Object[][] notFPorWontFixResolution() {
  184. return new Object[][] {
  185. {""},
  186. {randomAlphabetic(9)},
  187. {Issue.RESOLUTION_FIXED},
  188. {Issue.RESOLUTION_REMOVED}
  189. };
  190. }
  191. @Test
  192. @UseDataProvider("FPorWontFixResolution")
  193. public void deliver_checks_by_projectKey_if_notifications_have_subscribed_assignee_to_FPorWontFix_notifications(String newResolution) {
  194. Project projectKey1 = newProject(randomAlphabetic(4));
  195. Project projectKey2 = newProject(randomAlphabetic(5));
  196. Project projectKey3 = newProject(randomAlphabetic(6));
  197. Project projectKey4 = newProject(randomAlphabetic(7));
  198. Change changeMock = mock(Change.class);
  199. // some notifications with some issues on project1
  200. Stream<IssuesChangesNotificationBuilder> project1Notifications = IntStream.range(0, 1 + new Random().nextInt(2))
  201. .mapToObj(j -> new IssuesChangesNotificationBuilder(
  202. randomIssues(t -> t.setProject(projectKey1).setNewResolution(newResolution)).collect(toSet()),
  203. changeMock));
  204. // some notifications with some issues on project2
  205. Stream<IssuesChangesNotificationBuilder> project2Notifications = IntStream.range(0, 1 + new Random().nextInt(2))
  206. .mapToObj(j -> new IssuesChangesNotificationBuilder(
  207. randomIssues(t -> t.setProject(projectKey2).setNewResolution(newResolution)).collect(toSet()),
  208. changeMock));
  209. // some notifications with some issues on project3 and project 4
  210. Stream<IssuesChangesNotificationBuilder> project3And4Notifications = IntStream.range(0, 1 + new Random().nextInt(2))
  211. .mapToObj(j -> new IssuesChangesNotificationBuilder(
  212. Stream.concat(
  213. randomIssues(t -> t.setProject(projectKey3).setNewResolution(newResolution)),
  214. randomIssues(t -> t.setProject(projectKey4).setNewResolution(newResolution)))
  215. .collect(toSet()),
  216. changeMock));
  217. when(emailNotificationChannel.isActivated()).thenReturn(true);
  218. Set<IssuesChangesNotification> notifications = Stream.of(project1Notifications, project2Notifications, project3And4Notifications)
  219. .flatMap(t -> t)
  220. .map(serializer::serialize)
  221. .collect(toSet());
  222. int deliver = underTest.deliver(notifications);
  223. assertThat(deliver).isZero();
  224. verify(notificationManager).findSubscribedEmailRecipients(DO_NOT_FIX_ISSUE_CHANGE_DISPATCHER_KEY, projectKey1.getKey(), ALL_MUST_HAVE_ROLE_USER);
  225. verify(notificationManager).findSubscribedEmailRecipients(DO_NOT_FIX_ISSUE_CHANGE_DISPATCHER_KEY, projectKey2.getKey(), ALL_MUST_HAVE_ROLE_USER);
  226. verify(notificationManager).findSubscribedEmailRecipients(DO_NOT_FIX_ISSUE_CHANGE_DISPATCHER_KEY, projectKey3.getKey(), ALL_MUST_HAVE_ROLE_USER);
  227. verify(notificationManager).findSubscribedEmailRecipients(DO_NOT_FIX_ISSUE_CHANGE_DISPATCHER_KEY, projectKey4.getKey(), ALL_MUST_HAVE_ROLE_USER);
  228. verifyNoMoreInteractions(notificationManager);
  229. verify(emailNotificationChannel).isActivated();
  230. verifyNoMoreInteractions(emailNotificationChannel);
  231. verifyZeroInteractions(changeMock);
  232. }
  233. @Test
  234. @UseDataProvider("FPorWontFixResolution")
  235. public void deliver_does_not_send_email_request_for_notifications_a_subscriber_is_the_changeAuthor_of(String newResolution) {
  236. Project project = newProject(randomAlphabetic(5));
  237. User subscriber1 = newUser("subscriber1");
  238. User subscriber2 = newUser("subscriber2");
  239. User subscriber3 = newUser("subscriber3");
  240. User otherChangeAuthor = newUser("otherChangeAuthor");
  241. // subscriber1 is the changeAuthor of some notifications with issues assigned to subscriber1 only
  242. Set<IssuesChangesNotificationBuilder> subscriber1Notifications = IntStream.range(0, 1 + new Random().nextInt(2))
  243. .mapToObj(j -> new IssuesChangesNotificationBuilder(
  244. randomIssues(t -> t.setProject(project).setNewResolution(newResolution).setAssignee(subscriber2)).collect(toSet()),
  245. newUserChange(subscriber1)))
  246. .collect(toSet());
  247. // subscriber1 is the changeAuthor of some notifications with issues assigned to subscriber1 and subscriber2
  248. Set<IssuesChangesNotificationBuilder> subscriber1and2Notifications = IntStream.range(0, 1 + new Random().nextInt(2))
  249. .mapToObj(j -> new IssuesChangesNotificationBuilder(
  250. Stream.concat(
  251. randomIssues(t -> t.setProject(project).setNewResolution(newResolution).setAssignee(subscriber2)),
  252. randomIssues(t -> t.setProject(project).setNewResolution(newResolution).setAssignee(subscriber1)))
  253. .collect(toSet()),
  254. newUserChange(subscriber1)))
  255. .collect(toSet());
  256. // subscriber2 is the changeAuthor of some notifications with issues assigned to subscriber2 only
  257. Set<IssuesChangesNotificationBuilder> subscriber2Notifications = IntStream.range(0, 1 + new Random().nextInt(2))
  258. .mapToObj(j -> new IssuesChangesNotificationBuilder(
  259. randomIssues(t -> t.setProject(project).setNewResolution(newResolution).setAssignee(subscriber2)).collect(toSet()),
  260. newUserChange(subscriber2)))
  261. .collect(toSet());
  262. // subscriber2 is the changeAuthor of some notifications with issues assigned to subscriber2 and subscriber 3
  263. Set<IssuesChangesNotificationBuilder> subscriber2And3Notifications = IntStream.range(0, 1 + new Random().nextInt(2))
  264. .mapToObj(j -> new IssuesChangesNotificationBuilder(
  265. Stream.concat(
  266. randomIssues(t -> t.setProject(project).setNewResolution(newResolution).setAssignee(subscriber2)),
  267. randomIssues(t -> t.setProject(project).setNewResolution(newResolution).setAssignee(subscriber3)))
  268. .collect(toSet()),
  269. newUserChange(subscriber2)))
  270. .collect(toSet());
  271. // subscriber3 is the changeAuthor of no notification
  272. // otherChangeAuthor has some notifications
  273. Set<IssuesChangesNotificationBuilder> otherChangeAuthorNotifications = IntStream.range(0, 1 + new Random().nextInt(2))
  274. .mapToObj(j -> new IssuesChangesNotificationBuilder(randomIssues(t -> t.setProject(project).setNewResolution(newResolution)).collect(toSet()),
  275. newUserChange(otherChangeAuthor)))
  276. .collect(toSet());
  277. when(emailNotificationChannel.isActivated()).thenReturn(true);
  278. Set<String> subscriberLogins = ImmutableSet.of(subscriber1.getLogin(), subscriber2.getLogin(), subscriber3.getLogin());
  279. when(notificationManager.findSubscribedEmailRecipients(DO_NOT_FIX_ISSUE_CHANGE_DISPATCHER_KEY, project.getKey(), ALL_MUST_HAVE_ROLE_USER))
  280. .thenReturn(subscriberLogins.stream().map(FPOrWontFixNotificationHandlerTest::emailRecipientOf).collect(toSet()));
  281. int deliveredCount = new Random().nextInt(200);
  282. when(emailNotificationChannel.deliverAll(anySet()))
  283. .thenReturn(deliveredCount)
  284. .thenThrow(new IllegalStateException("deliver should be called only once"));
  285. Set<IssuesChangesNotification> notifications = Stream.of(
  286. subscriber1Notifications.stream(),
  287. subscriber1and2Notifications.stream(),
  288. subscriber2Notifications.stream(),
  289. subscriber2And3Notifications.stream(),
  290. otherChangeAuthorNotifications.stream())
  291. .flatMap(t -> t)
  292. .map(serializer::serialize)
  293. .collect(toSet());
  294. reset(serializer);
  295. int deliver = underTest.deliver(notifications);
  296. assertThat(deliver).isEqualTo(deliveredCount);
  297. verify(notificationManager).findSubscribedEmailRecipients(DO_NOT_FIX_ISSUE_CHANGE_DISPATCHER_KEY, project.getKey(), ALL_MUST_HAVE_ROLE_USER);
  298. verifyNoMoreInteractions(notificationManager);
  299. verify(emailNotificationChannel).isActivated();
  300. ArgumentCaptor<Set<EmailDeliveryRequest>> captor = ArgumentCaptor.forClass(requestSetType);
  301. verify(emailNotificationChannel).deliverAll(captor.capture());
  302. verifyNoMoreInteractions(emailNotificationChannel);
  303. ListMultimap<String, EmailDeliveryRequest> requestsByRecipientEmail = captor.getValue().stream()
  304. .collect(index(EmailDeliveryRequest::getRecipientEmail));
  305. assertThat(requestsByRecipientEmail.get(emailOf(subscriber1.getLogin())))
  306. .containsOnly(
  307. Stream.of(
  308. subscriber2Notifications.stream()
  309. .map(notif -> newEmailDeliveryRequest(notif, subscriber1, toFpOrWontFix(newResolution))),
  310. subscriber2And3Notifications.stream()
  311. .map(notif -> newEmailDeliveryRequest(notif, subscriber1, toFpOrWontFix(newResolution))),
  312. otherChangeAuthorNotifications.stream()
  313. .map(notif -> newEmailDeliveryRequest(notif, subscriber1, toFpOrWontFix(newResolution))))
  314. .flatMap(t -> t)
  315. .toArray(EmailDeliveryRequest[]::new));
  316. assertThat(requestsByRecipientEmail.get(emailOf(subscriber2.getLogin())))
  317. .containsOnly(
  318. Stream.of(
  319. subscriber1Notifications.stream()
  320. .map(notif -> newEmailDeliveryRequest(notif, subscriber2, toFpOrWontFix(newResolution))),
  321. subscriber1and2Notifications.stream()
  322. .map(notif -> newEmailDeliveryRequest(notif, subscriber2, toFpOrWontFix(newResolution))),
  323. otherChangeAuthorNotifications.stream()
  324. .map(notif -> newEmailDeliveryRequest(notif, subscriber2, toFpOrWontFix(newResolution))))
  325. .flatMap(t -> t)
  326. .toArray(EmailDeliveryRequest[]::new));
  327. assertThat(requestsByRecipientEmail.get(emailOf(subscriber3.getLogin())))
  328. .containsOnly(
  329. Stream.of(
  330. subscriber1Notifications.stream()
  331. .map(notif -> newEmailDeliveryRequest(notif, subscriber3, toFpOrWontFix(newResolution))),
  332. subscriber1and2Notifications.stream()
  333. .map(notif -> newEmailDeliveryRequest(notif, subscriber3, toFpOrWontFix(newResolution))),
  334. subscriber2Notifications.stream()
  335. .map(notif -> newEmailDeliveryRequest(notif, subscriber3, toFpOrWontFix(newResolution))),
  336. subscriber2And3Notifications.stream()
  337. .map(notif -> newEmailDeliveryRequest(notif, subscriber3, toFpOrWontFix(newResolution))),
  338. otherChangeAuthorNotifications.stream()
  339. .map(notif -> newEmailDeliveryRequest(notif, subscriber3, toFpOrWontFix(newResolution))))
  340. .flatMap(t -> t)
  341. .toArray(EmailDeliveryRequest[]::new));
  342. assertThat(requestsByRecipientEmail.get(emailOf(otherChangeAuthor.getLogin())))
  343. .isEmpty();
  344. }
  345. @Test
  346. @UseDataProvider("oneOrMoreProjectCounts")
  347. public void deliver_send_a_separated_email_request_for_FPs_and_Wont_Fix_issues(int projectCount) {
  348. Set<Project> projects = IntStream.range(0, projectCount).mapToObj(i -> newProject("prk_key_" + i)).collect(toSet());
  349. User subscriber1 = newUser("subscriber1");
  350. User changeAuthor = newUser("changeAuthor");
  351. Set<ChangedIssue> fpIssues = projects.stream()
  352. .flatMap(project -> randomIssues(t -> t.setProject(project).setNewResolution(RESOLUTION_FALSE_POSITIVE).setAssignee(subscriber1)))
  353. .collect(toSet());
  354. Set<ChangedIssue> wontFixIssues = projects.stream()
  355. .flatMap(project -> randomIssues(t -> t.setProject(project).setNewResolution(RESOLUTION_WONT_FIX).setAssignee(subscriber1)))
  356. .collect(toSet());
  357. UserChange userChange = newUserChange(changeAuthor);
  358. IssuesChangesNotificationBuilder fpAndWontFixNotifications = new IssuesChangesNotificationBuilder(
  359. Stream.concat(fpIssues.stream(), wontFixIssues.stream()).collect(toSet()),
  360. userChange);
  361. when(emailNotificationChannel.isActivated()).thenReturn(true);
  362. projects.forEach(project -> when(notificationManager.findSubscribedEmailRecipients(DO_NOT_FIX_ISSUE_CHANGE_DISPATCHER_KEY, project.getKey(), ALL_MUST_HAVE_ROLE_USER))
  363. .thenReturn(singleton(emailRecipientOf(subscriber1.getLogin()))));
  364. int deliveredCount = new Random().nextInt(200);
  365. when(emailNotificationChannel.deliverAll(anySet()))
  366. .thenReturn(deliveredCount)
  367. .thenThrow(new IllegalStateException("deliver should be called only once"));
  368. Set<IssuesChangesNotification> notifications = singleton(serializer.serialize(fpAndWontFixNotifications));
  369. reset(serializer);
  370. int deliver = underTest.deliver(notifications);
  371. assertThat(deliver).isEqualTo(deliveredCount);
  372. projects
  373. .forEach(project -> verify(notificationManager).findSubscribedEmailRecipients(DO_NOT_FIX_ISSUE_CHANGE_DISPATCHER_KEY, project.getKey(), ALL_MUST_HAVE_ROLE_USER));
  374. verifyNoMoreInteractions(notificationManager);
  375. verify(emailNotificationChannel).isActivated();
  376. ArgumentCaptor<Set<EmailDeliveryRequest>> captor = ArgumentCaptor.forClass(requestSetType);
  377. verify(emailNotificationChannel).deliverAll(captor.capture());
  378. verifyNoMoreInteractions(emailNotificationChannel);
  379. ListMultimap<String, EmailDeliveryRequest> requestsByRecipientEmail = captor.getValue().stream()
  380. .collect(index(EmailDeliveryRequest::getRecipientEmail));
  381. assertThat(requestsByRecipientEmail.get(emailOf(subscriber1.getLogin())))
  382. .containsOnly(
  383. new EmailDeliveryRequest(emailOf(subscriber1.getLogin()), new FPOrWontFixNotification(
  384. userChange, wontFixIssues, FpOrWontFix.WONT_FIX)),
  385. new EmailDeliveryRequest(emailOf(subscriber1.getLogin()), new FPOrWontFixNotification(
  386. userChange, fpIssues, FpOrWontFix.FP)));
  387. }
  388. @DataProvider
  389. public static Object[][] oneOrMoreProjectCounts() {
  390. return new Object[][] {
  391. {1},
  392. {2 + new Random().nextInt(3)},
  393. };
  394. }
  395. private static EmailDeliveryRequest newEmailDeliveryRequest(IssuesChangesNotificationBuilder notif, User user, FpOrWontFix resolution) {
  396. return new EmailDeliveryRequest(
  397. emailOf(user.getLogin()),
  398. new FPOrWontFixNotification(notif.getChange(), notif.getIssues(), resolution));
  399. }
  400. private static FpOrWontFix toFpOrWontFix(String newResolution) {
  401. if (newResolution.equals(Issue.RESOLUTION_WONT_FIX)) {
  402. return FpOrWontFix.WONT_FIX;
  403. }
  404. if (newResolution.equals(RESOLUTION_FALSE_POSITIVE)) {
  405. return FpOrWontFix.FP;
  406. }
  407. throw new IllegalArgumentException("unsupported resolution " + newResolution);
  408. }
  409. private static long counter = 233_343;
  410. private static UserChange newUserChange(User subscriber1) {
  411. return new UserChange(counter += 100, subscriber1);
  412. }
  413. public User newUser(String subscriber1) {
  414. return new User(subscriber1, subscriber1 + "_login", subscriber1 + "_name");
  415. }
  416. @DataProvider
  417. public static Object[][] FPorWontFixResolution() {
  418. return new Object[][] {
  419. {RESOLUTION_FALSE_POSITIVE},
  420. {Issue.RESOLUTION_WONT_FIX}
  421. };
  422. }
  423. private static Stream<ChangedIssue> randomIssues(Consumer<ChangedIssue.Builder> consumer) {
  424. return IntStream.range(0, 1 + new Random().nextInt(5))
  425. .mapToObj(i -> {
  426. ChangedIssue.Builder builder = new ChangedIssue.Builder("key_" + i)
  427. .setAssignee(new User(randomAlphabetic(3), randomAlphabetic(4), randomAlphabetic(5)))
  428. .setNewStatus(randomAlphabetic(12))
  429. .setNewResolution(randomAlphabetic(13))
  430. .setRule(newRandomNotAHotspotRule(randomAlphabetic(8)))
  431. .setProject(new Project.Builder(randomAlphabetic(9))
  432. .setKey(randomAlphabetic(10))
  433. .setProjectName(randomAlphabetic(11))
  434. .build());
  435. consumer.accept(builder);
  436. return builder.build();
  437. });
  438. }
  439. private static NotificationManager.EmailRecipient emailRecipientOf(String assignee1) {
  440. return new NotificationManager.EmailRecipient(assignee1, emailOf(assignee1));
  441. }
  442. private static String emailOf(String assignee1) {
  443. return assignee1 + "@baffe";
  444. }
  445. }