3 * Copyright (C) 2009-2020 SonarSource SA
4 * mailto:info AT sonarsource DOT com
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.
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.
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.server.issue.workflow;
22 import com.tngtech.java.junit.dataprovider.DataProvider;
23 import com.tngtech.java.junit.dataprovider.DataProviderRunner;
24 import com.tngtech.java.junit.dataprovider.UseDataProvider;
25 import java.util.Arrays;
26 import java.util.Calendar;
27 import java.util.Collection;
28 import java.util.Date;
29 import java.util.List;
30 import org.apache.commons.lang.time.DateUtils;
31 import org.junit.Test;
32 import org.junit.runner.RunWith;
33 import org.sonar.api.issue.DefaultTransitions;
34 import org.sonar.api.issue.Issue;
35 import org.sonar.api.rule.RuleKey;
36 import org.sonar.api.rules.RuleType;
37 import org.sonar.core.issue.DefaultIssue;
38 import org.sonar.core.issue.FieldDiffs;
39 import org.sonar.core.issue.IssueChangeContext;
40 import org.sonar.core.util.stream.MoreCollectors;
41 import org.sonar.server.issue.IssueFieldsSetter;
43 import static org.assertj.core.api.Assertions.assertThat;
44 import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
45 import static org.sonar.api.issue.Issue.RESOLUTION_REMOVED;
46 import static org.sonar.api.issue.Issue.STATUS_CLOSED;
47 import static org.sonar.api.issue.Issue.STATUS_IN_REVIEW;
48 import static org.sonar.api.issue.Issue.STATUS_OPEN;
49 import static org.sonar.api.issue.Issue.STATUS_RESOLVED;
50 import static org.sonar.api.issue.Issue.STATUS_REVIEWED;
51 import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW;
52 import static org.sonar.db.rule.RuleTesting.XOO_X1;
54 @RunWith(DataProviderRunner.class)
55 public class IssueWorkflowForSecurityHotspotsTest {
57 private static final String[] ALL_STATUSES_LEADING_TO_CLOSED = new String[] {STATUS_TO_REVIEW, STATUS_IN_REVIEW, STATUS_RESOLVED};
59 private static final String[] SUPPORTED_RESOLUTIONS_FOR_UNCLOSING = new String[] {RESOLUTION_FIXED, RESOLUTION_REMOVED};
61 private IssueFieldsSetter updater = new IssueFieldsSetter();
63 private IssueWorkflow underTest = new IssueWorkflow(new FunctionExecutor(updater), updater);
66 public static Object[][] allStatusesLeadingToClosed() {
67 return Arrays.stream(ALL_STATUSES_LEADING_TO_CLOSED)
68 .map(t -> new Object[] {t})
69 .toArray(Object[][]::new);
72 private static DefaultIssue newClosedIssue(String resolution) {
73 return new DefaultIssue()
75 .setRuleKey(RuleKey.of("js", "S001"))
76 .setResolution(resolution)
77 .setStatus(STATUS_CLOSED)
79 .setCloseDate(new Date(5_999_999L));
82 private static void setStatusPreviousToClosed(DefaultIssue issue, String previousStatus) {
83 addStatusChange(issue, new Date(), previousStatus, STATUS_CLOSED);
86 private static void addStatusChange(DefaultIssue issue, Date date, String previousStatus, String newStatus) {
87 issue.addChange(new FieldDiffs().setCreationDate(date).setDiff("status", previousStatus, newStatus));
91 public void list_out_transitions_in_status_to_review() {
93 DefaultIssue issue = new DefaultIssue().setType(RuleType.SECURITY_HOTSPOT).setStatus(STATUS_TO_REVIEW);
95 List<Transition> transitions = underTest.outTransitions(issue);
97 assertThat(keys(transitions)).containsExactlyInAnyOrder("setinreview", "resolveasreviewed", "openasvulnerability");
101 public void list_out_transitions_in_status_in_review() {
103 DefaultIssue issue = new DefaultIssue().setType(RuleType.SECURITY_HOTSPOT).setStatus(STATUS_IN_REVIEW);
105 List<Transition> transitions = underTest.outTransitions(issue);
107 assertThat(keys(transitions)).containsExactlyInAnyOrder("resolveasreviewed", "openasvulnerability", "resetastoreview");
111 public void list_out_transitions_in_status_reviewed() {
113 DefaultIssue issue = new DefaultIssue().setType(RuleType.SECURITY_HOTSPOT).setStatus(STATUS_REVIEWED);
115 List<Transition> transitions = underTest.outTransitions(issue);
117 assertThat(keys(transitions)).containsExactlyInAnyOrder("openasvulnerability", "resetastoreview");
121 public void list_out_vulnerability_transitions_in_status_open() {
123 DefaultIssue issue = new DefaultIssue().setType(RuleType.VULNERABILITY).setResolution(RESOLUTION_FIXED).setStatus(STATUS_OPEN).setIsFromHotspot(true);
125 List<Transition> transitions = underTest.outTransitions(issue);
127 assertThat(keys(transitions)).containsExactlyInAnyOrder("resolveasreviewed", "resetastoreview");
131 public void set_as_in_review() {
133 DefaultIssue issue = new DefaultIssue()
134 .setType(RuleType.SECURITY_HOTSPOT)
135 .setIsFromHotspot(true)
136 .setStatus(STATUS_TO_REVIEW);
138 boolean result = underTest.doManualTransition(issue, DefaultTransitions.SET_AS_IN_REVIEW, IssueChangeContext.createUser(new Date(), "USER1"));
140 assertThat(result).isTrue();
141 assertThat(issue.getStatus()).isEqualTo(STATUS_IN_REVIEW);
142 assertThat(issue.resolution()).isNull();
146 public void resolve_as_reviewed_from_to_review() {
148 DefaultIssue issue = new DefaultIssue()
149 .setType(RuleType.SECURITY_HOTSPOT)
150 .setIsFromHotspot(true)
151 .setStatus(STATUS_TO_REVIEW);
153 boolean result = underTest.doManualTransition(issue, DefaultTransitions.RESOLVE_AS_REVIEWED, IssueChangeContext.createUser(new Date(), "USER1"));
155 assertThat(result).isTrue();
156 assertThat(issue.getStatus()).isEqualTo(STATUS_REVIEWED);
157 assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
161 public void resolve_as_reviewed_from_in_review() {
163 DefaultIssue issue = new DefaultIssue()
164 .setType(RuleType.SECURITY_HOTSPOT)
165 .setIsFromHotspot(true)
166 .setStatus(STATUS_IN_REVIEW)
167 .setResolution(null);
169 boolean result = underTest.doManualTransition(issue, DefaultTransitions.RESOLVE_AS_REVIEWED, IssueChangeContext.createUser(new Date(), "USER1"));
171 assertThat(result).isTrue();
172 assertThat(issue.getStatus()).isEqualTo(STATUS_REVIEWED);
173 assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
177 public void open_as_vulnerability_from_in_review() {
179 DefaultIssue issue = new DefaultIssue()
180 .setType(RuleType.SECURITY_HOTSPOT)
181 .setIsFromHotspot(true)
182 .setStatus(STATUS_IN_REVIEW)
183 .setResolution(null);
185 boolean result = underTest.doManualTransition(issue, DefaultTransitions.OPEN_AS_VULNERABILITY, IssueChangeContext.createUser(new Date(), "USER1"));
187 assertThat(result).isTrue();
188 assertThat(issue.type()).isEqualTo(RuleType.VULNERABILITY);
189 assertThat(issue.getStatus()).isEqualTo(Issue.STATUS_OPEN);
190 assertThat(issue.resolution()).isNull();
194 public void open_as_vulnerability_from_to_review() {
196 DefaultIssue issue = new DefaultIssue()
197 .setType(RuleType.SECURITY_HOTSPOT)
198 .setIsFromHotspot(true)
199 .setStatus(STATUS_TO_REVIEW)
200 .setResolution(null);
202 boolean result = underTest.doManualTransition(issue, DefaultTransitions.OPEN_AS_VULNERABILITY, IssueChangeContext.createUser(new Date(), "USER1"));
204 assertThat(result).isTrue();
205 assertThat(issue.type()).isEqualTo(RuleType.VULNERABILITY);
206 assertThat(issue.getStatus()).isEqualTo(Issue.STATUS_OPEN);
207 assertThat(issue.resolution()).isNull();
211 public void open_as_vulnerability_from_reviewed() {
213 DefaultIssue issue = new DefaultIssue()
214 .setType(RuleType.SECURITY_HOTSPOT)
215 .setIsFromHotspot(true)
216 .setResolution(RESOLUTION_FIXED)
217 .setStatus(STATUS_REVIEWED);
219 boolean result = underTest.doManualTransition(issue, DefaultTransitions.OPEN_AS_VULNERABILITY, IssueChangeContext.createUser(new Date(), "USER1"));
221 assertThat(result).isTrue();
222 assertThat(issue.type()).isEqualTo(RuleType.VULNERABILITY);
223 assertThat(issue.getStatus()).isEqualTo(Issue.STATUS_OPEN);
224 assertThat(issue.resolution()).isNull();
228 public void reset_as_to_review_from_reviewed() {
230 DefaultIssue issue = new DefaultIssue()
231 .setType(RuleType.SECURITY_HOTSPOT)
232 .setIsFromHotspot(true)
233 .setStatus(STATUS_REVIEWED)
234 .setResolution(RESOLUTION_FIXED);
236 boolean result = underTest.doManualTransition(issue, DefaultTransitions.RESET_AS_TO_REVIEW, IssueChangeContext.createUser(new Date(), "USER1"));
237 assertThat(result).isTrue();
238 assertThat(issue.type()).isEqualTo(RuleType.SECURITY_HOTSPOT);
239 assertThat(issue.getStatus()).isEqualTo(STATUS_TO_REVIEW);
240 assertThat(issue.resolution()).isNull();
244 public void reset_as_to_review_from_in_review() {
246 DefaultIssue issue = new DefaultIssue()
247 .setType(RuleType.SECURITY_HOTSPOT)
248 .setIsFromHotspot(true)
249 .setStatus(STATUS_IN_REVIEW)
250 .setResolution(null);
252 boolean result = underTest.doManualTransition(issue, DefaultTransitions.RESET_AS_TO_REVIEW, IssueChangeContext.createUser(new Date(), "USER1"));
253 assertThat(result).isTrue();
254 assertThat(issue.type()).isEqualTo(RuleType.SECURITY_HOTSPOT);
255 assertThat(issue.getStatus()).isEqualTo(STATUS_TO_REVIEW);
256 assertThat(issue.resolution()).isNull();
260 public void reset_as_to_review_from_opened_as_vulnerability() {
262 DefaultIssue issue = new DefaultIssue()
263 .setType(RuleType.VULNERABILITY)
264 .setIsFromHotspot(true)
265 .setStatus(STATUS_OPEN)
266 .setResolution(null);
268 boolean result = underTest.doManualTransition(issue, DefaultTransitions.RESET_AS_TO_REVIEW, IssueChangeContext.createUser(new Date(), "USER1"));
269 assertThat(result).isTrue();
270 assertThat(issue.type()).isEqualTo(RuleType.SECURITY_HOTSPOT);
271 assertThat(issue.getStatus()).isEqualTo(STATUS_TO_REVIEW);
272 assertThat(issue.resolution()).isNull();
276 public void automatically_close_resolved_security_hotspots_in_status_to_review() {
278 DefaultIssue issue = new DefaultIssue()
279 .setType(RuleType.SECURITY_HOTSPOT)
281 .setStatus(STATUS_TO_REVIEW)
283 .setBeingClosed(true);
284 Date now = new Date();
286 underTest.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
288 assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
289 assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
290 assertThat(issue.closeDate()).isNotNull();
291 assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
295 public void automatically_close_resolved_security_hotspots_in_status_in_review() {
297 DefaultIssue issue = new DefaultIssue()
298 .setType(RuleType.SECURITY_HOTSPOT)
300 .setStatus(STATUS_IN_REVIEW)
302 .setBeingClosed(true);
303 Date now = new Date();
305 underTest.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
307 assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
308 assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
309 assertThat(issue.closeDate()).isNotNull();
310 assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
314 public void automatically_close_resolved_security_hotspots_in_status_reviewed() {
316 DefaultIssue issue = new DefaultIssue()
317 .setType(RuleType.SECURITY_HOTSPOT)
318 .setResolution(RESOLUTION_FIXED)
319 .setStatus(STATUS_REVIEWED)
321 .setBeingClosed(true);
322 Date now = new Date();
324 underTest.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
326 assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
327 assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
328 assertThat(issue.closeDate()).isNotNull();
329 assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
333 public void automatically_close_hotspots_opened_as_vulnerability() {
335 DefaultIssue issue = new DefaultIssue()
336 .setType(RuleType.VULNERABILITY)
338 .setStatus(STATUS_OPEN)
339 .setIsFromHotspot(true)
341 .setBeingClosed(true);
342 Date now = new Date();
344 underTest.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
346 assertThat(issue.resolution()).isEqualTo(RESOLUTION_FIXED);
347 assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
348 assertThat(issue.closeDate()).isNotNull();
349 assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
353 @UseDataProvider("allStatusesLeadingToClosed")
354 public void do_not_automatically_reopen_closed_issues_of_security_hotspots(String previousStatus) {
355 DefaultIssue[] issues = Arrays.stream(SUPPORTED_RESOLUTIONS_FOR_UNCLOSING)
357 DefaultIssue issue = newClosedIssue(resolution);
358 setStatusPreviousToClosed(issue, previousStatus);
359 issue.setType(RuleType.SECURITY_HOTSPOT);
362 .toArray(DefaultIssue[]::new);
363 Date now = new Date();
366 Arrays.stream(issues).forEach(issue -> {
367 underTest.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
369 assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
370 assertThat(issue.updateDate()).isNull();
375 public void doAutomaticTransition_does_nothing_on_security_hotspots_in_to_review_status() {
376 DefaultIssue issue = new DefaultIssue()
380 .setStatus(STATUS_TO_REVIEW);
383 underTest.doAutomaticTransition(issue, IssueChangeContext.createScan(new Date()));
385 assertThat(issue.status()).isEqualTo(STATUS_TO_REVIEW);
386 assertThat(issue.resolution()).isNull();
390 @UseDataProvider("allStatusesLeadingToClosed")
391 public void do_not_automatically_reopen_closed_issues_of_manual_vulnerability(String previousStatus) {
392 DefaultIssue[] issues = Arrays.stream(SUPPORTED_RESOLUTIONS_FOR_UNCLOSING)
394 DefaultIssue issue = newClosedIssue(resolution);
395 setStatusPreviousToClosed(issue, previousStatus);
396 issue.setIsFromHotspot(true);
399 .toArray(DefaultIssue[]::new);
400 Date now = new Date();
403 Arrays.stream(issues).forEach(issue -> {
404 underTest.doAutomaticTransition(issue, IssueChangeContext.createScan(now));
406 assertThat(issue.status()).isEqualTo(STATUS_CLOSED);
407 assertThat(issue.updateDate()).isNull();
412 public void do_not_allow_to_doManualTransition_when_condition_fails() {
414 DefaultIssue issue = new DefaultIssue()
416 // Detect is only available on hotspot
417 .setType(RuleType.VULNERABILITY)
418 .setIsFromHotspot(false)
419 .setStatus(STATUS_OPEN)
423 assertThat(underTest.doManualTransition(issue, DefaultTransitions.RESET_AS_TO_REVIEW, IssueChangeContext.createScan(new Date()))).isFalse();
426 private Collection<String> keys(List<Transition> transitions) {
427 return transitions.stream().map(Transition::key).collect(MoreCollectors.toList());