]> source.dussan.org Git - sonarqube.git/blob
9a2b0d77947e8fcefb29250a5d024e442ccfc186
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2024 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.notification;
21
22 import com.google.common.collect.ImmutableSet;
23 import java.io.InvalidClassException;
24 import java.util.Arrays;
25 import java.util.Collections;
26 import java.util.List;
27 import java.util.Random;
28 import java.util.Set;
29 import java.util.stream.Collectors;
30 import java.util.stream.IntStream;
31 import org.junit.Before;
32 import org.junit.Test;
33 import org.mockito.InOrder;
34 import org.sonar.api.notifications.Notification;
35 import org.sonar.api.utils.System2;
36 import org.sonar.db.DbClient;
37 import org.sonar.db.DbSession;
38 import org.sonar.db.EmailSubscriberDto;
39 import org.sonar.db.notification.NotificationQueueDao;
40 import org.sonar.db.notification.NotificationQueueDto;
41 import org.sonar.db.permission.AuthorizationDao;
42 import org.sonar.db.property.PropertiesDao;
43 import org.sonar.server.notification.NotificationManager.EmailRecipient;
44 import org.sonar.server.notification.NotificationManager.SubscriberPermissionsOnProject;
45
46 import static com.google.common.collect.Sets.newHashSet;
47 import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic;
48 import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric;
49 import static org.assertj.core.api.Assertions.assertThat;
50 import static org.assertj.core.api.Assertions.assertThatThrownBy;
51 import static org.mockito.ArgumentMatchers.any;
52 import static org.mockito.ArgumentMatchers.anyBoolean;
53 import static org.mockito.ArgumentMatchers.anySet;
54 import static org.mockito.ArgumentMatchers.anyString;
55 import static org.mockito.ArgumentMatchers.eq;
56 import static org.mockito.Mockito.inOrder;
57 import static org.mockito.Mockito.mock;
58 import static org.mockito.Mockito.only;
59 import static org.mockito.Mockito.spy;
60 import static org.mockito.Mockito.verify;
61 import static org.mockito.Mockito.when;
62 import static org.mockito.internal.verification.VerificationModeFactory.times;
63 import static org.sonar.server.notification.NotificationManager.SubscriberPermissionsOnProject.ALL_MUST_HAVE_ROLE_USER;
64
65 public class DefaultNotificationManagerTest {
66
67   private DefaultNotificationManager underTest;
68
69   private PropertiesDao propertiesDao = mock(PropertiesDao.class);
70   private NotificationDispatcher dispatcher = mock(NotificationDispatcher.class);
71   private NotificationChannel emailChannel = mock(NotificationChannel.class);
72   private NotificationChannel twitterChannel = mock(NotificationChannel.class);
73   private NotificationQueueDao notificationQueueDao = mock(NotificationQueueDao.class);
74   private AuthorizationDao authorizationDao = mock(AuthorizationDao.class);
75   private DbClient dbClient = mock(DbClient.class);
76   private DbSession dbSession = mock(DbSession.class);
77   private System2 system2 = mock(System2.class);
78
79   @Before
80   public void setUp() {
81     when(dispatcher.getKey()).thenReturn("NewViolations");
82     when(emailChannel.getKey()).thenReturn("Email");
83     when(twitterChannel.getKey()).thenReturn("Twitter");
84     when(dbClient.openSession(anyBoolean())).thenReturn(dbSession);
85     when(dbClient.propertiesDao()).thenReturn(propertiesDao);
86     when(dbClient.notificationQueueDao()).thenReturn(notificationQueueDao);
87     when(dbClient.authorizationDao()).thenReturn(authorizationDao);
88     when(system2.now()).thenReturn(0L);
89
90     underTest = new DefaultNotificationManager(new NotificationChannel[] {emailChannel, twitterChannel}, dbClient);
91   }
92
93   @Test
94   public void shouldProvideChannelList() {
95     assertThat(underTest.getChannels()).containsOnly(emailChannel, twitterChannel);
96
97     underTest = new DefaultNotificationManager(new NotificationChannel[] {}, dbClient);
98     assertThat(underTest.getChannels()).isEmpty();
99   }
100
101   @Test
102   public void shouldPersist() {
103     Notification notification = new Notification("test");
104     underTest.scheduleForSending(notification);
105
106     verify(notificationQueueDao, only()).insert(any(List.class));
107   }
108
109   @Test
110   public void shouldGetFromQueueAndDelete() {
111     Notification notification = new Notification("test");
112     NotificationQueueDto dto = NotificationQueueDto.toNotificationQueueDto(notification);
113     List<NotificationQueueDto> dtos = Arrays.asList(dto);
114     when(notificationQueueDao.selectOldest(1)).thenReturn(dtos);
115
116     assertThat(underTest.<Notification>getFromQueue()).isNotNull();
117
118     InOrder inOrder = inOrder(notificationQueueDao);
119     inOrder.verify(notificationQueueDao).selectOldest(1);
120     inOrder.verify(notificationQueueDao).delete(dtos);
121   }
122
123   // SONAR-4739
124   @Test
125   public void shouldNotFailWhenUnableToDeserialize() throws Exception {
126     NotificationQueueDto dto1 = mock(NotificationQueueDto.class);
127     when(dto1.toNotification()).thenThrow(new InvalidClassException("Pouet"));
128     List<NotificationQueueDto> dtos = Arrays.asList(dto1);
129     when(notificationQueueDao.selectOldest(1)).thenReturn(dtos);
130
131     underTest = spy(underTest);
132     assertThat(underTest.<Notification>getFromQueue()).isNull();
133     assertThat(underTest.<Notification>getFromQueue()).isNull();
134
135     verify(underTest, times(1)).logDeserializationIssue();
136   }
137
138   @Test
139   public void findSubscribedEmailRecipients_fails_with_NPE_if_projectKey_is_null() {
140     String dispatcherKey = randomAlphabetic(12);
141
142     assertThatThrownBy(() -> underTest.findSubscribedEmailRecipients(dispatcherKey, null, ALL_MUST_HAVE_ROLE_USER))
143       .isInstanceOf(NullPointerException.class)
144       .hasMessage("projectKey is mandatory");
145   }
146
147   @Test
148   public void findSubscribedEmailRecipients_with_logins_fails_with_NPE_if_projectKey_is_null() {
149     String dispatcherKey = randomAlphabetic(12);
150
151     assertThatThrownBy(() -> underTest.findSubscribedEmailRecipients(dispatcherKey, null, ImmutableSet.of(), ALL_MUST_HAVE_ROLE_USER))
152       .isInstanceOf(NullPointerException.class)
153       .hasMessage("projectKey is mandatory");
154   }
155
156   @Test
157   public void findSubscribedEmailRecipients_with_logins_fails_with_NPE_if_logins_is_null() {
158     String dispatcherKey = randomAlphabetic(12);
159     String projectKey = randomAlphabetic(6);
160
161     assertThatThrownBy(() -> underTest.findSubscribedEmailRecipients(dispatcherKey, projectKey, null, ALL_MUST_HAVE_ROLE_USER))
162       .isInstanceOf(NullPointerException.class)
163       .hasMessage("logins can't be null");
164   }
165
166   @Test
167   public void findSubscribedEmailRecipients_with_logins_returns_empty_if_login_set_is_empty() {
168     String dispatcherKey = randomAlphabetic(12);
169     String projectKey = randomAlphabetic(6);
170
171     Set<EmailRecipient> recipients = underTest.findSubscribedEmailRecipients(dispatcherKey, projectKey, ImmutableSet.of(), ALL_MUST_HAVE_ROLE_USER);
172
173     assertThat(recipients).isEmpty();
174   }
175
176   @Test
177   public void findSubscribedEmailRecipients_returns_empty_if_no_email_recipients_in_project_for_dispatcher_key() {
178     String dispatcherKey = randomAlphabetic(12);
179     String globalPermission = randomAlphanumeric(4);
180     String projectPermission = randomAlphanumeric(5);
181     String projectKey = randomAlphabetic(6);
182     when(propertiesDao.findEmailSubscribersForNotification(dbSession, dispatcherKey, "EmailNotificationChannel", projectKey))
183       .thenReturn(Collections.emptySet());
184
185     Set<EmailRecipient> emailRecipients = underTest.findSubscribedEmailRecipients(dispatcherKey, projectKey,
186       new SubscriberPermissionsOnProject(globalPermission, projectPermission));
187     assertThat(emailRecipients).isEmpty();
188
189     verify(authorizationDao, times(0)).keepAuthorizedLoginsOnEntity(any(DbSession.class), anySet(), anyString(), anyString());
190   }
191
192   @Test
193   public void findSubscribedEmailRecipients_with_logins_returns_empty_if_no_email_recipients_in_project_for_dispatcher_key() {
194     String dispatcherKey = randomAlphabetic(12);
195     String globalPermission = randomAlphanumeric(4);
196     String projectPermission = randomAlphanumeric(5);
197     String projectKey = randomAlphabetic(6);
198     Set<String> logins = IntStream.range(0, 1 + new Random().nextInt(10))
199       .mapToObj(i -> "login_" + i)
200       .collect(Collectors.toSet());
201     when(propertiesDao.findEmailSubscribersForNotification(dbSession, dispatcherKey, "EmailNotificationChannel", projectKey, logins))
202       .thenReturn(Collections.emptySet());
203
204     Set<EmailRecipient> emailRecipients = underTest.findSubscribedEmailRecipients(dispatcherKey, projectKey, logins,
205       new SubscriberPermissionsOnProject(globalPermission, projectPermission));
206     assertThat(emailRecipients).isEmpty();
207
208     verify(authorizationDao, times(0)).keepAuthorizedLoginsOnEntity(any(DbSession.class), anySet(), anyString(), anyString());
209   }
210
211   @Test
212   public void findSubscribedEmailRecipients_applies_distinct_permission_filtering_global_or_project_subscribers() {
213     String dispatcherKey = randomAlphabetic(12);
214     String globalPermission = randomAlphanumeric(4);
215     String projectPermission = randomAlphanumeric(5);
216     String projectKey = randomAlphabetic(6);
217     when(propertiesDao.findEmailSubscribersForNotification(dbSession, dispatcherKey, "EmailNotificationChannel", projectKey))
218       .thenReturn(
219         newHashSet(EmailSubscriberDto.create("user1", false, "user1@foo"), EmailSubscriberDto.create("user3", false, "user3@foo"),
220           EmailSubscriberDto.create("user3", true, "user3@foo")));
221     when(authorizationDao.keepAuthorizedLoginsOnEntity(dbSession, newHashSet("user3", "user4"), projectKey, globalPermission))
222       .thenReturn(newHashSet("user3"));
223     when(authorizationDao.keepAuthorizedLoginsOnEntity(dbSession, newHashSet("user1", "user3"), projectKey, projectPermission))
224       .thenReturn(newHashSet("user1", "user3"));
225
226     Set<EmailRecipient> emailRecipients = underTest.findSubscribedEmailRecipients(dispatcherKey, projectKey,
227       new SubscriberPermissionsOnProject(globalPermission, projectPermission));
228     assertThat(emailRecipients)
229       .isEqualTo(ImmutableSet.of(new EmailRecipient("user1", "user1@foo"), new EmailRecipient("user3", "user3@foo")));
230
231     // code is optimized to perform only 2 SQL requests for all channels
232     verify(authorizationDao, times(1)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(globalPermission));
233     verify(authorizationDao, times(1)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(projectPermission));
234   }
235
236   @Test
237   public void findSubscribedEmailRecipients_with_logins_applies_distinct_permission_filtering_global_or_project_subscribers() {
238     String dispatcherKey = randomAlphabetic(12);
239     String globalPermission = randomAlphanumeric(4);
240     String projectPermission = randomAlphanumeric(5);
241     String projectKey = randomAlphabetic(6);
242     Set<String> logins = ImmutableSet.of("user1", "user2", "user3");
243     when(propertiesDao.findEmailSubscribersForNotification(dbSession, dispatcherKey, "EmailNotificationChannel", projectKey, logins))
244       .thenReturn(
245         newHashSet(EmailSubscriberDto.create("user1", false, "user1@foo"), EmailSubscriberDto.create("user3", false, "user3@foo"),
246           EmailSubscriberDto.create("user3", true, "user3@foo")));
247     when(authorizationDao.keepAuthorizedLoginsOnEntity(dbSession, newHashSet("user3", "user4"), projectKey, globalPermission))
248       .thenReturn(newHashSet("user3"));
249     when(authorizationDao.keepAuthorizedLoginsOnEntity(dbSession, newHashSet("user1", "user3"), projectKey, projectPermission))
250       .thenReturn(newHashSet("user1", "user3"));
251
252     Set<EmailRecipient> emailRecipients = underTest.findSubscribedEmailRecipients(dispatcherKey, projectKey, logins,
253       new SubscriberPermissionsOnProject(globalPermission, projectPermission));
254     assertThat(emailRecipients)
255       .isEqualTo(ImmutableSet.of(new EmailRecipient("user1", "user1@foo"), new EmailRecipient("user3", "user3@foo")));
256
257     // code is optimized to perform only 2 SQL requests for all channels
258     verify(authorizationDao, times(1)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(globalPermission));
259     verify(authorizationDao, times(1)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(projectPermission));
260   }
261
262   @Test
263   public void findSubscribedEmailRecipients_does_not_call_db_for_project_permission_filtering_if_there_is_no_project_subscriber() {
264     String dispatcherKey = randomAlphabetic(12);
265     String globalPermission = randomAlphanumeric(4);
266     String projectPermission = randomAlphanumeric(5);
267     String projectKey = randomAlphabetic(6);
268     Set<EmailSubscriberDto> subscribers = IntStream.range(0, 1 + new Random().nextInt(10))
269       .mapToObj(i -> EmailSubscriberDto.create("user" + i, true, "user" + i + "@sonarsource.com"))
270       .collect(Collectors.toSet());
271     Set<String> logins = subscribers.stream().map(EmailSubscriberDto::getLogin).collect(Collectors.toSet());
272     when(propertiesDao.findEmailSubscribersForNotification(dbSession, dispatcherKey, "EmailNotificationChannel", projectKey))
273       .thenReturn(subscribers);
274     when(authorizationDao.keepAuthorizedLoginsOnEntity(dbSession, logins, projectKey, globalPermission))
275       .thenReturn(logins);
276
277     Set<EmailRecipient> emailRecipients = underTest.findSubscribedEmailRecipients(dispatcherKey, projectKey,
278       new SubscriberPermissionsOnProject(globalPermission, projectPermission));
279     Set<EmailRecipient> expected = subscribers.stream().map(i -> new EmailRecipient(i.getLogin(), i.getEmail())).collect(Collectors.toSet());
280     assertThat(emailRecipients)
281       .isEqualTo(expected);
282
283     verify(authorizationDao, times(1)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(globalPermission));
284     verify(authorizationDao, times(0)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(projectPermission));
285   }
286
287   @Test
288   public void findSubscribedEmailRecipients_with_logins_does_not_call_db_for_project_permission_filtering_if_there_is_no_project_subscriber() {
289     String dispatcherKey = randomAlphabetic(12);
290     String globalPermission = randomAlphanumeric(4);
291     String projectPermission = randomAlphanumeric(5);
292     String projectKey = randomAlphabetic(6);
293     Set<EmailSubscriberDto> subscribers = IntStream.range(0, 1 + new Random().nextInt(10))
294       .mapToObj(i -> EmailSubscriberDto.create("user" + i, true, "user" + i + "@sonarsource.com"))
295       .collect(Collectors.toSet());
296     Set<String> logins = subscribers.stream().map(EmailSubscriberDto::getLogin).collect(Collectors.toSet());
297     when(propertiesDao.findEmailSubscribersForNotification(dbSession, dispatcherKey, "EmailNotificationChannel", projectKey, logins))
298       .thenReturn(subscribers);
299     when(authorizationDao.keepAuthorizedLoginsOnEntity(dbSession, logins, projectKey, globalPermission))
300       .thenReturn(logins);
301
302     Set<EmailRecipient> emailRecipients = underTest.findSubscribedEmailRecipients(dispatcherKey, projectKey, logins,
303       new SubscriberPermissionsOnProject(globalPermission, projectPermission));
304     Set<EmailRecipient> expected = subscribers.stream().map(i -> new EmailRecipient(i.getLogin(), i.getEmail())).collect(Collectors.toSet());
305     assertThat(emailRecipients)
306       .isEqualTo(expected);
307
308     verify(authorizationDao, times(1)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(globalPermission));
309     verify(authorizationDao, times(0)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(projectPermission));
310   }
311
312   @Test
313   public void findSubscribedEmailRecipients_does_not_call_DB_for_project_permission_filtering_if_there_is_no_global_subscriber() {
314     String dispatcherKey = randomAlphabetic(12);
315     String globalPermission = randomAlphanumeric(4);
316     String projectPermission = randomAlphanumeric(5);
317     String projectKey = randomAlphabetic(6);
318     Set<EmailSubscriberDto> subscribers = IntStream.range(0, 1 + new Random().nextInt(10))
319       .mapToObj(i -> EmailSubscriberDto.create("user" + i, false, "user" + i + "@sonarsource.com"))
320       .collect(Collectors.toSet());
321     Set<String> logins = subscribers.stream().map(EmailSubscriberDto::getLogin).collect(Collectors.toSet());
322     when(propertiesDao.findEmailSubscribersForNotification(dbSession, dispatcherKey, "EmailNotificationChannel", projectKey))
323       .thenReturn(subscribers);
324     when(authorizationDao.keepAuthorizedLoginsOnEntity(dbSession, logins, projectKey, projectPermission))
325       .thenReturn(logins);
326
327     Set<EmailRecipient> emailRecipients = underTest.findSubscribedEmailRecipients(dispatcherKey, projectKey,
328       new SubscriberPermissionsOnProject(globalPermission, projectPermission));
329     Set<EmailRecipient> expected = subscribers.stream().map(i -> new EmailRecipient(i.getLogin(), i.getEmail())).collect(Collectors.toSet());
330     assertThat(emailRecipients)
331       .isEqualTo(expected);
332
333     verify(authorizationDao, times(0)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(globalPermission));
334     verify(authorizationDao, times(1)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(projectPermission));
335   }
336
337   @Test
338   public void findSubscribedEmailRecipients_with_logins_does_not_call_DB_for_project_permission_filtering_if_there_is_no_global_subscriber() {
339     String dispatcherKey = randomAlphabetic(12);
340     String globalPermission = randomAlphanumeric(4);
341     String projectPermission = randomAlphanumeric(5);
342     String projectKey = randomAlphabetic(6);
343     Set<EmailSubscriberDto> subscribers = IntStream.range(0, 1 + new Random().nextInt(10))
344       .mapToObj(i -> EmailSubscriberDto.create("user" + i, false, "user" + i + "@sonarsource.com"))
345       .collect(Collectors.toSet());
346     Set<String> logins = subscribers.stream().map(EmailSubscriberDto::getLogin).collect(Collectors.toSet());
347     when(propertiesDao.findEmailSubscribersForNotification(dbSession, dispatcherKey, "EmailNotificationChannel", projectKey, logins))
348       .thenReturn(subscribers);
349     when(authorizationDao.keepAuthorizedLoginsOnEntity(dbSession, logins, projectKey, projectPermission))
350       .thenReturn(logins);
351
352     Set<EmailRecipient> emailRecipients = underTest.findSubscribedEmailRecipients(dispatcherKey, projectKey, logins,
353       new SubscriberPermissionsOnProject(globalPermission, projectPermission));
354     Set<EmailRecipient> expected = subscribers.stream().map(i -> new EmailRecipient(i.getLogin(), i.getEmail())).collect(Collectors.toSet());
355     assertThat(emailRecipients)
356       .isEqualTo(expected);
357
358     verify(authorizationDao, times(0)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(globalPermission));
359     verify(authorizationDao, times(1)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(projectPermission));
360   }
361 }