*/
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
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")
*/
@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;
}
}
}
+ 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();
+ }
}