import org.sonar.ce.cleaning.CeCleaningModule;
import org.sonar.ce.db.ReadOnlyPropertiesDao;
import org.sonar.ce.log.CeProcessLogging;
+import org.sonar.ce.notification.ReportAnalysisFailureNotificationModule;
import org.sonar.ce.platform.ComputeEngineExtensionInstaller;
import org.sonar.ce.queue.CeQueueCleaner;
import org.sonar.ce.queue.PurgeCeActivities;
NotificationService.class,
DefaultNotificationManager.class,
EmailNotificationChannel.class,
+ ReportAnalysisFailureNotificationModule.class,
// Tests
TestIndexer.class,
--- /dev/null
+/*
+ * 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.sonar.ce.notification;
+
+import javax.annotation.Nullable;
+import org.sonar.api.resources.Qualifiers;
+import org.sonar.api.resources.Scopes;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.queue.CeTask;
+import org.sonar.ce.queue.CeTaskResult;
+import org.sonar.ce.taskprocessor.CeWorker;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.RowNotFoundException;
+import org.sonar.db.ce.CeActivityDto;
+import org.sonar.db.ce.CeTaskTypes;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.server.notification.NotificationService;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.lang.String.format;
+import static java.util.Collections.singleton;
+
+public class ReportAnalysisFailureNotificationExecutionListener implements CeWorker.ExecutionListener {
+ private final NotificationService notificationService;
+ private final DbClient dbClient;
+ private final ReportAnalysisFailureNotificationSerializer taskFailureNotificationSerializer;
+ private final System2 system2;
+
+ public ReportAnalysisFailureNotificationExecutionListener(NotificationService notificationService, DbClient dbClient,
+ ReportAnalysisFailureNotificationSerializer taskFailureNotificationSerializer, System2 system2) {
+ this.notificationService = notificationService;
+ this.dbClient = dbClient;
+ this.taskFailureNotificationSerializer = taskFailureNotificationSerializer;
+ this.system2 = system2;
+ }
+
+ @Override
+ public void onStart(CeTask ceTask) {
+ // nothing to do
+ }
+
+ @Override
+ public void onEnd(CeTask ceTask, CeActivityDto.Status status, @Nullable CeTaskResult taskResult, @Nullable Throwable error) {
+ if (status == CeActivityDto.Status.SUCCESS) {
+ return;
+ }
+ String projectUuid = ceTask.getComponentUuid();
+ if (!CeTaskTypes.REPORT.equals(ceTask.getType()) || projectUuid == null) {
+ return;
+ }
+
+ if (notificationService.hasProjectSubscribersForTypes(projectUuid, singleton(ReportAnalysisFailureNotification.TYPE))) {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ ComponentDto projectDto = dbClient.componentDao().selectOrFailByUuid(dbSession, projectUuid);
+ checkScopeAndQualifier(projectDto);
+ CeActivityDto ceActivityDto = dbClient.ceActivityDao().selectByUuid(dbSession, ceTask.getUuid())
+ .orElseThrow(() -> new RowNotFoundException(format("CeActivity with uuid '%s' not found", ceTask.getUuid())));
+ ReportAnalysisFailureNotification taskFailureNotification = buildNotification(ceActivityDto, projectDto, error);
+ notificationService.deliver(taskFailureNotificationSerializer.toNotification(taskFailureNotification));
+ }
+ }
+ }
+
+ /**
+ * @throws IllegalArgumentException if specified {@link ComponentDto} is not a project.
+ */
+ private static void checkScopeAndQualifier(ComponentDto projectDto) {
+ String scope = projectDto.scope();
+ String qualifier = projectDto.qualifier();
+ checkArgument(
+ scope.equals(Scopes.PROJECT) && qualifier.equals(Qualifiers.PROJECT),
+ "Component %s must be a project (scope=%s, qualifier=%s)", projectDto.uuid(), scope, qualifier);
+ }
+
+ private ReportAnalysisFailureNotification buildNotification(CeActivityDto ceActivityDto, ComponentDto projectDto, @Nullable Throwable error) {
+ Long executedAt = ceActivityDto.getExecutedAt();
+ return new ReportAnalysisFailureNotification(
+ new ReportAnalysisFailureNotification.Project(
+ projectDto.uuid(),
+ projectDto.getKey(),
+ projectDto.name(),
+ projectDto.getBranch()),
+ new ReportAnalysisFailureNotification.Task(
+ ceActivityDto.getUuid(),
+ ceActivityDto.getSubmittedAt(),
+ executedAt == null ? system2.now() : executedAt),
+ error == null ? null : error.getMessage());
+ }
+}
--- /dev/null
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.ce.notification;
+
+import javax.annotation.ParametersAreNonnullByDefault;
*/
package org.sonar.ce.taskprocessor;
+import org.sonar.ce.notification.ReportAnalysisFailureNotificationExecutionListener;
import org.sonar.core.platform.Module;
public class CeTaskProcessorModule extends Module {
add(
CeTaskProcessorRepositoryImpl.class,
CeLoggingWorkerExecutionListener.class,
+ ReportAnalysisFailureNotificationExecutionListener.class,
CeWorkerFactoryImpl.class,
EnabledCeWorkerControllerImpl.class,
CeProcessingSchedulerExecutorServiceImpl.class,
assertThat(picoContainer.getComponentAdapters())
.hasSize(
CONTAINER_ITSELF
- + 74 // level 4
+ + 75 // level 4
+ 4 // content of CeConfigurationModule
+ 7 // content of CeQueueModule
+ 4 // content of CeHttpModule
+ 3 // content of CeTaskCommonsModule
+ 4 // content of ProjectAnalysisTaskModule
- + 6 // content of CeTaskProcessorModule
+ + 7 // content of CeTaskProcessorModule
+ + 4 // content of ReportAnalysisFailureNotificationModule
+ 3 // CeCleaningModule + its content
+ 1 // CeDistributedInformation
);
--- /dev/null
+/*
+ * 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.sonar.ce.notification;
+
+import java.util.Arrays;
+import java.util.Random;
+import javax.annotation.Nullable;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mockito.ArgumentCaptor;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.utils.System2;
+import org.sonar.ce.queue.CeTask;
+import org.sonar.ce.queue.CeTaskResult;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbTester;
+import org.sonar.db.RowNotFoundException;
+import org.sonar.db.ce.CeActivityDto;
+import org.sonar.db.ce.CeQueueDto;
+import org.sonar.db.ce.CeTaskTypes;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.component.ComponentTesting;
+import org.sonar.db.organization.OrganizationDto;
+import org.sonar.db.organization.OrganizationTesting;
+import org.sonar.server.notification.NotificationService;
+
+import static java.util.Collections.singleton;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+import static org.sonar.db.component.ComponentTesting.newDirectory;
+import static org.sonar.db.component.ComponentTesting.newModuleDto;
+
+public class ReportAnalysisFailureNotificationExecutionListenerTest {
+ @Rule
+ public DbTester dbTester = DbTester.create(System2.INSTANCE);
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private final Random random = new Random();
+ private DbClient dbClient = dbTester.getDbClient();
+ private NotificationService notificationService = mock(NotificationService.class);
+ private ReportAnalysisFailureNotificationSerializer serializer = mock(ReportAnalysisFailureNotificationSerializer.class);
+ private System2 system2 = mock(System2.class);
+ private DbClient dbClientMock = mock(DbClient.class);
+ private CeTask ceTaskMock = mock(CeTask.class);
+ private Throwable throwableMock = mock(Throwable.class);
+ private CeTaskResult ceTaskResultMock = mock(CeTaskResult.class);
+ private ReportAnalysisFailureNotificationExecutionListener fullMockedUnderTest = new ReportAnalysisFailureNotificationExecutionListener(
+ notificationService, dbClientMock, serializer, system2);
+ private ReportAnalysisFailureNotificationExecutionListener underTest = new ReportAnalysisFailureNotificationExecutionListener(
+ notificationService, dbClient, serializer, system2);
+
+ @Before
+ public void setUp() throws Exception {
+
+ }
+
+ @Test
+ public void onStart_has_no_effect() {
+ CeTask mockedCeTask = mock(CeTask.class);
+
+ fullMockedUnderTest.onStart(mockedCeTask);
+
+ verifyZeroInteractions(mockedCeTask, notificationService, dbClientMock, serializer, system2);
+ }
+
+ @Test
+ public void onEnd_has_no_effect_if_status_is_SUCCESS() {
+ fullMockedUnderTest.onEnd(ceTaskMock, CeActivityDto.Status.SUCCESS, ceTaskResultMock, throwableMock);
+
+ verifyZeroInteractions(ceTaskMock, ceTaskResultMock, throwableMock, notificationService, dbClientMock, serializer, system2);
+ }
+
+ @Test
+ public void onEnd_has_no_effect_if_CeTask_type_is_not_report() {
+ when(ceTaskMock.getType()).thenReturn(randomAlphanumeric(12));
+
+ fullMockedUnderTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, throwableMock);
+
+ verifyZeroInteractions(ceTaskResultMock, throwableMock, notificationService, dbClientMock, serializer, system2);
+ }
+
+ @Test
+ public void onEnd_has_no_effect_if_CeTask_has_no_component_uuid() {
+ when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
+
+ fullMockedUnderTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, throwableMock);
+
+ verifyZeroInteractions(ceTaskResultMock, throwableMock, notificationService, dbClientMock, serializer, system2);
+ }
+
+ @Test
+ public void onEnd_has_no_effect_if_there_is_no_subscriber_for_ReportAnalysisFailureNotification_type() {
+ String componentUuid = randomAlphanumeric(6);
+ when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
+ when(ceTaskMock.getComponentUuid()).thenReturn(componentUuid);
+ when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.TYPE)))
+ .thenReturn(false);
+
+ fullMockedUnderTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, throwableMock);
+
+ verifyZeroInteractions(ceTaskResultMock, throwableMock, dbClientMock, serializer, system2);
+ }
+
+ @Test
+ public void onEnd_fails_with_RowNotFoundException_if_component_does_not_exist_in_DB() {
+ String componentUuid = randomAlphanumeric(6);
+ when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
+ when(ceTaskMock.getComponentUuid()).thenReturn(componentUuid);
+ when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.TYPE)))
+ .thenReturn(true);
+
+ expectedException.expect(RowNotFoundException.class);
+ expectedException.expectMessage("Component with uuid '" + componentUuid + "' not found");
+
+ underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, throwableMock);
+ }
+
+ @Test
+ public void onEnd_fails_with_IAE_if_component_is_not_a_project() {
+ when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
+ OrganizationDto organization = OrganizationTesting.newOrganizationDto();
+ ComponentDto project = dbTester.components().insertPrivateProject();
+ ComponentDto module = dbTester.components().insertComponent(newModuleDto(project));
+ ComponentDto directory = dbTester.components().insertComponent(newDirectory(module, randomAlphanumeric(12)));
+ ComponentDto file = dbTester.components().insertComponent(ComponentTesting.newFileDto(project));
+ ComponentDto view = dbTester.components().insertComponent(ComponentTesting.newView(organization));
+ ComponentDto subView = dbTester.components().insertComponent(ComponentTesting.newSubView(view));
+ ComponentDto projectCopy = dbTester.components().insertComponent(ComponentTesting.newProjectCopy(project, subView));
+ ComponentDto application = dbTester.components().insertComponent(ComponentTesting.newApplication(organization));
+
+ Arrays.asList(module, directory, file, view, subView, projectCopy, application)
+ .forEach(component -> {
+ try {
+ when(ceTaskMock.getComponentUuid()).thenReturn(component.uuid());
+ when(notificationService.hasProjectSubscribersForTypes(component.uuid(), singleton(ReportAnalysisFailureNotification.TYPE)))
+ .thenReturn(true);
+
+ underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, throwableMock);
+
+ fail("An IllegalArgumentException should have been thrown for component " + component);
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage()).isEqualTo(String.format(
+ "Component %s must be a project (scope=%s, qualifier=%s)", component.uuid(), component.scope(), component.qualifier()));
+ }
+ });
+ }
+
+ @Test
+ public void onEnd_fails_with_RowNotFoundException_if_activity_for_task_does_not_exist_in_DB() {
+ String componentUuid = randomAlphanumeric(6);
+ String taskUuid = randomAlphanumeric(6);
+ when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
+ when(ceTaskMock.getUuid()).thenReturn(taskUuid);
+ when(ceTaskMock.getComponentUuid()).thenReturn(componentUuid);
+ when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.TYPE)))
+ .thenReturn(true);
+ dbTester.components().insertPrivateProject(s -> s.setUuid(componentUuid));
+
+ expectedException.expect(RowNotFoundException.class);
+ expectedException.expectMessage("CeActivity with uuid '" + taskUuid + "' not found");
+
+ underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, throwableMock);
+ }
+
+ @Test
+ public void onEnd_creates_notification_with_data_from_activity_and_project_and_deliver_it() {
+ String taskUuid = randomAlphanumeric(12);
+ int createdAt = random.nextInt(999_999);
+ long executedAt = random.nextInt(999_999);
+ ComponentDto project = initMocksToPassConditions(taskUuid, createdAt, executedAt);
+ Notification notificationMock = mockSerializer();
+
+ underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, throwableMock);
+
+ ArgumentCaptor<ReportAnalysisFailureNotification> notificationCaptor = verifyAndCaptureSerializedNotification();
+ verify(notificationService).deliver(same(notificationMock));
+
+ ReportAnalysisFailureNotification reportAnalysisFailureNotification = notificationCaptor.getValue();
+
+ ReportAnalysisFailureNotification.Project notificationProject = reportAnalysisFailureNotification.getProject();
+ assertThat(notificationProject.getName()).isEqualTo(project.name());
+ assertThat(notificationProject.getKey()).isEqualTo(project.getKey());
+ assertThat(notificationProject.getUuid()).isEqualTo(project.uuid());
+ assertThat(notificationProject.getBranchName()).isEqualTo(project.getBranch());
+ ReportAnalysisFailureNotification.Task notificationTask = reportAnalysisFailureNotification.getTask();
+ assertThat(notificationTask.getUuid()).isEqualTo(taskUuid);
+ assertThat(notificationTask.getCreatedAt()).isEqualTo(createdAt);
+ assertThat(notificationTask.getFailedAt()).isEqualTo(executedAt);
+ }
+
+ @Test
+ public void onEnd_creates_notification_with_error_message_from_Throwable_argument_message() {
+ initMocksToPassConditions(randomAlphanumeric(12), random.nextInt(999_999), (long) random.nextInt(999_999));
+ String message = randomAlphanumeric(66);
+ when(throwableMock.getMessage()).thenReturn(message);
+
+ underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, throwableMock);
+
+ ArgumentCaptor<ReportAnalysisFailureNotification> notificationCaptor = verifyAndCaptureSerializedNotification();
+
+ ReportAnalysisFailureNotification reportAnalysisFailureNotification = notificationCaptor.getValue();
+ assertThat(reportAnalysisFailureNotification.getErrorMessage()).isEqualTo(message);
+ }
+
+ @Test
+ public void onEnd_creates_notification_with_null_error_message_if_Throwable_is_null() {
+ String taskUuid = randomAlphanumeric(12);
+ initMocksToPassConditions(taskUuid, random.nextInt(999_999), (long) random.nextInt(999_999));
+ Notification notificationMock = mockSerializer();
+
+ underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, null);
+
+ verify(notificationService).deliver(same(notificationMock));
+ ArgumentCaptor<ReportAnalysisFailureNotification> notificationCaptor = verifyAndCaptureSerializedNotification();
+
+ ReportAnalysisFailureNotification reportAnalysisFailureNotification = notificationCaptor.getValue();
+ assertThat(reportAnalysisFailureNotification.getErrorMessage()).isNull();
+ }
+
+ @Test
+ public void onEnd_ignores_null_CeTaskResult_argument() {
+ String taskUuid = randomAlphanumeric(12);
+ initMocksToPassConditions(taskUuid, random.nextInt(999_999), (long) random.nextInt(999_999));
+ Notification notificationMock = mockSerializer();
+
+ underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, null, null);
+
+ verify(notificationService).deliver(same(notificationMock));
+ }
+
+ @Test
+ public void onEnd_ignores_CeTaskResult_argument() {
+ String taskUuid = randomAlphanumeric(12);
+ initMocksToPassConditions(taskUuid, random.nextInt(999_999), (long) random.nextInt(999_999));
+ Notification notificationMock = mockSerializer();
+
+ underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, null);
+
+ verify(notificationService).deliver(same(notificationMock));
+ verifyZeroInteractions(ceTaskResultMock);
+ }
+
+ @Test
+ public void onEnd_uses_system_data_as_failedAt_if_task_has_no_executedAt() {
+ String taskUuid = randomAlphanumeric(12);
+ initMocksToPassConditions(taskUuid, random.nextInt(999_999), null);
+ long now = random.nextInt(999_999);
+ when(system2.now()).thenReturn(now);
+ Notification notificationMock = mockSerializer();
+
+ underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, null);
+
+ verify(notificationService).deliver(same(notificationMock));
+ ArgumentCaptor<ReportAnalysisFailureNotification> notificationCaptor = verifyAndCaptureSerializedNotification();
+ assertThat(notificationCaptor.getValue().getTask().getFailedAt()).isEqualTo(now);
+ }
+
+ private Notification mockSerializer() {
+ Notification notificationMock = mock(Notification.class);
+ when(serializer.toNotification(any(ReportAnalysisFailureNotification.class))).thenReturn(notificationMock);
+ return notificationMock;
+ }
+
+ private ComponentDto initMocksToPassConditions(String taskUuid, int createdAt, @Nullable Long executedAt) {
+ ComponentDto project = random.nextBoolean() ? dbTester.components().insertPrivateProject() : dbTester.components().insertPublicProject();
+ when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
+ when(ceTaskMock.getComponentUuid()).thenReturn(project.uuid());
+ when(ceTaskMock.getUuid()).thenReturn(taskUuid);
+ when(notificationService.hasProjectSubscribersForTypes(project.uuid(), singleton(ReportAnalysisFailureNotification.TYPE)))
+ .thenReturn(true);
+ insertActivityDto(taskUuid, createdAt, executedAt, project);
+ return project;
+ }
+
+ private void insertActivityDto(String taskUuid, int createdAt, @Nullable Long executedAt, ComponentDto project) {
+ dbClient.ceActivityDao().insert(dbTester.getSession(), new CeActivityDto(new CeQueueDto()
+ .setUuid(taskUuid)
+ .setTaskType(CeTaskTypes.REPORT)
+ .setComponentUuid(project.uuid())
+ .setCreatedAt(createdAt))
+ .setExecutedAt(executedAt)
+ .setStatus(CeActivityDto.Status.FAILED));
+ dbTester.getSession().commit();
+ }
+
+ private ArgumentCaptor<ReportAnalysisFailureNotification> verifyAndCaptureSerializedNotification() {
+ ArgumentCaptor<ReportAnalysisFailureNotification> notificationCaptor = ArgumentCaptor.forClass(ReportAnalysisFailureNotification.class);
+ verify(serializer).toNotification(notificationCaptor.capture());
+ return notificationCaptor;
+ }
+}
import org.junit.Test;
import org.picocontainer.ComponentAdapter;
+import org.sonar.ce.notification.ReportAnalysisFailureNotificationExecutionListener;
import org.sonar.core.platform.ComponentContainer;
import static org.assertj.core.api.Assertions.assertThat;
.map(ComponentAdapter::getComponentImplementation))
.contains(CeLoggingWorkerExecutionListener.class);
}
+
+ @Test
+ public void defines_ExecutionListener_for_report_processing_failure_notifications() {
+ ComponentContainer container = new ComponentContainer();
+
+ underTest.configure(container);
+
+ assertThat(container.getPicoContainer().getComponentAdapters(CeWorker.ExecutionListener.class)
+ .stream()
+ .map(ComponentAdapter::getComponentImplementation))
+ .contains(ReportAnalysisFailureNotificationExecutionListener.class);
+ }
}
--- /dev/null
+/*
+ * 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.sonar.ce.notification;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+import static java.util.Objects.requireNonNull;
+
+public class ReportAnalysisFailureNotification {
+ public static final String TYPE = "ce-report-task-failure";
+
+ private final Project project;
+ private final Task task;
+ private final String errorMessage;
+
+ public ReportAnalysisFailureNotification(Project project, Task task, @Nullable String errorMessage) {
+ this.project = requireNonNull(project, "project can't be null");
+ this.task = requireNonNull(task, "task can't be null");
+ this.errorMessage = errorMessage;
+ }
+
+ public Project getProject() {
+ return project;
+ }
+
+ public Task getTask() {
+ return task;
+ }
+
+ @CheckForNull
+ public String getErrorMessage() {
+ return errorMessage;
+ }
+
+ public static final class Project {
+ private final String uuid;
+ private final String key;
+ private final String name;
+ private final String branchName;
+
+ public Project(String uuid, String key, String name, @Nullable String branchName) {
+ this.uuid = requireNonNull(uuid, "uuid can't be null");
+ this.key = requireNonNull(key, "key can't be null");
+ this.name = requireNonNull(name, "name can't be null");
+ this.branchName = branchName;
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @CheckForNull
+ public String getBranchName() {
+ return branchName;
+ }
+ }
+
+ public static final class Task {
+ private final String uuid;
+ private final long createdAt;
+ private final long failedAt;
+
+ public Task(String uuid, long createdAt, long failedAt) {
+ this.uuid = requireNonNull(uuid, "uuid can't be null");
+ this.createdAt = createdAt;
+ this.failedAt = failedAt;
+ }
+
+ public String getUuid() {
+ return uuid;
+ }
+
+ public long getCreatedAt() {
+ return createdAt;
+ }
+
+ public long getFailedAt() {
+ return failedAt;
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.sonar.ce.notification;
+
+import com.google.common.collect.Multimap;
+import java.util.Collection;
+import java.util.Map;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.notifications.NotificationChannel;
+import org.sonar.server.notification.NotificationDispatcher;
+import org.sonar.server.notification.NotificationDispatcherMetadata;
+import org.sonar.server.notification.NotificationManager;
+
+public class ReportAnalysisFailureNotificationDispatcher extends NotificationDispatcher {
+
+ public static final String KEY = "CeReportTaskFailure";
+
+ private final NotificationManager manager;
+
+ public ReportAnalysisFailureNotificationDispatcher(NotificationManager manager) {
+ super(ReportAnalysisFailureNotification.TYPE);
+ this.manager = manager;
+ }
+
+ public static NotificationDispatcherMetadata newMetadata() {
+ return NotificationDispatcherMetadata.create(KEY)
+ .setProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION, String.valueOf(true))
+ .setProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION, String.valueOf(true));
+ }
+
+ @Override
+ public String getKey() {
+ return KEY;
+ }
+
+ @Override
+ public void dispatch(Notification notification, Context context) {
+ String projectUuid = notification.getFieldValue("project.uuid");
+ Multimap<String, NotificationChannel> subscribedRecipients = manager.findSubscribedRecipientsForDispatcher(this, projectUuid);
+
+ for (Map.Entry<String, Collection<NotificationChannel>> channelsByRecipients : subscribedRecipients.asMap().entrySet()) {
+ String userLogin = channelsByRecipients.getKey();
+ for (NotificationChannel channel : channelsByRecipients.getValue()) {
+ context.addUser(userLogin, channel);
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.sonar.ce.notification;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import org.sonar.api.config.EmailSettings;
+import org.sonar.api.notifications.Notification;
+import org.sonar.plugins.emailnotifications.api.EmailMessage;
+import org.sonar.plugins.emailnotifications.api.EmailTemplate;
+
+import static org.sonar.api.utils.DateUtils.formatDateTime;
+
+public class ReportAnalysisFailureNotificationEmailTemplate extends EmailTemplate {
+ private static final char LINE_RETURN = '\n';
+ private static final char TAB = '\t';
+
+ private final ReportAnalysisFailureNotificationSerializer serializer;
+ protected final EmailSettings settings;
+
+ public ReportAnalysisFailureNotificationEmailTemplate(ReportAnalysisFailureNotificationSerializer serializer, EmailSettings settings) {
+ this.serializer = serializer;
+ this.settings = settings;
+ }
+
+ @Override
+ public EmailMessage format(Notification notification) {
+ if (!ReportAnalysisFailureNotification.TYPE.equals(notification.getType())) {
+ return null;
+ }
+
+ ReportAnalysisFailureNotification taskFailureNotification = serializer.fromNotification(notification);
+ String projectUuid = taskFailureNotification.getProject().getUuid();
+ String projectFullName = computeProjectFullName(taskFailureNotification.getProject());
+
+ return new EmailMessage()
+ .setMessageId(notification.getType() + "/" + projectUuid)
+ .setSubject(subject(projectFullName))
+ .setMessage(message(projectFullName, taskFailureNotification));
+ }
+
+ private static String computeProjectFullName(ReportAnalysisFailureNotification.Project project) {
+ String branchName = project.getBranchName();
+ if (branchName != null) {
+ return String.format("%s (%s)", project.getName(), branchName);
+ }
+ return project.getName();
+ }
+
+ private static String subject(String projectFullName) {
+ return String.format("%s: Background task in failure", projectFullName);
+ }
+
+ private String message(String projectFullName, ReportAnalysisFailureNotification taskFailureNotification) {
+ ReportAnalysisFailureNotification.Project project = taskFailureNotification.getProject();
+ ReportAnalysisFailureNotification.Task task = taskFailureNotification.getTask();
+
+ StringBuilder res = new StringBuilder();
+ res.append("Project:").append(TAB).append(projectFullName).append(LINE_RETURN);
+ res.append("Background task:").append(TAB).append(task.getUuid()).append(LINE_RETURN);
+ res.append("Submission time:").append(TAB).append(formatDateTime(task.getCreatedAt())).append(LINE_RETURN);
+ res.append("Failure time:").append(TAB).append(formatDateTime(task.getFailedAt())).append(LINE_RETURN);
+
+ String errorMessage = taskFailureNotification.getErrorMessage();
+ if (errorMessage != null) {
+ res.append(LINE_RETURN);
+ res.append("Error message:").append(TAB).append(errorMessage).append(LINE_RETURN);
+ }
+
+ res.append(LINE_RETURN);
+ res.append("More details at: ").append(String.format("%s/project/background_tasks?id=%s", settings.getServerBaseURL(), encode(project.getKey())));
+
+ return res.toString();
+ }
+
+ private static String encode(String toEncode) {
+ try {
+ return URLEncoder.encode(toEncode, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalStateException("Encoding not supported", e);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.sonar.ce.notification;
+
+import org.sonar.core.platform.Module;
+
+public class ReportAnalysisFailureNotificationModule extends Module {
+ @Override
+ protected void configureModule() {
+ add(
+ ReportAnalysisFailureNotificationDispatcher.class,
+ ReportAnalysisFailureNotificationDispatcher.newMetadata(),
+ ReportAnalysisFailureNotificationSerializerImpl.class,
+ ReportAnalysisFailureNotificationEmailTemplate.class);
+ }
+}
--- /dev/null
+/*
+ * 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.sonar.ce.notification;
+
+import org.sonar.api.notifications.Notification;
+
+public interface ReportAnalysisFailureNotificationSerializer {
+ Notification toNotification(ReportAnalysisFailureNotification reportAnalysisFailureNotification);
+
+ ReportAnalysisFailureNotification fromNotification(Notification notification);
+}
--- /dev/null
+/*
+ * 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.sonar.ce.notification;
+
+import org.sonar.api.notifications.Notification;
+
+import static java.lang.String.valueOf;
+
+public class ReportAnalysisFailureNotificationSerializerImpl implements ReportAnalysisFailureNotificationSerializer {
+ private static final String FIELD_PROJECT_UUID = "project.uuid";
+ private static final String FIELD_PROJECT_KEY = "project.key";
+ private static final String FIELD_PROJECT_NAME = "project.name";
+ private static final String FIELD_PROJECT_BRANCH = "project.branchName";
+ private static final String FIELD_TASK_UUID = "task.uuid";
+ private static final String FIELD_TASK_CREATED_AT = "task.createdAt";
+ private static final String FIELD_TASK_FAILED_AT = "task.failedAt";
+ private static final String FIELD_ERROR_MESSAGE = "error.message";
+
+ @Override
+ public Notification toNotification(ReportAnalysisFailureNotification reportAnalysisFailureNotification) {
+ return new Notification(ReportAnalysisFailureNotification.TYPE)
+ .setFieldValue(FIELD_PROJECT_UUID, reportAnalysisFailureNotification.getProject().getUuid())
+ .setFieldValue(FIELD_PROJECT_KEY, reportAnalysisFailureNotification.getProject().getKey())
+ .setFieldValue(FIELD_PROJECT_NAME, reportAnalysisFailureNotification.getProject().getName())
+ .setFieldValue(FIELD_PROJECT_BRANCH, reportAnalysisFailureNotification.getProject().getBranchName())
+ .setFieldValue(FIELD_TASK_UUID, reportAnalysisFailureNotification.getTask().getUuid())
+ .setFieldValue(FIELD_TASK_CREATED_AT, valueOf(reportAnalysisFailureNotification.getTask().getCreatedAt()))
+ .setFieldValue(FIELD_TASK_FAILED_AT, valueOf(reportAnalysisFailureNotification.getTask().getFailedAt()))
+ .setFieldValue(FIELD_ERROR_MESSAGE, reportAnalysisFailureNotification.getErrorMessage());
+ }
+
+ @Override
+ public ReportAnalysisFailureNotification fromNotification(Notification notification) {
+ return new ReportAnalysisFailureNotification(
+ new ReportAnalysisFailureNotification.Project(
+ notification.getFieldValue(FIELD_PROJECT_UUID),
+ notification.getFieldValue(FIELD_PROJECT_KEY),
+ notification.getFieldValue(FIELD_PROJECT_NAME),
+ notification.getFieldValue(FIELD_PROJECT_BRANCH)),
+ new ReportAnalysisFailureNotification.Task(
+ notification.getFieldValue(FIELD_TASK_UUID),
+ Long.valueOf(notification.getFieldValue(FIELD_TASK_CREATED_AT)),
+ Long.valueOf(notification.getFieldValue(FIELD_TASK_FAILED_AT))),
+ notification.getFieldValue(FIELD_ERROR_MESSAGE));
+ }
+}
--- /dev/null
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.ce.notification;
+
+import javax.annotation.ParametersAreNonnullByDefault;
import java.util.Arrays;
import java.util.List;
-
import javax.annotation.Nullable;
-
import org.sonar.ce.organization.DefaultOrganizationLoader;
import org.sonar.ce.queue.CeTask;
import org.sonar.ce.settings.SettingsLoader;
import org.sonar.api.rules.XMLRuleParser;
import org.sonar.api.server.rule.RulesDefinitionXmlLoader;
import org.sonar.ce.CeModule;
+import org.sonar.ce.notification.ReportAnalysisFailureNotificationModule;
import org.sonar.ce.settings.ProjectConfigurationFactory;
import org.sonar.core.component.DefaultResourceTypes;
import org.sonar.core.timemachine.Periods;
PrivilegedPluginsStopper.class,
// Compute engine (must be after Views and Developer Cockpit)
+ ReportAnalysisFailureNotificationModule.class,
CeModule.class,
CeWsModule.class,
--- /dev/null
+/*
+ * 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.sonar.ce.notification;
+
+import com.google.common.collect.HashMultimap;
+import org.junit.Test;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.notifications.NotificationChannel;
+import org.sonar.server.notification.NotificationDispatcher;
+import org.sonar.server.notification.NotificationDispatcherMetadata;
+import org.sonar.server.notification.NotificationManager;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+public class ReportAnalysisFailureNotificationDispatcherTest {
+ private NotificationManager notificationManager = mock(NotificationManager.class);
+ private Notification notificationMock = mock(Notification.class);
+ private NotificationDispatcher.Context contextMock = mock(NotificationDispatcher.Context.class);
+ private ReportAnalysisFailureNotificationDispatcher underTest = new ReportAnalysisFailureNotificationDispatcher(notificationManager);
+
+ @Test
+ public void dispatcher_defines_key() {
+ assertThat(underTest.getKey()).isNotEmpty();
+ }
+
+ @Test
+ public void newMetadata_indicates_enabled_global_and_project_level_notifications() {
+ NotificationDispatcherMetadata metadata = ReportAnalysisFailureNotificationDispatcher.newMetadata();
+
+ assertThat(metadata.getProperty(NotificationDispatcherMetadata.GLOBAL_NOTIFICATION)).isEqualTo("true");
+ assertThat(metadata.getProperty(NotificationDispatcherMetadata.PER_PROJECT_NOTIFICATION)).isEqualTo("true");
+ }
+
+ @Test
+ public void performDispatch_has_no_effect_if_type_is_empty() {
+ when(notificationMock.getType()).thenReturn("");
+
+ underTest.performDispatch(notificationMock, contextMock);
+
+ verify(notificationMock).getType();
+ verifyNoMoreInteractions(notificationMock, contextMock);
+ }
+
+ @Test
+ public void performDispatch_has_no_effect_if_type_is_not_ReportAnalysisFailureNotification_TYPE() {
+ when(notificationMock.getType()).thenReturn(randomAlphanumeric(6));
+
+ underTest.performDispatch(notificationMock, contextMock);
+
+ verify(notificationMock).getType();
+ verifyNoMoreInteractions(notificationMock, contextMock);
+ }
+
+ @Test
+ public void performDispatch_adds_user_for_each_recipient_and_channel_for_the_component_uuid_in_the_notification() {
+ when(notificationMock.getType()).thenReturn(ReportAnalysisFailureNotification.TYPE);
+ String projectUuid = randomAlphanumeric(9);
+ when(notificationMock.getFieldValue("project.uuid")).thenReturn(projectUuid);
+ HashMultimap<String, NotificationChannel> multimap = HashMultimap.create();
+ String login1 = randomAlphanumeric(3);
+ String login2 = randomAlphanumeric(3);
+ NotificationChannel channel1 = mock(NotificationChannel.class);
+ NotificationChannel channel2 = mock(NotificationChannel.class);
+ NotificationChannel channel3 = mock(NotificationChannel.class);
+ multimap.put(login1, channel1);
+ multimap.put(login1, channel2);
+ multimap.put(login2, channel2);
+ multimap.put(login2, channel3);
+ when(notificationManager.findSubscribedRecipientsForDispatcher(underTest, projectUuid))
+ .thenReturn(multimap);
+
+ underTest.performDispatch(notificationMock, contextMock);
+
+ verify(contextMock).addUser(login1, channel1);
+ verify(contextMock).addUser(login1, channel2);
+ verify(contextMock).addUser(login2, channel2);
+ verify(contextMock).addUser(login2, channel3);
+ verifyNoMoreInteractions(contextMock);
+ }
+
+ @Test
+ public void performDispatch_adds_no_user_if_notification_manager_returns_none() {
+ when(notificationMock.getType()).thenReturn(ReportAnalysisFailureNotification.TYPE);
+ String projectUuid = randomAlphanumeric(9);
+ when(notificationMock.getFieldValue("project.uuid")).thenReturn(projectUuid);
+ HashMultimap<String, NotificationChannel> multimap = HashMultimap.create();
+ when(notificationManager.findSubscribedRecipientsForDispatcher(underTest, projectUuid))
+ .thenReturn(multimap);
+
+ underTest.performDispatch(notificationMock, contextMock);
+
+ verifyNoMoreInteractions(contextMock);
+ }
+}
--- /dev/null
+/*
+ * 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.sonar.ce.notification;
+
+import java.util.Random;
+import org.apache.commons.lang.RandomStringUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.api.config.EmailSettings;
+import org.sonar.api.notifications.Notification;
+import org.sonar.api.utils.DateUtils;
+import org.sonar.plugins.emailnotifications.api.EmailMessage;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ReportAnalysisFailureNotificationEmailTemplateTest {
+ private String serverUrl = RandomStringUtils.randomAlphanumeric(12);
+ private Notification notification = new Notification(ReportAnalysisFailureNotification.TYPE);
+ private Random random = new Random();
+ private ReportAnalysisFailureNotification.Project projectNoBranch = new ReportAnalysisFailureNotification.Project(
+ randomAlphanumeric(2),
+ randomAlphanumeric(3),
+ randomAlphanumeric(4),
+ null);
+ private ReportAnalysisFailureNotification.Project projectWithBranch = new ReportAnalysisFailureNotification.Project(
+ randomAlphanumeric(6),
+ randomAlphanumeric(7),
+ randomAlphanumeric(8),
+ randomAlphanumeric(9));
+ private ReportAnalysisFailureNotification.Task task = new ReportAnalysisFailureNotification.Task(
+ randomAlphanumeric(10),
+ random.nextInt(99),
+ random.nextInt(99));
+ private String errorMessage = randomAlphanumeric(11);
+
+ private ReportAnalysisFailureNotificationSerializer serializer = mock(ReportAnalysisFailureNotificationSerializer.class);
+ private EmailSettings emailSettings = mock(EmailSettings.class);
+ private ReportAnalysisFailureNotificationEmailTemplate underTest = new ReportAnalysisFailureNotificationEmailTemplate(serializer, emailSettings);
+
+ @Before
+ public void setUp() throws Exception {
+ when(emailSettings.getServerBaseURL()).thenReturn(serverUrl);
+ }
+
+ @Test
+ public void returns_null_if_notification_type_is_not_ReportAnalysisFailureNotification_TYPE() {
+ Notification notification = new Notification(RandomStringUtils.randomAlphanumeric(5));
+
+ EmailMessage emailMessage = underTest.format(notification);
+
+ assertThat(emailMessage).isNull();
+ }
+
+ @Test
+ public void format_returns_email_with_subject_without_branch() {
+ when(serializer.fromNotification(notification)).thenReturn(new ReportAnalysisFailureNotification(
+ projectNoBranch, task, errorMessage));
+
+ EmailMessage emailMessage = underTest.format(notification);
+
+ assertThat(emailMessage.getSubject()).isEqualTo(projectNoBranch.getName() + ": Background task in failure");
+ }
+
+ @Test
+ public void format_returns_email_with_subject_with_branch() {
+ when(serializer.fromNotification(notification)).thenReturn(new ReportAnalysisFailureNotification(
+ projectWithBranch, task, errorMessage));
+
+ EmailMessage emailMessage = underTest.format(notification);
+
+ assertThat(emailMessage.getSubject()).isEqualTo(projectWithBranch.getName() + " (" + projectWithBranch.getBranchName() + "): Background task in failure");
+ }
+
+ @Test
+ public void format_returns_email_with_message_without_branch() {
+ when(serializer.fromNotification(notification)).thenReturn(new ReportAnalysisFailureNotification(
+ projectNoBranch, task, errorMessage));
+
+ EmailMessage emailMessage = underTest.format(notification);
+
+ assertThat(emailMessage.getMessage())
+ .contains("Project:\t" + projectNoBranch.getName() + "\n");
+ }
+
+ @Test
+ public void format_returns_email_with_message_with_branch() {
+ when(serializer.fromNotification(notification)).thenReturn(new ReportAnalysisFailureNotification(
+ projectWithBranch, task, errorMessage));
+
+ EmailMessage emailMessage = underTest.format(notification);
+
+ assertThat(emailMessage.getMessage())
+ .contains("Project:\t" + projectWithBranch.getName() + " (" + projectWithBranch.getBranchName() + ")\n");
+ }
+
+ @Test
+ public void format_returns_email_with_message_containing_all_information() {
+ when(serializer.fromNotification(notification)).thenReturn(new ReportAnalysisFailureNotification(
+ projectNoBranch, task, errorMessage));
+
+ EmailMessage emailMessage = underTest.format(notification);
+
+ assertThat(emailMessage.getMessage())
+ .isEqualTo("Project:\t" + projectNoBranch.getName() + "\n" +
+ "Background task:\t" + task.getUuid() + "\n" +
+ "Submission time:\t" + DateUtils.formatDateTime(task.getCreatedAt()) + "\n" +
+ "Failure time:\t" + DateUtils.formatDateTime(task.getFailedAt()) + "\n" +
+ "\n" +
+ "Error message:\t" + errorMessage + "\n" +
+ "\n" +
+ "More details at: " + serverUrl + "/project/background_tasks?id=" + projectNoBranch.getKey());
+ }
+
+ @Test
+ public void format_returns_email_with_message_with_error_message_when_null() {
+ when(serializer.fromNotification(notification)).thenReturn(new ReportAnalysisFailureNotification(
+ projectNoBranch, task, null));
+
+ EmailMessage emailMessage = underTest.format(notification);
+
+ assertThat(emailMessage.getMessage())
+ .doesNotContain("Error message:\t");
+ }
+}
--- /dev/null
+/*
+ * 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.sonar.ce.notification;
+
+import org.junit.Test;
+import org.sonar.core.platform.ComponentContainer;
+import org.sonar.server.notification.NotificationDispatcherMetadata;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ReportAnalysisFailureNotificationModuleTest {
+ private ReportAnalysisFailureNotificationModule underTest = new ReportAnalysisFailureNotificationModule();
+
+ @Test
+ public void adds_dispatcher_and_its_metadata() {
+ ComponentContainer container = new ComponentContainer();
+
+ underTest.configure(container);
+
+ assertThat(container.getPicoContainer().getComponentAdapters(NotificationDispatcherMetadata.class)).isNotNull();
+ assertThat(container.getPicoContainer().getComponentAdapters(ReportAnalysisFailureNotificationDispatcher.class)).isNotNull();
+ }
+
+ @Test
+ public void adds_template_and_serializer() {
+ ComponentContainer container = new ComponentContainer();
+
+ underTest.configure(container);
+
+ assertThat(container.getPicoContainer().getComponentAdapters(ReportAnalysisFailureNotificationEmailTemplate.class)).isNotNull();
+ assertThat(container.getPicoContainer().getComponentAdapters(ReportAnalysisFailureNotificationSerializer.class)).isNotNull();
+ }
+}
--- /dev/null
+/*
+ * 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.sonar.ce.notification;
+
+import java.util.Random;
+import org.junit.Test;
+import org.sonar.api.notifications.Notification;
+
+import static java.lang.String.valueOf;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ReportAnalysisFailureNotificationSerializerImplTest {
+ private Random random = new Random();
+ private ReportAnalysisFailureNotification.Project project = new ReportAnalysisFailureNotification.Project(
+ randomAlphanumeric(2),
+ randomAlphanumeric(3),
+ randomAlphanumeric(4),
+ randomAlphanumeric(5));
+ private ReportAnalysisFailureNotification.Task task = new ReportAnalysisFailureNotification.Task(
+ randomAlphanumeric(6),
+ random.nextInt(99),
+ random.nextInt(99));
+ private String errorMessage = randomAlphanumeric(7);
+ private ReportAnalysisFailureNotificationSerializerImpl underTest = new ReportAnalysisFailureNotificationSerializerImpl();
+
+ @Test
+ public void verify_toNotification() {
+
+ Notification notification = underTest.toNotification(new ReportAnalysisFailureNotification(project, task, errorMessage));
+
+ assertThat(notification.getFieldValue("project.uuid")).isEqualTo(project.getUuid());
+ assertThat(notification.getFieldValue("project.name")).isEqualTo(project.getName());
+ assertThat(notification.getFieldValue("project.key")).isEqualTo(project.getKey());
+ assertThat(notification.getFieldValue("project.branchName")).isEqualTo(project.getBranchName());
+ assertThat(notification.getFieldValue("task.uuid")).isEqualTo(task.getUuid());
+ assertThat(notification.getFieldValue("task.createdAt")).isEqualTo(valueOf(task.getCreatedAt()));
+ assertThat(notification.getFieldValue("task.failedAt")).isEqualTo(valueOf(task.getFailedAt()));
+ assertThat(notification.getFieldValue("error.message")).isEqualTo(errorMessage);
+ }
+
+ @Test
+ public void verify_fromNotification() {
+ Notification notification = new Notification(randomAlphanumeric(1))
+ .setFieldValue("project.uuid", project.getUuid())
+ .setFieldValue("project.name", project.getName())
+ .setFieldValue("project.key", project.getKey())
+ .setFieldValue("project.branchName", project.getBranchName())
+ .setFieldValue("task.uuid", task.getUuid())
+ .setFieldValue("task.createdAt", valueOf(task.getCreatedAt()))
+ .setFieldValue("task.failedAt", valueOf(task.getFailedAt()))
+ .setFieldValue("error.message", errorMessage);
+
+ ReportAnalysisFailureNotification reportAnalysisFailureNotification = underTest.fromNotification(notification);
+
+ assertThat(reportAnalysisFailureNotification.getProject().getUuid()).isEqualTo(project.getUuid());
+ assertThat(reportAnalysisFailureNotification.getProject().getKey()).isEqualTo(project.getKey());
+ assertThat(reportAnalysisFailureNotification.getProject().getName()).isEqualTo(project.getName());
+ assertThat(reportAnalysisFailureNotification.getProject().getBranchName()).isEqualTo(project.getBranchName());
+ assertThat(reportAnalysisFailureNotification.getTask().getUuid()).isEqualTo(task.getUuid());
+ assertThat(reportAnalysisFailureNotification.getTask().getCreatedAt()).isEqualTo(task.getCreatedAt());
+ assertThat(reportAnalysisFailureNotification.getTask().getFailedAt()).isEqualTo(task.getFailedAt());
+ assertThat(reportAnalysisFailureNotification.getErrorMessage()).isEqualTo(errorMessage);
+ }
+}
--- /dev/null
+/*
+ * 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.sonar.ce.notification;
+
+import java.util.Random;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class ReportAnalysisFailureNotificationTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private Random random = new Random();
+ private ReportAnalysisFailureNotification.Task task = new ReportAnalysisFailureNotification.Task(
+ randomAlphanumeric(2), random.nextInt(5_996), random.nextInt(9_635));
+ private ReportAnalysisFailureNotification.Project project = new ReportAnalysisFailureNotification.Project(
+ randomAlphanumeric(6), randomAlphanumeric(7), randomAlphanumeric(8), randomAlphanumeric(9));
+
+ @Test
+ public void project_constructor_fails_if_uuid_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("uuid can't be null");
+
+ new ReportAnalysisFailureNotification.Project(null, randomAlphanumeric(2), randomAlphanumeric(3), randomAlphanumeric(4));
+ }
+
+ @Test
+ public void project_constructor_fails_if_key_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("key can't be null");
+
+ new ReportAnalysisFailureNotification.Project(randomAlphanumeric(2), null, randomAlphanumeric(3), randomAlphanumeric(4));
+ }
+
+ @Test
+ public void project_constructor_fails_if_name_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("name can't be null");
+
+ new ReportAnalysisFailureNotification.Project(randomAlphanumeric(2), randomAlphanumeric(3), null, randomAlphanumeric(4));
+ }
+
+ @Test
+ public void verify_report_getters() {
+ String uuid = randomAlphanumeric(2);
+ String key = randomAlphanumeric(3);
+ String name = randomAlphanumeric(4);
+ String branchName = randomAlphanumeric(5);
+ ReportAnalysisFailureNotification.Project underTest = new ReportAnalysisFailureNotification.Project(uuid, key, name, branchName);
+
+ assertThat(underTest.getUuid()).isEqualTo(uuid);
+ assertThat(underTest.getName()).isEqualTo(name);
+ assertThat(underTest.getKey()).isEqualTo(key);
+ assertThat(underTest.getBranchName()).isEqualTo(branchName);
+ }
+
+ @Test
+ public void project_supports_null_branch() {
+ ReportAnalysisFailureNotification.Project underTest = new ReportAnalysisFailureNotification.Project(randomAlphanumeric(2), randomAlphanumeric(3), randomAlphanumeric(4), null);
+
+ assertThat(underTest.getBranchName()).isNull();
+ }
+
+ @Test
+ public void task_constructor_fails_if_uuid_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("uuid can't be null");
+
+ new ReportAnalysisFailureNotification.Task(null, random.nextInt(5_996), random.nextInt(9_635));
+ }
+
+ @Test
+ public void verify_task_getters() {
+ String uuid = randomAlphanumeric(6);
+ int createdAt = random.nextInt(5_996);
+ int failedAt = random.nextInt(9_635);
+
+ ReportAnalysisFailureNotification.Task underTest = new ReportAnalysisFailureNotification.Task(uuid, createdAt, failedAt);
+
+ assertThat(underTest.getUuid()).isEqualTo(uuid);
+ assertThat(underTest.getCreatedAt()).isEqualTo(createdAt);
+ assertThat(underTest.getFailedAt()).isEqualTo(failedAt);
+ }
+
+ @Test
+ public void constructor_fails_with_NPE_if_Project_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("project can't be null");
+
+ new ReportAnalysisFailureNotification(null, task, randomAlphanumeric(99));
+ }
+
+ @Test
+ public void constructor_fails_with_NPE_if_Task_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("task can't be null");
+
+ new ReportAnalysisFailureNotification(project, null, randomAlphanumeric(99));
+ }
+
+ @Test
+ public void verify_getters() {
+ String message = randomAlphanumeric(99);
+ ReportAnalysisFailureNotification underTest = new ReportAnalysisFailureNotification(project, task, message);
+
+ assertThat(underTest.getProject()).isSameAs(project);
+ assertThat(underTest.getTask()).isSameAs(task);
+ assertThat(underTest.getErrorMessage()).isSameAs(message);
+ }
+
+ @Test
+ public void null_error_message_is_supported() {
+ ReportAnalysisFailureNotification underTest = new ReportAnalysisFailureNotification(project, task, null);
+
+ assertThat(underTest.getErrorMessage()).isNull();
+ }
+}
notification.dispatcher.NewAlerts=New quality gate status
notification.dispatcher.NewFalsePositiveIssue=Issues resolved as false positive or won't fix
notification.dispatcher.SQ-MyNewIssues=My new issues
+notification.dispatcher.CeReportTaskFailure=Report processing failure
#------------------------------------------------------------------------------