]> source.dussan.org Git - sonarqube.git/blob
74bb003acf69997974dabaf691523a0c2ade6ad4
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2024 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.CreationMethod;
50 import org.sonar.db.project.ProjectDto;
51 import org.sonar.server.notification.NotificationService;
52
53 import static java.util.Collections.singleton;
54 import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric;
55 import static org.assertj.core.api.Assertions.assertThat;
56 import static org.assertj.core.api.Assertions.assertThatThrownBy;
57 import static org.assertj.core.api.Assertions.fail;
58 import static org.mockito.ArgumentMatchers.any;
59 import static org.mockito.ArgumentMatchers.same;
60 import static org.mockito.Mockito.mock;
61 import static org.mockito.Mockito.verify;
62 import static org.mockito.Mockito.verifyNoInteractions;
63 import static org.mockito.Mockito.when;
64 import static org.sonar.db.component.ComponentTesting.newDirectory;
65
66 public class ReportAnalysisFailureNotificationExecutionListenerIT {
67   @Rule
68   public DbTester dbTester = DbTester.create(System2.INSTANCE);
69
70   private final Random random = new Random();
71   private final DbClient dbClient = dbTester.getDbClient();
72   private final NotificationService notificationService = mock(NotificationService.class);
73   private final ReportAnalysisFailureNotificationSerializer serializer = mock(ReportAnalysisFailureNotificationSerializer.class);
74   private final System2 system2 = mock(System2.class);
75   private final DbClient dbClientMock = mock(DbClient.class);
76   private final CeTask ceTaskMock = mock(CeTask.class);
77   private final Throwable throwableMock = mock(Throwable.class);
78   private final CeTaskResult ceTaskResultMock = mock(CeTaskResult.class);
79   private final ReportAnalysisFailureNotificationExecutionListener fullMockedUnderTest = new ReportAnalysisFailureNotificationExecutionListener(
80     notificationService, dbClientMock, serializer, system2);
81   private final ReportAnalysisFailureNotificationExecutionListener underTest = new ReportAnalysisFailureNotificationExecutionListener(
82     notificationService, dbClient, serializer, system2);
83
84   @Test
85   public void onStart_has_no_effect() {
86     CeTask mockedCeTask = mock(CeTask.class);
87
88     fullMockedUnderTest.onStart(mockedCeTask);
89
90     verifyNoInteractions(mockedCeTask, notificationService, dbClientMock, serializer, system2);
91   }
92
93   @Test
94   public void onEnd_has_no_effect_if_status_is_SUCCESS() {
95     fullMockedUnderTest.onEnd(ceTaskMock, CeActivityDto.Status.SUCCESS, randomDuration(), ceTaskResultMock, throwableMock);
96
97     verifyNoInteractions(ceTaskMock, ceTaskResultMock, throwableMock, notificationService, dbClientMock, serializer, system2);
98   }
99
100   @Test
101   public void onEnd_has_no_effect_if_CeTask_type_is_not_report() {
102     when(ceTaskMock.getType()).thenReturn(randomAlphanumeric(12));
103
104     fullMockedUnderTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, throwableMock);
105
106     verifyNoInteractions(ceTaskResultMock, throwableMock, notificationService, dbClientMock, serializer, system2);
107   }
108
109   @Test
110   public void onEnd_has_no_effect_if_CeTask_has_no_component_uuid() {
111     when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
112
113     fullMockedUnderTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, throwableMock);
114
115     verifyNoInteractions(ceTaskResultMock, throwableMock, notificationService, dbClientMock, serializer, system2);
116   }
117
118   @Test
119   public void onEnd_has_no_effect_if_there_is_no_subscriber_for_ReportAnalysisFailureNotification_type() {
120     ProjectData projectData = dbTester.components().insertPrivateProject();
121     when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
122     when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(projectData.getMainBranchDto().getUuid(), null, null)));
123     when(notificationService.hasProjectSubscribersForTypes(projectData.projectUuid(), singleton(ReportAnalysisFailureNotification.class)))
124       .thenReturn(false);
125
126     underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, throwableMock);
127
128     verifyNoInteractions(ceTaskResultMock, throwableMock, serializer, system2);
129   }
130
131   @Test
132   public void onEnd_fails_with_ISE_if_project_does_not_exist_in_DB() {
133     BranchDto branchDto = dbTester.components().insertProjectBranch(new ProjectDto().setUuid("uuid").setKee("kee").setName("name"));
134     String componentUuid = branchDto.getUuid();
135     when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
136     when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
137     when(notificationService.hasProjectSubscribersForTypes(branchDto.getProjectUuid(), singleton(ReportAnalysisFailureNotification.class)))
138       .thenReturn(true);
139
140     Duration randomDuration = randomDuration();
141     assertThatThrownBy(() -> underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration, ceTaskResultMock, throwableMock))
142       .isInstanceOf(IllegalStateException.class)
143       .hasMessage("Could not find project uuid " + branchDto.getProjectUuid());
144   }
145
146   @Test
147   public void onEnd_fails_with_ISE_if_branch_does_not_exist_in_DB() {
148     String componentUuid = randomAlphanumeric(6);
149     ProjectDto project = new ProjectDto().setUuid(componentUuid).setKey(randomAlphanumeric(5)).setQualifier(Qualifiers.PROJECT).setCreationMethod(CreationMethod.LOCAL_API);
150     dbTester.getDbClient().projectDao().insert(dbTester.getSession(), project);
151     dbTester.getSession().commit();
152     when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
153     when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
154     when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.class)))
155       .thenReturn(true);
156
157     Duration randomDuration = randomDuration();
158     assertThatThrownBy(() -> underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration, ceTaskResultMock, throwableMock))
159       .isInstanceOf(IllegalStateException.class)
160       .hasMessage("Could not find a branch with uuid " + componentUuid);
161   }
162
163   @Test
164   public void onEnd_fails_with_IAE_if_component_is_not_a_branch() {
165     when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
166     ComponentDto mainBranch = dbTester.components().insertPrivateProject().getMainBranchComponent();
167     ComponentDto directory = dbTester.components().insertComponent(newDirectory(mainBranch, randomAlphanumeric(12)));
168     ComponentDto file = dbTester.components().insertComponent(ComponentTesting.newFileDto(mainBranch));
169     ComponentDto view = dbTester.components().insertComponent(ComponentTesting.newPortfolio());
170     ComponentDto subView = dbTester.components().insertComponent(ComponentTesting.newSubPortfolio(view));
171     ComponentDto projectCopy = dbTester.components().insertComponent(ComponentTesting.newProjectCopy(mainBranch, subView));
172     ComponentDto application = dbTester.components().insertComponent(ComponentTesting.newApplication());
173
174     Arrays.asList(directory, file, view, subView, projectCopy, application)
175       .forEach(component -> {
176
177         when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(component.uuid(), null, null)));
178         when(notificationService.hasProjectSubscribersForTypes(component.uuid(), singleton(ReportAnalysisFailureNotification.class)))
179           .thenReturn(true);
180
181         Duration randomDuration = randomDuration();
182         try {
183           underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration, ceTaskResultMock, throwableMock);
184
185           fail("An IllegalArgumentException should have been thrown for component " + component);
186         } catch (IllegalStateException e) {
187           assertThat(e.getMessage()).isIn("Could not find project uuid " + component.uuid(), "Could not find a branch with uuid " + component.uuid());
188         }
189       });
190   }
191
192   @Test
193   public void onEnd_fails_with_RowNotFoundException_if_activity_for_task_does_not_exist_in_DB() {
194     ProjectData projectData = dbTester.components().insertPrivateProject();
195     ComponentDto mainBranch = projectData.getMainBranchComponent();
196     String taskUuid = randomAlphanumeric(6);
197     when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
198     when(ceTaskMock.getUuid()).thenReturn(taskUuid);
199     when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(mainBranch.uuid(), null, null)));
200     when(notificationService.hasProjectSubscribersForTypes(projectData.projectUuid(), singleton(ReportAnalysisFailureNotification.class)))
201       .thenReturn(true);
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 }