]> source.dussan.org Git - sonarqube.git/commitdiff
Improve error handling of ws-client
authorSimon Brandhof <simon.brandhof@gmail.com>
Wed, 12 Jun 2013 21:28:00 +0000 (23:28 +0200)
committerSimon Brandhof <simon.brandhof@gmail.com>
Thu, 13 Jun 2013 13:45:07 +0000 (15:45 +0200)
57 files changed:
sonar-ws-client/pom.xml
sonar-ws-client/src/main/java/org/sonar/wsclient/SonarClient.java
sonar-ws-client/src/main/java/org/sonar/wsclient/base/HttpException.java [new file with mode: 0644]
sonar-ws-client/src/main/java/org/sonar/wsclient/base/Paging.java [new file with mode: 0644]
sonar-ws-client/src/main/java/org/sonar/wsclient/internal/EncodingUtils.java
sonar-ws-client/src/main/java/org/sonar/wsclient/internal/HttpRequestFactory.java
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/ActionPlan.java
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/ActionPlanQuery.java
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/DefaultActionPlanClient.java [deleted file]
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/DefaultIssueClient.java [deleted file]
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/Issue.java
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/IssueClient.java
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/IssueComment.java
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/IssueJsonParser.java [deleted file]
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/IssueQuery.java
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/Issues.java
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/NewActionPlan.java
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/NewIssue.java
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/Paging.java [deleted file]
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/UpdateActionPlan.java
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/internal/DefaultActionPlan.java [new file with mode: 0644]
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/internal/DefaultActionPlanClient.java [new file with mode: 0644]
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/internal/DefaultIssue.java [new file with mode: 0644]
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/internal/DefaultIssueClient.java [new file with mode: 0644]
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/internal/DefaultIssueComment.java [new file with mode: 0644]
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/internal/DefaultIssues.java [new file with mode: 0644]
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/internal/IssueJsonParser.java [new file with mode: 0644]
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/internal/package-info.java [new file with mode: 0644]
sonar-ws-client/src/main/java/org/sonar/wsclient/user/DefaultUserClient.java
sonar-ws-client/src/test/java/org/sonar/wsclient/SonarClientTest.java
sonar-ws-client/src/test/java/org/sonar/wsclient/base/HttpExceptionTest.java [new file with mode: 0644]
sonar-ws-client/src/test/java/org/sonar/wsclient/internal/HttpRequestFactoryTest.java
sonar-ws-client/src/test/java/org/sonar/wsclient/issue/DefaultActionPlanClientTest.java [deleted file]
sonar-ws-client/src/test/java/org/sonar/wsclient/issue/DefaultIssueClientTest.java [deleted file]
sonar-ws-client/src/test/java/org/sonar/wsclient/issue/IssueJsonParserTest.java [deleted file]
sonar-ws-client/src/test/java/org/sonar/wsclient/issue/IssueQueryTest.java
sonar-ws-client/src/test/java/org/sonar/wsclient/issue/internal/DefaultActionPlanClientTest.java [new file with mode: 0644]
sonar-ws-client/src/test/java/org/sonar/wsclient/issue/internal/DefaultIssueClientTest.java [new file with mode: 0644]
sonar-ws-client/src/test/java/org/sonar/wsclient/issue/internal/IssueJsonParserTest.java [new file with mode: 0644]
sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/DefaultIssueClientTest/add_comment_result.json [deleted file]
sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/empty.json [deleted file]
sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/getTransitions.json [deleted file]
sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-action-plans.json [deleted file]
sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-comments.json [deleted file]
sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-components.json [deleted file]
sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-projects.json [deleted file]
sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-users.json [deleted file]
sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/search.json [deleted file]
sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/DefaultIssueClientTest/add_comment_result.json [new file with mode: 0644]
sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/IssueJsonParserTest/empty.json [new file with mode: 0644]
sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/IssueJsonParserTest/getTransitions.json [new file with mode: 0644]
sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/IssueJsonParserTest/issue-with-action-plans.json [new file with mode: 0644]
sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/IssueJsonParserTest/issue-with-comments.json [new file with mode: 0644]
sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/IssueJsonParserTest/issue-with-components.json [new file with mode: 0644]
sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/IssueJsonParserTest/issue-with-projects.json [new file with mode: 0644]
sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/IssueJsonParserTest/issue-with-users.json [new file with mode: 0644]
sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/IssueJsonParserTest/search.json [new file with mode: 0644]

index 70cadb60435b9ba3d2a2517690469f61399c24a9..eb61c38a516c5f6cbdcb22fa0f856a1e70cc6c89 100644 (file)
@@ -9,7 +9,7 @@
   <artifactId>sonar-ws-client</artifactId>
   <packaging>jar</packaging>
   <name>Sonar :: Web Service Client</name>
-  <description>Java library to request Sonar web services</description>
+  <description>Java Client Library for Sonar Web Services</description>
 
   <properties>
     <httpclient4.version>4.2.2</httpclient4.version>
               org.apache.httpcomponents.httpcore;bundle-version="${httpclient4.version}";optional="true"
             </Require-Bundle>
             <Export-Package>!.,org.sonar.wsclient,
+              org.sonar.wsclient.component,
               org.sonar.wsclient.connectors,
               org.sonar.wsclient.services,
               org.sonar.wsclient.issue,
-              org.sonar.wsclient.rule
+              org.sonar.wsclient.rule,
+              org.sonar.wsclient.user
             </Export-Package>
             <Bundle-RequiredExecutionEnvironment>JavaSE-1.6</Bundle-RequiredExecutionEnvironment>
           </instructions>
index 1ecc19f01aba93a89838ae3c81b6db4357f27bf8..767070141e16611c42388eb20303f2d42c8850a2 100644 (file)
@@ -21,8 +21,8 @@ package org.sonar.wsclient;
 
 import org.sonar.wsclient.internal.HttpRequestFactory;
 import org.sonar.wsclient.issue.ActionPlanClient;
-import org.sonar.wsclient.issue.DefaultActionPlanClient;
-import org.sonar.wsclient.issue.DefaultIssueClient;
+import org.sonar.wsclient.issue.internal.DefaultActionPlanClient;
+import org.sonar.wsclient.issue.internal.DefaultIssueClient;
 import org.sonar.wsclient.issue.IssueClient;
 import org.sonar.wsclient.user.DefaultUserClient;
 import org.sonar.wsclient.user.UserClient;
