]> source.dussan.org Git - sonarqube.git/blob
aac60df573f8bd90c0e4eadc250297add38280ca
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2024 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.server.issue.workflow;
21
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.Calendar;
26 import java.util.Collection;
27 import java.util.Date;
28 import java.util.List;
29 import java.util.stream.Stream;
30 import javax.annotation.Nullable;
31 import org.apache.commons.lang3.time.DateUtils;
32 import org.junit.Test;
33 import org.junit.runner.RunWith;
34 import org.sonar.api.issue.Issue;
35 import org.sonar.api.rule.RuleKey;
36 import org.sonar.core.issue.DefaultIssue;
37 import org.sonar.core.issue.FieldDiffs;
38 import org.sonar.core.issue.IssueChangeContext;
39 import org.sonar.server.issue.IssueFieldsSetter;
40
41 import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic;
42 import static org.assertj.core.api.Assertions.assertThat;
43 import static org.sonar.api.issue.DefaultTransitions.RESET_AS_TO_REVIEW;
44 import static org.sonar.api.issue.DefaultTransitions.RESOLVE_AS_ACKNOWLEDGED;
45 import static org.sonar.api.issue.DefaultTransitions.RESOLVE_AS_REVIEWED;
46 import static org.sonar.api.issue.DefaultTransitions.RESOLVE_AS_SAFE;
47 import static org.sonar.api.issue.Issue.RESOLUTION_ACKNOWLEDGED;
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.core.issue.IssueChangeContext.issueChangeContextByScanBuilder;
56 import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByUserBuilder;
57 import static org.sonar.db.rule.RuleTesting.XOO_X1;
58 import static org.sonar.server.issue.workflow.IssueWorkflowTest.emptyIfNull;
59
60 @RunWith(DataProviderRunner.class)
61 public class IssueWorkflowForSecurityHotspotsTest {
62   private static final IssueChangeContext SOME_CHANGE_CONTEXT = issueChangeContextByUserBuilder(new Date(), "USER1").build();
63   private static final List<String> RESOLUTION_TYPES = List.of(RESOLUTION_FIXED, RESOLUTION_SAFE, RESOLUTION_ACKNOWLEDGED);
64
65   private final IssueFieldsSetter updater = new IssueFieldsSetter();
66   private final IssueWorkflow underTest = new IssueWorkflow(new FunctionExecutor(updater), updater);
67
68   @Test
69   @UseDataProvider("anyResolutionIncludingNone")
70   public void to_review_hotspot_with_any_resolution_can_be_resolved_as_safe_or_fixed(@Nullable String resolution) {
71     underTest.start();
72     DefaultIssue hotspot = newHotspot(STATUS_TO_REVIEW, resolution);
73
74     List<Transition> transitions = underTest.outTransitions(hotspot);
75
76     assertThat(keys(transitions)).containsExactlyInAnyOrder(RESOLVE_AS_REVIEWED, RESOLVE_AS_SAFE, RESOLVE_AS_ACKNOWLEDGED);
77   }
78
79   @DataProvider
80   public static Object[][] anyResolutionIncludingNone() {
81     return Stream.of(
82       Issue.RESOLUTIONS.stream(),
83       Issue.SECURITY_HOTSPOT_RESOLUTIONS.stream(),
84       Stream.of(randomAlphabetic(12), null))
85       .flatMap(t -> t)
86       .map(t -> new Object[] {t})
87       .toArray(Object[][]::new);
88   }
89
90   @Test
91   public void reviewed_as_fixed_hotspot_can_be_resolved_as_safe_or_put_back_to_review() {
92     underTest.start();
93     DefaultIssue hotspot = newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED);
94
95     List<Transition> transitions = underTest.outTransitions(hotspot);
96
97     assertThat(keys(transitions)).containsExactlyInAnyOrder(RESOLVE_AS_SAFE, RESET_AS_TO_REVIEW, RESOLVE_AS_ACKNOWLEDGED);
98   }
99
100   @Test
101   public void reviewed_as_safe_hotspot_can_be_resolved_as_fixed_or_put_back_to_review() {
102     underTest.start();
103     DefaultIssue hotspot = newHotspot(STATUS_REVIEWED, RESOLUTION_SAFE);
104
105     List<Transition> transitions = underTest.outTransitions(hotspot);
106
107     assertThat(keys(transitions)).containsExactlyInAnyOrder(RESOLVE_AS_REVIEWED, RESET_AS_TO_REVIEW, RESOLVE_AS_ACKNOWLEDGED);
108   }
109
110   @Test
111   @UseDataProvider("anyResolutionButSafeOrFixed")
112   public void reviewed_with_any_resolution_but_safe_or_fixed_can_not_be_changed(String resolution) {
113     underTest.start();
114     DefaultIssue hotspot = newHotspot(STATUS_REVIEWED, resolution);
115
116     List<Transition> transitions = underTest.outTransitions(hotspot);
117
118     assertThat(transitions).isEmpty();
119   }
120
121   @DataProvider
122   public static Object[][] anyResolutionButSafeOrFixed() {
123     return Stream.of(
124       Issue.RESOLUTIONS.stream(),
125       Issue.SECURITY_HOTSPOT_RESOLUTIONS.stream(),
126       Stream.of(randomAlphabetic(12)))
127       .flatMap(t -> t)
128       .filter(t -> !RESOLUTION_TYPES.contains(t))
129       .map(t -> new Object[] {t})
130       .toArray(Object[][]::new);
131   }
132
133   @Test
134   public void doManualTransition_to_review_hostpot_is_resolved_as_fixed() {
135     underTest.start();
136     DefaultIssue hotspot = newHotspot(STATUS_TO_REVIEW, null);
137
138     boolean result = underTest.doManualTransition(hotspot, RESOLVE_AS_REVIEWED, SOME_CHANGE_CONTEXT);
139
140     assertThat(result).isTrue();
141     assertThat(hotspot.getStatus()).isEqualTo(STATUS_REVIEWED);
142     assertThat(hotspot.resolution()).isEqualTo(RESOLUTION_FIXED);
143   }
144
145   @Test
146   public void doManualTransition_reviewed_as_safe_hostpot_is_resolved_as_fixed() {
147     underTest.start();
148     DefaultIssue hotspot = newHotspot(STATUS_REVIEWED, RESOLUTION_SAFE);
149
150     boolean result = underTest.doManualTransition(hotspot, RESOLVE_AS_REVIEWED, SOME_CHANGE_CONTEXT);
151
152     assertThat(result).isTrue();
153     assertThat(hotspot.getStatus()).isEqualTo(STATUS_REVIEWED);
154     assertThat(hotspot.resolution()).isEqualTo(RESOLUTION_FIXED);
155   }
156
157   @Test
158   public void doManualTransition_to_review_hostpot_is_resolved_as_safe() {
159     underTest.start();
160     DefaultIssue hotspot = newHotspot(STATUS_TO_REVIEW, null);
161
162     boolean result = underTest.doManualTransition(hotspot, RESOLVE_AS_SAFE, SOME_CHANGE_CONTEXT);
163
164     assertThat(result).isTrue();
165     assertThat(hotspot.getStatus()).isEqualTo(STATUS_REVIEWED);
166     assertThat(hotspot.resolution()).isEqualTo(RESOLUTION_SAFE);
167   }
168
169   @Test
170   public void doManualTransition_reviewed_as_fixed_hostpot_is_resolved_as_safe() {
171     underTest.start();
172     DefaultIssue hotspot = newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED);
173
174     boolean result = underTest.doManualTransition(hotspot, RESOLVE_AS_SAFE, SOME_CHANGE_CONTEXT);
175
176     assertThat(result).isTrue();
177     assertThat(hotspot.getStatus()).isEqualTo(STATUS_REVIEWED);
178     assertThat(hotspot.resolution()).isEqualTo(RESOLUTION_SAFE);
179   }
180
181   @Test
182   public void doManualTransition_reviewed_as_fixed_hostpot_is_put_back_to_review() {
183     underTest.start();
184     DefaultIssue hotspot = newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED);
185
186     boolean result = underTest.doManualTransition(hotspot, RESET_AS_TO_REVIEW, SOME_CHANGE_CONTEXT);
187
188     assertThat(result).isTrue();
189     assertThat(hotspot.getStatus()).isEqualTo(STATUS_TO_REVIEW);
190     assertThat(hotspot.resolution()).isNull();
191   }
192
193   @Test
194   public void doManualTransition_reviewed_as_safe_hostpot_is_put_back_to_review() {
195     underTest.start();
196     DefaultIssue hotspot = newHotspot(STATUS_REVIEWED, RESOLUTION_SAFE);
197
198     boolean result = underTest.doManualTransition(hotspot, RESET_AS_TO_REVIEW, SOME_CHANGE_CONTEXT);
199
200     assertThat(result).isTrue();
201     assertThat(hotspot.getStatus()).isEqualTo(STATUS_TO_REVIEW);
202     assertThat(hotspot.resolution()).isNull();
203   }
204
205   @Test
206   public void reset_as_to_review_from_reviewed() {
207     underTest.start();
208     DefaultIssue hotspot = newHotspot(STATUS_REVIEWED, RESOLUTION_FIXED);
209
210     boolean result = underTest.doManualTransition(hotspot, RESET_AS_TO_REVIEW, SOME_CHANGE_CONTEXT);
211     assertThat(result).isTrue();
212     assertThat(hotspot.type()).isEqualTo(SECURITY_HOTSPOT);
213     assertThat(hotspot.getStatus()).isEqualTo(STATUS_TO_REVIEW);
214     assertThat(hotspot.resolution()).isNull();
215   }
216
217   @Test
218   public void automatically_close_resolved_security_hotspots_in_status_to_review() {
219     underTest.start();
220     DefaultIssue hotspot = newHotspot(STATUS_TO_REVIEW, null)
221       .setNew(false)
222       .setBeingClosed(true);
223     Date now = new Date();
224
225     underTest.doAutomaticTransition(hotspot, issueChangeContextByScanBuilder(now).build());
226
227     assertThat(hotspot.resolution()).isEqualTo(RESOLUTION_FIXED);
228     assertThat(hotspot.status()).isEqualTo(STATUS_CLOSED);
229     assertThat(hotspot.closeDate()).isNotNull();
230     assertThat(hotspot.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
231   }
232
233   @Test
234   @UseDataProvider("safeOrFixedResolutions")
235   public void automatically_close_hotspot_resolved_as_fixed_or_safe(String resolution) {
236     underTest.start();
237     DefaultIssue hotspot = newHotspot(STATUS_REVIEWED, resolution)
238       .setNew(false)
239       .setBeingClosed(true);
240     Date now = new Date();
241
242     underTest.doAutomaticTransition(hotspot, issueChangeContextByScanBuilder(now).build());
243
244     assertThat(hotspot.resolution()).isEqualTo(RESOLUTION_FIXED);
245     assertThat(hotspot.status()).isEqualTo(STATUS_CLOSED);
246     assertThat(hotspot.closeDate()).isNotNull();
247     assertThat(hotspot.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
248   }
249
250   @DataProvider
251   public static Object[][] safeOrFixedResolutions() {
252     return new Object[][] {
253       {RESOLUTION_SAFE},
254       {RESOLUTION_FIXED}
255     };
256   }
257
258   @Test
259   public void automatically_reopen_closed_security_hotspots() {
260     DefaultIssue hotspot1 = newClosedHotspot(RESOLUTION_REMOVED);
261     setStatusPreviousToClosed(hotspot1, STATUS_REVIEWED, RESOLUTION_SAFE, RESOLUTION_REMOVED);
262
263     DefaultIssue hotspot2 = newClosedHotspot(RESOLUTION_FIXED);
264     setStatusPreviousToClosed(hotspot2, STATUS_TO_REVIEW, null, RESOLUTION_FIXED);
265
266     Date now = new Date();
267     underTest.start();
268
269     underTest.doAutomaticTransition(hotspot1,  issueChangeContextByScanBuilder(now).build());
270     underTest.doAutomaticTransition(hotspot2,  issueChangeContextByScanBuilder(now).build());
271
272     assertThat(hotspot1.updateDate()).isNotNull();
273     assertThat(hotspot1.status()).isEqualTo(STATUS_REVIEWED);
274     assertThat(hotspot1.resolution()).isEqualTo(RESOLUTION_SAFE);
275
276     assertThat(hotspot2.updateDate()).isNotNull();
277     assertThat(hotspot2.status()).isEqualTo(STATUS_TO_REVIEW);
278     assertThat(hotspot2.resolution()).isNull();
279   }
280
281   @Test
282   public void doAutomaticTransition_does_nothing_on_security_hotspots_in_to_review_status() {
283     DefaultIssue hotspot = newHotspot(STATUS_TO_REVIEW, null)
284       .setKey("ABCDE")
285       .setRuleKey(XOO_X1);
286
287     underTest.start();
288     underTest.doAutomaticTransition(hotspot, issueChangeContextByScanBuilder(new Date()).build());
289
290     assertThat(hotspot.status()).isEqualTo(STATUS_TO_REVIEW);
291     assertThat(hotspot.resolution()).isNull();
292   }
293
294   private Collection<String> keys(List<Transition> transitions) {
295     return transitions.stream().map(Transition::key).toList();
296   }
297
298   private static void setStatusPreviousToClosed(DefaultIssue hotspot, String previousStatus, @Nullable String previousResolution, @Nullable String newResolution) {
299     addStatusChange(hotspot, new Date(), previousStatus, STATUS_CLOSED, previousResolution, newResolution);
300   }
301
302   private static void addStatusChange(DefaultIssue issue, Date date, String previousStatus, String newStatus, @Nullable String previousResolution, @Nullable String newResolution) {
303     issue.addChange(new FieldDiffs()
304       .setCreationDate(date)
305       .setDiff("status", previousStatus, newStatus)
306       .setDiff("resolution", emptyIfNull(previousResolution), emptyIfNull(newResolution)));
307   }
308
309   private static DefaultIssue newClosedHotspot(String resolution) {
310     return newHotspot(STATUS_CLOSED, resolution)
311       .setKey("ABCDE")
312       .setRuleKey(RuleKey.of("js", "S001"))
313       .setNew(false)
314       .setCloseDate(new Date(5_999_999L));
315   }
316
317   private static DefaultIssue newHotspot(String status, @Nullable String resolution) {
318     return new DefaultIssue()
319       .setType(SECURITY_HOTSPOT)
320       .setStatus(status)
321       .setResolution(resolution);
322   }
323 }