From d37d2abda52ff5bff7619826ac4c1ad70083e263 Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Lesaint?= Date: Mon, 18 Sep 2017 15:26:40 +0200 Subject: [PATCH] SONAR-7024 add IT on Report analysis failure notifications --- .../org/sonarqube/tests/Category6Suite.java | 4 +- .../ce/ReportFailureNotificationTest.java | 264 ++++++++++++++++++ 2 files changed, 267 insertions(+), 1 deletion(-) create mode 100644 tests/src/test/java/org/sonarqube/tests/ce/ReportFailureNotificationTest.java diff --git a/tests/src/test/java/org/sonarqube/tests/Category6Suite.java b/tests/src/test/java/org/sonarqube/tests/Category6Suite.java index e1e4e666dc6..8cebafd58bf 100644 --- a/tests/src/test/java/org/sonarqube/tests/Category6Suite.java +++ b/tests/src/test/java/org/sonarqube/tests/Category6Suite.java @@ -26,6 +26,7 @@ import org.junit.ClassRule; import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.sonarqube.tests.authorisation.PermissionTemplateTest; +import org.sonarqube.tests.ce.ReportFailureNotificationTest; import org.sonarqube.tests.issue.IssueTagsTest; import org.sonarqube.tests.issue.OrganizationIssueAssignTest; import org.sonarqube.tests.issue.OrganizationIssuesPageTest; @@ -81,7 +82,8 @@ import static util.ItUtils.xooPlugin; ProjectProvisioningTest.class, ProjectKeyUpdateTest.class, ProjectSearchTest.class, - PermissionTemplateTest.class + PermissionTemplateTest.class, + ReportFailureNotificationTest.class }) public class Category6Suite { diff --git a/tests/src/test/java/org/sonarqube/tests/ce/ReportFailureNotificationTest.java b/tests/src/test/java/org/sonarqube/tests/ce/ReportFailureNotificationTest.java new file mode 100644 index 00000000000..877b1334b0a --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/ce/ReportFailureNotificationTest.java @@ -0,0 +1,264 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonarqube.tests.ce; + +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.build.SonarScanner; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javax.annotation.Nullable; +import javax.mail.Address; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonarqube.tests.Category6Suite; +import org.sonarqube.tests.Tester; +import org.sonarqube.ws.WsProjects; +import org.sonarqube.ws.WsUsers; +import org.sonarqube.ws.client.PostRequest; +import org.sonarqube.ws.client.WsClient; +import org.subethamail.wiser.Wiser; +import org.subethamail.wiser.WiserMessage; + +import static java.lang.String.valueOf; +import static org.assertj.core.api.Assertions.assertThat; +import static util.ItUtils.newUserWsClient; +import static util.ItUtils.projectDir; + +public class ReportFailureNotificationTest { + @ClassRule + public static final Orchestrator orchestrator = Category6Suite.ORCHESTRATOR; + + @Rule + public Tester tester = new Tester(orchestrator).disableOrganizations(); + + private static Wiser smtpServer; + + @BeforeClass + public static void before() throws Exception { + smtpServer = new Wiser(0); + smtpServer.start(); + System.out.println("SMTP Server port: " + smtpServer.getServer().getPort()); + } + + @AfterClass + public static void stop() { + if (smtpServer != null) { + smtpServer.stop(); + } + } + + @Before + public void prepare() throws Exception { + tester.settings().setGlobalSettings( + "email.smtp_host.secured", smtpServer.getServer().getHostName(), + "email.smtp_port.secured", valueOf(smtpServer.getServer().getPort())); + + smtpServer.getMessages().clear(); + + tester.elasticsearch().unlockWrites("components"); + } + + @After + public void restoreElasticSearch() throws Exception { + tester.elasticsearch().unlockWrites("components"); + } + + @Test + public void send_notification_on_report_processing_failures_to_global_and_project_subscribers() throws Exception { + WsUsers.CreateWsResponse.User user1 = tester.users().generate(t -> t.setPassword("user1").setEmail("user1@bar.com")); + WsUsers.CreateWsResponse.User user2 = tester.users().generate(t -> t.setPassword("user2").setEmail("user2@bar.com")); + WsUsers.CreateWsResponse.User user3 = tester.users().generate(t -> t.setPassword("user3").setEmail("user3@bar.com")); + WsProjects.CreateWsResponse.Project project1 = tester.projects().generate(null, t -> t.setName("Project1")); + WsProjects.CreateWsResponse.Project project2 = tester.projects().generate(null, t -> t.setName("Project2")); + WsProjects.CreateWsResponse.Project project3 = tester.projects().generate(null, t -> t.setName("Project3")); + + // analyses successful and no-one subscribed => no email + executeAnalysis(project1); + executeAnalysis(project2); + executeAnalysis(project3); + assertThat(waitForEmails()).isEmpty(); + + // analyses failed and no-one subscribed => no email + tester.elasticsearch().lockWrites("components"); + executeAnalysis(project1); + executeAnalysis(project2); + executeAnalysis(project3); + assertThat(waitForEmails()).isEmpty(); + + // Add notifications to users: user1 globally, user2 for project1, user3 for project2 + subscribeToReportFailures(user1, "user1", null); + subscribeToReportFailures(user2, "user2", project1); + subscribeToReportFailures(user3, "user3", project2); + + // analysis failed and 1 global subscriber + 1 project subscriber => 2 emails + executeAnalysis(project1); + List messages = waitForEmails(); + assertThat(messages.stream().flatMap(toToRecipients()).collect(Collectors.toSet())) + .containsOnly("user1@bar.com", "user2@bar.com"); + assertSubjectAndContent(project1, messages); + // analysis failed and 1 global subscriber + 1 project subscriber => 2 emails + executeAnalysis(project2); + messages = waitForEmails(); + assertThat(messages.stream().flatMap(toToRecipients()).collect(Collectors.toSet())) + .containsOnly("user1@bar.com", "user3@bar.com"); + assertSubjectAndContent(project2, messages); + // analysis fails and 1 global subscriber => 1 email + executeAnalysis(project3); + messages = waitForEmails(); + assertThat(messages.stream().flatMap(toToRecipients()).collect(Collectors.toSet())) + .containsOnly("user1@bar.com"); + assertSubjectAndContent(project3, messages); + + // global and project subscribed but analysis is successful => no email + tester.elasticsearch().unlockWrites("components"); + executeAnalysis(project1); + executeAnalysis(project2); + executeAnalysis(project3); + assertThat(waitForEmails()).isEmpty(); + + // Add notifications to users: user1 globally, user2 for project1, user3 for project2 + unsubscribeFromReportFailures(user1, "user1", null); + unsubscribeFromReportFailures(user2, "user2", project1); + unsubscribeFromReportFailures(user3, "user3", project2); + + // analyses fail and no-one subscribed => no email + tester.elasticsearch().lockWrites("components"); + executeAnalysis(project1); + executeAnalysis(project2); + executeAnalysis(project3); + assertThat(waitForEmails()).isEmpty(); + } + + private static void assertSubjectAndContent(WsProjects.CreateWsResponse.Project project, List messages) { + assertThat(messages.stream().map(toSubject()).collect(Collectors.toSet())) + .containsOnly("[SONARQUBE] " + project.getName() + ": Background task in failure"); + Set content = messages.stream().map(toContent()).collect(Collectors.toSet()); + assertThat(content).hasSize(1); + assertThat(content.iterator().next()) + .contains("Project:\t" + project.getName(), + "Background task:\t", + "Submission time:\t", + "Failure time:\t", + "Error message:\tUnrecoverable indexation failures", + "More details at: http://localhost:9000/project/background_tasks?id=" + project.getKey()); + } + + private static Function> toToRecipients() { + return t -> { + try { + return Arrays.stream(t.getRecipients(Message.RecipientType.TO)).map(Address::toString); + } catch (MessagingException e) { + return Stream.of(); + } + }; + } + + private static Function toSubject() { + return t -> { + try { + return t.getSubject(); + } catch (MessagingException e) { + return null; + } + }; + } + + private static Function toContent() { + return t -> { + try { + return (String) t.getContent(); + } catch (IOException | MessagingException e) { + return null; + } + }; + } + + private void subscribeToReportFailures(WsUsers.CreateWsResponse.User user1, String password, @Nullable WsProjects.CreateWsResponse.Project project) { + WsClient wsClient = newUserWsClient(orchestrator, user1.getLogin(), password); + PostRequest request = new PostRequest("api/notifications/add") + .setParam("type", "CeReportTaskFailure") + .setParam("channel", "EmailNotificationChannel"); + if (project != null) { + request.setParam("project", project.getKey()); + } + wsClient.wsConnector().call(request) + .failIfNotSuccessful(); + } + + private void unsubscribeFromReportFailures(WsUsers.CreateWsResponse.User user1, String password, @Nullable WsProjects.CreateWsResponse.Project project) { + WsClient wsClient = newUserWsClient(orchestrator, user1.getLogin(), password); + PostRequest request = new PostRequest("api/notifications/remove") + .setParam("type", "CeReportTaskFailure") + .setParam("channel", "EmailNotificationChannel"); + if (project != null) { + request.setParam("project", project.getKey()); + } + wsClient.wsConnector().call(request) + .failIfNotSuccessful(); + } + + private void executeAnalysis(WsProjects.CreateWsResponse.Project project) { + SonarScanner sonarScanner = SonarScanner.create(projectDir("shared/xoo-sample"), + "sonar.projectKey", project.getKey(), + "sonar.projectName", project.getName()); + orchestrator.executeBuild(sonarScanner); + } + + private static List waitForEmails() throws InterruptedException { + System.out.println("Waiting for new emails..."); + for (int i = 0; i < 10; i++) { + Thread.sleep(1_000); + List res = new ArrayList<>(smtpServer.getMessages()); + // shortcut, we expect at most 2 emails at a time + if (res.size() >= 2) { + smtpServer.getMessages().clear(); + return toMimeMessages(res); + } + } + List res = new ArrayList<>(smtpServer.getMessages()); + smtpServer.getMessages().clear(); + return toMimeMessages(res); + } + + private static List toMimeMessages(List res) { + return res.stream().map(t -> { + try { + return t.getMimeMessage(); + } catch (MessagingException e) { + return null; + } + }).filter(t -> t != null) + .collect(Collectors.toList()); + } +} -- 2.39.5