]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-3755 new sonar-ws-client API for issues
authorSimon Brandhof <simon.brandhof@gmail.com>
Fri, 19 Apr 2013 08:23:04 +0000 (10:23 +0200)
committerSimon Brandhof <simon.brandhof@gmail.com>
Fri, 19 Apr 2013 11:39:38 +0000 (13:39 +0200)
25 files changed:
sonar-ws-client/pom.xml
sonar-ws-client/src/main/java/org/sonar/wsclient/SonarClient.java [new file with mode: 0644]
sonar-ws-client/src/main/java/org/sonar/wsclient/internal/EncodingUtils.java [new file with mode: 0644]
sonar-ws-client/src/main/java/org/sonar/wsclient/internal/HttpRequestFactory.java [new file with mode: 0644]
sonar-ws-client/src/main/java/org/sonar/wsclient/internal/package-info.java [new file with mode: 0644]
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/DefaultIssueClient.java [new file with mode: 0644]
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/Issue.java [new file with mode: 0644]
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/IssueChange.java [new file with mode: 0644]
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/IssueClient.java [new file with mode: 0644]
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/IssueParser.java [new file with mode: 0644]
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/IssueQuery.java [new file with mode: 0644]
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/Issues.java [new file with mode: 0644]
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/NewIssue.java [new file with mode: 0644]
sonar-ws-client/src/main/java/org/sonar/wsclient/issue/package-info.java [new file with mode: 0644]
sonar-ws-client/src/test/java/org/sonar/wsclient/MockHttpServer.java [new file with mode: 0644]
sonar-ws-client/src/test/java/org/sonar/wsclient/MockHttpServerInterceptor.java [new file with mode: 0644]
sonar-ws-client/src/test/java/org/sonar/wsclient/SonarClientTest.java [new file with mode: 0644]
sonar-ws-client/src/test/java/org/sonar/wsclient/internal/EncodingUtilsTest.java [new file with mode: 0644]
sonar-ws-client/src/test/java/org/sonar/wsclient/internal/HttpRequestFactoryTest.java [new file with mode: 0644]
sonar-ws-client/src/test/java/org/sonar/wsclient/issue/DefaultIssueClientTest.java [new file with mode: 0644]
sonar-ws-client/src/test/java/org/sonar/wsclient/issue/IssueChangeTest.java [new file with mode: 0644]
sonar-ws-client/src/test/java/org/sonar/wsclient/issue/IssueParserTest.java [new file with mode: 0644]
sonar-ws-client/src/test/java/org/sonar/wsclient/issue/IssueQueryTest.java [new file with mode: 0644]
sonar-ws-client/src/test/java/org/sonar/wsclient/issue/NewIssueTest.java [new file with mode: 0644]
sonar-ws-client/src/test/resources/org/sonar/wsclient/issue/IssueParserTest/search.json [new file with mode: 0644]

index e0b7dc6febbe98af6a5eea7b7e74e808c45b799c..7bb9caf112909efa4fc06e0c0c40e652843582e6 100644 (file)
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <groupId>org.codehaus.sonar</groupId>
@@ -16,8 +17,8 @@
   </properties>
 
   <dependencies>
-  <!-- Don't forget to update configuration of the m-bundle-p (especially Require-Bundle)
-  according to changes you make on dependencies -->
+    <!-- Don't forget to update configuration of the m-bundle-p (especially Require-Bundle)
+    according to changes you make on dependencies -->
     <dependency>
       <groupId>com.googlecode.json-simple</groupId>
       <artifactId>json-simple</artifactId>
       <groupId>commons-httpclient</groupId>
       <artifactId>commons-httpclient</artifactId>
       <version>3.1</version>
-      <optional>true</optional>
+      <scope>provided</scope>
     </dependency>
     <dependency>
       <groupId>org.apache.httpcomponents</groupId>
       <artifactId>httpclient</artifactId>
       <version>${httpclient4.version}</version>
-      <optional>true</optional>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.github.kevinsawicki</groupId>
+      <artifactId>http-request</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.google.code.findbugs</groupId>
+      <artifactId>jsr305</artifactId>
+      <scope>provided</scope>
     </dependency>
 
     <!-- unit tests -->
 
   <build>
     <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-shade-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>shade</goal>
+            </goals>
+            <configuration>
+              <createDependencyReducedPom>true</createDependencyReducedPom>
+              <minimizeJar>true</minimizeJar>
+              <relocations>
+                <relocation>
+                  <pattern>com.github.kevinsawicki.http</pattern>
+                  <shadedPattern>org.sonar.wsclient.kevinsawicki</shadedPattern>
+                </relocation>
+                <relocation>
+                  <pattern>org.json.simple</pattern>
+                  <shadedPattern>org.sonar.wsclient.jsonsimple</shadedPattern>
+                </relocation>
+              </relocations>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
       <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
             <Import-Package>!*</Import-Package>
             <Require-Bundle>org.apache.httpcomponents.httpclient;bundle-version="${httpclient4.version}";optional="true",
               org.apache.httpcomponents.httpcore;bundle-version="${httpclient4.version}";optional="true",
