]> source.dussan.org Git - sonarqube.git/blob
7d4bc08c27abe94432f0f5fc5109c05f5ba83313
[sonarqube.git] /
1 /*
2  * SonarQube, open source software quality management tool.
3  * Copyright (C) 2008-2014 SonarSource
4  * mailto:contact AT sonarsource DOT com
5  *
6  * SonarQube 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  * SonarQube 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.plugins.core.issue;
21
22 import org.junit.Before;
23 import org.junit.Test;
24 import org.mockito.ArgumentCaptor;
25 import org.mockito.ArgumentMatcher;
26 import org.sonar.api.batch.DecoratorContext;
27 import org.sonar.api.batch.SonarIndex;
28 import org.sonar.api.component.ResourcePerspectives;
29 import org.sonar.api.issue.Issue;
30 import org.sonar.api.issue.internal.DefaultIssue;
31 import org.sonar.api.issue.internal.IssueChangeContext;
32 import org.sonar.api.profiles.RulesProfile;
33 import org.sonar.api.resources.File;
34 import org.sonar.api.resources.Project;
35 import org.sonar.api.resources.Resource;
36 import org.sonar.api.rule.RuleKey;
37 import org.sonar.api.rules.Rule;
38 import org.sonar.api.rules.RuleFinder;
39 import org.sonar.api.utils.Duration;
40 import org.sonar.batch.issue.IssueCache;
41 import org.sonar.batch.scan.LastSnapshots;
42 import org.sonar.core.issue.IssueUpdater;
43 import org.sonar.core.issue.db.IssueChangeDto;
44 import org.sonar.core.issue.db.IssueDto;
45 import org.sonar.core.issue.workflow.IssueWorkflow;
46 import org.sonar.core.persistence.AbstractDaoTestCase;
47 import org.sonar.java.api.JavaClass;
48
49 import java.util.Arrays;
50 import java.util.Collection;
51 import java.util.Collections;
52 import java.util.List;
53
54 import static com.google.common.collect.Lists.newArrayList;
55 import static org.fest.assertions.Assertions.assertThat;
56 import static org.mockito.Matchers.any;
57 import static org.mockito.Matchers.anyCollection;
58 import static org.mockito.Matchers.argThat;
59 import static org.mockito.Matchers.eq;
60 import static org.mockito.Matchers.isA;
61 import static org.mockito.Mockito.*;
62 import static org.mockito.Mockito.anyString;
63
64 public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
65
66   IssueTrackingDecorator decorator;
67   IssueCache issueCache = mock(IssueCache.class, RETURNS_MOCKS);
68   InitialOpenIssuesStack initialOpenIssues = mock(InitialOpenIssuesStack.class);
69   IssueTracking tracking = mock(IssueTracking.class, RETURNS_MOCKS);
70   LastSnapshots lastSnapshots = mock(LastSnapshots.class);
71   SonarIndex index = mock(SonarIndex.class);
72   IssueHandlers handlers = mock(IssueHandlers.class);
73   IssueWorkflow workflow = mock(IssueWorkflow.class);
74   IssueUpdater updater = mock(IssueUpdater.class);
75   ResourcePerspectives perspectives = mock(ResourcePerspectives.class);
76   RulesProfile profile = mock(RulesProfile.class);
77   RuleFinder ruleFinder = mock(RuleFinder.class);
78
79   @Before
80   public void init() {
81     decorator = new IssueTrackingDecorator(
82       issueCache,
83       initialOpenIssues,
84       tracking,
85       lastSnapshots,
86       index,
87       handlers,
88       workflow,
89       updater,
90       mock(Project.class),
91       perspectives,
92       profile,
93       ruleFinder);
94   }
95
96   @Test
97   public void should_execute_on_project() {
98     Project project = mock(Project.class);
99     assertThat(decorator.shouldExecuteOnProject(project)).isTrue();
100   }
101
102   @Test
103   public void should_not_be_executed_on_classes_not_methods() throws Exception {
104     DecoratorContext context = mock(DecoratorContext.class);
105     decorator.decorate(JavaClass.create("org.foo.Bar"), context);
106     verifyZeroInteractions(context, issueCache, tracking, handlers, workflow);
107   }
108
109   @Test
110   public void should_process_open_issues() throws Exception {
111     Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
112     final DefaultIssue issue = new DefaultIssue();
113
114     // INPUT : one issue, no open issues during previous scan, no filtering
115     when(issueCache.byComponent("struts:Action.java")).thenReturn(Arrays.asList(issue));
116     List<IssueDto> dbIssues = Collections.emptyList();
117     when(initialOpenIssues.selectAndRemoveIssues("struts:Action.java")).thenReturn(dbIssues);
118
119     decorator.doDecorate(file);
120
121     // Apply filters, track, apply transitions, notify extensions then update cache
122     verify(tracking).track(isA(SourceHashHolder.class), eq(dbIssues), argThat(new ArgumentMatcher<Collection<DefaultIssue>>() {
123       @Override
124       public boolean matches(Object o) {
125         List<DefaultIssue> issues = (List<DefaultIssue>) o;
126         return issues.size() == 1 && issues.get(0) == issue;
127       }
128     }));
129     verify(workflow).doAutomaticTransition(eq(issue), any(IssueChangeContext.class));
130     verify(handlers).execute(eq(issue), any(IssueChangeContext.class));
131     verify(issueCache).put(issue);
132   }
133
134   @Test
135   public void should_register_unmatched_issues_as_end_of_life() throws Exception {
136     // "Unmatched" issues existed in previous scan but not in current one -> they have to be closed
137     Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
138
139     // INPUT : one issue existing during previous scan
140     IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey("squid", "AvoidCycle");
141
142     IssueTrackingResult trackingResult = new IssueTrackingResult();
143     trackingResult.addUnmatched(unmatchedIssue);
144
145     when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
146
147     decorator.doDecorate(file);
148
149     verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
150     verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
151
152     ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
153     verify(issueCache).put(argument.capture());
154
155     DefaultIssue issue = argument.getValue();
156     assertThat(issue.key()).isEqualTo("ABCDE");
157     assertThat(issue.isNew()).isFalse();
158     assertThat(issue.isEndOfLife()).isTrue();
159   }
160
161   @Test
162   public void manual_issues_should_be_moved_if_matching_line_found() throws Exception {
163     Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
164
165     // INPUT : one issue existing during previous scan
166     IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(6).setStatus("OPEN").setRuleKey("manual", "Performance");
167     when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance"));
168
169     IssueTrackingResult trackingResult = new IssueTrackingResult();
170     trackingResult.addUnmatched(unmatchedIssue);
171
172     String originalSource = "public interface Action {\n"
173       + "   void method1();\n"
174       + "   void method2();\n"
175       + "   void method3();\n"
176       + "   void method4();\n"
177       + "   void method5();\n" // Original issue here
178       + "}";
179     String newSource = "public interface Action {\n"
180       + "   void method5();\n" // New issue here
181       + "   void method1();\n"
182       + "   void method2();\n"
183       + "   void method3();\n"
184       + "   void method4();\n"
185       + "}";
186     when(index.getSource(file)).thenReturn(newSource);
187     when(lastSnapshots.getSource(file)).thenReturn(originalSource);
188
189     when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
190
191     decorator.doDecorate(file);
192
193     verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
194     verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
195
196     ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
197     verify(issueCache).put(argument.capture());
198
199     DefaultIssue issue = argument.getValue();
200     assertThat(issue.line()).isEqualTo(2);
201     assertThat(issue.key()).isEqualTo("ABCDE");
202     assertThat(issue.isNew()).isFalse();
203     assertThat(issue.isEndOfLife()).isFalse();
204     assertThat(issue.isOnDisabledRule()).isFalse();
205   }
206
207   @Test
208   public void manual_issues_should_be_untouched_if_already_closed() throws Exception {
209     Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
210
211     // INPUT : one issue existing during previous scan
212     IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(1).setStatus("CLOSED").setRuleKey("manual", "Performance");
213     when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance"));
214
215     IssueTrackingResult trackingResult = new IssueTrackingResult();
216     trackingResult.addUnmatched(unmatchedIssue);
217
218     String originalSource = "public interface Action {}";
219     when(index.getSource(file)).thenReturn(originalSource);
220     when(lastSnapshots.getSource(file)).thenReturn(originalSource);
221
222     when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
223
224     decorator.doDecorate(file);
225
226     verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
227     verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
228
229     ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
230     verify(issueCache).put(argument.capture());
231
232     DefaultIssue issue = argument.getValue();
233     assertThat(issue.line()).isEqualTo(1);
234     assertThat(issue.key()).isEqualTo("ABCDE");
235     assertThat(issue.isNew()).isFalse();
236     assertThat(issue.isEndOfLife()).isFalse();
237     assertThat(issue.isOnDisabledRule()).isFalse();
238     assertThat(issue.status()).isEqualTo("CLOSED");
239   }
240
241   @Test
242   public void manual_issues_should_be_untouched_if_line_is_null() throws Exception {
243     Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
244
245     // INPUT : one issue existing during previous scan
246     IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(null).setStatus("OPEN").setRuleKey("manual", "Performance");
247     when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance"));
248
249     IssueTrackingResult trackingResult = new IssueTrackingResult();
250     trackingResult.addUnmatched(unmatchedIssue);
251
252     String originalSource = "public interface Action {}";
253     when(index.getSource(file)).thenReturn(originalSource);
254     when(lastSnapshots.getSource(file)).thenReturn(originalSource);
255
256     when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
257
258     decorator.doDecorate(file);
259
260     verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
261     verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
262
263     ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
264     verify(issueCache).put(argument.capture());
265
266     DefaultIssue issue = argument.getValue();
267     assertThat(issue.line()).isEqualTo(null);
268     assertThat(issue.key()).isEqualTo("ABCDE");
269     assertThat(issue.isNew()).isFalse();
270     assertThat(issue.isEndOfLife()).isFalse();
271     assertThat(issue.isOnDisabledRule()).isFalse();
272     assertThat(issue.status()).isEqualTo("OPEN");
273   }
274
275   @Test
276   public void manual_issues_should_be_kept_if_matching_line_not_found() throws Exception {
277     // "Unmatched" issues existed in previous scan but not in current one -> they have to be closed
278     Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
279
280     // INPUT : one issue existing during previous scan
281     final int issueOnLine = 6;
282     IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(issueOnLine).setStatus("OPEN").setRuleKey("manual", "Performance");
283     when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance"));
284
285     IssueTrackingResult trackingResult = new IssueTrackingResult();
286     trackingResult.addUnmatched(unmatchedIssue);
287
288     String originalSource = "public interface Action {\n"
289       + "   void method1();\n"
290       + "   void method2();\n"
291       + "   void method3();\n"
292       + "   void method4();\n"
293       + "   void method5();\n" // Original issue here
294       + "}";
295     String newSource = "public interface Action {\n"
296       + "   void method1();\n"
297       + "   void method2();\n"
298       + "   void method3();\n"
299       + "   void method4();\n"
300       + "   void method6();\n" // Poof, no method5 anymore
301       + "}";
302     when(index.getSource(file)).thenReturn(newSource);
303     when(lastSnapshots.getSource(file)).thenReturn(originalSource);
304
305     when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
306
307     decorator.doDecorate(file);
308
309     verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
310     verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
311
312     ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
313     verify(issueCache).put(argument.capture());
314
315     DefaultIssue issue = argument.getValue();
316     assertThat(issue.line()).isEqualTo(issueOnLine);
317     assertThat(issue.key()).isEqualTo("ABCDE");
318     assertThat(issue.isNew()).isFalse();
319     assertThat(issue.isEndOfLife()).isFalse();
320     assertThat(issue.isOnDisabledRule()).isFalse();
321   }
322
323   @Test
324   public void manual_issues_should_be_kept_if_multiple_matching_lines_found() throws Exception {
325     // "Unmatched" issues existed in previous scan but not in current one -> they have to be closed
326     Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
327
328     // INPUT : one issue existing during previous scan
329     final int issueOnLine = 3;
330     IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(issueOnLine).setStatus("OPEN").setRuleKey("manual", "Performance");
331     when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance"));
332
333     IssueTrackingResult trackingResult = new IssueTrackingResult();
334     trackingResult.addUnmatched(unmatchedIssue);
335
336     String originalSource = "public class Action {\n"
337       + "   void method1() {\n"
338       + "     notify();\n" // initial issue
339       + "   }\n"
340       + "}";
341     String newSource = "public class Action {\n"
342       + "   \n"
343       + "   void method1() {\n" // new issue will appear here
344       + "     notify();\n"
345       + "   }\n"
346       + "   void method2() {\n"
347       + "     notify();\n"
348       + "   }\n"
349       + "}";
350     when(index.getSource(file)).thenReturn(newSource);
351     when(lastSnapshots.getSource(file)).thenReturn(originalSource);
352
353     when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
354
355     decorator.doDecorate(file);
356
357     verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
358     verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
359
360     ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
361     verify(issueCache).put(argument.capture());
362
363     DefaultIssue issue = argument.getValue();
364     assertThat(issue.line()).isEqualTo(issueOnLine);
365     assertThat(issue.key()).isEqualTo("ABCDE");
366     assertThat(issue.isNew()).isFalse();
367     assertThat(issue.isEndOfLife()).isFalse();
368     assertThat(issue.isOnDisabledRule()).isFalse();
369   }
370
371
372   @Test
373   public void manual_issues_should_be_closed_if_manual_rule_is_removed() throws Exception {
374     // "Unmatched" issues existed in previous scan but not in current one -> they have to be closed
375     Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
376
377     // INPUT : one issue existing during previous scan
378     IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(1).setStatus("OPEN").setRuleKey("manual", "Performance");
379     when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance").setStatus(Rule.STATUS_REMOVED));
380
381     IssueTrackingResult trackingResult = new IssueTrackingResult();
382     trackingResult.addUnmatched(unmatchedIssue);
383
384     String source = "public interface Action {}";
385     when(index.getSource(file)).thenReturn(source);
386     when(lastSnapshots.getSource(file)).thenReturn(source);
387
388     when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
389
390     decorator.doDecorate(file);
391
392     verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
393     verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
394
395     ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
396     verify(issueCache).put(argument.capture());
397
398     DefaultIssue issue = argument.getValue();
399     assertThat(issue.key()).isEqualTo("ABCDE");
400     assertThat(issue.isNew()).isFalse();
401     assertThat(issue.isEndOfLife()).isTrue();
402     assertThat(issue.isOnDisabledRule()).isTrue();
403   }
404
405   @Test
406   public void manual_issues_should_be_closed_if_manual_rule_is_not_found() throws Exception {
407     // "Unmatched" issues existed in previous scan but not in current one -> they have to be closed
408     Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
409
410     // INPUT : one issue existing during previous scan
411     IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(1).setStatus("OPEN").setRuleKey("manual", "Performance");
412     when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(null);
413
414     IssueTrackingResult trackingResult = new IssueTrackingResult();
415     trackingResult.addUnmatched(unmatchedIssue);
416
417     String source = "public interface Action {}";
418     when(index.getSource(file)).thenReturn(source);
419     when(lastSnapshots.getSource(file)).thenReturn(source);
420
421     when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
422
423     decorator.doDecorate(file);
424
425     verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
426     verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
427
428     ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
429     verify(issueCache).put(argument.capture());
430
431     DefaultIssue issue = argument.getValue();
432     assertThat(issue.key()).isEqualTo("ABCDE");
433     assertThat(issue.isNew()).isFalse();
434     assertThat(issue.isEndOfLife()).isTrue();
435     assertThat(issue.isOnDisabledRule()).isTrue();
436   }
437
438   @Test
439   public void manual_issues_should_be_closed_if_new_source_is_shorter() throws Exception {
440     // "Unmatched" issues existed in previous scan but not in current one -> they have to be closed
441     Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
442
443     // INPUT : one issue existing during previous scan
444     IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setLine(6).setStatus("OPEN").setRuleKey("manual", "Performance");
445     when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(null);
446
447     IssueTrackingResult trackingResult = new IssueTrackingResult();
448     trackingResult.addUnmatched(unmatchedIssue);
449
450     String originalSource = "public interface Action {\n"
451       + "   void method1();\n"
452       + "   void method2();\n"
453       + "   void method3();\n"
454       + "   void method4();\n"
455       + "   void method5();\n"
456       + "}";
457     String newSource = "public interface Action {\n"
458       + "   void method1();\n"
459       + "   void method2();\n"
460       + "}";
461     when(index.getSource(file)).thenReturn(newSource);
462     when(lastSnapshots.getSource(file)).thenReturn(originalSource);
463
464     when(tracking.track(isA(SourceHashHolder.class), anyCollection(), anyCollection())).thenReturn(trackingResult);
465
466     decorator.doDecorate(file);
467
468     verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
469     verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
470
471     ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
472     verify(issueCache).put(argument.capture());
473
474     DefaultIssue issue = argument.getValue();
475     verify(updater).setResolution(eq(issue), eq(Issue.RESOLUTION_REMOVED), any(IssueChangeContext.class));
476     verify(updater).setStatus(eq(issue), eq(Issue.STATUS_CLOSED), any(IssueChangeContext.class));
477
478     assertThat(issue.key()).isEqualTo("ABCDE");
479     assertThat(issue.isNew()).isFalse();
480     assertThat(issue.isEndOfLife()).isTrue();
481     assertThat(issue.isOnDisabledRule()).isTrue();
482   }
483
484   @Test
485   public void should_register_issues_on_deleted_components() throws Exception {
486     Project project = new Project("struts");
487     DefaultIssue openIssue = new DefaultIssue();
488     when(issueCache.byComponent("struts")).thenReturn(Arrays.asList(openIssue));
489     IssueDto deadIssue = new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey("squid", "AvoidCycle");
490     when(initialOpenIssues.selectAllIssues()).thenReturn(Arrays.asList(deadIssue));
491
492     decorator.doDecorate(project);
493
494     // the dead issue must be closed -> apply automatic transition, notify handlers and add to cache
495     verify(workflow, times(2)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
496     verify(handlers, times(2)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
497     verify(issueCache, times(2)).put(any(DefaultIssue.class));
498
499     verify(issueCache).put(argThat(new ArgumentMatcher<DefaultIssue>() {
500       @Override
501       public boolean matches(Object o) {
502         DefaultIssue dead = (DefaultIssue) o;
503         return "ABCDE".equals(dead.key()) && !dead.isNew() && dead.isEndOfLife();
504       }
505     }));
506   }
507
508   @Test
509   public void merge_matched_issue() throws Exception {
510     IssueDto previousIssue = new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey("squid", "AvoidCycle")
511       .setLine(10).setSeverity("MAJOR").setMessage("Message").setEffortToFix(1.5).setDebt(1L).setProjectKey("sample");
512     DefaultIssue issue = new DefaultIssue();
513
514     IssueTrackingResult trackingResult = mock(IssueTrackingResult.class);
515     when(trackingResult.matched()).thenReturn(newArrayList(issue));
516     when(trackingResult.matching(eq(issue))).thenReturn(previousIssue);
517     decorator.mergeMatched(trackingResult);
518
519     verify(updater).setPastSeverity(eq(issue), eq("MAJOR"), any(IssueChangeContext.class));
520     verify(updater).setPastLine(eq(issue), eq(10));
521     verify(updater).setPastMessage(eq(issue), eq("Message"), any(IssueChangeContext.class));
522     verify(updater).setPastEffortToFix(eq(issue), eq(1.5), any(IssueChangeContext.class));
523     verify(updater).setPastTechnicalDebt(eq(issue), eq(Duration.create(1L)), any(IssueChangeContext.class));
524     verify(updater).setPastProject(eq(issue), eq("sample"), any(IssueChangeContext.class));
525   }
526
527   @Test
528   public void merge_matched_issue_on_manual_severity() throws Exception {
529     IssueDto previousIssue = new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey("squid", "AvoidCycle")
530       .setLine(10).setManualSeverity(true).setSeverity("MAJOR").setMessage("Message").setEffortToFix(1.5).setDebt(1L);
531     DefaultIssue issue = new DefaultIssue();
532
533     IssueTrackingResult trackingResult = mock(IssueTrackingResult.class);
534     when(trackingResult.matched()).thenReturn(newArrayList(issue));
535     when(trackingResult.matching(eq(issue))).thenReturn(previousIssue);
536     decorator.mergeMatched(trackingResult);
537
538     assertThat(issue.manualSeverity()).isTrue();
539     assertThat(issue.severity()).isEqualTo("MAJOR");
540     verify(updater, never()).setPastSeverity(eq(issue), anyString(), any(IssueChangeContext.class));
541   }
542
543   @Test
544   public void merge_issue_changelog_with_previous_changelog() throws Exception {
545     when(initialOpenIssues.selectChangelog("ABCDE")).thenReturn(newArrayList(new IssueChangeDto().setIssueKey("ABCD")));
546
547     IssueDto previousIssue = new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey("squid", "AvoidCycle")
548       .setLine(10).setMessage("Message").setEffortToFix(1.5).setDebt(1L);
549     DefaultIssue issue = new DefaultIssue();
550
551     IssueTrackingResult trackingResult = mock(IssueTrackingResult.class);
552     when(trackingResult.matched()).thenReturn(newArrayList(issue));
553     when(trackingResult.matching(eq(issue))).thenReturn(previousIssue);
554     decorator.mergeMatched(trackingResult);
555
556     assertThat(issue.changes()).hasSize(1);
557   }
558
559 }