]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5111 Complete "api/issues" WS documentation
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Wed, 30 Apr 2014 16:36:57 +0000 (18:36 +0200)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Wed, 30 Apr 2014 16:37:10 +0000 (18:37 +0200)
sonar-plugin-api/src/main/java/org/sonar/api/issue/DefaultTransitions.java
sonar-plugin-api/src/main/java/org/sonar/api/issue/Issue.java
sonar-plugin-api/src/test/java/org/sonar/api/issue/action/ActionsTest.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/issue/ws/IssuesWs.java
sonar-server/src/main/resources/org/sonar/server/issue/ws/example-search.json [new file with mode: 0644]
sonar-server/src/main/resources/org/sonar/server/issue/ws/example-transitions.json [new file with mode: 0644]
sonar-server/src/test/java/org/sonar/server/issue/ws/IssueShowActionTest.java
sonar-server/src/test/java/org/sonar/server/issue/ws/IssuesWsTest.java

index 6c837f206dc6265ebb62c699d8737e8c8ef0ae2c..94109badb362370c59d4cba38dd2bb0bac39951e 100644 (file)
  */
 package org.sonar.api.issue;
 
+import com.google.common.collect.ImmutableList;
+
+import java.util.List;
+
 /**
  * @since 3.6
  */
 public interface DefaultTransitions {
+
   String CONFIRM = "confirm";
   String UNCONFIRM = "unconfirm";
   String REOPEN = "reopen";
   String RESOLVE = "resolve";
   String FALSE_POSITIVE = "falsepositive";
   String CLOSE = "close";
+
+  /**
+   * @since 4.4
+   */
+  List<String> ALL = ImmutableList.of(CONFIRM, UNCONFIRM, REOPEN, RESOLVE, FALSE_POSITIVE, CLOSE);
 }
