3 * Copyright (C) 2009-2023 SonarSource SA
4 * mailto:info AT sonarsource DOT com
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.
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.
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.
20 package org.sonar.server.notification;
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;
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.notifications.NotificationChannel;
36 import org.sonar.api.utils.System2;
37 import org.sonar.db.DbClient;
38 import org.sonar.db.DbSession;
39 import org.sonar.db.EmailSubscriberDto;
40 import org.sonar.db.notification.NotificationQueueDao;
41 import org.sonar.db.notification.NotificationQueueDto;
42 import org.sonar.db.permission.AuthorizationDao;
43 import org.sonar.db.property.PropertiesDao;
44 import org.sonar.server.notification.NotificationManager.EmailRecipient;
45 import org.sonar.server.notification.NotificationManager.SubscriberPermissionsOnProject;
47 import static com.google.common.collect.Sets.newHashSet;
48 import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
49 import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
50 import static org.assertj.core.api.Assertions.assertThat;
51 import static org.assertj.core.api.Assertions.assertThatThrownBy;
52 import static org.mockito.ArgumentMatchers.any;
53 import static org.mockito.ArgumentMatchers.anyBoolean;
54 import static org.mockito.ArgumentMatchers.anySet;
55 import static org.mockito.ArgumentMatchers.anyString;
56 import static org.mockito.ArgumentMatchers.eq;
57 import static org.mockito.Mockito.inOrder;
58 import static org.mockito.Mockito.mock;
59 import static org.mockito.Mockito.only;
60 import static org.mockito.Mockito.spy;
61 import static org.mockito.Mockito.verify;
62 import static org.mockito.Mockito.when;
63 import static org.mockito.internal.verification.VerificationModeFactory.times;
64 import static org.sonar.server.notification.NotificationManager.SubscriberPermissionsOnProject.ALL_MUST_HAVE_ROLE_USER;
66 public class DefaultNotificationManagerTest {
69 private DefaultNotificationManager underTest;
71 private PropertiesDao propertiesDao = mock(PropertiesDao.class);
72 private NotificationDispatcher dispatcher = mock(NotificationDispatcher.class);
73 private NotificationChannel emailChannel = mock(NotificationChannel.class);
74 private NotificationChannel twitterChannel = mock(NotificationChannel.class);
75 private NotificationQueueDao notificationQueueDao = mock(NotificationQueueDao.class);
76 private AuthorizationDao authorizationDao = mock(AuthorizationDao.class);
77 private DbClient dbClient = mock(DbClient.class);
78 private DbSession dbSession = mock(DbSession.class);
79 private System2 system2 = mock(System2.class);
83 when(dispatcher.getKey()).thenReturn("NewViolations");
84 when(emailChannel.getKey()).thenReturn("Email");
85 when(twitterChannel.getKey()).thenReturn("Twitter");
86 when(dbClient.openSession(anyBoolean())).thenReturn(dbSession);
87 when(dbClient.propertiesDao()).thenReturn(propertiesDao);
88 when(dbClient.notificationQueueDao()).thenReturn(notificationQueueDao);
89 when(dbClient.authorizationDao()).thenReturn(authorizationDao);
90 when(system2.now()).thenReturn(0L);
92 underTest = new DefaultNotificationManager(new NotificationChannel[] {emailChannel, twitterChannel}, dbClient);
96 public void shouldProvideChannelList() {
97 assertThat(underTest.getChannels()).containsOnly(emailChannel, twitterChannel);
99 underTest = new DefaultNotificationManager(new NotificationChannel[] {}, dbClient);
100 assertThat(underTest.getChannels()).isEmpty();
104 public void shouldPersist() {
105 Notification notification = new Notification("test");
106 underTest.scheduleForSending(notification);
108 verify(notificationQueueDao, only()).insert(any(List.class));
112 public void shouldGetFromQueueAndDelete() {
113 Notification notification = new Notification("test");
114 NotificationQueueDto dto = NotificationQueueDto.toNotificationQueueDto(notification);
115 List<NotificationQueueDto> dtos = Arrays.asList(dto);
116 when(notificationQueueDao.selectOldest(1)).thenReturn(dtos);
118 assertThat(underTest.<Notification>getFromQueue()).isNotNull();
120 InOrder inOrder = inOrder(notificationQueueDao);
121 inOrder.verify(notificationQueueDao).selectOldest(1);
122 inOrder.verify(notificationQueueDao).delete(dtos);
127 public void shouldNotFailWhenUnableToDeserialize() throws Exception {
128 NotificationQueueDto dto1 = mock(NotificationQueueDto.class);
129 when(dto1.toNotification()).thenThrow(new InvalidClassException("Pouet"));
130 List<NotificationQueueDto> dtos = Arrays.asList(dto1);
131 when(notificationQueueDao.selectOldest(1)).thenReturn(dtos);
133 underTest = spy(underTest);
134 assertThat(underTest.<Notification>getFromQueue()).isNull();
135 assertThat(underTest.<Notification>getFromQueue()).isNull();
137 verify(underTest, times(1)).logDeserializationIssue();
141 public void findSubscribedEmailRecipients_fails_with_NPE_if_projectKey_is_null() {
142 String dispatcherKey = randomAlphabetic(12);
144 assertThatThrownBy(() -> underTest.findSubscribedEmailRecipients(dispatcherKey, null, ALL_MUST_HAVE_ROLE_USER))
145 .isInstanceOf(NullPointerException.class)
146 .hasMessage("projectKey is mandatory");
150 public void findSubscribedEmailRecipients_with_logins_fails_with_NPE_if_projectKey_is_null() {
151 String dispatcherKey = randomAlphabetic(12);
153 assertThatThrownBy(() -> underTest.findSubscribedEmailRecipients(dispatcherKey, null, ImmutableSet.of(), ALL_MUST_HAVE_ROLE_USER))
154 .isInstanceOf(NullPointerException.class)
155 .hasMessage("projectKey is mandatory");
159 public void findSubscribedEmailRecipients_with_logins_fails_with_NPE_if_logins_is_null() {
160 String dispatcherKey = randomAlphabetic(12);
161 String projectKey = randomAlphabetic(6);
163 assertThatThrownBy(() -> underTest.findSubscribedEmailRecipients(dispatcherKey, projectKey, null, ALL_MUST_HAVE_ROLE_USER))
164 .isInstanceOf(NullPointerException.class)
165 .hasMessage("logins can't be null");
169 public void findSubscribedEmailRecipients_with_logins_returns_empty_if_login_set_is_empty() {
170 String dispatcherKey = randomAlphabetic(12);
171 String projectKey = randomAlphabetic(6);
173 Set<EmailRecipient> recipients = underTest.findSubscribedEmailRecipients(dispatcherKey, projectKey, ImmutableSet.of(), ALL_MUST_HAVE_ROLE_USER);
175 assertThat(recipients).isEmpty();
179 public void findSubscribedEmailRecipients_returns_empty_if_no_email_recipients_in_project_for_dispatcher_key() {
180 String dispatcherKey = randomAlphabetic(12);
181 String globalPermission = randomAlphanumeric(4);
182 String projectPermission = randomAlphanumeric(5);
183 String projectKey = randomAlphabetic(6);
184 when(propertiesDao.findEmailSubscribersForNotification(dbSession, dispatcherKey, "EmailNotificationChannel", projectKey))
185 .thenReturn(Collections.emptySet());
187 Set<EmailRecipient> emailRecipients = underTest.findSubscribedEmailRecipients(dispatcherKey, projectKey,
188 new SubscriberPermissionsOnProject(globalPermission, projectPermission));
189 assertThat(emailRecipients).isEmpty();
191 verify(authorizationDao, times(0)).keepAuthorizedLoginsOnEntity(any(DbSession.class), anySet(), anyString(), anyString());
195 public void findSubscribedEmailRecipients_with_logins_returns_empty_if_no_email_recipients_in_project_for_dispatcher_key() {
196 String dispatcherKey = randomAlphabetic(12);
197 String globalPermission = randomAlphanumeric(4);
198 String projectPermission = randomAlphanumeric(5);
199 String projectKey = randomAlphabetic(6);
200 Set<String> logins = IntStream.range(0, 1 + new Random().nextInt(10))
201 .mapToObj(i -> "login_" + i)
202 .collect(Collectors.toSet());
203 when(propertiesDao.findEmailSubscribersForNotification(dbSession, dispatcherKey, "EmailNotificationChannel", projectKey, logins))
204 .thenReturn(Collections.emptySet());
206 Set<EmailRecipient> emailRecipients = underTest.findSubscribedEmailRecipients(dispatcherKey, projectKey, logins,
207 new SubscriberPermissionsOnProject(globalPermission, projectPermission));
208 assertThat(emailRecipients).isEmpty();
210 verify(authorizationDao, times(0)).keepAuthorizedLoginsOnEntity(any(DbSession.class), anySet(), anyString(), anyString());
214 public void findSubscribedEmailRecipients_applies_distinct_permission_filtering_global_or_project_subscribers() {
215 String dispatcherKey = randomAlphabetic(12);
216 String globalPermission = randomAlphanumeric(4);
217 String projectPermission = randomAlphanumeric(5);
218 String projectKey = randomAlphabetic(6);
219 when(propertiesDao.findEmailSubscribersForNotification(dbSession, dispatcherKey, "EmailNotificationChannel", projectKey))
221 newHashSet(EmailSubscriberDto.create("user1", false, "user1@foo"), EmailSubscriberDto.create("user3", false, "user3@foo"),
222 EmailSubscriberDto.create("user3", true, "user3@foo")));
223 when(authorizationDao.keepAuthorizedLoginsOnEntity(dbSession, newHashSet("user3", "user4"), projectKey, globalPermission))
224 .thenReturn(newHashSet("user3"));
225 when(authorizationDao.keepAuthorizedLoginsOnEntity(dbSession, newHashSet("user1", "user3"), projectKey, projectPermission))
226 .thenReturn(newHashSet("user1", "user3"));
228 Set<EmailRecipient> emailRecipients = underTest.findSubscribedEmailRecipients(dispatcherKey, projectKey,
229 new SubscriberPermissionsOnProject(globalPermission, projectPermission));
230 assertThat(emailRecipients)
231 .isEqualTo(ImmutableSet.of(new EmailRecipient("user1", "user1@foo"), new EmailRecipient("user3", "user3@foo")));
233 // code is optimized to perform only 2 SQL requests for all channels
234 verify(authorizationDao, times(1)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(globalPermission));
235 verify(authorizationDao, times(1)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(projectPermission));
239 public void findSubscribedEmailRecipients_with_logins_applies_distinct_permission_filtering_global_or_project_subscribers() {
240 String dispatcherKey = randomAlphabetic(12);
241 String globalPermission = randomAlphanumeric(4);
242 String projectPermission = randomAlphanumeric(5);
243 String projectKey = randomAlphabetic(6);
244 Set<String> logins = ImmutableSet.of("user1", "user2", "user3");
245 when(propertiesDao.findEmailSubscribersForNotification(dbSession, dispatcherKey, "EmailNotificationChannel", projectKey, logins))
247 newHashSet(EmailSubscriberDto.create("user1", false, "user1@foo"), EmailSubscriberDto.create("user3", false, "user3@foo"),
248 EmailSubscriberDto.create("user3", true, "user3@foo")));
249 when(authorizationDao.keepAuthorizedLoginsOnEntity(dbSession, newHashSet("user3", "user4"), projectKey, globalPermission))
250 .thenReturn(newHashSet("user3"));
251 when(authorizationDao.keepAuthorizedLoginsOnEntity(dbSession, newHashSet("user1", "user3"), projectKey, projectPermission))
252 .thenReturn(newHashSet("user1", "user3"));
254 Set<EmailRecipient> emailRecipients = underTest.findSubscribedEmailRecipients(dispatcherKey, projectKey, logins,
255 new SubscriberPermissionsOnProject(globalPermission, projectPermission));
256 assertThat(emailRecipients)
257 .isEqualTo(ImmutableSet.of(new EmailRecipient("user1", "user1@foo"), new EmailRecipient("user3", "user3@foo")));
259 // code is optimized to perform only 2 SQL requests for all channels
260 verify(authorizationDao, times(1)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(globalPermission));
261 verify(authorizationDao, times(1)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(projectPermission));
265 public void findSubscribedEmailRecipients_does_not_call_db_for_project_permission_filtering_if_there_is_no_project_subscriber() {
266 String dispatcherKey = randomAlphabetic(12);
267 String globalPermission = randomAlphanumeric(4);
268 String projectPermission = randomAlphanumeric(5);
269 String projectKey = randomAlphabetic(6);
270 Set<EmailSubscriberDto> subscribers = IntStream.range(0, 1 + new Random().nextInt(10))
271 .mapToObj(i -> EmailSubscriberDto.create("user" + i, true, "user" + i + "@sonarsource.com"))
272 .collect(Collectors.toSet());
273 Set<String> logins = subscribers.stream().map(EmailSubscriberDto::getLogin).collect(Collectors.toSet());
274 when(propertiesDao.findEmailSubscribersForNotification(dbSession, dispatcherKey, "EmailNotificationChannel", projectKey))
275 .thenReturn(subscribers);
276 when(authorizationDao.keepAuthorizedLoginsOnEntity(dbSession, logins, projectKey, globalPermission))
279 Set<EmailRecipient> emailRecipients = underTest.findSubscribedEmailRecipients(dispatcherKey, projectKey,
280 new SubscriberPermissionsOnProject(globalPermission, projectPermission));
281 Set<EmailRecipient> expected = subscribers.stream().map(i -> new EmailRecipient(i.getLogin(), i.getEmail())).collect(Collectors.toSet());
282 assertThat(emailRecipients)
283 .isEqualTo(expected);
285 verify(authorizationDao, times(1)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(globalPermission));
286 verify(authorizationDao, times(0)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(projectPermission));
290 public void findSubscribedEmailRecipients_with_logins_does_not_call_db_for_project_permission_filtering_if_there_is_no_project_subscriber() {
291 String dispatcherKey = randomAlphabetic(12);
292 String globalPermission = randomAlphanumeric(4);
293 String projectPermission = randomAlphanumeric(5);
294 String projectKey = randomAlphabetic(6);
295 Set<EmailSubscriberDto> subscribers = IntStream.range(0, 1 + new Random().nextInt(10))
296 .mapToObj(i -> EmailSubscriberDto.create("user" + i, true, "user" + i + "@sonarsource.com"))
297 .collect(Collectors.toSet());
298 Set<String> logins = subscribers.stream().map(EmailSubscriberDto::getLogin).collect(Collectors.toSet());
299 when(propertiesDao.findEmailSubscribersForNotification(dbSession, dispatcherKey, "EmailNotificationChannel", projectKey, logins))
300 .thenReturn(subscribers);
301 when(authorizationDao.keepAuthorizedLoginsOnEntity(dbSession, logins, projectKey, globalPermission))
304 Set<EmailRecipient> emailRecipients = underTest.findSubscribedEmailRecipients(dispatcherKey, projectKey, logins,
305 new SubscriberPermissionsOnProject(globalPermission, projectPermission));
306 Set<EmailRecipient> expected = subscribers.stream().map(i -> new EmailRecipient(i.getLogin(), i.getEmail())).collect(Collectors.toSet());
307 assertThat(emailRecipients)
308 .isEqualTo(expected);
310 verify(authorizationDao, times(1)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(globalPermission));
311 verify(authorizationDao, times(0)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(projectPermission));
315 public void findSubscribedEmailRecipients_does_not_call_DB_for_project_permission_filtering_if_there_is_no_global_subscriber() {
316 String dispatcherKey = randomAlphabetic(12);
317 String globalPermission = randomAlphanumeric(4);
318 String projectPermission = randomAlphanumeric(5);
319 String projectKey = randomAlphabetic(6);
320 Set<EmailSubscriberDto> subscribers = IntStream.range(0, 1 + new Random().nextInt(10))
321 .mapToObj(i -> EmailSubscriberDto.create("user" + i, false, "user" + i + "@sonarsource.com"))
322 .collect(Collectors.toSet());
323 Set<String> logins = subscribers.stream().map(EmailSubscriberDto::getLogin).collect(Collectors.toSet());
324 when(propertiesDao.findEmailSubscribersForNotification(dbSession, dispatcherKey, "EmailNotificationChannel", projectKey))
325 .thenReturn(subscribers);
326 when(authorizationDao.keepAuthorizedLoginsOnEntity(dbSession, logins, projectKey, projectPermission))
329 Set<EmailRecipient> emailRecipients = underTest.findSubscribedEmailRecipients(dispatcherKey, projectKey,
330 new SubscriberPermissionsOnProject(globalPermission, projectPermission));
331 Set<EmailRecipient> expected = subscribers.stream().map(i -> new EmailRecipient(i.getLogin(), i.getEmail())).collect(Collectors.toSet());
332 assertThat(emailRecipients)
333 .isEqualTo(expected);
335 verify(authorizationDao, times(0)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(globalPermission));
336 verify(authorizationDao, times(1)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(projectPermission));
340 public void findSubscribedEmailRecipients_with_logins_does_not_call_DB_for_project_permission_filtering_if_there_is_no_global_subscriber() {
341 String dispatcherKey = randomAlphabetic(12);
342 String globalPermission = randomAlphanumeric(4);
343 String projectPermission = randomAlphanumeric(5);
344 String projectKey = randomAlphabetic(6);
345 Set<EmailSubscriberDto> subscribers = IntStream.range(0, 1 + new Random().nextInt(10))
346 .mapToObj(i -> EmailSubscriberDto.create("user" + i, false, "user" + i + "@sonarsource.com"))
347 .collect(Collectors.toSet());
348 Set<String> logins = subscribers.stream().map(EmailSubscriberDto::getLogin).collect(Collectors.toSet());
349 when(propertiesDao.findEmailSubscribersForNotification(dbSession, dispatcherKey, "EmailNotificationChannel", projectKey, logins))
350 .thenReturn(subscribers);
351 when(authorizationDao.keepAuthorizedLoginsOnEntity(dbSession, logins, projectKey, projectPermission))
354 Set<EmailRecipient> emailRecipients = underTest.findSubscribedEmailRecipients(dispatcherKey, projectKey, logins,
355 new SubscriberPermissionsOnProject(globalPermission, projectPermission));
356 Set<EmailRecipient> expected = subscribers.stream().map(i -> new EmailRecipient(i.getLogin(), i.getEmail())).collect(Collectors.toSet());
357 assertThat(emailRecipients)
358 .isEqualTo(expected);
360 verify(authorizationDao, times(0)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(globalPermission));
361 verify(authorizationDao, times(1)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(projectPermission));