You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

IssueWorkflowForSecurityHotspotsTest.java 13KB

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