]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-4824 Add ITs for notifications
authorEric Hartmann <hartmann.eric@gmail.com>
Wed, 20 Sep 2017 15:58:56 +0000 (17:58 +0200)
committerEric Hartmann <hartmann.eric@gmail.Com>
Mon, 2 Oct 2017 11:03:35 +0000 (13:03 +0200)
tests/src/test/java/org/sonarqube/tests/Category2Suite.java
tests/src/test/java/org/sonarqube/tests/Category6Suite.java
tests/src/test/java/org/sonarqube/tests/issue/AbstractIssueTest.java
tests/src/test/java/org/sonarqube/tests/issue/IssueNotificationsTest.java

index c84781c885f434eedc689dde1b190b9733495cc3..ab558497243fc42e1814d5ff34a5d44805f45f79 100644 (file)
@@ -36,7 +36,6 @@ import org.sonarqube.tests.issue.IssueFilterExtensionTest;
 import org.sonarqube.tests.issue.IssueFilterOnCommonRulesTest;
 import org.sonarqube.tests.issue.IssueFilterTest;
 import org.sonarqube.tests.issue.IssueMeasureTest;
-import org.sonarqube.tests.issue.IssueNotificationsTest;
 import org.sonarqube.tests.issue.IssuePurgeTest;
 import org.sonarqube.tests.issue.IssueSearchTest;
 import org.sonarqube.tests.issue.IssueTrackingTest;
@@ -81,7 +80,6 @@ import static util.ItUtils.xooPlugin;
   IssueFilterTest.class,
   IssueFilterExtensionTest.class,
   IssueMeasureTest.class,
-  IssueNotificationsTest.class,
   IssuePurgeTest.class,
   IssueSearchTest.class,
   IssueTrackingTest.class,
