3 * Copyright (C) 2009-2021 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 java.util.stream.Stream;
31 import javax.annotation.Nullable;
32 import org.apache.commons.lang.time.DateUtils;
33 import org.junit.Test;
34 import org.junit.runner.RunWith;
35 import org.sonar.api.issue.Issue;
36 import org.sonar.api.rule.RuleKey;
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.apache.commons.lang.RandomStringUtils.randomAlphabetic;
44 import static org.assertj.core.api.Assertions.assertThat;
45 import static org.sonar.api.issue.DefaultTransitions.RESET_AS_TO_REVIEW;
46 import static org.sonar.api.issue.DefaultTransitions.RESOLVE_AS_REVIEWED;
47 import static org.sonar.api.issue.DefaultTransitions.RESOLVE_AS_SAFE;
48 import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
49 import static org.sonar.api.issue.Issue.RESOLUTION_REMOVED;
50 import static org.sonar.api.issue.Issue.RESOLUTION_SAFE;
51 import static org.sonar.api.issue.Issue.STATUS_CLOSED;
52 import static org.sonar.api.issue.Issue.STATUS_REVIEWED;
53 import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW;
54 import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
55 import static org.sonar.db.rule.RuleTesting.XOO_X1;
57 @RunWith(DataProviderRunner.class)
58 public class IssueWorkflowForSecurityHotspotsTest {
60 private static final IssueChangeContext SOME_CHANGE_CONTEXT = IssueChangeContext.createUser(new Date(), "USER1");
62 private IssueFieldsSetter updater = new IssueFieldsSetter();
64 private IssueWorkflow underTest = new IssueWorkflow(new FunctionExecutor(updater), updater);
67 @UseDataProvider("anyResolutionIncludingNone")
68 public void to_review_hotspot_with_any_resolution_can_be_resolved_as_safe_or_fixed(@Nullable String resolution) {
70 DefaultIssue hotspot = newHotspot(STATUS_TO_REVIEW, resolution);
72 List<Transition> transitions = underTest.outTransitions(hotspot);
74 assertThat(keys(transitions)).containsExactlyInAnyOrder(RESOLVE_AS_REVIEWED, RESOLVE_AS_SAFE);
78 public static Object[][] anyResolutionIncludingNone() {
80 Issue.RESOLUTIONS.stream(),
81 Issue.SECURITY_HOTSPOT_RESOLUTIONS.stream(),
82 Stream.of(randomAlphabetic(12), null))
84 .map(t -> new Object[] {t})
85 .toArray(Object[][]::new);
89 public void reviewed_as_fixed_hotspot_can_be_resolved_as_safe_or_put_back_to_review() {
91 DefaultIssue hotspot = newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED);
93 List<Transition> transitions = underTest.outTransitions(hotspot);
95 assertThat(keys(transitions)).containsExactlyInAnyOrder(RESOLVE_AS_SAFE, RESET_AS_TO_REVIEW);
99 public void reviewed_as_safe_hotspot_can_be_resolved_as_fixed_or_put_back_to_review() {
101 DefaultIssue hotspot = newHotspot(STATUS_REVIEWED, RESOLUTION_SAFE);
103 List<Transition> transitions = underTest.outTransitions(hotspot);
105 assertThat(keys(transitions)).containsExactlyInAnyOrder(RESOLVE_AS_REVIEWED, RESET_AS_TO_REVIEW);
109 @UseDataProvider("anyResolutionButSafeOrFixed")
110 public void reviewed_with_any_resolution_but_safe_or_fixed_can_not_be_changed(String resolution) {
112 DefaultIssue hotspot = newHotspot(STATUS_REVIEWED, resolution);
114 List<Transition> transitions = underTest.outTransitions(hotspot);
116 assertThat(transitions).isEmpty();
120 public static Object[][] anyResolutionButSafeOrFixed() {
122 Issue.RESOLUTIONS.stream(),
123 Issue.SECURITY_HOTSPOT_RESOLUTIONS.stream(),
124 Stream.of(randomAlphabetic(12)))
126 .filter(t -> !RESOLUTION_FIXED.equals(t))
127 .filter(t -> !RESOLUTION_SAFE.equals(t))
128 .map(t -> new Object[] {t})
129 .toArray(Object[][]::new);
133 public void doManualTransition_to_review_hostpot_is_resolved_as_fixed() {
135 DefaultIssue hotspot = newHotspot(STATUS_TO_REVIEW, null);
137 boolean result = underTest.doManualTransition(hotspot, RESOLVE_AS_REVIEWED, SOME_CHANGE_CONTEXT);
139 assertThat(result).isTrue();
140 assertThat(hotspot.getStatus()).isEqualTo(STATUS_REVIEWED);
141 assertThat(hotspot.resolution()).isEqualTo(RESOLUTION_FIXED);
145 public void doManualTransition_reviewed_as_safe_hostpot_is_resolved_as_fixed() {
147 DefaultIssue hotspot = newHotspot(STATUS_REVIEWED, RESOLUTION_SAFE);
149 boolean result = underTest.doManualTransition(hotspot, RESOLVE_AS_REVIEWED, SOME_CHANGE_CONTEXT);
151 assertThat(result).isTrue();
152 assertThat(hotspot.getStatus()).isEqualTo(STATUS_REVIEWED);
153 assertThat(hotspot.resolution()).isEqualTo(RESOLUTION_FIXED);
157 public void doManualTransition_to_review_hostpot_is_resolved_as_safe() {
159 DefaultIssue hotspot = newHotspot(STATUS_TO_REVIEW, null);
161 boolean result = underTest.doManualTransition(hotspot, RESOLVE_AS_SAFE, SOME_CHANGE_CONTEXT);
163 assertThat(result).isTrue();
164 assertThat(hotspot.getStatus()).isEqualTo(STATUS_REVIEWED);
165 assertThat(hotspot.resolution()).isEqualTo(RESOLUTION_SAFE);
169 public void doManualTransition_reviewed_as_fixed_hostpot_is_resolved_as_safe() {
171 DefaultIssue hotspot = newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED);
173 boolean result = underTest.doManualTransition(hotspot, RESOLVE_AS_SAFE, SOME_CHANGE_CONTEXT);
175 assertThat(result).isTrue();
176 assertThat(hotspot.getStatus()).isEqualTo(STATUS_REVIEWED);
177 assertThat(hotspot.resolution()).isEqualTo(RESOLUTION_SAFE);
181 public void doManualTransition_reviewed_as_fixed_hostpot_is_put_back_to_review() {
183 DefaultIssue hotspot = newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED);
185 boolean result = underTest.doManualTransition(hotspot, RESET_AS_TO_REVIEW, SOME_CHANGE_CONTEXT);
187 assertThat(result).isTrue();
188 assertThat(hotspot.getStatus()).isEqualTo(STATUS_TO_REVIEW);
189 assertThat(hotspot.resolution()).isNull();
193 public void doManualTransition_reviewed_as_safe_hostpot_is_put_back_to_review() {
195 DefaultIssue hotspot = newHotspot(STATUS_REVIEWED, RESOLUTION_SAFE);
197 boolean result = underTest.doManualTransition(hotspot, RESET_AS_TO_REVIEW, SOME_CHANGE_CONTEXT);
199 assertThat(result).isTrue();
200 assertThat(hotspot.getStatus()).isEqualTo(STATUS_TO_REVIEW);
201 assertThat(hotspot.resolution()).isNull();
205 public void reset_as_to_review_from_reviewed() {
207 DefaultIssue hotspot = newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED);
209 boolean result = underTest.doManualTransition(hotspot, RESET_AS_TO_REVIEW, SOME_CHANGE_CONTEXT);
210 assertThat(result).isTrue();
211 assertThat(hotspot.type()).isEqualTo(SECURITY_HOTSPOT);
212 assertThat(hotspot.getStatus()).isEqualTo(STATUS_TO_REVIEW);
213 assertThat(hotspot.resolution()).isNull();
217 public void automatically_close_resolved_security_hotspots_in_status_to_review() {
219 DefaultIssue hotspot = newHotspot(STATUS_TO_REVIEW, null)
221 .setBeingClosed(true);
222 Date now = new Date();
224 underTest.doAutomaticTransition(hotspot, IssueChangeContext.createScan(now));
226 assertThat(hotspot.resolution()).isEqualTo(RESOLUTION_FIXED);
227 assertThat(hotspot.status()).isEqualTo(STATUS_CLOSED);
228 assertThat(hotspot.closeDate()).isNotNull();
229 assertThat(hotspot.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
233 @UseDataProvider("safeOrFixedResolutions")
234 public void automatically_close_hotspot_resolved_as_fixed_or_safe(String resolution) {
236 DefaultIssue hotspot = newHotspot(STATUS_REVIEWED, resolution)
238 .setBeingClosed(true);
239 Date now = new Date();
241 underTest.doAutomaticTransition(hotspot, IssueChangeContext.createScan(now));
243 assertThat(hotspot.resolution()).isEqualTo(RESOLUTION_FIXED);
244 assertThat(hotspot.status()).isEqualTo(STATUS_CLOSED);
245 assertThat(hotspot.closeDate()).isNotNull();
246 assertThat(hotspot.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
250 public static Object[][] safeOrFixedResolutions() {
251 return new Object[][] {
258 @UseDataProvider("allStatusesLeadingToClosed")
259 public void do_not_automatically_reopen_closed_issues_of_security_hotspots(String previousStatus) {
260 DefaultIssue[] hotspots = Stream.of(RESOLUTION_FIXED, RESOLUTION_REMOVED)
262 DefaultIssue issue = newClosedHotspot(resolution);
263 setStatusPreviousToClosed(issue, previousStatus);
266 .toArray(DefaultIssue[]::new);
267 Date now = new Date();
270 Arrays.stream(hotspots).forEach(hotspot -> {
271 underTest.doAutomaticTransition(hotspot, IssueChangeContext.createScan(now));
273 assertThat(hotspot.status()).isEqualTo(STATUS_CLOSED);
274 assertThat(hotspot.updateDate()).isNull();
279 public static Object[][] allStatusesLeadingToClosed() {
280 return Stream.of(STATUS_TO_REVIEW, STATUS_REVIEWED)
281 .map(t -> new Object[] {t})
282 .toArray(Object[][]::new);
286 public void doAutomaticTransition_does_nothing_on_security_hotspots_in_to_review_status() {
287 DefaultIssue hotspot = newHotspot(STATUS_TO_REVIEW, null)
292 underTest.doAutomaticTransition(hotspot, IssueChangeContext.createScan(new Date()));
294 assertThat(hotspot.status()).isEqualTo(STATUS_TO_REVIEW);
295 assertThat(hotspot.resolution()).isNull();
298 private Collection<String> keys(List<Transition> transitions) {
299 return transitions.stream().map(Transition::key).collect(MoreCollectors.toList());
302 private static void setStatusPreviousToClosed(DefaultIssue hotspot, String previousStatus) {
303 addStatusChange(hotspot, new Date(), previousStatus, STATUS_CLOSED);
306 private static void addStatusChange(DefaultIssue issue, Date date, String previousStatus, String newStatus) {
307 issue.addChange(new FieldDiffs().setCreationDate(date).setDiff("status", previousStatus, newStatus));
310 private static DefaultIssue newClosedHotspot(String resolution) {
311 return newHotspot(STATUS_CLOSED, resolution)
313 .setRuleKey(RuleKey.of("js", "S001"))
315 .setCloseDate(new Date(5_999_999L));
318 private static DefaultIssue newHotspot(String status, @Nullable String resolution) {
319 return new DefaultIssue()
320 .setType(SECURITY_HOTSPOT)
322 .setResolution(resolution);