]> source.dussan.org Git - sonarqube.git/blob
f0184c087625ba8d5cf100807eededfe0e74dc03
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2023 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
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.
10  *
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.
15  *
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.
19  */
20 package org.sonar.ce.notification;
21
22 import java.time.Duration;
23 import java.time.temporal.ChronoUnit;
24 import java.util.Arrays;
25 import java.util.Optional;
26 import java.util.Random;
27 import javax.annotation.Nullable;
28 import org.junit.Rule;
29 import org.junit.Test;
30 import org.mockito.ArgumentCaptor;
31 import org.sonar.api.notifications.Notification;
32 import org.sonar.api.resources.Qualifiers;
33 import org.sonar.api.utils.System2;
34 import org.sonar.ce.task.CeTask;
35 import org.sonar.ce.task.CeTaskResult;
36 import org.sonar.ce.task.projectanalysis.notification.ReportAnalysisFailureNotification;
37 import org.sonar.ce.task.projectanalysis.notification.ReportAnalysisFailureNotificationBuilder;
38 import org.sonar.ce.task.projectanalysis.notification.ReportAnalysisFailureNotificationSerializer;
39 import org.sonar.db.DbClient;
40 import org.sonar.db.DbTester;
41 import org.sonar.db.RowNotFoundException;
42 import org.sonar.db.ce.CeActivityDto;
43 import org.sonar.db.ce.CeQueueDto;
44 import org.sonar.db.ce.CeTaskTypes;
45 import org.sonar.db.component.BranchDto;
46 import org.sonar.db.component.ComponentDto;
47 import org.sonar.db.component.ComponentTesting;
48 import org.sonar.db.component.ProjectData;
49 import org.sonar.db.project.ProjectDto;
50 import org.sonar.server.notification.NotificationService;
51
52 import static java.util.Collections.singleton;
53 import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
54 import static org.assertj.core.api.Assertions.assertThat;
55 import static org.assertj.core.api.Assertions.assertThatThrownBy;
56 import static org.assertj.core.api.Assertions.fail;
57 import static org.mockito.ArgumentMatchers.any;
58 import static org.mockito.ArgumentMatchers.same;
59 import static org.mockito.Mockito.mock;
60 import static org.mockito.Mockito.verify;
61 import static org.mockito.Mockito.verifyNoInteractions;
62 import static org.mockito.Mockito.when;
63 import static org.sonar.db.component.ComponentTesting.newDirectory;
64
65 public class ReportAnalysisFailureNotificationExecutionListenerIT {
66   @Rule
67   public DbTester dbTester = DbTester.create(System2.INSTANCE);
68
69   private final Random random = new Random();
70   private final DbClient dbClient = dbTester.getDbClient();
71   private final NotificationService notificationService = mock(NotificationService.class);
72   private final ReportAnalysisFailureNotificationSerializer serializer = mock(ReportAnalysisFailureNotificationSerializer.class);
73   private final System2 system2 = mock(System2.class);
74   private final DbClient dbClientMock = mock(DbClient.class);
75   private final CeTask ceTaskMock = mock(CeTask.class);
76   private final Throwable throwableMock = mock(Throwable.class);
77   private final CeTaskResult ceTaskResultMock = mock(CeTaskResult.class);
78   private final ReportAnalysisFailureNotificationExecutionListener fullMockedUnderTest = new ReportAnalysisFailureNotificationExecutionListener(
79     notificationService, dbClientMock, serializer, system2);
80   private final ReportAnalysisFailureNotificationExecutionListener underTest = new ReportAnalysisFailureNotificationExecutionListener(
81     notificationService, dbClient, serializer, system2);
82
83   @Test
84   public void onStart_has_no_effect() {
85     CeTask mockedCeTask = mock(CeTask.class);
86
87     fullMockedUnderTest.onStart(mockedCeTask);
88
89     verifyNoInteractions(mockedCeTask, notificationService, dbClientMock, serializer, system2);
90   }
91
92   @Test
93   public void onEnd_has_no_effect_if_status_is_SUCCESS() {
94     fullMockedUnderTest.onEnd(ceTaskMock, CeActivityDto.Status.SUCCESS, randomDuration(), ceTaskResultMock, throwableMock);
95
96     verifyNoInteractions(ceTaskMock, ceTaskResultMock, throwableMock, notificationService, dbClientMock, serializer, system2);
97   }
98
99   @Test
100   public void onEnd_has_no_effect_if_CeTask_type_is_not_report() {
101     when(ceTaskMock.getType()).thenReturn(randomAlphanumeric(12));
102
103     fullMockedUnderTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, throwableMock);
104
105     verifyNoInteractions(ceTaskResultMock, throwableMock, notificationService, dbClientMock, serializer, system2);
106   }
107
108   @Test
109   public void onEnd_has_no_effect_if_CeTask_has_no_component_uuid() {
110     when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
111
112     fullMockedUnderTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, throwableMock);
113
114     verifyNoInteractions(ceTaskResultMock, throwableMock, notificationService, dbClientMock, serializer, system2);
115   }
116
117   @Test
118   public void onEnd_has_no_effect_if_there_is_no_subscriber_for_ReportAnalysisFailureNotification_type() {
119     ProjectData projectData = dbTester.components().insertPrivateProject();
120     when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
121     when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(projectData.getMainBranchDto().getUuid(), null, null)));
122     when(notificationService.hasProjectSubscribersForTypes(projectData.projectUuid(), singleton(ReportAnalysisFailureNotification.class)))
123       .thenReturn(false);
124
125     underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, throwableMock);
126
127     verifyNoInteractions(ceTaskResultMock, throwableMock, serializer, system2);
128   }
129
130   @Test
131   public void onEnd_fails_with_ISE_if_project_does_not_exist_in_DB() {
132     BranchDto branchDto = dbTester.components().insertProjectBranch(new ProjectDto().setUuid("uuid").setKee("kee").setName("name"));
133     String componentUuid = branchDto.getUuid();
134     when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
135     when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
136     when(notificationService.hasProjectSubscribersForTypes(branchDto.getProjectUuid(), singleton(ReportAnalysisFailureNotification.class)))
137       .thenReturn(true);
138
139     Duration randomDuration = randomDuration();
140     assertThatThrownBy(() -> underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration, ceTaskResultMock, throwableMock))
141       .isInstanceOf(IllegalStateException.class)
142       .hasMessage("Could not find project uuid " + branchDto.getProjectUuid());
143   }
144
145   @Test
146   public void onEnd_fails_with_ISE_if_branch_does_not_exist_in_DB() {
147     String componentUuid = randomAlphanumeric(6);
148     ProjectDto project = new ProjectDto().setUuid(componentUuid).setKey(randomAlphanumeric(5)).setQualifier(Qualifiers.PROJECT);
149     dbTester.getDbClient().projectDao().insert(dbTester.getSession(), project);
150     dbTester.getSession().commit();
151     when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
152     when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
153     when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.class)))
154       .thenReturn(true);
155
156     Duration randomDuration = randomDuration();
157     assertThatThrownBy(() -> underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration, ceTaskResultMock, throwableMock))
158       .isInstanceOf(IllegalStateException.class)
159       .hasMessage("Could not find a branch with uuid " + componentUuid);
160   }
161
162   @Test
163   public void onEnd_fails_with_IAE_if_component_is_not_a_branch() {
164     when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
165     ComponentDto mainBranch = dbTester.components().insertPrivateProject().getMainBranchComponent();
166     ComponentDto directory = dbTester.components().insertComponent(newDirectory(mainBranch, randomAlphanumeric(12)));
167     ComponentDto file = dbTester.components().insertComponent(ComponentTesting.newFileDto(mainBranch));
168     ComponentDto view = dbTester.components().insertComponent(ComponentTesting.newPortfolio());
169     ComponentDto subView = dbTester.components().insertComponent(ComponentTesting.newSubPortfolio(view));
170     ComponentDto projectCopy = dbTester.components().insertComponent(ComponentTesting.newProjectCopy(mainBranch, subView));
171     ComponentDto application = dbTester.components().insertComponent(ComponentTesting.newApplication());
172
173     Arrays.asList(directory, file, view, subView, projectCopy, application)
174       .forEach(component -> {
175
176         when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(component.uuid(), null, null)));
177         when(notificationService.hasProjectSubscribersForTypes(component.uuid(), singleton(ReportAnalysisFailureNotification.class)))
178           .thenReturn(true);
179
180         Duration randomDuration = randomDuration();
181         try {
182           underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration, ceTaskResultMock, throwableMock);
183
184           fail("An IllegalArgumentException should have been thrown for component " + component);
185         } catch (IllegalStateException e) {
186           assertThat(e.getMessage()).isIn("Could not find project uuid " + component.uuid(), "Could not find a branch with uuid " + component.uuid());
187         }
188       });
189   }
190
191   @Test
192   public void onEnd_fails_with_RowNotFoundException_if_activity_for_task_does_not_exist_in_DB() {
193     ProjectData projectData = dbTester.components().insertPrivateProject();
194     ComponentDto mainBranch = projectData.getMainBranchComponent();
195     String taskUuid = randomAlphanumeric(6);
196     when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
197     when(ceTaskMock.getUuid()).thenReturn(taskUuid);
198     when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(mainBranch.uuid(), null, null)));
199     when(notificationService.hasProjectSubscribersForTypes(projectData.projectUuid(), singleton(ReportAnalysisFailureNotification.class)))
200       .thenReturn(true);
201
202
203     Duration randomDuration = randomDuration();
204     assertThatThrownBy(() -> underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration, ceTaskResultMock, throwableMock))
205       .isInstanceOf(RowNotFoundException.class)
206       .hasMessage("CeActivity with uuid '" + taskUuid + "' not found");
207   }
208
209   @Test
210   public void onEnd_creates_notification_with_data_from_activity_and_project_and_deliver_it() {
211     String taskUuid = randomAlphanumeric(12);
212     int createdAt = random.nextInt(999_999);
213     long executedAt = random.nextInt(999_999);
214     ProjectData project = initMocksToPassConditions(taskUuid, createdAt, executedAt);
215     Notification notificationMock = mockSerializer();
216
217     underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, throwableMock);
218
219     ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
220     verify(notificationService).deliver(same(notificationMock));
221
222     ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder = notificationCaptor.getValue();
223
224     ReportAnalysisFailureNotificationBuilder.Project notificationProject = reportAnalysisFailureNotificationBuilder.project();
225     assertThat(notificationProject.name()).isEqualTo(project.getProjectDto().getName());
226     assertThat(notificationProject.key()).isEqualTo(project.getProjectDto().getKey());
227     assertThat(notificationProject.uuid()).isEqualTo(project.getProjectDto().getUuid());
228     assertThat(notificationProject.branchName()).isNull();
229     ReportAnalysisFailureNotificationBuilder.Task notificationTask = reportAnalysisFailureNotificationBuilder.task();
230     assertThat(notificationTask.uuid()).isEqualTo(taskUuid);
231     assertThat(notificationTask.createdAt()).isEqualTo(createdAt);
232     assertThat(notificationTask.failedAt()).isEqualTo(executedAt);
233   }
234
235   @Test
236   public void onEnd_shouldCreateNotificationWithDataFromActivity_whenNonMainBranchIsFailing() {
237     String taskUuid = randomAlphanumeric(12);
238     int createdAt = random.nextInt(999_999);
239     long executedAt = random.nextInt(999_999);
240
241     ProjectData project = random.nextBoolean() ? dbTester.components().insertPrivateProject() : dbTester.components().insertPublicProject();
242     ComponentDto branchComponent = dbTester.components().insertProjectBranch(project.getMainBranchComponent(), b->b.setKey("otherbranch"));
243     initMocksToPassConditionsForBranch(branchComponent, project, taskUuid, createdAt, executedAt);
244
245     Notification notificationMock = mockSerializer();
246
247     underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, throwableMock);
248
249     ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
250     verify(notificationService).deliver(same(notificationMock));
251
252     ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder = notificationCaptor.getValue();
253
254     ReportAnalysisFailureNotificationBuilder.Project notificationProject = reportAnalysisFailureNotificationBuilder.project();
255     assertThat(notificationProject.name()).isEqualTo(project.getProjectDto().getName());
256     assertThat(notificationProject.key()).isEqualTo(project.getProjectDto().getKey());
257     assertThat(notificationProject.uuid()).isEqualTo(project.getProjectDto().getUuid());
258     assertThat(notificationProject.branchName()).isEqualTo("otherbranch");
259     ReportAnalysisFailureNotificationBuilder.Task notificationTask = reportAnalysisFailureNotificationBuilder.task();
260     assertThat(notificationTask.uuid()).isEqualTo(taskUuid);
261     assertThat(notificationTask.createdAt()).isEqualTo(createdAt);
262     assertThat(notificationTask.failedAt()).isEqualTo(executedAt);
263   }
264
265   @Test
266   public void onEnd_creates_notification_with_error_message_from_Throwable_argument_message() {
267     initMocksToPassConditions(randomAlphanumeric(12), random.nextInt(999_999), (long) random.nextInt(999_999));
268     String message = randomAlphanumeric(66);
269     when(throwableMock.getMessage()).thenReturn(message);
270
271     underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, throwableMock);
272
273     ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
274
275     ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder = notificationCaptor.getValue();
276     assertThat(reportAnalysisFailureNotificationBuilder.errorMessage()).isEqualTo(message);
277   }
278
279   @Test
280   public void onEnd_creates_notification_with_null_error_message_if_Throwable_is_null() {
281     String taskUuid = randomAlphanumeric(12);
282     initMocksToPassConditions(taskUuid, random.nextInt(999_999), (long) random.nextInt(999_999));
283     Notification notificationMock = mockSerializer();
284
285     underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, null);
286
287     verify(notificationService).deliver(same(notificationMock));
288     ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
289
290     ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder = notificationCaptor.getValue();
291     assertThat(reportAnalysisFailureNotificationBuilder.errorMessage()).isNull();
292   }
293
294   @Test
295   public void onEnd_ignores_null_CeTaskResult_argument() {
296     String taskUuid = randomAlphanumeric(12);
297     initMocksToPassConditions(taskUuid, random.nextInt(999_999), (long) random.nextInt(999_999));
298     Notification notificationMock = mockSerializer();
299
300     underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), null, null);
301
302     verify(notificationService).deliver(same(notificationMock));
303   }
304
305   @Test
306   public void onEnd_ignores_CeTaskResult_argument() {
307     String taskUuid = randomAlphanumeric(12);
308     initMocksToPassConditions(taskUuid, random.nextInt(999_999), (long) random.nextInt(999_999));
309     Notification notificationMock = mockSerializer();
310
311     underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, null);
312
313     verify(notificationService).deliver(same(notificationMock));
314     verifyNoInteractions(ceTaskResultMock);
315   }
316
317   @Test
318   public void onEnd_uses_system_data_as_failedAt_if_task_has_no_executedAt() {
319     String taskUuid = randomAlphanumeric(12);
320     initMocksToPassConditions(taskUuid, random.nextInt(999_999), null);
321     long now = random.nextInt(999_999);
322     when(system2.now()).thenReturn(now);
323     Notification notificationMock = mockSerializer();
324
325     underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, null);
326
327     verify(notificationService).deliver(same(notificationMock));
328     ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
329     assertThat(notificationCaptor.getValue().task().failedAt()).isEqualTo(now);
330   }
331
332   private ReportAnalysisFailureNotification mockSerializer() {
333     ReportAnalysisFailureNotification notificationMock = mock(ReportAnalysisFailureNotification.class);
334     when(serializer.toNotification(any(ReportAnalysisFailureNotificationBuilder.class))).thenReturn(notificationMock);
335     return notificationMock;
336   }
337
338   private ProjectData initMocksToPassConditions(String taskUuid, int createdAt, @Nullable Long executedAt) {
339     ProjectData projectData = random.nextBoolean() ? dbTester.components().insertPrivateProject() : dbTester.components().insertPublicProject();
340     mockCeTask(taskUuid, createdAt, executedAt, projectData, projectData.getMainBranchComponent());
341     return projectData;
342   }
343
344   private ComponentDto initMocksToPassConditionsForBranch(ComponentDto branchComponent, ProjectData projectData, String taskUuid, int createdAt, @Nullable Long executedAt) {
345     mockCeTask(taskUuid, createdAt, executedAt, projectData, branchComponent);
346     return branchComponent;
347   }
348
349   private void mockCeTask(String taskUuid, int createdAt, @org.jetbrains.annotations.Nullable Long executedAt, ProjectData projectData, ComponentDto branchComponent) {
350     when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
351     when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(branchComponent.uuid(), null, null)));
352     when(ceTaskMock.getUuid()).thenReturn(taskUuid);
353     when(notificationService.hasProjectSubscribersForTypes(projectData.projectUuid(), singleton(ReportAnalysisFailureNotification.class)))
354       .thenReturn(true);
355     insertActivityDto(taskUuid, createdAt, executedAt, branchComponent);
356   }
357
358   private void insertActivityDto(String taskUuid, int createdAt, @Nullable Long executedAt, ComponentDto branch) {
359     dbClient.ceActivityDao().insert(dbTester.getSession(), new CeActivityDto(new CeQueueDto()
360       .setUuid(taskUuid)
361       .setTaskType(CeTaskTypes.REPORT)
362       .setComponentUuid(branch.uuid())
363       .setCreatedAt(createdAt))
364       .setExecutedAt(executedAt)
365       .setStatus(CeActivityDto.Status.FAILED));
366     dbTester.getSession().commit();
367   }
368
369   private ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> verifyAndCaptureSerializedNotification() {
370     ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = ArgumentCaptor.forClass(ReportAnalysisFailureNotificationBuilder.class);
371     verify(serializer).toNotification(notificationCaptor.capture());
372     return notificationCaptor;
373   }
374
375   private Duration randomDuration() {
376     return Duration.of(random.nextLong(), ChronoUnit.MILLIS);
377   }
378 }