@@ -30,6 +30,14 @@ import org.sonar.wsclient.user.UserClient;
 import javax.annotation.Nullable;
 
 /**
+ * Entry point of the Java Client for Sonar Web Services. It does not support all web services yet.
+ * <p/>
+ * Example:
+ * <pre>
+ *   SonarClient client = SonarClient.create("http://localhost:9000");
+ *   IssueClient issueClient = client.issueClient();
+ * </pre>
+ *
  * @since 3.6
  */
 public class SonarClient {
@@ -54,24 +62,37 @@ public class SonarClient {
       .setReadTimeoutInMilliseconds(builder.readTimeoutMs);
   }
 
+  /**
+   * New client to interact with web services related to issues
+   */
   public IssueClient issueClient() {
     return new DefaultIssueClient(requestFactory);
   }
 
+  /**
+   * New client to interact with web services related to issue action plans
+   */
   public ActionPlanClient actionPlanClient() {
     return new DefaultActionPlanClient(requestFactory);
   }
 
+  /**
+   * New client to interact with web services related to users
+   */
   public UserClient userClient() {
     return new DefaultUserClient(requestFactory);
   }
 
+  /**
+   * Create a builder of {@link SonarClient}s.
+   */
   public static Builder builder() {
     return new Builder();
   }
 
   /**
-   * Create a client with default configuration. Use {@link #builder()} to define a custom configuration.
+   * Create a client with default configuration. Use {@link #builder()} to define
+   * a custom configuration (credentials, HTTP proxy, HTTP timeouts).
    */
   public static SonarClient create(String serverUrl) {
     return builder().url(serverUrl).build();
@@ -85,22 +106,34 @@ public class SonarClient {
     private Builder() {
     }
 
+    /**
+     * Mandatory HTTP server URL, eg "http://localhost:9000"
+     */
     public Builder url(String url) {
       this.url = url;
       return this;
     }
 
+    /**
+     * Optional login, for example "admin"
+     */
     public Builder login(@Nullable String login) {
       this.login = login;
       return this;
     }
 
+    /**
+     * Optional password related to {@link #login(String)}, for example "admin"
+     */
     public Builder password(@Nullable String password) {
       this.password = password;
       return this;
     }
 
-    public Builder proxy(String proxyHost, int proxyPort) {
+    /**
+     * Host and port of the optional HTTP proxy
+     */
+    public Builder proxy(@Nullable String proxyHost, int proxyPort) {
       this.proxyHost = proxyHost;
       this.proxyPort = proxyPort;
       return this;
@@ -134,6 +167,9 @@ public class SonarClient {
       return this;
     }
 
+    /**
+     * Build a new client
+     */
     public SonarClient build() {
       if (url == null || "".equals(url)) {
         throw new IllegalStateException("Server URL must be set");
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/base/HttpException.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/base/HttpException.java
new file mode 100644 (file)
index 0000000..94d9bfb
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.wsclient.base;
+
+/**
+ * @since 3.6
+ */
+public class HttpException extends RuntimeException {
+
+  private final String url;
+  private final int status;
+
+  public HttpException(String url, int status) {
+    super(String.format("Error %d on %s", status, url));
+    this.url = url;
+    this.status = status;
+  }
+
+  public String url() {
+    return url;
+  }
+
+  public int status() {
+    return status;
+  }
+}
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/base/Paging.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/base/Paging.java
new file mode 100644 (file)
index 0000000..d803bdd
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.wsclient.base;
+
+import org.sonar.wsclient.unmarshallers.JsonUtils;
+
+import java.util.Map;
+
+/**
+ * @since 3.6
+ */
+public class Paging {
+
+  private final Map json;
+
+  /**
+   * For internal use
+   */
+  public Paging(Map json) {
+    this.json = json;
+  }
+
+  public Integer pageSize() {
+    return JsonUtils.getInteger(json, "pageSize");
+  }
+
+  public Integer pageIndex() {
+    return JsonUtils.getInteger(json, "pageIndex");
+  }
+
+  public Integer total() {
+    return JsonUtils.getInteger(json, "total");
+  }
+
+  public Integer pages() {
+    return JsonUtils.getInteger(json, "pages");
+  }
+
+}
index 698500541ec00f6d003627c199ccdf6d59e99ffd..99be0b4af2f9499389a214a16b46a6d929a3f985 100644 (file)
@@ -25,7 +25,7 @@ import java.util.LinkedHashMap;
 import java.util.Map;
 
 /**
- * Not an API, please do not directly use this class.
+ * Not an API. Please do not use this class, except maybe for unit tests.
  */
 public class EncodingUtils {
 
index a601df34efc3cfeeb348874a280ef5c9a0c26c19..e454a5854483a80c4695470e0b5ac74b05e7f759 100644 (file)
 package org.sonar.wsclient.internal;
 
 import com.github.kevinsawicki.http.HttpRequest;
+import org.sonar.wsclient.base.HttpException;
 
 import javax.annotation.Nullable;
-
 import java.util.Map;
 
 /**
- * Not an API, please do not directly use this class.
+ * Not an API. Please do not use this class, except maybe for unit tests.
  */
 public class HttpRequestFactory {
 
@@ -116,17 +116,34 @@ public class HttpRequestFactory {
     return readTimeoutInMilliseconds;
   }
 
-  public HttpRequest get(String wsUrl, Map<String, Object> queryParams) {
+  public String get(String wsUrl, Map<String, Object> queryParams) {
     HttpRequest request = HttpRequest.get(baseUrl + wsUrl, queryParams, true);
-    return prepare(request);
+    return execute(request);
   }
 
-  public HttpRequest post(String wsUrl, Map<String, Object> queryParams) {
+  public String post(String wsUrl, Map<String, Object> queryParams) {
     HttpRequest request = HttpRequest.post(baseUrl + wsUrl, queryParams, true);
-    return prepare(request);
+    return execute(request);
+  }
+
+  private String execute(HttpRequest request) {
+    try {
+      prepare(request);
+      if (request.ok()) {
+        return request.body("UTF-8");
+      }
+      // TODO handle error messages
+      throw new HttpException(request.url().toString(), request.code());
+
+    } catch (HttpException e) {
+      throw e;
+
+    } catch (HttpRequest.HttpRequestException e) {
+      throw new IllegalStateException(e.getCause());
+    }
   }
 
-  private HttpRequest prepare(HttpRequest request) {
+  private void prepare(HttpRequest request) {
     if (proxyHost != null) {
       request.useProxy(proxyHost, proxyPort);
       if (proxyLogin != null) {
@@ -145,6 +162,5 @@ public class HttpRequestFactory {
     if (login != null) {
       request.basic(login, password);
     }
-    return request;
   }
 }
index d7c40630111fdf9e50f5fcc7b82f4cdb67141aef..daf45ba7124bbb9093e52791ab662633d63b1fa8 100644 (file)
  * 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.
+ * Lesser General License for more details.
  *
- * You should have received a copy of the GNU Lesser General Public License
+ * You should have received a copy of the GNU Lesser General 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.wsclient.issue;
 
-import org.sonar.wsclient.unmarshallers.JsonUtils;
-
 import javax.annotation.CheckForNull;
-
 import java.util.Date;
-import java.util.Map;
 
 /**
  * @since 3.6
  */
-public class ActionPlan {
-
-  private final Map json;
-
-  ActionPlan(Map json) {
-    this.json = json;
-  }
+public interface ActionPlan {
 
   /**
    * Unique key
    */
-  public String key() {
-    return JsonUtils.getString(json, "key");
-  }
+  String key();
 
-  public String project() {
-    return JsonUtils.getString(json, "project");
-  }
+  String project();
 
-  public String name() {
-    return JsonUtils.getString(json, "name");
-  }
+  String name();
 
   @CheckForNull
-  public String description() {
-    return JsonUtils.getString(json, "desc");
-  }
+  String description();
 
-  public String status() {
-    return JsonUtils.getString(json, "status");
-  }
+  String status();
 
   /**
    * Login of the user who created the action plan.
    */
-  public String userLogin() {
-    return JsonUtils.getString(json, "userLogin");
-  }
+  String userLogin();
 
   @CheckForNull
-  public Date deadLine() {
-    return JsonUtils.getDateTime(json, "deadLine");
-  }
+  Date deadLine();
 
-  public Date createdAt() {
-    return JsonUtils.getDateTime(json, "createdAt");
-  }
+  Date createdAt();
 
-  public Date updatedAt() {
-    return JsonUtils.getDateTime(json, "updatedAt");
-  }
+  Date updatedAt();
 
   @CheckForNull
-  public Integer totalIssues() {
-    return JsonUtils.getInteger(json, "totalIssues");
-  }
+  Integer totalIssues();
 
   @CheckForNull
-  public Integer unresolvedIssues() {
-    return JsonUtils.getInteger(json, "unresolvedIssues");
-  }
+  Integer unresolvedIssues();
 
 }
index aa7124f5cd0dc8ac1212dd3b3270cae0ebd631a9..3f77468b783f4e788c221f47ab65380150986cd0 100644 (file)
@@ -27,7 +27,7 @@ import java.util.Map;
  */
 public class ActionPlanQuery {
 
-  static final String BASE_URL = "/api/action_plans/search";
+  public static final String BASE_URL = "/api/action_plans/search";
 
   private final Map<String, Object> params = new HashMap<String, Object>();
 
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/DefaultActionPlanClient.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/DefaultActionPlanClient.java
deleted file mode 100644 (file)
index 651800e..0000000
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 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.wsclient.issue;
-
-import com.github.kevinsawicki.http.HttpRequest;
-import org.json.simple.JSONValue;
-import org.sonar.wsclient.internal.EncodingUtils;
-import org.sonar.wsclient.internal.HttpRequestFactory;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Do not instantiate this class, but use {@link org.sonar.wsclient.SonarClient#actionPlanClient()}.
- */
-public class DefaultActionPlanClient implements ActionPlanClient {
-
-  private final HttpRequestFactory requestFactory;
-
-  /**
-   * For internal use. Use {@link org.sonar.wsclient.SonarClient} to get an instance.
-   */
-  public DefaultActionPlanClient(HttpRequestFactory requestFactory) {
-    this.requestFactory = requestFactory;
-  }
-
-  @Override
-  public List<ActionPlan> find(String projectKey) {
-    HttpRequest request = requestFactory.get(ActionPlanQuery.BASE_URL, EncodingUtils.toMap("project", projectKey));
-    if (!request.ok()) {
-      throw new IllegalStateException("Fail to search for action plans. Bad HTTP response status: " + request.code());
-    }
-    List<ActionPlan> result = new ArrayList<ActionPlan>();
-    String json = request.body("UTF-8");
-    Map jsonRoot = (Map) JSONValue.parse(json);
-    List<Map> jsonActionPlans = (List) jsonRoot.get("actionPlans");
-    if (jsonActionPlans != null) {
-      for (Map jsonActionPlan : jsonActionPlans) {
-        result.add(new ActionPlan(jsonActionPlan));
-      }
-    }
-    return result;
-  }
-
-  @Override
-  public ActionPlan create(NewActionPlan newActionPlan) {
-    HttpRequest request = requestFactory.post(NewActionPlan.BASE_URL, newActionPlan.urlParams());
-    if (!request.ok()) {
-      throw new IllegalStateException("Fail to create action plan. Bad HTTP response status: " + request.code());
-    }
-    return createActionPlanResult(request);
-  }
-
-  @Override
-  public ActionPlan update(UpdateActionPlan updateActionPlan) {
-    HttpRequest request = requestFactory.post(UpdateActionPlan.BASE_URL, updateActionPlan.urlParams());
-    if (!request.ok()) {
-      throw new IllegalStateException("Fail to update action plan. Bad HTTP response status: " + request.code());
-    }
-    return createActionPlanResult(request);
-  }
-
-  @Override
-  public void delete(String actionPlanKey) {
-    executeSimpleAction(actionPlanKey, "delete");
-  }
-
-  @Override
-  public ActionPlan open(String actionPlanKey) {
-    HttpRequest request = executeSimpleAction(actionPlanKey, "open");
-    return createActionPlanResult(request);
-  }
-
-  @Override
-  public ActionPlan close(String actionPlanKey) {
-    HttpRequest request = executeSimpleAction(actionPlanKey, "close");
-    return createActionPlanResult(request);
-  }
-
-  private HttpRequest executeSimpleAction(String actionPlanKey, String action) {
-    HttpRequest request = requestFactory.post("/api/action_plans/" + action, EncodingUtils.toMap("key", actionPlanKey));
-    if (!request.ok()) {
-      throw new IllegalStateException("Fail to " + action + " action plan. Bad HTTP response status: " + request.code());
-    }
-    return request;
-  }
-
-  private ActionPlan createActionPlanResult(HttpRequest request){
-    String json = request.body("UTF-8");
-    Map jsonRoot = (Map) JSONValue.parse(json);
-    return new ActionPlan((Map) jsonRoot.get("actionPlan"));
-  }
-
-}
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/DefaultIssueClient.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/DefaultIssueClient.java
deleted file mode 100644 (file)
index 4dd1972..0000000
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 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.wsclient.issue;
-
-import com.github.kevinsawicki.http.HttpRequest;
-import org.json.simple.JSONValue;
-import org.sonar.wsclient.internal.EncodingUtils;
-import org.sonar.wsclient.internal.HttpRequestFactory;
-
-import javax.annotation.Nullable;
-
-import java.util.List;
-import java.util.Map;
-
-/**
- * Do not instantiate this class, but use {@link org.sonar.wsclient.SonarClient#issueClient()}.
- */
-public class DefaultIssueClient implements IssueClient {
-
-  private final HttpRequestFactory requestFactory;
-  private final IssueJsonParser parser;
-
-  /**
-   * For internal use. Use {@link org.sonar.wsclient.SonarClient} to get an instance.
-   */
-  public DefaultIssueClient(HttpRequestFactory requestFactory) {
-    this.requestFactory = requestFactory;
-    this.parser = new IssueJsonParser();
-  }
-
-  public Issues find(IssueQuery query) {
-    HttpRequest request = requestFactory.get(IssueQuery.BASE_URL, query.urlParams());
-    if (!request.ok()) {
-      throw new IllegalStateException("Fail to search for issues. Bad HTTP response status: " + request.code());
-    }
-    String json = request.body("UTF-8");
-    return parser.parseIssues(json);
-  }
-
-  @Override
-  public Issue create(NewIssue newIssue) {
-    HttpRequest request = requestFactory.post(NewIssue.BASE_URL, newIssue.urlParams());
-    if (!request.ok()) {
-      throw new IllegalStateException("Fail to create issue. Bad HTTP response status: " + request.code());
-    }
-    return createIssueResult(request);
-  }
-
-  @Override
-  public Issue setSeverity(String issueKey, String severity) {
-    Map<String, Object> params = EncodingUtils.toMap("issue", issueKey, "severity", severity);
-    HttpRequest request = requestFactory.post("/api/issues/set_severity", params);
-    if (!request.ok()) {
-      throw new IllegalStateException("Fail to set severity. Bad HTTP response status: " + request.code());
-    }
-    return createIssueResult(request);
-  }
-
-  @Override
-  public Issue assign(String issueKey, @Nullable String assignee) {
-    Map<String, Object> params = EncodingUtils.toMap("issue", issueKey, "assignee", assignee);
-    HttpRequest request = requestFactory.post("/api/issues/assign", params);
-    if (!request.ok()) {
-      throw new IllegalStateException("Fail to assign issue to user. Bad HTTP response status: " + request.code());
-    }
-    return createIssueResult(request);
-  }
-
-  @Override
-  public Issue plan(String issueKey, @Nullable String actionPlanKey) {
-    Map<String, Object> params = EncodingUtils.toMap("issue", issueKey, "plan", actionPlanKey);
-    HttpRequest request = requestFactory.post("/api/issues/plan", params);
-    if (!request.ok()) {
-      throw new IllegalStateException("Fail to link action plan. Bad HTTP response status: " + request.code());
-    }
-    return createIssueResult(request);
-  }
-
-  @Override
-  public IssueComment addComment(String issueKey, String markdownText) {
-    Map<String, Object> params = EncodingUtils.toMap("issue", issueKey, "text", markdownText);
-    HttpRequest request = requestFactory.post("/api/issues/add_comment", params);
-    if (!request.ok()) {
-      throw new IllegalStateException("Fail to add issue comment. Bad HTTP response status: " + request.code());
-    }
-    Map rootJson = (Map) JSONValue.parse(request.body());
-    return new IssueComment((Map)rootJson.get("comment"));
-  }
-
-  @Override
-  public List<String> transitions(String issueKey) {
-    Map<String, Object> queryParams = EncodingUtils.toMap("issue", issueKey);
-    HttpRequest request = requestFactory.get("/api/issues/transitions", queryParams);
-    if (!request.ok()) {
-      throw new IllegalStateException("Fail to return transitions for issue. Bad HTTP response status: " + request.code());
-    }
-    String json = request.body("UTF-8");
-    return parser.parseTransitions(json);
-  }
-
-  @Override
-  public Issue doTransition(String issueKey, String transition) {
-    Map<String, Object> params = EncodingUtils.toMap("issue", issueKey, "transition", transition);
-    HttpRequest request = requestFactory.post("/api/issues/do_transition", params);
-    if (!request.ok()) {
-      throw new IllegalStateException("Fail to execute transition on issue " + issueKey + ".Bad HTTP response status: " + request.code());
-    }
-    return createIssueResult(request);
-  }
-
-  @Override
-  public List<String> actions(String issueKey) {
-    Map<String, Object> queryParams = EncodingUtils.toMap("issue", issueKey);
-    HttpRequest request = requestFactory.get("/api/issues/actions", queryParams);
-    if (!request.ok()) {
-      throw new IllegalStateException("Fail to return actions for issue. Bad HTTP response status: " + request.code());
-    }
-    String json = request.body("UTF-8");
-    return parser.parseActions(json);
-  }
-
-  @Override
-  public Issue doAction(String issueKey, String action) {
-    Map<String, Object> params = EncodingUtils.toMap("issue", issueKey, "actionKey", action);
-    HttpRequest request = requestFactory.post("/api/issues/do_action", params);
-    if (!request.ok()) {
-      throw new IllegalStateException("Fail to execute action on issue " + issueKey + ".Bad HTTP response status: " + request.code());
-    }
-    return createIssueResult(request);
-  }
-
-  private Issue createIssueResult(HttpRequest request){
-    String json = request.body("UTF-8");
-    Map jsonRoot = (Map) JSONValue.parse(json);
-    return new Issue((Map) jsonRoot.get("issue"));
-  }
-
-}
index b5b555087e59918e05a08b78d08bf3804006fa78..3294fa81f6588ceb6e8be9640beb43941270c915 100644 (file)
  * 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.
+ * Lesser General License for more details.
  *
- * You should have received a copy of the GNU Lesser General Public License
+ * You should have received a copy of the GNU Lesser General 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.wsclient.issue;
 
-import org.sonar.wsclient.unmarshallers.JsonUtils;
-
 import javax.annotation.CheckForNull;
-
-import java.util.*;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
 
 /**
  * @since 3.6
  */
-public class Issue {
-
-  private final Map json;
-
-  Issue(Map json) {
-    this.json = json;
-  }
+public interface Issue {
 
   /**
    * Unique key
    */
-  public String key() {
-    return JsonUtils.getString(json, "key");
-  }
+  String key();
 
-  public String componentKey() {
-    return JsonUtils.getString(json, "component");
-  }
+  String componentKey();
 
-  public String projectKey() {
-    return JsonUtils.getString(json, "project");
-  }
+  String projectKey();
 
-  public String ruleKey() {
-    return JsonUtils.getString(json, "rule");
-  }
+  String ruleKey();
 
-  public String severity() {
-    return JsonUtils.getString(json, "severity");
-  }
+  String severity();
 
   @CheckForNull
-  public String message() {
-    return JsonUtils.getString(json, "message");
-  }
+  String message();
 
   @CheckForNull
-  public Integer line() {
-    return JsonUtils.getInteger(json, "line");
-  }
+  Integer line();
 
   @CheckForNull
-  public Double effortToFix() {
-    return JsonUtils.getDouble(json, "effortToFix");
-  }
+  Double effortToFix();
 
-  public String status() {
-    return JsonUtils.getString(json, "status");
-  }
+  String status();
 
   /**
    * The resolution type. Null if the issue is not resolved.
    */
   @CheckForNull
-  public String resolution() {
-    return JsonUtils.getString(json, "resolution");
-  }
+  String resolution();
 
   @CheckForNull
-  public String reporter() {
-    return JsonUtils.getString(json, "reporter");
-  }
+  String reporter();
 
   /**
    * Login of assignee. Null if issue is not assigned.
    */
   @CheckForNull
-  public String assignee() {
-    return JsonUtils.getString(json, "assignee");
-  }
+  String assignee();
 
   /**
    * SCM account
    */
   @CheckForNull
-  public String author() {
-    return JsonUtils.getString(json, "author");
-  }
+  String author();
 
   @CheckForNull
-  public String actionPlan() {
-    return JsonUtils.getString(json, "actionPlan");
-  }
+  String actionPlan();
 
-  public Date creationDate() {
-    return JsonUtils.getDateTime(json, "creationDate");
-  }
+  Date creationDate();
 
-  public Date updateDate() {
-    return JsonUtils.getDateTime(json, "updateDate");
-  }
+  Date updateDate();
 
   @CheckForNull
-  public Date closeDate() {
-    return JsonUtils.getDateTime(json, "closeDate");
-  }
+  Date closeDate();
 
   @CheckForNull
-  public String attribute(String key) {
-    return attributes().get(key);
-  }
-
-  public Map<String, String> attributes() {
-    Map<String, String> attr = (Map) json.get("attr");
-    if (attr == null) {
-      return Collections.emptyMap();
-    }
-    return attr;
-  }
+  String attribute(String key);
+
+  Map<String, String> attributes();
 
   /**
    * Non-null list of comments
    */
-  public List<IssueComment> comments() {
-    List<IssueComment> comments = new ArrayList();
-    List<Map> jsonComments = (List<Map>) json.get("comments");
-    if (jsonComments != null) {
-      for (Map jsonComment : jsonComments) {
-        comments.add(new IssueComment(jsonComment));
-      }
-    }
-    return comments;
-  }
+  List<IssueComment> comments();
 }
index 3527938710a83fb97b0677e94959455a0dccd4c5..1e1fd52df4fa7829abc46b3c4a16a751d98446a0 100644 (file)
@@ -24,16 +24,35 @@ import javax.annotation.Nullable;
 import java.util.List;
 
 /**
+ * This client is a wrapper over the web services related to issues
+ *
  * @since 3.6
  */
 public interface IssueClient {
 
+  /**
+   * Wrap the web service /api/issues/search in order to search for issues.
+   */
   Issues find(IssueQuery query);
 
+  /**
+   * Assign an existing issue to a user. A null assignee removes the assignee.
+   *
+   * @return the updated issue
+   */
   Issue assign(String issueKey, @Nullable String assignee);
 
+  /**
+   * Change the severity of an existing issue. Supported values are "INFO", "MINOR",
+   * "MAJOR", "CRITICAL" and "BLOCKER".
+   *
+   * @return the updated issue
+   */
   Issue setSeverity(String issueKey, String severity);
 
+  /**
+   * Link an existing issue to an action plan. A null action plan unlinks the issue.
+   */
   Issue plan(String issueKey, @Nullable String actionPlan);
 
   IssueComment addComment(String issueKey, String markdownText);
index f649d573b8f090b63eef24618de671d4d6e232e4..2ce101d0ac7c689e251c4965f1e5a5d4f462a0f7 100644 (file)
  * 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.
+ * Lesser General License for more details.
  *
- * You should have received a copy of the GNU Lesser General Public License
+ * You should have received a copy of the GNU Lesser General 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.wsclient.issue;
 
-import org.sonar.wsclient.unmarshallers.JsonUtils;
-
 import java.util.Date;
-import java.util.Map;
 
 /**
  * @since 3.6
  */
-public class IssueComment {
-  private final Map json;
-
-  IssueComment(Map json) {
-    this.json = json;
-  }
-
-  public String key() {
-    return JsonUtils.getString(json, "key");
-  }
+public interface IssueComment {
+  String key();
 
-  public String htmlText() {
-    return JsonUtils.getString(json, "htmlText");
-  }
+  String htmlText();
 
-  public String login() {
-    return JsonUtils.getString(json, "login");
-  }
+  String login();
 
-  public Date createdAt() {
-    return JsonUtils.getDateTime(json, "createdAt");
-  }
+  Date createdAt();
 }
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/IssueJsonParser.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/IssueJsonParser.java
deleted file mode 100644 (file)
index 906929c..0000000
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 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.wsclient.issue;
-
-import org.json.simple.JSONValue;
-import org.sonar.wsclient.component.Component;
-import org.sonar.wsclient.rule.Rule;
-import org.sonar.wsclient.unmarshallers.JsonUtils;
-import org.sonar.wsclient.user.User;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-/**
- * @since 3.6
- */
-class IssueJsonParser {
-
-  Issues parseIssues(String json) {
-    Issues result = new Issues();
-    Map jsonRoot = (Map) JSONValue.parse(json);
-    List<Map> jsonIssues = (List) jsonRoot.get("issues");
-    if (jsonIssues != null) {
-      for (Map jsonIssue : jsonIssues) {
-        result.add(new Issue(jsonIssue));
-      }
-    }
-    parseRules(result, jsonRoot);
-    parseUsers(result, jsonRoot);
-    parseComponents(result, jsonRoot);
-    parseProjects(result, jsonRoot);
-    parseActionPlans(result, jsonRoot);
-    parsePaging(result, jsonRoot);
-    return result;
-  }
-
-  private void parsePaging(Issues result, Map jsonRoot) {
-    Map paging = (Map) jsonRoot.get("paging");
-    result.setPaging(new Paging(paging));
-    result.setMaxResultsReached(JsonUtils.getBoolean(jsonRoot, "maxResultsReached"));
-  }
-
-  private void parseProjects(Issues result, Map jsonRoot) {
-    List<Map> jsonProjects = (List) jsonRoot.get("projects");
-    if (jsonProjects != null) {
-      for (Map jsonProject : jsonProjects) {
-        result.addProject(new Component(jsonProject));
-      }
-    }
-  }
-
-  private void parseComponents(Issues result, Map jsonRoot) {
-    List<Map> jsonComponents = (List) jsonRoot.get("components");
-    if (jsonComponents != null) {
-      for (Map jsonComponent : jsonComponents) {
-        result.addComponent(new Component(jsonComponent));
-      }
-    }
-  }
-
-  private void parseUsers(Issues result, Map jsonRoot) {
-    List<Map> jsonUsers = (List) jsonRoot.get("users");
-    if (jsonUsers != null) {
-      for (Map jsonUser : jsonUsers) {
-        result.add(new User(jsonUser));
-      }
-    }
-  }
-
-  private void parseRules(Issues result, Map jsonRoot) {
-    List<Map> jsonRules = (List) jsonRoot.get("rules");
-    if (jsonRules != null) {
-      for (Map jsonRule : jsonRules) {
-        result.add(new Rule(jsonRule));
-      }
-    }
-  }
-
-  private void parseActionPlans(Issues result, Map jsonRoot) {
-    List<Map> jsonRules = (List) jsonRoot.get("actionPlans");
-    if (jsonRules != null) {
-      for (Map jsonRule : jsonRules) {
-        result.add(new ActionPlan(jsonRule));
-      }
-    }
-  }
-
-  List<String> parseTransitions(String json) {
-    List<String> transitions = new ArrayList<String>();
-    Map jRoot = (Map) JSONValue.parse(json);
-    List<String> jTransitions = (List) jRoot.get("transitions");
-    for (String jTransition : jTransitions) {
-      transitions.add(jTransition);
-    }
-    return transitions;
-  }
-
-  List<String> parseActions(String json) {
-    List<String> actions = new ArrayList<String>();
-    Map jRoot = (Map) JSONValue.parse(json);
-    List<String> jActions = (List) jRoot.get("actions");
-    for (String jAction : jActions) {
-      actions.add(jAction);
-    }
-    return actions;
-  }
-}
index db597021e088c2de8461e4ce3dfcbffe86bd3025..f291d31051aeadcbea4ec494851eab4daf9d1b37 100644 (file)
@@ -30,7 +30,6 @@ import java.util.Map;
  */
 public class IssueQuery {
 
-  static final String BASE_URL = "/api/issues/search";
   private final Map<String, Object> params = new HashMap<String, Object>();
 
   private IssueQuery() {
@@ -40,7 +39,10 @@ public class IssueQuery {
     return new IssueQuery();
   }
 
-  Map<String, Object> urlParams() {
+  /**
+   * URL query string, for internal use
+   */
+  public Map<String, Object> urlParams() {
     return params;
   }
 
index ff761f26cdf6f5a2f5ca013e3510dacdcfef7abf..43d5cf687ace9695d3f5715392269eb73b5f2154 100644 (file)
  * 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.
+ * Lesser General License for more details.
  *
- * You should have received a copy of the GNU Lesser General Public License
+ * You should have received a copy of the GNU Lesser General 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.wsclient.issue;
 
+import org.sonar.wsclient.base.Paging;
 import org.sonar.wsclient.component.Component;
 import org.sonar.wsclient.rule.Rule;
 import org.sonar.wsclient.user.User;
 
 import javax.annotation.CheckForNull;
-
-import java.util.*;
+import java.util.Collection;
+import java.util.List;
 
 /**
  * @since 3.6
  */
-public class Issues {
+public interface Issues {
+  List<Issue> list();
 
-  private final List<Issue> list = new ArrayList<Issue>();
-  private final Map<String, Rule> rulesByKey = new HashMap<String, Rule>();
-  private final Map<String, User> usersByKey = new HashMap<String, User>();
-  private final Map<String, Component> componentsByKey = new HashMap<String, Component>();
-  private final Map<String, Component> projectsByKey = new HashMap<String, Component>();
-  private final Map<String, ActionPlan> actionPlansByKey = new HashMap<String, ActionPlan>();
-  private Paging paging;
-  private Boolean maxResultsReached;
+  int size();
 
-  public List<Issue> list() {
-    return list;
-  }
+  Collection<Rule> rules();
 
-  public int size() {
-    return list.size();
-  }
+  Rule rule(Issue issue);
 
-  public Collection<Rule> rules() {
-    return rulesByKey.values();
-  }
+  Collection<User> users();
 
-  public Rule rule(Issue issue) {
-    return rulesByKey.get(issue.ruleKey());
-  }
+  @CheckForNull
+  User user(String login);
 
-  public Collection<User> users() {
-    return usersByKey.values();
-  }
+  Collection<Component> components();
 
   @CheckForNull
-  public User user(String login) {
-    return usersByKey.get(login);
-  }
+  Component component(Issue issue);
 
-  public Collection<Component> components() {
-    return componentsByKey.values();
-  }
+  Collection<Component> projects();
 
   @CheckForNull
-  public Component component(Issue issue) {
-    return componentsByKey.get(issue.componentKey());
-  }
+  Component project(Issue issue);
 
-  public Collection<Component> projects() {
-    return projectsByKey.values();
-  }
+  Collection<ActionPlan> actionPlans();
 
   @CheckForNull
-  public Component project(Issue issue) {
-    return projectsByKey.get(issue.projectKey());
-  }
+  ActionPlan actionPlans(Issue issue);
 
-  public Collection<ActionPlan> actionPlans() {
-    return actionPlansByKey.values();
-  }
+  Paging paging();
+
+  Boolean maxResultsReached();
 
-  @CheckForNull
-  public ActionPlan actionPlans(Issue issue) {
-    return actionPlansByKey.get(issue.actionPlan());
-  }
-
-  public Paging paging() {
-    return paging;
-  }
-
-  public Boolean maxResultsReached() {
-    return maxResultsReached;
-  }
-
-  Issues add(Issue issue) {
-    list.add(issue);
-    return this;
-  }
-
-  Issues add(Rule rule) {
-    rulesByKey.put(rule.key(), rule);
-    return this;
-  }
-
-  Issues add(User user) {
-    usersByKey.put(user.login(), user);
-    return this;
-  }
-
-  Issues add(ActionPlan actionPlan) {
-    actionPlansByKey.put(actionPlan.key(), actionPlan);
-    return this;
-  }
-
-  Issues addComponent(Component c) {
-    componentsByKey.put(c.key(), c);
-    return this;
-  }
-
-  Issues addProject(Component c) {
-    projectsByKey.put(c.key(), c);
-    return this;
-  }
-
-  Issues setPaging(Paging paging) {
-    this.paging = paging;
-    return this;
-  }
-
-  Issues setMaxResultsReached(Boolean maxResultsReached) {
-    this.maxResultsReached = maxResultsReached;
-    return this;
-  }
 }
index 74c92acbfe88e5142bfa99aa594499ea3952aa44..19655b9edeb07a7f7533a2933092e10a622c7153 100644 (file)
@@ -30,7 +30,6 @@ import java.util.Map;
  */
 public class NewActionPlan {
 
-  static final String BASE_URL = "/api/action_plans/create";
   private final Map<String, Object> params = new HashMap<String, Object>();
 
   private NewActionPlan() {
@@ -40,7 +39,7 @@ public class NewActionPlan {
     return new NewActionPlan();
   }
 
-  Map<String, Object> urlParams() {
+  public Map<String, Object> urlParams() {
     return params;
   }
 
index 4b8932c2a113a3b9d8f5798aa2febea4545e1a3a..8bda9a02fc21fca3130eedb0e28c588e3d5cf8c6 100644 (file)
@@ -27,7 +27,7 @@ import java.util.Map;
  * @since 3.6
  */
 public class NewIssue {
-  static final String BASE_URL = "/api/issues/create";
+
   private final Map<String, Object> params = new HashMap<String, Object>();
 
   private NewIssue() {
@@ -37,7 +37,7 @@ public class NewIssue {
     return new NewIssue();
   }
 
-  Map<String, Object> urlParams() {
+  public Map<String, Object> urlParams() {
     return params;
   }
 
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/Paging.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/Paging.java
deleted file mode 100644 (file)
index cd24f2b..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 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.wsclient.issue;
-
-import org.sonar.wsclient.unmarshallers.JsonUtils;
-
-import java.util.Map;
-
-/**
- * @since 3.6
- * TODO move outside this package
- */
-public class Paging {
-
-  private final Map json;
-
-  Paging(Map json) {
-    this.json = json;
-  }
-
-  public Integer pageSize() {
-    return JsonUtils.getInteger(json, "pageSize");
-  }
-
-  public Integer pageIndex() {
-    return JsonUtils.getInteger(json, "pageIndex");
-  }
-
-  public Integer total() {
-    return JsonUtils.getInteger(json, "total");
-  }
-
-  public Integer pages() {
-    return JsonUtils.getInteger(json, "pages");
-  }
-
-}
index 2ca316217b3589a3be8105c981ad5664ea1eb5f4..c8aace9c92c032a66597f9200630b1055e9a4cc9 100644 (file)
@@ -30,7 +30,6 @@ import java.util.Map;
  */
 public class UpdateActionPlan {
 
-  static final String BASE_URL = "/api/action_plans/update";
   private final Map<String, Object> params = new HashMap<String, Object>();
 
   private UpdateActionPlan() {
@@ -40,7 +39,7 @@ public class UpdateActionPlan {
     return new UpdateActionPlan();
   }
 
-  Map<String, Object> urlParams() {
+  public Map<String, Object> urlParams() {
     return params;
   }
 
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/internal/DefaultActionPlan.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/internal/DefaultActionPlan.java
new file mode 100644 (file)
index 0000000..afa077b
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.wsclient.issue.internal;
+
+import org.sonar.wsclient.issue.ActionPlan;
+import org.sonar.wsclient.unmarshallers.JsonUtils;
+
+import javax.annotation.CheckForNull;
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * @since 3.6
+ */
+public class DefaultActionPlan implements ActionPlan {
+
+  private final Map json;
+
+  DefaultActionPlan(Map json) {
+    this.json = json;
+  }
+
+  /**
+   * Unique key
+   */
+  public String key() {
+    return JsonUtils.getString(json, "key");
+  }
+
+  public String project() {
+    return JsonUtils.getString(json, "project");
+  }
+
+  public String name() {
+    return JsonUtils.getString(json, "name");
+  }
+
+  @CheckForNull
+  public String description() {
+    return JsonUtils.getString(json, "desc");
+  }
+
+  public String status() {
+    return JsonUtils.getString(json, "status");
+  }
+
+  /**
+   * Login of the user who created the action plan.
+   */
+  public String userLogin() {
+    return JsonUtils.getString(json, "userLogin");
+  }
+
+  @CheckForNull
+  public Date deadLine() {
+    return JsonUtils.getDateTime(json, "deadLine");
+  }
+
+  public Date createdAt() {
+    return JsonUtils.getDateTime(json, "createdAt");
+  }
+
+  public Date updatedAt() {
+    return JsonUtils.getDateTime(json, "updatedAt");
+  }
+
+  @CheckForNull
+  public Integer totalIssues() {
+    return JsonUtils.getInteger(json, "totalIssues");
+  }
+
+  @CheckForNull
+  public Integer unresolvedIssues() {
+    return JsonUtils.getInteger(json, "unresolvedIssues");
+  }
+
+}
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/internal/DefaultActionPlanClient.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/internal/DefaultActionPlanClient.java
new file mode 100644 (file)
index 0000000..3a187b1
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.wsclient.issue.internal;
+
+import org.json.simple.JSONValue;
+import org.sonar.wsclient.internal.EncodingUtils;
+import org.sonar.wsclient.internal.HttpRequestFactory;
+import org.sonar.wsclient.issue.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Do not instantiate this class, but use {@link org.sonar.wsclient.SonarClient#actionPlanClient()}.
+ */
+public class DefaultActionPlanClient implements ActionPlanClient {
+
+  private final HttpRequestFactory requestFactory;
+
+  /**
+   * For internal use. Use {@link org.sonar.wsclient.SonarClient} to get an instance.
+   */
+  public DefaultActionPlanClient(HttpRequestFactory requestFactory) {
+    this.requestFactory = requestFactory;
+  }
+
+  @Override
+  public List<ActionPlan> find(String projectKey) {
+    String json = requestFactory.get(ActionPlanQuery.BASE_URL, EncodingUtils.toMap("project", projectKey));
+    List<ActionPlan> result = new ArrayList<ActionPlan>();
+    Map jsonRoot = (Map) JSONValue.parse(json);
+    List<Map> jsonActionPlans = (List<Map>) jsonRoot.get("actionPlans");
+    if (jsonActionPlans != null) {
+      for (Map jsonActionPlan : jsonActionPlans) {
+        result.add(new DefaultActionPlan(jsonActionPlan));
+      }
+    }
+    return result;
+  }
+
+  @Override
+  public ActionPlan create(NewActionPlan newActionPlan) {
+    String json = requestFactory.post("/api/action_plans/create", newActionPlan.urlParams());
+    return createActionPlanResult(json);
+  }
+
+  @Override
+  public ActionPlan update(UpdateActionPlan updateActionPlan) {
+    String json = requestFactory.post("/api/action_plans/update", updateActionPlan.urlParams());
+    return createActionPlanResult(json);
+  }
+
+  @Override
+  public void delete(String actionPlanKey) {
+    executeSimpleAction(actionPlanKey, "delete");
+  }
+
+  @Override
+  public ActionPlan open(String actionPlanKey) {
+    String json = executeSimpleAction(actionPlanKey, "open");
+    return createActionPlanResult(json);
+  }
+
+  @Override
+  public ActionPlan close(String actionPlanKey) {
+    String json = executeSimpleAction(actionPlanKey, "close");
+    return createActionPlanResult(json);
+  }
+
+  private String executeSimpleAction(String actionPlanKey, String action) {
+    return requestFactory.post("/api/action_plans/" + action, EncodingUtils.toMap("key", actionPlanKey));
+  }
+
+  private ActionPlan createActionPlanResult(String json) {
+    Map jsonRoot = (Map) JSONValue.parse(json);
+    return new DefaultActionPlan((Map) jsonRoot.get("actionPlan"));
+  }
+
+}
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/internal/DefaultIssue.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/internal/DefaultIssue.java
new file mode 100644 (file)
index 0000000..36f25bc
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.wsclient.issue.internal;
+
+import org.sonar.wsclient.issue.Issue;
+import org.sonar.wsclient.issue.IssueComment;
+import org.sonar.wsclient.unmarshallers.JsonUtils;
+
+import javax.annotation.CheckForNull;
+import java.util.*;
+
+/**
+ * @since 3.6
+ */
+public class DefaultIssue implements Issue {
+
+  private final Map json;
+
+  DefaultIssue(Map json) {
+    this.json = json;
+  }
+
+  /**
+   * Unique key
+   */
+  public String key() {
+    return JsonUtils.getString(json, "key");
+  }
+
+  public String componentKey() {
+    return JsonUtils.getString(json, "component");
+  }
+
+  public String projectKey() {
+    return JsonUtils.getString(json, "project");
+  }
+
+  public String ruleKey() {
+    return JsonUtils.getString(json, "rule");
+  }
+
+  public String severity() {
+    return JsonUtils.getString(json, "severity");
+  }
+
+  @CheckForNull
+  public String message() {
+    return JsonUtils.getString(json, "message");
+  }
+
+  @CheckForNull
+  public Integer line() {
+    return JsonUtils.getInteger(json, "line");
+  }
+
+  @CheckForNull
+  public Double effortToFix() {
+    return JsonUtils.getDouble(json, "effortToFix");
+  }
+
+  public String status() {
+    return JsonUtils.getString(json, "status");
+  }
+
+  /**
+   * The resolution type. Null if the issue is not resolved.
+   */
+  @CheckForNull
+  public String resolution() {
+    return JsonUtils.getString(json, "resolution");
+  }
+
+  @CheckForNull
+  public String reporter() {
+    return JsonUtils.getString(json, "reporter");
+  }
+
+  /**
+   * Login of assignee. Null if issue is not assigned.
+   */
+  @CheckForNull
+  public String assignee() {
+    return JsonUtils.getString(json, "assignee");
+  }
+
+  /**
+   * SCM account
+   */
+  @CheckForNull
+  public String author() {
+    return JsonUtils.getString(json, "author");
+  }
+
+  @CheckForNull
+  public String actionPlan() {
+    return JsonUtils.getString(json, "actionPlan");
+  }
+
+  public Date creationDate() {
+    return JsonUtils.getDateTime(json, "creationDate");
+  }
+
+  public Date updateDate() {
+    return JsonUtils.getDateTime(json, "updateDate");
+  }
+
+  @CheckForNull
+  public Date closeDate() {
+    return JsonUtils.getDateTime(json, "closeDate");
+  }
+
+  @CheckForNull
+  public String attribute(String key) {
+    return attributes().get(key);
+  }
+
+  public Map<String, String> attributes() {
+    Map<String, String> attr = (Map<String,String>) json.get("attr");
+    if (attr == null) {
+      return Collections.emptyMap();
+    }
+    return attr;
+  }
+
+  /**
+   * Non-null list of comments
+   */
+  public List<IssueComment> comments() {
+    List<IssueComment> comments = new ArrayList<IssueComment>();
+    List<Map> jsonComments = (List<Map>) json.get("comments");
+    if (jsonComments != null) {
+      for (Map jsonComment : jsonComments) {
+        comments.add(new DefaultIssueComment(jsonComment));
+      }
+    }
+    return comments;
+  }
+}
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/internal/DefaultIssueClient.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/internal/DefaultIssueClient.java
new file mode 100644 (file)
index 0000000..c4b2f32
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.wsclient.issue.internal;
+
+import org.json.simple.JSONValue;
+import org.sonar.wsclient.internal.EncodingUtils;
+import org.sonar.wsclient.internal.HttpRequestFactory;
+import org.sonar.wsclient.issue.*;
+
+import javax.annotation.Nullable;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Do not instantiate this class, but use {@link org.sonar.wsclient.SonarClient#issueClient()}.
+ */
+public class DefaultIssueClient implements IssueClient {
+
+  private static final String SEARCH_URL = "/api/issues/search";
+
+  private final HttpRequestFactory requestFactory;
+  private final IssueJsonParser parser;
+
+  public DefaultIssueClient(HttpRequestFactory requestFactory) {
+    this.requestFactory = requestFactory;
+    this.parser = new IssueJsonParser();
+  }
+
+  public Issues find(IssueQuery query) {
+    String json = requestFactory.get(SEARCH_URL, query.urlParams());
+    return parser.parseIssues(json);
+  }
+
+  @Override
+  public Issue create(NewIssue newIssue) {
+    String json = requestFactory.post("/api/issues/create", newIssue.urlParams());
+    return jsonToIssue(json);
+  }
+
+  @Override
+  public Issue setSeverity(String issueKey, String severity) {
+    Map<String, Object> params = EncodingUtils.toMap("issue", issueKey, "severity", severity);
+    String json = requestFactory.post("/api/issues/set_severity", params);
+    return jsonToIssue(json);
+  }
+
+  @Override
+  public Issue assign(String issueKey, @Nullable String assignee) {
+    Map<String, Object> params = EncodingUtils.toMap("issue", issueKey, "assignee", assignee);
+    String json = requestFactory.post("/api/issues/assign", params);
+    return jsonToIssue(json);
+  }
+
+  @Override
+  public Issue plan(String issueKey, @Nullable String actionPlanKey) {
+    Map<String, Object> params = EncodingUtils.toMap("issue", issueKey, "plan", actionPlanKey);
+    String json = requestFactory.post("/api/issues/plan", params);
+    return jsonToIssue(json);
+  }
+
+  @Override
+  public IssueComment addComment(String issueKey, String markdownText) {
+    Map<String, Object> params = EncodingUtils.toMap("issue", issueKey, "text", markdownText);
+    String json = requestFactory.post("/api/issues/add_comment", params);
+    Map rootJson = (Map) JSONValue.parse(json);
+    return new DefaultIssueComment((Map) rootJson.get("comment"));
+  }
+
+  @Override
+  public List<String> transitions(String issueKey) {
+    Map<String, Object> queryParams = EncodingUtils.toMap("issue", issueKey);
+    String json = requestFactory.get("/api/issues/transitions", queryParams);
+    return parser.parseTransitions(json);
+  }
+
+  @Override
+  public Issue doTransition(String issueKey, String transition) {
+    Map<String, Object> params = EncodingUtils.toMap("issue", issueKey, "transition", transition);
+    String json = requestFactory.post("/api/issues/do_transition", params);
+    return jsonToIssue(json);
+  }
+
+  @Override
+  public List<String> actions(String issueKey) {
+    Map<String, Object> queryParams = EncodingUtils.toMap("issue", issueKey);
+    String json = requestFactory.get("/api/issues/actions", queryParams);
+    return parser.parseActions(json);
+  }
+
+  @Override
+  public Issue doAction(String issueKey, String action) {
+    Map<String, Object> params = EncodingUtils.toMap("issue", issueKey, "actionKey", action);
+    String json = requestFactory.post("/api/issues/do_action", params);
+    return jsonToIssue(json);
+  }
+
+  private Issue jsonToIssue(String json) {
+    Map jsonRoot = (Map) JSONValue.parse(json);
+    return new DefaultIssue((Map) jsonRoot.get("issue"));
+  }
+
+}
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/internal/DefaultIssueComment.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/internal/DefaultIssueComment.java
new file mode 100644 (file)
index 0000000..bcd4256
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.wsclient.issue.internal;
+
+import org.sonar.wsclient.issue.IssueComment;
+import org.sonar.wsclient.unmarshallers.JsonUtils;
+
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * @since 3.6
+ */
+public class DefaultIssueComment implements IssueComment {
+  private final Map json;
+
+  DefaultIssueComment(Map json) {
+    this.json = json;
+  }
+
+  public String key() {
+    return JsonUtils.getString(json, "key");
+  }
+
+  public String htmlText() {
+    return JsonUtils.getString(json, "htmlText");
+  }
+
+  public String login() {
+    return JsonUtils.getString(json, "login");
+  }
+
+  public Date createdAt() {
+    return JsonUtils.getDateTime(json, "createdAt");
+  }
+}
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/internal/DefaultIssues.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/internal/DefaultIssues.java
new file mode 100644 (file)
index 0000000..37b5348
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.wsclient.issue.internal;
+
+import org.sonar.wsclient.component.Component;
+import org.sonar.wsclient.issue.ActionPlan;
+import org.sonar.wsclient.issue.Issue;
+import org.sonar.wsclient.issue.Issues;
+import org.sonar.wsclient.base.Paging;
+import org.sonar.wsclient.rule.Rule;
+import org.sonar.wsclient.user.User;
+
+import javax.annotation.CheckForNull;
+import java.util.*;
+
+/**
+ * @since 3.6
+ */
+public class DefaultIssues implements Issues {
+
+  private final List<Issue> list = new ArrayList<Issue>();
+  private final Map<String, Rule> rulesByKey = new HashMap<String, Rule>();
+  private final Map<String, User> usersByKey = new HashMap<String, User>();
+  private final Map<String, Component> componentsByKey = new HashMap<String, Component>();
+  private final Map<String, Component> projectsByKey = new HashMap<String, Component>();
+  private final Map<String, ActionPlan> actionPlansByKey = new HashMap<String, ActionPlan>();
+  private Paging paging;
+  private Boolean maxResultsReached;
+
+  public List<Issue> list() {
+    return list;
+  }
+
+  public int size() {
+    return list.size();
+  }
+
+  public Collection<Rule> rules() {
+    return rulesByKey.values();
+  }
+
+  public Rule rule(Issue issue) {
+    return rulesByKey.get(issue.ruleKey());
+  }
+
+  public Collection<User> users() {
+    return usersByKey.values();
+  }
+
+  @CheckForNull
+  public User user(String login) {
+    return usersByKey.get(login);
+  }
+
+  public Collection<Component> components() {
+    return componentsByKey.values();
+  }
+
+  @CheckForNull
+  public Component component(Issue issue) {
+    return componentsByKey.get(issue.componentKey());
+  }
+
+  public Collection<Component> projects() {
+    return projectsByKey.values();
+  }
+
+  @CheckForNull
+  public Component project(Issue issue) {
+    return projectsByKey.get(issue.projectKey());
+  }
+
+  public Collection<ActionPlan> actionPlans() {
+    return actionPlansByKey.values();
+  }
+
+  @CheckForNull
+  public ActionPlan actionPlans(Issue issue) {
+    return actionPlansByKey.get(issue.actionPlan());
+  }
+
+  public Paging paging() {
+    return paging;
+  }
+
+  public Boolean maxResultsReached() {
+    return maxResultsReached;
+  }
+
+  DefaultIssues add(Issue issue) {
+    list.add(issue);
+    return this;
+  }
+
+  DefaultIssues add(Rule rule) {
+    rulesByKey.put(rule.key(), rule);
+    return this;
+  }
+
+  DefaultIssues add(User user) {
+    usersByKey.put(user.login(), user);
+    return this;
+  }
+
+  DefaultIssues add(ActionPlan actionPlan) {
+    actionPlansByKey.put(actionPlan.key(), actionPlan);
+    return this;
+  }
+
+  DefaultIssues addComponent(Component c) {
+    componentsByKey.put(c.key(), c);
+    return this;
+  }
+
+  DefaultIssues addProject(Component c) {
+    projectsByKey.put(c.key(), c);
+    return this;
+  }
+
+  DefaultIssues setPaging(Paging paging) {
+    this.paging = paging;
+    return this;
+  }
+
+  DefaultIssues setMaxResultsReached(Boolean maxResultsReached) {
+    this.maxResultsReached = maxResultsReached;
+    return this;
+  }
+}
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/internal/IssueJsonParser.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/internal/IssueJsonParser.java
new file mode 100644 (file)
index 0000000..d123e97
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.wsclient.issue.internal;
+
+import org.json.simple.JSONValue;
+import org.sonar.wsclient.component.Component;
+import org.sonar.wsclient.issue.Issues;
+import org.sonar.wsclient.base.Paging;
+import org.sonar.wsclient.rule.Rule;
+import org.sonar.wsclient.unmarshallers.JsonUtils;
+import org.sonar.wsclient.user.User;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @since 3.6
+ */
+public class IssueJsonParser {
+
+  public Issues parseIssues(String json) {
+    DefaultIssues result = new DefaultIssues();
+    Map jsonRoot = (Map) JSONValue.parse(json);
+    List<Map> jsonIssues = (List<Map>) jsonRoot.get("issues");
+    if (jsonIssues != null) {
+      for (Map jsonIssue : jsonIssues) {
+        result.add(new DefaultIssue(jsonIssue));
+      }
+    }
+    parseRules(result, jsonRoot);
+    parseUsers(result, jsonRoot);
+    parseComponents(result, jsonRoot);
+    parseProjects(result, jsonRoot);
+    parseActionPlans(result, jsonRoot);
+    parsePaging(result, jsonRoot);
+    return result;
+  }
+
+  private void parsePaging(DefaultIssues result, Map jsonRoot) {
+    Map paging = (Map) jsonRoot.get("paging");
+    result.setPaging(new Paging(paging));
+    result.setMaxResultsReached(JsonUtils.getBoolean(jsonRoot, "maxResultsReached"));
+  }
+
+  private void parseProjects(DefaultIssues result, Map jsonRoot) {
+    List<Map> jsonProjects = (List<Map>) jsonRoot.get("projects");
+    if (jsonProjects != null) {
+      for (Map jsonProject : jsonProjects) {
+        result.addProject(new Component(jsonProject));
+      }
+    }
+  }
+
+  private void parseComponents(DefaultIssues result, Map jsonRoot) {
+    List<Map> jsonComponents = (List<Map>) jsonRoot.get("components");
+    if (jsonComponents != null) {
+      for (Map jsonComponent : jsonComponents) {
+        result.addComponent(new Component(jsonComponent));
+      }
+    }
+  }
+
+  private void parseUsers(DefaultIssues result, Map jsonRoot) {
+    List<Map> jsonUsers = (List<Map>) jsonRoot.get("users");
+    if (jsonUsers != null) {
+      for (Map jsonUser : jsonUsers) {
+        result.add(new User(jsonUser));
+      }
+    }
+  }
+
+  private void parseRules(DefaultIssues result, Map jsonRoot) {
+    List<Map> jsonRules = (List<Map>) jsonRoot.get("rules");
+    if (jsonRules != null) {
+      for (Map jsonRule : jsonRules) {
+        result.add(new Rule(jsonRule));
+      }
+    }
+  }
+
+  private void parseActionPlans(DefaultIssues result, Map jsonRoot) {
+    List<Map> jsonRules = (List) jsonRoot.get("actionPlans");
+    if (jsonRules != null) {
+      for (Map jsonRule : jsonRules) {
+        result.add(new DefaultActionPlan(jsonRule));
+      }
+    }
+  }
+
+  List<String> parseTransitions(String json) {
+    List<String> transitions = new ArrayList<String>();
+    Map jRoot = (Map) JSONValue.parse(json);
+    List<String> jTransitions = (List<String>) jRoot.get("transitions");
+    for (String jTransition : jTransitions) {
+      transitions.add(jTransition);
+    }
+    return transitions;
+  }
+
+  List<String> parseActions(String json) {
+    List<String> actions = new ArrayList<String>();
+    Map jRoot = (Map) JSONValue.parse(json);
+    List<String> jActions = (List<String>) jRoot.get("actions");
+    for (String jAction : jActions) {
+      actions.add(jAction);
+    }
+    return actions;
+  }
+}
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/internal/package-info.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/internal/package-info.java
new file mode 100644 (file)
index 0000000..bd54aab
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.
+ */
+@javax.annotation.ParametersAreNonnullByDefault
+package org.sonar.wsclient.issue.internal;
\ No newline at end of file
index fd842df823ca4c4a37bbbbccbb561979d6c6bdd6..b7c6086699159d89b832f8f0125864ead00009a0 100644 (file)
@@ -19,7 +19,6 @@
  */
 package org.sonar.wsclient.user;
 
-import com.github.kevinsawicki.http.HttpRequest;
 import org.json.simple.JSONValue;
 import org.sonar.wsclient.internal.HttpRequestFactory;
 
@@ -43,14 +42,10 @@ public class DefaultUserClient implements UserClient {
 
   @Override
   public List<User> find(UserQuery query) {
-    HttpRequest request = requestFactory.get(UserQuery.BASE_URL, query.urlParams());
-    if (!request.ok()) {
-      throw new IllegalStateException("Fail to search for users. Bad HTTP response status: " + request.code());
-    }
+    String json = requestFactory.get(UserQuery.BASE_URL, query.urlParams());
     List<User> result = new ArrayList<User>();
-    String json = request.body("UTF-8");
     Map jsonRoot = (Map) JSONValue.parse(json);
-    List<Map> jsonUsers = (List) jsonRoot.get("users");
+    List<Map> jsonUsers = (List<Map>) jsonRoot.get("users");
     if (jsonUsers != null) {
       for (Map jsonUser : jsonUsers) {
         result.add(new User(jsonUser));
index 8216d88767e7264806347bade293ea76153f1216..74b601c3f3d2a28769a02f33abc05aaae6187b02 100644 (file)
@@ -20,8 +20,8 @@
 package org.sonar.wsclient;
 
 import org.junit.Test;
-import org.sonar.wsclient.issue.DefaultActionPlanClient;
-import org.sonar.wsclient.issue.DefaultIssueClient;
+import org.sonar.wsclient.issue.internal.DefaultActionPlanClient;
+import org.sonar.wsclient.issue.internal.DefaultIssueClient;
 import org.sonar.wsclient.user.DefaultUserClient;
 
 import static org.fest.assertions.Assertions.assertThat;
diff --git a/sonar-ws-client/src/test/java/org/sonar/wsclient/base/HttpExceptionTest.java b/sonar-ws-client/src/test/java/org/sonar/wsclient/base/HttpExceptionTest.java
new file mode 100644 (file)
index 0000000..d0a41a0
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.wsclient.base;
+
+import org.junit.Test;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class HttpExceptionTest {
+  @Test
+  public void test_exception() throws Exception {
+    HttpException exception = new HttpException("http://localhost:9000/api/search", 500);
+    assertThat(exception.status()).isEqualTo(500);
+    assertThat(exception.url()).isEqualTo("http://localhost:9000/api/search");
+    assertThat(exception.getMessage()).isEqualTo("Error 500 on http://localhost:9000/api/search");
+  }
+}
index 0006ad7295baf496d03b6a0eeeb63a26a0be4671..9969090a1ee3edec220486412e1e3c7fc619163f 100644 (file)
  */
 package org.sonar.wsclient.internal;
 
-import com.github.kevinsawicki.http.HttpRequest;
 import org.junit.Rule;
 import org.junit.Test;
 import org.sonar.wsclient.MockHttpServerInterceptor;
-import org.sonar.wsclient.issue.DefaultIssueClient;
 import org.sonar.wsclient.issue.IssueClient;
 import org.sonar.wsclient.issue.IssueQuery;
+import org.sonar.wsclient.issue.internal.DefaultIssueClient;
 
+import java.net.ConnectException;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.Collections;
 import java.util.Date;
 
 import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
 
 public class HttpRequestFactoryTest {
   @Rule
@@ -40,56 +41,62 @@ public class HttpRequestFactoryTest {
 
   @Test
   public void test_get() {
-    httpServer.doReturnStatus(200).doReturnBody("list of issues");
+    httpServer.doReturnStatus(200).doReturnBody("{'issues': []}");
 
     HttpRequestFactory factory = new HttpRequestFactory(httpServer.url());
-    HttpRequest request = factory.get("/api/issues", Collections.<String, Object>emptyMap());
+    String json = factory.get("/api/issues", Collections.<String, Object>emptyMap());
 
-    assertThat(request.method()).isEqualTo("GET");
-    assertThat(request.body()).isEqualTo("list of issues");
-    assertThat(request.code()).isEqualTo(200);
+    assertThat(json).isEqualTo("{'issues': []}");
     assertThat(httpServer.requestedPath()).isEqualTo("/api/issues");
   }
 
+  @Test
+  public void should_throw_illegal_state_exc_if_connect_exception() {
+    HttpRequestFactory factory = new HttpRequestFactory("http://localhost:1");
+    try {
+      factory.get("/api/issues", Collections.<String, Object>emptyMap());
+      fail();
+    } catch (Exception e) {
+      assertThat(e).isInstanceOf(IllegalStateException.class);
+      assertThat(e).hasMessage("java.net.ConnectException: Connection refused");
+    }
+  }
+
   @Test
   public void test_post() {
-    httpServer.doReturnStatus(200);
+    httpServer.doReturnStatus(200).doReturnBody("{}");
 
     HttpRequestFactory factory = new HttpRequestFactory(httpServer.url());
-    HttpRequest request = factory.post("/api/issues/change", Collections.<String, Object>emptyMap());
+    String json = factory.post("/api/issues/change", Collections.<String, Object>emptyMap());
 
-    assertThat(request.method()).isEqualTo("POST");
-    assertThat(request.code()).isEqualTo(200);
+    assertThat(json).isEqualTo("{}");
     assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/change");
   }
 
   @Test
   public void test_authentication() {
-    httpServer.doReturnStatus(200).doReturnBody("list of issues");
+    httpServer.doReturnStatus(200).doReturnBody("{}");
 
     HttpRequestFactory factory = new HttpRequestFactory(httpServer.url()).setLogin("karadoc").setPassword("legrascestlavie");
-    HttpRequest request = factory.get("/api/issues", Collections.<String, Object>emptyMap());
+    String json = factory.get("/api/issues", Collections.<String, Object>emptyMap());
 
-    assertThat(request.body()).isEqualTo("list of issues");
-    assertThat(request.code()).isEqualTo(200);
+    assertThat(json).isEqualTo("{}");
     assertThat(httpServer.requestedPath()).isEqualTo("/api/issues");
     assertThat(httpServer.requestHeaders().get("Authorization")).isEqualTo("Basic a2FyYWRvYzpsZWdyYXNjZXN0bGF2aWU=");
   }
 
   @Test
   public void test_proxy() throws Exception {
-    HttpRequestFactory factory = new HttpRequestFactory(httpServer.url()).setProxyHost("localhost").setProxyPort(5020);
-    HttpRequest request = factory.get("/api/issues", Collections.<String, Object>emptyMap());
-    // it's not possible to check that the proxy is correctly configured
-  }
-
-  @Test
-  public void test_proxy_credentials() throws Exception {
     HttpRequestFactory factory = new HttpRequestFactory(httpServer.url())
-      .setProxyHost("localhost").setProxyPort(5020)
+      .setProxyHost("localhost").setProxyPort(1)
       .setProxyLogin("john").setProxyPassword("smith");
-    HttpRequest request = factory.get("/api/issues", Collections.<String, Object>emptyMap());
-    // it's not possible to check that the proxy is correctly configured
+    try {
+      factory.get("/api/issues", Collections.<String, Object>emptyMap());
+      fail();
+    } catch (IllegalStateException e) {
+      // it's not possible to check that the proxy is correctly configured
+      assertThat(e.getCause()).isInstanceOf(ConnectException.class);
+    }
   }
 
   @Test
diff --git a/sonar-ws-client/src/test/java/org/sonar/wsclient/issue/DefaultActionPlanClientTest.java b/sonar-ws-client/src/test/java/org/sonar/wsclient/issue/DefaultActionPlanClientTest.java
deleted file mode 100644 (file)
index e56572f..0000000
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 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.wsclient.issue;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.wsclient.MockHttpServerInterceptor;
-import org.sonar.wsclient.internal.HttpRequestFactory;
-
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.List;
-
-import static org.fest.assertions.Assertions.assertThat;
-import static org.fest.assertions.Fail.fail;
-
-public class DefaultActionPlanClientTest {
-
-  @Rule
-  public MockHttpServerInterceptor httpServer = new MockHttpServerInterceptor();
-
-  @Test
-  public void should_find_action_plans() {
-    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
-    httpServer.doReturnBody("{\"actionPlans\": [{\"key\": \"382f6f2e-ad9d-424a-b973-9b065e04348a\",\n" +
-      "\"name\": \"Long term\",\n" +
-      "\"status\": \"CLOSED\",\n" +
-      "\"project\": \"com.sonarsource.it.samples:simple-sample\",\n" +
-      "\"userLogin\": \"admin\",\n" +
-      "\"deadLine\": \"2013-05-30T00:00:00+0200\",\n" +
-      "\"totalIssues\": 3,\n" +
-      "\"unresolvedIssues\": 2,\n" +
-      "\"createdAt\": \"2013-05-13T12:50:29+0200\",\n" +
-      "\"updatedAt\": \"2013-05-13T12:50:44+0200\"}]}");
-
-    ActionPlanClient client = new DefaultActionPlanClient(requestFactory);
-    List<ActionPlan> actionPlans = client.find("com.sonarsource.it.samples:simple-sample");
-
-    assertThat(httpServer.requestedPath()).isEqualTo("/api/action_plans/search?project=com.sonarsource.it.samples:simple-sample");
-    assertThat(actionPlans).hasSize(1);
-    ActionPlan actionPlan = actionPlans.get(0);
-    assertThat(actionPlan.key()).isEqualTo("382f6f2e-ad9d-424a-b973-9b065e04348a");
-    assertThat(actionPlan.name()).isEqualTo("Long term");
-    assertThat(actionPlan.project()).isEqualTo("com.sonarsource.it.samples:simple-sample");
-    assertThat(actionPlan.status()).isEqualTo("CLOSED");
-    assertThat(actionPlan.userLogin()).isEqualTo("admin");
-    assertThat(actionPlan.deadLine()).isNotNull();
-    assertThat(actionPlan.totalIssues()).isEqualTo(3);
-    assertThat(actionPlan.unresolvedIssues()).isEqualTo(2);
-    assertThat(actionPlan.createdAt()).isNotNull();
-    assertThat(actionPlan.updatedAt()).isNotNull();
-  }
-
-  @Test
-  public void should_create_action_plan() throws Exception {
-    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
-    httpServer.doReturnBody("{\"actionPlan\": {\"key\": \"382f6f2e-ad9d-424a-b973-9b065e04348a\"}}");
-
-    ActionPlanClient client = new DefaultActionPlanClient(requestFactory);
-    ActionPlan result = client.create(
-      NewActionPlan.create().name("Short term").project("org.sonar.Sample").description("Short term issues").deadLine(stringToDate("2014-01-01")));
-
-    assertThat(httpServer.requestedPath()).isEqualTo("/api/action_plans/create?project=org.sonar.Sample&description=Short%20term%20issues&name=Short%20term&deadLine=2014-01-01");
-    assertThat(result).isNotNull();
-  }
-
-  @Test
-  public void should_update_action_plan() {
-    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
-    httpServer.doReturnBody("{\"actionPlan\": {\"key\": \"382f6f2e-ad9d-424a-b973-9b065e04348a\"}}");
-
-    ActionPlanClient client = new DefaultActionPlanClient(requestFactory);
-    ActionPlan result = client.update(
-      UpdateActionPlan.create().key("382f6f2e-ad9d-424a-b973-9b065e04348a").name("Short term").description("Short term issues").deadLine(stringToDate("2014-01-01")));
-
-    assertThat(httpServer.requestedPath()).isEqualTo("/api/action_plans/update?description=Short%20term%20issues&name=Short%20term&deadLine=2014-01-01&key=382f6f2e-ad9d-424a-b973-9b065e04348a");
-    assertThat(result).isNotNull();
-  }
-
-  @Test
-  public void should_delete_action_plan() {
-    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
-
-    ActionPlanClient client = new DefaultActionPlanClient(requestFactory);
-    client.delete("382f6f2e-ad9d-424a-b973-9b065e04348a");
-
-    assertThat(httpServer.requestedPath()).isEqualTo("/api/action_plans/delete?key=382f6f2e-ad9d-424a-b973-9b065e04348a");
-  }
-
-  @Test
-  public void should_fail_to_delete_action_plan() {
-    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
-    httpServer.doReturnStatus(500);
-
-    ActionPlanClient client = new DefaultActionPlanClient(requestFactory);
-    try {
-      client.delete("382f6f2e-ad9d-424a-b973-9b065e04348a");
-      fail();
-    } catch (IllegalStateException e) {
-      assertThat(e).hasMessage("Fail to delete action plan. Bad HTTP response status: 500");
-    }
-  }
-
-  @Test
-  public void should_open_action_plan() {
-    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
-    httpServer.doReturnBody("{\"actionPlan\": {\"key\": \"382f6f2e-ad9d-424a-b973-9b065e04348a\"}}");
-
-    ActionPlanClient client = new DefaultActionPlanClient(requestFactory);
-    ActionPlan result = client.open("382f6f2e-ad9d-424a-b973-9b065e04348a");
-
-    assertThat(httpServer.requestedPath()).isEqualTo("/api/action_plans/open?key=382f6f2e-ad9d-424a-b973-9b065e04348a");
-    assertThat(result).isNotNull();
-  }
-
-  @Test
-  public void should_close_action_plan() {
-    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
-    httpServer.doReturnBody("{\"actionPlan\": {\"key\": \"382f6f2e-ad9d-424a-b973-9b065e04348a\"}}");
-
-    ActionPlanClient client = new DefaultActionPlanClient(requestFactory);
-    ActionPlan result = client.close("382f6f2e-ad9d-424a-b973-9b065e04348a");
-
-    assertThat(httpServer.requestedPath()).isEqualTo("/api/action_plans/close?key=382f6f2e-ad9d-424a-b973-9b065e04348a");
-    assertThat(result).isNotNull();
-  }
-
-  private static Date stringToDate(String sDate) {
-    try {
-      SimpleDateFormat sdf = new SimpleDateFormat("yyyy-dd-MM");
-      return sdf.parse(sDate);
-    } catch (ParseException e) {
-      throw new RuntimeException(e);
-    }
-  }
-
-}
diff --git a/sonar-ws-client/src/test/java/org/sonar/wsclient/issue/DefaultIssueClientTest.java b/sonar-ws-client/src/test/java/org/sonar/wsclient/issue/DefaultIssueClientTest.java
deleted file mode 100644 (file)
index 7ad6f6d..0000000
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 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.wsclient.issue;
-
-import org.apache.commons.io.IOUtils;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.wsclient.MockHttpServerInterceptor;
-import org.sonar.wsclient.internal.HttpRequestFactory;
-
-import java.util.List;
-
-import static org.fest.assertions.Assertions.assertThat;
-import static org.fest.assertions.Fail.fail;
-
-public class DefaultIssueClientTest {
-  @Rule
-  public MockHttpServerInterceptor httpServer = new MockHttpServerInterceptor();
-
-  @Test
-  public void should_find_issues() {
-    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
-    httpServer.doReturnBody("{\"issues\": [{\"key\": \"ABCDE\"}]}");
-
-    IssueClient client = new DefaultIssueClient(requestFactory);
-    IssueQuery query = IssueQuery.create().issues("ABCDE");
-    Issues issues = client.find(query);
-
-    assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/search?issues=ABCDE");
-    assertThat(issues.list()).hasSize(1);
-    assertThat(issues.list().get(0).key()).isEqualTo("ABCDE");
-  }
-
-  @Test
-  public void should_fail_to_find_issues() {
-    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
-    httpServer.doReturnStatus(500);
-
-    IssueClient client = new DefaultIssueClient(requestFactory);
-    try {
-      client.find(IssueQuery.create());
-      fail();
-    } catch (IllegalStateException e) {
-      assertThat(e).hasMessage("Fail to search for issues. Bad HTTP response status: 500");
-    }
-  }
-
-  @Test
-  public void should_set_severity() {
-    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
-    httpServer.doReturnBody("{\"issue\": {\"key\": \"ABCDE\"}}");
-
-    IssueClient client = new DefaultIssueClient(requestFactory);
-    Issue result = client.setSeverity("ABCDE", "BLOCKER");
-
-    assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/set_severity?issue=ABCDE&severity=BLOCKER");
-    assertThat(result).isNotNull();
-  }
-
-  @Test
-  public void should_assign() {
-    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
-    httpServer.doReturnBody("{\"issue\": {\"key\": \"ABCDE\"}}");
-
-    IssueClient client = new DefaultIssueClient(requestFactory);
-    Issue result = client.assign("ABCDE", "emmerik");
-
-    assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/assign?issue=ABCDE&assignee=emmerik");
-    assertThat(result).isNotNull();
-  }
-
-  @Test
-  public void should_unassign() {
-    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
-    httpServer.doReturnBody("{\"issue\": {\"key\": \"ABCDE\"}}");
-
-    IssueClient client = new DefaultIssueClient(requestFactory);
-    Issue result = client.assign("ABCDE", null);
-
-    assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/assign?issue=ABCDE");
-    assertThat(result).isNotNull();
-  }
-
-  @Test
-  public void should_plan() {
-    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
-    httpServer.doReturnBody("{\"issue\": {\"key\": \"ABCDE\"}}");
-
-    IssueClient client = new DefaultIssueClient(requestFactory);
-    Issue result = client.plan("ABCDE", "DEFGH");
-
-    assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/plan?issue=ABCDE&plan=DEFGH");
-    assertThat(result).isNotNull();
-  }
-
-  @Test
-  public void should_unplan() {
-    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
-    httpServer.doReturnBody("{\"issue\": {\"key\": \"ABCDE\"}}");
-
-    IssueClient client = new DefaultIssueClient(requestFactory);
-    Issue result = client.plan("ABCDE", null);
-
-    assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/plan?issue=ABCDE");
-    assertThat(result).isNotNull();
-  }
-
-  @Test
-  public void should_create_issue() {
-    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
-    httpServer.doReturnBody("{\"issue\": {\"key\": \"ABCDE\"}}");
-
-    IssueClient client = new DefaultIssueClient(requestFactory);
-    Issue result = client.create(NewIssue.create().component("Action.java").rule("squid:AvoidCycle"));
-
-    assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/create?component=Action.java&rule=squid:AvoidCycle");
-    assertThat(result).isNotNull();
-  }
-
-  @Test
-  public void should_get_transitions() {
-    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
-    httpServer.doReturnBody("{\n" +
-      "  \"transitions\": [\n" +
-      "    \"resolve\",\n" +
-      "    \"falsepositive\"\n" +
-      "  ]\n" +
-      "}");
-
-    IssueClient client = new DefaultIssueClient(requestFactory);
-    List<String> transitions = client.transitions("ABCDE");
-
-    assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/transitions?issue=ABCDE");
-    assertThat(transitions).hasSize(2);
-    assertThat(transitions).containsOnly("resolve", "falsepositive");
-  }
-
-  @Test
-  public void should_apply_transition() {
-    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
-    httpServer.doReturnBody("{\"issue\": {\"key\": \"ABCDE\"}}");
-
-    IssueClient client = new DefaultIssueClient(requestFactory);
-    Issue result = client.doTransition("ABCDE", "resolve");
-
-    assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/do_transition?issue=ABCDE&transition=resolve");
-    assertThat(result).isNotNull();
-  }
-
-  @Test
-  public void should_add_comment() throws Exception {
-    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
-    httpServer.doReturnBody(IOUtils.toString(getClass().getResourceAsStream("/org/sonar/wsclient/issue/DefaultIssueClientTest/add_comment_result.json")));
-
-    IssueClient client = new DefaultIssueClient(requestFactory);
-    IssueComment comment = client.addComment("ISSUE-1", "this is my comment");
-
-    assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/add_comment?issue=ISSUE-1&text=this%20is%20my%20comment");
-    assertThat(comment).isNotNull();
-    assertThat(comment.key()).isEqualTo("COMMENT-123");
-    assertThat(comment.htmlText()).isEqualTo("this is my comment");
-    assertThat(comment.login()).isEqualTo("admin");
-    assertThat(comment.createdAt().getDate()).isEqualTo(18);
-  }
-
-  @Test
-  public void should_get_actions() {
-    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
-    httpServer.doReturnBody("{\n" +
-      "  \"actions\": [\n" +
-      "    \"link-to-jira\",\n" +
-      "    \"tweet\"\n" +
-      "  ]\n" +
-      "}");
-
-    IssueClient client = new DefaultIssueClient(requestFactory);
-    List<String> actions = client.actions("ABCDE");
-
-    assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/actions?issue=ABCDE");
-    assertThat(actions).hasSize(2);
-    assertThat(actions).containsOnly("link-to-jira", "tweet");
-  }
-
-  @Test
-  public void should_apply_action() {
-    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
-    httpServer.doReturnBody("{\"issue\": {\"key\": \"ABCDE\"}}");
-
-    IssueClient client = new DefaultIssueClient(requestFactory);
-    Issue result = client.doAction("ABCDE", "tweet");
-
-    assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/do_action?issue=ABCDE&actionKey=tweet");
-    assertThat(result).isNotNull();
-  }
-}
diff --git a/sonar-ws-client/src/test/java/org/sonar/wsclient/issue/IssueJsonParserTest.java b/sonar-ws-client/src/test/java/org/sonar/wsclient/issue/IssueJsonParserTest.java
deleted file mode 100644 (file)
index 09cb394..0000000
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 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.wsclient.issue;
-
-import org.apache.commons.io.IOUtils;
-import org.junit.Test;
-import org.sonar.wsclient.component.Component;
-import org.sonar.wsclient.user.User;
-
-import java.util.List;
-
-import static org.fest.assertions.Assertions.assertThat;
-
-public class IssueJsonParserTest {
-  @Test
-  public void test_GET_search() throws Exception {
-    String json = IOUtils.toString(getClass().getResourceAsStream("/org/sonar/wsclient/issue/IssueParserTest/search.json"));
-    Issues issues = new IssueJsonParser().parseIssues(json);
-    assertThat(issues).isNotNull();
-    List<Issue> list = issues.list();
-    assertThat(list).hasSize(2);
-    Issue first = list.get(0);
-    assertThat(first.key()).isEqualTo("ABCDE");
-    assertThat(first.componentKey()).isEqualTo("Action.java");
-    assertThat(first.projectKey()).isEqualTo("struts");
-    assertThat(first.ruleKey()).isEqualTo("squid:CycleBetweenPackages");
-    assertThat(first.severity()).isEqualTo("CRITICAL");
-    assertThat(first.line()).isEqualTo(10);
-    assertThat(first.resolution()).isEqualTo("FIXED");
-    assertThat(first.status()).isEqualTo("OPEN");
-    assertThat(first.assignee()).isEqualTo("karadoc");
-    assertThat(first.message()).isEqualTo("the message");
-    assertThat(first.effortToFix()).isEqualTo(4.2);
-    assertThat(first.reporter()).isEqualTo("perceval");
-    assertThat(first.author()).isEqualTo("pirlouis");
-    assertThat(first.actionPlan()).isEqualTo("9450b10c-e725-48b8-bf01-acdec751c491");
-    assertThat(first.creationDate()).isNotNull();
-    assertThat(first.updateDate()).isNotNull();
-    assertThat(first.closeDate()).isNotNull();
-    assertThat(first.attribute("JIRA")).isEqualTo("FOO-1234");
-    assertThat(first.attribute("OTHER")).isNull();
-    assertThat(first.attributes()).hasSize(1);
-    assertThat(first.comments()).isEmpty();
-
-    Issue second = list.get(1);
-    assertThat(second.key()).isEqualTo("FGHIJ");
-    assertThat(second.line()).isNull();
-    assertThat(second.effortToFix()).isNull();
-    assertThat(second.reporter()).isNull();
-    assertThat(second.author()).isNull();
-    assertThat(second.attribute("JIRA")).isNull();
-    assertThat(second.attributes()).isEmpty();
-    assertThat(second.comments()).isEmpty();
-
-    assertThat(issues.rules()).hasSize(2);
-    assertThat(issues.rule(first).key()).isEqualTo("squid:CycleBetweenPackages");
-    assertThat(issues.rule(first).name()).isEqualTo("Avoid cycle between java packages");
-    assertThat(issues.rule(first).description()).contains("When several packages");
-
-    assertThat(issues.paging()).isNotNull();
-    Paging paging = issues.paging();
-    assertThat(paging.pageIndex()).isEqualTo(1);
-    assertThat(paging.pageSize()).isEqualTo(100);
-    assertThat(paging.pages()).isEqualTo(1);
-    assertThat(paging.total()).isEqualTo(2);
-
-    assertThat(issues.maxResultsReached()).isTrue();
-  }
-
-  @Test
-  public void test_GET_empty_search() throws Exception {
-    String json = IOUtils.toString(getClass().getResourceAsStream("/org/sonar/wsclient/issue/IssueParserTest/empty.json"));
-    Issues issues = new IssueJsonParser().parseIssues(json);
-    assertThat(issues).isNotNull();
-    assertThat(issues.list()).isEmpty();
-    assertThat(issues.rules()).isEmpty();
-    assertThat(issues.maxResultsReached()).isFalse();
-  }
-
-  @Test
-  public void test_GET_transitions() throws Exception {
-    String json = IOUtils.toString(getClass().getResourceAsStream("/org/sonar/wsclient/issue/IssueParserTest/getTransitions.json"));
-    List<String> transitions = new IssueJsonParser().parseTransitions(json);
-
-    assertThat(transitions).isNotNull();
-    assertThat(transitions).hasSize(2);
-    assertThat(transitions).containsOnly("resolve", "falsepositive");
-  }
-
-  @Test
-  public void should_parse_comments() throws Exception {
-    String json = IOUtils.toString(getClass().getResourceAsStream("/org/sonar/wsclient/issue/IssueParserTest/issue-with-comments.json"));
-    Issues issues = new IssueJsonParser().parseIssues(json);
-    assertThat(issues.size()).isEqualTo(1);
-
-    Issue issue = issues.list().get(0);
-    assertThat(issue.comments()).hasSize(2);
-
-    IssueComment firstComment = issue.comments().get(0);
-    assertThat(firstComment.key()).isEqualTo("COMMENT-1");
-    assertThat(firstComment.login()).isEqualTo("morgan");
-    assertThat(firstComment.htmlText()).isEqualTo("the first comment");
-    assertThat(firstComment.createdAt().getDate()).isEqualTo(18);
-
-    IssueComment secondComment = issue.comments().get(1);
-    assertThat(secondComment.key()).isEqualTo("COMMENT-2");
-    assertThat(secondComment.login()).isEqualTo("arthur");
-    assertThat(secondComment.htmlText()).isEqualTo("the second comment");
-    assertThat(secondComment.createdAt().getDate()).isEqualTo(19);
-  }
-
-  @Test
-  public void should_parse_users() throws Exception {
-    String json = IOUtils.toString(getClass().getResourceAsStream("/org/sonar/wsclient/issue/IssueParserTest/issue-with-users.json"));
-    Issues issues = new IssueJsonParser().parseIssues(json);
-
-    assertThat(issues.users()).hasSize(2);
-
-    User morgan = issues.user("morgan");
-    assertThat(morgan.login()).isEqualTo("morgan");
-    assertThat(morgan.name()).isEqualTo("Morgan");
-    assertThat(morgan.active()).isTrue();
-    assertThat(morgan.email()).isEqualTo("mor@gan.bzh");
-
-    User arthur = issues.user("arthur");
-    assertThat(arthur.login()).isEqualTo("arthur");
-    assertThat(arthur.name()).isEqualTo("Arthur");
-    assertThat(arthur.active()).isFalse();
-    assertThat(arthur.email()).isEqualTo("ar@thur.bzh");
-  }
-
-  @Test
-  public void should_parse_components() throws Exception {
-    String json = IOUtils.toString(getClass().getResourceAsStream("/org/sonar/wsclient/issue/IssueParserTest/issue-with-components.json"));
-    Issues issues = new IssueJsonParser().parseIssues(json);
-
-    assertThat(issues.components()).hasSize(1);
-
-    Component component = issues.component(issues.list().get(0));
-    assertThat(component.key()).isEqualTo("struts:Action.java");
-    assertThat(component.qualifier()).isEqualTo("CLA");
-    assertThat(component.name()).isEqualTo("Action");
-    assertThat(component.longName()).isEqualTo("org.struts.Action");
-  }
-
-  @Test
-  public void should_parse_projects() throws Exception {
-    String json = IOUtils.toString(getClass().getResourceAsStream("/org/sonar/wsclient/issue/IssueParserTest/issue-with-projects.json"));
-    Issues issues = new IssueJsonParser().parseIssues(json);
-
-    assertThat(issues.projects()).hasSize(1);
-
-    Component component = issues.project(issues.list().get(0));
-    assertThat(component.key()).isEqualTo("struts");
-    assertThat(component.qualifier()).isEqualTo("TRK");
-    assertThat(component.name()).isEqualTo("Struts");
-    assertThat(component.longName()).isEqualTo("org.struts");
-  }
-
-  @Test
-  public void should_parse_action_plans() throws Exception {
-    String json = IOUtils.toString(getClass().getResourceAsStream("/org/sonar/wsclient/issue/IssueParserTest/issue-with-action-plans.json"));
-    Issues issues = new IssueJsonParser().parseIssues(json);
-
-    assertThat(issues.actionPlans()).hasSize(1);
-
-    ActionPlan actionPlan = issues.actionPlans(issues.list().get(0));
-    assertThat(actionPlan.key()).isEqualTo("9450b10c-e725-48b8-bf01-acdec751c491");
-    assertThat(actionPlan.name()).isEqualTo("3.6");
-    assertThat(actionPlan.status()).isEqualTo("OPEN");
-    assertThat(actionPlan.project()).isEqualTo("struts");
-    assertThat(actionPlan.deadLine().getTime()).isEqualTo(1369951200000l);
-    assertThat(actionPlan.createdAt().getTime()).isEqualTo(1369828520000l);
-    assertThat(actionPlan.updatedAt().getTime()).isEqualTo(1369828520000l);
-  }
-}
index bd1ddc18e01de8cf03d3988e970e13cab258257a..941894aee783747d90c0b451610f25ee4b3dff25 100644 (file)
@@ -21,6 +21,9 @@ package org.sonar.wsclient.issue;
 
 import org.junit.Test;
 
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+
 import static org.fest.assertions.Assertions.assertThat;
 import static org.fest.assertions.MapAssert.entry;
 
@@ -32,7 +35,8 @@ public class IssueQueryTest {
   }
 
   @Test
-  public void get_all_issues_by_parameter() {
+  public void get_all_issues_by_parameter() throws ParseException {
+    SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm");
     IssueQuery query = IssueQuery.create()
       .issues("ABCDE", "FGHIJ")
       .assignees("arthur", "perceval")
@@ -47,12 +51,14 @@ public class IssueQueryTest {
       .statuses("OPEN", "CLOSED")
       .severities("BLOCKER", "INFO")
       .reporters("login1", "login2")
+      .createdBefore(df.parse("2015-12-13T05:59"))
+      .createdAfter(df.parse("2012-01-23T13:40"))
       .sort("ASSIGNEE")
       .asc(false)
       .pageSize(5)
       .pageIndex(4);
 
-    assertThat(query.urlParams()).hasSize(17);
+    assertThat(query.urlParams()).hasSize(19);
     assertThat(query.urlParams()).includes(entry("issues", "ABCDE,FGHIJ"));
     assertThat(query.urlParams()).includes(entry("assignees", "arthur,perceval"));
     assertThat(query.urlParams()).includes(entry("assigned", true));
@@ -66,6 +72,8 @@ public class IssueQueryTest {
     assertThat(query.urlParams()).includes(entry("statuses", "OPEN,CLOSED"));
     assertThat(query.urlParams()).includes(entry("severities", "BLOCKER,INFO"));
     assertThat(query.urlParams()).includes(entry("reporters", "login1,login2"));
+    assertThat((String)query.urlParams().get("createdBefore")).startsWith("2015-12-13T05:59");
+    assertThat((String)query.urlParams().get("createdAfter")).startsWith("2012-01-23T13:40:00");
     assertThat(query.urlParams()).includes(entry("sort", "ASSIGNEE"));
     assertThat(query.urlParams()).includes(entry("asc", false));
     assertThat(query.urlParams()).includes(entry("pageSize", 5));
diff --git a/sonar-ws-client/src/test/java/org/sonar/wsclient/issue/internal/DefaultActionPlanClientTest.java b/sonar-ws-client/src/test/java/org/sonar/wsclient/issue/internal/DefaultActionPlanClientTest.java
new file mode 100644 (file)
index 0000000..2e74730
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.wsclient.issue.internal;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.wsclient.MockHttpServerInterceptor;
+import org.sonar.wsclient.base.HttpException;
+import org.sonar.wsclient.internal.HttpRequestFactory;
+import org.sonar.wsclient.issue.ActionPlan;
+import org.sonar.wsclient.issue.ActionPlanClient;
+import org.sonar.wsclient.issue.NewActionPlan;
+import org.sonar.wsclient.issue.UpdateActionPlan;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
+
+public class DefaultActionPlanClientTest {
+
+  @Rule
+  public MockHttpServerInterceptor httpServer = new MockHttpServerInterceptor();
+
+  @Test
+  public void should_find_action_plans() {
+    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
+    httpServer.doReturnBody("{\"actionPlans\": [{\"key\": \"382f6f2e-ad9d-424a-b973-9b065e04348a\",\n" +
+      "\"name\": \"Long term\",\n" +
+      "\"status\": \"CLOSED\",\n" +
+      "\"project\": \"com.sonarsource.it.samples:simple-sample\",\n" +
+      "\"userLogin\": \"admin\",\n" +
+      "\"deadLine\": \"2013-05-30T00:00:00+0200\",\n" +
+      "\"totalIssues\": 3,\n" +
+      "\"unresolvedIssues\": 2,\n" +
+      "\"createdAt\": \"2013-05-13T12:50:29+0200\",\n" +
+      "\"updatedAt\": \"2013-05-13T12:50:44+0200\"}]}");
+
+    ActionPlanClient client = new DefaultActionPlanClient(requestFactory);
+    List<ActionPlan> actionPlans = client.find("com.sonarsource.it.samples:simple-sample");
+
+    assertThat(httpServer.requestedPath()).isEqualTo("/api/action_plans/search?project=com.sonarsource.it.samples:simple-sample");
+    assertThat(actionPlans).hasSize(1);
+    ActionPlan actionPlan = actionPlans.get(0);
+    assertThat(actionPlan.key()).isEqualTo("382f6f2e-ad9d-424a-b973-9b065e04348a");
+    assertThat(actionPlan.name()).isEqualTo("Long term");
+    assertThat(actionPlan.project()).isEqualTo("com.sonarsource.it.samples:simple-sample");
+    assertThat(actionPlan.status()).isEqualTo("CLOSED");
+    assertThat(actionPlan.userLogin()).isEqualTo("admin");
+    assertThat(actionPlan.deadLine()).isNotNull();
+    assertThat(actionPlan.totalIssues()).isEqualTo(3);
+    assertThat(actionPlan.unresolvedIssues()).isEqualTo(2);
+    assertThat(actionPlan.createdAt()).isNotNull();
+    assertThat(actionPlan.updatedAt()).isNotNull();
+  }
+
+  @Test
+  public void should_create_action_plan() throws Exception {
+    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
+    httpServer.doReturnBody("{\"actionPlan\": {\"key\": \"382f6f2e-ad9d-424a-b973-9b065e04348a\"}}");
+
+    ActionPlanClient client = new DefaultActionPlanClient(requestFactory);
+    ActionPlan result = client.create(
+      NewActionPlan.create().name("Short term").project("org.sonar.Sample").description("Short term issues").deadLine(stringToDate("2014-01-01")));
+
+    assertThat(httpServer.requestedPath()).isEqualTo("/api/action_plans/create?project=org.sonar.Sample&description=Short%20term%20issues&name=Short%20term&deadLine=2014-01-01");
+    assertThat(result).isNotNull();
+  }
+
+  @Test
+  public void should_update_action_plan() {
+    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
+    httpServer.doReturnBody("{\"actionPlan\": {\"key\": \"382f6f2e-ad9d-424a-b973-9b065e04348a\"}}");
+
+    ActionPlanClient client = new DefaultActionPlanClient(requestFactory);
+    ActionPlan result = client.update(
+      UpdateActionPlan.create().key("382f6f2e-ad9d-424a-b973-9b065e04348a").name("Short term").description("Short term issues").deadLine(stringToDate("2014-01-01")));
+
+    assertThat(httpServer.requestedPath()).isEqualTo("/api/action_plans/update?description=Short%20term%20issues&name=Short%20term&deadLine=2014-01-01&key=382f6f2e-ad9d-424a-b973-9b065e04348a");
+    assertThat(result).isNotNull();
+  }
+
+  @Test
+  public void should_delete_action_plan() {
+    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
+
+    ActionPlanClient client = new DefaultActionPlanClient(requestFactory);
+    client.delete("382f6f2e-ad9d-424a-b973-9b065e04348a");
+
+    assertThat(httpServer.requestedPath()).isEqualTo("/api/action_plans/delete?key=382f6f2e-ad9d-424a-b973-9b065e04348a");
+  }
+
+  @Test
+  public void should_fail_to_delete_action_plan() {
+    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
+    httpServer.doReturnStatus(500);
+
+    ActionPlanClient client = new DefaultActionPlanClient(requestFactory);
+    try {
+      client.delete("382f6f2e-ad9d-424a-b973-9b065e04348a");
+      fail();
+    } catch (HttpException e) {
+      assertThat(e.status()).isEqualTo(500);
+      assertThat(e.url()).startsWith("http://localhost");
+      assertThat(e.url()).endsWith("/api/action_plans/delete?key=382f6f2e-ad9d-424a-b973-9b065e04348a");
+    }
+  }
+
+  @Test
+  public void should_open_action_plan() {
+    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
+    httpServer.doReturnBody("{\"actionPlan\": {\"key\": \"382f6f2e-ad9d-424a-b973-9b065e04348a\"}}");
+
+    ActionPlanClient client = new DefaultActionPlanClient(requestFactory);
+    ActionPlan result = client.open("382f6f2e-ad9d-424a-b973-9b065e04348a");
+
+    assertThat(httpServer.requestedPath()).isEqualTo("/api/action_plans/open?key=382f6f2e-ad9d-424a-b973-9b065e04348a");
+    assertThat(result).isNotNull();
+  }
+
+  @Test
+  public void should_close_action_plan() {
+    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
+    httpServer.doReturnBody("{\"actionPlan\": {\"key\": \"382f6f2e-ad9d-424a-b973-9b065e04348a\"}}");
+
+    ActionPlanClient client = new DefaultActionPlanClient(requestFactory);
+    ActionPlan result = client.close("382f6f2e-ad9d-424a-b973-9b065e04348a");
+
+    assertThat(httpServer.requestedPath()).isEqualTo("/api/action_plans/close?key=382f6f2e-ad9d-424a-b973-9b065e04348a");
+    assertThat(result).isNotNull();
+  }
+
+  private static Date stringToDate(String sDate) {
+    try {
+      SimpleDateFormat sdf = new SimpleDateFormat("yyyy-dd-MM");
+      return sdf.parse(sDate);
+    } catch (ParseException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+}
diff --git a/sonar-ws-client/src/test/java/org/sonar/wsclient/issue/internal/DefaultIssueClientTest.java b/sonar-ws-client/src/test/java/org/sonar/wsclient/issue/internal/DefaultIssueClientTest.java
new file mode 100644 (file)
index 0000000..d262073
--- /dev/null
@@ -0,0 +1,217 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.wsclient.issue.internal;
+
+import org.apache.commons.io.IOUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.wsclient.MockHttpServerInterceptor;
+import org.sonar.wsclient.base.HttpException;
+import org.sonar.wsclient.internal.HttpRequestFactory;
+import org.sonar.wsclient.issue.*;
+import org.sonar.wsclient.issue.internal.DefaultIssueClient;
+
+import java.util.List;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
+
+public class DefaultIssueClientTest {
+  @Rule
+  public MockHttpServerInterceptor httpServer = new MockHttpServerInterceptor();
+
+  @Test
+  public void should_find_issues() {
+    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
+    httpServer.doReturnBody("{\"issues\": [{\"key\": \"ABCDE\"}]}");
+
+    IssueClient client = new DefaultIssueClient(requestFactory);
+    IssueQuery query = IssueQuery.create().issues("ABCDE");
+    Issues issues = client.find(query);
+
+    assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/search?issues=ABCDE");
+    assertThat(issues.list()).hasSize(1);
+    assertThat(issues.list().get(0).key()).isEqualTo("ABCDE");
+  }
+
+  @Test
+  public void should_fail_to_find_issues() {
+    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
+    httpServer.doReturnStatus(500);
+
+    IssueClient client = new DefaultIssueClient(requestFactory);
+    try {
+      client.find(IssueQuery.create());
+      fail();
+    } catch (HttpException e) {
+      assertThat(e.status()).isEqualTo(500);
+      assertThat(e.url()).startsWith("http://localhost");
+      assertThat(e.url()).endsWith("/api/issues/search");
+    }
+  }
+
+  @Test
+  public void should_set_severity() {
+    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
+    httpServer.doReturnBody("{\"issue\": {\"key\": \"ABCDE\"}}");
+
+    IssueClient client = new DefaultIssueClient(requestFactory);
+    Issue result = client.setSeverity("ABCDE", "BLOCKER");
+
+    assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/set_severity?issue=ABCDE&severity=BLOCKER");
+    assertThat(result).isNotNull();
+  }
+
+  @Test
+  public void should_assign() {
+    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
+    httpServer.doReturnBody("{\"issue\": {\"key\": \"ABCDE\"}}");
+
+    IssueClient client = new DefaultIssueClient(requestFactory);
+    Issue result = client.assign("ABCDE", "emmerik");
+
+    assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/assign?issue=ABCDE&assignee=emmerik");
+    assertThat(result).isNotNull();
+  }
+
+  @Test
+  public void should_unassign() {
+    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
+    httpServer.doReturnBody("{\"issue\": {\"key\": \"ABCDE\"}}");
+
+    IssueClient client = new DefaultIssueClient(requestFactory);
+    Issue result = client.assign("ABCDE", null);
+
+    assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/assign?issue=ABCDE");
+    assertThat(result).isNotNull();
+  }
+
+  @Test
+  public void should_plan() {
+    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
+    httpServer.doReturnBody("{\"issue\": {\"key\": \"ABCDE\"}}");
+
+    IssueClient client = new DefaultIssueClient(requestFactory);
+    Issue result = client.plan("ABCDE", "DEFGH");
+
+    assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/plan?issue=ABCDE&plan=DEFGH");
+    assertThat(result).isNotNull();
+  }
+
+  @Test
+  public void should_unplan() {
+    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
+    httpServer.doReturnBody("{\"issue\": {\"key\": \"ABCDE\"}}");
+
+    IssueClient client = new DefaultIssueClient(requestFactory);
+    Issue result = client.plan("ABCDE", null);
+
+    assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/plan?issue=ABCDE");
+    assertThat(result).isNotNull();
+  }
+
+  @Test
+  public void should_create_issue() {
+    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
+    httpServer.doReturnBody("{\"issue\": {\"key\": \"ABCDE\"}}");
+
+    IssueClient client = new DefaultIssueClient(requestFactory);
+    Issue result = client.create(NewIssue.create().component("Action.java").rule("squid:AvoidCycle"));
+
+    assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/create?component=Action.java&rule=squid:AvoidCycle");
+    assertThat(result).isNotNull();
+  }
+
+  @Test
+  public void should_get_transitions() {
+    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
+    httpServer.doReturnBody("{\n" +
+      "  \"transitions\": [\n" +
+      "    \"resolve\",\n" +
+      "    \"falsepositive\"\n" +
+      "  ]\n" +
+      "}");
+
+    IssueClient client = new DefaultIssueClient(requestFactory);
+    List<String> transitions = client.transitions("ABCDE");
+
+    assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/transitions?issue=ABCDE");
+    assertThat(transitions).hasSize(2);
+    assertThat(transitions).containsOnly("resolve", "falsepositive");
+  }
+
+  @Test
+  public void should_apply_transition() {
+    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
+    httpServer.doReturnBody("{\"issue\": {\"key\": \"ABCDE\"}}");
+
+    IssueClient client = new DefaultIssueClient(requestFactory);
+    Issue result = client.doTransition("ABCDE", "resolve");
+
+    assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/do_transition?issue=ABCDE&transition=resolve");
+    assertThat(result).isNotNull();
+  }
+
+  @Test
+  public void should_add_comment() throws Exception {
+    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
+    httpServer.doReturnBody(IOUtils.toString(getClass().getResourceAsStream("/org/sonar/wsclient/issue/internal/DefaultIssueClientTest/add_comment_result.json")));
+
+    IssueClient client = new DefaultIssueClient(requestFactory);
+    IssueComment comment = client.addComment("ISSUE-1", "this is my comment");
+
+    assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/add_comment?issue=ISSUE-1&text=this%20is%20my%20comment");
+    assertThat(comment).isNotNull();
+    assertThat(comment.key()).isEqualTo("COMMENT-123");
+    assertThat(comment.htmlText()).isEqualTo("this is my comment");
+    assertThat(comment.login()).isEqualTo("admin");
+    assertThat(comment.createdAt().getDate()).isEqualTo(18);
+  }
+
+  @Test
+  public void should_get_actions() {
+    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
+    httpServer.doReturnBody("{\n" +
+      "  \"actions\": [\n" +
+      "    \"link-to-jira\",\n" +
+      "    \"tweet\"\n" +
+      "  ]\n" +
+      "}");
+
+    IssueClient client = new DefaultIssueClient(requestFactory);
+    List<String> actions = client.actions("ABCDE");
+
+    assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/actions?issue=ABCDE");
+    assertThat(actions).hasSize(2);
+    assertThat(actions).containsOnly("link-to-jira", "tweet");
+  }
+
+  @Test
+  public void should_apply_action() {
+    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url());
+    httpServer.doReturnBody("{\"issue\": {\"key\": \"ABCDE\"}}");
+
+    IssueClient client = new DefaultIssueClient(requestFactory);
+    Issue result = client.doAction("ABCDE", "tweet");
+
+    assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/do_action?issue=ABCDE&actionKey=tweet");
+    assertThat(result).isNotNull();
+  }
+}
diff --git a/sonar-ws-client/src/test/java/org/sonar/wsclient/issue/internal/IssueJsonParserTest.java b/sonar-ws-client/src/test/java/org/sonar/wsclient/issue/internal/IssueJsonParserTest.java
new file mode 100644 (file)
index 0000000..c84bfe8
--- /dev/null
@@ -0,0 +1,199 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 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.wsclient.issue.internal;
+
+import org.apache.commons.io.IOUtils;
+import org.junit.Test;
+import org.sonar.wsclient.base.Paging;
+import org.sonar.wsclient.component.Component;
+import org.sonar.wsclient.issue.ActionPlan;
+import org.sonar.wsclient.issue.Issue;
+import org.sonar.wsclient.issue.IssueComment;
+import org.sonar.wsclient.issue.Issues;
+import org.sonar.wsclient.issue.internal.IssueJsonParser;
+import org.sonar.wsclient.user.User;
+
+import java.util.List;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class IssueJsonParserTest {
+  @Test
+  public void test_GET_search() throws Exception {
+    String json = IOUtils.toString(getClass().getResourceAsStream("/org/sonar/wsclient/issue/internal/IssueJsonParserTest/search.json"));
+    Issues issues = new IssueJsonParser().parseIssues(json);
+    assertThat(issues).isNotNull();
+    List<Issue> list = issues.list();
+    assertThat(list).hasSize(2);
+    Issue first = list.get(0);
+    assertThat(first.key()).isEqualTo("ABCDE");
+    assertThat(first.componentKey()).isEqualTo("Action.java");
+    assertThat(first.projectKey()).isEqualTo("struts");
+    assertThat(first.ruleKey()).isEqualTo("squid:CycleBetweenPackages");
+    assertThat(first.severity()).isEqualTo("CRITICAL");
+    assertThat(first.line()).isEqualTo(10);
+    assertThat(first.resolution()).isEqualTo("FIXED");
+    assertThat(first.status()).isEqualTo("OPEN");
+    assertThat(first.assignee()).isEqualTo("karadoc");
+    assertThat(first.message()).isEqualTo("the message");
+    assertThat(first.effortToFix()).isEqualTo(4.2);
+    assertThat(first.reporter()).isEqualTo("perceval");
+    assertThat(first.author()).isEqualTo("pirlouis");
+    assertThat(first.actionPlan()).isEqualTo("9450b10c-e725-48b8-bf01-acdec751c491");
+    assertThat(first.creationDate()).isNotNull();
+    assertThat(first.updateDate()).isNotNull();
+    assertThat(first.closeDate()).isNotNull();
+    assertThat(first.attribute("JIRA")).isEqualTo("FOO-1234");
+    assertThat(first.attribute("OTHER")).isNull();
+    assertThat(first.attributes()).hasSize(1);
+    assertThat(first.comments()).isEmpty();
+
+    Issue second = list.get(1);
+    assertThat(second.key()).isEqualTo("FGHIJ");
+    assertThat(second.line()).isNull();
+    assertThat(second.effortToFix()).isNull();
+    assertThat(second.reporter()).isNull();
+    assertThat(second.author()).isNull();
+    assertThat(second.attribute("JIRA")).isNull();
+    assertThat(second.attributes()).isEmpty();
+    assertThat(second.comments()).isEmpty();
+
+    assertThat(issues.rules()).hasSize(2);
+    assertThat(issues.rule(first).key()).isEqualTo("squid:CycleBetweenPackages");
+    assertThat(issues.rule(first).name()).isEqualTo("Avoid cycle between java packages");
+    assertThat(issues.rule(first).description()).contains("When several packages");
+
+    assertThat(issues.paging()).isNotNull();
+    Paging paging = issues.paging();
+    assertThat(paging.pageIndex()).isEqualTo(1);
+    assertThat(paging.pageSize()).isEqualTo(100);
+    assertThat(paging.pages()).isEqualTo(1);
+    assertThat(paging.total()).isEqualTo(2);
+
+    assertThat(issues.maxResultsReached()).isTrue();
+  }
+
+  @Test
+  public void test_GET_empty_search() throws Exception {
+    String json = IOUtils.toString(getClass().getResourceAsStream("/org/sonar/wsclient/issue/internal/IssueJsonParserTest/empty.json"));
+    Issues issues = new IssueJsonParser().parseIssues(json);
+    assertThat(issues).isNotNull();
+    assertThat(issues.list()).isEmpty();
+    assertThat(issues.rules()).isEmpty();
+    assertThat(issues.maxResultsReached()).isFalse();
+  }
+
+  @Test
+  public void test_GET_transitions() throws Exception {
+    String json = IOUtils.toString(getClass().getResourceAsStream("/org/sonar/wsclient/issue/internal/IssueJsonParserTest/getTransitions.json"));
+    List<String> transitions = new IssueJsonParser().parseTransitions(json);
+
+    assertThat(transitions).isNotNull();
+    assertThat(transitions).hasSize(2);
+    assertThat(transitions).containsOnly("resolve", "falsepositive");
+  }
+
+  @Test
+  public void should_parse_comments() throws Exception {
+    String json = IOUtils.toString(getClass().getResourceAsStream("/org/sonar/wsclient/issue/internal/IssueJsonParserTest/issue-with-comments.json"));
+    Issues issues = new IssueJsonParser().parseIssues(json);
+    assertThat(issues.size()).isEqualTo(1);
+
+    Issue issue = issues.list().get(0);
+    assertThat(issue.comments()).hasSize(2);
+
+    IssueComment firstComment = issue.comments().get(0);
+    assertThat(firstComment.key()).isEqualTo("COMMENT-1");
+    assertThat(firstComment.login()).isEqualTo("morgan");
+    assertThat(firstComment.htmlText()).isEqualTo("the first comment");
+    assertThat(firstComment.createdAt().getDate()).isEqualTo(18);
+
+    IssueComment secondComment = issue.comments().get(1);
+    assertThat(secondComment.key()).isEqualTo("COMMENT-2");
+    assertThat(secondComment.login()).isEqualTo("arthur");
+    assertThat(secondComment.htmlText()).isEqualTo("the second comment");
+    assertThat(secondComment.createdAt().getDate()).isEqualTo(19);
+  }
+
+  @Test
+  public void should_parse_users() throws Exception {
+    String json = IOUtils.toString(getClass().getResourceAsStream("/org/sonar/wsclient/issue/internal/IssueJsonParserTest/issue-with-users.json"));
+    Issues issues = new IssueJsonParser().parseIssues(json);
+
+    assertThat(issues.users()).hasSize(2);
+
+    User morgan = issues.user("morgan");
+    assertThat(morgan.login()).isEqualTo("morgan");
+    assertThat(morgan.name()).isEqualTo("Morgan");
+    assertThat(morgan.active()).isTrue();
+    assertThat(morgan.email()).isEqualTo("mor@gan.bzh");
+
+    User arthur = issues.user("arthur");
+    assertThat(arthur.login()).isEqualTo("arthur");
+    assertThat(arthur.name()).isEqualTo("Arthur");
+    assertThat(arthur.active()).isFalse();
+    assertThat(arthur.email()).isEqualTo("ar@thur.bzh");
+  }
+
+  @Test
+  public void should_parse_components() throws Exception {
+    String json = IOUtils.toString(getClass().getResourceAsStream("/org/sonar/wsclient/issue/internal/IssueJsonParserTest/issue-with-components.json"));
+    Issues issues = new IssueJsonParser().parseIssues(json);
+
+    assertThat(issues.components()).hasSize(1);
+
+    Component component = issues.component(issues.list().get(0));
+    assertThat(component.key()).isEqualTo("struts:Action.java");
+    assertThat(component.qualifier()).isEqualTo("CLA");
+    assertThat(component.name()).isEqualTo("Action");
+    assertThat(component.longName()).isEqualTo("org.struts.Action");
+  }
+
+  @Test
+  public void should_parse_projects() throws Exception {
+    String json = IOUtils.toString(getClass().getResourceAsStream("/org/sonar/wsclient/issue/internal/IssueJsonParserTest/issue-with-projects.json"));
+    Issues issues = new IssueJsonParser().parseIssues(json);
+
+    assertThat(issues.projects()).hasSize(1);
+
+    Component component = issues.project(issues.list().get(0));
+    assertThat(component.key()).isEqualTo("struts");
+    assertThat(component.qualifier()).isEqualTo("TRK");
+    assertThat(component.name()).isEqualTo("Struts");
+    assertThat(component.longName()).isEqualTo("org.struts");
+  }
+
+  @Test
+  public void should_parse_action_plans() throws Exception {
+    String json = IOUtils.toString(getClass().getResourceAsStream("/org/sonar/wsclient/issue/internal/IssueJsonParserTest/issue-with-action-plans.json"));
+    Issues issues = new IssueJsonParser().parseIssues(json);
+
+    assertThat(issues.actionPlans()).hasSize(1);
+
+    ActionPlan actionPlan = issues.actionPlans(issues.list().get(0));
+    assertThat(actionPlan.key()).isEqualTo("9450b10c-e725-48b8-bf01-acdec751c491");
+    assertThat(actionPlan.name()).isEqualTo("3.6");
+    assertThat(actionPlan.status()).isEqualTo("OPEN");
+    assertThat(actionPlan.project()).isEqualTo("struts");
+    assertThat(actionPlan.deadLine().getTime()).isEqualTo(1369951200000l);
+    assertThat(actionPlan.createdAt().getTime()).isEqualTo(1369828520000l);
+    assertThat(actionPlan.updatedAt().getTime()).isEqualTo(1369828520000l);
+  }
+}
diff --git a/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/DefaultIssueClientTest/add_comment_result.json b/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/DefaultIssueClientTest/add_comment_result.json
deleted file mode 100644 (file)
index 7329cc8..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "comment": {
-    "key": "COMMENT-123",
-    "htmlText": "this is my comment",
-    "login": "admin",
-    "createdAt": "2013-05-18T13:45:34+0200"
-  }
-}
\ No newline at end of file
diff --git a/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/empty.json b/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/empty.json
deleted file mode 100644 (file)
index b3712e0..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-  "paging": {
-    "pageIndex": 1,
-    "pageSize": 100,
-    "total": 0,
-    "pages": 0
-  },
-  "maxResultsReached": false
-}
\ No newline at end of file
diff --git a/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/getTransitions.json b/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/getTransitions.json
deleted file mode 100644 (file)
index 0451945..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-{
-  "transitions": [
-    "resolve",
-    "falsepositive"
-  ]
-}
\ No newline at end of file
diff --git a/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-action-plans.json b/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-action-plans.json
deleted file mode 100644 (file)
index 46718d5..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-{
-  "issues": [
-    {
-      "key": "ABCDE",
-      "component": "struts:Action.java",
-      "project": "struts",
-      "rule": "squid:CycleBetweenPackages",
-      "severity": "CRITICAL",
-      "status": "OPEN",
-      "actionPlan": "9450b10c-e725-48b8-bf01-acdec751c491",
-      "comments": [
-        {
-          "key": "COMMENT-1",
-          "login": "morgan",
-          "htmlText": "the first comment",
-          "createdAt": "2013-05-18T13:45:34+0200"
-        },
-        {
-          "key": "COMMENT-2",
-          "login": "arthur",
-          "htmlText": "the second comment",
-          "createdAt": "2013-06-19T00:02:03+0100"
-        }
-      ]
-    }
-  ],
-  "rules": [
-    {
-
-      "key": "squid:CycleBetweenPackages",
-      "name": "Avoid cycle between java packages",
-      "desc": "<p>\nWhen several packages are involved in a cycle (package A > package B > package C > package A where \">\" means \"depends upon\"),\nthat means that those packages are highly coupled and that there is no way to reuse/extract one of those packages without importing all the other packages.\nSuch cycle could quickly increase the effort required to maintain an application and to embrace business change.\nSonar not only detect cycles between packages but also determines what is the minimum effort to break those cycles.\nThis rule log a violation on each source file having an outgoing dependency to be but in order to break a cycle.\n</p>\n"
-
-    }
-  ],
-  "components": [
-    {
-      "key": "struts:Action.java",
-      "name": "Action",
-      "qualifier": "CLA",
-      "longName": "org.struts.Action"
-    }
-  ],
-  "projects": [
-    {
-      "key": "struts",
-      "name": "Struts",
-      "qualifier": "TRK",
-      "longName": "org.struts"
-    }
-  ],
-  "actionPlans": [
-    {
-      "key": "9450b10c-e725-48b8-bf01-acdec751c491",
-      "name": "3.6",
-      "status": "OPEN",
-      "project": "struts",
-      "userLogin": "arthur",
-      "deadLine": "2013-05-31T00:00:00+0200",
-      "createdAt": "2013-05-29T13:55:20+0200",
-      "updatedAt": "2013-05-29T13:55:20+0200"
-    }
-  ],
-  "paging": {
-    "pageIndex": 1,
-    "pageSize": 100,
-    "total": 2,
-    "pages": 1
-  },
-  "maxResultsReached": false
-}
\ No newline at end of file
diff --git a/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-comments.json b/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-comments.json
deleted file mode 100644 (file)
index 5118aae..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-{
-    "issues": [
-        {
-            "key": "ABCDE",
-            "component": "Action.java",
-            "project": "struts",
-            "rule": "squid:CycleBetweenPackages",
-            "severity": "CRITICAL",
-            "status": "OPEN",
-            "comments": [
-                {
-                    "key": "COMMENT-1",
-                    "login": "morgan",
-                    "htmlText": "the first comment",
-                    "createdAt": "2013-05-18T13:45:34+0200"
-                },
-                {
-                    "key": "COMMENT-2",
-                    "login": "arthur",
-                    "htmlText": "the second comment",
-                    "createdAt": "2013-06-19T00:02:03+0100"
-                }
-            ]
-        }
-    ],
-    "rules": [
-        {
-
-            "key": "squid:CycleBetweenPackages",
-            "name": "Avoid cycle between java packages",
-            "desc": "<p>\nWhen several packages are involved in a cycle (package A > package B > package C > package A where \">\" means \"depends upon\"),\nthat means that those packages are highly coupled and that there is no way to reuse/extract one of those packages without importing all the other packages.\nSuch cycle could quickly increase the effort required to maintain an application and to embrace business change.\nSonar not only detect cycles between packages but also determines what is the minimum effort to break those cycles.\nThis rule log a violation on each source file having an outgoing dependency to be but in order to break a cycle.\n</p>\n"
-
-        }
-    ],
-    "paging": {
-        "pageIndex": 1,
-        "pageSize": 100,
-        "total": 2,
-        "pages": 1
-    },
-    "maxResultsReached": false
-}
\ No newline at end of file
diff --git a/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-components.json b/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-components.json
deleted file mode 100644 (file)
index 2f33171..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-{
-  "issues": [
-    {
-      "key": "ABCDE",
-      "component": "struts:Action.java",
-      "project": "struts",
-      "rule": "squid:CycleBetweenPackages",
-      "severity": "CRITICAL",
-      "status": "OPEN",
-      "comments": [
-        {
-          "key": "COMMENT-1",
-          "login": "morgan",
-          "htmlText": "the first comment",
-          "createdAt": "2013-05-18T13:45:34+0200"
-        },
-        {
-          "key": "COMMENT-2",
-          "login": "arthur",
-          "htmlText": "the second comment",
-          "createdAt": "2013-06-19T00:02:03+0100"
-        }
-      ]
-    }
-  ],
-  "rules": [
-    {
-
-      "key": "squid:CycleBetweenPackages",
-      "name": "Avoid cycle between java packages",
-      "desc": "<p>\nWhen several packages are involved in a cycle (package A > package B > package C > package A where \">\" means \"depends upon\"),\nthat means that those packages are highly coupled and that there is no way to reuse/extract one of those packages without importing all the other packages.\nSuch cycle could quickly increase the effort required to maintain an application and to embrace business change.\nSonar not only detect cycles between packages but also determines what is the minimum effort to break those cycles.\nThis rule log a violation on each source file having an outgoing dependency to be but in order to break a cycle.\n</p>\n"
-
-    }
-  ],
-  "components": [
-    {
-      "key": "struts:Action.java",
-      "name": "Action",
-      "qualifier": "CLA",
-      "longName": "org.struts.Action"
-    }
-  ],
-  "paging": {
-    "pageIndex": 1,
-    "pageSize": 100,
-    "total": 2,
-    "pages": 1
-  },
-  "maxResultsReached": false
-}
\ No newline at end of file
diff --git a/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-projects.json b/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-projects.json
deleted file mode 100644 (file)
index a5e4ea7..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-{
-  "issues": [
-    {
-      "key": "ABCDE",
-      "component": "struts:Action.java",
-      "project": "struts",
-      "rule": "squid:CycleBetweenPackages",
-      "severity": "CRITICAL",
-      "status": "OPEN",
-      "comments": [
-        {
-          "key": "COMMENT-1",
-          "login": "morgan",
-          "htmlText": "the first comment",
-          "createdAt": "2013-05-18T13:45:34+0200"
-        },
-        {
-          "key": "COMMENT-2",
-          "login": "arthur",
-          "htmlText": "the second comment",
-          "createdAt": "2013-06-19T00:02:03+0100"
-        }
-      ]
-    }
-  ],
-  "rules": [
-    {
-
-      "key": "squid:CycleBetweenPackages",
-      "name": "Avoid cycle between java packages",
-      "desc": "<p>\nWhen several packages are involved in a cycle (package A > package B > package C > package A where \">\" means \"depends upon\"),\nthat means that those packages are highly coupled and that there is no way to reuse/extract one of those packages without importing all the other packages.\nSuch cycle could quickly increase the effort required to maintain an application and to embrace business change.\nSonar not only detect cycles between packages but also determines what is the minimum effort to break those cycles.\nThis rule log a violation on each source file having an outgoing dependency to be but in order to break a cycle.\n</p>\n"
-
-    }
-  ],
-  "components": [
-    {
-      "key": "struts:Action.java",
-      "name": "Action",
-      "qualifier": "CLA",
-      "longName": "org.struts.Action"
-    }
-  ],
-  "projects": [
-    {
-      "key": "struts",
-      "name": "Struts",
-      "qualifier": "TRK",
-      "longName": "org.struts"
-    }
-  ],
-  "paging": {
-    "pageIndex": 1,
-    "pageSize": 100,
-    "total": 2,
-    "pages": 1
-  },
-  "maxResultsReached": false
-}
\ No newline at end of file
diff --git a/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-users.json b/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/issue-with-users.json
deleted file mode 100644 (file)
index 7d8bc65..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-{
-    "issues": [
-        {
-            "key": "ABCDE",
-            "component": "Action.java",
-            "project": "struts",
-            "rule": "squid:CycleBetweenPackages",
-            "severity": "CRITICAL",
-            "status": "OPEN",
-            "comments": [
-                {
-                    "key": "COMMENT-1",
-                    "login": "morgan",
-                    "htmlText": "the first comment",
-                    "createdAt": "2013-05-18T13:45:34+0200"
-                },
-                {
-                    "key": "COMMENT-2",
-                    "login": "arthur",
-                    "htmlText": "the second comment",
-                    "createdAt": "2013-06-19T00:02:03+0100"
-                }
-            ]
-        }
-    ],
-    "rules": [
-        {
-
-            "key": "squid:CycleBetweenPackages",
-            "name": "Avoid cycle between java packages",
-            "desc": "<p>\nWhen several packages are involved in a cycle (package A > package B > package C > package A where \">\" means \"depends upon\"),\nthat means that those packages are highly coupled and that there is no way to reuse/extract one of those packages without importing all the other packages.\nSuch cycle could quickly increase the effort required to maintain an application and to embrace business change.\nSonar not only detect cycles between packages but also determines what is the minimum effort to break those cycles.\nThis rule log a violation on each source file having an outgoing dependency to be but in order to break a cycle.\n</p>\n"
-
-        }
-    ],
-    "users": [
-      {
-        "login": "morgan",
-        "name": "Morgan",
-        "active": true,
-        "email": "mor@gan.bzh"
-      },
-      {
-        "login": "arthur",
-        "name": "Arthur",
-        "active": false,
-        "email": "ar@thur.bzh"
-      }
-    ],
-    "paging": {
-        "pageIndex": 1,
-        "pageSize": 100,
-        "total": 2,
-        "pages": 1
-    },
-    "maxResultsReached": false
-}
\ No newline at end of file
diff --git a/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/search.json b/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/search.json
deleted file mode 100644 (file)
index 8c8505f..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-{
-  "issues": [
-    {
-      "key": "ABCDE",
-      "component": "Action.java",
-      "project": "struts",
-      "rule": "squid:CycleBetweenPackages",
-      "severity": "CRITICAL",
-      "line": 10,
-      "resolution": "FIXED",
-      "status": "OPEN",
-      "assignee": "karadoc",
-      "effortToFix": 4.2,
-      "message": "the message",
-      "title": "the title",
-      "reporter": "perceval",
-      "author": "pirlouis",
-      "actionPlan": "9450b10c-e725-48b8-bf01-acdec751c491",
-      "creationDate": "2013-05-18T12:45:34+0200",
-      "updateDate": "2013-05-18T13:45:34+0200",
-      "closeDate": "2013-05-18T14:45:34+0200",
-      "attr": {
-        "JIRA": "FOO-1234"
-      }
-    },
-    {
-      "key": "FGHIJ",
-      "component": "Filter.java",
-      "project": "struts",
-      "rule": "checkstyle:com.puppycrawl.tools.checkstyle.checks.imports.UnusedImportsCheck",
-      "severity": "BLOCKER",
-      "resolution": "FIXED",
-      "status": "OPEN"
-    }
-  ],
-  "rules": [
-    {
-
-      "key": "squid:CycleBetweenPackages",
-      "name": "Avoid cycle between java packages",
-      "desc": "<p>\nWhen several packages are involved in a cycle (package A > package B > package C > package A where \">\" means \"depends upon\"),\nthat means that those packages are highly coupled and that there is no way to reuse/extract one of those packages without importing all the other packages.\nSuch cycle could quickly increase the effort required to maintain an application and to embrace business change.\nSonar not only detect cycles between packages but also determines what is the minimum effort to break those cycles.\nThis rule log a violation on each source file having an outgoing dependency to be but in order to break a cycle.\n</p>\n"
-
-    },
-    {
-      "key": "checkstyle:com.puppycrawl.tools.checkstyle.checks.imports.UnusedImportsCheck",
-      "name": "Unused Imports",
-      "desc": "Checks for unused import statements."
-    }
-  ],
-  "paging": {
-    "pageIndex": 1,
-    "pageSize": 100,
-    "total": 2,
-    "pages": 1
-  },
-  "maxResultsReached": true
-}
\ No newline at end of file
diff --git a/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/DefaultIssueClientTest/add_comment_result.json b/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/DefaultIssueClientTest/add_comment_result.json
new file mode 100644 (file)
index 0000000..7329cc8
--- /dev/null
@@ -0,0 +1,8 @@
+{
+  "comment": {
+    "key": "COMMENT-123",
+    "htmlText": "this is my comment",
+    "login": "admin",
+    "createdAt": "2013-05-18T13:45:34+0200"
+  }
+}
\ No newline at end of file
diff --git a/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/IssueJsonParserTest/empty.json b/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/IssueJsonParserTest/empty.json
new file mode 100644 (file)
index 0000000..b3712e0
--- /dev/null
@@ -0,0 +1,9 @@
+{
+  "paging": {
+    "pageIndex": 1,
+    "pageSize": 100,
+    "total": 0,
+    "pages": 0
+  },
+  "maxResultsReached": false
+}
\ No newline at end of file
diff --git a/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/IssueJsonParserTest/getTransitions.json b/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/IssueJsonParserTest/getTransitions.json
new file mode 100644 (file)
index 0000000..0451945
--- /dev/null
@@ -0,0 +1,6 @@
+{
+  "transitions": [
+    "resolve",
+    "falsepositive"
+  ]
+}
\ No newline at end of file
diff --git a/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/IssueJsonParserTest/issue-with-action-plans.json b/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/IssueJsonParserTest/issue-with-action-plans.json
new file mode 100644 (file)
index 0000000..46718d5
--- /dev/null
@@ -0,0 +1,71 @@
+{
+  "issues": [
+    {
+      "key": "ABCDE",
+      "component": "struts:Action.java",
+      "project": "struts",
+      "rule": "squid:CycleBetweenPackages",
+      "severity": "CRITICAL",
+      "status": "OPEN",
+      "actionPlan": "9450b10c-e725-48b8-bf01-acdec751c491",
+      "comments": [
+        {
+          "key": "COMMENT-1",
+          "login": "morgan",
+          "htmlText": "the first comment",
+          "createdAt": "2013-05-18T13:45:34+0200"
+        },
+        {
+          "key": "COMMENT-2",
+          "login": "arthur",
+          "htmlText": "the second comment",
+          "createdAt": "2013-06-19T00:02:03+0100"
+        }
+      ]
+    }
+  ],
+  "rules": [
+    {
+
+      "key": "squid:CycleBetweenPackages",
+      "name": "Avoid cycle between java packages",
+      "desc": "<p>\nWhen several packages are involved in a cycle (package A > package B > package C > package A where \">\" means \"depends upon\"),\nthat means that those packages are highly coupled and that there is no way to reuse/extract one of those packages without importing all the other packages.\nSuch cycle could quickly increase the effort required to maintain an application and to embrace business change.\nSonar not only detect cycles between packages but also determines what is the minimum effort to break those cycles.\nThis rule log a violation on each source file having an outgoing dependency to be but in order to break a cycle.\n</p>\n"
+
+    }
+  ],
+  "components": [
+    {
+      "key": "struts:Action.java",
+      "name": "Action",
+      "qualifier": "CLA",
+      "longName": "org.struts.Action"
+    }
+  ],
+  "projects": [
+    {
+      "key": "struts",
+      "name": "Struts",
+      "qualifier": "TRK",
+      "longName": "org.struts"
+    }
+  ],
+  "actionPlans": [
+    {
+      "key": "9450b10c-e725-48b8-bf01-acdec751c491",
+      "name": "3.6",
+      "status": "OPEN",
+      "project": "struts",
+      "userLogin": "arthur",
+      "deadLine": "2013-05-31T00:00:00+0200",
+      "createdAt": "2013-05-29T13:55:20+0200",
+      "updatedAt": "2013-05-29T13:55:20+0200"
+    }
+  ],
+  "paging": {
+    "pageIndex": 1,
+    "pageSize": 100,
+    "total": 2,
+    "pages": 1
+  },
+  "maxResultsReached": false
+}
\ No newline at end of file
diff --git a/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/IssueJsonParserTest/issue-with-comments.json b/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/IssueJsonParserTest/issue-with-comments.json
new file mode 100644 (file)
index 0000000..5118aae
--- /dev/null
@@ -0,0 +1,42 @@
+{
+    "issues": [
+        {
+            "key": "ABCDE",
+            "component": "Action.java",
+            "project": "struts",
+            "rule": "squid:CycleBetweenPackages",
+            "severity": "CRITICAL",
+            "status": "OPEN",
+            "comments": [
+                {
+                    "key": "COMMENT-1",
+                    "login": "morgan",
+                    "htmlText": "the first comment",
+                    "createdAt": "2013-05-18T13:45:34+0200"
+                },
+                {
+                    "key": "COMMENT-2",
+                    "login": "arthur",
+                    "htmlText": "the second comment",
+                    "createdAt": "2013-06-19T00:02:03+0100"
+                }
+            ]
+        }
+    ],
+    "rules": [
+        {
+
+            "key": "squid:CycleBetweenPackages",
+            "name": "Avoid cycle between java packages",
+            "desc": "<p>\nWhen several packages are involved in a cycle (package A > package B > package C > package A where \">\" means \"depends upon\"),\nthat means that those packages are highly coupled and that there is no way to reuse/extract one of those packages without importing all the other packages.\nSuch cycle could quickly increase the effort required to maintain an application and to embrace business change.\nSonar not only detect cycles between packages but also determines what is the minimum effort to break those cycles.\nThis rule log a violation on each source file having an outgoing dependency to be but in order to break a cycle.\n</p>\n"
+
+        }
+    ],
+    "paging": {
+        "pageIndex": 1,
+        "pageSize": 100,
+        "total": 2,
+        "pages": 1
+    },
+    "maxResultsReached": false
+}
\ No newline at end of file
diff --git a/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/IssueJsonParserTest/issue-with-components.json b/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/IssueJsonParserTest/issue-with-components.json
new file mode 100644 (file)
index 0000000..2f33171
--- /dev/null
@@ -0,0 +1,50 @@
+{
+  "issues": [
+    {
+      "key": "ABCDE",
+      "component": "struts:Action.java",
+      "project": "struts",
+      "rule": "squid:CycleBetweenPackages",
+      "severity": "CRITICAL",
+      "status": "OPEN",
+      "comments": [
+        {
+          "key": "COMMENT-1",
+          "login": "morgan",
+          "htmlText": "the first comment",
+          "createdAt": "2013-05-18T13:45:34+0200"
+        },
+        {
+          "key": "COMMENT-2",
+          "login": "arthur",
+          "htmlText": "the second comment",
+          "createdAt": "2013-06-19T00:02:03+0100"
+        }
+      ]
+    }
+  ],
+  "rules": [
+    {
+
+      "key": "squid:CycleBetweenPackages",
+      "name": "Avoid cycle between java packages",
+      "desc": "<p>\nWhen several packages are involved in a cycle (package A > package B > package C > package A where \">\" means \"depends upon\"),\nthat means that those packages are highly coupled and that there is no way to reuse/extract one of those packages without importing all the other packages.\nSuch cycle could quickly increase the effort required to maintain an application and to embrace business change.\nSonar not only detect cycles between packages but also determines what is the minimum effort to break those cycles.\nThis rule log a violation on each source file having an outgoing dependency to be but in order to break a cycle.\n</p>\n"
+
+    }
+  ],
+  "components": [
+    {
+      "key": "struts:Action.java",
+      "name": "Action",
+      "qualifier": "CLA",
+      "longName": "org.struts.Action"
+    }
+  ],
+  "paging": {
+    "pageIndex": 1,
+    "pageSize": 100,
+    "total": 2,
+    "pages": 1
+  },
+  "maxResultsReached": false
+}
\ No newline at end of file
diff --git a/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/IssueJsonParserTest/issue-with-projects.json b/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/IssueJsonParserTest/issue-with-projects.json
new file mode 100644 (file)
index 0000000..a5e4ea7
--- /dev/null
@@ -0,0 +1,58 @@
+{
+  "issues": [
+    {
+      "key": "ABCDE",
+      "component": "struts:Action.java",
+      "project": "struts",
+      "rule": "squid:CycleBetweenPackages",
+      "severity": "CRITICAL",
+      "status": "OPEN",
+      "comments": [
+        {
+          "key": "COMMENT-1",
+          "login": "morgan",
+          "htmlText": "the first comment",
+          "createdAt": "2013-05-18T13:45:34+0200"
+        },
+        {
+          "key": "COMMENT-2",
+          "login": "arthur",
+          "htmlText": "the second comment",
+          "createdAt": "2013-06-19T00:02:03+0100"
+        }
+      ]
+    }
+  ],
+  "rules": [
+    {
+
+      "key": "squid:CycleBetweenPackages",
+      "name": "Avoid cycle between java packages",
+      "desc": "<p>\nWhen several packages are involved in a cycle (package A > package B > package C > package A where \">\" means \"depends upon\"),\nthat means that those packages are highly coupled and that there is no way to reuse/extract one of those packages without importing all the other packages.\nSuch cycle could quickly increase the effort required to maintain an application and to embrace business change.\nSonar not only detect cycles between packages but also determines what is the minimum effort to break those cycles.\nThis rule log a violation on each source file having an outgoing dependency to be but in order to break a cycle.\n</p>\n"
+
+    }
+  ],
+  "components": [
+    {
+      "key": "struts:Action.java",
+      "name": "Action",
+      "qualifier": "CLA",
+      "longName": "org.struts.Action"
+    }
+  ],
+  "projects": [
+    {
+      "key": "struts",
+      "name": "Struts",
+      "qualifier": "TRK",
+      "longName": "org.struts"
+    }
+  ],
+  "paging": {
+    "pageIndex": 1,
+    "pageSize": 100,
+    "total": 2,
+    "pages": 1
+  },
+  "maxResultsReached": false
+}
\ No newline at end of file
diff --git a/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/IssueJsonParserTest/issue-with-users.json b/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/IssueJsonParserTest/issue-with-users.json
new file mode 100644 (file)
index 0000000..7d8bc65
--- /dev/null
@@ -0,0 +1,56 @@
+{
+    "issues": [
+        {
+            "key": "ABCDE",
+            "component": "Action.java",
+            "project": "struts",
+            "rule": "squid:CycleBetweenPackages",
+            "severity": "CRITICAL",
+            "status": "OPEN",
+            "comments": [
+                {
+                    "key": "COMMENT-1",
+                    "login": "morgan",
+                    "htmlText": "the first comment",
+                    "createdAt": "2013-05-18T13:45:34+0200"
+                },
+                {
+                    "key": "COMMENT-2",
+                    "login": "arthur",
+                    "htmlText": "the second comment",
+                    "createdAt": "2013-06-19T00:02:03+0100"
+                }
+            ]
+        }
+    ],
+    "rules": [
+        {
+
+            "key": "squid:CycleBetweenPackages",
+            "name": "Avoid cycle between java packages",
+            "desc": "<p>\nWhen several packages are involved in a cycle (package A > package B > package C > package A where \">\" means \"depends upon\"),\nthat means that those packages are highly coupled and that there is no way to reuse/extract one of those packages without importing all the other packages.\nSuch cycle could quickly increase the effort required to maintain an application and to embrace business change.\nSonar not only detect cycles between packages but also determines what is the minimum effort to break those cycles.\nThis rule log a violation on each source file having an outgoing dependency to be but in order to break a cycle.\n</p>\n"
+
+        }
+    ],
+    "users": [
+      {
+        "login": "morgan",
+        "name": "Morgan",
+        "active": true,
+        "email": "mor@gan.bzh"
+      },
+      {
+        "login": "arthur",
+        "name": "Arthur",
+        "active": false,
+        "email": "ar@thur.bzh"
+      }
+    ],
+    "paging": {
+        "pageIndex": 1,
+        "pageSize": 100,
+        "total": 2,
+        "pages": 1
+    },
+    "maxResultsReached": false
+}
\ No newline at end of file
diff --git a/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/IssueJsonParserTest/search.json b/sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/internal/IssueJsonParserTest/search.json
new file mode 100644 (file)
index 0000000..8c8505f
--- /dev/null
@@ -0,0 +1,57 @@
+{
+  "issues": [
+    {
+      "key": "ABCDE",
+      "component": "Action.java",
+      "project": "struts",
+      "rule": "squid:CycleBetweenPackages",
+      "severity": "CRITICAL",
+      "line": 10,
+      "resolution": "FIXED",
+      "status": "OPEN",
+      "assignee": "karadoc",
+      "effortToFix": 4.2,
+      "message": "the message",
+      "title": "the title",
+      "reporter": "perceval",
+      "author": "pirlouis",
+      "actionPlan": "9450b10c-e725-48b8-bf01-acdec751c491",
+      "creationDate": "2013-05-18T12:45:34+0200",
+      "updateDate": "2013-05-18T13:45:34+0200",
+      "closeDate": "2013-05-18T14:45:34+0200",
+      "attr": {
+        "JIRA": "FOO-1234"
+      }
+    },
+    {
+      "key": "FGHIJ",
+      "component": "Filter.java",
+      "project": "struts",
+      "rule": "checkstyle:com.puppycrawl.tools.checkstyle.checks.imports.UnusedImportsCheck",
+      "severity": "BLOCKER",
+      "resolution": "FIXED",
+      "status": "OPEN"
+    }
+  ],
+  "rules": [
+    {
+
+      "key": "squid:CycleBetweenPackages",
+      "name": "Avoid cycle between java packages",
+      "desc": "<p>\nWhen several packages are involved in a cycle (package A > package B > package C > package A where \">\" means \"depends upon\"),\nthat means that those packages are highly coupled and that there is no way to reuse/extract one of those packages without importing all the other packages.\nSuch cycle could quickly increase the effort required to maintain an application and to embrace business change.\nSonar not only detect cycles between packages but also determines what is the minimum effort to break those cycles.\nThis rule log a violation on each source file having an outgoing dependency to be but in order to break a cycle.\n</p>\n"
+
+    },
+    {
+      "key": "checkstyle:com.puppycrawl.tools.checkstyle.checks.imports.UnusedImportsCheck",
+      "name": "Unused Imports",
+      "desc": "Checks for unused import statements."
+    }
+  ],
+  "paging": {
+    "pageIndex": 1,
+    "pageSize": 100,
+    "total": 2,
+    "pages": 1
+  },
+  "maxResultsReached": true
+}
\ No newline at end of file