]> source.dussan.org Git - sonarqube.git/blob
efff8f1b796b93c00e13ed96f3043b1b029d806f
[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.email;
21
22 import com.tngtech.java.junit.dataprovider.DataProvider;
23 import com.tngtech.java.junit.dataprovider.DataProviderRunner;
24 import com.tngtech.java.junit.dataprovider.UseDataProvider;
25 import java.io.IOException;
26 import java.util.Collections;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Random;
30 import java.util.Set;
31 import java.util.stream.IntStream;
32 import java.util.stream.Stream;
33 import javax.mail.MessagingException;
34 import javax.mail.internet.MimeMessage;
35 import org.apache.commons.mail.EmailException;
36 import org.junit.After;
37 import org.junit.Before;
38 import org.junit.Rule;
39 import org.junit.Test;
40 import org.junit.runner.RunWith;
41 import org.slf4j.event.Level;
42 import org.sonar.api.notifications.Notification;
43 import org.sonar.api.platform.Server;
44 import org.sonar.api.testfixtures.log.LogTester;
45 import org.sonar.api.utils.log.LoggerLevel;
46 import org.sonar.server.email.EmailSmtpConfiguration;
47 import org.sonar.server.issue.notification.EmailMessage;
48 import org.sonar.server.issue.notification.EmailTemplate;
49 import org.sonar.server.notification.email.EmailNotificationChannel.EmailDeliveryRequest;
50 import org.subethamail.wiser.Wiser;
51 import org.subethamail.wiser.WiserMessage;
52
53 import static java.util.stream.Collectors.toMap;
54 import static java.util.stream.Collectors.toSet;
55 import static junit.framework.Assert.fail;
56 import static org.apache.commons.lang3.RandomStringUtils.random;
57 import static org.assertj.core.api.Assertions.assertThat;
58 import static org.mockito.Mockito.mock;
59 import static org.mockito.Mockito.verify;
60 import static org.mockito.Mockito.verifyNoInteractions;
61 import static org.mockito.Mockito.verifyNoMoreInteractions;
62 import static org.mockito.Mockito.when;
63
64 @RunWith(DataProviderRunner.class)
65 public class EmailNotificationChannelTest {
66
67   private static final String SUBJECT_PREFIX = "[SONARQUBE]";
68
69   @Rule
70   public LogTester logTester = new LogTester();
71
72   private Wiser smtpServer;
73   private EmailSmtpConfiguration configuration;
74   private Server server;
75   private EmailNotificationChannel underTest;
76
77   @Before
78   public void setUp() {
79     logTester.setLevel(LoggerLevel.DEBUG);
80     smtpServer = new Wiser(0);
81     smtpServer.start();
82
83     configuration = mock(EmailSmtpConfiguration.class);
84     server = mock(Server.class);
85
86     underTest = new EmailNotificationChannel(configuration, server, null, null);
87   }
88
89   @After
90   public void tearDown() {
91     smtpServer.stop();
92   }
93
94   @Test
95   public void isActivated_returns_true_if_smpt_host_is_not_empty() {
96     when(configuration.getSmtpHost()).thenReturn(random(5));
97
98     assertThat(underTest.isActivated()).isTrue();
99   }
100
101   @Test
102   public void isActivated_returns_false_if_smpt_host_is_null() {
103     when(configuration.getSmtpHost()).thenReturn(null);
104
105     assertThat(underTest.isActivated()).isFalse();
106   }
107
108   @Test
109   public void isActivated_returns_false_if_smpt_host_is_empty() {
110     when(configuration.getSmtpHost()).thenReturn("");
111
112     assertThat(underTest.isActivated()).isFalse();
113   }
114
115   @Test
116   public void shouldSendTestEmail() throws Exception {
117     configure();
118     underTest.sendTestEmail("user@nowhere", "Test Message from SonarQube", "This is a test message from SonarQube.");
119
120     List<WiserMessage> messages = smtpServer.getMessages();
121     assertThat(messages).hasSize(1);
122
123     MimeMessage email = messages.get(0).getMimeMessage();
124     assertThat(email.getHeader("Content-Type", null)).isEqualTo("text/plain; charset=UTF-8");
125     assertThat(email.getHeader("From", ",")).isEqualTo("SonarQube from NoWhere <server@nowhere>");
126     assertThat(email.getHeader("To", null)).isEqualTo("<user@nowhere>");
127     assertThat(email.getHeader("Subject", null)).isEqualTo("[SONARQUBE] Test Message from SonarQube");
128     assertThat((String) email.getContent()).startsWith("This is a test message from SonarQube.\r\n\r\nMail sent from: http://nemo.sonarsource.org");
129   }
130
131   @Test
132   public void sendTestEmailShouldSanitizeLog() throws Exception {
133     logTester.setLevel(LoggerLevel.TRACE);
134     configure();
135     underTest.sendTestEmail("user@nowhere", "Test Message from SonarQube", "This is a message \n containing line breaks \r that should be sanitized when logged.");
136
137     assertThat(logTester.logs(Level.TRACE)).isNotEmpty()
138       .contains("Sending email: This is a message _ containing line breaks _ that should be sanitized when logged.__Mail sent from: http://nemo.sonarsource.org");
139
140   }
141
142   @Test
143   public void shouldThrowAnExceptionWhenUnableToSendTestEmail() {
144     configure();
145     smtpServer.stop();
146
147     try {
148       underTest.sendTestEmail("user@nowhere", "Test Message from SonarQube", "This is a test message from SonarQube.");
149       fail();
150     } catch (EmailException e) {
151       // expected
152     }
153   }
154
155   @Test
156   public void shouldNotSendEmailWhenHostnameNotConfigured() {
157     EmailMessage emailMessage = new EmailMessage()
158       .setTo("user@nowhere")
159       .setSubject("Foo")
160       .setPlainTextMessage("Bar");
161     boolean delivered = underTest.deliver(emailMessage);
162     assertThat(smtpServer.getMessages()).isEmpty();
163     assertThat(delivered).isFalse();
164   }
165
166   @Test
167   public void shouldSendThreadedEmail() throws Exception {
168     configure();
169     EmailMessage emailMessage = new EmailMessage()
170       .setMessageId("reviews/view/1")
171       .setFrom("Full Username")
172       .setTo("user@nowhere")
173       .setSubject("Review #3")
174       .setPlainTextMessage("I'll take care of this violation.");
175     boolean delivered = underTest.deliver(emailMessage);
176
177     List<WiserMessage> messages = smtpServer.getMessages();
178     assertThat(messages).hasSize(1);
179
180     MimeMessage email = messages.get(0).getMimeMessage();
181
182     assertThat(email.getHeader("Content-Type", null)).isEqualTo("text/plain; charset=UTF-8");
183
184     assertThat(email.getHeader("In-Reply-To", null)).isEqualTo("<reviews/view/1@nemo.sonarsource.org>");
185     assertThat(email.getHeader("References", null)).isEqualTo("<reviews/view/1@nemo.sonarsource.org>");
186
187     assertThat(email.getHeader("List-ID", null)).isEqualTo("SonarQube <sonar.nemo.sonarsource.org>");
188     assertThat(email.getHeader("List-Archive", null)).isEqualTo("http://nemo.sonarsource.org");
189
190     assertThat(email.getHeader("From", ",")).isEqualTo("\"Full Username (SonarQube from NoWhere)\" <server@nowhere>");
191     assertThat(email.getHeader("To", null)).isEqualTo("<user@nowhere>");
192     assertThat(email.getHeader("Subject", null)).isEqualTo("[SONARQUBE] Review #3");
193     assertThat((String) email.getContent()).startsWith("I'll take care of this violation.");
194     assertThat(delivered).isTrue();
195   }
196
197   @Test
198   public void shouldSendNonThreadedEmail() throws Exception {
199     configure();
200     EmailMessage emailMessage = new EmailMessage()
201       .setTo("user@nowhere")
202       .setSubject("Foo")
203       .setPlainTextMessage("Bar");
204     boolean delivered = underTest.deliver(emailMessage);
205
206     List<WiserMessage> messages = smtpServer.getMessages();
207     assertThat(messages).hasSize(1);
208
209     MimeMessage email = messages.get(0).getMimeMessage();
210
211     assertThat(email.getHeader("Content-Type", null)).isEqualTo("text/plain; charset=UTF-8");
212
213     assertThat(email.getHeader("In-Reply-To", null)).isNull();
214     assertThat(email.getHeader("References", null)).isNull();
215
216     assertThat(email.getHeader("List-ID", null)).isEqualTo("SonarQube <sonar.nemo.sonarsource.org>");
217     assertThat(email.getHeader("List-Archive", null)).isEqualTo("http://nemo.sonarsource.org");
218
219     assertThat(email.getHeader("From", null)).isEqualTo("SonarQube from NoWhere <server@nowhere>");
220     assertThat(email.getHeader("To", null)).isEqualTo("<user@nowhere>");
221     assertThat(email.getHeader("Subject", null)).isEqualTo("[SONARQUBE] Foo");
222     assertThat((String) email.getContent()).startsWith("Bar");
223     assertThat(delivered).isTrue();
224   }
225
226   @Test
227   public void shouldNotThrowAnExceptionWhenUnableToSendEmail() {
228     configure();
229     smtpServer.stop();
230
231     EmailMessage emailMessage = new EmailMessage()
232       .setTo("user@nowhere")
233       .setSubject("Foo")
234       .setPlainTextMessage("Bar");
235     boolean delivered = underTest.deliver(emailMessage);
236
237     assertThat(delivered).isFalse();
238   }
239
240   @Test
241   public void shouldSendTestEmailWithSTARTTLS() {
242     smtpServer.getServer().setEnableTLS(true);
243     smtpServer.getServer().setRequireTLS(true);
244     configure();
245     when(configuration.getSecureConnection()).thenReturn("STARTTLS");
246
247     try {
248       underTest.sendTestEmail("user@nowhere", "Test Message from SonarQube", "This is a test message from SonarQube.");
249       fail("An SSL exception was expected a a proof that STARTTLS is enabled");
250     } catch (EmailException e) {
251       // We don't have a SSL certificate so we are expecting a SSL error
252       assertThat(e.getCause().getMessage()).isEqualTo("Could not convert socket to TLS");
253     }
254   }
255
256   @Test
257   public void deliverAll_has_no_effect_if_set_is_empty() {
258     EmailSmtpConfiguration emailSettings = mock(EmailSmtpConfiguration.class);
259     EmailNotificationChannel emailNotificationChannel = new EmailNotificationChannel(emailSettings, server, null, null);
260
261     int count = emailNotificationChannel.deliverAll(Collections.emptySet());
262
263     assertThat(count).isZero();
264     verifyNoInteractions(emailSettings);
265     assertThat(smtpServer.getMessages()).isEmpty();
266   }
267
268   @Test
269   public void deliverAll_has_no_effect_if_smtp_host_is_null() {
270     EmailSmtpConfiguration emailSettings = mock(EmailSmtpConfiguration.class);
271     when(emailSettings.getSmtpHost()).thenReturn(null);
272     Set<EmailDeliveryRequest> requests = IntStream.range(0, 1 + new Random().nextInt(10))
273       .mapToObj(i -> new EmailDeliveryRequest("foo" + i + "@moo", mock(Notification.class)))
274       .collect(toSet());
275     EmailNotificationChannel emailNotificationChannel = new EmailNotificationChannel(emailSettings, server, null, null);
276
277     int count = emailNotificationChannel.deliverAll(requests);
278
279     assertThat(count).isZero();
280     verify(emailSettings).getSmtpHost();
281     verifyNoMoreInteractions(emailSettings);
282     assertThat(smtpServer.getMessages()).isEmpty();
283   }
284
285   @Test
286   @UseDataProvider("emptyStrings")
287   public void deliverAll_ignores_requests_which_recipient_is_empty(String emptyString) {
288     EmailSmtpConfiguration emailSettings = mock(EmailSmtpConfiguration.class);
289     when(emailSettings.getSmtpHost()).thenReturn(null);
290     Set<EmailDeliveryRequest> requests = IntStream.range(0, 1 + new Random().nextInt(10))
291       .mapToObj(i -> new EmailDeliveryRequest(emptyString, mock(Notification.class)))
292       .collect(toSet());
293     EmailNotificationChannel emailNotificationChannel = new EmailNotificationChannel(emailSettings, server, null, null);
294
295     int count = emailNotificationChannel.deliverAll(requests);
296
297     assertThat(count).isZero();
298     verify(emailSettings).getSmtpHost();
299     verifyNoMoreInteractions(emailSettings);
300     assertThat(smtpServer.getMessages()).isEmpty();
301   }
302
303   @Test
304   public void deliverAll_returns_count_of_request_for_which_at_least_one_formatter_accept_it() throws MessagingException, IOException {
305     String recipientEmail = "foo@donut";
306     configure();
307     Notification notification1 = mock(Notification.class);
308     Notification notification2 = mock(Notification.class);
309     Notification notification3 = mock(Notification.class);
310     EmailTemplate template1 = mock(EmailTemplate.class);
311     EmailTemplate template3 = mock(EmailTemplate.class);
312     EmailMessage emailMessage1 = new EmailMessage().setTo(recipientEmail).setSubject("sub11").setPlainTextMessage("msg11");
313     EmailMessage emailMessage3 = new EmailMessage().setTo(recipientEmail).setSubject("sub3").setPlainTextMessage("msg3");
314     when(template1.format(notification1)).thenReturn(emailMessage1);
315     when(template3.format(notification3)).thenReturn(emailMessage3);
316     Set<EmailDeliveryRequest> requests = Stream.of(notification1, notification2, notification3)
317       .map(t -> new EmailDeliveryRequest(recipientEmail, t))
318       .collect(toSet());
319     EmailNotificationChannel emailNotificationChannel = new EmailNotificationChannel(configuration, server, new EmailTemplate[] {template1, template3}, null);
320
321     int count = emailNotificationChannel.deliverAll(requests);
322
323     assertThat(count).isEqualTo(2);
324     assertThat(smtpServer.getMessages()).hasSize(2);
325     Map<String, MimeMessage> messagesBySubject = smtpServer.getMessages().stream()
326       .map(t -> {
327         try {
328           return t.getMimeMessage();
329         } catch (MessagingException e) {
330           throw new RuntimeException(e);
331         }
332       })
333       .collect(toMap(t -> {
334         try {
335           return t.getSubject();
336         } catch (MessagingException e) {
337           throw new RuntimeException(e);
338         }
339       }, t -> t));
340
341     assertThat((String) messagesBySubject.get(SUBJECT_PREFIX + " " + emailMessage1.getSubject()).getContent())
342       .contains(emailMessage1.getMessage());
343     assertThat((String) messagesBySubject.get(SUBJECT_PREFIX + " " + emailMessage3.getSubject()).getContent())
344       .contains(emailMessage3.getMessage());
345   }
346
347   @Test
348   public void deliverAll_ignores_multiple_templates_by_notification_and_takes_the_first_one_only() throws MessagingException, IOException {
349     String recipientEmail = "foo@donut";
350     configure();
351     Notification notification1 = mock(Notification.class);
352     EmailTemplate template11 = mock(EmailTemplate.class);
353     EmailTemplate template12 = mock(EmailTemplate.class);
354     EmailMessage emailMessage11 = new EmailMessage().setTo(recipientEmail).setSubject("sub11").setPlainTextMessage("msg11");
355     EmailMessage emailMessage12 = new EmailMessage().setTo(recipientEmail).setSubject("sub12").setPlainTextMessage("msg12");
356     when(template11.format(notification1)).thenReturn(emailMessage11);
357     when(template12.format(notification1)).thenReturn(emailMessage12);
358     EmailDeliveryRequest request = new EmailDeliveryRequest(recipientEmail, notification1);
359     EmailNotificationChannel emailNotificationChannel = new EmailNotificationChannel(configuration, server, new EmailTemplate[] {template11, template12}, null);
360
361     int count = emailNotificationChannel.deliverAll(Collections.singleton(request));
362
363     assertThat(count).isOne();
364     assertThat(smtpServer.getMessages()).hasSize(1);
365     assertThat((String) smtpServer.getMessages().iterator().next().getMimeMessage().getContent())
366       .contains(emailMessage11.getMessage());
367   }
368
369   @DataProvider
370   public static Object[][] emptyStrings() {
371     return new Object[][] {
372       {""},
373       {"  "},
374       {" \n "}
375     };
376   }
377
378   private void configure() {
379     when(configuration.getSmtpHost()).thenReturn("localhost");
380     when(configuration.getSmtpPort()).thenReturn(smtpServer.getServer().getPort());
381     when(configuration.getSecureConnection()).thenReturn("NONE");
382     when(configuration.getFrom()).thenReturn("server@nowhere");
383     when(configuration.getFromName()).thenReturn("SonarQube from NoWhere");
384     when(configuration.getPrefix()).thenReturn(SUBJECT_PREFIX);
385     when(server.getPublicRootUrl()).thenReturn("http://nemo.sonarsource.org");
386   }
387
388 }