]> source.dussan.org Git - sonarqube.git/blob
75c8812f05c545d242db70285e037d9882d269ac
[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.ComponentDto;
46 import org.sonar.db.component.ComponentTesting;
47 import org.sonar.db.project.ProjectDto;
48 import org.sonar.server.notification.NotificationService;
49
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.assertThatThrownBy;
54 import static org.assertj.core.api.Assertions.fail;
55 import static org.mockito.ArgumentMatchers.any;
56 import static org.mockito.ArgumentMatchers.same;
57 import static org.mockito.Mockito.mock;
58 import static org.mockito.Mockito.verify;
59 import static org.mockito.Mockito.verifyNoInteractions;
60 import static org.mockito.Mockito.when;
61 import static org.sonar.db.component.ComponentTesting.newDirectory;
62
63 public class ReportAnalysisFailureNotificationExecutionListenerIT {
64   @Rule
65   public DbTester dbTester = DbTester.create(System2.INSTANCE);
66
67   private final Random random = new Random();
68   private final DbClient dbClient = dbTester.getDbClient();
69   private final NotificationService notificationService = mock(NotificationService.class);
70   private final ReportAnalysisFailureNotificationSerializer serializer = mock(ReportAnalysisFailureNotificationSerializer.class);
71   private final System2 system2 = mock(System2.class);
72   private final DbClient dbClientMock = mock(DbClient.class);
73   private final CeTask ceTaskMock = mock(CeTask.class);
74   private final Throwable throwableMock = mock(Throwable.class);
75   private final CeTaskResult ceTaskResultMock = mock(CeTaskResult.class);
76   private final ReportAnalysisFailureNotificationExecutionListener fullMockedUnderTest = new ReportAnalysisFailureNotificationExecutionListener(
77     notificationService, dbClientMock, serializer, system2);
78   private final ReportAnalysisFailureNotificationExecutionListener underTest = new ReportAnalysisFailureNotificationExecutionListener(
79     notificationService, dbClient, serializer, system2);
80
81   @Test
82   public void onStart_has_no_effect() {
83     CeTask mockedCeTask = mock(CeTask.class);
84
85     fullMockedUnderTest.onStart(mockedCeTask);
86
87     verifyNoInteractions(mockedCeTask, notificationService, dbClientMock, serializer, system2);
88   }
89
90   @Test
91   public void onEnd_has_no_effect_if_status_is_SUCCESS() {
92     fullMockedUnderTest.onEnd(ceTaskMock, CeActivityDto.Status.SUCCESS, randomDuration(), ceTaskResultMock, throwableMock);
93
94     verifyNoInteractions(ceTaskMock, ceTaskResultMock, throwableMock, notificationService, dbClientMock, serializer, system2);
95   }
96
97   @Test
98   public void onEnd_has_no_effect_if_CeTask_type_is_not_report() {
99     when(ceTaskMock.getType()).thenReturn(randomAlphanumeric(12));
100
101     fullMockedUnderTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, throwableMock);
102
103     verifyNoInteractions(ceTaskResultMock, throwableMock, notificationService, dbClientMock, serializer, system2);
104   }
105
106   @Test
107   public void onEnd_has_no_effect_if_CeTask_has_no_component_uuid() {
108     when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
109
110     fullMockedUnderTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, throwableMock);
111
112     verifyNoInteractions(ceTaskResultMock, throwableMock, notificationService, dbClientMock, serializer, system2);
113   }
114
115   @Test
116   public void onEnd_has_no_effect_if_there_is_no_subscriber_for_ReportAnalysisFailureNotification_type() {
117     String componentUuid = randomAlphanumeric(6);
118     when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
119     when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
120     when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.class)))
121       .thenReturn(false);
122
123     fullMockedUnderTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, throwableMock);
124
125     verifyNoInteractions(ceTaskResultMock, throwableMock, dbClientMock, serializer, system2);
126   }
127
128   @Test
129   public void onEnd_fails_with_ISE_if_project_does_not_exist_in_DB() {
130     String componentUuid = randomAlphanumeric(6);
131     when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
132     when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
133     when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.class)))
134       .thenReturn(true);
135
136     Duration randomDuration = randomDuration();
137     assertThatThrownBy(() -> underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration, ceTaskResultMock, throwableMock))
138       .isInstanceOf(IllegalStateException.class)
139       .hasMessage("Could not find project uuid " + componentUuid);
140   }
141
142   @Test
143   public void onEnd_fails_with_ISE_if_branch_does_not_exist_in_DB() {
144     String componentUuid = randomAlphanumeric(6);
145     ProjectDto project = new ProjectDto().setUuid(componentUuid).setKey(randomAlphanumeric(5)).setQualifier(Qualifiers.PROJECT);
146     dbTester.getDbClient().projectDao().insert(dbTester.getSession(), project);
147     dbTester.getSession().commit();
148     when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
149     when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
150     when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.class)))
151       .thenReturn(true);
152
153     Duration randomDuration = randomDuration();
154     assertThatThrownBy(() -> underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration, ceTaskResultMock, throwableMock))
155       .isInstanceOf(IllegalStateException.class)
156       .hasMessage("Could not find a branch for project uuid " + componentUuid);
157   }
158
159   @Test
160   public void onEnd_fails_with_IAE_if_component_is_not_a_project() {
161     when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
162     ComponentDto project = dbTester.components().insertPrivateProject().getMainBranchComponent();
163     ComponentDto directory = dbTester.components().insertComponent(newDirectory(project, randomAlphanumeric(12)));
164     ComponentDto file = dbTester.components().insertComponent(ComponentTesting.newFileDto(project));
165     ComponentDto view = dbTester.components().insertComponent(ComponentTesting.newPortfolio());
166     ComponentDto subView = dbTester.components().insertComponent(ComponentTesting.newSubPortfolio(view));
167     ComponentDto projectCopy = dbTester.components().insertComponent(ComponentTesting.newProjectCopy(project, subView));
168     ComponentDto application = dbTester.components().insertComponent(ComponentTesting.newApplication());
169
170     Arrays.asList(directory, file, view, subView, projectCopy, application)
171       .forEach(component -> {
172
173       when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(component.uuid(), null, null)));
174       when(notificationService.hasProjectSubscribersForTypes(component.uuid(), singleton(ReportAnalysisFailureNotification.class)))
175         .thenReturn(true);
176
177         Duration randomDuration = randomDuration();
178         try {
179           underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration, ceTaskResultMock, throwableMock);
180
181           fail("An IllegalArgumentException should have been thrown for component " + component);
182         } catch (IllegalArgumentException e) {
183           assertThat(e.getMessage()).isEqualTo(String.format("Component %s must be a project (qualifier=%s)", component.uuid(), component.qualifier()));
184         } catch (IllegalStateException e) {
185           assertThat(e.getMessage()).isEqualTo("Could not find project uuid " + component.uuid());
186         }
187       });
188   }
189
190   @Test
191   public void onEnd_fails_with_RowNotFoundException_if_activity_for_task_does_not_exist_in_DB() {
192     String componentUuid = randomAlphanumeric(6);
193     String taskUuid = randomAlphanumeric(6);
194     when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
195     when(ceTaskMock.getUuid()).thenReturn(taskUuid);
196     when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(componentUuid, null, null)));
197     when(notificationService.hasProjectSubscribersForTypes(componentUuid, singleton(ReportAnalysisFailureNotification.class)))
198       .thenReturn(true);
199     dbTester.components().insertPrivateProject(s -> s.setUuid(componentUuid)).getMainBranchComponent();
200
201     Duration randomDuration = randomDuration();
202     assertThatThrownBy(() -> underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration, ceTaskResultMock, throwableMock))
203       .isInstanceOf(RowNotFoundException.class)
204       .hasMessage("CeActivity with uuid '" + taskUuid + "' not found");
205   }
206
207   @Test
208   public void onEnd_creates_notification_with_data_from_activity_and_project_and_deliver_it() {
209     String taskUuid = randomAlphanumeric(12);
210     int createdAt = random.nextInt(999_999);
211     long executedAt = random.nextInt(999_999);
212     ComponentDto project = initMocksToPassConditions(taskUuid, createdAt, executedAt);
213     Notification notificationMock = mockSerializer();
214
215     underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, throwableMock);
216
217     ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
218     verify(notificationService).deliver(same(notificationMock));
219
220     ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder = notificationCaptor.getValue();
221
222     ReportAnalysisFailureNotificationBuilder.Project notificationProject = reportAnalysisFailureNotificationBuilder.project();
223     assertThat(notificationProject.name()).isEqualTo(project.name());
224     assertThat(notificationProject.key()).isEqualTo(project.getKey());
225     assertThat(notificationProject.uuid()).isEqualTo(project.uuid());
226     assertThat(notificationProject.branchName()).isNull();
227     ReportAnalysisFailureNotificationBuilder.Task notificationTask = reportAnalysisFailureNotificationBuilder.task();
228     assertThat(notificationTask.uuid()).isEqualTo(taskUuid);
229     assertThat(notificationTask.createdAt()).isEqualTo(createdAt);
230     assertThat(notificationTask.failedAt()).isEqualTo(executedAt);
231   }
232
233   @Test
234   public void onEnd_creates_notification_with_error_message_from_Throwable_argument_message() {
235     initMocksToPassConditions(randomAlphanumeric(12), random.nextInt(999_999), (long) random.nextInt(999_999));
236     String message = randomAlphanumeric(66);
237     when(throwableMock.getMessage()).thenReturn(message);
238
239     underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, throwableMock);
240
241     ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
242
243     ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder = notificationCaptor.getValue();
244     assertThat(reportAnalysisFailureNotificationBuilder.errorMessage()).isEqualTo(message);
245   }
246
247   @Test
248   public void onEnd_creates_notification_with_null_error_message_if_Throwable_is_null() {
249     String taskUuid = randomAlphanumeric(12);
250     initMocksToPassConditions(taskUuid, random.nextInt(999_999), (long) random.nextInt(999_999));
251     Notification notificationMock = mockSerializer();
252
253     underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, null);
254
255     verify(notificationService).deliver(same(notificationMock));
256     ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
257
258     ReportAnalysisFailureNotificationBuilder reportAnalysisFailureNotificationBuilder = notificationCaptor.getValue();
259     assertThat(reportAnalysisFailureNotificationBuilder.errorMessage()).isNull();
260   }
261
262   @Test
263   public void onEnd_ignores_null_CeTaskResult_argument() {
264     String taskUuid = randomAlphanumeric(12);
265     initMocksToPassConditions(taskUuid, random.nextInt(999_999), (long) random.nextInt(999_999));
266     Notification notificationMock = mockSerializer();
267
268     underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), null, null);
269
270     verify(notificationService).deliver(same(notificationMock));
271   }
272
273   @Test
274   public void onEnd_ignores_CeTaskResult_argument() {
275     String taskUuid = randomAlphanumeric(12);
276     initMocksToPassConditions(taskUuid, random.nextInt(999_999), (long) random.nextInt(999_999));
277     Notification notificationMock = mockSerializer();
278
279     underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, null);
280
281     verify(notificationService).deliver(same(notificationMock));
282     verifyNoInteractions(ceTaskResultMock);
283   }
284
285   @Test
286   public void onEnd_uses_system_data_as_failedAt_if_task_has_no_executedAt() {
287     String taskUuid = randomAlphanumeric(12);
288     initMocksToPassConditions(taskUuid, random.nextInt(999_999), null);
289     long now = random.nextInt(999_999);
290     when(system2.now()).thenReturn(now);
291     Notification notificationMock = mockSerializer();
292
293     underTest.onEnd(ceTaskMock, CeActivityDto.Status.FAILED, randomDuration(), ceTaskResultMock, null);
294
295     verify(notificationService).deliver(same(notificationMock));
296     ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = verifyAndCaptureSerializedNotification();
297     assertThat(notificationCaptor.getValue().task().failedAt()).isEqualTo(now);
298   }
299
300   private ReportAnalysisFailureNotification mockSerializer() {
301     ReportAnalysisFailureNotification notificationMock = mock(ReportAnalysisFailureNotification.class);
302     when(serializer.toNotification(any(ReportAnalysisFailureNotificationBuilder.class))).thenReturn(notificationMock);
303     return notificationMock;
304   }
305
306   private ComponentDto initMocksToPassConditions(String taskUuid, int createdAt, @Nullable Long executedAt) {
307     ComponentDto project = random.nextBoolean() ? dbTester.components().insertPrivateProject().getMainBranchComponent() : dbTester.components().insertPublicProject().getMainBranchComponent();
308     when(ceTaskMock.getType()).thenReturn(CeTaskTypes.REPORT);
309     when(ceTaskMock.getComponent()).thenReturn(Optional.of(new CeTask.Component(project.uuid(), null, null)));
310     when(ceTaskMock.getUuid()).thenReturn(taskUuid);
311     when(notificationService.hasProjectSubscribersForTypes(project.uuid(), singleton(ReportAnalysisFailureNotification.class)))
312       .thenReturn(true);
313     insertActivityDto(taskUuid, createdAt, executedAt, project);
314     return project;
315   }
316
317   private void insertActivityDto(String taskUuid, int createdAt, @Nullable Long executedAt, ComponentDto project) {
318     dbClient.ceActivityDao().insert(dbTester.getSession(), new CeActivityDto(new CeQueueDto()
319       .setUuid(taskUuid)
320       .setTaskType(CeTaskTypes.REPORT)
321       .setComponentUuid(project.uuid())
322       .setCreatedAt(createdAt))
323       .setExecutedAt(executedAt)
324       .setStatus(CeActivityDto.Status.FAILED));
325     dbTester.getSession().commit();
326   }
327
328   private ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> verifyAndCaptureSerializedNotification() {
329     ArgumentCaptor<ReportAnalysisFailureNotificationBuilder> notificationCaptor = ArgumentCaptor.forClass(ReportAnalysisFailureNotificationBuilder.class);
330     verify(serializer).toNotification(notificationCaptor.capture());
331     return notificationCaptor;
332   }
333
334   private Duration randomDuration() {
335     return Duration.of(random.nextLong(), ChronoUnit.MILLIS);
336   }
337 }