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