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