-              com.googlecode.json-simple
             </Require-Bundle>
             <Export-Package>!.,org.sonar.wsclient,
               org.sonar.wsclient.connectors,
-              org.sonar.wsclient.services</Export-Package>
-            <Bundle-RequiredExecutionEnvironment>J2SE-1.5</Bundle-RequiredExecutionEnvironment>
+              org.sonar.wsclient.services
+            </Export-Package>
+            <Bundle-RequiredExecutionEnvironment>J2SE-1.6</Bundle-RequiredExecutionEnvironment>
           </instructions>
         </configuration>
         <executions>
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/SonarClient.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/SonarClient.java
new file mode 100644 (file)
index 0000000..2a2555f
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.wsclient;
+
+import org.sonar.wsclient.internal.HttpRequestFactory;
+import org.sonar.wsclient.issue.DefaultIssueClient;
+import org.sonar.wsclient.issue.IssueClient;
+
+/**
+ * @since 3.6
+ */
+public class SonarClient {
+
+  private final HttpRequestFactory requestFactory;
+
+  private SonarClient(Builder builder) {
+    requestFactory = new HttpRequestFactory(builder.url, builder.login, builder.password);
+  }
+
+  public IssueClient issueClient() {
+    return new DefaultIssueClient(requestFactory);
+  }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class Builder {
+    private String login, password, url;
+
+    private Builder() {
+    }
+
+    public Builder login(String login) {
+      this.login = login;
+      return this;
+    }
+
+    public Builder password(String password) {
+      this.password = password;
+      return this;
+    }
+
+    public Builder url(String url) {
+      this.url = url;
+      return this;
+    }
+
+    public SonarClient build() {
+      if (url == null || "".equals(url)) {
+        throw new IllegalStateException("Server URL must be set");
+      }
+      return new SonarClient(this);
+    }
+  }
+}
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/internal/EncodingUtils.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/internal/EncodingUtils.java
new file mode 100644 (file)
index 0000000..0fa31de
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.wsclient.internal;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * Not an API, please do not directly use this class.
+ */
+public class EncodingUtils {
+
+  private static final String DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ";
+  private static final String DATE_FORMAT = "yyyy-MM-dd";
+
+  private EncodingUtils() {
+    // only static methods
+  }
+
+  public static String toQueryParam(String[] strings) {
+    StringBuilder sb = new StringBuilder();
+    boolean first = true;
+    for (String string : strings) {
+      if (!first) {
+        sb.append(',');
+      }
+      sb.append(string);
+      first = false;
+    }
+    return sb.toString();
+  }
+
+  public static String toQueryParam(Date d, boolean includeTime) {
+    String format = (includeTime ? DATETIME_FORMAT : DATE_FORMAT);
+    SimpleDateFormat dateFormat = new SimpleDateFormat(format);
+    return dateFormat.format(d);
+  }
+}
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/internal/HttpRequestFactory.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/internal/HttpRequestFactory.java
new file mode 100644 (file)
index 0000000..4b23653
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.wsclient.internal;
+
+import com.github.kevinsawicki.http.HttpRequest;
+
+import java.util.Map;
+
+/**
+ * Not an API, please do not directly use this class.
+ */
+public class HttpRequestFactory {
+
+  static final int CONNECT_TIMEOUT_MILLISECONDS = 30000;
+  static final int READ_TIMEOUT_MILLISECONDS = 60000;
+
+  private final String baseURl;
+  private final String login, password;
+
+  public HttpRequestFactory(String baseURl, String login, String password) {
+    this.baseURl = baseURl;
+    this.login = login;
+    this.password = password;
+  }
+
+  public HttpRequest get(String wsUrl, Map<String, Object> queryParams) {
+    HttpRequest request = HttpRequest.get(baseURl + wsUrl, queryParams, true);
+    return prepare(request);
+  }
+
+  public HttpRequest post(String wsUrl, Map<String, Object> queryParams) {
+    HttpRequest request = HttpRequest.post(baseURl + wsUrl, queryParams, true);
+    return prepare(request);
+  }
+
+  private HttpRequest prepare(HttpRequest request) {
+    request
+      .acceptGzipEncoding()
+      .uncompress(true)
+      .acceptJson()
+      .acceptCharset(HttpRequest.CHARSET_UTF8)
+      .connectTimeout(CONNECT_TIMEOUT_MILLISECONDS)
+      .readTimeout(READ_TIMEOUT_MILLISECONDS)
+      .trustAllCerts()
+      .trustAllCerts();
+    if (login != null) {
+      request.basic(login, password);
+    }
+    return request;
+  }
+}
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/internal/package-info.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/internal/package-info.java
new file mode 100644 (file)
index 0000000..81ddb10
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ * Sonar Runner - Implementation
+ * Copyright (C) 2011 SonarSource
+ * dev@sonar.codehaus.org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+@javax.annotation.ParametersAreNonnullByDefault
+package org.sonar.wsclient.internal;
\ No newline at end of file
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
new file mode 100644 (file)
index 0000000..f9a93ee
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.wsclient.issue;
+
+import com.github.kevinsawicki.http.HttpRequest;
+import org.sonar.wsclient.internal.HttpRequestFactory;
+
+import java.util.LinkedHashMap;
+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 IssueParser parser;
+
+  /**
+   * For internal use. Use {@link org.sonar.wsclient.SonarClient} to get an instance.
+   */
+  public DefaultIssueClient(HttpRequestFactory requestFactory) {
+    this.requestFactory = requestFactory;
+    this.parser = new IssueParser();
+  }
+
+  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 void apply(String issueKey, IssueChange change) {
+    if (!change.urlParams().isEmpty()) {
+      Map<String, Object> queryParams = new LinkedHashMap<String, Object>(change.urlParams());
+      queryParams.put("key", issueKey);
+      HttpRequest request = requestFactory.post(IssueChange.BASE_URL, queryParams);
+      if (!request.ok()) {
+        throw new IllegalStateException("Fail to change issue " + issueKey + ".Bad HTTP response status: " + request.code());
+      }
+    }
+  }
+
+  @Override
+  public void 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());
+    }
+  }
+
+  @Override
+  public void comment(String issueKey, String comment) {
+    apply(issueKey, IssueChange.create().comment(comment));
+  }
+
+}
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/Issue.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/Issue.java
new file mode 100644 (file)
index 0000000..f714918
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar{} if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.wsclient.issue;
+
+import org.sonar.wsclient.unmarshallers.JsonUtils;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * @since 3.6
+ */
+public class Issue {
+
+  private final Map json;
+
+  Issue(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 ruleKey() {
+    return JsonUtils.getString(json, "rule");
+  }
+
+  public String severity() {
+    return JsonUtils.getString(json, "severity");
+  }
+
+  public String title() {
+    return JsonUtils.getString(json, "title");
+  }
+
+  public String description() {
+    return JsonUtils.getString(json, "desc");
+  }
+
+  public Integer line() {
+    return JsonUtils.getInteger(json, "line");
+  }
+
+  public Double cost() {
+    return JsonUtils.getDouble(json, "cost");
+  }
+
+  public String status() {
+    return JsonUtils.getString(json, "status");
+  }
+
+  public String resolution() {
+    return JsonUtils.getString(json, "resolution");
+  }
+
+  public String userLogin() {
+    return JsonUtils.getString(json, "userLogin");
+  }
+
+  public String assignee() {
+    return JsonUtils.getString(json, "assignee");
+  }
+
+  public Date createdAt() {
+    return JsonUtils.getDateTime(json, "createdAt");
+  }
+
+  public Date updatedAt() {
+    return JsonUtils.getDateTime(json, "updatedAt");
+  }
+
+  public Date closedAt() {
+    return JsonUtils.getDateTime(json, "closedAt");
+  }
+
+  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;
+  }
+}
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/IssueChange.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/IssueChange.java
new file mode 100644 (file)
index 0000000..5179770
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.wsclient.issue;
+
+import javax.annotation.Nullable;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * @since 3.6
+ */
+public class IssueChange {
+  static final String BASE_URL = "/api/issues/change";
+  private final Map<String, Object> params = new LinkedHashMap<String, Object>();
+
+  private IssueChange() {
+  }
+
+  public static IssueChange create() {
+    return new IssueChange();
+  }
+
+  Map<String, Object> urlParams() {
+    return params;
+  }
+
+  public IssueChange severity(String s) {
+    params.put("newSeverity", s);
+    return this;
+  }
+
+  /**
+   * Add a comment
+   */
+  public IssueChange comment(String s) {
+    params.put("newComment", s);
+    return this;
+  }
+
+  public IssueChange cost(Double d) {
+    params.put("newCost", d);
+    return this;
+  }
+
+  public IssueChange resolution(String s) {
+    params.put("newResolution", s);
+    return this;
+  }
+
+  public IssueChange assignee(String login) {
+    params.put("newAssignee", login);
+    return this;
+  }
+
+  public IssueChange attribute(String key, @Nullable String value) {
+    if (value == null) {
+      params.put("newAttr[" + key + "]", "");
+    } else {
+      params.put("newAttr[" + key + "]", value);
+    }
+    return this;
+  }
+}
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/IssueClient.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/IssueClient.java
new file mode 100644 (file)
index 0000000..5649862
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.wsclient.issue;
+
+/**
+ * @since 3.6
+ */
+public interface IssueClient {
+
+  Issues find(IssueQuery query);
+
+  void apply(String issueKey, IssueChange change);
+
+  void create(NewIssue issue);
+
+  /**
+   * Shortcut for {@code #apply(issueKey, IssueChange.create().comment(comment)}
+   */
+  void comment(String issueKey, String comment);
+}
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/IssueParser.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/IssueParser.java
new file mode 100644 (file)
index 0000000..c763e18
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.wsclient.issue;
+
+import org.json.simple.JSONValue;
+
+import java.util.List;
+import java.util.Map;
+
+class IssueParser {
+  Issues parseIssues(String json) {
+    Issues result = new Issues();
+    Map jRoot = (Map) JSONValue.parse(json);
+    List<Map> jIssues = (List) jRoot.get("issues");
+    for (Map jIssue : jIssues) {
+      result.add(new Issue(jIssue));
+    }
+    return result;
+  }
+}
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/IssueQuery.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/IssueQuery.java
new file mode 100644 (file)
index 0000000..baa177b
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.wsclient.issue;
+
+import org.sonar.wsclient.internal.EncodingUtils;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @since 3.6
+ */
+public class IssueQuery {
+
+  static final String BASE_URL = "/api/issues/search";
+  private final Map<String, Object> params = new HashMap<String, Object>();
+
+  private IssueQuery() {
+  }
+
+  public static IssueQuery create() {
+    return new IssueQuery();
+  }
+
+  Map<String, Object> urlParams() {
+    return params;
+  }
+
+  public IssueQuery keys(String... keys) {
+    return addParam("keys", keys);
+  }
+
+  public IssueQuery severities(String... severities) {
+    return addParam("severities", severities);
+  }
+
+  public IssueQuery statuses(String... statuses) {
+    return addParam("statuses", statuses);
+  }
+
+  public IssueQuery resolutions(String... resolutions) {
+    return addParam("resolutions", resolutions);
+  }
+
+  public IssueQuery components(String... components) {
+    return addParam("components", components);
+  }
+
+  public IssueQuery componentRoots(String... componentRoots) {
+    return addParam("componentRoots", componentRoots);
+  }
+
+  public IssueQuery rules(String... s) {
+    return addParam("rules", s);
+  }
+
+  public IssueQuery userLogins(String... s) {
+    return addParam("userLogins", s);
+  }
+
+  public IssueQuery assignees(String... s) {
+    return addParam("assignees", s);
+  }
+
+  public IssueQuery createdAfter(Date d) {
+    params.put("createdAfter", EncodingUtils.toQueryParam(d, true));
+    return this;
+  }
+
+  public IssueQuery createdBefore(Date d) {
+    params.put("createdBefore", EncodingUtils.toQueryParam(d, true));
+    return this;
+  }
+
+  public IssueQuery limit(int limit) {
+    params.put("limit", limit);
+    return this;
+  }
+
+  public IssueQuery offset(int offset) {
+    params.put("offset", offset);
+    return this;
+  }
+
+  private IssueQuery addParam(String key, String[] values) {
+    if (values != null) {
+      params.put(key, EncodingUtils.toQueryParam(values));
+    }
+    return this;
+  }
+
+}
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/Issues.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/Issues.java
new file mode 100644 (file)
index 0000000..51ff417
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.wsclient.issue;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @since 3.6
+ */
+public class Issues {
+
+  private final List<Issue> list = new ArrayList<Issue>();
+
+  Issues add(Issue issue) {
+    list.add(issue);
+    return this;
+  }
+  public List<Issue> list() {
+    return list;
+  }
+}
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/NewIssue.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/NewIssue.java
new file mode 100644 (file)
index 0000000..ce6d7cd
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.wsclient.issue;
+
+import java.util.HashMap;
+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() {
+  }
+
+  public static NewIssue create() {
+    return new NewIssue();
+  }
+
+  Map<String, Object> urlParams() {
+    return params;
+  }
+
+  public NewIssue severity(String s) {
+    params.put("severity", s);
+    return this;
+  }
+
+  public NewIssue component(String s) {
+    params.put("component", s);
+    return this;
+  }
+
+  public NewIssue rule(String s) {
+    params.put("rule", s);
+    return this;
+  }
+
+  /**
+   * Optional line
+   */
+  public NewIssue line(int i) {
+    params.put("line", i);
+    return this;
+  }
+
+  public NewIssue description(String s) {
+    params.put("desc", s);
+    return this;
+  }
+
+  public NewIssue title(String s) {
+    params.put("title", s);
+    return this;
+  }
+
+  public NewIssue cost(Double d) {
+    params.put("cost", d);
+    return this;
+  }
+
+  public NewIssue attribute(String key, String value) {
+    params.put("attr[" + key + "]", value);
+    return this;
+  }
+}
diff --git a/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/package-info.java b/sonar-ws-client/src/main/java/org/sonar/wsclient/issue/package-info.java
new file mode 100644 (file)
index 0000000..fdc9b46
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ * Sonar Runner - Implementation
+ * Copyright (C) 2011 SonarSource
+ * dev@sonar.codehaus.org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+@javax.annotation.ParametersAreNonnullByDefault
+package org.sonar.wsclient.issue;
\ No newline at end of file
diff --git a/sonar-ws-client/src/test/java/org/sonar/wsclient/MockHttpServer.java b/sonar-ws-client/src/test/java/org/sonar/wsclient/MockHttpServer.java
new file mode 100644 (file)
index 0000000..f39cdf5
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2011-2013 SonarSource SA
+ * All rights reserved
+ * mailto:contact AT sonarsource DOT com
+ */
+package org.sonar.wsclient;
+
+import org.apache.commons.io.IOUtils;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+import static javax.servlet.http.HttpServletResponse.SC_OK;
+import static org.apache.commons.io.IOUtils.write;
+
+public class MockHttpServer {
+  private Server server;
+  private String responseBody;
+  private int responseStatus = SC_OK;
+  private String requestBody, requestPath;
+  private Map requestHeaders = new HashMap();
+
+  public void start() throws Exception {
+    // 0 is random available port
+    server = new Server(0);
+    server.setHandler(getMockHandler());
+    server.start();
+  }
+
+  public Handler getMockHandler() {
+    Handler handler = new AbstractHandler() {
+      @Override
+      public void handle(String target, Request baseRequest, HttpServletRequest httpServletRequest, HttpServletResponse response) throws IOException, ServletException {
+        requestPath = baseRequest.getUri().toString();
+        requestBody = IOUtils.toString(baseRequest.getInputStream());
+        requestHeaders.clear();
+        Enumeration headerNames = baseRequest.getHeaderNames();
+        while (headerNames.hasMoreElements()) {
+          String headerName = (String)headerNames.nextElement();
+          requestHeaders.put(headerName, baseRequest.getHeader(headerName));
+        }
+        response.setStatus(responseStatus);
+        response.setContentType("application/json;charset=utf-8");
+        write(responseBody, response.getOutputStream());
+        baseRequest.setHandled(true);
+      }
+    };
+    return handler;
+  }
+
+  public void stop() {
+    try {
+      if (server != null) {
+        server.stop();
+      }
+    } catch (Exception e) {
+      throw new IllegalStateException("Fail to stop HTTP server", e);
+    }
+  }
+
+  public MockHttpServer doReturnBody(String responseBody) {
+    this.responseBody = responseBody;
+    return this;
+  }
+
+  public MockHttpServer doReturnStatus(int status) {
+    this.responseStatus = status;
+    return this;
+  }
+
+  public String requestBody() {
+    return requestBody;
+  }
+
+  public String requestPath() {
+    return requestPath;
+  }
+
+  public Map requestHeaders() {
+    return requestHeaders;
+  }
+
+  public int getPort() {
+    return server.getConnectors()[0].getLocalPort();
+  }
+}
diff --git a/sonar-ws-client/src/test/java/org/sonar/wsclient/MockHttpServerInterceptor.java b/sonar-ws-client/src/test/java/org/sonar/wsclient/MockHttpServerInterceptor.java
new file mode 100644 (file)
index 0000000..bdebb27
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2011-2013 SonarSource SA
+ * All rights reserved
+ * mailto:contact AT sonarsource DOT com
+ */
+package org.sonar.wsclient;
+
+import org.junit.rules.ExternalResource;
+
+import java.util.Map;
+
+public final class MockHttpServerInterceptor extends ExternalResource {
+
+  private MockHttpServer server;
+
+  @Override
+  protected final void before() throws Throwable {
+    server = new MockHttpServer();
+    server.start();
+  }
+
+  @Override
+  protected void after() {
+    server.stop();
+  }
+
+  public MockHttpServerInterceptor doReturnBody(String body) {
+    server.doReturnBody(body);
+    return this;
+  }
+
+  public MockHttpServerInterceptor doReturnStatus(int status) {
+    server.doReturnStatus(status);
+    return this;
+  }
+
+  public String requestedPath() {
+    return server.requestPath();
+  }
+
+  public Map requestHeaders() {
+    return server.requestHeaders();
+  }
+
+  public int port() {
+    return server.getPort();
+  }
+
+  public String url() {
+    return "http://localhost:" + port();
+  }
+}
\ No newline at end of file
diff --git a/sonar-ws-client/src/test/java/org/sonar/wsclient/SonarClientTest.java b/sonar-ws-client/src/test/java/org/sonar/wsclient/SonarClientTest.java
new file mode 100644 (file)
index 0000000..2f40648
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.wsclient;
+
+import org.junit.Test;
+import org.sonar.wsclient.issue.DefaultIssueClient;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
+
+public class SonarClientTest {
+  @Test
+  public void should_build_client() {
+    SonarClient client = SonarClient.builder().url("http://localhost:9000").build();
+    assertThat(client.issueClient()).isNotNull().isInstanceOf(DefaultIssueClient.class);
+  }
+
+  @Test
+  public void url_should_not_be_null() {
+    try {
+      SonarClient.builder().build();
+      fail();
+    } catch (IllegalStateException e) {
+      assertThat(e).hasMessage("Server URL must be set");
+    }
+  }
+
+  @Test
+  public void url_should_not_be_empty() {
+    try {
+      SonarClient.builder().url("").build();
+      fail();
+    } catch (IllegalStateException e) {
+      assertThat(e).hasMessage("Server URL must be set");
+    }
+  }
+}
diff --git a/sonar-ws-client/src/test/java/org/sonar/wsclient/internal/EncodingUtilsTest.java b/sonar-ws-client/src/test/java/org/sonar/wsclient/internal/EncodingUtilsTest.java
new file mode 100644 (file)
index 0000000..c01d16e
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.wsclient.internal;
+
+import org.junit.Test;
+
+import java.util.Date;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class EncodingUtilsTest {
+  @Test
+  public void test_date_param() {
+
+    assertThat(EncodingUtils.toQueryParam(new Date(), false)).isNotEmpty().matches("\\d{4}-\\d{2}-\\d{2}");
+    assertThat(EncodingUtils.toQueryParam(new Date(), true)).isNotEmpty();
+  }
+
+  @Test
+  public void test_string_array_param() {
+    assertThat(EncodingUtils.toQueryParam(new String[]{"foo", "bar"})).isEqualTo("foo,bar");
+    assertThat(EncodingUtils.toQueryParam(new String[]{"foo"})).isEqualTo("foo");
+    assertThat(EncodingUtils.toQueryParam(new String[]{""})).isEqualTo("");
+  }
+}
diff --git a/sonar-ws-client/src/test/java/org/sonar/wsclient/internal/HttpRequestFactoryTest.java b/sonar-ws-client/src/test/java/org/sonar/wsclient/internal/HttpRequestFactoryTest.java
new file mode 100644 (file)
index 0000000..5a5164e
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+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 java.util.Collections;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class HttpRequestFactoryTest {
+  @Rule
+  public MockHttpServerInterceptor httpServer = new MockHttpServerInterceptor();
+
+  @Test
+  public void test_get() {
+    httpServer.doReturnStatus(200).doReturnBody("list of issues");
+
+    HttpRequestFactory factory = new HttpRequestFactory(httpServer.url(), null, null);
+    HttpRequest request = 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(httpServer.requestedPath()).isEqualTo("/api/issues");
+  }
+
+  @Test
+  public void test_post() {
+    httpServer.doReturnStatus(200);
+
+    HttpRequestFactory factory = new HttpRequestFactory(httpServer.url(), null, null);
+    HttpRequest request = factory.post("/api/issues/change", Collections.<String, Object>emptyMap());
+
+    assertThat(request.method()).isEqualTo("POST");
+    assertThat(request.code()).isEqualTo(200);
+    assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/change");
+  }
+
+  @Test
+  public void test_authentication() {
+    httpServer.doReturnStatus(200).doReturnBody("list of issues");
+
+    HttpRequestFactory factory = new HttpRequestFactory(httpServer.url(), "karadoc", "legrascestlavie");
+    HttpRequest request = factory.get("/api/issues", Collections.<String, Object>emptyMap());
+
+    assertThat(request.body()).isEqualTo("list of issues");
+    assertThat(request.code()).isEqualTo(200);
+    assertThat(httpServer.requestedPath()).isEqualTo("/api/issues");
+    assertThat(httpServer.requestHeaders().get("Authorization")).isEqualTo("Basic a2FyYWRvYzpsZWdyYXNjZXN0bGF2aWU=");
+  }
+}
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
new file mode 100644 (file)
index 0000000..d377100
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+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 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(), null, null);
+    httpServer.doReturnBody("{\"issues\": [{\"key\": \"ABCDE\"}]}");
+
+    IssueClient client = new DefaultIssueClient(requestFactory);
+    IssueQuery query = IssueQuery.create().keys("ABCDE");
+    Issues issues = client.find(query);
+
+    assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/search?keys=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(), null, null);
+    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_apply_change() {
+    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url(), null, null);
+
+    IssueClient client = new DefaultIssueClient(requestFactory);
+    client.apply("ABCDE", IssueChange.create().severity("BLOCKER").comment("because!"));
+
+    assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/change?newSeverity=BLOCKER&newComment=because!&key=ABCDE");
+  }
+
+  @Test
+  public void should_not_apply_empty_change() {
+    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url(), null, null);
+
+    IssueClient client = new DefaultIssueClient(requestFactory);
+    client.apply("ABCDE", IssueChange.create());
+
+    assertThat(httpServer.requestedPath()).isNull();
+  }
+
+  @Test
+  public void should_create_issue() {
+    HttpRequestFactory requestFactory = new HttpRequestFactory(httpServer.url(), null, null);
+
+    IssueClient client = new DefaultIssueClient(requestFactory);
+    client.create(NewIssue.create().component("Action.java").rule("squid:AvoidCycle"));
+
+    assertThat(httpServer.requestedPath()).isEqualTo("/api/issues/create?component=Action.java&rule=squid:AvoidCycle");
+  }
+}
diff --git a/sonar-ws-client/src/test/java/org/sonar/wsclient/issue/IssueChangeTest.java b/sonar-ws-client/src/test/java/org/sonar/wsclient/issue/IssueChangeTest.java
new file mode 100644 (file)
index 0000000..4d3be57
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.wsclient.issue;
+
+import org.junit.Test;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.MapAssert.entry;
+
+public class IssueChangeTest {
+  @Test
+  public void should_create_empty_change() {
+    IssueChange change = IssueChange.create();
+    assertThat(change.urlParams()).isEmpty();
+  }
+
+  @Test
+  public void should_create_change() {
+    IssueChange change = IssueChange.create()
+      .comment("this is a comment")
+      .assignee("lancelot")
+      .attribute("JIRA", "FOO-1234")
+      .attribute("LINK", null)
+      .cost(4.2)
+      .resolution("FIXED")
+      .severity("BLOCKER");
+    assertThat(change.urlParams()).hasSize(7).includes(
+      entry("newComment", "this is a comment"),
+      entry("newAssignee", "lancelot"),
+      entry("newAttr[JIRA]", "FOO-1234"),
+      entry("newAttr[LINK]", ""),
+      entry("newCost", 4.2),
+      entry("newResolution", "FIXED"),
+      entry("newSeverity", "BLOCKER")
+    );
+  }
+}
diff --git a/sonar-ws-client/src/test/java/org/sonar/wsclient/issue/IssueParserTest.java b/sonar-ws-client/src/test/java/org/sonar/wsclient/issue/IssueParserTest.java
new file mode 100644 (file)
index 0000000..46d714e
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.wsclient.issue;
+
+import org.apache.commons.io.IOUtils;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class IssueParserTest {
+  @Test
+  public void test_GET_search() throws Exception {
+    String json = IOUtils.toString(getClass().getResourceAsStream("/org/sonar/wsclient/issue/IssueParserTest/search.json"));
+    Issues issues = new IssueParser().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.ruleKey()).isEqualTo("squid:AvoidCycle");
+    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.description()).isEqualTo("the desc");
+    assertThat(first.cost()).isEqualTo(4.2);
+    assertThat(first.title()).isEqualTo("the title");
+    assertThat(first.userLogin()).isEqualTo("perceval");
+    assertThat(first.createdAt()).isNotNull();
+    assertThat(first.updatedAt()).isNotNull();
+    assertThat(first.closedAt()).isNotNull();
+    assertThat(first.attribute("JIRA")).isEqualTo("FOO-1234");
+    assertThat(first.attribute("OTHER")).isNull();
+    assertThat(first.attributes()).hasSize(1);
+
+    Issue second = list.get(1);
+    assertThat(second.key()).isEqualTo("FGHIJ");
+    assertThat(second.line()).isNull();
+    assertThat(second.cost()).isNull();
+    assertThat(second.description()).isNull();
+    assertThat(second.attribute("JIRA")).isNull();
+    assertThat(second.attributes()).isEmpty();
+  }
+}
diff --git a/sonar-ws-client/src/test/java/org/sonar/wsclient/issue/IssueQueryTest.java b/sonar-ws-client/src/test/java/org/sonar/wsclient/issue/IssueQueryTest.java
new file mode 100644 (file)
index 0000000..429ce55
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.wsclient.issue;
+
+import org.junit.Test;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.MapAssert.entry;
+
+public class IssueQueryTest {
+  @Test
+  public void get_all_issues() {
+    IssueQuery query = IssueQuery.create();
+    assertThat(query.urlParams()).isEmpty();
+  }
+
+  @Test
+  public void get_all_issues_by_parameter() {
+    IssueQuery query = IssueQuery.create()
+      .keys("ABCDE", "FGHIJ")
+      .assignees("arthur", "perceval")
+      .components("Action.java", "Filter.java")
+      .componentRoots("struts")
+      .resolutions("FIXED", "FALSE-POSITIVE")
+      .rules("squid:AvoidCycle")
+      .statuses("OPEN", "CLOSED")
+      .severities("BLOCKER", "INFO")
+      .userLogins("login1", "login2")
+      .limit(5)
+      .offset(4);
+
+    assertThat(query.urlParams()).hasSize(11);
+    assertThat(query.urlParams()).includes(entry("keys", "ABCDE,FGHIJ"));
+    assertThat(query.urlParams()).includes(entry("assignees", "arthur,perceval"));
+    assertThat(query.urlParams()).includes(entry("components", "Action.java,Filter.java"));
+    assertThat(query.urlParams()).includes(entry("componentRoots", "struts"));
+    assertThat(query.urlParams()).includes(entry("resolutions", "FIXED,FALSE-POSITIVE"));
+    assertThat(query.urlParams()).includes(entry("statuses", "OPEN,CLOSED"));
+    assertThat(query.urlParams()).includes(entry("severities", "BLOCKER,INFO"));
+    assertThat(query.urlParams()).includes(entry("userLogins", "login1,login2"));
+    assertThat(query.urlParams()).includes(entry("limit", 5));
+    assertThat(query.urlParams()).includes(entry("offset", 4));
+  }
+
+  @Test
+  public void should_ignore_null_values() {
+    IssueQuery query = IssueQuery.create().severities(null);
+    assertThat(query.urlParams()).isEmpty();
+  }
+}
diff --git a/sonar-ws-client/src/test/java/org/sonar/wsclient/issue/NewIssueTest.java b/sonar-ws-client/src/test/java/org/sonar/wsclient/issue/NewIssueTest.java
new file mode 100644 (file)
index 0000000..af6d105
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar 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.
+ *
+ * Sonar 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 Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.wsclient.issue;
+
+import org.junit.Test;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.MapAssert.entry;
+
+public class NewIssueTest {
+  @Test
+  public void test_create_empty() {
+    assertThat(NewIssue.create().urlParams()).isEmpty();
+  }
+
+  @Test
+  public void test_create() {
+    NewIssue newIssue = NewIssue.create()
+      .component("Action.java")
+      .cost(4.2)
+      .description("the desc")
+      .line(123)
+      .rule("squid:AvoidCycle")
+      .severity("BLOCKER")
+      .title("the title")
+      .attribute("JIRA", "FOO-123");
+
+    assertThat(newIssue.urlParams()).hasSize(8).includes(
+      entry("component", "Action.java"),
+      entry("cost", 4.2),
+      entry("desc", "the desc"),
+      entry("line", 123),
+      entry("rule", "squid:AvoidCycle"),
+      entry("severity", "BLOCKER"),
+      entry("title", "the title"),
+      entry("attr[JIRA]", "FOO-123")
+    );
+  }
+}
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
new file mode 100644 (file)
index 0000000..85b84ce
--- /dev/null
@@ -0,0 +1,32 @@
+{
+  "issues": [
+    {
+      "key": "ABCDE",
+      "component": "Action.java",
+      "rule": "squid:AvoidCycle",
+      "severity": "CRITICAL",
+      "line": 10,
+      "resolution": "FIXED",
+      "status": "OPEN",
+      "assignee": "karadoc",
+      "cost": 4.2,
+      "desc": "the desc",
+      "title": "the title",
+      "userLogin": "perceval",
+      "createdAt": "2013-05-18T12:45:34+0200",
+      "updatedAt": "2013-05-18T13:45:34+0200",
+      "closedAt": "2013-05-18T14:45:34+0200",
+      "attr": {
+        "JIRA": "FOO-1234"
+      }
+    },
+    {
+      "key": "FGHIJ",
+      "component": "Filter.java",
+      "rule": "squid:NullRef",
+      "severity": "BLOCKER",
+      "resolution": "FIXED",
+      "status": "OPEN"
+    }
+  ]
+}
\ No newline at end of file