]> source.dussan.org Git - sonarqube.git/blob
e7aa43128dcd6d5a4618abdfd6796a8affe7b7ce
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2023 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.ce.task.projectanalysis.issue;
21
22 import java.util.Arrays;
23 import java.util.List;
24 import java.util.Map;
25 import javax.annotation.Nullable;
26 import org.assertj.core.data.MapEntry;
27 import org.junit.Rule;
28 import org.junit.Test;
29 import org.sonar.api.rules.RuleType;
30 import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule;
31 import org.sonar.ce.task.projectanalysis.component.Component;
32 import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule;
33 import org.sonar.ce.task.projectanalysis.measure.MeasureRepoEntry;
34 import org.sonar.ce.task.projectanalysis.measure.MeasureRepositoryRule;
35 import org.sonar.ce.task.projectanalysis.metric.MetricRepositoryRule;
36 import org.sonar.core.issue.DefaultIssue;
37 import org.sonar.db.rule.RuleTesting;
38
39 import static java.util.Arrays.stream;
40 import static org.assertj.core.api.Assertions.assertThat;
41 import static org.assertj.core.api.Assertions.entry;
42 import static org.mockito.ArgumentMatchers.any;
43 import static org.mockito.ArgumentMatchers.eq;
44 import static org.mockito.Mockito.mock;
45 import static org.mockito.Mockito.when;
46 import static org.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE;
47 import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
48 import static org.sonar.api.issue.Issue.RESOLUTION_WONT_FIX;
49 import static org.sonar.api.issue.Issue.STATUS_CLOSED;
50 import static org.sonar.api.issue.Issue.STATUS_CONFIRMED;
51 import static org.sonar.api.issue.Issue.STATUS_OPEN;
52 import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
53 import static org.sonar.api.measures.CoreMetrics.BLOCKER_VIOLATIONS;
54 import static org.sonar.api.measures.CoreMetrics.BLOCKER_VIOLATIONS_KEY;
55 import static org.sonar.api.measures.CoreMetrics.BUGS;
56 import static org.sonar.api.measures.CoreMetrics.BUGS_KEY;
57 import static org.sonar.api.measures.CoreMetrics.CODE_SMELLS;
58 import static org.sonar.api.measures.CoreMetrics.CODE_SMELLS_KEY;
59 import static org.sonar.api.measures.CoreMetrics.CONFIRMED_ISSUES;
60 import static org.sonar.api.measures.CoreMetrics.CONFIRMED_ISSUES_KEY;
61 import static org.sonar.api.measures.CoreMetrics.CRITICAL_VIOLATIONS;
62 import static org.sonar.api.measures.CoreMetrics.CRITICAL_VIOLATIONS_KEY;
63 import static org.sonar.api.measures.CoreMetrics.FALSE_POSITIVE_ISSUES;
64 import static org.sonar.api.measures.CoreMetrics.FALSE_POSITIVE_ISSUES_KEY;
65 import static org.sonar.api.measures.CoreMetrics.INFO_VIOLATIONS;
66 import static org.sonar.api.measures.CoreMetrics.MAJOR_VIOLATIONS;
67 import static org.sonar.api.measures.CoreMetrics.MAJOR_VIOLATIONS_KEY;
68 import static org.sonar.api.measures.CoreMetrics.MINOR_VIOLATIONS;
69 import static org.sonar.api.measures.CoreMetrics.NEW_BLOCKER_VIOLATIONS;
70 import static org.sonar.api.measures.CoreMetrics.NEW_BLOCKER_VIOLATIONS_KEY;
71 import static org.sonar.api.measures.CoreMetrics.NEW_BUGS;
72 import static org.sonar.api.measures.CoreMetrics.NEW_BUGS_KEY;
73 import static org.sonar.api.measures.CoreMetrics.NEW_CODE_SMELLS;
74 import static org.sonar.api.measures.CoreMetrics.NEW_CODE_SMELLS_KEY;
75 import static org.sonar.api.measures.CoreMetrics.NEW_CRITICAL_VIOLATIONS;
76 import static org.sonar.api.measures.CoreMetrics.NEW_CRITICAL_VIOLATIONS_KEY;
77 import static org.sonar.api.measures.CoreMetrics.NEW_INFO_VIOLATIONS;
78 import static org.sonar.api.measures.CoreMetrics.NEW_MAJOR_VIOLATIONS;
79 import static org.sonar.api.measures.CoreMetrics.NEW_MAJOR_VIOLATIONS_KEY;
80 import static org.sonar.api.measures.CoreMetrics.NEW_MINOR_VIOLATIONS;
81 import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS;
82 import static org.sonar.api.measures.CoreMetrics.NEW_SECURITY_HOTSPOTS_KEY;
83 import static org.sonar.api.measures.CoreMetrics.NEW_VIOLATIONS;
84 import static org.sonar.api.measures.CoreMetrics.NEW_VIOLATIONS_KEY;
85 import static org.sonar.api.measures.CoreMetrics.NEW_VULNERABILITIES;
86 import static org.sonar.api.measures.CoreMetrics.NEW_VULNERABILITIES_KEY;
87 import static org.sonar.api.measures.CoreMetrics.OPEN_ISSUES;
88 import static org.sonar.api.measures.CoreMetrics.OPEN_ISSUES_KEY;
89 import static org.sonar.api.measures.CoreMetrics.REOPENED_ISSUES;
90 import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS;
91 import static org.sonar.api.measures.CoreMetrics.SECURITY_HOTSPOTS_KEY;
92 import static org.sonar.api.measures.CoreMetrics.VIOLATIONS;
93 import static org.sonar.api.measures.CoreMetrics.VIOLATIONS_KEY;
94 import static org.sonar.api.measures.CoreMetrics.VULNERABILITIES;
95 import static org.sonar.api.measures.CoreMetrics.VULNERABILITIES_KEY;
96 import static org.sonar.api.measures.CoreMetrics.WONT_FIX_ISSUES;
97 import static org.sonar.api.measures.CoreMetrics.WONT_FIX_ISSUES_KEY;
98 import static org.sonar.api.rule.Severity.BLOCKER;
99 import static org.sonar.api.rule.Severity.CRITICAL;
100 import static org.sonar.api.rule.Severity.MAJOR;
101 import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder;
102 import static org.sonar.ce.task.projectanalysis.measure.Measure.newMeasureBuilder;
103 import static org.sonar.ce.task.projectanalysis.measure.MeasureRepoEntry.entryOf;
104
105 public class IssueCounterTest {
106
107   private static final Component FILE1 = builder(Component.Type.FILE, 1).build();
108   private static final Component FILE2 = builder(Component.Type.FILE, 2).build();
109   private static final Component FILE3 = builder(Component.Type.FILE, 3).build();
110   private static final Component PROJECT = builder(Component.Type.PROJECT, 4).addChildren(FILE1, FILE2, FILE3).build();
111
112   @Rule
113   public BatchReportReaderRule reportReader = new BatchReportReaderRule();
114
115   @Rule
116   public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
117
118   @Rule
119   public MetricRepositoryRule metricRepository = new MetricRepositoryRule()
120     .add(VIOLATIONS)
121     .add(OPEN_ISSUES)
122     .add(REOPENED_ISSUES)
123     .add(CONFIRMED_ISSUES)
124     .add(BLOCKER_VIOLATIONS)
125     .add(CRITICAL_VIOLATIONS)
126     .add(MAJOR_VIOLATIONS)
127     .add(MINOR_VIOLATIONS)
128     .add(INFO_VIOLATIONS)
129     .add(NEW_VIOLATIONS)
130     .add(NEW_BLOCKER_VIOLATIONS)
131     .add(NEW_CRITICAL_VIOLATIONS)
132     .add(NEW_MAJOR_VIOLATIONS)
133     .add(NEW_MINOR_VIOLATIONS)
134     .add(NEW_INFO_VIOLATIONS)
135     .add(FALSE_POSITIVE_ISSUES)
136     .add(WONT_FIX_ISSUES)
137     .add(CODE_SMELLS)
138     .add(BUGS)
139     .add(VULNERABILITIES)
140     .add(SECURITY_HOTSPOTS)
141     .add(NEW_CODE_SMELLS)
142     .add(NEW_BUGS)
143     .add(NEW_VULNERABILITIES)
144     .add(NEW_SECURITY_HOTSPOTS);
145
146   @Rule
147   public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);
148   private NewIssueClassifier newIssueClassifier = mock(NewIssueClassifier.class);
149   private IssueCounter underTest = new IssueCounter(metricRepository, measureRepository, newIssueClassifier);
150
151   @Test
152   public void count_issues_by_status() {
153     // bottom-up traversal -> from files to project
154     underTest.beforeComponent(FILE1);
155     underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER));
156     underTest.onIssue(FILE1, createIssue(RESOLUTION_FIXED, STATUS_CLOSED, MAJOR));
157     underTest.onIssue(FILE1, createIssue(RESOLUTION_FALSE_POSITIVE, STATUS_RESOLVED, MAJOR));
158     underTest.afterComponent(FILE1);
159
160     underTest.beforeComponent(FILE2);
161     underTest.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, BLOCKER));
162     underTest.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, MAJOR));
163     underTest.afterComponent(FILE2);
164
165     underTest.beforeComponent(FILE3);
166     // Security hotspot should be ignored
167     underTest.onIssue(FILE3, createSecurityHotspot().setStatus(STATUS_OPEN));
168     underTest.afterComponent(FILE3);
169
170     underTest.beforeComponent(PROJECT);
171     underTest.afterComponent(PROJECT);
172
173     assertMeasures(FILE1, entry(VIOLATIONS_KEY, 1), entry(OPEN_ISSUES_KEY, 1), entry(CONFIRMED_ISSUES_KEY, 0));
174     assertMeasures(FILE2, entry(VIOLATIONS_KEY, 2), entry(OPEN_ISSUES_KEY, 0), entry(CONFIRMED_ISSUES_KEY, 2));
175     assertMeasures(FILE3, entry(VIOLATIONS_KEY, 0));
176     assertMeasures(PROJECT, entry(VIOLATIONS_KEY, 3), entry(OPEN_ISSUES_KEY, 1), entry(CONFIRMED_ISSUES_KEY, 2));
177   }
178
179   @Test
180   public void count_issues_by_resolution() {
181     // bottom-up traversal -> from files to project
182     underTest.beforeComponent(FILE1);
183     underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER));
184     underTest.onIssue(FILE1, createIssue(RESOLUTION_FIXED, STATUS_CLOSED, MAJOR));
185     underTest.onIssue(FILE1, createIssue(RESOLUTION_FALSE_POSITIVE, STATUS_RESOLVED, MAJOR));
186     underTest.onIssue(FILE1, createIssue(RESOLUTION_WONT_FIX, STATUS_CLOSED, MAJOR));
187     underTest.afterComponent(FILE1);
188
189     underTest.beforeComponent(FILE2);
190     underTest.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, BLOCKER));
191     underTest.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, MAJOR));
192     underTest.onIssue(FILE2, createIssue(RESOLUTION_WONT_FIX, STATUS_CLOSED, MAJOR));
193     underTest.afterComponent(FILE2);
194
195     underTest.beforeComponent(FILE3);
196     // Security hotspot should be ignored
197     underTest.onIssue(FILE3, createSecurityHotspot().setResolution(RESOLUTION_WONT_FIX));
198     underTest.afterComponent(FILE3);
199
200     underTest.beforeComponent(PROJECT);
201     underTest.afterComponent(PROJECT);
202
203     assertMeasures(FILE1, entry(VIOLATIONS_KEY, 1), entry(FALSE_POSITIVE_ISSUES_KEY, 1), entry(WONT_FIX_ISSUES_KEY, 1));
204     assertMeasures(FILE2, entry(VIOLATIONS_KEY, 2), entry(FALSE_POSITIVE_ISSUES_KEY, 0), entry(WONT_FIX_ISSUES_KEY, 1));
205     assertMeasures(FILE3, entry(VIOLATIONS_KEY, 0));
206     assertMeasures(PROJECT, entry(VIOLATIONS_KEY, 3), entry(FALSE_POSITIVE_ISSUES_KEY, 1), entry(WONT_FIX_ISSUES_KEY, 2));
207   }
208
209   @Test
210   public void count_unresolved_issues_by_severity() {
211     // bottom-up traversal -> from files to project
212     underTest.beforeComponent(FILE1);
213     underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER));
214     // this resolved issue is ignored
215     underTest.onIssue(FILE1, createIssue(RESOLUTION_FIXED, STATUS_CLOSED, MAJOR));
216     underTest.afterComponent(FILE1);
217
218     underTest.beforeComponent(FILE2);
219     underTest.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, BLOCKER));
220     underTest.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, MAJOR));
221     underTest.afterComponent(FILE2);
222
223     underTest.beforeComponent(PROJECT);
224     // Security hotspot should be ignored
225     underTest.onIssue(FILE3, createSecurityHotspot().setSeverity(MAJOR));
226     underTest.afterComponent(PROJECT);
227
228     assertMeasures(FILE1, entry(BLOCKER_VIOLATIONS_KEY, 1), entry(CRITICAL_VIOLATIONS_KEY, 0), entry(MAJOR_VIOLATIONS_KEY, 0));
229     assertMeasures(FILE2, entry(BLOCKER_VIOLATIONS_KEY, 1), entry(CRITICAL_VIOLATIONS_KEY, 0), entry(MAJOR_VIOLATIONS_KEY, 1));
230     assertMeasures(PROJECT, entry(BLOCKER_VIOLATIONS_KEY, 2), entry(CRITICAL_VIOLATIONS_KEY, 0), entry(MAJOR_VIOLATIONS_KEY, 1));
231   }
232
233   @Test
234   public void count_unresolved_issues_by_type() {
235     // bottom-up traversal -> from files to project
236     // file1 : one open code smell, one closed code smell (which will be excluded from metric)
237     underTest.beforeComponent(FILE1);
238     underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER).setType(RuleType.CODE_SMELL));
239     underTest.onIssue(FILE1, createIssue(RESOLUTION_FIXED, STATUS_CLOSED, MAJOR).setType(RuleType.CODE_SMELL));
240     underTest.afterComponent(FILE1);
241
242     // file2 : one bug
243     underTest.beforeComponent(FILE2);
244     underTest.onIssue(FILE2, createIssue(null, STATUS_CONFIRMED, BLOCKER).setType(RuleType.BUG));
245     underTest.afterComponent(FILE2);
246
247     // file3 : one unresolved security hotspot
248     underTest.beforeComponent(FILE3);
249     underTest.onIssue(FILE3, createSecurityHotspot());
250     underTest.onIssue(FILE3, createSecurityHotspot().setResolution(RESOLUTION_WONT_FIX).setStatus(STATUS_CLOSED));
251     underTest.afterComponent(FILE3);
252
253     underTest.beforeComponent(PROJECT);
254     underTest.afterComponent(PROJECT);
255
256     assertMeasures(FILE1, entry(CODE_SMELLS_KEY, 1), entry(BUGS_KEY, 0), entry(VULNERABILITIES_KEY, 0), entry(SECURITY_HOTSPOTS_KEY, 0));
257     assertMeasures(FILE2, entry(CODE_SMELLS_KEY, 0), entry(BUGS_KEY, 1), entry(VULNERABILITIES_KEY, 0), entry(SECURITY_HOTSPOTS_KEY, 0));
258     assertMeasures(FILE3, entry(CODE_SMELLS_KEY, 0), entry(BUGS_KEY, 0), entry(VULNERABILITIES_KEY, 0), entry(SECURITY_HOTSPOTS_KEY, 1));
259     assertMeasures(PROJECT, entry(CODE_SMELLS_KEY, 1), entry(BUGS_KEY, 1), entry(VULNERABILITIES_KEY, 0), entry(SECURITY_HOTSPOTS_KEY, 1));
260   }
261
262   @Test
263   public void count_new_issues() {
264     when(newIssueClassifier.isEnabled()).thenReturn(true);
265
266     underTest.beforeComponent(FILE1);
267     // created before -> existing issues (so ignored)
268     underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER).setType(RuleType.CODE_SMELL));
269     underTest.onIssue(FILE1, createIssue(null, STATUS_OPEN, BLOCKER).setType(RuleType.BUG));
270
271     // created after -> 4 new issues but 1 is closed
272     underTest.onIssue(FILE1, createNewIssue(null, STATUS_OPEN, CRITICAL).setType(RuleType.CODE_SMELL));
273     underTest.onIssue(FILE1, createNewIssue(null, STATUS_OPEN, CRITICAL).setType(RuleType.BUG));
274     underTest.onIssue(FILE1, createNewIssue(RESOLUTION_FIXED, STATUS_CLOSED, MAJOR).setType(RuleType.BUG));
275     underTest.onIssue(FILE1, createNewSecurityHotspot());
276     underTest.onIssue(FILE1, createNewSecurityHotspot().setResolution(RESOLUTION_WONT_FIX).setStatus(STATUS_CLOSED));
277     underTest.afterComponent(FILE1);
278
279     underTest.beforeComponent(FILE2);
280     underTest.afterComponent(FILE2);
281
282     underTest.beforeComponent(PROJECT);
283     underTest.afterComponent(PROJECT);
284
285     assertValues(FILE1, entry(NEW_VIOLATIONS_KEY, 2), entry(NEW_CRITICAL_VIOLATIONS_KEY, 2), entry(NEW_BLOCKER_VIOLATIONS_KEY, 0), entry(NEW_MAJOR_VIOLATIONS_KEY, 0),
286       entry(NEW_CODE_SMELLS_KEY, 1), entry(NEW_BUGS_KEY, 1), entry(NEW_VULNERABILITIES_KEY, 0), entry(NEW_SECURITY_HOTSPOTS_KEY, 1));
287     assertValues(PROJECT, entry(NEW_VIOLATIONS_KEY, 2), entry(NEW_CRITICAL_VIOLATIONS_KEY, 2), entry(NEW_BLOCKER_VIOLATIONS_KEY, 0), entry(NEW_MAJOR_VIOLATIONS_KEY, 0),
288       entry(NEW_CODE_SMELLS_KEY, 1), entry(NEW_BUGS_KEY, 1), entry(NEW_VULNERABILITIES_KEY, 0), entry(NEW_SECURITY_HOTSPOTS_KEY, 1));
289   }
290
291   @Test
292   public void exclude_hotspots_from_issue_counts() {
293     // bottom-up traversal -> from files to project
294     underTest.beforeComponent(FILE1);
295     underTest.onIssue(FILE1, createSecurityHotspot());
296     underTest.onIssue(FILE1, createSecurityHotspot());
297     underTest.afterComponent(FILE1);
298
299     underTest.beforeComponent(FILE2);
300     underTest.onIssue(FILE2, createSecurityHotspot());
301     underTest.afterComponent(FILE2);
302
303     underTest.beforeComponent(FILE3);
304     underTest.afterComponent(FILE3);
305
306     underTest.beforeComponent(PROJECT);
307     underTest.afterComponent(PROJECT);
308
309     assertMeasures(FILE1, entry(VIOLATIONS_KEY, 0), entry(OPEN_ISSUES_KEY, 0), entry(CONFIRMED_ISSUES_KEY, 0));
310     assertMeasures(FILE2, entry(VIOLATIONS_KEY, 0), entry(OPEN_ISSUES_KEY, 0), entry(CONFIRMED_ISSUES_KEY, 0));
311     assertMeasures(FILE3, entry(VIOLATIONS_KEY, 0));
312     assertMeasures(PROJECT, entry(VIOLATIONS_KEY, 0), entry(OPEN_ISSUES_KEY, 0), entry(CONFIRMED_ISSUES_KEY, 0));
313   }
314
315   @Test
316   public void exclude_new_hotspots_from_issue_counts() {
317     when(newIssueClassifier.isEnabled()).thenReturn(true);
318
319     underTest.beforeComponent(FILE1);
320     // created before -> existing issues (so ignored)
321     underTest.onIssue(FILE1, createSecurityHotspot());
322     underTest.onIssue(FILE1, createSecurityHotspot());
323
324     // created after, but closed
325     underTest.onIssue(FILE1, createNewSecurityHotspot().setStatus(STATUS_RESOLVED).setResolution(RESOLUTION_WONT_FIX));
326
327     for (String severity : Arrays.asList(CRITICAL, BLOCKER, MAJOR)) {
328       DefaultIssue issue = createNewSecurityHotspot();
329       issue.setSeverity(severity);
330       underTest.onIssue(FILE1, issue);
331     }
332     underTest.afterComponent(FILE1);
333
334     underTest.beforeComponent(FILE2);
335     underTest.afterComponent(FILE2);
336
337     underTest.beforeComponent(PROJECT);
338     underTest.afterComponent(PROJECT);
339
340     assertValues(FILE1, entry(NEW_VIOLATIONS_KEY, 0), entry(NEW_CRITICAL_VIOLATIONS_KEY, 0), entry(NEW_BLOCKER_VIOLATIONS_KEY, 0), entry(NEW_MAJOR_VIOLATIONS_KEY, 0),
341       entry(NEW_VULNERABILITIES_KEY, 0));
342     assertValues(PROJECT, entry(NEW_VIOLATIONS_KEY, 0), entry(NEW_CRITICAL_VIOLATIONS_KEY, 0), entry(NEW_BLOCKER_VIOLATIONS_KEY, 0), entry(NEW_MAJOR_VIOLATIONS_KEY, 0),
343       entry(NEW_VULNERABILITIES_KEY, 0));
344   }
345
346   @SafeVarargs
347   private final void assertValues(Component componentRef, MapEntry<String, Integer>... entries) {
348     assertThat(measureRepository.getRawMeasures(componentRef).entrySet()
349       .stream()
350       .map(e -> entry(e.getKey(), e.getValue().getIntValue())))
351       .contains(entries);
352   }
353
354   @SafeVarargs
355   private final void assertMeasures(Component componentRef, Map.Entry<String, Integer>... entries) {
356     List<MeasureRepoEntry> expected = stream(entries)
357       .map(e -> entryOf(e.getKey(), newMeasureBuilder().create(e.getValue())))
358       .toList();
359
360     assertThat(measureRepository.getRawMeasures(componentRef).entrySet().stream().map(e -> entryOf(e.getKey(), e.getValue())))
361       .containsAll(expected);
362   }
363
364   private DefaultIssue createNewIssue(@Nullable String resolution, String status, String severity) {
365     return createNewIssue(resolution, status, severity, RuleType.CODE_SMELL);
366   }
367
368   private DefaultIssue createNewIssue(@Nullable String resolution, String status, String severity, RuleType ruleType) {
369     DefaultIssue issue = createIssue(resolution, status, severity, ruleType);
370     when(newIssueClassifier.isNew(any(), eq(issue))).thenReturn(true);
371     return issue;
372   }
373
374   private static DefaultIssue createIssue(@Nullable String resolution, String status, String severity) {
375     return createIssue(resolution, status, severity, RuleType.CODE_SMELL);
376   }
377
378   private static DefaultIssue createIssue(@Nullable String resolution, String status, String severity, RuleType ruleType) {
379     return new DefaultIssue()
380       .setResolution(resolution).setStatus(status)
381       .setSeverity(severity).setRuleKey(RuleTesting.XOO_X1)
382       .setType(ruleType);
383   }
384
385   private static DefaultIssue createSecurityHotspot() {
386     return createIssue(null, STATUS_OPEN, "MAJOR", RuleType.SECURITY_HOTSPOT);
387   }
388
389   private DefaultIssue createNewSecurityHotspot() {
390     return createNewIssue(null, STATUS_OPEN, "MAJOR", RuleType.SECURITY_HOTSPOT);
391   }
392 }