2 * SonarQube, open source software quality management tool.
3 * Copyright (C) 2008-2013 SonarSource
4 * mailto:contact AT sonarsource DOT com
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.
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.
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.plugins.core.issue;
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.component.ResourcePerspectives;
28 import org.sonar.api.profiles.RulesProfile;
29 import org.sonar.api.resources.File;
30 import org.sonar.api.resources.Project;
31 import org.sonar.api.resources.Resource;
32 import org.sonar.api.rule.RuleKey;
33 import org.sonar.api.rules.Rule;
34 import org.sonar.api.rules.RuleFinder;
35 import org.sonar.batch.issue.IssueCache;
36 import org.sonar.core.issue.DefaultIssue;
37 import org.sonar.core.issue.IssueChangeContext;
38 import org.sonar.core.issue.IssueUpdater;
39 import org.sonar.core.issue.db.IssueDto;
40 import org.sonar.core.issue.workflow.IssueWorkflow;
41 import org.sonar.core.persistence.AbstractDaoTestCase;
42 import org.sonar.java.api.JavaClass;
44 import java.util.Arrays;
45 import java.util.Collection;
46 import java.util.Collections;
47 import java.util.List;
49 import static org.fest.assertions.Assertions.assertThat;
50 import static org.mockito.Mockito.*;
52 public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
54 IssueTrackingDecorator decorator;
55 IssueCache issueCache = mock(IssueCache.class, RETURNS_MOCKS);
56 InitialOpenIssuesStack initialOpenIssues = mock(InitialOpenIssuesStack.class);
57 IssueTracking tracking = mock(IssueTracking.class, RETURNS_MOCKS);
58 IssueHandlers handlers = mock(IssueHandlers.class);
59 IssueWorkflow workflow = mock(IssueWorkflow.class);
60 IssueUpdater updater = mock(IssueUpdater.class);
61 ResourcePerspectives perspectives = mock(ResourcePerspectives.class);
62 RulesProfile profile = mock(RulesProfile.class);
63 RuleFinder ruleFinder = mock(RuleFinder.class);
67 decorator = new IssueTrackingDecorator(
81 public void should_execute_on_project() {
82 Project project = mock(Project.class);
83 assertThat(decorator.shouldExecuteOnProject(project)).isTrue();
87 public void should_not_be_executed_on_classes_not_methods() throws Exception {
88 DecoratorContext context = mock(DecoratorContext.class);
89 decorator.decorate(JavaClass.create("org.foo.Bar"), context);
90 verifyZeroInteractions(context, issueCache, tracking, handlers, workflow);
94 public void should_process_open_issues() throws Exception {
95 Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
96 final DefaultIssue issue = new DefaultIssue();
98 // INPUT : one issue, no open issues during previous scan, no filtering
99 when(issueCache.byComponent("struts:Action.java")).thenReturn(Arrays.asList(issue));
100 List<IssueDto> dbIssues = Collections.emptyList();
101 when(initialOpenIssues.selectAndRemove("struts:Action.java")).thenReturn(dbIssues);
103 decorator.doDecorate(file);
105 // Apply filters, track, apply transitions, notify extensions then update cache
106 verify(tracking).track(eq(file), eq(dbIssues), argThat(new ArgumentMatcher<Collection<DefaultIssue>>() {
108 public boolean matches(Object o) {
109 List<DefaultIssue> issues = (List<DefaultIssue>) o;
110 return issues.size() == 1 && issues.get(0) == issue;
113 verify(workflow).doAutomaticTransition(eq(issue), any(IssueChangeContext.class));
114 verify(handlers).execute(eq(issue), any(IssueChangeContext.class));
115 verify(issueCache).put(issue);
119 public void should_register_unmatched_issues_as_end_of_life() throws Exception {
120 // "Unmatched" issues existed in previous scan but not in current one -> they have to be closed
121 Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
123 // INPUT : one issue existing during previous scan
124 IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey_unit_test_only("squid", "AvoidCycle");
126 IssueTrackingResult trackingResult = new IssueTrackingResult();
127 trackingResult.addUnmatched(unmatchedIssue);
129 when(tracking.track(eq(file), anyCollection(), anyCollection())).thenReturn(trackingResult);
131 decorator.doDecorate(file);
133 verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
134 verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
136 ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
137 verify(issueCache).put(argument.capture());
139 DefaultIssue issue = argument.getValue();
140 assertThat(issue.key()).isEqualTo("ABCDE");
141 assertThat(issue.isNew()).isFalse();
142 assertThat(issue.isEndOfLife()).isTrue();
146 public void manual_issues_should_be_kept_open() throws Exception {
147 // "Unmatched" issues existed in previous scan but not in current one -> they have to be closed
148 Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
150 // INPUT : one issue existing during previous scan
151 IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setStatus("OPEN").setRuleKey_unit_test_only("manual", "Performance");
152 when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance"));
154 IssueTrackingResult trackingResult = new IssueTrackingResult();
155 trackingResult.addUnmatched(unmatchedIssue);
157 when(tracking.track(eq(file), anyCollection(), anyCollection())).thenReturn(trackingResult);
159 decorator.doDecorate(file);
161 verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
162 verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
164 ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
165 verify(issueCache).put(argument.capture());
167 DefaultIssue issue = argument.getValue();
168 assertThat(issue.key()).isEqualTo("ABCDE");
169 assertThat(issue.isNew()).isFalse();
170 assertThat(issue.isEndOfLife()).isFalse();
171 assertThat(issue.isOnDisabledRule()).isFalse();
175 public void manual_issues_should_be_closed_if_manual_rule_is_removed() throws Exception {
176 // "Unmatched" issues existed in previous scan but not in current one -> they have to be closed
177 Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
179 // INPUT : one issue existing during previous scan
180 IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setReporter("freddy").setStatus("OPEN").setRuleKey_unit_test_only("manual", "Performance");
181 when(ruleFinder.findByKey(RuleKey.of("manual", "Performance"))).thenReturn(new Rule("manual", "Performance").setStatus(Rule.STATUS_REMOVED));
183 IssueTrackingResult trackingResult = new IssueTrackingResult();
184 trackingResult.addUnmatched(unmatchedIssue);
186 when(tracking.track(eq(file), anyCollection(), anyCollection())).thenReturn(trackingResult);
188 decorator.doDecorate(file);
190 verify(workflow, times(1)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
191 verify(handlers, times(1)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
193 ArgumentCaptor<DefaultIssue> argument = ArgumentCaptor.forClass(DefaultIssue.class);
194 verify(issueCache).put(argument.capture());
196 DefaultIssue issue = argument.getValue();
197 assertThat(issue.key()).isEqualTo("ABCDE");
198 assertThat(issue.isNew()).isFalse();
199 assertThat(issue.isEndOfLife()).isTrue();
200 assertThat(issue.isOnDisabledRule()).isTrue();
204 public void should_register_issues_on_deleted_components() throws Exception {
205 Project project = new Project("struts");
206 DefaultIssue openIssue = new DefaultIssue();
207 when(issueCache.byComponent("struts")).thenReturn(Arrays.asList(openIssue));
208 IssueDto deadIssue = new IssueDto().setKee("ABCDE").setResolution(null).setStatus("OPEN").setRuleKey_unit_test_only("squid", "AvoidCycle");
209 when(initialOpenIssues.selectAll()).thenReturn(Arrays.asList(deadIssue));
211 decorator.doDecorate(project);
213 // the dead issue must be closed -> apply automatic transition, notify handlers and add to cache
214 verify(workflow, times(2)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
215 verify(handlers, times(2)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
216 verify(issueCache, times(2)).put(any(DefaultIssue.class));
218 verify(issueCache).put(argThat(new ArgumentMatcher<DefaultIssue>() {
220 public boolean matches(Object o) {
221 DefaultIssue dead = (DefaultIssue) o;
222 return "ABCDE".equals(dead.key()) && !dead.isNew() && dead.isEndOfLife();