3 * Copyright (C) 2009-2017 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.server.qualitygate.changeevent;
22 import com.google.common.collect.ImmutableList;
23 import com.google.common.collect.ImmutableMap;
24 import com.google.common.collect.ImmutableSet;
25 import com.tngtech.java.junit.dataprovider.DataProvider;
26 import com.tngtech.java.junit.dataprovider.DataProviderRunner;
27 import com.tngtech.java.junit.dataprovider.UseDataProvider;
28 import java.util.Arrays;
29 import java.util.Collection;
30 import java.util.Date;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.List;
35 import java.util.Random;
37 import java.util.stream.Collectors;
38 import java.util.stream.IntStream;
39 import java.util.stream.Stream;
40 import javax.annotation.Nullable;
41 import org.junit.Rule;
42 import org.junit.Test;
43 import org.junit.runner.RunWith;
44 import org.mockito.ArgumentCaptor;
45 import org.mockito.Matchers;
46 import org.mockito.Mockito;
47 import org.sonar.api.config.Configuration;
48 import org.sonar.api.issue.DefaultTransitions;
49 import org.sonar.api.issue.Issue;
50 import org.sonar.api.rules.RuleType;
51 import org.sonar.api.utils.System2;
52 import org.sonar.core.issue.IssueChangeContext;
53 import org.sonar.core.util.UuidFactoryFast;
54 import org.sonar.core.util.stream.MoreCollectors;
55 import org.sonar.db.DbClient;
56 import org.sonar.db.DbSession;
57 import org.sonar.db.DbTester;
58 import org.sonar.db.component.AnalysisPropertyDto;
59 import org.sonar.db.component.BranchDao;
60 import org.sonar.db.component.BranchDto;
61 import org.sonar.db.component.BranchType;
62 import org.sonar.db.component.ComponentDao;
63 import org.sonar.db.component.ComponentDto;
64 import org.sonar.db.component.ComponentTesting;
65 import org.sonar.db.component.SnapshotDao;
66 import org.sonar.db.component.SnapshotDto;
67 import org.sonar.db.issue.IssueDto;
68 import org.sonar.db.organization.OrganizationDto;
69 import org.sonar.server.qualitygate.changeevent.IssueChangeTrigger.IssueChange;
70 import org.sonar.server.settings.ProjectConfigurationLoader;
71 import org.sonar.server.tester.UserSessionRule;
72 import org.sonar.server.webhook.WebhookPayloadFactory;
74 import static com.google.common.base.Preconditions.checkArgument;
75 import static java.util.Arrays.asList;
76 import static java.util.Collections.emptyList;
77 import static java.util.Collections.singleton;
78 import static java.util.Collections.singletonList;
79 import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
80 import static org.assertj.core.api.Assertions.assertThat;
81 import static org.assertj.core.api.Assertions.tuple;
82 import static org.mockito.Mockito.mock;
83 import static org.sonar.core.util.stream.MoreCollectors.toArrayList;
84 import static org.sonar.db.component.ComponentTesting.newBranchDto;
86 @RunWith(DataProviderRunner.class)
87 public class IssueChangeTriggerImplTest {
89 public DbTester dbTester = DbTester.create(System2.INSTANCE);
91 public UserSessionRule userSessionRule = UserSessionRule.standalone();
93 private DbClient dbClient = dbTester.getDbClient();
95 private Random random = new Random();
96 private RuleType randomRuleType = RuleType.values()[random.nextInt(RuleType.values().length)];
98 private IssueChangeContext scanChangeContext = IssueChangeContext.createScan(new Date());
99 private IssueChangeContext userChangeContext = IssueChangeContext.createUser(new Date(), "userLogin");
100 private WebhookPayloadFactory webhookPayloadFactory = mock(WebhookPayloadFactory.class);
101 private DbClient spiedOnDbClient = Mockito.spy(dbClient);
102 private ProjectConfigurationLoader projectConfigurationLoader = mock(ProjectConfigurationLoader.class);
103 private QGChangeEventListeners qgChangeEventListeners = mock(QGChangeEventListeners.class);
104 private IssueChangeTriggerImpl underTest = new IssueChangeTriggerImpl(spiedOnDbClient, projectConfigurationLoader, qgChangeEventListeners);
105 private DbClient mockedDbClient = mock(DbClient.class);
106 private IssueChangeTriggerImpl mockedUnderTest = new IssueChangeTriggerImpl(mockedDbClient, projectConfigurationLoader, qgChangeEventListeners);
109 public void on_type_change_has_no_effect_if_SearchResponseData_has_no_issue() {
110 mockedUnderTest.onChange(issueChangeData(), new IssueChange(randomRuleType), userChangeContext);
112 Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
116 public void on_type_change_has_no_effect_if_scan_IssueChangeContext() {
117 mockedUnderTest.onChange(issueChangeData(), new IssueChange(randomRuleType), scanChangeContext);
119 Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
123 public void on_transition_change_has_no_effect_if_SearchResponseData_has_no_issue() {
124 mockedUnderTest.onChange(issueChangeData(), new IssueChange(randomAlphanumeric(12)), userChangeContext);
126 Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
130 public void onTransition_has_no_effect_if_transition_key_is_empty() {
131 on_transition_changeHasNoEffectForTransitionKey("");
135 public void onTransition_has_no_effect_if_transition_key_is_random() {
136 on_transition_changeHasNoEffectForTransitionKey(randomAlphanumeric(99));
140 public void on_transition_change_has_no_effect_if_transition_key_is_ignored_default_transition_key() {
141 Set<String> supportedDefaultTransitionKeys = ImmutableSet.of(
142 DefaultTransitions.RESOLVE, DefaultTransitions.FALSE_POSITIVE, DefaultTransitions.WONT_FIX, DefaultTransitions.REOPEN);
143 DefaultTransitions.ALL.stream()
144 .filter(s -> !supportedDefaultTransitionKeys.contains(s))
145 .forEach(this::on_transition_changeHasNoEffectForTransitionKey);
148 private void on_transition_changeHasNoEffectForTransitionKey(@Nullable String transitionKey) {
149 Mockito.reset(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
151 mockedUnderTest.onChange(issueChangeData(newIssueDto()), new IssueChange(transitionKey), userChangeContext);
153 Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
157 public void on_transition_change_has_no_effect_if_scan_IssueChangeContext() {
158 mockedUnderTest.onChange(issueChangeData(newIssueDto()), new IssueChange(randomAlphanumeric(12)), scanChangeContext);
160 Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
164 public void on_type_and_transition_change_has_no_effect_if_SearchResponseData_has_no_issue() {
165 mockedUnderTest.onChange(issueChangeData(), new IssueChange(randomRuleType, randomAlphanumeric(3)), userChangeContext);
167 Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
171 public void on_type_and_transition_change_has_no_effect_if_scan_IssueChangeContext() {
172 mockedUnderTest.onChange(issueChangeData(), new IssueChange(randomRuleType, randomAlphanumeric(3)), scanChangeContext);
174 Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
178 public void on_type_and_transition_has_no_effect_if_transition_key_is_empty() {
179 on_type_and_transition_changeHasNoEffectForTransitionKey("");
183 public void on_type_and_transition_has_no_effect_if_transition_key_is_random() {
184 on_type_and_transition_changeHasNoEffectForTransitionKey(randomAlphanumeric(66));
188 public void on_type_and_transition_has_no_effect_if_transition_key_is_ignored_default_transition_key() {
189 Set<String> supportedDefaultTransitionKeys = ImmutableSet.of(
190 DefaultTransitions.RESOLVE, DefaultTransitions.FALSE_POSITIVE, DefaultTransitions.WONT_FIX, DefaultTransitions.REOPEN);
191 DefaultTransitions.ALL.stream()
192 .filter(s -> !supportedDefaultTransitionKeys.contains(s))
193 .forEach(this::on_type_and_transition_changeHasNoEffectForTransitionKey);
196 private void on_type_and_transition_changeHasNoEffectForTransitionKey(@Nullable String transitionKey) {
197 Mockito.reset(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
199 mockedUnderTest.onChange(issueChangeData(newIssueDto()), new IssueChange(randomRuleType, transitionKey), userChangeContext);
201 Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
205 @UseDataProvider("validIssueChanges")
206 public void broadcast_to_QGEventListeners_for_short_living_branch_of_issue(IssueChange issueChange) {
207 OrganizationDto organization = dbTester.organizations().insert();
208 ComponentDto project = dbTester.components().insertPublicProject(organization);
209 ComponentAndBranch branch = insertProjectBranch(project, BranchType.SHORT, "foo");
210 SnapshotDto analysis = insertAnalysisTask(branch);
211 Configuration configuration = mockLoadProjectConfiguration(branch);
213 Map<String, String> properties = new HashMap<>();
214 properties.put("sonar.analysis.test1", randomAlphanumeric(50));
215 properties.put("sonar.analysis.test2", randomAlphanumeric(5000));
216 insertPropertiesFor(analysis.getUuid(), properties);
218 underTest.onChange(issueChangeData(newIssueDto(branch)), issueChange, userChangeContext);
220 Collection<QGChangeEvent> events = verifyListenersBroadcastedTo();
221 assertThat(events).hasSize(1);
222 QGChangeEvent event = events.iterator().next();
223 assertThat(event.getProject()).isEqualTo(branch.component);
224 assertThat(event.getBranch()).isEqualTo(branch.branch);
225 assertThat(event.getAnalysis()).isEqualTo(analysis);
226 assertThat(event.getProjectConfiguration()).isSameAs(configuration);
230 public void do_not_load_project_configuration_nor_analysis_nor_call_webhook_if_there_are_no_short_branch() {
231 OrganizationDto organization = dbTester.organizations().insert();
232 ComponentDto project = dbTester.components().insertPublicProject(organization);
233 ComponentAndBranch longBranch1 = insertProjectBranch(project, BranchType.LONG, "foo");
234 ComponentAndBranch longBranch2 = insertProjectBranch(project, BranchType.LONG, "bar");
235 ImmutableList<IssueDto> issueDtos = ImmutableList.of(newIssueDto(project), newIssueDto(longBranch1), newIssueDto(longBranch2));
237 SnapshotDao snapshotDaoSpy = Mockito.spy(dbClient.snapshotDao());
238 Mockito.when(spiedOnDbClient.snapshotDao()).thenReturn(snapshotDaoSpy);
239 underTest.onChange(issueChangeData(issueDtos), new IssueChange(randomRuleType), userChangeContext);
241 Mockito.verifyZeroInteractions(projectConfigurationLoader);
242 Mockito.verify(snapshotDaoSpy, Mockito.times(0)).selectLastAnalysesByRootComponentUuids(Matchers.any(DbSession.class), Matchers.anyCollectionOf(String.class));
246 public void creates_single_QGChangeEvent_per_short_branch_with_at_least_one_issue_in_SearchResponseData() {
247 OrganizationDto organization = dbTester.organizations().insert();
248 ComponentAndBranch branch1 = insertPrivateBranch(organization, BranchType.SHORT);
249 ComponentAndBranch branch2 = insertPrivateBranch(organization, BranchType.SHORT);
250 ComponentAndBranch branch3 = insertPrivateBranch(organization, BranchType.SHORT);
251 SnapshotDto analysis1 = insertAnalysisTask(branch1);
252 SnapshotDto analysis2 = insertAnalysisTask(branch2);
253 SnapshotDto analysis3 = insertAnalysisTask(branch3);
254 int issuesBranch1 = 2 + random.nextInt(10);
255 int issuesBranch2 = 2 + random.nextInt(10);
256 int issuesBranch3 = 2 + random.nextInt(10);
257 List<IssueDto> issueDtos = Stream.of(
258 IntStream.range(0, issuesBranch1).mapToObj(i -> newIssueDto(branch1.component)),
259 IntStream.range(0, issuesBranch2).mapToObj(i -> newIssueDto(branch2.component)),
260 IntStream.range(0, issuesBranch3).mapToObj(i -> newIssueDto(branch3.component)))
262 .collect(MoreCollectors.toList());
263 Configuration configuration1 = mock(Configuration.class);
264 Configuration configuration2 = mock(Configuration.class);
265 Configuration configuration3 = mock(Configuration.class);
266 mockLoadProjectConfigurations(
267 branch1.component, configuration1,
268 branch2.component, configuration2,
269 branch3.component, configuration3);
271 ComponentDao componentDaoSpy = Mockito.spy(dbClient.componentDao());
272 BranchDao branchDaoSpy = Mockito.spy(dbClient.branchDao());
273 SnapshotDao snapshotDaoSpy = Mockito.spy(dbClient.snapshotDao());
274 Mockito.when(spiedOnDbClient.componentDao()).thenReturn(componentDaoSpy);
275 Mockito.when(spiedOnDbClient.branchDao()).thenReturn(branchDaoSpy);
276 Mockito.when(spiedOnDbClient.snapshotDao()).thenReturn(snapshotDaoSpy);
277 underTest.onChange(issueChangeData(issueDtos), new IssueChange(randomRuleType), userChangeContext);
279 Collection<QGChangeEvent> qgChangeEvents = verifyListenersBroadcastedTo();
280 assertThat(qgChangeEvents)
282 .extracting(QGChangeEvent::getBranch, QGChangeEvent::getProjectConfiguration, QGChangeEvent::getAnalysis)
284 tuple(branch1.branch, configuration1, analysis1),
285 tuple(branch2.branch, configuration2, analysis2),
286 tuple(branch3.branch, configuration3, analysis3));
288 // verifyWebhookCalled(branch1, configuration1, analysis1);
289 // verifyWebhookCalled(branch2, configuration2, analysis2);
290 // verifyWebhookCalled(branch3, configuration3, analysis3);
291 // extractPayloadFactoryArguments(3);
293 Set<String> uuids = ImmutableSet.of(branch1.uuid(), branch2.uuid(), branch3.uuid());
294 Mockito.verify(componentDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
295 Mockito.verify(branchDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
296 Mockito.verify(snapshotDaoSpy).selectLastAnalysesByRootComponentUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
297 Mockito.verifyNoMoreInteractions(componentDaoSpy, branchDaoSpy, snapshotDaoSpy);
301 public void create_QGChangeEvent_only_for_short_branches() {
302 OrganizationDto organization = dbTester.organizations().insert();
303 ComponentAndBranch shortBranch = insertPrivateBranch(organization, BranchType.SHORT);
304 ComponentAndBranch longBranch = insertPrivateBranch(organization, BranchType.LONG);
305 SnapshotDto analysis1 = insertAnalysisTask(shortBranch);
306 SnapshotDto analysis2 = insertAnalysisTask(longBranch);
307 Configuration configuration = mockLoadProjectConfiguration(shortBranch);
309 ComponentDao componentDaoSpy = Mockito.spy(dbClient.componentDao());
310 BranchDao branchDaoSpy = Mockito.spy(dbClient.branchDao());
311 SnapshotDao snapshotDaoSpy = Mockito.spy(dbClient.snapshotDao());
312 Mockito.when(spiedOnDbClient.componentDao()).thenReturn(componentDaoSpy);
313 Mockito.when(spiedOnDbClient.branchDao()).thenReturn(branchDaoSpy);
314 Mockito.when(spiedOnDbClient.snapshotDao()).thenReturn(snapshotDaoSpy);
316 issueChangeData(asList(newIssueDto(shortBranch), newIssueDto(longBranch))),
317 new IssueChange(randomRuleType),
320 Collection<QGChangeEvent> qgChangeEvents = verifyListenersBroadcastedTo();
321 assertThat(qgChangeEvents)
323 .extracting(QGChangeEvent::getBranch, QGChangeEvent::getProjectConfiguration, QGChangeEvent::getAnalysis)
324 .containsOnly(tuple(shortBranch.branch, configuration, analysis1));
326 Set<String> uuids = ImmutableSet.of(shortBranch.uuid(), longBranch.uuid());
327 Mockito.verify(componentDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
328 Mockito.verify(branchDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
329 Mockito.verify(snapshotDaoSpy).selectLastAnalysesByRootComponentUuids(Matchers.any(DbSession.class), Matchers.eq(ImmutableSet.of(shortBranch.uuid())));
330 Mockito.verifyNoMoreInteractions(componentDaoSpy, branchDaoSpy, snapshotDaoSpy);
334 public void do_not_load_componentDto_from_DB_if_all_are_in_inputData() {
335 OrganizationDto organization = dbTester.organizations().insert();
336 ComponentAndBranch branch1 = insertPrivateBranch(organization, BranchType.SHORT);
337 ComponentAndBranch branch2 = insertPrivateBranch(organization, BranchType.SHORT);
338 ComponentAndBranch branch3 = insertPrivateBranch(organization, BranchType.SHORT);
339 SnapshotDto analysis1 = insertAnalysisTask(branch1);
340 SnapshotDto analysis2 = insertAnalysisTask(branch2);
341 SnapshotDto analysis3 = insertAnalysisTask(branch3);
342 List<IssueDto> issueDtos = asList(newIssueDto(branch1), newIssueDto(branch2), newIssueDto(branch3));
343 Configuration configuration1 = mock(Configuration.class);
344 Configuration configuration2 = mock(Configuration.class);
345 Configuration configuration3 = mock(Configuration.class);
346 mockLoadProjectConfigurations(
347 branch1.component, configuration1,
348 branch2.component, configuration2,
349 branch3.component, configuration3);
351 ComponentDao componentDaoSpy = Mockito.spy(dbClient.componentDao());
352 Mockito.when(spiedOnDbClient.componentDao()).thenReturn(componentDaoSpy);
354 issueChangeData(issueDtos, branch1, branch2, branch3),
355 new IssueChange(randomRuleType),
358 Collection<QGChangeEvent> qgChangeEvents = verifyListenersBroadcastedTo();
359 assertThat(qgChangeEvents)
361 .extracting(QGChangeEvent::getBranch, QGChangeEvent::getProjectConfiguration, QGChangeEvent::getAnalysis)
363 tuple(branch1.branch, configuration1, analysis1),
364 tuple(branch2.branch, configuration2, analysis2),
365 tuple(branch3.branch, configuration3, analysis3));
367 Mockito.verify(componentDaoSpy, Mockito.times(0)).selectByUuids(Matchers.any(DbSession.class), Matchers.anyCollectionOf(String.class));
368 Mockito.verifyNoMoreInteractions(componentDaoSpy);
372 public void call_db_only_for_componentDto_not_in_inputData() {
373 OrganizationDto organization = dbTester.organizations().insert();
374 ComponentAndBranch branch1 = insertPrivateBranch(organization, BranchType.SHORT);
375 ComponentAndBranch branch2 = insertPrivateBranch(organization, BranchType.SHORT);
376 ComponentAndBranch branch3 = insertPrivateBranch(organization, BranchType.SHORT);
377 SnapshotDto analysis1 = insertAnalysisTask(branch1);
378 SnapshotDto analysis2 = insertAnalysisTask(branch2);
379 SnapshotDto analysis3 = insertAnalysisTask(branch3);
380 List<IssueDto> issueDtos = asList(newIssueDto(branch1), newIssueDto(branch2), newIssueDto(branch3));
381 Configuration configuration1 = mock(Configuration.class);
382 Configuration configuration2 = mock(Configuration.class);
383 Configuration configuration3 = mock(Configuration.class);
384 mockLoadProjectConfigurations(
385 branch1.component, configuration1,
386 branch2.component, configuration2,
387 branch3.component, configuration3);
389 ComponentDao componentDaoSpy = Mockito.spy(dbClient.componentDao());
390 BranchDao branchDaoSpy = Mockito.spy(dbClient.branchDao());
391 SnapshotDao snapshotDaoSpy = Mockito.spy(dbClient.snapshotDao());
392 Mockito.when(spiedOnDbClient.componentDao()).thenReturn(componentDaoSpy);
393 Mockito.when(spiedOnDbClient.branchDao()).thenReturn(branchDaoSpy);
394 Mockito.when(spiedOnDbClient.snapshotDao()).thenReturn(snapshotDaoSpy);
396 issueChangeData(issueDtos, branch1, branch3),
397 new IssueChange(randomRuleType),
400 assertThat(verifyListenersBroadcastedTo()).hasSize(3);
402 Set<String> uuids = ImmutableSet.of(branch1.uuid(), branch2.uuid(), branch3.uuid());
403 Mockito.verify(componentDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(ImmutableSet.of(branch2.uuid())));
404 Mockito.verify(branchDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
405 Mockito.verify(snapshotDaoSpy).selectLastAnalysesByRootComponentUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
406 Mockito.verifyNoMoreInteractions(componentDaoSpy, branchDaoSpy, snapshotDaoSpy);
410 public void supports_issues_on_files_and_filter_on_short_branches_asap_when_calling_db() {
411 OrganizationDto organization = dbTester.organizations().insert();
412 ComponentDto project = dbTester.components().insertPrivateProject(organization);
413 ComponentDto file = dbTester.components().insertComponent(ComponentTesting.newFileDto(project));
414 ComponentAndBranch shortBranch = insertProjectBranch(project, BranchType.SHORT, "foo");
415 ComponentAndBranch longBranch = insertProjectBranch(project, BranchType.LONG, "bar");
416 ComponentDto shortBranchFile = dbTester.components().insertComponent(ComponentTesting.newFileDto(shortBranch.component));
417 ComponentDto longBranchFile = dbTester.components().insertComponent(ComponentTesting.newFileDto(longBranch.component));
418 SnapshotDto analysis1 = insertAnalysisTask(project);
419 SnapshotDto analysis2 = insertAnalysisTask(shortBranch);
420 SnapshotDto analysis3 = insertAnalysisTask(longBranch);
421 List<IssueDto> issueDtos = asList(
422 newIssueDto(file, project),
423 newIssueDto(shortBranchFile, shortBranch),
424 newIssueDto(longBranchFile, longBranch));
425 Configuration configuration = mockLoadProjectConfiguration(shortBranch);
427 ComponentDao componentDaoSpy = Mockito.spy(dbClient.componentDao());
428 BranchDao branchDaoSpy = Mockito.spy(dbClient.branchDao());
429 SnapshotDao snapshotDaoSpy = Mockito.spy(dbClient.snapshotDao());
430 Mockito.when(spiedOnDbClient.componentDao()).thenReturn(componentDaoSpy);
431 Mockito.when(spiedOnDbClient.branchDao()).thenReturn(branchDaoSpy);
432 Mockito.when(spiedOnDbClient.snapshotDao()).thenReturn(snapshotDaoSpy);
433 underTest.onChange(issueChangeData(issueDtos), new IssueChange(randomRuleType), userChangeContext);
435 Collection<QGChangeEvent> qgChangeEvents = verifyListenersBroadcastedTo();
436 assertThat(qgChangeEvents)
438 .extracting(QGChangeEvent::getBranch, QGChangeEvent::getProjectConfiguration, QGChangeEvent::getAnalysis)
439 .containsOnly(tuple(shortBranch.branch, configuration, analysis2));
441 Set<String> uuids = ImmutableSet.of(project.uuid(), shortBranch.uuid(), longBranch.uuid());
442 Mockito.verify(componentDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(uuids));
443 Mockito.verify(branchDaoSpy).selectByUuids(Matchers.any(DbSession.class), Matchers.eq(ImmutableSet.of(shortBranch.uuid(), longBranch.uuid())));
444 Mockito.verify(snapshotDaoSpy).selectLastAnalysesByRootComponentUuids(Matchers.any(DbSession.class), Matchers.eq(ImmutableSet.of(shortBranch.uuid())));
445 Mockito.verifyNoMoreInteractions(componentDaoSpy, branchDaoSpy, snapshotDaoSpy);
448 private Configuration mockLoadProjectConfiguration(ComponentAndBranch componentAndBranch) {
449 Configuration configuration = mock(Configuration.class);
450 Mockito.when(projectConfigurationLoader.loadProjectConfigurations(Matchers.any(DbSession.class), Matchers.eq(singleton(componentAndBranch.component))))
451 .thenReturn(ImmutableMap.of(componentAndBranch.uuid(), configuration));
452 return configuration;
455 private void mockLoadProjectConfigurations(Object... branchesAndConfiguration) {
456 checkArgument(branchesAndConfiguration.length % 2 == 0);
457 Set<ComponentDto> components = new HashSet<>();
458 Map<String, Configuration> result = new HashMap<>();
459 for (int i = 0; i < branchesAndConfiguration.length; i++) {
460 ComponentDto component = (ComponentDto) branchesAndConfiguration[i++];
461 Configuration configuration = (Configuration) branchesAndConfiguration[i];
462 components.add(component);
463 result.put(component.uuid(), configuration);
465 Mockito.when(projectConfigurationLoader.loadProjectConfigurations(Matchers.any(DbSession.class), Matchers.eq(components)))
469 private ComponentAndBranch insertPrivateBranch(OrganizationDto organization, BranchType branchType) {
470 ComponentDto project = dbTester.components().insertPrivateProject(organization);
471 BranchDto branchDto = newBranchDto(project.projectUuid(), branchType)
473 ComponentDto newComponent = dbTester.components().insertProjectBranch(project, branchDto);
474 return new ComponentAndBranch(newComponent, branchDto);
477 public ComponentAndBranch insertProjectBranch(ComponentDto project, BranchType type, String branchKey) {
478 BranchDto branchDto = newBranchDto(project.projectUuid(), type).setKey(branchKey);
479 ComponentDto newComponent = dbTester.components().insertProjectBranch(project, branchDto);
480 return new ComponentAndBranch(newComponent, branchDto);
483 private static class ComponentAndBranch {
484 private final ComponentDto component;
486 private final BranchDto branch;
488 private ComponentAndBranch(ComponentDto component, BranchDto branch) {
489 this.component = component;
490 this.branch = branch;
493 public ComponentDto getComponent() {
497 public BranchDto getBranch() {
501 public String uuid() {
502 return component.uuid();
507 private void insertPropertiesFor(String snapshotUuid, Map<String, String> properties) {
508 List<AnalysisPropertyDto> analysisProperties = properties.entrySet().stream()
509 .map(entry -> new AnalysisPropertyDto()
510 .setUuid(UuidFactoryFast.getInstance().create())
511 .setSnapshotUuid(snapshotUuid)
512 .setKey(entry.getKey())
513 .setValue(entry.getValue()))
514 .collect(toArrayList(properties.size()));
515 dbTester.getDbClient().analysisPropertiesDao().insert(dbTester.getSession(), analysisProperties);
516 dbTester.getSession().commit();
519 private SnapshotDto insertAnalysisTask(ComponentAndBranch componentAndBranch) {
520 return insertAnalysisTask(componentAndBranch.component);
523 private SnapshotDto insertAnalysisTask(ComponentDto component) {
524 return dbTester.components().insertSnapshot(component);
527 private Collection<QGChangeEvent> verifyListenersBroadcastedTo() {
528 Class<Collection<QGChangeEvent>> clazz = (Class<Collection<QGChangeEvent>>) (Class) Collection.class;
529 ArgumentCaptor<Collection<QGChangeEvent>> supplierCaptor = ArgumentCaptor.forClass(clazz);
530 Mockito.verify(qgChangeEventListeners).broadcast(
531 Matchers.same(Trigger.ISSUE_CHANGE),
532 supplierCaptor.capture());
533 return supplierCaptor.getValue();
537 public static Object[][] validIssueChanges() {
538 return new Object[][] {
539 {new IssueChange(RuleType.BUG)},
540 {new IssueChange(RuleType.VULNERABILITY)},
541 {new IssueChange(RuleType.CODE_SMELL)},
542 {new IssueChange(DefaultTransitions.RESOLVE)},
543 {new IssueChange(RuleType.BUG, DefaultTransitions.RESOLVE)},
544 {new IssueChange(RuleType.VULNERABILITY, DefaultTransitions.RESOLVE)},
545 {new IssueChange(RuleType.CODE_SMELL, DefaultTransitions.RESOLVE)},
546 {new IssueChange(DefaultTransitions.FALSE_POSITIVE)},
547 {new IssueChange(RuleType.BUG, DefaultTransitions.FALSE_POSITIVE)},
548 {new IssueChange(RuleType.VULNERABILITY, DefaultTransitions.FALSE_POSITIVE)},
549 {new IssueChange(RuleType.CODE_SMELL, DefaultTransitions.FALSE_POSITIVE)},
550 {new IssueChange(DefaultTransitions.WONT_FIX)},
551 {new IssueChange(RuleType.BUG, DefaultTransitions.WONT_FIX)},
552 {new IssueChange(RuleType.VULNERABILITY, DefaultTransitions.WONT_FIX)},
553 {new IssueChange(RuleType.CODE_SMELL, DefaultTransitions.WONT_FIX)},
554 {new IssueChange(DefaultTransitions.REOPEN)},
555 {new IssueChange(RuleType.BUG, DefaultTransitions.REOPEN)},
556 {new IssueChange(RuleType.VULNERABILITY, DefaultTransitions.REOPEN)},
557 {new IssueChange(RuleType.CODE_SMELL, DefaultTransitions.REOPEN)}
561 private IssueChangeTrigger.IssueChangeData issueChangeData() {
562 return new IssueChangeTrigger.IssueChangeData(emptyList(), emptyList());
565 private IssueChangeTrigger.IssueChangeData issueChangeData(IssueDto issueDto) {
566 return new IssueChangeTrigger.IssueChangeData(singletonList(issueDto.toDefaultIssue()), emptyList());
569 private IssueChangeTrigger.IssueChangeData issueChangeData(Collection<IssueDto> issueDtos, ComponentAndBranch... components) {
570 return new IssueChangeTrigger.IssueChangeData(
571 issueDtos.stream().map(IssueDto::toDefaultIssue).collect(Collectors.toList()),
572 Arrays.stream(components).map(ComponentAndBranch::getComponent).collect(Collectors.toList()));
575 private IssueDto newIssueDto(@Nullable ComponentAndBranch projectAndBranch) {
576 return projectAndBranch == null ? newIssueDto() : newIssueDto(projectAndBranch.component, projectAndBranch.component);
579 private IssueDto newIssueDto(ComponentDto componentDto) {
580 return newIssueDto(componentDto, componentDto);
583 private IssueDto newIssueDto() {
584 return newIssueDto((ComponentDto) null, (ComponentDto) null);
587 private IssueDto newIssueDto(@Nullable ComponentDto component, @Nullable ComponentAndBranch componentAndBranch) {
588 return newIssueDto(component, componentAndBranch == null ? null : componentAndBranch.component);
591 private IssueDto newIssueDto(@Nullable ComponentDto component, @Nullable ComponentDto project) {
592 RuleType randomRuleType = RuleType.values()[random.nextInt(RuleType.values().length)];
593 String randomStatus = Issue.STATUSES.get(random.nextInt(Issue.STATUSES.size()));
594 IssueDto res = new IssueDto()
595 .setType(randomRuleType)
596 .setStatus(randomStatus)
597 .setRuleKey(randomAlphanumeric(3), randomAlphanumeric(4));
598 if (component != null) {
599 res.setComponent(component);
601 if (project != null) {
602 res.setProject(project);