3 * Copyright (C) 2009-2019 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 package org.sonar.ce.notification;
22 import java.util.Arrays;
23 import java.util.Optional;
24 import java.util.Random;
25 import javax.annotation.Nullable;
26 import org.junit.Before;
27 import org.junit.Rule;
28 import org.junit.Test;
29 import org.junit.rules.ExpectedException;
30 import org.mockito.ArgumentCaptor;
31 import org.sonar.api.notifications.Notification;
32 import org.sonar.api.utils.System2;
33 import org.sonar.ce.task.CeTask;
34 import org.sonar.ce.task.CeTaskResult;
35 import org.sonar.ce.task.projectanalysis.notification.ReportAnalysisFailureNotification;
36 import org.sonar.ce.task.projectanalysis.notification.ReportAnalysisFailureNotificationBuilder;
37 import org.sonar.ce.task.projectanalysis.notification.ReportAnalysisFailureNotificationSerializer;
38 import org.sonar.db.DbClient;
39 import org.sonar.db.DbTester;
40 import org.sonar.db.RowNotFoundException;
41 import org.sonar.db.ce.CeActivityDto;
42 import org.sonar.db.ce.CeQueueDto;
43 import org.sonar.db.ce.CeTaskTypes;
44 import org.sonar.db.component.ComponentDto;
45 import org.sonar.db.component.ComponentTesting;
46 import org.sonar.db.organization.OrganizationDto;
47 import org.sonar.db.organization.OrganizationTesting;
48 import org.sonar.server.notification.NotificationService;
50 import static java.util.Collections.singleton;
51 import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
52 import static org.assertj.core.api.Assertions.assertThat;
53 import static org.assertj.core.api.Assertions.fail;
54 import static org.mockito.ArgumentMatchers.any;
55 import static org.mockito.ArgumentMatchers.same;
56 import static org.mockito.Mockito.mock;
57 import static org.mockito.Mockito.verify;
58 import static org.mockito.Mockito.verifyZeroInteractions;
59 import static org.mockito.Mockito.when;
60 import static org.sonar.db.component.ComponentTesting.newDirectory;
61 import static org.sonar.db.component.ComponentTesting.newModuleDto;
63 public class ReportAnalysisFailureNotificationExecutionListenerTest {
65 public DbTester dbTester = DbTester.create(System2.INSTANCE);
67 public ExpectedException expectedException = ExpectedException.none();
69 private final Random random = new Random();
70 private DbClient dbClient = dbTester.getDbClient();
71 private NotificationService notificationService = mock(NotificationService.class);
72 private ReportAnalysisFailureNotificationSerializer serializer = mock(ReportAnalysisFailureNotificationSerializer.class);
73 private System2 system2 = mock(System2.class);
74 private DbClient dbClientMock = mock(DbClient.class);
75 private CeTask ceTaskMock = mock(CeTask.class);
76 private Throwable throwableMock = mock(Throwable.class);
77 private CeTaskResult ceTaskResultMock = mock(CeTaskResult.class);
78 private ReportAnalysisFailureNotificationExecutionListener fullMockedUnderTest = new ReportAnalysisFailureNotificationExecutionListener(
79 notificationService, dbClientMock, serializer, system2);
80 private ReportAnalysisFailureNotificationExecutionListener underTest = new ReportAnalysisFailureNotificationExecutionListener(
81 notificationService, dbClient, serializer, system2);
89 public void onStart_has_no_effect() {
90 CeTask mockedCeTask = mock(CeTask.class);
92 fullMockedUnderTest.onStart(mockedCeTask);
94 verifyZeroInteractions(mockedCeTask, notificationService, dbClientMock, serializer, system2);
98 public void onEnd_has_no_effect_if_status_is_SUCCESS() {
99 fullMockedUnderTest.onEnd(ceTaskMock, CeActivityDto.Status.SUCCESS, ceTaskResultMock, throwableMock);
101 verifyZeroInteractions(ceTaskMock, ceTaskResultMock, throwableMock, notificationService, dbClientMock, serializer, system2);
105 public void onEnd_has_no_effect_if_CeTask_type_is_not_report() {
106 when(ceTaskMock.getType()).thenReturn(randomAlphanumeric(12));
108 fullMockedUnderTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, throwableMock);
110 verifyZeroInteractions(ceTaskResultMock, throwableMock, notificationService, dbClientMock, serializer, system2);
114 public void onEnd_has_no_effect_if_CeTask_has_no_component_uuid() {
115 when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
117 fullMockedUnderTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, throwableMock);
119 verifyZeroInteractions(ceTaskResultMock, throwableMock, notificationService, dbClientMock, serializer, system2);
123 public void onEnd_has_no_effect_if_there_is_no_subscriber_for_ReportAnalysisFailureNotification_type() {
124 String componentUuid = randomAlphanumeric(6);
125 when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
126 when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
127 when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.class)))
130 fullMockedUnderTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, throwableMock);
132 verifyZeroInteractions(ceTaskResultMock, throwableMock, dbClientMock, serializer, system2);
136 public void onEnd_fails_with_RowNotFoundException_if_component_does_not_exist_in_DB() {
137 String componentUuid = randomAlphanumeric(6);
138 when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
139 when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
140 when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.class)))
143 expectedException.expect(RowNotFoundException.class);
144 expectedException.expectMessage("Component with uuid '" + componentUuid + "' not found");
146 underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, throwableMock);
150 public void onEnd_fails_with_IAE_if_component_is_not_a_project() {
151 when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
152 OrganizationDto organization = OrganizationTesting.newOrganizationDto();
153 ComponentDto project = dbTester.components().insertPrivateProject();
154 ComponentDto module = dbTester.components().insertComponent(newModuleDto(project));
155 ComponentDto directory = dbTester.components().insertComponent(newDirectory(module, randomAlphanumeric(12)));
156 ComponentDto file = dbTester.components().insertComponent(ComponentTesting.newFileDto(project));
157 ComponentDto view = dbTester.components().insertComponent(ComponentTesting.newView(organization));
158 ComponentDto subView = dbTester.components().insertComponent(ComponentTesting.newSubView(view));
159 ComponentDto projectCopy = dbTester.components().insertComponent(ComponentTesting.newProjectCopy(project, subView));
160 ComponentDto application = dbTester.components().insertComponent(ComponentTesting.newApplication(organization));
162 Arrays.asList(module, directory, file, view, subView, projectCopy, application)
163 .forEach(component -> {
165 when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(component.uuid(), null, null)));
166 when(notificationService.hasProjectSubscribersForTypes(component.uuid(), singleton(ReportAnalysisFailureNotification.class)))
169 underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, throwableMock);
171 fail("An IllegalArgumentException should have been thrown for component " + component);
172 } catch (IllegalArgumentException e) {
173 assertThat(e.getMessage()).isEqualTo(String.format(
174 "Component %s must be a project (scope=%s, qualifier=%s)", component.uuid(), component.scope(), component.qualifier()));
180 public void onEnd_fails_with_RowNotFoundException_if_activity_for_task_does_not_exist_in_DB() {
181 String componentUuid = randomAlphanumeric(6);
182 String taskUuid = randomAlphanumeric(6);
183 when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
184 when(ceTaskMock.getUuid()).thenReturn(taskUuid);
185 when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
186 when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.class)))
188 dbTester.components().insertPrivateProject(s -> s.setUuid(componentUuid));
190 expectedException.expect(RowNotFoundException.class);
191 expectedException.expectMessage("CeActivity with uuid '" + taskUuid + "' not found");
193 underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, throwableMock);
197 public void onEnd_creates_notification_with_data_from_activity_and_project_and_deliver_it() {
198 String taskUuid = randomAlphanumeric(12);
199 int createdAt = random.nextInt(999_999);
200 long executedAt = random.nextInt(999_999);
201 ComponentDto project = initMocksToPassConditions(taskUuid, createdAt, executedAt);
202 Notification notificationMock = mockSerializer();
204 underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, throwableMock);
206 ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
207 verify(notificationService).deliver(same(notificationMock));
209 ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder = notificationCaptor.getValue();
211 ReportAnalysisFailureNotificationBuilder.Project notificationProject = reportAnalysisFailureNotificationBuilder.getProject();
212 assertThat(notificationProject.getName()).isEqualTo(project.name());
213 assertThat(notificationProject.getKey()).isEqualTo(project.getKey());
214 assertThat(notificationProject.getUuid()).isEqualTo(project.uuid());
215 assertThat(notificationProject.getBranchName()).isEqualTo(project.getBranch());
216 ReportAnalysisFailureNotificationBuilder.Task notificationTask = reportAnalysisFailureNotificationBuilder.getTask();
217 assertThat(notificationTask.getUuid()).isEqualTo(taskUuid);
218 assertThat(notificationTask.getCreatedAt()).isEqualTo(createdAt);
219 assertThat(notificationTask.getFailedAt()).isEqualTo(executedAt);
223 public void onEnd_creates_notification_with_error_message_from_Throwable_argument_message() {
224 initMocksToPassConditions(randomAlphanumeric(12), random.nextInt(999_999), (long) random.nextInt(999_999));
225 String message = randomAlphanumeric(66);
226 when(throwableMock.getMessage()).thenReturn(message);
228 underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, throwableMock);
230 ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
232 ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder = notificationCaptor.getValue();
233 assertThat(reportAnalysisFailureNotificationBuilder.getErrorMessage()).isEqualTo(message);
237 public void onEnd_creates_notification_with_null_error_message_if_Throwable_is_null() {
238 String taskUuid = randomAlphanumeric(12);
239 initMocksToPassConditions(taskUuid, random.nextInt(999_999), (long) random.nextInt(999_999));
240 Notification notificationMock = mockSerializer();
242 underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, null);
244 verify(notificationService).deliver(same(notificationMock));
245 ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
247 ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder = notificationCaptor.getValue();
248 assertThat(reportAnalysisFailureNotificationBuilder.getErrorMessage()).isNull();
252 public void onEnd_ignores_null_CeTaskResult_argument() {
253 String taskUuid = randomAlphanumeric(12);
254 initMocksToPassConditions(taskUuid, random.nextInt(999_999), (long) random.nextInt(999_999));
255 Notification notificationMock = mockSerializer();
257 underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, null, null);
259 verify(notificationService).deliver(same(notificationMock));
263 public void onEnd_ignores_CeTaskResult_argument() {
264 String taskUuid = randomAlphanumeric(12);
265 initMocksToPassConditions(taskUuid, random.nextInt(999_999), (long) random.nextInt(999_999));
266 Notification notificationMock = mockSerializer();
268 underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, null);
270 verify(notificationService).deliver(same(notificationMock));
271 verifyZeroInteractions(ceTaskResultMock);
275 public void onEnd_uses_system_data_as_failedAt_if_task_has_no_executedAt() {
276 String taskUuid = randomAlphanumeric(12);
277 initMocksToPassConditions(taskUuid, random.nextInt(999_999), null);
278 long now = random.nextInt(999_999);
279 when(system2.now()).thenReturn(now);
280 Notification notificationMock = mockSerializer();
282 underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, ceTaskResultMock, null);
284 verify(notificationService).deliver(same(notificationMock));
285 ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
286 assertThat(notificationCaptor.getValue().getTask().getFailedAt()).isEqualTo(now);
289 private ReportAnalysisFailureNotification mockSerializer() {
290 ReportAnalysisFailureNotification notificationMock = mock(ReportAnalysisFailureNotification.class);
291 when(serializer.toNotification(any(ReportAnalysisFailureNotificationBuilder.class))).thenReturn(notificationMock);
292 return notificationMock;
295 private ComponentDto initMocksToPassConditions(String taskUuid, int createdAt, @Nullable Long executedAt) {
296 ComponentDto project = random.nextBoolean() ? dbTester.components().insertPrivateProject() : dbTester.components().insertPublicProject();
297 when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
298 when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(project.uuid(), null, null)));
299 when(ceTaskMock.getUuid()).thenReturn(taskUuid);
300 when(notificationService.hasProjectSubscribersForTypes(project.uuid(), singleton(ReportAnalysisFailureNotification.class)))
302 insertActivityDto(taskUuid, createdAt, executedAt, project);
306 private void insertActivityDto(String taskUuid, int createdAt, @Nullable Long executedAt, ComponentDto project) {
307 dbClient.ceActivityDao().insert(dbTester.getSession(), new CeActivityDto(new CeQueueDto()
309 .setTaskType(CeTaskTypes.REPORT)
310 .setComponentUuid(project.uuid())
311 .setCreatedAt(createdAt))
312 .setExecutedAt(executedAt)
313 .setStatus(CeActivityDto.Status.FAILED));
314 dbTester.getSession().commit();
317 private ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> verifyAndCaptureSerializedNotification() {
318 ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = ArgumentCaptor.forClass(ReportAnalysisFailureNotificationBuilder.class);
319 verify(serializer).toNotification(notificationCaptor.capture());
320 return notificationCaptor;