]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-19372 validate transitions
authorPierre Guillot <pierre.guillot@sonarsource.com>
Mon, 31 Jul 2023 07:07:28 +0000 (09:07 +0200)
committersonartech <sonartech@sonarsource.com>
Wed, 2 Aug 2023 20:03:03 +0000 (20:03 +0000)
server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/anticipatedtransition/AnticipatedTransitionsActionIT.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/anticipatedtransition/AnticipatedTransitionParser.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/anticipatedtransition/AnticipatedTransitionsAction.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/anticipatedtransition/AnticipatedTransitionParserTest.java
server/sonar-webserver-webapi/src/test/resources/org/sonar/server/issue/ws/anticipatedtransition/request-with-transitions.json

index b7913311c6a2df96ae116f83ad36c13b22d1e593..8dee07a4aa7702b1678f52c4d39e06a025f07551 100644 (file)
@@ -71,18 +71,18 @@ public class AnticipatedTransitionsActionIT {
     assertThat(definition.key()).isEqualTo("anticipated_transitions");
     assertThat(definition.description()).isEqualTo("""
       Receive a list of anticipated transitions that can be applied to not yet discovered issues on a specific project.<br>
-      Requires the following permission: 'Administer' on the specified project.<br><br>
+      Requires the following permission: 'Administer Issues' on the specified project.<br>
+      Only <code>falsepositive</code> and <code>wontfix</code> transitions are supported.<br>
       Upon successful execution, the HTTP status code returned is 202 (Accepted).<br><br>
       Request example:
-      <pre><code>
-      [
+      <pre><code>[
         {
           "ruleKey": "squid:S0001",
           "issueMessage": "issueMessage1",
           "filePath": "filePath1",
           "line": 1,
           "lineHash": "lineHash1",
-          "transition": "transition1",
+          "transition": "falsepositive",
           "comment": "comment1"
         },
         {
@@ -91,10 +91,10 @@ public class AnticipatedTransitionsActionIT {
           "filePath": "filePath2",
           "line": 2,
           "lineHash": "lineHash2",
-          "transition": "transition2",
+          "transition": "wontfix",
           "comment": "comment2"
         }
-      ]""");
+      ]</code></pre>""");
     assertThat(definition.isPost()).isTrue();
     assertThat(definition.isInternal()).isTrue();
     assertThat(definition.params()).extracting(WebService.Param::key, WebService.Param::isRequired, WebService.Param::description, WebService.Param::since).containsExactlyInAnyOrder(
@@ -166,7 +166,7 @@ public class AnticipatedTransitionsActionIT {
           "filePath": "filePath3",
           "line": 3,
           "lineHash": "lineHash3",
-          "transition": "transition3",
+          "transition": "wontfix",
           "comment": "comment3"
         }
       ]""";