index b0f3b34cb6f3a19313138332c4e79c2db10a829f..01fb13b1f5a4b6b178a3bb3d582319622c4619e6 100644 (file)
@@ -62,6 +62,13 @@ public interface Issue extends Serializable {
 
   List<String> RESOLUTIONS = ImmutableList.of(RESOLUTION_FALSE_POSITIVE, RESOLUTION_FIXED, RESOLUTION_REMOVED);
 
+  /**
+   * Return all available statuses
+   *
+   * @since 4.4
+   */
+  List<String> STATUSES = ImmutableList.of(STATUS_OPEN, STATUS_CONFIRMED, STATUS_REOPENED, STATUS_RESOLVED, STATUS_CLOSED);
+
   /**
    * Unique generated key. It looks like "d2de809c-1512-4ae2-9f34-f5345c9f1a13".
    */
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/issue/action/ActionsTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/issue/action/ActionsTest.java
new file mode 100644 (file)
index 0000000..75663b2
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.api.issue.action;
+
+import org.junit.Test;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class ActionsTest {
+
+  @Test
+  public void add_action() throws Exception {
+    Actions actions = new Actions();
+    actions.add("plan");
+
+    assertThat(actions.list()).hasSize(1);
+  }
+
+}
index e371a0380ffde24ef6eb26dbb9b0c1607fd3ba5a..2caf43db2f2f46209d42bdf19f28e2fd5ab597a1 100644 (file)
@@ -19,6 +19,9 @@
  */
 package org.sonar.server.issue.ws;
 
+import com.google.common.io.Resources;
+import org.sonar.api.issue.DefaultTransitions;
+import org.sonar.api.issue.Issue;
 import org.sonar.api.rule.Severity;
 import org.sonar.api.server.ws.RailsHandler;
 import org.sonar.api.server.ws.WebService;
@@ -36,20 +39,286 @@ public class IssuesWs implements WebService {
     NewController controller = context.createController("api/issues");
     controller.setDescription("Coding rule issues");
     controller.setSince("3.6");
+
     showHandler.define(controller);
+    defineSearchAction(controller);
+    defineAssignAction(controller);
+    defineAddCommentAction(controller);
+    defineDeleteCommentAction(controller);
+    defineEditCommentAction(controller);
+    defineChangeSeverityAction(controller);
+    definePlanAction(controller);
+    defineDoTransitionAction(controller);
+    defineTransitionsAction(controller);
+    defineCreateAction(controller);
+    defineBulkChangeAction(controller);
+
+    controller.done();
+  }
 
-    WebService.NewAction search = controller.createAction("search")
-      .setDescription("Get a list of issues. If the number of issues is greater than 10,000, only the first 10,000 ones are returned by the web service. Requires Browse permission on project(s).")
+  private void defineSearchAction(NewController controller) {
+    WebService.NewAction action = controller.createAction("search")
+      .setDescription("Get a list of issues. If the number of issues is greater than 10,000, only the first 10,000 ones are returned by the web service. " +
+        "Requires Browse permission on project(s).")
       .setSince("3.6")
-      .setHandler(RailsHandler.INSTANCE);
-    search.createParam("issues")
+      .setHandler(RailsHandler.INSTANCE)
+      .setResponseExample(Resources.getResource(this.getClass(), "example-search.json"));
+
+    action.createParam("issues")
       .setDescription("Comma-separated list of issue keys.")
       .setExampleValue("5bccd6e8-f525-43a2-8d76-fcb13dde79ef");
-    search.createParam("severities")
+    action.createParam("severities")
       .setDescription("Comma-separated list of severities.")
-      .setExampleValue("BLOCKER,CRITICAL")
-      .setPossibleValues(Severity.ALL.toArray(new String[Severity.ALL.size()]));
+      .setExampleValue(Severity.BLOCKER + "," + Severity.CRITICAL)
+      .setPossibleValues(Severity.ALL);
+    action.createParam("statuses")
+      .setDescription("Comma-separated list of statuses.")
+      .setExampleValue(Issue.STATUS_OPEN + "," + Issue.STATUS_REOPENED)
+      .setPossibleValues(Issue.STATUSES);
+    action.createParam("resolutions")
+      .setDescription("Comma-separated list of resolutions.")
+      .setExampleValue(Issue.RESOLUTION_FIXED + "," + Issue.RESOLUTION_REMOVED)
+      .setPossibleValues(Issue.RESOLUTIONS);
+    action.createParam("resolved")
+      .setDescription("To match resolved or unresolved issues.")
+      .setExampleValue("true")
+      .setPossibleValues("true", "false");
+    action.createParam("components")
+      .setDescription("To retrieve issues associated to a specific list of components (comma-separated list of component keys). " +
+        "Note that if you set the value to a project key, only issues associated to this project are retrieved. " +
+        "Issues associated to its sub-components (such as files, packages, etc.) are not retrieved. See also componentRoots.")
+      .setExampleValue("org.apache.struts:struts:org.apache.struts.Action");
+    action.createParam("componentRoots")
+      .setDescription("To retrieve issues associated to a specific list of components and their sub-components (comma-separated list of component keys). " +
+        "Views are not supported.")
+      .setExampleValue("org.apache.struts:struts");
+    action.createParam("rules")
+      .setDescription("Comma-separated list of coding rule keys. Format is <repository>:<rule>.")
+      .setExampleValue("squid:AvoidCycles");
+    action.createParam("actionPlans")
+      .setDescription("Comma-separated list of action plan keys (not names).")
+      .setExampleValue("3f19de90-1521-4482-a737-a311758ff513");
+    action.createParam("planned")
+      .setDescription("To retrieve issues associated to an action plan or not.")
+      .setExampleValue("true")
+      .setPossibleValues("true", "false");
+    action.createParam("reporters")
+      .setDescription("Comma-separated list of reporter logins.")
+      .setExampleValue("admin");
+    action.createParam("assignees")
+      .setDescription("Comma-separated list of assignee logins.")
+      .setExampleValue("admin,usera");
+    action.createParam("assigned")
+      .setDescription("To retrieve assigned or unassigned issues.")
+      .setExampleValue("true")
+      .setPossibleValues("true", "false");
+    action.createParam("createdAfter")
+      .setDescription("To retrieve issues created after the given date (inclusive). Format: date or datetime ISO formats.")
+      .setExampleValue("2013-05-01 (or 2013-05-01T13:00:00+0100)");
+    action.createParam("createdBefore")
+      .setDescription("To retrieve issues created before the given date (exclusive). Format: date or datetime ISO formats.")
+      .setExampleValue("2013-05-01 (or 2013-05-01T13:00:00+0100)");
+    action.createParam("pageSize")
+      .setDescription("Maximum number of results per page.\n" +
+        "Default value: 100 (except when the 'components' parameter is set, value is set to \"-1\" in this case)\n" +
+        "If set to \"-1\", the max possible value is used.")
+      .setExampleValue("50");
+    action.createParam("pageIndex")
+      .setDescription("Index of the selected page")
+      .setDefaultValue("1")
+      .setExampleValue("2");
+  }
 
-    controller.done();
+  private void defineAssignAction(NewController controller) {
+    WebService.NewAction action = controller.createAction("assign")
+      .setDescription("Assign/Unassign an issue. Requires authentication and Browse permission on project.")
+      .setSince("3.6")
+      .setHandler(RailsHandler.INSTANCE)
+      .setPost(true);
+
+    action.createParam("issue")
+      .setDescription("Key of the issue.")
+      .setRequired(true)
+      .setExampleValue("5bccd6e8-f525-43a2-8d76-fcb13dde79ef");
+    action.createParam("assignee")
+      .setDescription("Login of the assignee.")
+      .setExampleValue("admin");
+  }
+
+  private void defineAddCommentAction(NewController controller) {
+    WebService.NewAction action = controller.createAction("add_comment")
+      .setDescription("Add a comment. Requires authentication and Browse permission on project.")
+      .setSince("3.6")
+      .setHandler(RailsHandler.INSTANCE)
+      .setPost(true);
+
+    action.createParam("issue")
+      .setDescription("Key of the issue.")
+      .setRequired(true)
+      .setExampleValue("5bccd6e8-f525-43a2-8d76-fcb13dde79ef");
+    action.createParam("text")
+      .setDescription("Comment.")
+      .setExampleValue("blabla...");
+  }
+
+  private void defineDeleteCommentAction(NewController controller) {
+    WebService.NewAction action = controller.createAction("delete_comment")
+      .setDescription("Delete a comment. Requires authentication and Browse permission on project.")
+      .setSince("3.6")
+      .setHandler(RailsHandler.INSTANCE)
+      .setPost(true);
+
+    action.createParam("key")
+      .setDescription("Key of the comment.")
+      .setRequired(true)
+      .setExampleValue("392160d3-a4f2-4c52-a565-e4542cfa2096");
+  }
+
+  private void defineEditCommentAction(NewController controller) {
+    WebService.NewAction action = controller.createAction("edit_comment")
+      .setDescription("Edit a comment. Requires authentication and User role on project.")
+      .setSince("3.6")
+      .setHandler(RailsHandler.INSTANCE)
+      .setPost(true);
+
+    action.createParam("key")
+      .setDescription("Key of the comment.")
+      .setRequired(true)
+      .setExampleValue("392160d3-a4f2-4c52-a565-e4542cfa2096");
+    action.createParam("text")
+      .setDescription("New comment.")
+      .setExampleValue("blabla2...");
+  }
+
+  private void defineChangeSeverityAction(NewController controller) {
+    WebService.NewAction action = controller.createAction("set_severity")
+      .setDescription("Change severity. Requires authentication and Browse permission on project.")
+      .setSince("3.6")
+      .setHandler(RailsHandler.INSTANCE)
+      .setPost(true);
+
+    action.createParam("issue")
+      .setDescription("Key of the issue.")
+      .setRequired(true)
+      .setExampleValue("5bccd6e8-f525-43a2-8d76-fcb13dde79ef");
+    action.createParam("severity")
+      .setDescription("New severity.")
+      .setExampleValue(Severity.BLOCKER)
+      .setPossibleValues(Severity.ALL);
+  }
+
+  private void definePlanAction(NewController controller) {
+    WebService.NewAction action = controller.createAction("plan")
+      .setDescription("Plan/Unplan an issue. Requires authentication and Browse permission on project.")
+      .setSince("3.6")
+      .setHandler(RailsHandler.INSTANCE)
+      .setPost(true);
+
+    action.createParam("issue")
+      .setDescription("Key of the issue.")
+      .setRequired(true)
+      .setExampleValue("5bccd6e8-f525-43a2-8d76-fcb13dde79ef");
+    action.createParam("plan")
+      .setDescription("Key of the action plan.")
+      .setExampleValue("3f19de90-1521-4482-a737-a311758ff513");
+  }
+
+  private void defineDoTransitionAction(NewController controller) {
+    WebService.NewAction action = controller.createAction("do_transition")
+      .setDescription("Do workflow transition on an issue. Requires authentication and Browse permission on project.")
+      .setSince("3.6")
+      .setHandler(RailsHandler.INSTANCE)
+      .setPost(true);
+
+    action.createParam("issue")
+      .setDescription("Key of the issue.")
+      .setRequired(true)
+      .setExampleValue("5bccd6e8-f525-43a2-8d76-fcb13dde79ef");
+    action.createParam("transition")
+      .setDescription("Transition.")
+      .setExampleValue("reopen")
+      .setPossibleValues(DefaultTransitions.ALL);
+  }
+
+  private void defineTransitionsAction(NewController controller) {
+    WebService.NewAction action = controller.createAction("transitions")
+      .setDescription("Get Possible Workflow Transitions for an Issue. Requires Browse permission on project.")
+      .setSince("3.6")
+      .setHandler(RailsHandler.INSTANCE)
+      .setResponseExample(Resources.getResource(this.getClass(), "example-transitions.json"));
+
+    action.createParam("issue")
+      .setDescription("Key of the issue.")
+      .setRequired(true)
+      .setExampleValue("5bccd6e8-f525-43a2-8d76-fcb13dde79ef");
   }
+
+  private void defineCreateAction(NewController controller) {
+    WebService.NewAction action = controller.createAction("create")
+      .setDescription("Create a manual issue. Requires authentication and Browse permission on project.")
+      .setSince("3.6")
+      .setHandler(RailsHandler.INSTANCE)
+      .setPost(true);
+
+    action.createParam("component")
+      .setDescription("Key of the component on which to log the issue.")
+      .setRequired(true)
+      .setExampleValue("org.apache.struts:struts:org.apache.struts.Action");
+    action.createParam("rule")
+      .setDescription("Manual rule key.")
+      .setRequired(true)
+      .setExampleValue("manual:performance");
+    action.createParam("severity")
+      .setDescription("Severity of the issue.")
+      .setExampleValue(Severity.BLOCKER + "," + Severity.CRITICAL)
+      .setPossibleValues(Severity.ALL);
+    action.createParam("line")
+      .setDescription("Line on which to log the issue.\n" +
+        "If no line is specified, the issue is attached to the component and not to a specific line.")
+      .setExampleValue("15");
+    action.createParam("message")
+      .setDescription("Description of the issue.")
+      .setExampleValue("blabla...");
+  }
+
+  private void defineBulkChangeAction(NewController controller) {
+    WebService.NewAction action = controller.createAction("bulk_change")
+      .setDescription("Bulk change on issues. Requires authentication and User role on project(s).")
+      .setSince("3.7")
+      .setHandler(RailsHandler.INSTANCE)
+      .setPost(true);
+
+    action.createParam("issues")
+      .setDescription("Comma-separated list of issue keys.")
+      .setRequired(true)
+      .setExampleValue("01fc972e-2a3c-433e-bcae-0bd7f88f5123,01fc972e-2a3c-433e-bcae-0bd7f88f9999");
+    action.createParam("actions")
+      // TODO It's not possible to load actions as actions defined by plugins are not availables
+      .setDescription("Comma-separated list of actions to perform. Possible values: assign | set_severity | plan | do_transition.")
+      .setRequired(true)
+      .setExampleValue("assign,plan");
+    action.createParam("assign.assignee")
+      .setDescription("To assign the list of issues to a specific user (login), or unassign all the issues.")
+      .setExampleValue("sbrandhof");
+    action.createParam("set_severity.severity")
+      .setDescription("To change the severity of the list of issues.")
+      .setExampleValue(Severity.BLOCKER)
+      .setPossibleValues(Severity.ALL);
+    action.createParam("plan.plan")
+      .setDescription("To plan the list of issues to a specific action plan (key), or unlink all the issues from an action plan.")
+      .setExampleValue("3f19de90-1521-4482-a737-a311758ff513");
+    action.createParam("do_transition.transition")
+      .setDescription("Transition.")
+      .setExampleValue("reopen")
+      .setPossibleValues(DefaultTransitions.ALL);
+    action.createParam("comment")
+      .setDescription("To add a comment to a list of issues.")
+      .setExampleValue("blabla...");
+    action.createParam("sendNotifications")
+      .setDescription("Available since version 4.0.")
+      .setDefaultValue("false")
+      .setExampleValue("true")
+      .setPossibleValues("true", "false");
+  }
+
 }
