aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java2
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/issue/TaintChecker.java1
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/Condition.java1
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/Function.java1
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/IssueWorkflow.java35
-rw-r--r--server/sonar-server-common/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowForSecurityHotspotsTest.java4
-rw-r--r--server/sonar-server-common/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowTest.java170
-rw-r--r--server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/TransitionActionIT.java2
-rw-r--r--server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/TransitionServiceIT.java3
-rw-r--r--server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/BulkChangeActionIT.java3
-rw-r--r--server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/DoTransitionActionIT.java3
-rw-r--r--server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/ListActionIT.java4
-rw-r--r--server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionComponentsIT.java9
-rw-r--r--server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionDependenciesIT.java3
-rw-r--r--server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionIT.java3
-rw-r--r--sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueComment.java4
16 files changed, 172 insertions, 76 deletions
diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
index 2d2a032c41d..0550bead69c 100644
--- a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
+++ b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
@@ -95,6 +95,7 @@ import org.sonar.server.extension.CoreExtensionStopper;
import org.sonar.server.favorite.FavoriteUpdater;
import org.sonar.server.issue.IssueFieldsSetter;
import org.sonar.server.issue.IssueStorage;
+import org.sonar.server.issue.TaintChecker;
import org.sonar.server.issue.index.IssueIndexer;
import org.sonar.server.issue.index.IssueIteratorFactory;
import org.sonar.server.issue.notification.IssuesChangesNotificationModule;
@@ -395,6 +396,7 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
IssueIteratorFactory.class,
IssueFieldsSetter.class, // used in Web Services and CE's DebtCalculator
FunctionExecutor.class, // used by IssueWorkflow
+ TaintChecker.class,
IssueWorkflow.class, // used in Web Services and CE's DebtCalculator
NewIssuesEmailTemplate.class,
MyNewIssuesEmailTemplate.class,
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/TaintChecker.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/TaintChecker.java
index 8dd24d57dab..da5f22e5694 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/TaintChecker.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/TaintChecker.java
@@ -89,7 +89,6 @@ public class TaintChecker {
return repositories;
}
-
public boolean isTaintVulnerability(DefaultIssue issue) {
return taintRepositories.contains(issue.getRuleKey().repository())
&& issue.getLocations() != null
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/Condition.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/Condition.java
index 603c718004a..985d37722e9 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/Condition.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/Condition.java
@@ -21,6 +21,7 @@ package org.sonar.server.issue.workflow;
import org.sonar.api.issue.Issue;
+@FunctionalInterface
public interface Condition {
boolean matches(Issue issue);
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/Function.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/Function.java
index 69a4e17de6c..4d5ad54e4a1 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/Function.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/Function.java
@@ -24,6 +24,7 @@ import org.sonar.api.issue.Issue;
import org.sonar.api.rules.RuleType;
import org.sonar.db.user.UserDto;
+@FunctionalInterface
interface Function {
interface Context {
Issue issue();
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/IssueWorkflow.java b/server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/IssueWorkflow.java
index f9127dadc6e..813594eb30f 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/IssueWorkflow.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/issue/workflow/IssueWorkflow.java
@@ -29,8 +29,10 @@ import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ServerSide;
import org.sonar.api.web.UserRole;
import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.DefaultIssueComment;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.server.issue.IssueFieldsSetter;
+import org.sonar.server.issue.TaintChecker;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
@@ -55,11 +57,13 @@ public class IssueWorkflow implements Startable {
private static final String AUTOMATIC_CLOSE_TRANSITION = "automaticclose";
private final FunctionExecutor functionExecutor;
private final IssueFieldsSetter updater;
+ private final TaintChecker taintChecker;
private StateMachine machine;
- public IssueWorkflow(FunctionExecutor functionExecutor, IssueFieldsSetter updater) {
+ public IssueWorkflow(FunctionExecutor functionExecutor, IssueFieldsSetter updater, TaintChecker taintChecker) {
this.functionExecutor = functionExecutor;
this.updater = updater;
+ this.taintChecker = taintChecker;
}
@Override
@@ -238,7 +242,7 @@ public class IssueWorkflow implements Startable {
.build());
}
- private static void buildAutomaticTransitions(StateMachine.Builder builder) {
+ private void buildAutomaticTransitions(StateMachine.Builder builder) {
// Close the "end of life" issues (disabled/deleted rule, deleted component)
builder
.transition(Transition.builder(AUTOMATIC_CLOSE_TRANSITION)
@@ -277,7 +281,6 @@ public class IssueWorkflow implements Startable {
.functions(SetClosed.INSTANCE, SetCloseDate.INSTANCE)
.automatic()
.build())
-
// Reopen issues that are marked as resolved but that are still alive.
.transition(Transition.builder("automaticreopen")
.from(STATUS_RESOLVED).to(STATUS_REOPENED)
@@ -285,7 +288,6 @@ public class IssueWorkflow implements Startable {
.functions(new SetResolution(null), UnsetCloseDate.INSTANCE)
.automatic()
.build())
-
.transition(Transition.builder("automaticuncloseopen")
.from(STATUS_CLOSED).to(STATUS_OPEN)
.conditions(
@@ -322,7 +324,6 @@ public class IssueWorkflow implements Startable {
.functions(RestoreResolutionFunction.INSTANCE, UnsetCloseDate.INSTANCE)
.automatic()
.build())
-
// reopen closed hotspots
.transition(Transition.builder("automaticunclosetoreview")
.from(STATUS_CLOSED).to(STATUS_TO_REVIEW)
@@ -341,7 +342,29 @@ public class IssueWorkflow implements Startable {
IsHotspot.INSTANCE)
.functions(RestoreResolutionFunction.INSTANCE, UnsetCloseDate.INSTANCE)
.automatic()
- .build());
+ .build())
+ .transition(reopenTaintVulnOnFlowChanged());
+ }
+
+ private Transition reopenTaintVulnOnFlowChanged() {
+ return Transition.builder("reopentaintvulnerability")
+ .from(STATUS_RESOLVED)
+ .to(STATUS_REOPENED)
+ .conditions(
+ issue -> taintChecker.isTaintVulnerability((DefaultIssue) issue),
+ issue -> ((DefaultIssue) issue).locationsChanged())
+ .functions(
+ Function.Context::unsetCloseDate,
+ context -> context.setResolution(null),
+ IssueWorkflow::commentOnTaintVulnReopened)
+ .automatic()
+ .build();
+ }
+
+ private static void commentOnTaintVulnReopened(Function.Context context) {
+ DefaultIssue issue = (DefaultIssue) context.issue();
+ DefaultIssueComment defaultIssueComment = DefaultIssueComment.create(issue.key(), "Automatically reopened because the vulnerability flow changed.");
+ issue.addComment(defaultIssueComment);
}
@Override
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowForSecurityHotspotsTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowForSecurityHotspotsTest.java
index 52b3abd9e10..d01e7a4a8ad 100644
--- a/server/sonar-server-common/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowForSecurityHotspotsTest.java
+++ b/server/sonar-server-common/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowForSecurityHotspotsTest.java
@@ -37,9 +37,11 @@ import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.FieldDiffs;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.server.issue.IssueFieldsSetter;
+import org.sonar.server.issue.TaintChecker;
import static org.apache.commons.lang3.RandomStringUtils.secure;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
import static org.sonar.api.issue.DefaultTransitions.RESET_AS_TO_REVIEW;
import static org.sonar.api.issue.DefaultTransitions.RESOLVE_AS_ACKNOWLEDGED;
import static org.sonar.api.issue.DefaultTransitions.RESOLVE_AS_REVIEWED;
@@ -63,7 +65,7 @@ public class IssueWorkflowForSecurityHotspotsTest {
private static final List<String> RESOLUTION_TYPES = List.of(RESOLUTION_FIXED, RESOLUTION_SAFE, RESOLUTION_ACKNOWLEDGED);
private final IssueFieldsSetter updater = new IssueFieldsSetter();
- private final IssueWorkflow underTest = new IssueWorkflow(new FunctionExecutor(updater), updater);
+ private final IssueWorkflow underTest = new IssueWorkflow(new FunctionExecutor(updater), updater, mock(TaintChecker.class));
@Test
@UseDataProvider("anyResolutionIncludingNone")
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowTest.java
index 52bbc77b442..01a1eea0822 100644
--- a/server/sonar-server-common/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowTest.java
+++ b/server/sonar-server-common/src/test/java/org/sonar/server/issue/workflow/IssueWorkflowTest.java
@@ -20,9 +20,6 @@
package org.sonar.server.issue.workflow;
import com.google.common.collect.Collections2;
-import com.tngtech.java.junit.dataprovider.DataProvider;
-import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@@ -30,22 +27,29 @@ import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Random;
+import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.apache.commons.lang3.time.DateUtils;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
import org.sonar.api.issue.DefaultTransitions;
+import org.sonar.api.issue.IssueStatus;
import org.sonar.api.rule.RuleKey;
import org.sonar.core.issue.DefaultIssue;
+import org.sonar.core.issue.DefaultIssueComment;
import org.sonar.core.issue.FieldDiffs;
import org.sonar.core.issue.IssueChangeContext;
import org.sonar.server.issue.IssueFieldsSetter;
+import org.sonar.server.issue.TaintChecker;
import static com.google.common.base.Preconditions.checkArgument;
import static org.apache.commons.lang3.time.DateUtils.addDays;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.junit.Assert.fail;
+import static org.assertj.core.api.Assertions.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import static org.sonar.api.issue.Issue.RESOLUTION_FALSE_POSITIVE;
import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
import static org.sonar.api.issue.Issue.RESOLUTION_REMOVED;
@@ -59,15 +63,25 @@ import static org.sonar.api.issue.Issue.STATUS_REVIEWED;
import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW;
import static org.sonar.core.issue.IssueChangeContext.issueChangeContextByScanBuilder;
-@RunWith(DataProviderRunner.class)
-public class IssueWorkflowTest {
+class IssueWorkflowTest {
- private IssueFieldsSetter updater = new IssueFieldsSetter();
+ private static final String[] ALL_STATUSES_LEADING_TO_CLOSED = new String[]{STATUS_OPEN, STATUS_REOPENED, STATUS_CONFIRMED, STATUS_RESOLVED};
+ private static final String[] ALL_RESOLUTIONS_BEFORE_CLOSING = new String[]{
+ null,
+ RESOLUTION_FIXED,
+ RESOLUTION_WONT_FIX,
+ RESOLUTION_FALSE_POSITIVE
+ };
+ private static final String[] SUPPORTED_RESOLUTIONS_FOR_UNCLOSING = new String[] {RESOLUTION_FIXED, RESOLUTION_REMOVED};
+
+ private final IssueFieldsSetter updater = new IssueFieldsSetter();
- private IssueWorkflow underTest = new IssueWorkflow(new FunctionExecutor(updater), updater);
+ private final TaintChecker taintChecker = mock(TaintChecker.class);
+
+ private final IssueWorkflow underTest = new IssueWorkflow(new FunctionExecutor(updater), updater, taintChecker);
@Test
- public void list_statuses() {
+ void list_statuses() {
underTest.start();
List<String> expectedStatus = new ArrayList<>();
@@ -80,7 +94,7 @@ public class IssueWorkflowTest {
}
@Test
- public void list_out_transitions_from_status_open() {
+ void list_out_transitions_from_status_open() {
underTest.start();
DefaultIssue issue = new DefaultIssue().setStatus(STATUS_OPEN);
@@ -89,7 +103,7 @@ public class IssueWorkflowTest {
}
@Test
- public void list_out_transitions_from_status_confirmed() {
+ void list_out_transitions_from_status_confirmed() {
underTest.start();
DefaultIssue issue = new DefaultIssue().setStatus(STATUS_CONFIRMED);
@@ -98,7 +112,7 @@ public class IssueWorkflowTest {
}
@Test
- public void list_out_transitions_from_status_resolved() {
+ void list_out_transitions_from_status_resolved() {
underTest.start();
DefaultIssue issue = new DefaultIssue().setStatus(STATUS_RESOLVED);
@@ -107,7 +121,7 @@ public class IssueWorkflowTest {
}
@Test
- public void list_out_transitions_from_status_reopen() {
+ void list_out_transitions_from_status_reopen() {
underTest.start();
DefaultIssue issue = new DefaultIssue().setStatus(STATUS_REOPENED);
@@ -116,7 +130,7 @@ public class IssueWorkflowTest {
}
@Test
- public void list_no_out_transition_from_status_closed() {
+ void list_no_out_transition_from_status_closed() {
underTest.start();
DefaultIssue issue = new DefaultIssue().setStatus(STATUS_CLOSED).setRuleKey(RuleKey.of("java", "R1 "));
@@ -125,7 +139,7 @@ public class IssueWorkflowTest {
}
@Test
- public void fail_if_unknown_status_when_listing_transitions() {
+ void fail_if_unknown_status_when_listing_transitions() {
underTest.start();
DefaultIssue issue = new DefaultIssue().setStatus("xxx");
@@ -138,7 +152,7 @@ public class IssueWorkflowTest {
}
@Test
- public void automatically_close_resolved_issue() {
+ void automatically_close_resolved_issue() {
underTest.start();
DefaultIssue issue = new DefaultIssue()
@@ -156,9 +170,9 @@ public class IssueWorkflowTest {
assertThat(issue.updateDate()).isEqualTo(DateUtils.truncate(now, Calendar.SECOND));
}
- @Test
- @UseDataProvider("allStatusesLeadingToClosed")
- public void automatically_reopen_closed_issue_to_its_previous_status_from_changelog(String previousStatus) {
+ @ParameterizedTest
+ @MethodSource("allStatusesLeadingToClosed")
+ void automatically_reopen_closed_issue_to_its_previous_status_from_changelog(String previousStatus) {
DefaultIssue[] issues = Arrays.stream(SUPPORTED_RESOLUTIONS_FOR_UNCLOSING)
.map(resolution -> {
DefaultIssue issue = newClosedIssue(resolution);
@@ -179,9 +193,9 @@ public class IssueWorkflowTest {
});
}
- @Test
- @UseDataProvider("allStatusesLeadingToClosed")
- public void automatically_reopen_closed_issue_to_most_recent_previous_status_from_changelog(String previousStatus) {
+ @ParameterizedTest
+ @MethodSource("allStatusesLeadingToClosed")
+ void automatically_reopen_closed_issue_to_most_recent_previous_status_from_changelog(String previousStatus) {
DefaultIssue[] issues = Arrays.stream(SUPPORTED_RESOLUTIONS_FOR_UNCLOSING)
.map(resolution -> {
DefaultIssue issue = newClosedIssue(resolution);
@@ -205,9 +219,9 @@ public class IssueWorkflowTest {
});
}
- @Test
- @UseDataProvider("allResolutionsBeforeClosing")
- public void automatically_reopen_closed_issue_to_previous_resolution_from_changelog(String resolutionBeforeClosed) {
+ @ParameterizedTest
+ @MethodSource("allResolutionsBeforeClosing")
+ void automatically_reopen_closed_issue_to_previous_resolution_from_changelog(String resolutionBeforeClosed) {
String randomPreviousStatus = ALL_STATUSES_LEADING_TO_CLOSED[new Random().nextInt(ALL_STATUSES_LEADING_TO_CLOSED.length)];
DefaultIssue[] issues = Arrays.stream(SUPPORTED_RESOLUTIONS_FOR_UNCLOSING)
.map(resolution -> {
@@ -231,7 +245,7 @@ public class IssueWorkflowTest {
}
@Test
- public void automatically_reopen_closed_issue_to_no_resolution_if_no_previous_one_changelog() {
+ void automatically_reopen_closed_issue_to_no_resolution_if_no_previous_one_changelog() {
String randomPreviousStatus = ALL_STATUSES_LEADING_TO_CLOSED[new Random().nextInt(ALL_STATUSES_LEADING_TO_CLOSED.length)];
DefaultIssue[] issues = Arrays.stream(SUPPORTED_RESOLUTIONS_FOR_UNCLOSING)
.map(resolution -> {
@@ -254,9 +268,9 @@ public class IssueWorkflowTest {
});
}
- @Test
- @UseDataProvider("allResolutionsBeforeClosing")
- public void automatically_reopen_closed_issue_to_previous_resolution_of_closing_the_issue_if_most_recent_of_all_resolution_changes(String resolutionBeforeClosed) {
+ @ParameterizedTest
+ @MethodSource("allResolutionsBeforeClosing")
+ void automatically_reopen_closed_issue_to_previous_resolution_of_closing_the_issue_if_most_recent_of_all_resolution_changes(String resolutionBeforeClosed) {
String randomPreviousStatus = ALL_STATUSES_LEADING_TO_CLOSED[new Random().nextInt(ALL_STATUSES_LEADING_TO_CLOSED.length)];
DefaultIssue[] issues = Arrays.stream(SUPPORTED_RESOLUTIONS_FOR_UNCLOSING)
.map(resolution -> {
@@ -282,15 +296,12 @@ public class IssueWorkflowTest {
});
}
- @DataProvider
- public static Object[][] allResolutionsBeforeClosing() {
- return Arrays.stream(ALL_RESOLUTIONS_BEFORE_CLOSING)
- .map(t -> new Object[] {t})
- .toArray(Object[][]::new);
+ static Stream<String> allResolutionsBeforeClosing() {
+ return Stream.of(ALL_RESOLUTIONS_BEFORE_CLOSING);
}
@Test
- public void do_not_automatically_reopen_closed_issue_which_have_no_previous_status_in_changelog() {
+ void do_not_automatically_reopen_closed_issue_which_have_no_previous_status_in_changelog() {
DefaultIssue[] issues = Arrays.stream(SUPPORTED_RESOLUTIONS_FOR_UNCLOSING)
.map(IssueWorkflowTest::newClosedIssue)
.toArray(DefaultIssue[]::new);
@@ -305,25 +316,70 @@ public class IssueWorkflowTest {
});
}
- private static final String[] ALL_STATUSES_LEADING_TO_CLOSED = new String[]{STATUS_OPEN, STATUS_REOPENED, STATUS_CONFIRMED, STATUS_RESOLVED};
- private static final String[] ALL_RESOLUTIONS_BEFORE_CLOSING = new String[]{
- null,
- RESOLUTION_FIXED,
- RESOLUTION_WONT_FIX,
- RESOLUTION_FALSE_POSITIVE
- };
+ @Test
+ void automatically_reopen_taint_vulnerability_when_flow_changed() {
+ DefaultIssue issue = new DefaultIssue()
+ .setKey("issue_key")
+ .setRuleKey(RuleKey.of("xoo", "S001"))
+ .setStatus(STATUS_RESOLVED)
+ .setResolution(RESOLUTION_FALSE_POSITIVE)
+ .setLocationsChanged(true)
+ .setNew(false);
+ when(taintChecker.isTaintVulnerability(issue))
+ .thenReturn(true);
- private static final String[] SUPPORTED_RESOLUTIONS_FOR_UNCLOSING = new String[] {RESOLUTION_FIXED, RESOLUTION_REMOVED};
+ underTest.start();
+ underTest.doAutomaticTransition(issue, issueChangeContextByScanBuilder(new Date()).build());
+
+ assertThat(issue.issueStatus()).isEqualTo(IssueStatus.OPEN);
+ List<DefaultIssueComment> issueComments = issue.defaultIssueComments();
+ assertThat(issueComments).hasSize(1);
+ DefaultIssueComment defaultIssueComment = issueComments.get(0);
+ assertThat(defaultIssueComment.markdownText()).isEqualTo("Automatically reopened because the vulnerability flow changed.");
+ }
+
+ @Test
+ void do_not_automatically_reopen_issue_when_flow_changed_but_not_taint_vulnerability() {
+ DefaultIssue issue = new DefaultIssue()
+ .setKey("issue_key")
+ .setRuleKey(RuleKey.of("xoo", "S001"))
+ .setStatus(STATUS_RESOLVED)
+ .setResolution(RESOLUTION_FALSE_POSITIVE)
+ .setLocationsChanged(true)
+ .setNew(false);
+ when(taintChecker.isTaintVulnerability(issue))
+ .thenReturn(false);
+
+ underTest.start();
+ underTest.doAutomaticTransition(issue, issueChangeContextByScanBuilder(new Date()).build());
+
+ assertThat(issue.issueStatus()).isEqualTo(IssueStatus.FALSE_POSITIVE);
+ }
+
+ @Test
+ void do_not_automatically_reopen_taint_vulnerability_when_flow_did_not_change() {
+ DefaultIssue issue = new DefaultIssue()
+ .setKey("issue_key")
+ .setRuleKey(RuleKey.of("xoo", "S001"))
+ .setStatus(STATUS_RESOLVED)
+ .setResolution(RESOLUTION_FALSE_POSITIVE)
+ .setLocationsChanged(false)
+ .setNew(false);
+ when(taintChecker.isTaintVulnerability(issue))
+ .thenReturn(true);
+
+ underTest.start();
+ underTest.doAutomaticTransition(issue, issueChangeContextByScanBuilder(new Date()).build());
+
+ assertThat(issue.issueStatus()).isEqualTo(IssueStatus.FALSE_POSITIVE);
+ }
- @DataProvider
- public static Object[][] allStatusesLeadingToClosed() {
- return Arrays.stream(ALL_STATUSES_LEADING_TO_CLOSED)
- .map(t -> new Object[]{t})
- .toArray(Object[][]::new);
+ static Stream<String> allStatusesLeadingToClosed() {
+ return Stream.of(ALL_STATUSES_LEADING_TO_CLOSED);
}
@Test
- public void close_open_dead_issue() {
+ void close_open_dead_issue() {
underTest.start();
DefaultIssue issue = new DefaultIssue()
@@ -341,7 +397,7 @@ public class IssueWorkflowTest {
}
@Test
- public void close_reopened_dead_issue() {
+ void close_reopened_dead_issue() {
underTest.start();
DefaultIssue issue = new DefaultIssue()
@@ -359,7 +415,7 @@ public class IssueWorkflowTest {
}
@Test
- public void close_confirmed_dead_issue() {
+ void close_confirmed_dead_issue() {
underTest.start();
DefaultIssue issue = new DefaultIssue()
@@ -377,7 +433,7 @@ public class IssueWorkflowTest {
}
@Test
- public void fail_if_unknown_status_on_automatic_trans() {
+ void fail_if_unknown_status_on_automatic_trans() {
underTest.start();
DefaultIssue issue = new DefaultIssue()
@@ -394,7 +450,7 @@ public class IssueWorkflowTest {
}
@Test
- public void flag_as_false_positive() {
+ void flag_as_false_positive() {
DefaultIssue issue = new DefaultIssue()
.setKey("ABCDE")
.setStatus(STATUS_OPEN)
@@ -412,7 +468,7 @@ public class IssueWorkflowTest {
}
@Test
- public void wont_fix() {
+ void wont_fix() {
DefaultIssue issue = new DefaultIssue()
.setKey("ABCDE")
.setStatus(STATUS_OPEN)
@@ -430,7 +486,7 @@ public class IssueWorkflowTest {
}
@Test
- public void doManualTransition_shouldTransitionToResolutionWontFix_whenAccepted() {
+ void doManualTransition_shouldTransitionToResolutionWontFix_whenAccepted() {
DefaultIssue issue = new DefaultIssue()
.setKey("ABCDE")
.setStatus(STATUS_OPEN)
diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/TransitionActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/TransitionActionIT.java
index 59aa812c38c..5ee187c8f9d 100644
--- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/TransitionActionIT.java
+++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/TransitionActionIT.java
@@ -58,7 +58,7 @@ public class TransitionActionIT {
public UserSessionRule userSession = UserSessionRule.standalone();
private final IssueFieldsSetter updater = new IssueFieldsSetter();
- private final IssueWorkflow workflow = new IssueWorkflow(new FunctionExecutor(updater), updater);
+ private final IssueWorkflow workflow = new IssueWorkflow(new FunctionExecutor(updater), updater, mock(TaintChecker.class));
private final TransitionService transitionService = new TransitionService(userSession, workflow);
private final Action.Context context = mock(Action.Context.class);
private final DefaultIssue issue = newIssue().toDefaultIssue();
diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/TransitionServiceIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/TransitionServiceIT.java
index 93568596174..ea4a458d05b 100644
--- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/TransitionServiceIT.java
+++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/TransitionServiceIT.java
@@ -36,6 +36,7 @@ import org.sonar.server.issue.workflow.Transition;
import org.sonar.server.tester.UserSessionRule;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
import static org.sonar.api.issue.Issue.STATUS_CONFIRMED;
import static org.sonar.api.issue.Issue.STATUS_OPEN;
import static org.sonar.api.rules.RuleType.CODE_SMELL;
@@ -51,7 +52,7 @@ public class TransitionServiceIT {
public UserSessionRule userSession = UserSessionRule.standalone();
private IssueFieldsSetter updater = new IssueFieldsSetter();
- private IssueWorkflow workflow = new IssueWorkflow(new FunctionExecutor(updater), updater);
+ private IssueWorkflow workflow = new IssueWorkflow(new FunctionExecutor(updater), updater, mock(TaintChecker.class));
private TransitionService underTest = new TransitionService(userSession, workflow);
diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/BulkChangeActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/BulkChangeActionIT.java
index cbda06ae6a1..9829b3f3c22 100644
--- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/BulkChangeActionIT.java
+++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/BulkChangeActionIT.java
@@ -47,6 +47,7 @@ import org.sonar.server.es.EsTester;
import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.issue.Action;
import org.sonar.server.issue.IssueFieldsSetter;
+import org.sonar.server.issue.TaintChecker;
import org.sonar.server.issue.TestIssueChangePostProcessor;
import org.sonar.server.issue.TransitionService;
import org.sonar.server.issue.WebIssueStorage;
@@ -120,7 +121,7 @@ public class BulkChangeActionIT {
private IssueChangeEventService issueChangeEventService = mock(IssueChangeEventService.class);
private IssueFieldsSetter issueFieldsSetter = new IssueFieldsSetter();
- private IssueWorkflow issueWorkflow = new IssueWorkflow(new FunctionExecutor(issueFieldsSetter), issueFieldsSetter);
+ private IssueWorkflow issueWorkflow = new IssueWorkflow(new FunctionExecutor(issueFieldsSetter), issueFieldsSetter, mock(TaintChecker.class));
private WebIssueStorage issueStorage = new WebIssueStorage(system2, dbClient,
new DefaultRuleFinder(dbClient, mock(RuleDescriptionFormatter.class)),
new IssueIndexer(es.client(), dbClient, new IssueIteratorFactory(dbClient), null), new SequenceUuidFactory());
diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/DoTransitionActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/DoTransitionActionIT.java
index 4e557120ac2..d2430df5aeb 100644
--- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/DoTransitionActionIT.java
+++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/DoTransitionActionIT.java
@@ -42,6 +42,7 @@ import org.sonar.server.exceptions.NotFoundException;
import org.sonar.server.exceptions.UnauthorizedException;
import org.sonar.server.issue.IssueFieldsSetter;
import org.sonar.server.issue.IssueFinder;
+import org.sonar.server.issue.TaintChecker;
import org.sonar.server.issue.TestIssueChangePostProcessor;
import org.sonar.server.issue.TransitionService;
import org.sonar.server.issue.WebIssueStorage;
@@ -95,7 +96,7 @@ public class DoTransitionActionIT {
private IssueChangeEventService issueChangeEventService = mock(IssueChangeEventService.class);
private IssueFieldsSetter updater = new IssueFieldsSetter();
- private IssueWorkflow workflow = new IssueWorkflow(new FunctionExecutor(updater), updater);
+ private IssueWorkflow workflow = new IssueWorkflow(new FunctionExecutor(updater), updater, mock(TaintChecker.class));
private TransitionService transitionService = new TransitionService(userSession, workflow);
private OperationResponseWriter responseWriter = mock(OperationResponseWriter.class);
private IssueIndexer issueIndexer = new IssueIndexer(es.client(), dbClient, new IssueIteratorFactory(dbClient), null);
diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/ListActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/ListActionIT.java
index 4606f7936cb..9e4416b0751 100644
--- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/ListActionIT.java
+++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/ListActionIT.java
@@ -51,6 +51,7 @@ import org.sonar.server.component.TestComponentFinder;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.issue.IssueFieldsSetter;
import org.sonar.server.issue.NewCodePeriodResolver;
+import org.sonar.server.issue.TaintChecker;
import org.sonar.server.issue.TextRangeResponseFormatter;
import org.sonar.server.issue.TransitionService;
import org.sonar.server.issue.workflow.FunctionExecutor;
@@ -68,6 +69,7 @@ import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.groups.Tuple.tuple;
+import static org.mockito.Mockito.mock;
import static org.sonar.api.issue.Issue.RESOLUTION_FIXED;
import static org.sonar.api.issue.Issue.RESOLUTION_WONT_FIX;
import static org.sonar.api.issue.Issue.STATUS_CLOSED;
@@ -102,7 +104,7 @@ public class ListActionIT {
private final DbClient dbClient = db.getDbClient();
private final IssueFieldsSetter issueFieldsSetter = new IssueFieldsSetter();
- private final IssueWorkflow issueWorkflow = new IssueWorkflow(new FunctionExecutor(issueFieldsSetter), issueFieldsSetter);
+ private final IssueWorkflow issueWorkflow = new IssueWorkflow(new FunctionExecutor(issueFieldsSetter), issueFieldsSetter, mock(TaintChecker.class));
private final SearchResponseLoader searchResponseLoader = new SearchResponseLoader(userSession, dbClient, new TransitionService(userSession, issueWorkflow));
private final Languages languages = new Languages();
private final UserResponseFormatter userFormatter = new UserResponseFormatter(new AvatarResolverImpl());
diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionComponentsIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionComponentsIT.java
index 462d716dc40..4b948491032 100644
--- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionComponentsIT.java
+++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionComponentsIT.java
@@ -24,7 +24,6 @@ import java.util.Arrays;
import java.util.Date;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
-import org.mockito.Mockito;
import org.sonar.api.config.Configuration;
import org.sonar.api.resources.Languages;
import org.sonar.api.rule.RuleKey;
@@ -42,6 +41,7 @@ import org.sonar.db.rule.RuleDto;
import org.sonar.server.common.avatar.AvatarResolverImpl;
import org.sonar.server.es.EsTester;
import org.sonar.server.issue.IssueFieldsSetter;
+import org.sonar.server.issue.TaintChecker;
import org.sonar.server.issue.TextRangeResponseFormatter;
import org.sonar.server.issue.TransitionService;
import org.sonar.server.issue.index.IssueIndex;
@@ -64,6 +64,7 @@ import org.sonarqube.ws.Issues.SearchWsResponse;
import static org.apache.commons.lang3.RandomStringUtils.secure;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
+import static org.mockito.Mockito.mock;
import static org.sonar.api.utils.DateUtils.addDays;
import static org.sonar.api.utils.DateUtils.parseDateTime;
import static org.sonar.api.web.UserRole.USER;
@@ -92,8 +93,8 @@ class SearchActionComponentsIT {
private final DbTester db = DbTester.create();
@RegisterExtension
private final EsTester es = EsTester.create();
-
- private final Configuration config = Mockito.mock(Configuration.class);
+
+ private final Configuration config = mock(Configuration.class);
private final DbClient dbClient = db.getDbClient();
private final IssueIndex issueIndex = new IssueIndex(es.client(), System2.INSTANCE, userSession, new WebAuthorizationTypeSupport(userSession), config);
@@ -101,7 +102,7 @@ class SearchActionComponentsIT {
private final ViewIndexer viewIndexer = new ViewIndexer(dbClient, es.client());
private final IssueQueryFactory issueQueryFactory = new IssueQueryFactory(dbClient, Clock.systemUTC(), userSession);
private final IssueFieldsSetter issueFieldsSetter = new IssueFieldsSetter();
- private final IssueWorkflow issueWorkflow = new IssueWorkflow(new FunctionExecutor(issueFieldsSetter), issueFieldsSetter);
+ private final IssueWorkflow issueWorkflow = new IssueWorkflow(new FunctionExecutor(issueFieldsSetter), issueFieldsSetter, mock(TaintChecker.class));
private final SearchResponseLoader searchResponseLoader = new SearchResponseLoader(userSession, dbClient, new TransitionService(userSession, issueWorkflow));
private final Languages languages = new Languages();
private final UserResponseFormatter userFormatter = new UserResponseFormatter(new AvatarResolverImpl());
diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionDependenciesIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionDependenciesIT.java
index b564e5f5352..bae8a1ba30d 100644
--- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionDependenciesIT.java
+++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionDependenciesIT.java
@@ -40,6 +40,7 @@ import org.sonar.db.rule.RuleDto;
import org.sonar.server.common.avatar.AvatarResolverImpl;
import org.sonar.server.es.EsTester;
import org.sonar.server.issue.IssueFieldsSetter;
+import org.sonar.server.issue.TaintChecker;
import org.sonar.server.issue.TextRangeResponseFormatter;
import org.sonar.server.issue.TransitionService;
import org.sonar.server.issue.index.AsyncIssueIndexing;
@@ -78,7 +79,7 @@ class SearchActionDependenciesIT {
private final IssueIndexer issueIndexer = new IssueIndexer(es.client(), dbClient, new IssueIteratorFactory(dbClient), mock(AsyncIssueIndexing.class));
private final IssueQueryFactory issueQueryFactory = new IssueQueryFactory(dbClient, Clock.systemUTC(), userSession);
private final IssueFieldsSetter issueFieldsSetter = new IssueFieldsSetter();
- private final IssueWorkflow issueWorkflow = new IssueWorkflow(new FunctionExecutor(issueFieldsSetter), issueFieldsSetter);
+ private final IssueWorkflow issueWorkflow = new IssueWorkflow(new FunctionExecutor(issueFieldsSetter), issueFieldsSetter, mock(TaintChecker.class));
private final SearchResponseLoader searchResponseLoader = new SearchResponseLoader(userSession, dbClient, new TransitionService(userSession, issueWorkflow));
private final Languages languages = new Languages();
private final UserResponseFormatter userFormatter = new UserResponseFormatter(new AvatarResolverImpl());
diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionIT.java
index 66fd1979232..8c408f44ce3 100644
--- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionIT.java
+++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/SearchActionIT.java
@@ -77,6 +77,7 @@ import org.sonar.server.common.avatar.AvatarResolverImpl;
import org.sonar.server.es.EsTester;
import org.sonar.server.es.SearchOptions;
import org.sonar.server.issue.IssueFieldsSetter;
+import org.sonar.server.issue.TaintChecker;
import org.sonar.server.issue.TextRangeResponseFormatter;
import org.sonar.server.issue.TransitionService;
import org.sonar.server.issue.index.IssueIndex;
@@ -179,7 +180,7 @@ class SearchActionIT {
private final IssueIndexer issueIndexer = new IssueIndexer(es.client(), dbClient, new IssueIteratorFactory(dbClient), null);
private final IssueQueryFactory issueQueryFactory = new IssueQueryFactory(dbClient, Clock.systemUTC(), userSession);
private final IssueFieldsSetter issueFieldsSetter = new IssueFieldsSetter();
- private final IssueWorkflow issueWorkflow = new IssueWorkflow(new FunctionExecutor(issueFieldsSetter), issueFieldsSetter);
+ private final IssueWorkflow issueWorkflow = new IssueWorkflow(new FunctionExecutor(issueFieldsSetter), issueFieldsSetter, mock(TaintChecker.class));
private final SearchResponseLoader searchResponseLoader = new SearchResponseLoader(userSession, dbClient,
new TransitionService(userSession, issueWorkflow));
private final Languages languages = new Languages();
diff --git a/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueComment.java b/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueComment.java
index 9f548926fcc..ec253c71268 100644
--- a/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueComment.java
+++ b/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueComment.java
@@ -40,6 +40,10 @@ public class DefaultIssueComment implements Serializable {
private String markdownText;
private boolean isNew;
+ public static DefaultIssueComment create(String issueKey, String markdownText) {
+ return create(issueKey, null, markdownText);
+ }
+
public static DefaultIssueComment create(String issueKey, @Nullable String userUuid, String markdownText) {
DefaultIssueComment comment = new DefaultIssueComment();
comment.setIssueKey(issueKey);