index 2687b7b16bcb8067c2eb527a3e9b43e117e02db2..eb02da3726a54a8bd48975e05b5664810bb5b1e9 100644 (file)
@@ -22,19 +22,34 @@ package org.sonar.server.issue.ws.anticipatedtransition;
 import com.google.gson.Gson;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Set;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.core.issue.AnticipatedTransition;
 
 public class AnticipatedTransitionParser {
   private static final Gson GSON = new Gson();
+  private static final String WONTFIX = "wontfix";
+  private static final String FALSEPOSITIVE = "falsepositive";
+  private static final Set<String> ALLOWED_TRANSITIONS = Set.of(WONTFIX, FALSEPOSITIVE);
+  private static final String TRANSITION_NOT_SUPPORTED_ERROR_MESSAGE = "Transition '%s' not supported. Only 'wontfix' and 'falsepositive' are supported.";
 
   public List<AnticipatedTransition> parse(String requestBody, String userUuid, String projectKey) {
+    List<GsonAnticipatedTransition> anticipatedTransitions;
     try {
-      List<GsonAnticipatedTransition> anticipatedTransitions = Arrays.asList(GSON.fromJson(requestBody, GsonAnticipatedTransition[].class));
-      return mapBodyToAnticipatedTransitions(anticipatedTransitions, userUuid, projectKey);
+      anticipatedTransitions = Arrays.asList(GSON.fromJson(requestBody, GsonAnticipatedTransition[].class));
     } catch (Exception e) {
       throw new IllegalStateException("Unable to parse anticipated transitions from request body.", e);
     }
+    validateAnticipatedTransitions(anticipatedTransitions);
+    return mapBodyToAnticipatedTransitions(anticipatedTransitions, userUuid, projectKey);
+  }
+
+  private static void validateAnticipatedTransitions(List<GsonAnticipatedTransition> anticipatedTransitions) {
+    for (GsonAnticipatedTransition anticipatedTransition : anticipatedTransitions) {
+      if (!ALLOWED_TRANSITIONS.contains(anticipatedTransition.transition())) {
+        throw new IllegalArgumentException(String.format(TRANSITION_NOT_SUPPORTED_ERROR_MESSAGE, anticipatedTransition.transition()));
+      }
+    }
   }
 
   private static List<AnticipatedTransition> mapBodyToAnticipatedTransitions(List<GsonAnticipatedTransition> anticipatedTransitions, String userUuid, String projectKey) {
index ca89b13a46c7b68b6f6a6505029b48371940fb0a..287f0abd740ba0b0acac59b56845062f26b5e6b7 100644 (file)
@@ -45,18 +45,18 @@ public class AnticipatedTransitionsAction implements IssuesWsAction {
   public void define(WebService.NewController controller) {
     WebService.NewAction action = controller.createAction(IssuesWsParameters.ACTION_ANTICIPATED_TRANSITIONS).setDescription("""
       Receive a list of anticipated transitions that can be applied to not yet discovered issues on a specific project.<br>
-      Requires the following permission: 'Administer' on the specified project.<br><br>
+      Requires the following permission: 'Administer Issues' on the specified project.<br>
+      Only <code>falsepositive</code> and <code>wontfix</code> transitions are supported.<br>
       Upon successful execution, the HTTP status code returned is 202 (Accepted).<br><br>
       Request example:
-      <pre><code>
-      [
+      <pre><code>[
         {
           "ruleKey": "squid:S0001",
           "issueMessage": "issueMessage1",
           "filePath": "filePath1",
           "line": 1,
           "lineHash": "lineHash1",
-          "transition": "transition1",
+          "transition": "falsepositive",
           "comment": "comment1"
         },
         {
@@ -65,10 +65,10 @@ public class AnticipatedTransitionsAction implements IssuesWsAction {
           "filePath": "filePath2",
           "line": 2,
           "lineHash": "lineHash2",
-          "transition": "transition2",
+          "transition": "wontfix",
           "comment": "comment2"
         }
-      ]""")
+      ]</code></pre>""")
       .setSince("10.2")
       .setHandler(this)
       .setInternal(true)
index 6d648b582fe11580ce470fc4ccc032e75eca9ec0..02b3e5042df6f8836bb05fab66d455cf07e2929f 100644 (file)
@@ -74,6 +74,29 @@ public class AnticipatedTransitionParserTest {
       .hasMessage("Unable to parse anticipated transitions from request body.");
   }
 
+  @Test
+  public void givenRequestBodyWithInvalidTransition_whenParse_thenExceptionIsThrown() throws IOException {
+    // given
+    String requestBodyWithInvalidTransition = """
+      [
+        {
+          "ruleKey": "squid:S0001",
+          "issueMessage": "issueMessage1",
+          "filePath": "filePath1",
+          "line": 1,
+          "lineHash": "lineHash1",
+          "transition": "invalid-transition",
+          "comment": "comment1"
+        },
+      ]
+      """;
+
+    // when
+    Assertions.assertThatThrownBy(() -> underTest.parse(requestBodyWithInvalidTransition, USER_UUID, PROJECT_KEY))
+      .isInstanceOf(IllegalArgumentException.class)
+      .hasMessage("Transition 'invalid-transition' not supported. Only 'wontfix' and 'falsepositive' are supported.");
+  }
+
   // Handwritten Anticipated Transitions that are expected from the request-with-transitions.json file
   private List<AnticipatedTransition> transitionsExpectedFromTestFile() {
     return List.of(
@@ -86,7 +109,7 @@ public class AnticipatedTransitionParserTest {
         "filePath1",
         1,
         "lineHash1",
-        "transition1",
+        "wontfix",
         "comment1"),
       new AnticipatedTransition(
         PROJECT_KEY,
@@ -97,7 +120,7 @@ public class AnticipatedTransitionParserTest {
         "filePath2",
         2,
         "lineHash2",
-        "transition2",
+        "falsepositive",
         "comment2"));
   }
 
@@ -105,4 +128,4 @@ public class AnticipatedTransitionParserTest {
     return Files.readString(Path.of(getClass().getResource(fileName).getPath()));
   }
 
-}
\ No newline at end of file
+}
index c9697230b86a51aeabff11e1d9dab961009cdee8..0a822ac5b77e6a81546f468f9570cc101df04810 100644 (file)
@@ -5,7 +5,7 @@
     "filePath": "filePath1",
     "line": 1,
     "lineHash": "lineHash1",
-    "transition": "transition1",
+    "transition": "wontfix",
     "comment": "comment1"
   },
   {
@@ -14,7 +14,7 @@
     "filePath": "filePath2",
     "line": 2,
     "lineHash": "lineHash2",
-    "transition": "transition2",
+    "transition": "falsepositive",
     "comment": "comment2"
   }
 ]
\ No newline at end of file