]> source.dussan.org Git - sonarqube.git/blob
d0d48fb01ec2d1b8759e4cb27b8edb27bf60f30e
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2017 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.server.qualitygate.changeevent;
21
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;
34 import java.util.Map;
35 import java.util.Random;
36 import java.util.Set;
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;
73
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;
85
86 @RunWith(DataProviderRunner.class)
87 public class IssueChangeTriggerImplTest {
88   @Rule
89   public DbTester dbTester = DbTester.create(System2.INSTANCE);
90   @Rule
91   public UserSessionRule userSessionRule = UserSessionRule.standalone();
92
93   private DbClient dbClient = dbTester.getDbClient();
94
95   private Random random = new Random();
96   private RuleType randomRuleType = RuleType.values()[random.nextInt(RuleType.values().length)];
97
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);
107
108   @Test
109   public void on_type_change_has_no_effect_if_SearchResponseData_has_no_issue() {
110     mockedUnderTest.onChange(issueChangeData(), new IssueChange(randomRuleType), userChangeContext);
111
112     Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
113   }
114
115   @Test
116   public void on_type_change_has_no_effect_if_scan_IssueChangeContext() {
117     mockedUnderTest.onChange(issueChangeData(), new IssueChange(randomRuleType), scanChangeContext);
118
119     Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
120   }
121
122   @Test
123   public void on_transition_change_has_no_effect_if_SearchResponseData_has_no_issue() {
124     mockedUnderTest.onChange(issueChangeData(), new IssueChange(randomAlphanumeric(12)), userChangeContext);
125
126     Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
127   }
128
129   @Test
130   public void onTransition_has_no_effect_if_transition_key_is_empty() {
131     on_transition_changeHasNoEffectForTransitionKey("");
132   }
133
134   @Test
135   public void onTransition_has_no_effect_if_transition_key_is_random() {
136     on_transition_changeHasNoEffectForTransitionKey(randomAlphanumeric(99));
137   }
138
139   @Test
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);
146   }
147
148   private void on_transition_changeHasNoEffectForTransitionKey(@Nullable String transitionKey) {
149     Mockito.reset(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
150
151     mockedUnderTest.onChange(issueChangeData(newIssueDto()), new IssueChange(transitionKey), userChangeContext);
152
153     Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
154   }
155
156   @Test
157   public void on_transition_change_has_no_effect_if_scan_IssueChangeContext() {
158     mockedUnderTest.onChange(issueChangeData(newIssueDto()), new IssueChange(randomAlphanumeric(12)), scanChangeContext);
159
160     Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
161   }
162
163   @Test
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);
166
167     Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
168   }
169
170   @Test
171   public void on_type_and_transition_change_has_no_effect_if_scan_IssueChangeContext() {
172     mockedUnderTest.onChange(issueChangeData(), new IssueChange(randomRuleType, randomAlphanumeric(3)), scanChangeContext);
173
174     Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
175   }
176
177   @Test
178   public void on_type_and_transition_has_no_effect_if_transition_key_is_empty() {
179     on_type_and_transition_changeHasNoEffectForTransitionKey("");
180   }
181
182   @Test
183   public void on_type_and_transition_has_no_effect_if_transition_key_is_random() {
184     on_type_and_transition_changeHasNoEffectForTransitionKey(randomAlphanumeric(66));
185   }
186
187   @Test
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);
194   }
195
196   private void on_type_and_transition_changeHasNoEffectForTransitionKey(@Nullable String transitionKey) {
197     Mockito.reset(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
198
199     mockedUnderTest.onChange(issueChangeData(newIssueDto()), new IssueChange(randomRuleType, transitionKey), userChangeContext);
200
201     Mockito.verifyZeroInteractions(mockedDbClient, projectConfigurationLoader, webhookPayloadFactory);
202   }
203
204   @Test
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);
212
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);
217
218     underTest.onChange(issueChangeData(newIssueDto(branch)), issueChange, userChangeContext);
219
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);
227   }
228
229   @Test
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));
236
237     SnapshotDao snapshotDaoSpy = Mockito.spy(dbClient.snapshotDao());
238     Mockito.when(spiedOnDbClient.snapshotDao()).thenReturn(snapshotDaoSpy);
239     underTest.onChange(issueChangeData(issueDtos), new IssueChange(randomRuleType), userChangeContext);
240
241     Mockito.verifyZeroInteractions(projectConfigurationLoader);
242     Mockito.verify(snapshotDaoSpy, Mockito.times(0)).selectLastAnalysesByRootComponentUuids(Matchers.any(DbSession.class), Matchers.anyCollectionOf(String.class));
243   }
244
245   @Test
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)))
261       .flatMap(s -> s)
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);
270
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);
278
279     Collection<QGChangeEvent> qgChangeEvents = verifyListenersBroadcastedTo();
280     assertThat(qgChangeEvents)
281       .hasSize(3)
282       .extracting(QGChangeEvent::getBranch, QGChangeEvent::getProjectConfiguration, QGChangeEvent::getAnalysis)
283       .containsOnly(
284         tuple(branch1.branch, configuration1, analysis1),
285         tuple(branch2.branch, configuration2, analysis2),
286         tuple(branch3.branch, configuration3, analysis3));
287
288     // verifyWebhookCalled(branch1, configuration1, analysis1);
289     // verifyWebhookCalled(branch2, configuration2, analysis2);
290     // verifyWebhookCalled(branch3, configuration3, analysis3);
291     // extractPayloadFactoryArguments(3);
292
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);
298   }
299
300   @Test
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);
308
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);
315     underTest.onChange(
316       issueChangeData(asList(newIssueDto(shortBranch), newIssueDto(longBranch))),
317       new IssueChange(randomRuleType),
318       userChangeContext);
319
320     Collection<QGChangeEvent> qgChangeEvents = verifyListenersBroadcastedTo();
321     assertThat(qgChangeEvents)
322       .hasSize(1)
323       .extracting(QGChangeEvent::getBranch, QGChangeEvent::getProjectConfiguration, QGChangeEvent::getAnalysis)
324       .containsOnly(tuple(shortBranch.branch, configuration, analysis1));
325
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);
331   }
332
333   @Test
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);
350
351     ComponentDao componentDaoSpy = Mockito.spy(dbClient.componentDao());
352     Mockito.when(spiedOnDbClient.componentDao()).thenReturn(componentDaoSpy);
353     underTest.onChange(
354       issueChangeData(issueDtos, branch1, branch2, branch3),
355       new IssueChange(randomRuleType),
356       userChangeContext);
357
358     Collection<QGChangeEvent> qgChangeEvents = verifyListenersBroadcastedTo();
359     assertThat(qgChangeEvents)
360       .hasSize(3)
361       .extracting(QGChangeEvent::getBranch, QGChangeEvent::getProjectConfiguration, QGChangeEvent::getAnalysis)
362       .containsOnly(
363         tuple(branch1.branch, configuration1, analysis1),
364         tuple(branch2.branch, configuration2, analysis2),
365         tuple(branch3.branch, configuration3, analysis3));
366
367     Mockito.verify(componentDaoSpy, Mockito.times(0)).selectByUuids(Matchers.any(DbSession.class), Matchers.anyCollectionOf(String.class));
368     Mockito.verifyNoMoreInteractions(componentDaoSpy);
369   }
370
371   @Test
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);
388
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);
395     underTest.onChange(
396       issueChangeData(issueDtos, branch1, branch3),
397       new IssueChange(randomRuleType),
398       userChangeContext);
399
400     assertThat(verifyListenersBroadcastedTo()).hasSize(3);
401
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);
407   }
408
409   @Test
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);
426
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);
434
435     Collection<QGChangeEvent> qgChangeEvents = verifyListenersBroadcastedTo();
436     assertThat(qgChangeEvents)
437       .hasSize(1)
438       .extracting(QGChangeEvent::getBranch, QGChangeEvent::getProjectConfiguration, QGChangeEvent::getAnalysis)
439       .containsOnly(tuple(shortBranch.branch, configuration, analysis2));
440
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);
446   }
447
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;
453   }
454
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);
464     }
465     Mockito.when(projectConfigurationLoader.loadProjectConfigurations(Matchers.any(DbSession.class), Matchers.eq(components)))
466       .thenReturn(result);
467   }
468
469   private ComponentAndBranch insertPrivateBranch(OrganizationDto organization, BranchType branchType) {
470     ComponentDto project = dbTester.components().insertPrivateProject(organization);
471     BranchDto branchDto = newBranchDto(project.projectUuid(), branchType)
472       .setKey("foo");
473     ComponentDto newComponent = dbTester.components().insertProjectBranch(project, branchDto);
474     return new ComponentAndBranch(newComponent, branchDto);
475   }
476
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);
481   }
482
483   private static class ComponentAndBranch {
484     private final ComponentDto component;
485
486     private final BranchDto branch;
487
488     private ComponentAndBranch(ComponentDto component, BranchDto branch) {
489       this.component = component;
490       this.branch = branch;
491     }
492
493     public ComponentDto getComponent() {
494       return component;
495     }
496
497     public BranchDto getBranch() {
498       return branch;
499     }
500
501     public String uuid() {
502       return component.uuid();
503     }
504
505   }
506
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();
517   }
518
519   private SnapshotDto insertAnalysisTask(ComponentAndBranch componentAndBranch) {
520     return insertAnalysisTask(componentAndBranch.component);
521   }
522
523   private SnapshotDto insertAnalysisTask(ComponentDto component) {
524     return dbTester.components().insertSnapshot(component);
525   }
526
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();
534   }
535
536   @DataProvider
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)}
558     };
559   }
560
561   private IssueChangeTrigger.IssueChangeData issueChangeData() {
562     return new IssueChangeTrigger.IssueChangeData(emptyList(), emptyList());
563   }
564
565   private IssueChangeTrigger.IssueChangeData issueChangeData(IssueDto issueDto) {
566     return new IssueChangeTrigger.IssueChangeData(singletonList(issueDto.toDefaultIssue()), emptyList());
567   }
568
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()));
573   }
574
575   private IssueDto newIssueDto(@Nullable ComponentAndBranch projectAndBranch) {
576     return projectAndBranch == null ? newIssueDto() : newIssueDto(projectAndBranch.component, projectAndBranch.component);
577   }
578
579   private IssueDto newIssueDto(ComponentDto componentDto) {
580     return newIssueDto(componentDto, componentDto);
581   }
582
583   private IssueDto newIssueDto() {
584     return newIssueDto((ComponentDto) null, (ComponentDto) null);
585   }
586
587   private IssueDto newIssueDto(@Nullable ComponentDto component, @Nullable ComponentAndBranch componentAndBranch) {
588     return newIssueDto(component, componentAndBranch == null ? null : componentAndBranch.component);
589   }
590
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);
600     }
601     if (project != null) {
602       res.setProject(project);
603     }
604     return res;
605   }
606 }