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 com.google.common.collect.Sets;
23 import org.junit.Before;
24 import org.junit.Test;
25 import org.mockito.ArgumentMatcher;
26 import org.mockito.Mockito;
27 import org.sonar.api.batch.DecoratorContext;
28 import org.sonar.api.resources.File;
29 import org.sonar.api.resources.Project;
30 import org.sonar.api.resources.Resource;
31 import org.sonar.batch.issue.ScanIssues;
32 import org.sonar.core.issue.DefaultIssue;
33 import org.sonar.core.issue.IssueChangeContext;
34 import org.sonar.core.issue.db.IssueDto;
35 import org.sonar.core.issue.workflow.IssueWorkflow;
36 import org.sonar.core.persistence.AbstractDaoTestCase;
37 import org.sonar.java.api.JavaClass;
41 import static org.fest.assertions.Assertions.assertThat;
42 import static org.mockito.Mockito.*;
44 public class IssueTrackingDecoratorTest extends AbstractDaoTestCase {
46 IssueTrackingDecorator decorator;
47 ScanIssues scanIssues = mock(ScanIssues.class);
48 InitialOpenIssuesStack initialOpenIssues = mock(InitialOpenIssuesStack.class);
49 IssueTracking tracking = mock(IssueTracking.class);
50 IssueFilters filters = mock(IssueFilters.class);
51 IssueHandlers handlers = mock(IssueHandlers.class);
52 IssueWorkflow workflow = mock(IssueWorkflow.class);
53 Date loadedDate = new Date();
57 when(initialOpenIssues.getLoadedDate()).thenReturn(loadedDate);
58 decorator = new IssueTrackingDecorator(scanIssues, initialOpenIssues, tracking,
59 filters, handlers, workflow, mock(Project.class));
63 public void should_execute_on_project() {
64 Project project = mock(Project.class);
65 when(project.isLatestAnalysis()).thenReturn(true);
66 assertThat(decorator.shouldExecuteOnProject(project)).isTrue();
70 public void should_not_execute_on_project_if_past_scan() {
71 Project project = mock(Project.class);
72 when(project.isLatestAnalysis()).thenReturn(false);
73 assertThat(decorator.shouldExecuteOnProject(project)).isFalse();
77 public void should_not_be_executed_on_classes_not_methods() throws Exception {
78 DecoratorContext context = mock(DecoratorContext.class);
79 decorator.decorate(JavaClass.create("org.foo.Bar"), context);
80 verifyZeroInteractions(context, scanIssues, tracking, filters, handlers, workflow);
84 public void should_process_open_issues() throws Exception {
85 Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
86 final DefaultIssue issue = new DefaultIssue();
88 // INPUT : one issue, no open issues during previous scan, no filtering
89 when(scanIssues.issues("struts:Action.java")).thenReturn(Arrays.asList(issue));
90 when(filters.accept(issue)).thenReturn(true);
91 List<IssueDto> dbIssues = Collections.emptyList();
92 when(initialOpenIssues.selectAndRemove(123)).thenReturn(dbIssues);
94 decorator.decorate(file, mock(DecoratorContext.class, Mockito.RETURNS_MOCKS));
96 // Apply filters, track, apply transitions, notify extensions then update cache
97 verify(filters).accept(issue);
98 verify(tracking).track(eq(file), eq(dbIssues), argThat(new ArgumentMatcher<Collection<DefaultIssue>>() {
100 public boolean matches(Object o) {
101 List<DefaultIssue> issues = (List<DefaultIssue>) o;
102 return issues.size() == 1 && issues.get(0) == issue;
105 verify(workflow).doAutomaticTransition(eq(issue), any(IssueChangeContext.class));
106 verify(handlers).execute(eq(issue), any(IssueChangeContext.class));
107 verify(scanIssues).addOrUpdate(issue);
111 public void should_register_unmatched_issues() throws Exception {
112 // "Unmatched" issues existed in previous scan but not in current one -> they have to be closed
113 Resource file = new File("Action.java").setEffectiveKey("struts:Action.java").setId(123);
114 DefaultIssue openIssue = new DefaultIssue();
116 // INPUT : one issue, one open issue during previous scan, no filtering
117 when(scanIssues.issues("struts:Action.java")).thenReturn(Arrays.asList(openIssue));
118 when(filters.accept(openIssue)).thenReturn(true);
119 IssueDto unmatchedIssue = new IssueDto().setKee("ABCDE").setResolution("OPEN").setStatus("OPEN").setRuleKey_unit_test_only("squid", "AvoidCycle");
120 List<IssueDto> unmatchedIssues = Arrays.asList(unmatchedIssue);
121 when(tracking.track(eq(file), anyCollection(), anyCollection())).thenReturn(Sets.newHashSet(unmatchedIssues));
123 decorator.decorate(file, mock(DecoratorContext.class, Mockito.RETURNS_MOCKS));
125 verify(workflow, times(2)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
126 verify(handlers, times(2)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
127 verify(scanIssues, times(2)).addOrUpdate(any(DefaultIssue.class));
129 verify(scanIssues).addOrUpdate(argThat(new ArgumentMatcher<DefaultIssue>() {
131 public boolean matches(Object o) {
132 DefaultIssue issue = (DefaultIssue) o;
133 return "ABCDE".equals(issue.key());
139 public void should_register_issues_on_deleted_components() throws Exception {
140 Project project = new Project("struts");
141 DefaultIssue openIssue = new DefaultIssue();
142 when(scanIssues.issues("struts")).thenReturn(Arrays.asList(openIssue));
143 when(filters.accept(openIssue)).thenReturn(true);
144 IssueDto deadIssue = new IssueDto().setKee("ABCDE").setResolution("OPEN").setStatus("OPEN").setRuleKey_unit_test_only("squid", "AvoidCycle");
145 when(initialOpenIssues.getAllIssues()).thenReturn(Arrays.asList(deadIssue));
147 decorator.decorate(project, mock(DecoratorContext.class, Mockito.RETURNS_MOCKS));
149 // the dead issue must be closed -> apply automatic transition, notify handlers and add to cache
150 verify(workflow, times(2)).doAutomaticTransition(any(DefaultIssue.class), any(IssueChangeContext.class));
151 verify(handlers, times(2)).execute(any(DefaultIssue.class), any(IssueChangeContext.class));
152 verify(scanIssues, times(2)).addOrUpdate(any(DefaultIssue.class));
154 verify(scanIssues).addOrUpdate(argThat(new ArgumentMatcher<DefaultIssue>() {
156 public boolean matches(Object o) {
157 DefaultIssue dead = (DefaultIssue) o;
158 return "ABCDE".equals(dead.key()) && !dead.isNew() && !dead.isAlive();