]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-15352 Reopened closed hotspots should bring back their previous state
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Thu, 16 Sep 2021 19:52:15 +0000 (14:52 -0500)
committersonartech <sonartech@sonarsource.com>
Wed, 22 Sep 2021 20:03:21 +0000 (20:03 +0000)
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ComponentIssuesLoader.java
server/sonar-db-dao/src/main/resources/org/sonar/db/issue/IssueMapper.xml
server/sonar-db-dao/src/test/java/org/sonar/db/issue/IssueMapperTest.java
server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/IsHotspot.java [new file with mode: 0644]
server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/IssueWorkflow.java
server/sonar-server-common/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowForSecurityHotspotsTest.java
server/sonar-server-common/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowTest.java

index a1e359739e6491cb6fd2139948edaff96f8f9cea..bafc40cf4ef9e5dc3d4825602656a27ce4c405f6 100644 (file)
@@ -46,6 +46,7 @@ import org.sonar.db.issue.IssueMapper;
 
 import static com.google.common.base.Preconditions.checkState;
 import static java.util.Collections.emptyList;
+import static java.util.Collections.unmodifiableList;
 import static java.util.stream.Collectors.groupingBy;
 import static java.util.stream.Collectors.toList;
 import static org.sonar.api.issue.Issue.STATUS_CLOSED;
