123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378 |
- /*
- * SonarQube
- * Copyright (C) 2009-2024 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.time.Duration;
- import java.time.temporal.ChronoUnit;
- import java.util.Arrays;
- import java.util.Optional;
- import java.util.Random;
- import javax.annotation.Nullable;
- import org.junit.Rule;
- import org.junit.Test;
- import org.mockito.ArgumentCaptor;
- import org.sonar.api.notifications.Notification;
- import org.sonar.api.resources.Qualifiers;
- import org.sonar.api.utils.System2;
- import org.sonar.ce.task.CeTask;
- import org.sonar.ce.task.CeTaskResult;
- import org.sonar.ce.task.projectanalysis.notification.ReportAnalysisFailureNotification;
- import org.sonar.ce.task.projectanalysis.notification.ReportAnalysisFailureNotificationBuilder;
- import org.sonar.ce.task.projectanalysis.notification.ReportAnalysisFailureNotificationSerializer;
- 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.BranchDto;
- import org.sonar.db.component.ComponentDto;
- import org.sonar.db.component.ComponentTesting;
- import org.sonar.db.component.ProjectData;
- import org.sonar.db.project.CreationMethod;
- import org.sonar.db.project.ProjectDto;
- import org.sonar.server.notification.NotificationService;
-
- import static java.util.Collections.singleton;
- import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric;
- import static org.assertj.core.api.Assertions.assertThat;
- import static org.assertj.core.api.Assertions.assertThatThrownBy;
- import static org.assertj.core.api.Assertions.fail;
- import static org.mockito.ArgumentMatchers.any;
- import static org.mockito.ArgumentMatchers.same;
- import static org.mockito.Mockito.mock;
- import static org.mockito.Mockito.verify;
- import static org.mockito.Mockito.verifyNoInteractions;
- import static org.mockito.Mockito.when;
- import static org.sonar.db.component.ComponentTesting.newDirectory;
-
- public class ReportAnalysisFailureNotificationExecutionListenerIT {
- @Rule
- public DbTester dbTester = DbTester.create(System2.INSTANCE);
-
- private final Random random = new Random();
- private final DbClient dbClient = dbTester.getDbClient();
- private final NotificationService notificationService = mock(NotificationService.class);
- private final ReportAnalysisFailureNotificationSerializer serializer = mock(ReportAnalysisFailureNotificationSerializer.class);
- private final System2 system2 = mock(System2.class);
- private final DbClient dbClientMock = mock(DbClient.class);
- private final CeTask ceTaskMock = mock(CeTask.class);
- private final Throwable throwableMock = mock(Throwable.class);
- private final CeTaskResult ceTaskResultMock = mock(CeTaskResult.class);
- private final ReportAnalysisFailureNotificationExecutionListener fullMockedUnderTest = new ReportAnalysisFailureNotificationExecutionListener(
- notificationService, dbClientMock, serializer, system2);
- private final ReportAnalysisFailureNotificationExecutionListener underTest = new ReportAnalysisFailureNotificationExecutionListener(
- notificationService, dbClient, serializer, system2);
-
- @Test
- public void onStart_has_no_effect() {
- CeTask mockedCeTask = mock(CeTask.class);
-
- fullMockedUnderTest.onStart(mockedCeTask);
-
- verifyNoInteractions(mockedCeTask, notificationService, dbClientMock, serializer, system2);
- }
-
- @Test
- public void onEnd_has_no_effect_if_status_is_SUCCESS() {
- fullMockedUnderTest.onEnd(ceTaskMock, CeActivityDto.Status.SUCCESS, randomDuration(), ceTaskResultMock, throwableMock);
-
- verifyNoInteractions(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, randomDuration(), ceTaskResultMock, throwableMock);
-
- verifyNoInteractions(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, randomDuration(), ceTaskResultMock, throwableMock);
-
- verifyNoInteractions(ceTaskResultMock, throwableMock, notificationService, dbClientMock, serializer, system2);
- }
-
- @Test
- public void onEnd_has_no_effect_if_there_is_no_subscriber_for_ReportAnalysisFailureNotification_type() {
- ProjectData projectData = dbTester.components().insertPrivateProject();
- when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
- when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(projectData.getMainBranchDto().getUuid(), null, null)));
- when(notificationService.hasProjectSubscribersForTypes(projectData.projectUuid(), singleton(ReportAnalysisFailureNotification.class)))
- .thenReturn(false);
-
- underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, throwableMock);
-
- verifyNoInteractions(ceTaskResultMock, throwableMock, serializer, system2);
- }
-
- @Test
- public void onEnd_fails_with_ISE_if_project_does_not_exist_in_DB() {
- BranchDto branchDto = dbTester.components().insertProjectBranch(new ProjectDto().setUuid("uuid").setKee("kee").setName("name"));
- String componentUuid = branchDto.getUuid();
- when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
- when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
- when(notificationService.hasProjectSubscribersForTypes(branchDto.getProjectUuid(), singleton(ReportAnalysisFailureNotification.class)))
- .thenReturn(true);
-
- Duration randomDuration = randomDuration();
- assertThatThrownBy(() -> underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration, ceTaskResultMock, throwableMock))
- .isInstanceOf(IllegalStateException.class)
- .hasMessage("Could not find project uuid " + branchDto.getProjectUuid());
- }
-
- @Test
- public void onEnd_fails_with_ISE_if_branch_does_not_exist_in_DB() {
- String componentUuid = randomAlphanumeric(6);
- ProjectDto project = new ProjectDto().setUuid(componentUuid).setKey(randomAlphanumeric(5)).setQualifier(Qualifiers.PROJECT).setCreationMethod(CreationMethod.LOCAL_API);
- dbTester.getDbClient().projectDao().insert(dbTester.getSession(), project);
- dbTester.getSession().commit();
- when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
- when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
- when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.class)))
- .thenReturn(true);
-
- Duration randomDuration = randomDuration();
- assertThatThrownBy(() -> underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration, ceTaskResultMock, throwableMock))
- .isInstanceOf(IllegalStateException.class)
- .hasMessage("Could not find a branch with uuid " + componentUuid);
- }
-
- @Test
- public void onEnd_fails_with_IAE_if_component_is_not_a_branch() {
- when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
- ComponentDto mainBranch = dbTester.components().insertPrivateProject().getMainBranchComponent();
- ComponentDto directory = dbTester.components().insertComponent(newDirectory(mainBranch, randomAlphanumeric(12)));
- ComponentDto file = dbTester.components().insertComponent(ComponentTesting.newFileDto(mainBranch));
- ComponentDto view = dbTester.components().insertComponent(ComponentTesting.newPortfolio());
- ComponentDto subView = dbTester.components().insertComponent(ComponentTesting.newSubPortfolio(view));
- ComponentDto projectCopy = dbTester.components().insertComponent(ComponentTesting.newProjectCopy(mainBranch, subView));
- ComponentDto application = dbTester.components().insertComponent(ComponentTesting.newApplication());
-
- Arrays.asList(directory, file, view, subView, projectCopy, application)
- .forEach(component -> {
-
- when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(component.uuid(), null, null)));
- when(notificationService.hasProjectSubscribersForTypes(component.uuid(), singleton(ReportAnalysisFailureNotification.class)))
- .thenReturn(true);
-
- Duration randomDuration = randomDuration();
- try {
- underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration, ceTaskResultMock, throwableMock);
-
- fail("An IllegalArgumentException should have been thrown for component " + component);
- } catch (IllegalStateException e) {
- assertThat(e.getMessage()).isIn("Could not find project uuid " + component.uuid(), "Could not find a branch with uuid " + component.uuid());
- }
- });
- }
-
- @Test
- public void onEnd_fails_with_RowNotFoundException_if_activity_for_task_does_not_exist_in_DB() {
- ProjectData projectData = dbTester.components().insertPrivateProject();
- ComponentDto mainBranch = projectData.getMainBranchComponent();
- String taskUuid = randomAlphanumeric(6);
- when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
- when(ceTaskMock.getUuid()).thenReturn(taskUuid);
- when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(mainBranch.uuid(), null, null)));
- when(notificationService.hasProjectSubscribersForTypes(projectData.projectUuid(), singleton(ReportAnalysisFailureNotification.class)))
- .thenReturn(true);
-
- Duration randomDuration = randomDuration();
- assertThatThrownBy(() -> underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration, ceTaskResultMock, throwableMock))
- .isInstanceOf(RowNotFoundException.class)
- .hasMessage("CeActivity with uuid '" + taskUuid + "' not found");
- }
-
- @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);
- ProjectData project = initMocksToPassConditions(taskUuid, createdAt, executedAt);
- Notification notificationMock = mockSerializer();
-
- underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, throwableMock);
-
- ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
- verify(notificationService).deliver(same(notificationMock));
-
- ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder = notificationCaptor.getValue();
-
- ReportAnalysisFailureNotificationBuilder.Project notificationProject = reportAnalysisFailureNotificationBuilder.project();
- assertThat(notificationProject.name()).isEqualTo(project.getProjectDto().getName());
- assertThat(notificationProject.key()).isEqualTo(project.getProjectDto().getKey());
- assertThat(notificationProject.uuid()).isEqualTo(project.getProjectDto().getUuid());
- assertThat(notificationProject.branchName()).isNull();
- ReportAnalysisFailureNotificationBuilder.Task notificationTask = reportAnalysisFailureNotificationBuilder.task();
- assertThat(notificationTask.uuid()).isEqualTo(taskUuid);
- assertThat(notificationTask.createdAt()).isEqualTo(createdAt);
- assertThat(notificationTask.failedAt()).isEqualTo(executedAt);
- }
-
- @Test
- public void onEnd_shouldCreateNotificationWithDataFromActivity_whenNonMainBranchIsFailing() {
- String taskUuid = randomAlphanumeric(12);
- int createdAt = random.nextInt(999_999);
- long executedAt = random.nextInt(999_999);
-
- ProjectData project = random.nextBoolean() ? dbTester.components().insertPrivateProject() : dbTester.components().insertPublicProject();
- ComponentDto branchComponent = dbTester.components().insertProjectBranch(project.getMainBranchComponent(), b -> b.setKey("otherbranch"));
- initMocksToPassConditionsForBranch(branchComponent, project, taskUuid, createdAt, executedAt);
-
- Notification notificationMock = mockSerializer();
-
- underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, throwableMock);
-
- ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
- verify(notificationService).deliver(same(notificationMock));
-
- ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder = notificationCaptor.getValue();
-
- ReportAnalysisFailureNotificationBuilder.Project notificationProject = reportAnalysisFailureNotificationBuilder.project();
- assertThat(notificationProject.name()).isEqualTo(project.getProjectDto().getName());
- assertThat(notificationProject.key()).isEqualTo(project.getProjectDto().getKey());
- assertThat(notificationProject.uuid()).isEqualTo(project.getProjectDto().getUuid());
- assertThat(notificationProject.branchName()).isEqualTo("otherbranch");
- ReportAnalysisFailureNotificationBuilder.Task notificationTask = reportAnalysisFailureNotificationBuilder.task();
- assertThat(notificationTask.uuid()).isEqualTo(taskUuid);
- assertThat(notificationTask.createdAt()).isEqualTo(createdAt);
- assertThat(notificationTask.failedAt()).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, randomDuration(), ceTaskResultMock, throwableMock);
-
- ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
-
- ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder = notificationCaptor.getValue();
- assertThat(reportAnalysisFailureNotificationBuilder.errorMessage()).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, randomDuration(), ceTaskResultMock, null);
-
- verify(notificationService).deliver(same(notificationMock));
- ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
-
- ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder = notificationCaptor.getValue();
- assertThat(reportAnalysisFailureNotificationBuilder.errorMessage()).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, randomDuration(), 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, randomDuration(), ceTaskResultMock, null);
-
- verify(notificationService).deliver(same(notificationMock));
- verifyNoInteractions(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, randomDuration(), ceTaskResultMock, null);
-
- verify(notificationService).deliver(same(notificationMock));
- ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
- assertThat(notificationCaptor.getValue().task().failedAt()).isEqualTo(now);
- }
-
- private ReportAnalysisFailureNotification mockSerializer() {
- ReportAnalysisFailureNotification notificationMock = mock(ReportAnalysisFailureNotification.class);
- when(serializer.toNotification(any(ReportAnalysisFailureNotificationBuilder.class))).thenReturn(notificationMock);
- return notificationMock;
- }
-
- private ProjectData initMocksToPassConditions(String taskUuid, int createdAt, @Nullable Long executedAt) {
- ProjectData projectData = random.nextBoolean() ? dbTester.components().insertPrivateProject() : dbTester.components().insertPublicProject();
- mockCeTask(taskUuid, createdAt, executedAt, projectData, projectData.getMainBranchComponent());
- return projectData;
- }
-
- private ComponentDto initMocksToPassConditionsForBranch(ComponentDto branchComponent, ProjectData projectData, String taskUuid, int createdAt, @Nullable Long executedAt) {
- mockCeTask(taskUuid, createdAt, executedAt, projectData, branchComponent);
- return branchComponent;
- }
-
- private void mockCeTask(String taskUuid, int createdAt, @org.jetbrains.annotations.Nullable Long executedAt, ProjectData projectData, ComponentDto branchComponent) {
- when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
- when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(branchComponent.uuid(), null, null)));
- when(ceTaskMock.getUuid()).thenReturn(taskUuid);
- when(notificationService.hasProjectSubscribersForTypes(projectData.projectUuid(), singleton(ReportAnalysisFailureNotification.class)))
- .thenReturn(true);
- insertActivityDto(taskUuid, createdAt, executedAt, branchComponent);
- }
-
- private void insertActivityDto(String taskUuid, int createdAt, @Nullable Long executedAt, ComponentDto branch) {
- dbClient.ceActivityDao().insert(dbTester.getSession(), new CeActivityDto(new CeQueueDto()
- .setUuid(taskUuid)
- .setTaskType(CeTaskTypes.REPORT)
- .setComponentUuid(branch.uuid())
- .setCreatedAt(createdAt))
- .setExecutedAt(executedAt)
- .setStatus(CeActivityDto.Status.FAILED));
- dbTester.getSession().commit();
- }
-
- private ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> verifyAndCaptureSerializedNotification() {
- ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = ArgumentCaptor.forClass(ReportAnalysisFailureNotificationBuilder.class);
- verify(serializer).toNotification(notificationCaptor.capture());
- return notificationCaptor;
- }
-
- private Duration randomDuration() {
- return Duration.of(random.nextLong(), ChronoUnit.MILLIS);
- }
- }
|