]> source.dussan.org Git - sonarqube.git/blob
fa5eb7a58c1edb694cf6c5f759f29f63f3ac8293
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2022 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
22 import com.google.common.collect.ImmutableSet;
23 import com.google.common.collect.ListMultimap;
24 import com.tngtech.java.junit.dataprovider.DataProvider;
25 import com.tngtech.java.junit.dataprovider.DataProviderRunner;
26 import com.tngtech.java.junit.dataprovider.UseDataProvider;
27 import java.util.Collections;
28 import java.util.List;
29 import java.util.Random;
30 import java.util.Set;
31 import java.util.stream.Collectors;
32 import java.util.stream.IntStream;
33 import java.util.stream.Stream;
34 import javax.annotation.Nullable;
35 import org.junit.Test;
36 import org.junit.runner.RunWith;
37 import org.mockito.ArgumentCaptor;
38 import org.mockito.Mockito;
39 import org.sonar.core.util.stream.MoreCollectors;
40 import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.AnalysisChange;
41 import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.Change;
42 import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.ChangedIssue;
43 import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.Project;
44 import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.Rule;
45 import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.User;
46 import org.sonar.server.issue.notification.IssuesChangesNotificationBuilder.UserChange;
47 import org.sonar.server.notification.NotificationDispatcherMetadata;
48 import org.sonar.server.notification.NotificationManager;
49 import org.sonar.server.notification.email.EmailNotificationChannel;
50 import org.sonar.server.notification.email.EmailNotificationChannel.EmailDeliveryRequest;
51
52 import static java.util.stream.Collectors.toSet;
53 import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
54 import static org.assertj.core.api.Assertions.assertThat;
55 import static org.mockito.ArgumentMatchers.anySet;
56 import static org.mockito.Mockito.mock;
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.core.util.stream.MoreCollectors.index;
62 import static org.sonar.core.util.stream.MoreCollectors.unorderedIndex;
63 import static org.sonar.server.issue.notification.IssuesChangesNotificationBuilderTesting.newRandomNotAHotspotRule;
64 import static org.sonar.server.notification.NotificationDispatcherMetadata.GLOBAL_NOTIFICATION;
65 import static org.sonar.server.notification.NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION;
66 import static org.sonar.server.notification.NotificationManager.SubscriberPermissionsOnProject.ALL_MUST_HAVE_ROLE_USER;
67
68 @RunWith(DataProviderRunner.class)
69 public class ChangesOnMyIssueNotificationHandlerTest {
70   private static final String CHANGE_ON_MY_ISSUES_DISPATCHER_KEY = "ChangesOnMyIssue";
71   private static final String NO_CHANGE_AUTHOR = null;
72
73   private NotificationManager notificationManager = mock(NotificationManager.class);
74   private EmailNotificationChannel emailNotificationChannel = mock(EmailNotificationChannel.class);
75   private IssuesChangesNotificationSerializer serializer = new IssuesChangesNotificationSerializer();
76   private ChangesOnMyIssueNotificationHandler underTest = new ChangesOnMyIssueNotificationHandler(
77     notificationManager, emailNotificationChannel, serializer);
78
79   private Class<Set<EmailDeliveryRequest>> emailDeliveryRequestSetType = (Class<Set<EmailDeliveryRequest>>) (Object) Set.class;
80   private ArgumentCaptor<Set<EmailDeliveryRequest>> emailDeliveryRequestSetCaptor = ArgumentCaptor.forClass(emailDeliveryRequestSetType);
81
82   @Test
83   public void getMetadata_returns_same_instance_as_static_method() {
84     assertThat(underTest.getMetadata().get()).isSameAs(ChangesOnMyIssueNotificationHandler.newMetadata());
85   }
86
87   @Test
88   public void verify_changeOnMyIssues_notification_dispatcher_key() {
89     NotificationDispatcherMetadata metadata = ChangesOnMyIssueNotificationHandler.newMetadata();
90
91     assertThat(metadata.getDispatcherKey()).isEqualTo(CHANGE_ON_MY_ISSUES_DISPATCHER_KEY);
92   }
93
94   @Test
95   public void changeOnMyIssues_notification_is_enable_at_global_level() {
96     NotificationDispatcherMetadata metadata = ChangesOnMyIssueNotificationHandler.newMetadata();
97
98     assertThat(metadata.getProperty(GLOBAL_NOTIFICATION)).isEqualTo("true");
99   }
100
101   @Test
102   public void changeOnMyIssues_notification_is_enable_at_project_level() {
103     NotificationDispatcherMetadata metadata = ChangesOnMyIssueNotificationHandler.newMetadata();
104
105     assertThat(metadata.getProperty(PER_PROJECT_NOTIFICATION)).isEqualTo("true");
106   }
107
108   @Test
109   public void getNotificationClass_is_IssueChangeNotification() {
110     assertThat(underTest.getNotificationClass()).isEqualTo(IssuesChangesNotification.class);
111   }
112
113   @Test
114   public void deliver_has_no_effect_if_notifications_is_empty() {
115     when(emailNotificationChannel.isActivated()).thenReturn(true);
116     int deliver = underTest.deliver(Collections.emptyList());
117
118     assertThat(deliver).isZero();
119     verifyZeroInteractions(notificationManager, emailNotificationChannel);
120   }
121
122   @Test
123   public void deliver_has_no_effect_if_emailNotificationChannel_is_disabled() {
124     when(emailNotificationChannel.isActivated()).thenReturn(false);
125     Set<IssuesChangesNotification> notifications = IntStream.range(0, 1 + new Random().nextInt(10))
126       .mapToObj(i -> mock(IssuesChangesNotification.class))
127       .collect(toSet());
128
129     int deliver = underTest.deliver(notifications);
130
131     assertThat(deliver).isZero();
132     verifyZeroInteractions(notificationManager);
133     verify(emailNotificationChannel).isActivated();
134     verifyNoMoreInteractions(emailNotificationChannel);
135     notifications.forEach(Mockito::verifyZeroInteractions);
136   }
137
138   @Test
139   public void deliver_has_no_effect_if_no_notification_has_assignee() {
140     when(emailNotificationChannel.isActivated()).thenReturn(true);
141     Set<ChangedIssue> issues = IntStream.range(0, 1 + new Random().nextInt(2))
142       .mapToObj(i -> new ChangedIssue.Builder("issue_key_" + i)
143         .setNewStatus("foo")
144         .setAssignee(null)
145         .setRule(newRule())
146         .setProject(newProject(i + ""))
147         .build())
148       .collect(toSet());
149     IssuesChangesNotificationBuilder builder = new IssuesChangesNotificationBuilder(issues, new UserChange(new Random().nextLong(), new User("user_uuid", "user_login", null)));
150
151     int deliver = underTest.deliver(ImmutableSet.of(serializer.serialize(builder)));
152
153     assertThat(deliver).isZero();
154     verifyZeroInteractions(notificationManager);
155     verify(emailNotificationChannel).isActivated();
156     verifyNoMoreInteractions(emailNotificationChannel);
157   }
158
159   @Test
160   public void deliver_has_no_effect_if_all_issues_are_assigned_to_the_changeAuthor() {
161     when(emailNotificationChannel.isActivated()).thenReturn(true);
162     Set<UserChange> userChanges = IntStream.range(0, 1 + new Random().nextInt(3))
163       .mapToObj(i -> new UserChange(new Random().nextLong(), new User("user_uuid_" + i, "user_login_" + i, null)))
164       .collect(toSet());
165     Set<IssuesChangesNotificationBuilder> notificationBuilders = userChanges.stream()
166       .map(userChange -> {
167         Set<ChangedIssue> issues = IntStream.range(0, 1 + new Random().nextInt(2))
168           .mapToObj(i -> new ChangedIssue.Builder("issue_key_" + i + userChange.getUser().getUuid())
169             .setNewStatus("foo")
170             .setAssignee(userChange.getUser())
171             .setRule(newRule())
172             .setProject(newProject(i + ""))
173             .build())
174           .collect(toSet());
175         return new IssuesChangesNotificationBuilder(issues, userChange);
176       })
177       .collect(toSet());
178     Set<IssuesChangesNotification> notifications = notificationBuilders.stream()
179       .map(t -> serializer.serialize(t))
180       .collect(toSet());
181
182     int deliver = underTest.deliver(notifications);
183
184     assertThat(deliver).isZero();
185     verifyZeroInteractions(notificationManager);
186     verify(emailNotificationChannel).isActivated();
187     verifyNoMoreInteractions(emailNotificationChannel);
188   }
189
190   @Test
191   public void deliver_checks_by_projectKey_if_notifications_have_subscribed_assignee_to_ChangesOnMyIssues_notifications() {
192     when(emailNotificationChannel.isActivated()).thenReturn(true);
193     Project project = newProject();
194     Set<ChangedIssue> issues = IntStream.range(0, 1 + new Random().nextInt(2))
195       .mapToObj(i -> new ChangedIssue.Builder("issue_key_" + i)
196         .setNewStatus("foo")
197         .setAssignee(newUser("assignee_" + i))
198         .setRule(newRule())
199         .setProject(project)
200         .build())
201       .collect(toSet());
202     IssuesChangesNotificationBuilder builder = new IssuesChangesNotificationBuilder(issues, new UserChange(new Random().nextLong(), new User("user_uuid", "user_login", null)));
203
204     int deliver = underTest.deliver(ImmutableSet.of(serializer.serialize(builder)));
205
206     assertThat(deliver).isZero();
207     Set<String> assigneeLogins = issues.stream().map(i -> i.getAssignee().get().getLogin()).collect(toSet());
208     verify(notificationManager).findSubscribedEmailRecipients(CHANGE_ON_MY_ISSUES_DISPATCHER_KEY, project.getKey(), assigneeLogins, ALL_MUST_HAVE_ROLE_USER);
209     verifyNoMoreInteractions(notificationManager);
210     verify(emailNotificationChannel).isActivated();
211     verifyNoMoreInteractions(emailNotificationChannel);
212   }
213
214   @Test
215   public void deliver_checks_by_projectKeys_if_notifications_have_subscribed_assignee_to_ChangesOnMyIssues_notifications() {
216     when(emailNotificationChannel.isActivated()).thenReturn(true);
217     Set<ChangedIssue> issues = IntStream.range(0, 1 + new Random().nextInt(2))
218       .mapToObj(i -> new ChangedIssue.Builder("issue_key_" + i)
219         .setNewStatus("foo")
220         .setAssignee(newUser("" + i))
221         .setRule(newRule())
222         .setProject(newProject(i + ""))
223         .build())
224       .collect(toSet());
225     IssuesChangesNotificationBuilder builder = new IssuesChangesNotificationBuilder(issues, new UserChange(new Random().nextLong(), new User("user_uuid", "user_login", null)));
226
227     int deliver = underTest.deliver(ImmutableSet.of(serializer.serialize(builder)));
228
229     assertThat(deliver).isZero();
230     issues.stream()
231       .collect(MoreCollectors.index(ChangedIssue::getProject))
232       .asMap()
233       .forEach((key, value) -> {
234         String projectKey = key.getKey();
235         Set<String> assigneeLogins = value.stream().map(i -> i.getAssignee().get().getLogin()).collect(toSet());
236         verify(notificationManager).findSubscribedEmailRecipients(CHANGE_ON_MY_ISSUES_DISPATCHER_KEY, projectKey, assigneeLogins, ALL_MUST_HAVE_ROLE_USER);
237       });
238     verifyNoMoreInteractions(notificationManager);
239     verify(emailNotificationChannel).isActivated();
240     verifyNoMoreInteractions(emailNotificationChannel);
241   }
242
243   @Test
244   @UseDataProvider("userOrAnalysisChange")
245   public void deliver_creates_a_notification_per_assignee_with_only_his_issues_on_the_single_project(Change userOrAnalysisChange) {
246     when(emailNotificationChannel.isActivated()).thenReturn(true);
247     Project project = newProject();
248     User assignee1 = newUser("assignee_1");
249     User assignee2 = newUser("assignee_2");
250     Set<ChangedIssue> assignee1Issues = IntStream.range(0, 10)
251       .mapToObj(i -> newChangedIssue("1_issue_key_" + i, assignee1, project))
252       .collect(toSet());
253     Set<ChangedIssue> assignee2Issues = IntStream.range(0, 10)
254       .mapToObj(i -> newChangedIssue("2_issue_key_" + i, assignee2, project))
255       .collect(toSet());
256     Set<IssuesChangesNotification> notifications = Stream.of(
257       // notification with only assignee1 5 notifications
258       new IssuesChangesNotificationBuilder(assignee1Issues.stream().limit(5).collect(toSet()), userOrAnalysisChange),
259       // notification with only assignee2 6 notifications
260       new IssuesChangesNotificationBuilder(assignee2Issues.stream().limit(6).collect(toSet()), userOrAnalysisChange),
261       // notification with 4 assignee1 and 3 assignee2 notifications
262       new IssuesChangesNotificationBuilder(
263         Stream.concat(assignee1Issues.stream().skip(6), assignee2Issues.stream().skip(7)).collect(toSet()),
264         userOrAnalysisChange))
265       .map(t -> serializer.serialize(t))
266       .collect(toSet());
267     when(notificationManager.findSubscribedEmailRecipients(CHANGE_ON_MY_ISSUES_DISPATCHER_KEY, project.getKey(), ImmutableSet.of(assignee1.getLogin(), assignee2.getLogin()),
268       ALL_MUST_HAVE_ROLE_USER))
269         .thenReturn(ImmutableSet.of(emailRecipientOf(assignee1.getLogin()), emailRecipientOf(assignee2.getLogin())));
270     int deliveredCount = new Random().nextInt(100);
271     when(emailNotificationChannel.deliverAll(anySet())).thenReturn(deliveredCount);
272
273     int deliver = underTest.deliver(notifications);
274
275     assertThat(deliver).isEqualTo(deliveredCount);
276     verify(notificationManager).findSubscribedEmailRecipients(CHANGE_ON_MY_ISSUES_DISPATCHER_KEY,
277       project.getKey(), ImmutableSet.of(assignee1.getLogin(), assignee2.getLogin()), ALL_MUST_HAVE_ROLE_USER);
278     verifyNoMoreInteractions(notificationManager);
279     verify(emailNotificationChannel).isActivated();
280     verify(emailNotificationChannel).deliverAll(emailDeliveryRequestSetCaptor.capture());
281     verifyNoMoreInteractions(emailNotificationChannel);
282
283     Set<EmailDeliveryRequest> emailDeliveryRequests = emailDeliveryRequestSetCaptor.getValue();
284     assertThat(emailDeliveryRequests).hasSize(4);
285     ListMultimap<String, EmailDeliveryRequest> emailDeliveryRequestByEmail = emailDeliveryRequests.stream()
286       .collect(index(EmailDeliveryRequest::getRecipientEmail));
287     List<EmailDeliveryRequest> assignee1Requests = emailDeliveryRequestByEmail.get(emailOf(assignee1.getLogin()));
288     assertThat(assignee1Requests)
289       .hasSize(2)
290       .extracting(t -> (ChangesOnMyIssuesNotification) t.getNotification())
291       .extracting(ChangesOnMyIssuesNotification::getChange)
292       .containsOnly(userOrAnalysisChange);
293     assertThat(assignee1Requests)
294       .extracting(t -> (ChangesOnMyIssuesNotification) t.getNotification())
295       .extracting(ChangesOnMyIssuesNotification::getChangedIssues)
296       .containsOnly(
297         assignee1Issues.stream().limit(5).collect(unorderedIndex(t -> project, t -> t)),
298         assignee1Issues.stream().skip(6).collect(unorderedIndex(t -> project, t -> t)));
299
300     List<EmailDeliveryRequest> assignee2Requests = emailDeliveryRequestByEmail.get(emailOf(assignee2.getLogin()));
301     assertThat(assignee2Requests)
302       .hasSize(2)
303       .extracting(t -> (ChangesOnMyIssuesNotification) t.getNotification())
304       .extracting(ChangesOnMyIssuesNotification::getChange)
305       .containsOnly(userOrAnalysisChange);
306     assertThat(assignee2Requests)
307       .extracting(t -> (ChangesOnMyIssuesNotification) t.getNotification())
308       .extracting(ChangesOnMyIssuesNotification::getChangedIssues)
309       .containsOnly(
310         assignee2Issues.stream().limit(6).collect(unorderedIndex(t -> project, t -> t)),
311         assignee2Issues.stream().skip(7).collect(unorderedIndex(t -> project, t -> t)));
312   }
313
314   @Test
315   @UseDataProvider("userOrAnalysisChange")
316   public void deliver_ignores_issues_which_assignee_is_the_changeAuthor(Change userOrAnalysisChange) {
317     when(emailNotificationChannel.isActivated()).thenReturn(true);
318     Project project1 = newProject();
319     Project project2 = newProject();
320     User assignee1 = newUser("assignee_1");
321     User assignee2 = newUser("assignee_2");
322     Set<ChangedIssue> assignee1Issues = IntStream.range(0, 10)
323       .mapToObj(i -> newChangedIssue("1_issue_key_" + i, assignee1, project1))
324       .collect(toSet());
325     Set<ChangedIssue> assignee2Issues = IntStream.range(0, 10)
326       .mapToObj(i -> newChangedIssue("2_issue_key_" + i, assignee2, project2))
327       .collect(toSet());
328
329     UserChange assignee2Change1 = new UserChange(new Random().nextLong(), assignee2);
330     Set<IssuesChangesNotification> notifications = Stream.of(
331       // notification from assignee1 with issues from assignee1 only
332       new IssuesChangesNotificationBuilder(
333         assignee1Issues.stream().limit(4).collect(toSet()),
334         new UserChange(new Random().nextLong(), assignee1)),
335       // notification from assignee2 with issues from assignee1 and assignee2
336       new IssuesChangesNotificationBuilder(
337         Stream.concat(
338           assignee1Issues.stream().skip(4).limit(2),
339           assignee2Issues.stream().limit(4))
340           .collect(toSet()),
341         assignee2Change1),
342       // notification from assignee2 with issues from assignee2 only
343       new IssuesChangesNotificationBuilder(
344         assignee2Issues.stream().skip(4).limit(3).collect(toSet()),
345         new UserChange(new Random().nextLong(), assignee2)),
346       // notification from other change with issues from assignee1 and assignee2)
347       new IssuesChangesNotificationBuilder(
348         Stream.concat(
349           assignee1Issues.stream().skip(6),
350           assignee2Issues.stream().skip(7))
351           .collect(toSet()),
352         userOrAnalysisChange))
353       .map(t -> serializer.serialize(t))
354       .collect(toSet());
355     when(notificationManager.findSubscribedEmailRecipients(
356       CHANGE_ON_MY_ISSUES_DISPATCHER_KEY, project1.getKey(), ImmutableSet.of(assignee1.getLogin()), ALL_MUST_HAVE_ROLE_USER))
357         .thenReturn(ImmutableSet.of(emailRecipientOf(assignee1.getLogin())));
358     when(notificationManager.findSubscribedEmailRecipients(
359       CHANGE_ON_MY_ISSUES_DISPATCHER_KEY, project2.getKey(), ImmutableSet.of(assignee2.getLogin()), ALL_MUST_HAVE_ROLE_USER))
360         .thenReturn(ImmutableSet.of(emailRecipientOf(assignee2.getLogin())));
361     int deliveredCount = new Random().nextInt(100);
362     when(emailNotificationChannel.deliverAll(anySet())).thenReturn(deliveredCount);
363
364     int deliver = underTest.deliver(notifications);
365
366     assertThat(deliver).isEqualTo(deliveredCount);
367     verify(notificationManager).findSubscribedEmailRecipients(CHANGE_ON_MY_ISSUES_DISPATCHER_KEY,
368       project1.getKey(), ImmutableSet.of(assignee1.getLogin()), ALL_MUST_HAVE_ROLE_USER);
369     verify(notificationManager).findSubscribedEmailRecipients(CHANGE_ON_MY_ISSUES_DISPATCHER_KEY,
370       project2.getKey(), ImmutableSet.of(assignee2.getLogin()), ALL_MUST_HAVE_ROLE_USER);
371     verifyNoMoreInteractions(notificationManager);
372     verify(emailNotificationChannel).isActivated();
373     verify(emailNotificationChannel).deliverAll(emailDeliveryRequestSetCaptor.capture());
374     verifyNoMoreInteractions(emailNotificationChannel);
375
376     Set<EmailDeliveryRequest> emailDeliveryRequests = emailDeliveryRequestSetCaptor.getValue();
377     assertThat(emailDeliveryRequests).hasSize(3);
378     ListMultimap<String, EmailDeliveryRequest> emailDeliveryRequestByEmail = emailDeliveryRequests.stream()
379       .collect(index(EmailDeliveryRequest::getRecipientEmail));
380     List<EmailDeliveryRequest> assignee1Requests = emailDeliveryRequestByEmail.get(emailOf(assignee1.getLogin()));
381     assertThat(assignee1Requests)
382       .hasSize(2)
383       .extracting(t -> (ChangesOnMyIssuesNotification) t.getNotification())
384       .extracting(ChangesOnMyIssuesNotification::getChange)
385       .containsOnly(userOrAnalysisChange, assignee2Change1);
386     assertThat(assignee1Requests)
387       .extracting(t -> (ChangesOnMyIssuesNotification) t.getNotification())
388       .extracting(ChangesOnMyIssuesNotification::getChangedIssues)
389       .containsOnly(
390         assignee1Issues.stream().skip(4).limit(2).collect(unorderedIndex(t -> project1, t -> t)),
391         assignee1Issues.stream().skip(6).collect(unorderedIndex(t -> project1, t -> t)));
392
393     List<EmailDeliveryRequest> assignee2Requests = emailDeliveryRequestByEmail.get(emailOf(assignee2.getLogin()));
394     assertThat(assignee2Requests)
395       .hasSize(1)
396       .extracting(t -> (ChangesOnMyIssuesNotification) t.getNotification())
397       .extracting(ChangesOnMyIssuesNotification::getChange)
398       .containsOnly(userOrAnalysisChange);
399     assertThat(assignee2Requests)
400       .extracting(t -> (ChangesOnMyIssuesNotification) t.getNotification())
401       .extracting(ChangesOnMyIssuesNotification::getChangedIssues)
402       .containsOnly(assignee2Issues.stream().skip(7).collect(unorderedIndex(t -> project2, t -> t)));
403   }
404
405   @DataProvider
406   public static Object[][] userOrAnalysisChange() {
407     User changeAuthor = new User(randomAlphabetic(12), randomAlphabetic(10), randomAlphabetic(11));
408     return new Object[][] {
409       {new AnalysisChange(new Random().nextLong())},
410       {new UserChange(new Random().nextLong(), changeAuthor)},
411     };
412   }
413
414   private static Project newProject() {
415     String base = randomAlphabetic(6);
416     return newProject(base);
417   }
418
419   private static Project newProject(String base) {
420     return new Project.Builder("prj_uuid_" + base)
421       .setKey("prj_key_" + base)
422       .setProjectName("prj_name_" + base)
423       .build();
424   }
425
426   private static User newUser(String name) {
427     return new User(name + "_uuid", name + "login", name);
428   }
429
430   private static ChangedIssue newChangedIssue(String key, User assignee1, Project project) {
431     return new ChangedIssue.Builder(key)
432       .setNewStatus("foo")
433       .setAssignee(assignee1)
434       .setRule(newRule())
435       .setProject(project)
436       .build();
437   }
438
439   private static Rule newRule() {
440     return newRandomNotAHotspotRule(randomAlphabetic(5));
441   }
442
443   private static Set<IssuesChangesNotification> randomSetOfNotifications(@Nullable String projectKey, @Nullable String assignee, @Nullable String changeAuthor) {
444     return IntStream.range(0, 1 + new Random().nextInt(5))
445       .mapToObj(i -> newNotification(projectKey, assignee, changeAuthor))
446       .collect(Collectors.toSet());
447   }
448
449   private static IssuesChangesNotification newNotification(@Nullable String projectKey, @Nullable String assignee, @Nullable String changeAuthor) {
450     return mock(IssuesChangesNotification.class);
451   }
452
453   private static NotificationManager.EmailRecipient emailRecipientOf(String login) {
454     return new NotificationManager.EmailRecipient(login, emailOf(login));
455   }
456
457   private static String emailOf(String login) {
458     return login + "@plouf";
459   }
460
461 }