@@ -203,7 +204,7 @@ public class ComponentIssuesLoader {
 
   /**
    * Load closed issues for the specified Component, which have at least one line diff in changelog AND are
-   * neither hotspots nor manual vulnerabilities.
+   * not manual vulnerabilities.
    * <p>
    * Closed issues do not have a line number in DB (it is unset when the issue is closed), this method
    * returns {@link DefaultIssue} objects which line number is populated from the most recent diff logging
@@ -230,7 +231,7 @@ public class ComponentIssuesLoader {
   private static List<DefaultIssue> loadClosedIssues(DbSession dbSession, String componentUuid, long closeDateAfter) {
     ClosedIssuesResultHandler handler = new ClosedIssuesResultHandler();
     dbSession.getMapper(IssueMapper.class).scrollClosedByComponentUuid(componentUuid, closeDateAfter, handler);
-    return ImmutableList.copyOf(handler.issues);
+    return unmodifiableList(handler.issues);
   }
 
   private static class ClosedIssuesResultHandler implements ResultHandler<IssueDto> {
index 933fa200820419369087561a169d51c431c2aa79..f8abc8fdf9f0204bbd38b3d9440de80e26538930 100644 (file)
       and i.status = 'CLOSED'
       and i.issue_close_date is not null
       and i.issue_close_date >= #{closeDateAfter,jdbcType=BIGINT}
-      and i.issue_type &lt;&gt; 4
     order by
       i.kee, ic.issue_change_creation_date desc
   </select>
index ff9bb5f60dceaf36f273d812a1287606889fb93e..fc40fcab45766e52346ac6c1ccf82139beaed97e 100644 (file)
@@ -313,11 +313,11 @@ public class IssueMapperTest {
   }
 
   @Test
-  public void scrollClosedByComponentUuid_does_not_return_closed_issues_of_type_SECURITY_HOTSPOT() {
+  public void scrollClosedByComponentUuid_returns_closed_issues_of_type_SECURITY_HOTSPOT() {
     RuleType ruleType = randomSupportedRuleType();
     ComponentDto component = randomComponent();
     IssueDto securityHotspotIssue = insertNewClosedIssue(component, RuleType.SECURITY_HOTSPOT);
-    insertToClosedDiff(securityHotspotIssue);
+    IssueChangeDto hotspotChangedData = insertToClosedDiff(securityHotspotIssue);
     IssueDto issue = insertNewClosedIssue(component, ruleType);
     IssueChangeDto issueChange = insertToClosedDiff(issue);
 
@@ -326,7 +326,9 @@ public class IssueMapperTest {
 
     assertThat(resultHandler.issues)
       .extracting(IssueDto::getKey, t -> t.getClosedChangeData().get())
-      .containsOnly(tuple(issue.getKey(), issueChange.getChangeData()));
+      .containsOnly(
+        tuple(issue.getKey(), issueChange.getChangeData()),
+        tuple(securityHotspotIssue.getKey(), hotspotChangedData.getChangeData()));
   }
 
   @Test
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/IsHotspot.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/IsHotspot.java
new file mode 100644 (file)
index 0000000..0e0cbaa
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.issue.workflow;
+
+import org.sonar.api.issue.Issue;
+import org.sonar.api.rules.RuleType;
+import org.sonar.core.issue.DefaultIssue;
+
+enum IsHotspot implements Condition {
+  INSTANCE;
+
+  @Override
+  public boolean matches(Issue issue) {
+    return ((DefaultIssue) issue).type() == RuleType.SECURITY_HOTSPOT;
+  }
+}
index 613d0ef9db4050fa0bd7cbdadf01acf8fae0a9f2..bd2671a2c18fde94ae7cc5dae99ae4b1f120662d 100644 (file)
@@ -283,6 +283,26 @@ public class IssueWorkflow implements Startable {
           IsNotHotspot.INSTANCE)
         .functions(RestoreResolutionFunction.INSTANCE, UnsetCloseDate.INSTANCE)
         .automatic()
+        .build())
+
+    // reopen closed hotspots
+      .transition(Transition.builder("automaticunclosetoreview")
+        .from(STATUS_CLOSED).to(STATUS_TO_REVIEW)
+        .conditions(
+          new PreviousStatusWas(STATUS_TO_REVIEW),
+          new HasResolution(RESOLUTION_REMOVED, RESOLUTION_FIXED),
+          IsHotspot.INSTANCE)
+        .functions(RestoreResolutionFunction.INSTANCE, UnsetCloseDate.INSTANCE)
+        .automatic()
+        .build())
+      .transition(Transition.builder("automaticunclosereviewed")
+        .from(STATUS_CLOSED).to(STATUS_REVIEWED)
+        .conditions(
+          new PreviousStatusWas(STATUS_REVIEWED),
+          new HasResolution(RESOLUTION_REMOVED, RESOLUTION_FIXED),
+          IsHotspot.INSTANCE)
+        .functions(RestoreResolutionFunction.INSTANCE, UnsetCloseDate.INSTANCE)
+        .automatic()
         .build());
   }
 
index d71bb665765ed1ec929fdd9bc82fe525c9dd1793..83815d8c87c04d2c06f2f6b2dea7a9a61d42314f 100644 (file)
@@ -22,7 +22,6 @@ package org.sonar.server.issue.workflow;
 import com.tngtech.java.junit.dataprovider.DataProvider;
 import com.tngtech.java.junit.dataprovider.DataProviderRunner;
 import com.tngtech.java.junit.dataprovider.UseDataProvider;
-import java.util.Arrays;
 import java.util.Calendar;
 import java.util.Collection;
 import java.util.Date;
@@ -53,15 +52,14 @@ import static org.sonar.api.issue.Issue.STATUS_REVIEWED;
 import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW;
 import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
 import static org.sonar.db.rule.RuleTesting.XOO_X1;
+import static org.sonar.server.issue.workflow.IssueWorkflowTest.emptyIfNull;
 
 @RunWith(DataProviderRunner.class)
 public class IssueWorkflowForSecurityHotspotsTest {
-
   private static final IssueChangeContext SOME_CHANGE_CONTEXT = IssueChangeContext.createUser(new Date(), "USER1");
 
-  private IssueFieldsSetter updater = new IssueFieldsSetter();
-
-  private IssueWorkflow underTest = new IssueWorkflow(new FunctionExecutor(updater), updater);
+  private final IssueFieldsSetter updater = new IssueFieldsSetter();
+  private final IssueWorkflow underTest = new IssueWorkflow(new FunctionExecutor(updater), updater);
 
   @Test
   @UseDataProvider("anyResolutionIncludingNone")
@@ -255,31 +253,26 @@ public class IssueWorkflowForSecurityHotspotsTest {
   }
 
   @Test
-  @UseDataProvider("allStatusesLeadingToClosed")
-  public void do_not_automatically_reopen_closed_issues_of_security_hotspots(String previousStatus) {
-    DefaultIssue[] hotspots = Stream.of(RESOLUTION_FIXED, RESOLUTION_REMOVED)
-      .map(resolution -> {
-        DefaultIssue issue = newClosedHotspot(resolution);
-        setStatusPreviousToClosed(issue, previousStatus);
-        return issue;
-      })
-      .toArray(DefaultIssue[]::new);
+  public void automatically_reopen_closed_security_hotspots() {
+    DefaultIssue hotspot1 = newClosedHotspot(RESOLUTION_REMOVED);
+    setStatusPreviousToClosed(hotspot1, STATUS_REVIEWED, RESOLUTION_SAFE, RESOLUTION_REMOVED);
+
+    DefaultIssue hotspot2 = newClosedHotspot(RESOLUTION_FIXED);
+    setStatusPreviousToClosed(hotspot2, STATUS_TO_REVIEW, null, RESOLUTION_FIXED);
+
     Date now = new Date();
     underTest.start();
 
-    Arrays.stream(hotspots).forEach(hotspot -> {
-      underTest.doAutomaticTransition(hotspot, IssueChangeContext.createScan(now));
+    underTest.doAutomaticTransition(hotspot1,  IssueChangeContext.createScan(now));
+    underTest.doAutomaticTransition(hotspot2,  IssueChangeContext.createScan(now));
 
-      assertThat(hotspot.status()).isEqualTo(STATUS_CLOSED);
-      assertThat(hotspot.updateDate()).isNull();
-    });
-  }
+    assertThat(hotspot1.updateDate()).isNotNull();
+    assertThat(hotspot1.status()).isEqualTo(STATUS_REVIEWED);
+    assertThat(hotspot1.resolution()).isEqualTo(RESOLUTION_SAFE);
 
-  @DataProvider
-  public static Object[][] allStatusesLeadingToClosed() {
-    return Stream.of(STATUS_TO_REVIEW, STATUS_REVIEWED)
-      .map(t -> new Object[] {t})
-      .toArray(Object[][]::new);
+    assertThat(hotspot2.updateDate()).isNotNull();
+    assertThat(hotspot2.status()).isEqualTo(STATUS_TO_REVIEW);
+    assertThat(hotspot2.resolution()).isNull();
   }
 
   @Test
@@ -299,12 +292,15 @@ public class IssueWorkflowForSecurityHotspotsTest {
     return transitions.stream().map(Transition::key).collect(MoreCollectors.toList());
   }
 
-  private static void setStatusPreviousToClosed(DefaultIssue hotspot, String previousStatus) {
-    addStatusChange(hotspot, new Date(), previousStatus, STATUS_CLOSED);
+  private static void setStatusPreviousToClosed(DefaultIssue hotspot, String previousStatus, @Nullable String previousResolution, @Nullable String newResolution) {
+    addStatusChange(hotspot, new Date(), previousStatus, STATUS_CLOSED, previousResolution, newResolution);
   }
 
-  private static void addStatusChange(DefaultIssue issue, Date date, String previousStatus, String newStatus) {
-    issue.addChange(new FieldDiffs().setCreationDate(date).setDiff("status", previousStatus, newStatus));
+  private static void addStatusChange(DefaultIssue issue, Date date, String previousStatus, String newStatus, @Nullable String previousResolution, @Nullable String newResolution) {
+    issue.addChange(new FieldDiffs()
+      .setCreationDate(date)
+      .setDiff("status", previousStatus, newStatus)
+      .setDiff("resolution", emptyIfNull(previousResolution), emptyIfNull(newResolution)));
   }
 
   private static DefaultIssue newClosedHotspot(String resolution) {
index f8499c73d4978a94e0ea8be4692fde630dbb59f5..a26a03d6ec630934c2c21d66decdcd15bc607f90 100644 (file)
@@ -466,7 +466,7 @@ public class IssueWorkflowTest {
     issue.addChange(fieldDiffs);
   }
 
-  private static String emptyIfNull(@Nullable String newResolution) {
+  static String emptyIfNull(@Nullable String newResolution) {
     return newResolution == null ? "" : newResolution;
   }