diff --git a/sonar-server/src/main/resources/org/sonar/server/issue/ws/example-search.json b/sonar-server/src/main/resources/org/sonar/server/issue/ws/example-search.json
new file mode 100644 (file)
index 0000000..c216a33
--- /dev/null
@@ -0,0 +1,67 @@
+{
+  "securityExclusions": false,
+  "maxResultsReached": false,
+  "paging": {
+    "pageIndex": 1,
+    "pageSize": 5,
+    "total": 206,
+    "pages": 42
+  },
+  "issues": [
+    {
+      "key": "01fc972e-2a3c-433e-bcae-0bd7f88f5123",
+      "component": "com.github.kevinsawicki:http-request:com.github.kevinsawicki.http.HttpRequest",
+      "project": "com.github.kevinsawicki:http-request",
+      "rule": "checkstyle:com.puppycrawl.tools.checkstyle.checks.coding.MagicNumberCheck",
+      "status": "RESOLVED",
+      "resolution": "FALSE-POSITIVE",
+      "severity": "MINOR",
+      "message": "'3' is a magic number.",
+      "line": 530,
+      "author": "Developer 1",
+      "creationDate": "2013-05-13T17:55:39+0200",
+      "updateDate": "2013-05-13T17:55:39+0200",
+      "comments": [
+        {
+          "key": "7d7c56f5-7b5a-41b9-87f8-36fa70caa5ba",
+          "login": "admin",
+          "htmlText": "foooooo",
+          "createdAt": "2013-05-13T18:08:34+0200"
+        }
+      ]
+    }
+  ],
+  "components": [
+    {
+      "key": "com.github.kevinsawicki:http-request:com.github.kevinsawicki.http.HttpRequest",
+      "qualifier": "CLA",
+      "name": "HttpRequest",
+      "longName": "com.github.kevinsawicki.http.HttpRequest"
+    }
+  ],
+  "projects": [
+    {
+      "key": "com.github.kevinsawicki:http-request",
+      "qualifier": "TRK",
+      "name": "http-request",
+      "longName": "http-request"
+    }
+  ],
+  "rules": [
+    {
+      "key": "checkstyle:com.puppycrawl.tools.checkstyle.checks.coding.MagicNumberCheck",
+      "name": "Magic Number",
+      "desc": "Checks for magic numbers.",
+      "status": "READY"
+    }
+  ],
+  "users": [
+    {
+      "login": "admin",
+      "name": "Administrator",
+      "active": true,
+      "email": "admin@sonarqube.org"
+    }
+  ]
+
+}
diff --git a/sonar-server/src/main/resources/org/sonar/server/issue/ws/example-transitions.json b/sonar-server/src/main/resources/org/sonar/server/issue/ws/example-transitions.json
new file mode 100644 (file)
index 0000000..fcee98b
--- /dev/null
@@ -0,0 +1,3 @@
+{
+  "transitions": ["confirm", "resolve", "falsepositive"]
+}
index 20d023e6ec6ede8d9cba7672b602b21d3db29779..914d7d5ecc7fc577a85c4fce7f9a48e86a119bff 100644 (file)
@@ -134,19 +134,19 @@ public class IssueShowActionTest {
     issues.add(issue);
 
     result.addComponents(Lists.<Component>newArrayList(new ComponentDto()
-      .setId(10L)
-      .setKey("org.sonar.server.issue.IssueClient")
-      .setLongName("SonarQube :: Issue Client")
-      .setQualifier("FIL")
-      .setSubProjectId(1L)
-      .setProjectId(1L)
+        .setId(10L)
+        .setKey("org.sonar.server.issue.IssueClient")
+        .setLongName("SonarQube :: Issue Client")
+        .setQualifier("FIL")
+        .setSubProjectId(1L)
+        .setProjectId(1L)
     ));
 
     result.addComponents(Lists.<Component>newArrayList(new ComponentDto()
-      .setId(1L)
-      .setKey("org.sonar.Sonar")
-      .setLongName("SonarQube")
-      .setProjectId(1L)
+        .setId(1L)
+        .setKey("org.sonar.Sonar")
+        .setLongName("SonarQube")
+        .setProjectId(1L)
     ));
 
     MockUserSession.set();
@@ -396,7 +396,8 @@ public class IssueShowActionTest {
           .setKey("COMMENT-ABCD")
           .setMarkdownText("*My comment*")
           .setUserLogin("john")
-          .setCreatedAt(date1))
+          .setCreatedAt(date1)
+      )
       .addComment(
         new DefaultIssueComment()
           .setKey("COMMENT-ABCE")
@@ -542,19 +543,19 @@ public class IssueShowActionTest {
 
   private void addComponentAndProject() {
     result.addComponents(Lists.<Component>newArrayList(new ComponentDto()
-      .setId(10L)
-      .setKey("org.sonar.server.issue.IssueClient")
-      .setLongName("SonarQube :: Issue Client")
-      .setQualifier("FIL")
-      .setSubProjectId(1L)
-      .setProjectId(1L)
+        .setId(10L)
+        .setKey("org.sonar.server.issue.IssueClient")
+        .setLongName("SonarQube :: Issue Client")
+        .setQualifier("FIL")
+        .setSubProjectId(1L)
+        .setProjectId(1L)
     ));
 
     result.addComponents(Lists.<Component>newArrayList(new ComponentDto()
-      .setId(1L)
-      .setKey("org.sonar.Sonar")
-      .setLongName("SonarQube")
-      .setProjectId(1L)
+        .setId(1L)
+        .setKey("org.sonar.Sonar")
+        .setLongName("SonarQube")
+        .setProjectId(1L)
     ));
   }
 
index fe1401d646a199f4bf529acd154d9de3ba91ece4..2c908aa63fd139e64796fc963f4ddbb43289515c 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.server.issue.ws;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.sonar.api.i18n.I18n;
 import org.sonar.api.issue.IssueFinder;
@@ -36,9 +37,16 @@ import static org.mockito.Mockito.mock;
 
 public class IssuesWsTest {
 
-  IssueShowAction showAction = new IssueShowAction(mock(IssueFinder.class), mock(IssueService.class), mock(IssueChangelogService.class), mock(ActionService.class),
-    mock(DebtModelService.class), mock(I18n.class), mock(Durations.class));
-  WsTester tester = new WsTester(new IssuesWs(showAction));
+  IssueShowAction showAction;
+
+  WsTester tester;
+
+  @Before
+  public void setUp() throws Exception {
+    showAction = new IssueShowAction(mock(IssueFinder.class), mock(IssueService.class), mock(IssueChangelogService.class), mock(ActionService.class),
+      mock(DebtModelService.class), mock(I18n.class), mock(Durations.class));
+    tester = new WsTester(new IssuesWs(showAction));
+  }
 
   @Test
   public void define_controller() throws Exception {
@@ -46,22 +54,23 @@ public class IssuesWsTest {
     assertThat(controller).isNotNull();
     assertThat(controller.description()).isNotEmpty();
     assertThat(controller.since()).isEqualTo("3.6");
-    assertThat(controller.actions()).hasSize(2);
+    assertThat(controller.actions()).hasSize(12);
   }
 
   @Test
   public void define_show_action() throws Exception {
     WebService.Controller controller = tester.controller("api/issues");
 
-    WebService.Action show = controller.action("show");
-    assertThat(show).isNotNull();
-    assertThat(show.handler()).isNotNull();
-    assertThat(show.since()).isEqualTo("4.2");
-    assertThat(show.isPost()).isFalse();
-    assertThat(show.isInternal()).isTrue();
-    assertThat(show.handler()).isSameAs(showAction);
+    WebService.Action action = controller.action("show");
+    assertThat(action).isNotNull();
+    assertThat(action.handler()).isNotNull();
+    assertThat(action.since()).isEqualTo("4.2");
+    assertThat(action.isPost()).isFalse();
+    assertThat(action.isInternal()).isTrue();
+    assertThat(action.handler()).isSameAs(showAction);
+    assertThat(action.params()).hasSize(1);
 
-    WebService.Param key = show.param("key");
+    WebService.Param key = action.param("key");
     assertThat(key).isNotNull();
     assertThat(key.description()).isNotNull();
     assertThat(key.isRequired()).isFalse();
@@ -78,7 +87,148 @@ public class IssuesWsTest {
     assertThat(show.isPost()).isFalse();
     assertThat(show.isInternal()).isFalse();
     assertThat(show.handler()).isInstanceOf(RailsHandler.class);
-    assertThat(show.params()).hasSize(2);
+    assertThat(show.responseExampleAsString()).isNotEmpty();
+    assertThat(show.params()).hasSize(17);
+  }
+
+  @Test
+  public void define_assign_action() throws Exception {
+    WebService.Controller controller = tester.controller("api/issues");
+
+    WebService.Action action = controller.action("assign");
+    assertThat(action).isNotNull();
+    assertThat(action.handler()).isNotNull();
+    assertThat(action.since()).isEqualTo("3.6");
+    assertThat(action.isPost()).isTrue();
+    assertThat(action.isInternal()).isFalse();
+    assertThat(action.handler()).isInstanceOf(RailsHandler.class);
+    assertThat(action.params()).hasSize(2);
+  }
+
+  @Test
+  public void define_add_comment_action() throws Exception {
+    WebService.Controller controller = tester.controller("api/issues");
+
+    WebService.Action action = controller.action("add_comment");
+    assertThat(action).isNotNull();
+    assertThat(action.handler()).isNotNull();
+    assertThat(action.since()).isEqualTo("3.6");
+    assertThat(action.isPost()).isTrue();
+    assertThat(action.isInternal()).isFalse();
+    assertThat(action.handler()).isInstanceOf(RailsHandler.class);
+    assertThat(action.params()).hasSize(2);
+  }
+
+  @Test
+  public void define_delete_comment_action() throws Exception {
+    WebService.Controller controller = tester.controller("api/issues");
+
+    WebService.Action action = controller.action("delete_comment");
+    assertThat(action).isNotNull();
+    assertThat(action.handler()).isNotNull();
+    assertThat(action.since()).isEqualTo("3.6");
+    assertThat(action.isPost()).isTrue();
+    assertThat(action.isInternal()).isFalse();
+    assertThat(action.handler()).isInstanceOf(RailsHandler.class);
+    assertThat(action.params()).hasSize(1);
+  }
+
+  @Test
+  public void define_edit_comment_action() throws Exception {
+    WebService.Controller controller = tester.controller("api/issues");
+
+    WebService.Action action = controller.action("edit_comment");
+    assertThat(action).isNotNull();
+    assertThat(action.handler()).isNotNull();
+    assertThat(action.since()).isEqualTo("3.6");
+    assertThat(action.isPost()).isTrue();
+    assertThat(action.isInternal()).isFalse();
+    assertThat(action.handler()).isInstanceOf(RailsHandler.class);
+    assertThat(action.params()).hasSize(2);
+  }
+
+  @Test
+  public void define_change_severity_action() throws Exception {
+    WebService.Controller controller = tester.controller("api/issues");
+
+    WebService.Action action = controller.action("set_severity");
+    assertThat(action).isNotNull();
+    assertThat(action.handler()).isNotNull();
+    assertThat(action.since()).isEqualTo("3.6");
+    assertThat(action.isPost()).isTrue();
+    assertThat(action.isInternal()).isFalse();
+    assertThat(action.handler()).isInstanceOf(RailsHandler.class);
+    assertThat(action.params()).hasSize(2);
+  }
+
+  @Test
+  public void define_plan_action() throws Exception {
+    WebService.Controller controller = tester.controller("api/issues");
+
+    WebService.Action action = controller.action("plan");
+    assertThat(action).isNotNull();
+    assertThat(action.handler()).isNotNull();
+    assertThat(action.since()).isEqualTo("3.6");
+    assertThat(action.isPost()).isTrue();
+    assertThat(action.isInternal()).isFalse();
+    assertThat(action.handler()).isInstanceOf(RailsHandler.class);
+    assertThat(action.params()).hasSize(2);
+  }
+
+  @Test
+  public void define_do_transition_action() throws Exception {
+    WebService.Controller controller = tester.controller("api/issues");
+
+    WebService.Action action = controller.action("do_transition");
+    assertThat(action).isNotNull();
+    assertThat(action.handler()).isNotNull();
+    assertThat(action.since()).isEqualTo("3.6");
+    assertThat(action.isPost()).isTrue();
+    assertThat(action.isInternal()).isFalse();
+    assertThat(action.handler()).isInstanceOf(RailsHandler.class);
+    assertThat(action.params()).hasSize(2);
+  }
+
+  @Test
+  public void define_transitions_action() throws Exception {
+    WebService.Controller controller = tester.controller("api/issues");
+
+    WebService.Action action = controller.action("transitions");
+    assertThat(action).isNotNull();
+    assertThat(action.handler()).isNotNull();
+    assertThat(action.since()).isEqualTo("3.6");
+    assertThat(action.isPost()).isFalse();
+    assertThat(action.isInternal()).isFalse();
+    assertThat(action.handler()).isInstanceOf(RailsHandler.class);
+    assertThat(action.responseExampleAsString()).isNotEmpty();
+    assertThat(action.params()).hasSize(1);
+  }
+
+  @Test
+  public void define_create_action() throws Exception {
+    WebService.Controller controller = tester.controller("api/issues");
+
+    WebService.Action action = controller.action("create");
+    assertThat(action).isNotNull();
+    assertThat(action.handler()).isNotNull();
+    assertThat(action.since()).isEqualTo("3.6");
+    assertThat(action.isPost()).isTrue();
+    assertThat(action.isInternal()).isFalse();
+    assertThat(action.params()).hasSize(5);
+  }
+
+  @Test
+  public void define_bulk_change_action() throws Exception {
+    WebService.Controller controller = tester.controller("api/issues");
+
+    WebService.Action action = controller.action("bulk_change");
+    assertThat(action).isNotNull();
+    assertThat(action.handler()).isNotNull();
+    assertThat(action.since()).isEqualTo("3.7");
+    assertThat(action.isPost()).isTrue();
+    assertThat(action.isInternal()).isFalse();
+    assertThat(action.handler()).isInstanceOf(RailsHandler.class);
+    assertThat(action.params()).hasSize(8);
   }
 
 }