@@ -115,6 +113,8 @@ public class Category2Suite {
     // 1 second. Required for notification test.
     .setServerProperty("sonar.notifications.delay", "1")
 
+    .setServerProperty("organization.enabled", "true")
+
     // reduce memory for Elasticsearch to 128M
     .setServerProperty("sonar.search.javaOpts", "-Xms128m -Xmx128m")
 
index 8cebafd58bffe3dbcc7bc105ba7931d3e64e3cd3..660ff241cd1aecf0a31a5c72856b635d22107a36 100644 (file)
@@ -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.issue.IssueNotificationsTest;
 import org.sonarqube.tests.ce.ReportFailureNotificationTest;
 import org.sonarqube.tests.issue.IssueTagsTest;
 import org.sonarqube.tests.issue.OrganizationIssueAssignTest;
@@ -83,7 +84,8 @@ import static util.ItUtils.xooPlugin;
   ProjectKeyUpdateTest.class,
   ProjectSearchTest.class,
   PermissionTemplateTest.class,
-  ReportFailureNotificationTest.class
+  ReportFailureNotificationTest.class,
+  IssueNotificationsTest.class
 })
 public class Category6Suite {
 
@@ -96,6 +98,7 @@ public class Category6Suite {
     .setServerProperty("sonar.search.httpPort", "" + SEARCH_HTTP_PORT)
     .setServerProperty("sonar.search.recovery.delayInMs", "1000")
     .setServerProperty("sonar.search.recovery.minAgeInMs", "3000")
+    .setServerProperty("sonar.notifications.delay", "1")
 
     .addPlugin(xooPlugin())
     .addPlugin(pluginArtifact("base-auth-plugin"))
index 4f85822c7d64d82ba3834204d81324d4e4938d38..fe452745f1aec795bb9edba44659aa5f9571f7bd 100644 (file)
 package org.sonarqube.tests.issue;
 
 import com.sonar.orchestrator.Orchestrator;
-import org.sonarqube.tests.Category2Suite;
 import java.util.List;
 import org.junit.ClassRule;
 import org.sonar.wsclient.issue.Issue;
 import org.sonar.wsclient.issue.IssueClient;
 import org.sonar.wsclient.issue.IssueQuery;
 import org.sonar.wsclient.issue.Issues;
+import org.sonarqube.tests.Category2Suite;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
index a8ffdb9c0ddc90c712819026074dde996b67b9e4..477f3ab48d3d2f13d0da0b508cb8f7156cbdb527 100644 (file)
  */
 package org.sonarqube.tests.issue;
 
-import java.util.Iterator;
+import com.google.common.collect.ObjectArrays;
+import com.sonar.orchestrator.Orchestrator;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.mail.MessagingException;
 import javax.mail.internet.MimeMessage;
 import org.apache.commons.lang.RandomStringUtils;
+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.sonar.wsclient.issue.Issue;
-import org.sonar.wsclient.issue.IssueClient;
-import org.sonar.wsclient.issue.IssueQuery;
-import org.sonar.wsclient.issue.Issues;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.sonarqube.tests.Category6Suite;
+import org.sonarqube.tests.Tester;
+import org.sonarqube.ws.Issues.Issue;
+import org.sonarqube.ws.Issues.SearchWsResponse;
+import org.sonarqube.ws.Organizations.Organization;
+import org.sonarqube.ws.QualityProfiles;
+import org.sonarqube.ws.WsProjects.CreateWsResponse.Project;
+import org.sonarqube.ws.WsUsers;
+import org.sonarqube.ws.WsUsers.CreateWsResponse.User;
 import org.sonarqube.ws.client.PostRequest;
 import org.sonarqube.ws.client.WsClient;
+import org.sonarqube.ws.client.issue.AssignRequest;
 import org.sonarqube.ws.client.issue.BulkChangeRequest;
-import org.sonarqube.ws.client.issue.IssuesService;
+import org.sonarqube.ws.client.issue.SearchWsRequest;
+import org.sonarqube.ws.client.permission.AddUserWsRequest;
 import org.subethamail.wiser.Wiser;
 import org.subethamail.wiser.WiserMessage;
-import util.ItUtils;
-import util.user.UserRule;
 
+import static java.lang.String.format;
 import static java.util.Collections.singletonList;
 import static org.assertj.core.api.Assertions.assertThat;
-import static util.ItUtils.newAdminWsClient;
-import static util.ItUtils.newUserWsClient;
-import static util.ItUtils.resetEmailSettings;
+import static org.assertj.core.api.Fail.fail;
 import static util.ItUtils.runProjectAnalysis;
-import static util.ItUtils.setServerProperty;
 
-public class IssueNotificationsTest extends AbstractIssueTest {
+@RunWith(Parameterized.class)
+public class IssueNotificationsTest {
 
+  @ClassRule
+  public static final Orchestrator ORCHESTRATOR = Category6Suite.ORCHESTRATOR;
+
+  @Rule
+  public Tester tester = new Tester(ORCHESTRATOR);
+
+  private final static String EMAIL_TEST = "test@test.com";
   private final static String PROJECT_KEY = "sample";
-  private final static String USER_LOGIN = "tester";
-  private final static String USER_PASSWORD = "tester";
-  private static final String USER_EMAIL = "tester@example.org";
 
   private static Wiser smtpServer;
 
-  private IssueClient issueClient;
+  private Organization organization;
+  private User userWithUserRole;
+  private User userWithUserRoleThroughGroups;
+  private User userNotInOrganization;
 
-  private IssuesService issuesService;
+  @Parameters
+  public static List<Boolean> data() {
+    return Arrays.asList(true, false);
+  }
+
+  @Parameter
+  public boolean privateProject;
 
-  @ClassRule
-  public static UserRule userRule = UserRule.from(ORCHESTRATOR);
 
   @BeforeClass
-  public static void before() throws Exception {
+  public static void setUp() {
     smtpServer = new Wiser(0);
     smtpServer.start();
     System.out.println("SMTP Server port: " + smtpServer.getServer().getPort());
-
-    // Configure Sonar
-    resetEmailSettings(ORCHESTRATOR);
-    setServerProperty(ORCHESTRATOR, "email.smtp_host.secured", "localhost");
-    setServerProperty(ORCHESTRATOR, "email.smtp_port.secured", Integer.toString(smtpServer.getServer().getPort()));
-
-    // Send test email to the test user
-    newAdminWsClient(ORCHESTRATOR).wsConnector().call(new PostRequest("api/emails/send")
-      .setParam("to", USER_EMAIL)
-      .setParam("message", "This is a test message from SonarQube"))
-      .failIfNotSuccessful();
-
-    // We need to wait until all notifications will be delivered
-    waitUntilAllNotificationsAreDelivered(1);
-
-    Iterator<WiserMessage> emails = smtpServer.getMessages().iterator();
-
-    MimeMessage message = emails.next().getMimeMessage();
-    assertThat(message.getHeader("To", null)).isEqualTo("<" + USER_EMAIL + ">");
-    assertThat((String) message.getContent()).contains("This is a test message from SonarQube");
-
-    assertThat(emails.hasNext()).isFalse();
   }
 
   @AfterClass
@@ -98,109 +103,128 @@ public class IssueNotificationsTest extends AbstractIssueTest {
     if (smtpServer != null) {
       smtpServer.stop();
     }
-    userRule.deactivateUsers(USER_LOGIN);
-    setServerProperty(ORCHESTRATOR, "sonar.issues.defaultAssigneeLogin", null);
-    resetEmailSettings(ORCHESTRATOR);
   }
 
   @Before
-  public void prepare() {
-    ORCHESTRATOR.resetData();
+  public void before() throws Exception {
+    organization = tester.organizations().generate();
 
-    // Create test user
-    userRule.createUser(USER_LOGIN, "Tester", USER_EMAIL, USER_LOGIN);
+    // Configure Sonar
+    tester.settings().setGlobalSettings("email.smtp_host.secured", "localhost");
+    tester.settings().setGlobalSettings("email.smtp_port.secured", Integer.toString(smtpServer.getServer().getPort()));
 
-    smtpServer.getMessages().clear();
-    issueClient = ORCHESTRATOR.getServer().adminWsClient().issueClient();
-    issuesService = newAdminWsClient(ORCHESTRATOR).issues();
+    clearSmtpMessages();
+    checkEmailSettings();
+    clearSmtpMessages();
+  }
 
-    setServerProperty(ORCHESTRATOR, "sonar.issues.defaultAssigneeLogin", null);
-    ItUtils.restoreProfile(ORCHESTRATOR, getClass().getResource("/issue/one-issue-per-line-profile.xml"));
-    ORCHESTRATOR.getServer().provisionProject(PROJECT_KEY, "Sample");
-    ORCHESTRATOR.getServer().associateProjectToQualityProfile(PROJECT_KEY, "xoo", "one-issue-per-line-profile");
+  @After
+  public void after() throws Exception {
+    clearSmtpMessages();
+  }
 
-    // Add notifications to the test user
-    WsClient wsClient = newUserWsClient(ORCHESTRATOR, USER_LOGIN, USER_PASSWORD);
-    wsClient.wsConnector().call(new PostRequest("api/notifications/add")
-      .setParam("type", "NewIssues")
-      .setParam("channel", "EmailNotificationChannel"))
-      .failIfNotSuccessful();
-    wsClient.wsConnector().call(new PostRequest("api/notifications/add")
-      .setParam("type", "ChangesOnMyIssue")
-      .setParam("channel", "EmailNotificationChannel"))
-      .failIfNotSuccessful();
-    wsClient.wsConnector().call(new PostRequest("api/notifications/add")
-      .setParam("type", "SQ-MyNewIssues")
-      .setParam("channel", "EmailNotificationChannel"))
-      .failIfNotSuccessful();
+  @Test
+  public void notifications_on_new_issues_should_send_emails_to_subscribers() throws Exception {
+    String version = RandomStringUtils.randomAlphanumeric(10);
+    createSampleProject(privateProject ? "private" : "public");
+    createUsers();
+    runAnalysis("shared/xoo-sample",
+      "sonar.projectVersion", version,
+      "sonar.projectDate", "2015-12-15");
+
+    // If project is private userNotInOrganization will not receive and email (missing UserRole.User permission)
+    waitUntilAllNotificationsAreDelivered(privateProject ? 2 : 3);
+    assertThat(smtpServer.getMessages()).hasSize(privateProject ? 2 : 3);
+
+    if (privateProject) {
+      assertThat(extractRecipients(smtpServer.getMessages()))
+        .containsExactlyInAnyOrder(
+          format("<%s>", userWithUserRole.getEmail()),
+          format("<%s>", userWithUserRoleThroughGroups.getEmail()));
+    } else {
+      assertThat(extractRecipients(smtpServer.getMessages()))
+        .containsExactlyInAnyOrder(
+          format("<%s>", userWithUserRole.getEmail()),
+          format("<%s>", userWithUserRoleThroughGroups.getEmail()),
+          format("<%s>", userNotInOrganization.getEmail()));
+    }
+
+    extractBodies(smtpServer.getMessages()).forEach(
+      message -> assertThat(message)
+            .contains("Project: Sample")
+            .contains("Version: " + version)
+            .contains("17 new issues (new debt: 17min)")
+            .contains("Type")
+            .contains("One Issue Per Line (xoo): 17")
+            .contains("More details at: http://localhost:9000/project/issues?id=sample&createdAt=2015-12-15T00%3A00%3A00%2B")
+    );
+
+    clearSmtpMessages();
   }
 
   @Test
-  public void notifications_for_new_issues_and_issue_changes() throws Exception {
+  public void notifications_for_issue_changes() throws Exception {
     String version = RandomStringUtils.randomAlphanumeric(10);
-    runProjectAnalysis(ORCHESTRATOR, "shared/xoo-sample",
-      "sonar.projectDate", "2015-12-15",
-      "sonar.projectVersion", version);
+    createSampleProject(privateProject ? "private" : "public");
+    createUsers();
+    runAnalysis("shared/xoo-sample",
+      "sonar.projectVersion", version,
+      "sonar.projectDate", "2015-12-15");
 
-    // change assignee
-    Issues issues = issueClient.find(IssueQuery.create().componentRoots(PROJECT_KEY));
-    Issue issue = issues.list().get(0);
-    issueClient.assign(issue.key(), USER_LOGIN);
+    // Ignore the messages generated by the analysis
+    clearSmtpMessages();
 
-    waitUntilAllNotificationsAreDelivered(2);
+    // Change assignee
+    SearchWsResponse issues = tester.wsClient().issues().search(new SearchWsRequest().setProjectKeys(singletonList(PROJECT_KEY)));
+    Issue issue = issues.getIssuesList().get(0);
+    tester.wsClient().issues().assign(new AssignRequest(issue.getKey(), userWithUserRole.getLogin()));
 
-    Iterator<WiserMessage> emails = smtpServer.getMessages().iterator();
+    // Only the assignee should receive the email
+    waitUntilAllNotificationsAreDelivered(1);
+    assertThat(smtpServer.getMessages()).hasSize(1);
+    assertThat(extractRecipients(smtpServer.getMessages())).containsExactly(format("<%s>", userWithUserRole.getEmail()));
 
-    assertThat(emails.hasNext()).isTrue();
-    MimeMessage message = emails.next().getMimeMessage();
-    assertThat(message.getHeader("To", null)).isEqualTo("<tester@example.org>");
-    assertThat((String) message.getContent())
-      .contains("Project: Sample")
-      .contains("Version: " + version)
-      .contains("17 new issues (new debt: 17min)")
-      .contains("Type")
-      .contains("One Issue Per Line (xoo): 17")
-      .contains("More details at: http://localhost:9000/project/issues?id=sample&createdAt=2015-12-15T00%3A00%3A00%2B");
-
-    assertThat(emails.hasNext()).isTrue();
-    message = emails.next().getMimeMessage();
-    assertThat(message.getHeader("To", null)).isEqualTo("<tester@example.org>");
-    assertThat((String) message.getContent())
+    assertThat(extractBodies(smtpServer.getMessages()).get(0))
       .contains("sample/Sample.xoo")
-      .contains("Assignee changed to Tester")
-      .contains("More details at: http://localhost:9000/project/issues?id=sample&issues=" + issue.key() + "&open=" + issue.key());
-
-    assertThat(emails.hasNext()).isFalse();
+      .contains("Assignee changed to userWithUserRole")
+      .contains("More details at: http://localhost:9000/project/issues?id=sample&issues=" + issue.getKey() + "&open=" + issue.getKey());
   }
 
   @Test
   public void notifications_for_personalized_emails() throws Exception {
     String version = RandomStringUtils.randomAlphanumeric(10);
-    setServerProperty(ORCHESTRATOR, "sonar.issues.defaultAssigneeLogin", USER_LOGIN);
     // 1st analysis without any issue (because no file is analyzed)
-    runProjectAnalysis(ORCHESTRATOR, "issue/xoo-with-scm",
+    createSampleProject(privateProject ? "private" : "public");
+    createUsers();
+    tester.settings().setGlobalSettings("sonar.issues.defaultAssigneeLogin", userWithUserRole.getLogin());
+
+    runAnalysis("issue/xoo-with-scm",
+      "sonar.projectVersion", version,
       "sonar.scm.provider", "xoo",
       "sonar.scm.disabled", "false",
-      "sonar.exclusions", "**/*",
-      "sonar.projectVersion", version);
+      "sonar.exclusions", "**/*"
+    );
 
-    waitUntilAllNotificationsAreDelivered(2);
+    // No email since all code is ignored
+    waitUntilAllNotificationsAreDelivered(1);
     assertThat(smtpServer.getMessages()).isEmpty();
 
     // run 2nd analysis which will generate issues on the leak period
-    runProjectAnalysis(ORCHESTRATOR, "issue/xoo-with-scm",
+    runAnalysis("issue/xoo-with-scm",
+      "sonar.projectVersion", version,
       "sonar.scm.provider", "xoo",
-      "sonar.scm.disabled", "false",
-      "sonar.projectVersion", version);
+      "sonar.scm.disabled", "false"
+    );
 
-    waitUntilAllNotificationsAreDelivered(2);
+    // We expect to receive a notification for each subscriber with UserRole.user
+    // And a personalized email for the assignee userWithUserRole
+    waitUntilAllNotificationsAreDelivered(privateProject ? 3 : 4);
+    assertThat(smtpServer.getMessages()).hasSize(privateProject ? 3 : 4);
 
-    Iterator<WiserMessage> emails = smtpServer.getMessages().iterator();
-    emails.next();
-    // the second email sent is the personalized one
-    MimeMessage message = emails.next().getMimeMessage();
+    // the last email sent is the personalized one
+    MimeMessage message = smtpServer.getMessages().get(privateProject ? 2 : 3).getMimeMessage();
 
-    assertThat(message.getHeader("To", null)).isEqualTo("<tester@example.org>");
+    assertThat(message.getHeader("To", null)).isEqualTo(format("<%s>", userWithUserRole.getEmail()));
     assertThat(message.getSubject()).contains("You have 13 new issues");
     assertThat((String) message.getContent())
       .contains("Project: Sample")
@@ -212,42 +236,123 @@ public class IssueNotificationsTest extends AbstractIssueTest {
    */
   @Test
   public void notifications_for_bulk_change_ws() throws Exception {
-    runProjectAnalysis(ORCHESTRATOR, "shared/xoo-sample", "sonar.projectDate", "2015-12-15");
+    String version = RandomStringUtils.randomAlphanumeric(10);
+    createSampleProject(privateProject ? "private" : "public");
+    createUsers();
+    runAnalysis("shared/xoo-sample",
+      "sonar.projectVersion", version,
+      "sonar.projectDate", "2015-12-15");
+
+    // If project is private userNotInOrganization will not receive and email (missing UserRole.User permission)
+    waitUntilAllNotificationsAreDelivered(privateProject ? 2 : 3);
+    assertThat(smtpServer.getMessages()).hasSize(privateProject ? 2 : 3);
+    clearSmtpMessages();
 
-    Issues issues = issueClient.find(IssueQuery.create().componentRoots(PROJECT_KEY));
-    Issue issue = issues.list().get(0);
+    SearchWsResponse issues = tester.wsClient().issues().search(new SearchWsRequest().setProjectKeys(singletonList(PROJECT_KEY)));
+    Issue issue = issues.getIssuesList().get(0);
 
     // bulk change without notification by default
-    issuesService.bulkChange(BulkChangeRequest.builder()
-      .setIssues(singletonList(issue.key()))
-      .setAssign(USER_LOGIN)
+    tester.wsClient().issues().bulkChange(BulkChangeRequest.builder()
+      .setIssues(singletonList(issue.getKey()))
+      .setAssign(userWithUserRole.getLogin())
       .setSetSeverity("MINOR")
       .build());
 
     // bulk change with notification
-    issuesService.bulkChange(BulkChangeRequest.builder()
-      .setIssues(singletonList(issue.key()))
+    tester.wsClient().issues().bulkChange(BulkChangeRequest.builder()
+      .setIssues(singletonList(issue.getKey()))
       .setSetSeverity("BLOCKER")
       .setSendNotifications(true)
       .build());
 
+    // We are waiting for a single notification for userWithUserRole
+    // for a change on MyIssues
+    waitUntilAllNotificationsAreDelivered(1);
+    assertThat(smtpServer.getMessages()).hasSize(1);
+
+    assertThat(extractRecipients(smtpServer.getMessages()))
+      .containsExactly(format("<%s>", userWithUserRole.getEmail()));
+    assertThat(extractBodies(smtpServer.getMessages()).get(0))
+      .contains("sample/Sample.xoo")
+      .contains("Severity: BLOCKER (was MINOR)")
+      .contains("More details at: http://localhost:9000/project/issues?id=sample&issues=" + issue.getKey() + "&open=" + issue.getKey());
+  }
+
+  private void runAnalysis(String projectRelativePath, String... extraParameters) throws Exception {
+    String[] parameters = {
+      "sonar.login", "admin",
+      "sonar.password", "admin",
+      "sonar.organization", organization.getKey()};
+    runProjectAnalysis(ORCHESTRATOR, projectRelativePath,
+      ObjectArrays.concat(parameters, extraParameters, String.class));
+
+    // Two emails should be sent for subscribers of "New issues"
     waitUntilAllNotificationsAreDelivered(2);
+  }
+
+  private void createSampleProject(String visibility) {
+    // Create project
+    QualityProfiles.CreateWsResponse.QualityProfile profile = tester.qProfiles().createXooProfile(organization);
+    Project project = tester.projects().generate(organization, p -> p.setKey(PROJECT_KEY)
+      .setName("Sample")
+      .setVisibility(visibility));
+    tester.qProfiles()
+      .activateRule(profile, "xoo:OneIssuePerLine")
+      .assignQProfileToProject(profile, project);
+  }
 
-    Iterator<WiserMessage> emails = smtpServer.getMessages().iterator();
+  private void createUsers() {
+    // Create a user with User role
+    userWithUserRole = tester.users().generateMember(organization,
+      u -> u.setLogin("userWithUserRole")
+        .setPassword("userWithUserRole")
+        .setName("userWithUserRole")
+        .setEmail("userWithUserRole@nowhere.com"));
+    tester.organizations().addMember(organization, userWithUserRole);
+    tester.wsClient().permissions().addUser(
+      new AddUserWsRequest()
+        .setLogin(userWithUserRole.getLogin())
+        .setProjectKey(PROJECT_KEY)
+        .setPermission("user"));
+    addNotificationsTo(userWithUserRole);
+
+    // Create a user that have User role through Members group
+    userWithUserRoleThroughGroups = tester.users().generate(
+      u -> u.setLogin("userWithUserRoleThroughGroups")
+        .setPassword("userWithUserRoleThroughGroups")
+        .setName("userWithUserRoleThroughGroups")
+        .setEmail("userWithUserRoleThroughGroups@nowhere.com"));
+    tester.organizations().addMember(organization, userWithUserRoleThroughGroups);
+    addNotificationsTo(userWithUserRoleThroughGroups);
+
+    // Create a user that does not belongs to organization
+    userNotInOrganization = tester.users().generate(
+      u -> u.setLogin("userNotInOrganization")
+        .setPassword("userNotInOrganization")
+        .setName("userNotInOrganization")
+        .setEmail("userNotInOrganization@nowhere.com"));
+    addNotificationsTo(userNotInOrganization);
+  }
+
+  void checkEmailSettings() throws Exception {
+    // Send test email to the test user
+    tester.wsClient().wsConnector().call(new PostRequest("api/emails/send")
+      .setParam("to", EMAIL_TEST)
+      .setParam("message", "This is a test message from SonarQube"))
+      .failIfNotSuccessful();
+
+    // We need to wait until all notifications will be delivered
+    waitUntilAllNotificationsAreDelivered(1);
 
-    emails.next();
-    MimeMessage message = emails.next().getMimeMessage();
-    assertThat(message.getHeader("To", null)).isEqualTo("<tester@example.org>");
-    assertThat((String) message.getContent()).contains("sample/Sample.xoo");
-    assertThat((String) message.getContent()).contains("Severity: BLOCKER (was MINOR)");
-    assertThat((String) message.getContent()).contains(
-      "More details at: http://localhost:9000/project/issues?id=sample&issues=" + issue.key() + "&open=" + issue.key());
+    assertThat(smtpServer.getMessages()).hasSize(1);
 
-    assertThat(emails.hasNext()).isFalse();
+    MimeMessage message = smtpServer.getMessages().get(0).getMimeMessage();
+    assertThat(message.getHeader("To", null)).isEqualTo("<" + EMAIL_TEST + ">");
+    assertThat((String) message.getContent()).contains("This is a test message from SonarQube");
   }
 
   private static void waitUntilAllNotificationsAreDelivered(int expectedNumberOfEmails) throws InterruptedException {
-    for (int i = 0; i < 10; i++) {
+    for (int i = 0; i < 5; i++) {
       if (smtpServer.getMessages().size() == expectedNumberOfEmails) {
         break;
       }
@@ -255,4 +360,50 @@ public class IssueNotificationsTest extends AbstractIssueTest {
     }
   }
 
+  private static void clearSmtpMessages() {
+    synchronized (smtpServer.getMessages()) {
+      smtpServer.getMessages().clear();
+    }
+  }
+
+  private List<String> extractRecipients(List<WiserMessage> messages) {
+    return messages.stream()
+      .map(m -> {
+        try {
+          return m.getMimeMessage().getHeader("To", null);
+        } catch (MessagingException e) {
+          fail(e.getMessage(), e);
+          return null;
+        }
+      }).collect(Collectors.toList());
+  }
+
+  private List<String> extractBodies(List<WiserMessage> messages) {
+    return messages.stream()
+      .map(m -> {
+        try {
+          return m.getMimeMessage().getContent().toString();
+        } catch (MessagingException | IOException e) {
+          fail(e.getMessage(), e);
+          return null;
+        }
+      }).collect(Collectors.toList());
+  }
+
+  private void addNotificationsTo(WsUsers.CreateWsResponse.User user) {
+    // Add notifications to the test user
+    WsClient wsClient = tester.as(user.getLogin()).wsClient();
+    wsClient.wsConnector().call(new PostRequest("api/notifications/add")
+      .setParam("type", "NewIssues")
+      .setParam("channel", "EmailNotificationChannel"))
+      .failIfNotSuccessful();
+    wsClient.wsConnector().call(new PostRequest("api/notifications/add")
+      .setParam("type", "ChangesOnMyIssue")
+      .setParam("channel", "EmailNotificationChannel"))
+      .failIfNotSuccessful();
+    wsClient.wsConnector().call(new PostRequest("api/notifications/add")
+      .setParam("type", "SQ-MyNewIssues")
+      .setParam("channel", "EmailNotificationChannel"))
+      .failIfNotSuccessful();
+  }
 }