From 025f115ad199361ec591967d64b4d65a1e849f17 Mon Sep 17 00:00:00 2001 From: =?utf8?q?L=C3=A9o=20Geoffroy?= Date: Wed, 21 Jun 2023 09:52:44 +0200 Subject: [PATCH] SONAR-19674 Notification for background task failure should apply to all the branches --- ...ailureNotificationExecutionListenerIT.java | 116 ++++++++++++------ ...sFailureNotificationExecutionListener.java | 17 +-- 2 files changed, 88 insertions(+), 45 deletions(-) diff --git a/server/sonar-ce/src/it/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationExecutionListenerIT.java b/server/sonar-ce/src/it/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationExecutionListenerIT.java index 75c8812f05c..5e9f98b99b0 100644 --- a/server/sonar-ce/src/it/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationExecutionListenerIT.java +++ b/server/sonar-ce/src/it/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationExecutionListenerIT.java @@ -42,13 +42,16 @@ 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.ProjectDto; 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.anyOf; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.fail; @@ -62,7 +65,7 @@ import static org.sonar.db.component.ComponentTesting.newDirectory; public class ReportAnalysisFailureNotificationExecutionListenerIT { @Rule - public DbTester dbTester = DbTester.create(System2.INSTANCE); + public DbTester dbTester = DbTester.create(System2.INSTANCE, true); private final Random random = new Random(); private final DbClient dbClient = dbTester.getDbClient(); @@ -114,29 +117,30 @@ public class ReportAnalysisFailureNotificationExecutionListenerIT { @Test public void onEnd_has_no_effect_if_there_is_no_subscriber_for_ReportAnalysisFailureNotification_type() { - String componentUuid = randomAlphanumeric(6); + ProjectData projectData = dbTester.components().insertPrivateProject(); 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))) + when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(projectData.getMainBranchDto().getUuid(), null, null))); + when(notificationService.hasProjectSubscribersForTypes(projectData.projectUuid(), singleton(ReportAnalysisFailureNotification.class))) .thenReturn(false); - fullMockedUnderTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, throwableMock); + underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, throwableMock); - verifyNoInteractions(ceTaskResultMock, throwableMock, dbClientMock, serializer, system2); + verifyNoInteractions(ceTaskResultMock, throwableMock, serializer, system2); } @Test public void onEnd_fails_with_ISE_if_project_does_not_exist_in_DB() { - String componentUuid = randomAlphanumeric(6); + 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(componentUuid, singleton(ReportAnalysisFailureNotification.class))) + 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 " + componentUuid); + .hasMessage("Could not find project uuid " + branchDto.getProjectUuid()); } @Test @@ -153,50 +157,49 @@ public class ReportAnalysisFailureNotificationExecutionListenerIT { Duration randomDuration = randomDuration(); assertThatThrownBy(() -> underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration, ceTaskResultMock, throwableMock)) .isInstanceOf(IllegalStateException.class) - .hasMessage("Could not find a branch for project uuid " + componentUuid); + .hasMessage("Could not find a branch with uuid " + componentUuid); } @Test - public void onEnd_fails_with_IAE_if_component_is_not_a_project() { + public void onEnd_fails_with_IAE_if_component_is_not_a_branch() { when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT); - ComponentDto project = dbTester.components().insertPrivateProject().getMainBranchComponent(); - ComponentDto directory = dbTester.components().insertComponent(newDirectory(project, randomAlphanumeric(12))); - ComponentDto file = dbTester.components().insertComponent(ComponentTesting.newFileDto(project)); + 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(project, subView)); + 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); + 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 (IllegalArgumentException e) { - assertThat(e.getMessage()).isEqualTo(String.format("Component %s must be a project (qualifier=%s)", component.uuid(), component.qualifier())); } catch (IllegalStateException e) { - assertThat(e.getMessage()).isEqualTo("Could not find project uuid " + component.uuid()); + 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() { - String componentUuid = randomAlphanumeric(6); + 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(componentUuid, null, null))); - when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.class))) + when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(mainBranch.uuid(), null, null))); + when(notificationService.hasProjectSubscribersForTypes(projectData.projectUuid(), singleton(ReportAnalysisFailureNotification.class))) .thenReturn(true); - dbTester.components().insertPrivateProject(s -> s.setUuid(componentUuid)).getMainBranchComponent(); + Duration randomDuration = randomDuration(); assertThatThrownBy(() -> underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration, ceTaskResultMock, throwableMock)) @@ -209,7 +212,7 @@ public class ReportAnalysisFailureNotificationExecutionListenerIT { String taskUuid = randomAlphanumeric(12); int createdAt = random.nextInt(999_999); long executedAt = random.nextInt(999_999); - ComponentDto project = initMocksToPassConditions(taskUuid, createdAt, executedAt); + ProjectData project = initMocksToPassConditions(taskUuid, createdAt, executedAt); Notification notificationMock = mockSerializer(); underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, throwableMock); @@ -220,9 +223,9 @@ public class ReportAnalysisFailureNotificationExecutionListenerIT { ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder = notificationCaptor.getValue(); ReportAnalysisFailureNotificationBuilder.Project notificationProject = reportAnalysisFailureNotificationBuilder.project(); - assertThat(notificationProject.name()).isEqualTo(project.name()); - assertThat(notificationProject.key()).isEqualTo(project.getKey()); - assertThat(notificationProject.uuid()).isEqualTo(project.uuid()); + 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); @@ -230,6 +233,36 @@ public class ReportAnalysisFailureNotificationExecutionListenerIT { 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 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)); @@ -303,22 +336,31 @@ public class ReportAnalysisFailureNotificationExecutionListenerIT { return notificationMock; } - private ComponentDto initMocksToPassConditions(String taskUuid, int createdAt, @Nullable Long executedAt) { - ComponentDto project = random.nextBoolean() ? dbTester.components().insertPrivateProject().getMainBranchComponent() : dbTester.components().insertPublicProject().getMainBranchComponent(); + 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(project.uuid(), null, null))); + when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(branchComponent.uuid(), null, null))); when(ceTaskMock.getUuid()).thenReturn(taskUuid); - when(notificationService.hasProjectSubscribersForTypes(project.uuid(), singleton(ReportAnalysisFailureNotification.class))) + when(notificationService.hasProjectSubscribersForTypes(projectData.projectUuid(), singleton(ReportAnalysisFailureNotification.class))) .thenReturn(true); - insertActivityDto(taskUuid, createdAt, executedAt, project); - return project; + insertActivityDto(taskUuid, createdAt, executedAt, branchComponent); } - private void insertActivityDto(String taskUuid, int createdAt, @Nullable Long executedAt, ComponentDto project) { + 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(project.uuid()) + .setComponentUuid(branch.uuid()) .setCreatedAt(createdAt)) .setExecutedAt(executedAt) .setStatus(CeActivityDto.Status.FAILED)); diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationExecutionListener.java b/server/sonar-ce/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationExecutionListener.java index db89507ff10..179900ccee1 100644 --- a/server/sonar-ce/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationExecutionListener.java +++ b/server/sonar-ce/src/main/java/org/sonar/ce/notification/ReportAnalysisFailureNotificationExecutionListener.java @@ -67,17 +67,18 @@ public class ReportAnalysisFailureNotificationExecutionListener implements CeWor if (status == CeActivityDto.Status.SUCCESS) { return; } - String projectUuid = ceTask.getComponent().map(CeTask.Component::getUuid).orElse(null); - if (!CeTaskTypes.REPORT.equals(ceTask.getType()) || projectUuid == null) { + String branchUuid = ceTask.getComponent().map(CeTask.Component::getUuid).orElse(null); + if (!CeTaskTypes.REPORT.equals(ceTask.getType()) || branchUuid == null) { return; } - if (notificationService.hasProjectSubscribersForTypes(projectUuid, singleton(ReportAnalysisFailureNotification.class))) { - try (DbSession dbSession = dbClient.openSession(false)) { - ProjectDto projectDto = dbClient.projectDao().selectByUuid(dbSession, projectUuid) - .orElseThrow(() -> new IllegalStateException("Could not find project uuid " + projectUuid)); - BranchDto branchDto = dbClient.branchDao().selectByUuid(dbSession, projectDto.getUuid()) - .orElseThrow(() -> new IllegalStateException("Could not find a branch for project uuid " + projectDto.getUuid())); + try (DbSession dbSession = dbClient.openSession(false)) { + BranchDto branchDto = dbClient.branchDao().selectByUuid(dbSession, branchUuid) + .orElseThrow(() -> new IllegalStateException("Could not find a branch with uuid %s".formatted(branchUuid))); + if (notificationService.hasProjectSubscribersForTypes(branchDto.getProjectUuid(), singleton(ReportAnalysisFailureNotification.class))) { + ProjectDto projectDto = dbClient.projectDao().selectByUuid(dbSession, branchDto.getProjectUuid()) + .orElseThrow(() -> new IllegalStateException("Could not find project uuid %s".formatted(branchDto.getProjectUuid()))); + checkQualifier(projectDto); CeActivityDto ceActivityDto = dbClient.ceActivityDao().selectByUuid(dbSession, ceTask.getUuid()) .orElseThrow(() -> new RowNotFoundException(format("CeActivity with uuid '%s' not found", ceTask.getUuid()))); -- 2.39.5