3 * Copyright (C) 2009-2024 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.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;
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;
65 public class DefaultNotificationManagerTest {
67 private DefaultNotificationManager underTest;
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);
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);
90 underTest = new DefaultNotificationManager(new NotificationChannel[] {emailChannel, twitterChannel}, dbClient);
94 public void shouldProvideChannelList() {
95 assertThat(underTest.getChannels()).containsOnly(emailChannel, twitterChannel);
97 underTest = new DefaultNotificationManager(new NotificationChannel[] {}, dbClient);
98 assertThat(underTest.getChannels()).isEmpty();
102 public void shouldPersist() {
103 Notification notification = new Notification("test");
104 underTest.scheduleForSending(notification);
106 verify(notificationQueueDao, only()).insert(any(List.class));
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);
116 assertThat(underTest.<Notification>getFromQueue()).isNotNull();
118 InOrder inOrder = inOrder(notificationQueueDao);
119 inOrder.verify(notificationQueueDao).selectOldest(1);
120 inOrder.verify(notificationQueueDao).delete(dtos);
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);
131 underTest = spy(underTest);
132 assertThat(underTest.<Notification>getFromQueue()).isNull();
133 assertThat(underTest.<Notification>getFromQueue()).isNull();
135 verify(underTest, times(1)).logDeserializationIssue();
139 public void findSubscribedEmailRecipients_fails_with_NPE_if_projectKey_is_null() {
140 String dispatcherKey = randomAlphabetic(12);
142 assertThatThrownBy(() -> underTest.findSubscribedEmailRecipients(dispatcherKey, null, ALL_MUST_HAVE_ROLE_USER))
143 .isInstanceOf(NullPointerException.class)
144 .hasMessage("projectKey is mandatory");
148 public void findSubscribedEmailRecipients_with_logins_fails_with_NPE_if_projectKey_is_null() {
149 String dispatcherKey = randomAlphabetic(12);
151 assertThatThrownBy(() -> underTest.findSubscribedEmailRecipients(dispatcherKey, null, ImmutableSet.of(), ALL_MUST_HAVE_ROLE_USER))
152 .isInstanceOf(NullPointerException.class)
153 .hasMessage("projectKey is mandatory");
157 public void findSubscribedEmailRecipients_with_logins_fails_with_NPE_if_logins_is_null() {
158 String dispatcherKey = randomAlphabetic(12);
159 String projectKey = randomAlphabetic(6);
161 assertThatThrownBy(() -> underTest.findSubscribedEmailRecipients(dispatcherKey, projectKey, null, ALL_MUST_HAVE_ROLE_USER))
162 .isInstanceOf(NullPointerException.class)
163 .hasMessage("logins can't be null");
167 public void findSubscribedEmailRecipients_with_logins_returns_empty_if_login_set_is_empty() {
168 String dispatcherKey = randomAlphabetic(12);
169 String projectKey = randomAlphabetic(6);
171 Set<EmailRecipient> recipients = underTest.findSubscribedEmailRecipients(dispatcherKey, projectKey, ImmutableSet.of(), ALL_MUST_HAVE_ROLE_USER);
173 assertThat(recipients).isEmpty();
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());
185 Set<EmailRecipient> emailRecipients = underTest.findSubscribedEmailRecipients(dispatcherKey, projectKey,
186 new SubscriberPermissionsOnProject(globalPermission, projectPermission));
187 assertThat(emailRecipients).isEmpty();
189 verify(authorizationDao, times(0)).keepAuthorizedLoginsOnEntity(any(DbSession.class), anySet(), anyString(), anyString());
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());
204 Set<EmailRecipient> emailRecipients = underTest.findSubscribedEmailRecipients(dispatcherKey, projectKey, logins,
205 new SubscriberPermissionsOnProject(globalPermission, projectPermission));
206 assertThat(emailRecipients).isEmpty();
208 verify(authorizationDao, times(0)).keepAuthorizedLoginsOnEntity(any(DbSession.class), anySet(), anyString(), anyString());
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))
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"));
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")));
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));
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))
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"));
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")));
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));
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))
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);
283 verify(authorizationDao, times(1)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(globalPermission));
284 verify(authorizationDao, times(0)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(projectPermission));
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))
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);
308 verify(authorizationDao, times(1)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(globalPermission));
309 verify(authorizationDao, times(0)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(projectPermission));
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))
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);
333 verify(authorizationDao, times(0)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(globalPermission));
334 verify(authorizationDao, times(1)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(projectPermission));
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))
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);
358 verify(authorizationDao, times(0)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(globalPermission));
359 verify(authorizationDao, times(1)).keepAuthorizedLoginsOnEntity(eq(dbSession), anySet(), anyString(), eq(projectPermission));