]> source.dussan.org Git - sonarqube.git/commitdiff
ws-client should not throw HttpException by default
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Wed, 2 Dec 2015 10:40:43 +0000 (11:40 +0100)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Wed, 2 Dec 2015 13:16:49 +0000 (14:16 +0100)
38 files changed:
it/it-tests/src/test/java/it/authorisation/AuthenticationTest.java
sonar-batch/src/main/java/org/sonar/batch/analysis/AnalysisWSLoaderProvider.java
sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java
sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchWsClient.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchWsClientProvider.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java
sonar-batch/src/main/java/org/sonar/batch/bootstrap/WsClientLoggingInterceptor.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/bootstrap/WsClientProvider.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/cache/StrategyWSLoaderProvider.java
sonar-batch/src/main/java/org/sonar/batch/cache/WSLoader.java
sonar-batch/src/main/java/org/sonar/batch/report/ReportPublisher.java
sonar-batch/src/main/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoader.java
sonar-batch/src/test/java/org/sonar/batch/analysis/AnalysisWSLoaderProviderTest.java
sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginInstallerTest.java
sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchWsClientProviderTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchWsClientTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/bootstrap/WsClientLoggingInterceptorTest.java [deleted file]
sonar-batch/src/test/java/org/sonar/batch/bootstrap/WsClientProviderTest.java [deleted file]
sonar-batch/src/test/java/org/sonar/batch/cache/StrategyWSLoaderProviderTest.java
sonar-batch/src/test/java/org/sonar/batch/cache/WSLoaderTest.java
sonar-batch/src/test/java/org/sonar/batch/report/ReportPublisherTest.java
sonar-batch/src/test/java/org/sonar/batch/repository/DefaultProjectRepositoriesLoaderTest.java
sonar-ws/src/main/java/org/sonarqube/ws/client/BaseResponse.java [new file with mode: 0644]
sonar-ws/src/main/java/org/sonarqube/ws/client/BaseService.java
sonar-ws/src/main/java/org/sonarqube/ws/client/HttpConnector.java
sonar-ws/src/main/java/org/sonarqube/ws/client/HttpException.java
sonar-ws/src/main/java/org/sonarqube/ws/client/HttpResponse.java
sonar-ws/src/main/java/org/sonarqube/ws/client/HttpWsClient.java
sonar-ws/src/main/java/org/sonarqube/ws/client/MockWsResponse.java
sonar-ws/src/main/java/org/sonarqube/ws/client/WsClient.java
sonar-ws/src/main/java/org/sonarqube/ws/client/WsConnector.java
sonar-ws/src/main/java/org/sonarqube/ws/client/WsResponse.java
sonar-ws/src/main/java/org/sonarqube/ws/client/ce/ComputeEngineService.java [deleted file]
sonar-ws/src/main/java/org/sonarqube/ws/client/ce/SubmitWsRequest.java [deleted file]
sonar-ws/src/main/java/org/sonarqube/ws/client/ce/package-info.java [deleted file]
sonar-ws/src/test/java/org/sonarqube/ws/client/BaseServiceTest.java
sonar-ws/src/test/java/org/sonarqube/ws/client/HttpConnectorTest.java
sonar-ws/src/test/java/org/sonarqube/ws/client/HttpExceptionTest.java

index 7288287edbe8f1708cb3520367d7b6c8a1a07064..806b6935183445d0a28b9e68346de5ce89d52da8 100644 (file)
@@ -93,7 +93,7 @@ public class AuthenticationTest {
     // authenticate
     WsClient wsClient = new HttpWsClient(new HttpConnector.Builder().url(ORCHESTRATOR.getServer().getUrl()).credentials(login, password).build());
     WsResponse response = wsClient.wsConnector().call(new GetRequest("api/authentication/validate"));
-    assertThat(response.getContent()).isEqualTo("{\"valid\":true}");
+    assertThat(response.content()).isEqualTo("{\"valid\":true}");
   }
 
   @Test
@@ -107,7 +107,7 @@ public class AuthenticationTest {
 
     WsResponse response = wsClient.wsConnector().call(new GetRequest("api/authentication/validate"));
 
-    assertThat(response.getContent()).isEqualTo("{\"valid\":true}");
+    assertThat(response.content()).isEqualTo("{\"valid\":true}");
   }
 
   /**
@@ -126,7 +126,7 @@ public class AuthenticationTest {
     // authenticate
     WsClient wsClient = new HttpWsClient(new HttpConnector.Builder().url(ORCHESTRATOR.getServer().getUrl()).credentials(login, password).build());
     WsResponse response = wsClient.wsConnector().call(new GetRequest("api/authentication/validate"));
-    assertThat(response.getContent()).isEqualTo("{\"valid\":false}");
+    assertThat(response.content()).isEqualTo("{\"valid\":false}");
   }
 
   @Test
index 328478d3f87515cdd3658db3ba1fa8c362c40ec1..7ca47ae787a51bb6d5a8d175be3a3ca0f76ba820 100644 (file)
@@ -21,15 +21,15 @@ package org.sonar.batch.analysis;
 
 import org.picocontainer.injectors.ProviderAdapter;
 import org.sonar.api.batch.AnalysisMode;
+import org.sonar.batch.bootstrap.BatchWsClient;
 import org.sonar.batch.cache.WSLoader;
 import org.sonar.batch.cache.WSLoader.LoadStrategy;
 import org.sonar.home.cache.PersistentCache;
-import org.sonarqube.ws.client.WsClient;
 
 public class AnalysisWSLoaderProvider extends ProviderAdapter {
   private WSLoader wsLoader;
 
-  public WSLoader provide(AnalysisMode mode, PersistentCache cache, WsClient client) {
+  public WSLoader provide(AnalysisMode mode, PersistentCache cache, BatchWsClient client) {
     if (wsLoader == null) {
       // recreate cache directory if needed for this analysis
       cache.reconfigure();
index 56b4199e8e6bcf0e3b2fdae5f5ba37a2ca3b5ebb..950d3e9baf3702fc20a98abfbb9e1df79e9ffe47 100644 (file)
@@ -42,7 +42,6 @@ import org.sonar.core.platform.RemotePlugin;
 import org.sonar.core.platform.RemotePluginFile;
 import org.sonar.home.cache.FileCache;
 import org.sonarqube.ws.client.GetRequest;
-import org.sonarqube.ws.client.WsClient;
 import org.sonarqube.ws.client.WsResponse;
 
 import static java.lang.String.format;
@@ -59,9 +58,9 @@ public class BatchPluginInstaller implements PluginInstaller {
   private final WSLoader wsLoader;
   private final FileCache fileCache;
   private final BatchPluginPredicate pluginPredicate;
-  private final WsClient wsClient;
+  private final BatchWsClient wsClient;
 
-  public BatchPluginInstaller(WSLoader wsLoader, WsClient wsClient, FileCache fileCache, BatchPluginPredicate pluginPredicate) {
+  public BatchPluginInstaller(WSLoader wsLoader, BatchWsClient wsClient, FileCache fileCache, BatchPluginPredicate pluginPredicate) {
     this.wsLoader = wsLoader;
     this.fileCache = fileCache;
     this.pluginPredicate = pluginPredicate;
@@ -151,8 +150,8 @@ public class BatchPluginInstaller implements PluginInstaller {
         LOG.info("Download {}", filename);
       }
 
-      WsResponse response = wsClient.wsConnector().call(new GetRequest(url));
-      try (InputStream stream = response.getContentStream()) {
+      WsResponse response = wsClient.call(new GetRequest(url));
+      try (InputStream stream = response.contentStream()) {
         FileUtils.copyInputStreamToFile(stream, toFile);
       }
     }
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchWsClient.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchWsClient.java
new file mode 100644 (file)
index 0000000..16bac82
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.batch.bootstrap;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import java.net.HttpURLConnection;
+import java.util.ArrayList;
+import java.util.List;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.StringUtils;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.utils.MessageException;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.api.utils.log.Profiler;
+import org.sonarqube.ws.client.HttpException;
+import org.sonarqube.ws.client.WsClient;
+import org.sonarqube.ws.client.WsConnector;
+import org.sonarqube.ws.client.WsRequest;
+import org.sonarqube.ws.client.WsResponse;
+
+import static java.lang.String.format;
+
+public class BatchWsClient {
+
+  private static final Logger LOG = Loggers.get(BatchWsClient.class);
+
+  private final WsClient target;
+  private final boolean hasCredentials;
+  private final String publicBaseUrl;
+
+  public BatchWsClient(WsClient target, boolean hasCredentials, @Nullable String publicBaseUrl) {
+    this.target = target;
+    this.hasCredentials = hasCredentials;
+    if (StringUtils.isBlank(publicBaseUrl)) {
+      this.publicBaseUrl = target.wsConnector().baseUrl();
+    } else {
+      this.publicBaseUrl = publicBaseUrl.replaceAll("(/)+$", "") + "/";
+    }
+  }
+
+  /**
+   * @throws IllegalStateException if the request could not be executed due to
+   *     a connectivity problem or timeout. Because networks can
+   *     fail during an exchange, it is possible that the remote server
+   *     accepted the request before the failure
+   * @throws HttpException if the response code is not in range [200..300)
+   */
+  public WsResponse call(WsRequest request) {
+    Profiler profiler = Profiler.createIfDebug(LOG).start();
+    WsResponse response = target.wsConnector().call(request);
+    profiler.stopDebug(format("%s %d %s", request.getMethod(), response.code(), response.requestUrl()));
+    failIfUnauthorized(response);
+    return response;
+  }
+
+  public String baseUrl() {
+    return target.wsConnector().baseUrl();
+  }
+
+  /**
+   * The public URL is optionally configured on server. If not, then the regular {@link #baseUrl()} is returned.
+   * URL has a trailing slash.
+   * See https://jira.sonarsource.com/browse/SONAR-4239
+   */
+  public String publicBaseUrl() {
+    return publicBaseUrl;
+  }
+
+  @VisibleForTesting
+  WsConnector wsConnector() {
+    return target.wsConnector();
+  }
+
+  private void failIfUnauthorized(WsResponse response) {
+    if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
+      if (hasCredentials) {
+        // credentials are not valid
+        throw MessageException.of(format("Not authorized. Please check the properties %s and %s.",
+          CoreProperties.LOGIN, CoreProperties.PASSWORD));
+      }
+      // not authenticated - see https://jira.sonarsource.com/browse/SONAR-4048
+      throw MessageException.of(format("Not authorized. Analyzing this project requires to be authenticated. " +
+        "Please provide the values of the properties %s and %s.", CoreProperties.LOGIN, CoreProperties.PASSWORD));
+
+    }
+    if (response.code() == HttpURLConnection.HTTP_FORBIDDEN) {
+      // SONAR-4397 Details are in response content
+      throw MessageException.of(tryParseAsJsonError(response.content()));
+    }
+    response.failIfNotSuccessful();
+  }
+
+  private static String tryParseAsJsonError(String responseContent) {
+    try {
+      JsonParser parser = new JsonParser();
+      JsonObject obj = parser.parse(responseContent).getAsJsonObject();
+      JsonArray errors = obj.getAsJsonArray("errors");
+      List<String> errorMessages = new ArrayList<>();
+      for (JsonElement e : errors) {
+        errorMessages.add(e.getAsJsonObject().get("msg").getAsString());
+      }
+      return Joiner.on(", ").join(errorMessages);
+    } catch (Exception e) {
+      return responseContent;
+    }
+  }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchWsClientProvider.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchWsClientProvider.java
new file mode 100644 (file)
index 0000000..a8ac402
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.batch.bootstrap;
+
+import org.picocontainer.injectors.ProviderAdapter;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.batch.BatchSide;
+import org.sonar.batch.bootstrapper.EnvironmentInformation;
+import org.sonarqube.ws.client.HttpConnector;
+import org.sonarqube.ws.client.HttpWsClient;
+
+import static java.lang.Integer.parseInt;
+import static java.lang.String.valueOf;
+import static org.apache.commons.lang.StringUtils.defaultIfBlank;
+
+@BatchSide
+public class BatchWsClientProvider extends ProviderAdapter {
+
+  static final int CONNECT_TIMEOUT_MS = 5_000;
+  static final String READ_TIMEOUT_SEC_PROPERTY = "sonar.ws.timeout";
+  static final int DEFAULT_READ_TIMEOUT_SEC = 60;
+
+  private BatchWsClient wsClient;
+
+  public synchronized BatchWsClient provide(final GlobalProperties settings, final EnvironmentInformation env) {
+    if (wsClient == null) {
+      String url = defaultIfBlank(settings.property("sonar.host.url"), CoreProperties.SERVER_BASE_URL_DEFAULT_VALUE);
+      HttpConnector.Builder builder = new HttpConnector.Builder();
+
+      // TODO proxy
+
+      String timeoutSec = defaultIfBlank(settings.property(READ_TIMEOUT_SEC_PROPERTY), valueOf(DEFAULT_READ_TIMEOUT_SEC));
+      String login = defaultIfBlank(settings.property(CoreProperties.LOGIN), null);
+      builder
+        .readTimeoutMilliseconds(parseInt(timeoutSec) * 1_000)
+        .connectTimeoutMilliseconds(CONNECT_TIMEOUT_MS)
+        .userAgent(env.toString())
+        .url(url)
+        .credentials(login, settings.property(CoreProperties.PASSWORD));
+
+      wsClient = new BatchWsClient(new HttpWsClient(builder.build()), login != null, settings.property(CoreProperties.SERVER_BASE_URL));
+    }
+    return wsClient;
+  }
+}
index 40b5cacb9d1f1d5a21102a88b669a6c8c9419210..0440645b5c33c7d6cffff8483f7626bf698e97af 100644 (file)
@@ -91,7 +91,7 @@ public class GlobalContainer extends ComponentContainer {
 
       CachesManager.class,
       GlobalSettings.class,
-      new WsClientProvider(),
+      new BatchWsClientProvider(),
       DefaultServer.class,
       new GlobalTempFolderProvider(),
       DefaultHttpDownloader.class,
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/WsClientLoggingInterceptor.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/WsClientLoggingInterceptor.java
deleted file mode 100644 (file)
index f94bf08..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.batch.bootstrap;
-
-import com.google.common.base.Joiner;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
-import com.squareup.okhttp.Interceptor;
-import com.squareup.okhttp.Request;
-import com.squareup.okhttp.Response;
-import java.io.IOException;
-import java.net.HttpURLConnection;
-import java.util.ArrayList;
-import java.util.List;
-import org.apache.commons.lang.StringUtils;
-import org.sonar.api.CoreProperties;
-import org.sonar.api.utils.MessageException;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
-import org.sonar.api.utils.log.Profiler;
-
-import static java.lang.String.format;
-
-public class WsClientLoggingInterceptor implements Interceptor {
-
-  private static final Logger LOG = Loggers.get(WsClientLoggingInterceptor.class);
-
-  @Override
-  public Response intercept(Chain chain) throws IOException {
-    Response response = logAndSendRequest(chain);
-    if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
-      if (StringUtils.isBlank(response.request().header("Authorization"))) {
-        // not authenticated - see https://jira.sonarsource.com/browse/SONAR-4048
-        throw MessageException.of(format("Not authorized. Analyzing this project requires to be authenticated. " +
-          "Please provide the values of the properties %s and %s.", CoreProperties.LOGIN, CoreProperties.PASSWORD));
-      }
-      // credentials are not valid
-      throw MessageException.of(format("Not authorized. Please check the properties %s and %s.",
-        CoreProperties.LOGIN, CoreProperties.PASSWORD));
-    }
-    if (response.code() == HttpURLConnection.HTTP_FORBIDDEN) {
-      // SONAR-4397 Details are in response content
-      throw MessageException.of(tryParseAsJsonError(response.body().string()));
-    }
-    return response;
-  }
-
-  private Response logAndSendRequest(Chain chain) throws IOException {
-    Request request = chain.request();
-    Response response;
-    Profiler profiler = Profiler.createIfDebug(LOG).startTrace(format("%s %s", request.method(), request.url()));
-    response = chain.proceed(request);
-    profiler.stopDebug(format("%s %d %s", request.method(), response.code(), request.url()));
-    return response;
-  }
-
-  private static String tryParseAsJsonError(String responseContent) {
-    try {
-      JsonParser parser = new JsonParser();
-      JsonObject obj = parser.parse(responseContent).getAsJsonObject();
-      JsonArray errors = obj.getAsJsonArray("errors");
-      List<String> errorMessages = new ArrayList<>();
-      for (JsonElement e : errors) {
-        errorMessages.add(e.getAsJsonObject().get("msg").getAsString());
-      }
-      return Joiner.on(", ").join(errorMessages);
-    } catch (Exception e) {
-      return responseContent;
-    }
-  }
-}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/WsClientProvider.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/WsClientProvider.java
deleted file mode 100644 (file)
index 78e50cc..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.batch.bootstrap;
-
-import org.picocontainer.injectors.ProviderAdapter;
-import org.sonar.api.CoreProperties;
-import org.sonar.api.batch.BatchSide;
-import org.sonar.batch.bootstrapper.EnvironmentInformation;
-import org.sonarqube.ws.client.HttpConnector;
-import org.sonarqube.ws.client.HttpWsClient;
-import org.sonarqube.ws.client.WsClient;
-
-import static java.lang.Integer.parseInt;
-import static java.lang.String.valueOf;
-import static org.apache.commons.lang.StringUtils.defaultIfBlank;
-
-@BatchSide
-public class WsClientProvider extends ProviderAdapter {
-
-  static final int CONNECT_TIMEOUT_MS = 5_000;
-  static final String READ_TIMEOUT_SEC_PROPERTY = "sonar.ws.timeout";
-  static final int DEFAULT_READ_TIMEOUT_SEC = 60;
-
-  private HttpWsClient wsClient;
-
-  public synchronized WsClient provide(final GlobalProperties settings, final EnvironmentInformation env) {
-    if (wsClient == null) {
-      String url = defaultIfBlank(settings.property("sonar.host.url"), CoreProperties.SERVER_BASE_URL_DEFAULT_VALUE);
-      HttpConnector.Builder builder = new HttpConnector.Builder();
-
-      // TODO proxy
-
-      String timeoutSec = defaultIfBlank(settings.property(READ_TIMEOUT_SEC_PROPERTY), valueOf(DEFAULT_READ_TIMEOUT_SEC));
-      builder
-        .readTimeoutMilliseconds(parseInt(timeoutSec) * 1_000)
-        .connectTimeoutMilliseconds(CONNECT_TIMEOUT_MS)
-        .userAgent(env.toString())
-        .url(url)
-        .credentials(settings.property(CoreProperties.LOGIN), settings.property(CoreProperties.PASSWORD))
-        .interceptor(new WsClientLoggingInterceptor());
-
-      wsClient = new HttpWsClient(builder.build());
-    }
-    return wsClient;
-  }
-}
index e7c573753dfa01eb11e6952ed8f053380c6f01d0..e6f7d6b00cb316c3bc93bc1a6f32abf14f401e25 100644 (file)
@@ -20,9 +20,9 @@
 package org.sonar.batch.cache;
 
 import org.picocontainer.injectors.ProviderAdapter;
+import org.sonar.batch.bootstrap.BatchWsClient;
 import org.sonar.batch.cache.WSLoader.LoadStrategy;
 import org.sonar.home.cache.PersistentCache;
-import org.sonarqube.ws.client.WsClient;
 
 public class StrategyWSLoaderProvider extends ProviderAdapter {
   private final LoadStrategy strategy;
@@ -32,7 +32,7 @@ public class StrategyWSLoaderProvider extends ProviderAdapter {
     this.strategy = strategy;
   }
 
-  public WSLoader provide(PersistentCache cache, WsClient client) {
+  public WSLoader provide(PersistentCache cache, BatchWsClient client) {
     if (wsLoader == null) {
       wsLoader = new WSLoader(strategy, cache, client);
     }
index 69e7c14538f8a9d5885da4a2a6c0daaf2993e3dc..9864e574f9bbfc9bc21f0b0fe39c7c131b5d3cc0 100644 (file)
@@ -26,12 +26,13 @@ import java.nio.charset.StandardCharsets;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import org.apache.commons.io.IOUtils;
+import org.sonar.api.utils.MessageException;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
+import org.sonar.batch.bootstrap.BatchWsClient;
 import org.sonar.home.cache.PersistentCache;
 import org.sonarqube.ws.client.GetRequest;
 import org.sonarqube.ws.client.HttpException;
-import org.sonarqube.ws.client.WsClient;
 
 import static org.sonar.batch.cache.WSLoader.ServerStatus.ACCESSIBLE;
 import static org.sonar.batch.cache.WSLoader.ServerStatus.NOT_ACCESSIBLE;
@@ -50,7 +51,7 @@ public class WSLoader {
   }
 
   private final LoadStrategy defautLoadStrategy;
-  private final WsClient client;
+  private final BatchWsClient wsClient;
   private final PersistentCache cache;
   private ServerStatus serverStatus;
 
@@ -58,7 +59,7 @@ public class WSLoader {
     @Override
     public String load(String id) throws IOException {
       GetRequest getRequest = new GetRequest(id);
-      try (Reader reader = client.wsConnector().call(getRequest).getContentReader()) {
+      try (Reader reader = wsClient.call(getRequest).contentReader()) {
         String str = IOUtils.toString(reader);
         try {
           cache.put(id, str.getBytes(StandardCharsets.UTF_8));
@@ -81,7 +82,7 @@ public class WSLoader {
     @Override
     public InputStream load(String id) throws IOException {
       GetRequest getRequest = new GetRequest(id);
-      try (InputStream is = client.wsConnector().call(getRequest).getContentStream()) {
+      try (InputStream is = wsClient.call(getRequest).contentStream()) {
         try {
           cache.put(id, is);
         } catch (IOException e) {
@@ -111,11 +112,11 @@ public class WSLoader {
     }
   }
 
-  public WSLoader(LoadStrategy strategy, PersistentCache cache, WsClient client) {
+  public WSLoader(LoadStrategy strategy, PersistentCache cache, BatchWsClient wsClient) {
     this.defautLoadStrategy = strategy;
     this.serverStatus = UNKNOWN;
     this.cache = cache;
-    this.client = client;
+    this.wsClient = wsClient;
   }
 
   @Nonnull
@@ -193,7 +194,7 @@ public class WSLoader {
           throw new IllegalStateException(FAIL_MSG, serverNotAvailable.getCause());
         }
       }
-      throw new IllegalStateException("Server is not available: " + client.wsConnector().baseUrl(), serverNotAvailable.getCause());
+      throw new IllegalStateException("Server is not available: " + wsClient.baseUrl(), serverNotAvailable.getCause());
     }
   }
 
@@ -201,7 +202,6 @@ public class WSLoader {
     T load(String id) throws IOException;
   }
 
-  @Nonnull
   private <T> WSLoaderResult<T> loadFromCache(String id, DataLoader<T> loader) throws NotAvailableException {
     T result;
 
@@ -217,7 +217,6 @@ public class WSLoader {
     return new WSLoaderResult<>(result, true);
   }
 
-  @Nonnull
   private <T> WSLoaderResult<T> loadFromServer(String id, DataLoader<T> loader) throws NotAvailableException {
     if (isOffline()) {
       throw new NotAvailableException("Server not available");
@@ -226,11 +225,10 @@ public class WSLoader {
       T t = loader.load(id);
       switchToOnline();
       return new WSLoaderResult<>(t, false);
+    } catch (HttpException | MessageException e) {
+      // fail fast if it could connect but there was a application-level error
+      throw e;
     } catch (IllegalStateException e) {
-      if (e.getCause() instanceof HttpException) {
-        // fail fast if it could connect but there was a application-level error
-        throw e;
-      }
       switchToOffline();
       throw new NotAvailableException(e);
     } catch (Exception e) {
index 209dd29712f0edc0ea06234502eb821ae79c8e22..524a9a034f5ca59ea3f0b07e4f09d071571e3f44 100644 (file)
 package org.sonar.batch.report;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Throwables;
 import com.google.common.io.Files;
 import com.squareup.okhttp.HttpUrl;
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.Writer;
 import java.net.URL;
 import java.nio.charset.StandardCharsets;
@@ -42,11 +44,13 @@ import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
 import org.sonar.api.utils.text.JsonWriter;
 import org.sonar.batch.analysis.DefaultAnalysisMode;
+import org.sonar.batch.bootstrap.BatchWsClient;
 import org.sonar.batch.protocol.output.BatchReportWriter;
 import org.sonar.batch.scan.ImmutableProjectReactor;
+import org.sonarqube.ws.MediaTypes;
 import org.sonarqube.ws.WsCe;
-import org.sonarqube.ws.client.WsClient;
-import org.sonarqube.ws.client.ce.SubmitWsRequest;
+import org.sonarqube.ws.client.PostRequest;
+import org.sonarqube.ws.client.WsResponse;
 
 import static org.apache.commons.lang.StringUtils.defaultIfBlank;
 
@@ -60,7 +64,7 @@ public class ReportPublisher implements Startable {
   public static final String METADATA_DUMP_FILENAME = "analysis-details.json";
 
   private final Settings settings;
-  private final WsClient wsClient;
+  private final BatchWsClient wsClient;
   private final AnalysisContextReportPublisher contextPublisher;
   private final ImmutableProjectReactor projectReactor;
   private final DefaultAnalysisMode analysisMode;
@@ -70,7 +74,7 @@ public class ReportPublisher implements Startable {
   private File reportDir;
   private BatchReportWriter writer;
 
-  public ReportPublisher(Settings settings, WsClient wsClient, AnalysisContextReportPublisher contextPublisher,
+  public ReportPublisher(Settings settings, BatchWsClient wsClient, AnalysisContextReportPublisher contextPublisher,
     ImmutableProjectReactor projectReactor, DefaultAnalysisMode analysisMode, TempFolder temp, ReportPublisherStep[] publishers) {
     this.settings = settings;
     this.wsClient = wsClient;
@@ -145,15 +149,22 @@ public class ReportPublisher implements Startable {
     LOG.debug("Upload report");
     long startTime = System.currentTimeMillis();
     ProjectDefinition projectDefinition = projectReactor.getRoot();
-    SubmitWsRequest submitRequest = new SubmitWsRequest();
-    submitRequest.setProjectKey(projectDefinition.getKey());
-    submitRequest.setProjectName(projectDefinition.getName());
-    submitRequest.setProjectBranch(projectDefinition.getBranch());
-    submitRequest.setReport(report);
-    WsCe.SubmitResponse submitResponse = wsClient.computeEngine().submit(submitRequest);
-    long stopTime = System.currentTimeMillis();
-    LOG.info("Analysis report uploaded in " + (stopTime - startTime) + "ms");
-    return submitResponse.getTaskId();
+    PostRequest.Part filePart = new PostRequest.Part(MediaTypes.ZIP, report);
+    PostRequest post = new PostRequest("api/ce/submit")
+      .setMediaType(MediaTypes.PROTOBUF)
+      .setParam("projectKey", projectDefinition.getKey())
+      .setParam("projectName", projectDefinition.getName())
+      .setParam("projectBranch", projectDefinition.getBranch())
+      .setPart("report", filePart);
+    WsResponse response = wsClient.call(post).failIfNotSuccessful();
+    try (InputStream protobuf = response.contentStream()) {
+      return WsCe.SubmitResponse.parser().parseFrom(protobuf).getTaskId();
+    } catch (Exception e) {
+      throw Throwables.propagate(e);
+    } finally {
+      long stopTime = System.currentTimeMillis();
+      LOG.info("Analysis report uploaded in " + (stopTime - startTime) + "ms");
+    }
   }
 
   @VisibleForTesting
@@ -165,13 +176,13 @@ public class ReportPublisher implements Startable {
       String effectiveKey = projectReactor.getRoot().getKeyWithBranch();
       metadata.put("projectKey", effectiveKey);
 
-      URL dashboardUrl = HttpUrl.parse(publicUrl()).newBuilder()
+      URL dashboardUrl = HttpUrl.parse(wsClient.publicBaseUrl()).newBuilder()
         .addPathSegment("dashboard").addPathSegment("index").addPathSegment(effectiveKey)
         .build()
         .url();
       metadata.put("dashboardUrl", dashboardUrl.toExternalForm());
 
-      URL taskUrl = HttpUrl.parse(publicUrl()).newBuilder()
+      URL taskUrl = HttpUrl.parse(wsClient.publicBaseUrl()).newBuilder()
         .addPathSegment("api").addPathSegment("ce").addPathSegment("task")
         .addQueryParameter("id", taskId)
         .build()
@@ -202,12 +213,4 @@ public class ReportPublisher implements Startable {
       throw new IllegalStateException("Unable to dump " + file, e);
     }
   }
-
-  /**
-   * The public URL is optionally configured on server. If not, then the regular URL is returned.
-   * See https://jira.sonarsource.com/browse/SONAR-4239
-   */
-  private String publicUrl() {
-    return defaultIfBlank(settings.getString(CoreProperties.SERVER_BASE_URL), wsClient.wsConnector().baseUrl());
-  }
 }
index 3a00c304aaac0fae515a0558f1c0fa0ef159c2a7..8168c676b8ab7f07808e8597e70ccc1b98cf0403 100644 (file)
@@ -36,6 +36,7 @@ import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang.mutable.MutableBoolean;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.sonar.api.utils.MessageException;
 import org.sonar.batch.cache.WSLoader;
 import org.sonar.batch.cache.WSLoaderResult;
 import org.sonar.batch.util.BatchUtils;
@@ -88,6 +89,9 @@ public class DefaultProjectRepositoriesLoader implements ProjectRepositoriesLoad
         HttpException http = (HttpException) t;
         return http.code() != HttpURLConnection.HTTP_NOT_FOUND;
       }
+      if (t instanceof MessageException) {
+        return true;
+      }
     }
 
     return false;
index 5687ebdc27c88307dfb1fb77eeab1aca7c3ab903..b175faa6bc67464dd98e20d9edbf4bf0a556db27 100644 (file)
  */
 package org.sonar.batch.analysis;
 
-import org.junit.Before;
 import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 import org.sonar.api.batch.AnalysisMode;
+import org.sonar.batch.bootstrap.BatchWsClient;
 import org.sonar.batch.cache.WSLoader;
 import org.sonar.batch.cache.WSLoader.LoadStrategy;
 import org.sonar.home.cache.PersistentCache;
-import org.sonarqube.ws.client.WsClient;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
 
 public class AnalysisWSLoaderProviderTest {
-  @Mock
-  private PersistentCache cache;
 
-  @Mock
-  private WsClient client;
+  PersistentCache cache = mock(PersistentCache.class);
+  BatchWsClient wsClient = mock(BatchWsClient.class);
+  AnalysisMode mode = mock(AnalysisMode.class);
 
-  @Mock
-  private AnalysisMode mode;
-
-  private AnalysisWSLoaderProvider loaderProvider;
-
-  @Before
-  public void setUp() {
-    MockitoAnnotations.initMocks(this);
-    loaderProvider = new AnalysisWSLoaderProvider();
-  }
+  AnalysisWSLoaderProvider underTest = new AnalysisWSLoaderProvider();
 
   @Test
   public void testDefault() {
-    WSLoader loader = loaderProvider.provide(mode, cache, client);
+    WSLoader loader = underTest.provide(mode, cache, wsClient);
     assertThat(loader.getDefaultStrategy()).isEqualTo(LoadStrategy.SERVER_ONLY);
   }
 }
index fdc159569a07edf38065ff20004d4b44e92ae1a0..0acdb08e87cc3c0ade7a4818c7cdb8e0262c8fc6 100644 (file)
@@ -29,7 +29,6 @@ import org.sonar.batch.cache.WSLoader;
 import org.sonar.batch.cache.WSLoaderResult;
 import org.sonar.core.platform.RemotePlugin;
 import org.sonar.home.cache.FileCache;
-import org.sonarqube.ws.client.WsClient;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Matchers.any;
@@ -47,7 +46,7 @@ public class BatchPluginInstallerTest {
   public ExpectedException thrown = ExpectedException.none();
 
   FileCache fileCache = mock(FileCache.class);
-  WsClient wsClient = mock(WsClient.class);
+  BatchWsClient wsClient = mock(BatchWsClient.class);
   BatchPluginPredicate pluginPredicate = mock(BatchPluginPredicate.class);
 
   @Test
@@ -55,9 +54,9 @@ public class BatchPluginInstallerTest {
 
     WSLoader wsLoader = mock(WSLoader.class);
     when(wsLoader.loadString("/deploy/plugins/index.txt")).thenReturn(new WSLoaderResult<>("checkstyle\nsqale", true));
-    BatchPluginInstaller installer = new BatchPluginInstaller(wsLoader, wsClient, fileCache, pluginPredicate);
+    BatchPluginInstaller underTest = new BatchPluginInstaller(wsLoader, wsClient, fileCache, pluginPredicate);
 
-    List<RemotePlugin> remotePlugins = installer.listRemotePlugins();
+    List<RemotePlugin> remotePlugins = underTest.listRemotePlugins();
     assertThat(remotePlugins).extracting("key").containsOnly("checkstyle", "sqale");
   }
 
@@ -67,10 +66,10 @@ public class BatchPluginInstallerTest {
     when(fileCache.get(eq("checkstyle-plugin.jar"), eq("fakemd5_1"), any(FileCache.Downloader.class))).thenReturn(pluginJar);
 
     WSLoader wsLoader = mock(WSLoader.class);
-    BatchPluginInstaller installer = new BatchPluginInstaller(wsLoader, wsClient, fileCache, pluginPredicate);
+    BatchPluginInstaller underTest = new BatchPluginInstaller(wsLoader, wsClient, fileCache, pluginPredicate);
 
     RemotePlugin remote = new RemotePlugin("checkstyle").setFile("checkstyle-plugin.jar", "fakemd5_1");
-    File file = installer.download(remote);
+    File file = underTest.download(remote);
 
     assertThat(file).isEqualTo(pluginJar);
   }
diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchWsClientProviderTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchWsClientProviderTest.java
new file mode 100644 (file)
index 0000000..3b19135
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.batch.bootstrap;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Test;
+import org.sonar.batch.bootstrapper.EnvironmentInformation;
+import org.sonarqube.ws.client.HttpConnector;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class BatchWsClientProviderTest {
+
+  BatchWsClientProvider underTest = new BatchWsClientProvider();
+  EnvironmentInformation env = new EnvironmentInformation("Maven Plugin", "2.3");
+
+  @Test
+  public void provide_client_with_default_settings() {
+    GlobalProperties settings = new GlobalProperties(new HashMap<String, String>());
+
+    BatchWsClient client = underTest.provide(settings, env);
+
+    assertThat(client).isNotNull();
+    assertThat(client.baseUrl()).isEqualTo("http://localhost:9000/");
+    assertThat(client.publicBaseUrl()).isEqualTo("http://localhost:9000/");
+    HttpConnector httpConnector = (HttpConnector) client.wsConnector();
+    assertThat(httpConnector.baseUrl()).isEqualTo("http://localhost:9000/");
+    assertThat(httpConnector.okHttpClient().getProxy()).isNull();
+    assertThat(httpConnector.okHttpClient().getConnectTimeout()).isEqualTo(5_000);
+    assertThat(httpConnector.okHttpClient().getReadTimeout()).isEqualTo(60_000);
+    assertThat(httpConnector.userAgent()).isEqualTo("Maven Plugin/2.3");
+  }
+
+  @Test
+  public void provide_client_with_custom_settings() {
+    Map<String, String> props = new HashMap<>();
+    props.put("sonar.host.url", "https://here/sonarqube");
+    props.put("sonar.login", "theLogin");
+    props.put("sonar.password", "thePassword");
+    props.put("sonar.ws.timeout", "42");
+    GlobalProperties settings = new GlobalProperties(props);
+
+    BatchWsClient client = underTest.provide(settings, env);
+
+    assertThat(client).isNotNull();
+    HttpConnector httpConnector = (HttpConnector) client.wsConnector();
+    assertThat(httpConnector.baseUrl()).isEqualTo("https://here/sonarqube/");
+    assertThat(httpConnector.okHttpClient().getProxy()).isNull();
+    assertThat(httpConnector.userAgent()).isEqualTo("Maven Plugin/2.3");
+  }
+
+  @Test
+  public void build_singleton() {
+    GlobalProperties settings = new GlobalProperties(new HashMap<String, String>());
+    BatchWsClient first = underTest.provide(settings, env);
+    BatchWsClient second = underTest.provide(settings, env);
+    assertThat(first).isSameAs(second);
+  }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchWsClientTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchWsClientTest.java
new file mode 100644 (file)
index 0000000..f7b9a21
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.batch.bootstrap;
+
+import java.util.List;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mockito.Mockito;
+import org.sonar.api.utils.MessageException;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonarqube.ws.client.GetRequest;
+import org.sonarqube.ws.client.MockWsResponse;
+import org.sonarqube.ws.client.WsClient;
+import org.sonarqube.ws.client.WsRequest;
+import org.sonarqube.ws.client.WsResponse;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class BatchWsClientTest {
+
+  @Rule
+  public LogTester logTester = new LogTester();
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  WsClient wsClient = mock(WsClient.class, Mockito.RETURNS_DEEP_STUBS);
+
+  @Test
+  public void define_public_url() {
+    when(wsClient.wsConnector().baseUrl()).thenReturn("https://local/");
+    BatchWsClient underTest = new BatchWsClient(wsClient, true, "https://public/");
+    assertThat(underTest.baseUrl()).isEqualTo("https://local/");
+    assertThat(underTest.publicBaseUrl()).isEqualTo("https://public/");
+  }
+
+  /**
+   * Returned URL has trailing slash, even if configured URL doesn't have.
+   * That's useful for {@link com.squareup.okhttp.HttpUrl}
+   */
+  @Test
+  public void public_url_has_trailing_slash() {
+    BatchWsClient underTest = new BatchWsClient(wsClient, true, "https://public");
+    assertThat(underTest.publicBaseUrl()).isEqualTo("https://public/");
+  }
+
+
+  @Test
+  public void public_url_is_the_base_url_by_default() {
+    when(wsClient.wsConnector().baseUrl()).thenReturn("https://local/");
+    BatchWsClient underTest = new BatchWsClient(wsClient, true, null);
+    assertThat(underTest.publicBaseUrl()).isEqualTo("https://local/");
+  }
+
+  @Test
+  public void log_and_profile_request_if_debug_level() throws Exception {
+    WsRequest request = newRequest();
+    WsResponse response = newResponse().setRequestUrl("https://local/api/issues/search");
+    when(wsClient.wsConnector().call(request)).thenReturn(response);
+
+    logTester.setLevel(LoggerLevel.DEBUG);
+    BatchWsClient underTest = new BatchWsClient(wsClient, false, null);
+
+    WsResponse result = underTest.call(request);
+
+    // do not fail the execution -> interceptor returns the response
+    assertThat(result).isSameAs(response);
+
+    // check logs
+    List<String> debugLogs = logTester.logs(LoggerLevel.DEBUG);
+    assertThat(debugLogs).hasSize(1);
+    assertThat(debugLogs.get(0)).contains("GET 200 https://local/api/issues/search | time=");
+  }
+
+  @Test
+  public void fail_if_requires_credentials() throws Exception {
+    expectedException.expect(MessageException.class);
+    expectedException
+      .expectMessage("Not authorized. Analyzing this project requires to be authenticated. Please provide the values of the properties sonar.login and sonar.password.");
+
+    WsRequest request = newRequest();
+    WsResponse response = newResponse().setCode(401);
+    when(wsClient.wsConnector().call(request)).thenReturn(response);
+
+    new BatchWsClient(wsClient, false, null).call(request);
+  }
+
+  @Test
+  public void fail_if_credentials_are_not_valid() throws Exception {
+    expectedException.expect(MessageException.class);
+    expectedException.expectMessage("Not authorized. Please check the properties sonar.login and sonar.password.");
+
+    WsRequest request = newRequest();
+    WsResponse response = newResponse().setCode(401);
+    when(wsClient.wsConnector().call(request)).thenReturn(response);
+
+    new BatchWsClient(wsClient, /* credentials are configured */true, null).call(request);
+  }
+
+  @Test
+  public void fail_if_requires_permission() throws Exception {
+    expectedException.expect(MessageException.class);
+    expectedException.expectMessage("missing scan permission, missing another permission");
+
+    WsRequest request = newRequest();
+    WsResponse response = newResponse()
+      .setCode(403)
+      .setContent("{\"errors\":[{\"msg\":\"missing scan permission\"}, {\"msg\":\"missing another permission\"}]}");
+    when(wsClient.wsConnector().call(request)).thenReturn(response);
+
+    new BatchWsClient(wsClient, true, null).call(request);
+  }
+
+  private MockWsResponse newResponse() {
+    return new MockWsResponse().setRequestUrl("https://local/api/issues/search");
+  }
+
+  private WsRequest newRequest() {
+    return new GetRequest("api/issues/search");
+  }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/WsClientLoggingInterceptorTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/WsClientLoggingInterceptorTest.java
deleted file mode 100644 (file)
index 4655371..0000000
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.batch.bootstrap;
-
-import com.squareup.okhttp.Interceptor;
-import com.squareup.okhttp.MediaType;
-import com.squareup.okhttp.Protocol;
-import com.squareup.okhttp.Request;
-import com.squareup.okhttp.Response;
-import com.squareup.okhttp.ResponseBody;
-import java.util.List;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.api.utils.MessageException;
-import org.sonar.api.utils.log.LogTester;
-import org.sonar.api.utils.log.LoggerLevel;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class WsClientLoggingInterceptorTest {
-
-  @Rule
-  public LogTester logTester = new LogTester();
-
-  @Rule
-  public ExpectedException expectedException = ExpectedException.none();
-
-  WsClientLoggingInterceptor underTest = new WsClientLoggingInterceptor();
-  Interceptor.Chain chain = mock(Interceptor.Chain.class);
-
-  @Test
-  public void log_and_profile_request_if_debug_level() throws Exception {
-    Request request = newRequest();
-    Response response = newResponse(request, 200, "");
-    when(chain.request()).thenReturn(request);
-    when(chain.proceed(request)).thenReturn(response);
-
-    logTester.setLevel(LoggerLevel.DEBUG);
-    Response result = underTest.intercept(chain);
-
-    // do not fail the execution -> interceptor returns the response
-    assertThat(result).isSameAs(response);
-
-    // check logs
-    List<String> debugLogs = logTester.logs(LoggerLevel.DEBUG);
-    assertThat(debugLogs).hasSize(1);
-    assertThat(debugLogs.get(0)).contains("GET 200 https://localhost:9000/api/issues/search | time=");
-    List<String> traceLogs = logTester.logs(LoggerLevel.TRACE);
-    assertThat(traceLogs).hasSize(1);
-    assertThat(traceLogs.get(0)).isEqualTo("GET https://localhost:9000/api/issues/search");
-  }
-
-  @Test
-  public void fail_if_requires_authentication() throws Exception {
-    expectedException.expect(MessageException.class);
-    expectedException
-      .expectMessage("Not authorized. Analyzing this project requires to be authenticated. Please provide the values of the properties sonar.login and sonar.password.");
-
-    Request request = newRequest();
-    Response response = newResponse(request, 401, "");
-    when(chain.request()).thenReturn(request);
-    when(chain.proceed(request)).thenReturn(response);
-
-    underTest.intercept(chain);
-  }
-
-  @Test
-  public void fail_if_credentials_are_not_valid() throws Exception {
-    expectedException.expect(MessageException.class);
-    expectedException.expectMessage("Not authorized. Please check the properties sonar.login and sonar.password.");
-
-    Request request = new Request.Builder()
-      .url("https://localhost:9000/api/issues/search")
-      .header("Authorization", "Basic BAD_CREDENTIALS")
-      .get()
-      .build();
-    Response response = newResponse(request, 401, "");
-    when(chain.request()).thenReturn(request);
-    when(chain.proceed(request)).thenReturn(response);
-
-    underTest.intercept(chain);
-  }
-
-  @Test
-  public void fail_if_requires_permission() throws Exception {
-    expectedException.expect(MessageException.class);
-    expectedException.expectMessage("missing scan permission, missing another permission");
-
-    Request request = newRequest();
-    Response response = newResponse(request, 403, "{\"errors\":[{\"msg\":\"missing scan permission\"}, {\"msg\":\"missing another permission\"}]}");
-    when(chain.request()).thenReturn(request);
-    when(chain.proceed(request)).thenReturn(response);
-
-    underTest.intercept(chain);
-  }
-
-  private Request newRequest() {
-    return new Request.Builder().url("https://localhost:9000/api/issues/search").get().build();
-  }
-
-  private Response newResponse(Request getRequest, int code, String jsonBody) {
-    return new Response.Builder().request(getRequest)
-      .code(code)
-      .protocol(Protocol.HTTP_1_1)
-      .body(ResponseBody.create(MediaType.parse("application/json"), jsonBody))
-      .build();
-  }
-
-}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/WsClientProviderTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/WsClientProviderTest.java
deleted file mode 100644 (file)
index ac5a9c8..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.batch.bootstrap;
-
-import java.util.HashMap;
-import java.util.Map;
-import org.junit.Test;
-import org.sonar.batch.bootstrapper.EnvironmentInformation;
-import org.sonarqube.ws.client.HttpConnector;
-import org.sonarqube.ws.client.WsClient;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.batch.bootstrap.WsClientProvider.CONNECT_TIMEOUT_MS;
-import static org.sonar.batch.bootstrap.WsClientProvider.DEFAULT_READ_TIMEOUT_SEC;
-
-public class WsClientProviderTest {
-
-  WsClientProvider underTest = new WsClientProvider();
-  EnvironmentInformation env = new EnvironmentInformation("Maven Plugin", "2.3");
-
-  @Test
-  public void provide_client_with_default_settings() {
-    GlobalProperties settings = new GlobalProperties(new HashMap<String, String>());
-
-    WsClient client = underTest.provide(settings, env);
-
-    assertThat(client).isNotNull();
-    HttpConnector httpConnector = (HttpConnector) client.wsConnector();
-    assertThat(httpConnector.baseUrl()).isEqualTo("http://localhost:9000/");
-    assertThat(httpConnector.okHttpClient().getProxy()).isNull();
-    assertThat(httpConnector.okHttpClient().getConnectTimeout()).isEqualTo(5_000);
-    assertThat(httpConnector.okHttpClient().getReadTimeout()).isEqualTo(60_000);
-    assertThat(httpConnector.userAgent()).isEqualTo("Maven Plugin/2.3");
-    assertThat(httpConnector.okHttpClient().interceptors())
-      .hasSize(1)
-      .hasOnlyElementsOfType(WsClientLoggingInterceptor.class);
-  }
-
-  @Test
-  public void provide_client_with_custom_settings() {
-    Map<String, String> props = new HashMap<>();
-    props.put("sonar.host.url", "https://here/sonarqube");
-    props.put("sonar.login", "theLogin");
-    props.put("sonar.password", "thePassword");
-    props.put("sonar.ws.timeout", "42");
-    GlobalProperties settings = new GlobalProperties(props);
-
-    WsClient client = underTest.provide(settings, env);
-
-    assertThat(client).isNotNull();
-    HttpConnector httpConnector = (HttpConnector) client.wsConnector();
-    assertThat(httpConnector.baseUrl()).isEqualTo("https://here/sonarqube/");
-    assertThat(httpConnector.okHttpClient().getProxy()).isNull();
-    assertThat(httpConnector.userAgent()).isEqualTo("Maven Plugin/2.3");
-  }
-
-  @Test
-  public void build_singleton() {
-    GlobalProperties settings = new GlobalProperties(new HashMap<String, String>());
-    WsClient first = underTest.provide(settings, env);
-    WsClient second = underTest.provide(settings, env);
-    assertThat(first).isSameAs(second);
-  }
-}
index 4134b7f6653d930dd4ce0d496eff4596ed759b3e..0b36dfd399748a7c0cd7186603b892038b12aa50 100644 (file)
@@ -23,9 +23,9 @@ import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.sonar.batch.bootstrap.BatchWsClient;
 import org.sonar.batch.cache.WSLoader.LoadStrategy;
 import org.sonar.home.cache.PersistentCache;
-import org.sonarqube.ws.client.WsClient;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
@@ -34,7 +34,7 @@ public class StrategyWSLoaderProviderTest {
   private PersistentCache cache;
 
   @Mock
-  private WsClient client;
+  private BatchWsClient client;
 
   @Before
   public void setUp() {
index 2db585445f8b1c212ac4781441631637ecb50d64..8b553e8eb02ce511101540fbc852b1245aaa505f 100644 (file)
@@ -28,11 +28,11 @@ import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.mockito.InOrder;
 import org.mockito.Mockito;
+import org.sonar.batch.bootstrap.BatchWsClient;
 import org.sonar.batch.cache.WSLoader.LoadStrategy;
 import org.sonar.home.cache.PersistentCache;
 import org.sonarqube.ws.client.HttpException;
 import org.sonarqube.ws.client.MockWsResponse;
-import org.sonarqube.ws.client.WsClient;
 import org.sonarqube.ws.client.WsConnector;
 import org.sonarqube.ws.client.WsRequest;
 
@@ -55,7 +55,7 @@ public class WSLoaderTest {
   @Rule
   public ExpectedException exception = ExpectedException.none();
 
-  WsClient ws = mock(WsClient.class, Mockito.RETURNS_DEEP_STUBS);
+  BatchWsClient ws = mock(BatchWsClient.class, Mockito.RETURNS_DEEP_STUBS);
   PersistentCache cache = mock(PersistentCache.class);
 
   @Test
@@ -88,7 +88,7 @@ public class WSLoaderTest {
   public void put_stream_in_cache() throws IOException {
     InputStream input = IOUtils.toInputStream("is");
 
-    when(ws.wsConnector().call(any(WsRequest.class))).thenReturn(new MockWsResponse().setContent(input));
+    when(ws.call(any(WsRequest.class))).thenReturn(new MockWsResponse().setContent(input));
     when(cache.getStream(ID)).thenReturn(input);
 
     // SERVER_FIRST -> load from server then put to cache
@@ -96,25 +96,24 @@ public class WSLoaderTest {
     WSLoaderResult<InputStream> result = underTest.loadStream(ID);
     assertThat(result.get()).isEqualTo(input);
 
-    WsConnector wsConnector = ws.wsConnector();
-    InOrder inOrder = inOrder(wsConnector, cache);
-    inOrder.verify(wsConnector).call(any(WsRequest.class));
+    InOrder inOrder = inOrder(ws, cache);
+    inOrder.verify(ws).call(any(WsRequest.class));
     inOrder.verify(cache).put(eq(ID), any(InputStream.class));
     inOrder.verify(cache).getStream(ID);
-    verifyNoMoreInteractions(cache, wsConnector);
+    verifyNoMoreInteractions(cache, ws);
   }
 
   @Test
   public void test_cache_strategy_fallback() throws IOException {
     turnCacheEmpty();
-    when(ws.wsConnector().call(any(WsRequest.class))).thenReturn(new MockWsResponse().setContent(serverValue));
+    when(ws.call(any(WsRequest.class))).thenReturn(new MockWsResponse().setContent(serverValue));
     WSLoader loader = new WSLoader(LoadStrategy.CACHE_FIRST, cache, ws);
 
     assertResult(loader.loadString(ID), serverValue, false);
 
-    InOrder inOrder = inOrder(ws.wsConnector(), cache);
+    InOrder inOrder = inOrder(ws, cache);
     inOrder.verify(cache).getString(ID);
-    inOrder.verify(ws.wsConnector()).call(any(WsRequest.class));
+    inOrder.verify(ws).call(any(WsRequest.class));
   }
 
   @Test
@@ -125,14 +124,14 @@ public class WSLoaderTest {
 
     assertResult(loader.loadString(ID), cacheValue, true);
 
-    InOrder inOrder = inOrder(ws.wsConnector(), cache);
-    inOrder.verify(ws.wsConnector()).call(any(WsRequest.class));
+    InOrder inOrder = inOrder(ws, cache);
+    inOrder.verify(ws).call(any(WsRequest.class));
     inOrder.verify(cache).getString(ID);
   }
 
   @Test
   public void test_put_cache() throws IOException {
-    when(ws.wsConnector().call(any(WsRequest.class))).thenReturn(new MockWsResponse().setContent(serverValue));
+    when(ws.call(any(WsRequest.class))).thenReturn(new MockWsResponse().setContent(serverValue));
     WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, ws);
     loader.loadString(ID);
     verify(cache).put(ID, serverValue.getBytes());
@@ -171,17 +170,14 @@ public class WSLoaderTest {
 
   @Test
   public void test_throw_http_exceptions() {
-    HttpException httpException = new HttpException("url", 500, "Internal Error");
-    IllegalStateException wrapperException = new IllegalStateException(httpException);
-
-    when(ws.wsConnector().call(any(WsRequest.class))).thenThrow(wrapperException);
+    when(ws.call(any(WsRequest.class))).thenThrow(new HttpException("url", 500));
 
     WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, ws);
 
     try {
       loader.loadString(ID);
       fail("IllegalStateException expected");
-    } catch (IllegalStateException e) {
+    } catch (HttpException e) {
       // cache should not be used
       verifyNoMoreInteractions(cache);
     }
@@ -223,7 +219,7 @@ public class WSLoaderTest {
 
   @Test
   public void test_server_strategy() throws IOException {
-    when(ws.wsConnector().call(any(WsRequest.class))).thenReturn(new MockWsResponse().setContent(serverValue));
+    when(ws.call(any(WsRequest.class))).thenReturn(new MockWsResponse().setContent(serverValue));
     WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, ws);
     assertResult(loader.loadString(ID), serverValue, false);
 
@@ -241,7 +237,7 @@ public class WSLoaderTest {
 
   @Test
   public void test_string() {
-    when(ws.wsConnector().call(any(WsRequest.class))).thenReturn(new MockWsResponse().setContent(serverValue));
+    when(ws.call(any(WsRequest.class))).thenReturn(new MockWsResponse().setContent(serverValue));
     WSLoader loader = new WSLoader(LoadStrategy.SERVER_FIRST, cache, ws);
     assertResult(loader.loadString(ID), serverValue, false);
   }
@@ -251,7 +247,7 @@ public class WSLoaderTest {
   }
 
   private void assertUsedServer(int times) {
-    verify(ws.wsConnector(), times(times)).call(any(WsRequest.class));
+    verify(ws, times(times)).call(any(WsRequest.class));
   }
 
   private void assertResult(WSLoaderResult<String> result, String expected, boolean fromCache) {
@@ -261,7 +257,7 @@ public class WSLoaderTest {
   }
 
   private void turnServerOffline() {
-    when(ws.wsConnector().call(any(WsRequest.class))).thenThrow(new IllegalStateException());
+    when(ws.call(any(WsRequest.class))).thenThrow(new IllegalStateException());
   }
 
   private void turnCacheEmpty() throws IOException {
index d1c5e1a93c60a38cab406be06f20dbb1a19cdd1f..dce14a13b8ea31d2bd3377fa5633a240b08fccf8 100644 (file)
@@ -35,9 +35,9 @@ import org.sonar.api.utils.TempFolder;
 import org.sonar.api.utils.log.LogTester;
 import org.sonar.api.utils.log.LoggerLevel;
 import org.sonar.batch.analysis.DefaultAnalysisMode;
+import org.sonar.batch.bootstrap.BatchWsClient;
 import org.sonar.batch.scan.ImmutableProjectReactor;
 import org.sonar.test.JsonAssert;
-import org.sonarqube.ws.client.WsClient;
 
 import static org.apache.commons.io.FileUtils.readFileToString;
 import static org.assertj.core.api.Assertions.assertThat;
@@ -54,7 +54,7 @@ public class ReportPublisherTest {
 
   DefaultAnalysisMode mode = mock(DefaultAnalysisMode.class);
   Settings settings = new Settings();
-  WsClient wsClient = mock(WsClient.class, Mockito.RETURNS_DEEP_STUBS);
+  BatchWsClient wsClient = mock(BatchWsClient.class, Mockito.RETURNS_DEEP_STUBS);
   ImmutableProjectReactor reactor = mock(ImmutableProjectReactor.class);
   ProjectDefinition root;
   AnalysisContextReportPublisher contextPublisher = mock(AnalysisContextReportPublisher.class);
@@ -63,7 +63,8 @@ public class ReportPublisherTest {
   public void setUp() {
     root = ProjectDefinition.create().setKey("struts").setWorkDir(temp.getRoot());
     when(reactor.getRoot()).thenReturn(root);
-    when(wsClient.wsConnector().baseUrl()).thenReturn("https://localhost");
+    when(wsClient.baseUrl()).thenReturn("https://localhost/");
+    when(wsClient.publicBaseUrl()).thenReturn("https://public/");
   }
 
   @Test
@@ -73,41 +74,20 @@ public class ReportPublisherTest {
     underTest.logSuccess("TASK-123");
 
     assertThat(logTester.logs(LoggerLevel.INFO))
-      .contains("ANALYSIS SUCCESSFUL, you can browse https://localhost/dashboard/index/struts")
+      .contains("ANALYSIS SUCCESSFUL, you can browse https://public/dashboard/index/struts")
       .contains("Note that you will be able to access the updated dashboard once the server has processed the submitted analysis report")
-      .contains("More about the report processing at https://localhost/api/ce/task?id=TASK-123");
+      .contains("More about the report processing at https://public/api/ce/task?id=TASK-123");
 
     File detailsFile = new File(temp.getRoot(), "analysis-details.json");
     JsonAssert.assertJson(readFileToString(detailsFile)).isSimilarTo("{" +
       "\"projectKey\": \"struts\"," +
-      "\"dashboardUrl\": \"https://localhost/dashboard/index/struts\"," +
+      "\"dashboardUrl\": \"https://public/dashboard/index/struts\"," +
       "\"ceTaskId\": \"TASK-123\"," +
-      "\"ceTaskUrl\": \"https://localhost/api/ce/task?id=TASK-123\"" +
+      "\"ceTaskUrl\": \"https://public/api/ce/task?id=TASK-123\"" +
       "}"
       );
   }
 
-  @Test
-  public void log_public_url_if_defined() throws IOException {
-    settings.setProperty(CoreProperties.SERVER_BASE_URL, "https://publicserver/sonarqube");
-    ReportPublisher underTest = new ReportPublisher(settings, wsClient, contextPublisher, reactor, mode, mock(TempFolder.class), new ReportPublisherStep[0]);
-
-    underTest.logSuccess("TASK-123");
-
-    assertThat(logTester.logs(LoggerLevel.INFO))
-      .contains("ANALYSIS SUCCESSFUL, you can browse https://publicserver/sonarqube/dashboard/index/struts")
-      .contains("More about the report processing at https://publicserver/sonarqube/api/ce/task?id=TASK-123");
-
-    File detailsFile = new File(temp.getRoot(), "analysis-details.json");
-    JsonAssert.assertJson(readFileToString(detailsFile)).isSimilarTo("{" +
-      "\"projectKey\": \"struts\"," +
-      "\"dashboardUrl\": \"https://publicserver/sonarqube/dashboard/index/struts\"," +
-      "\"ceTaskId\": \"TASK-123\"," +
-      "\"ceTaskUrl\": \"https://publicserver/sonarqube/api/ce/task?id=TASK-123\"" +
-      "}"
-    );
-  }
-
   @Test
   public void log_but_not_dump_information_when_report_is_not_uploaded() {
     ReportPublisher underTest = new ReportPublisher(settings, wsClient, contextPublisher, reactor, mode, mock(TempFolder.class), new ReportPublisherStep[0]);
index e0eebd486d3e2568e617a8d4af1a590f319726fa..5d59cf28121b761e51ee7f56600d278f9920c2d1 100644 (file)
@@ -75,7 +75,7 @@ public class DefaultProjectRepositoriesLoaderTest {
 
   @Test(expected = IllegalStateException.class)
   public void failFastHttpError() {
-    HttpException http = new HttpException("url", 403, "Forbidden");
+    HttpException http = new HttpException("url", 403);
     IllegalStateException e = new IllegalStateException("http error", http);
     when(wsLoader.loadStream(anyString())).thenThrow(e);
     loader.load(PROJECT_KEY, false, null);
@@ -86,7 +86,7 @@ public class DefaultProjectRepositoriesLoaderTest {
     thrown.expect(MessageException.class);
     thrown.expectMessage("http error");
     
-    HttpException http = new HttpException("uri", 403, "Forbidden");
+    HttpException http = new HttpException("uri", 403);
     MessageException e = MessageException.of("http error", http);
     when(wsLoader.loadStream(anyString())).thenThrow(e);
     loader.load(PROJECT_KEY, false, null);
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/BaseResponse.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/BaseResponse.java
new file mode 100644 (file)
index 0000000..d649f61
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonarqube.ws.client;
+
+abstract class BaseResponse implements WsResponse {
+
+  @Override
+  public boolean isSuccessful() {
+    return code() >= 200 && code() < 300;
+  }
+
+  @Override
+  public WsResponse failIfNotSuccessful() {
+    if (!isSuccessful()) {
+      throw new HttpException(requestUrl(), code());
+    }
+    return this;
+  }
+
+}
index 11d4d0064195af311e448015fad80748af923e1c..fea0cecd61528f79aef36fcfb3062302d8dc3e7b 100644 (file)
@@ -40,20 +40,20 @@ public abstract class BaseService {
 
   protected <T extends Message> T call(BaseRequest request, Parser<T> parser) {
     request.setMediaType(MediaTypes.PROTOBUF);
-    WsResponse response = wsConnector.call(request);
+    WsResponse response = call(request);
     return convert(response, parser);
   }
 
   protected WsResponse call(WsRequest request) {
-    return wsConnector.call(request);
+    return wsConnector.call(request).failIfNotSuccessful();
   }
 
   public <T extends Message> T convert(WsResponse response, Parser<T> parser) {
-    try (InputStream byteStream = response.getContentStream()) {
+    try (InputStream byteStream = response.contentStream()) {
       // HTTP header "Content-Type" is not verified. It may be different than protobuf.
       return parser.parseFrom(byteStream);
     } catch (Exception e) {
-      throw new IllegalStateException("Fail to parse protobuf response of " + response.getRequestUrl(), e);
+      throw new IllegalStateException("Fail to parse protobuf response of " + response.requestUrl(), e);
     }
   }
 
index 7f46d08cacb105a3563c9ae7f846c55716c3ce22..c76672924c52b34777c43089e4be734757874096 100644 (file)
@@ -23,7 +23,6 @@ import com.squareup.okhttp.Call;
 import com.squareup.okhttp.Credentials;
 import com.squareup.okhttp.Headers;
 import com.squareup.okhttp.HttpUrl;
-import com.squareup.okhttp.Interceptor;
 import com.squareup.okhttp.MediaType;
 import com.squareup.okhttp.MultipartBuilder;
 import com.squareup.okhttp.OkHttpClient;
@@ -32,8 +31,6 @@ import com.squareup.okhttp.RequestBody;
 import com.squareup.okhttp.Response;
 import java.io.IOException;
 import java.net.Proxy;
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 import javax.annotation.CheckForNull;
@@ -84,7 +81,6 @@ public class HttpConnector implements WsConnector {
 
     this.okHttpClient.setConnectTimeout(builder.connectTimeoutMs, TimeUnit.MILLISECONDS);
     this.okHttpClient.setReadTimeout(builder.readTimeoutMs, TimeUnit.MILLISECONDS);
-    this.okHttpClient.interceptors().addAll(builder.interceptors);
   }
 
   @Override
@@ -92,18 +88,13 @@ public class HttpConnector implements WsConnector {
     return baseUrl.url().toExternalForm();
   }
 
-  public OkHttpClient okHttpClient() {
-    return okHttpClient;
-  }
-
   @CheckForNull
   public String userAgent() {
     return userAgent;
   }
 
-  @CheckForNull
-  public String credentials() {
-    return credentials;
+  public OkHttpClient okHttpClient() {
+    return okHttpClient;
   }
 
   @Override
@@ -176,9 +167,6 @@ public class HttpConnector implements WsConnector {
     Call call = okHttpClient.newCall(okRequest);
     try {
       Response okResponse = call.execute();
-      if (!okResponse.isSuccessful()) {
-        throw new HttpException(okRequest.urlString(), okResponse.code(), okResponse.message());
-      }
       return new HttpResponse(okResponse);
     } catch (IOException e) {
       throw new IllegalStateException("Fail to request " + okRequest.urlString(), e);
@@ -195,7 +183,6 @@ public class HttpConnector implements WsConnector {
     private String proxyPassword;
     private int connectTimeoutMs = DEFAULT_CONNECT_TIMEOUT_MILLISECONDS;
     private int readTimeoutMs = DEFAULT_READ_TIMEOUT_MILLISECONDS;
-    private final List<Interceptor> interceptors = new ArrayList<>();
 
     /**
      * Optional User  Agent
@@ -260,15 +247,6 @@ public class HttpConnector implements WsConnector {
       return this;
     }
 
-    /**
-     * Adds a OkHttp interceptor, for example to log request URLs or response errors.
-     * See https://github.com/square/okhttp/wiki/Interceptors
-     */
-    public Builder interceptor(Interceptor interceptor) {
-      this.interceptors.add(interceptor);
-      return this;
-    }
-
     public HttpConnector build() {
       checkArgument(!isNullOrEmpty(url), "Server URL is not defined");
       return new HttpConnector(this);
index 393c6232f89e07609c554ae9fb6b9c6ad057b313..81fd1d69ab810159463bf5325ca81636c0e21c1a 100644 (file)
@@ -27,8 +27,8 @@ public class HttpException extends RuntimeException {
   private final String url;
   private final int code;
 
-  public HttpException(String url, int code, String message) {
-    super(String.format("Error %d on %s : %s", code, url, message));
+  public HttpException(String url, int code) {
+    super(String.format("Error %d on %s", code, url));
     this.url = url;
     this.code = code;
   }
index b2e3f2766e43f2ed877d172942c91eeb835f2eb1..0eac10b596c01578adad0df784314411caecda66 100644 (file)
@@ -26,7 +26,7 @@ import java.io.Reader;
 
 import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
 
-class HttpResponse implements WsResponse {
+class HttpResponse extends BaseResponse {
 
   private final Response okResponse;
 
@@ -35,7 +35,12 @@ class HttpResponse implements WsResponse {
   }
 
   @Override
-  public String getRequestUrl() {
+  public int code() {
+    return okResponse.code();
+  }
+
+  @Override
+  public String requestUrl() {
     return okResponse.request().urlString();
   }
 
@@ -45,7 +50,7 @@ class HttpResponse implements WsResponse {
   }
 
   @Override
-  public String getContentType() {
+  public String contentType() {
     return okResponse.header("Content-Type");
   }
 
@@ -53,7 +58,7 @@ class HttpResponse implements WsResponse {
    * Get stream of bytes
    */
   @Override
-  public InputStream getContentStream() {
+  public InputStream contentStream() {
     try {
       return okResponse.body().byteStream();
     } catch (IOException e) {
@@ -67,7 +72,7 @@ class HttpResponse implements WsResponse {
    * charset, this will attempt to decode the response body as UTF-8.
    */
   @Override
-  public Reader getContentReader() {
+  public Reader contentReader() {
     try {
       return okResponse.body().charStream();
     } catch (IOException e) {
@@ -76,7 +81,7 @@ class HttpResponse implements WsResponse {
   }
 
   @Override
-  public String getContent() {
+  public String content() {
     try {
       return okResponse.body().string();
     } catch (IOException e) {
@@ -85,6 +90,6 @@ class HttpResponse implements WsResponse {
   }
 
   private RuntimeException fail(Exception e) {
-    throw new IllegalStateException("Fail to read response of " + getRequestUrl(), e);
+    throw new IllegalStateException("Fail to read response of " + requestUrl(), e);
   }
 }
index e9e69a7e8b708b384ac3e108aea32f30dc299c4a..d172f4bbf8840a9eb6a5e7ad7b4d01ecd502dbb9 100644 (file)
@@ -20,7 +20,6 @@
 
 package org.sonarqube.ws.client;
 
-import org.sonarqube.ws.client.ce.ComputeEngineService;
 import org.sonarqube.ws.client.component.ComponentsService;
 import org.sonarqube.ws.client.issue.IssuesService;
 import org.sonarqube.ws.client.permission.PermissionsService;
@@ -34,7 +33,6 @@ import org.sonarqube.ws.client.usertoken.UserTokensService;
  */
 public class HttpWsClient implements WsClient {
 
-  private final ComputeEngineService ceWsClient;
   private final PermissionsService permissionsService;
   private final ComponentsService componentsService;
   private final QualityProfilesService qualityProfilesService;
@@ -44,7 +42,6 @@ public class HttpWsClient implements WsClient {
 
   public HttpWsClient(WsConnector wsConnector) {
     this.wsConnector = wsConnector;
-    this.ceWsClient = new ComputeEngineService(wsConnector);
     this.permissionsService = new PermissionsService(wsConnector);
     this.componentsService = new ComponentsService(wsConnector);
     this.qualityProfilesService = new QualityProfilesService(wsConnector);
@@ -62,11 +59,6 @@ public class HttpWsClient implements WsClient {
     return this.permissionsService;
   }
 
-  @Override
-  public ComputeEngineService computeEngine() {
-    return ceWsClient;
-  }
-
   @Override
   public ComponentsService components() {
     return componentsService;
index 00411fb86016d1ad37fa12f88e776a588f41aa32..54f1c05c556f7c30afa05b4f5b536ed7903d7b8d 100644 (file)
@@ -25,20 +25,32 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.Reader;
 import java.io.StringReader;
+import java.net.HttpURLConnection;
 import java.nio.charset.StandardCharsets;
 import org.apache.commons.io.IOUtils;
 import org.sonarqube.ws.MediaTypes;
 
 import static java.util.Objects.requireNonNull;
 
-public class MockWsResponse implements WsResponse {
+public class MockWsResponse extends BaseResponse {
 
+  private int code = HttpURLConnection.HTTP_OK;
   private String requestUrl;
   private byte[] content;
   private String contentType;
 
   @Override
-  public String getContentType() {
+  public int code() {
+    return code;
+  }
+
+  public MockWsResponse setCode(int code) {
+    this.code = code;
+    return this;
+  }
+
+  @Override
+  public String contentType() {
     requireNonNull(contentType);
     return contentType;
   }
@@ -77,25 +89,25 @@ public class MockWsResponse implements WsResponse {
   }
 
   @Override
-  public String getRequestUrl() {
+  public String requestUrl() {
     requireNonNull(requestUrl);
     return requestUrl;
   }
 
   @Override
-  public InputStream getContentStream() {
+  public InputStream contentStream() {
     requireNonNull(content);
     return new ByteArrayInputStream(content);
   }
 
   @Override
-  public Reader getContentReader() {
+  public Reader contentReader() {
     requireNonNull(content);
     return new StringReader(new String(content, StandardCharsets.UTF_8));
   }
 
   @Override
-  public String getContent() {
+  public String content() {
     requireNonNull(content);
     return new String(content, StandardCharsets.UTF_8);
   }
index 4b125627b5b0d37240e6b1cae3d1a1e7bc51fce2..3f980d7c5910ba4d41b93b6707519bf9176531ec 100644 (file)
@@ -19,7 +19,6 @@
  */
 package org.sonarqube.ws.client;
 
-import org.sonarqube.ws.client.ce.ComputeEngineService;
 import org.sonarqube.ws.client.component.ComponentsService;
 import org.sonarqube.ws.client.issue.IssuesService;
 import org.sonarqube.ws.client.permission.PermissionsService;
@@ -32,8 +31,6 @@ import org.sonarqube.ws.client.usertoken.UserTokensService;
 public interface WsClient {
   ComponentsService components();
 
-  ComputeEngineService computeEngine();
-
   IssuesService issues();
 
   PermissionsService permissions();
index d7754d598fc7bd56eb4439db9a00ba9cf6c3fa16..4d4829b6006827e5f244861a272c617c9e6fdb57 100644 (file)
@@ -25,18 +25,17 @@ package org.sonarqube.ws.client;
  */
 public interface WsConnector {
 
+  /**
+   * Server base URL, always with trailing slash, for instance "http://localhost:9000/"
+   */
+  String baseUrl();
+
   /**
    * @throws IllegalStateException if the request could not be executed due to
    *     a connectivity problem or timeout. Because networks can
    *     fail during an exchange, it is possible that the remote server
    *     accepted the request before the failure
-   * @throws HttpException if the response code is not in range [200..300)
    */
   WsResponse call(WsRequest wsRequest);
 
-  /**
-   * Server base URL, always with trailing slash, for instance "http://localhost:9000/"
-   */
-  String baseUrl();
-
 }
index c6550ffb44a14473713b4e91634265e951748b5a..d1045ba3d4e7eb50a989260f0ba23a108efb211e 100644 (file)
@@ -27,15 +27,35 @@ import java.io.Reader;
  */
 public interface WsResponse {
 
-  boolean hasContent();
+  /**
+   * The absolute requested URL
+   */
+  String requestUrl();
+
+  /**
+   * HTTP status code
+   */
+  int code();
+
+  /**
+   * Returns true if the code is in [200..300), which means the request was
+   * successfully received, understood, and accepted.
+   */
+  boolean isSuccessful() ;
 
-  String getContentType();
+  /**
+   * Throws a {@link HttpException} if {@link #isSuccessful()} is false.
+   */
+  WsResponse failIfNotSuccessful();
+
+  String contentType();
+
+  boolean hasContent();
 
-  String getRequestUrl();
+  InputStream contentStream();
 
-  InputStream getContentStream();
+  Reader contentReader();
 
-  Reader getContentReader();
+  String content();
 
-  String getContent();
 }
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/ComputeEngineService.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/ComputeEngineService.java
deleted file mode 100644 (file)
index 465b96d..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-package org.sonarqube.ws.client.ce;
-
-import org.sonarqube.ws.MediaTypes;
-import org.sonarqube.ws.WsCe;
-import org.sonarqube.ws.client.BaseService;
-import org.sonarqube.ws.client.PostRequest;
-import org.sonarqube.ws.client.WsConnector;
-
-public class ComputeEngineService extends BaseService {
-
-  public ComputeEngineService(WsConnector wsConnector) {
-    super(wsConnector, "api/ce");
-  }
-
-  public WsCe.SubmitResponse submit(SubmitWsRequest request) {
-    PostRequest.Part filePart = new PostRequest.Part(MediaTypes.ZIP, request.getReport());
-    PostRequest post = new PostRequest(path("submit"))
-      .setParam("projectKey", request.getProjectKey())
-      .setParam("projectName", request.getProjectName())
-      .setParam("projectBranch", request.getProjectBranch())
-      .setPart("report", filePart);
-    return call(post, WsCe.SubmitResponse.parser());
-  }
-}
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/SubmitWsRequest.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/SubmitWsRequest.java
deleted file mode 100644 (file)
index af414ee..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-package org.sonarqube.ws.client.ce;
-
-import java.io.File;
-import javax.annotation.CheckForNull;
-
-public class SubmitWsRequest {
-
-  private String projectKey;
-  private String projectName;
-  private String projectBranch;
-  private File report;
-
-  public String getProjectKey() {
-    return projectKey;
-  }
-
-  public SubmitWsRequest setProjectKey(String projectKey) {
-    this.projectKey = projectKey;
-    return this;
-  }
-
-  @CheckForNull
-  public String getProjectName() {
-    return projectName;
-  }
-
-  public SubmitWsRequest setProjectName(String projectName) {
-    this.projectName = projectName;
-    return this;
-  }
-
-  @CheckForNull
-  public String getProjectBranch() {
-    return projectBranch;
-  }
-
-  public SubmitWsRequest setProjectBranch(String projectBranch) {
-    this.projectBranch = projectBranch;
-    return this;
-  }
-
-  @CheckForNull
-  public File getReport() {
-    return report;
-  }
-
-  public SubmitWsRequest setReport(File report) {
-    this.report = report;
-    return this;
-  }
-}
diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/package-info.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/ce/package-info.java
deleted file mode 100644 (file)
index 8b9789b..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * SonarQube is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-@ParametersAreNonnullByDefault
-package org.sonarqube.ws.client.ce;
-
-import javax.annotation.ParametersAreNonnullByDefault;
-
index 2d92809cbd75e7d8e56df5a468b64ef6345f8645..5fbab7b84c92a504ed67d0649219d477792172d0 100644 (file)
@@ -43,7 +43,7 @@ public class BaseServiceTest {
 
         WsResponse response = call(get);
 
-        assertThat(response.getContent()).isEqualTo("ok");
+        assertThat(response.content()).isEqualTo("ok");
       }
 
     }.test();
@@ -68,6 +68,25 @@ public class BaseServiceTest {
     }.test();
   }
 
+  @Test
+  public void fail_if_http_error() {
+    new BaseService(wsConnector, "api/issues") {
+
+      public void test() {
+        GetRequest get = new GetRequest(path("issue")).setParam("key", "ABC");
+        when(wsConnector.call(get)).thenReturn(new MockWsResponse().setCode(403).setRequestUrl("https://local/foo"));
+
+        try {
+          call(get, Testing.Fake.parser());
+          fail();
+        } catch (HttpException e) {
+          assertThat(e.code()).isEqualTo(403);
+        }
+      }
+
+    }.test();
+  }
+
   @Test
   public void fail_to_parse_protobuf_response() {
     new BaseService(wsConnector, "api/issues") {
index 79362ea815f60aafeeda8543184c01d1fc593340..27f68d64bc7b51ef57e9695c99bde66cf191b9fa 100644 (file)
  */
 package org.sonarqube.ws.client;
 
-import com.squareup.okhttp.Interceptor;
-import com.squareup.okhttp.Response;
 import com.squareup.okhttp.mockwebserver.MockResponse;
 import com.squareup.okhttp.mockwebserver.MockWebServer;
 import com.squareup.okhttp.mockwebserver.RecordedRequest;
 import java.io.File;
-import java.io.IOException;
-import java.util.concurrent.atomic.AtomicBoolean;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang.StringUtils;
@@ -70,7 +66,7 @@ public class HttpConnectorTest {
 
     // verify response
     assertThat(response.hasContent()).isTrue();
-    assertThat(response.getContent()).isEqualTo("hello, world!");
+    assertThat(response.content()).isEqualTo("hello, world!");
 
     // verify the request received by server
     RecordedRequest recordedRequest = server.takeRequest();
@@ -196,7 +192,7 @@ public class HttpConnectorTest {
 
     // verify response
     assertThat(response.hasContent()).isTrue();
-    assertThat(response.getContent()).isEqualTo("hello, world!");
+    assertThat(response.content()).isEqualTo("hello, world!");
 
     // verify the request received by server
     RecordedRequest recordedRequest = server.takeRequest();
@@ -234,34 +230,8 @@ public class HttpConnectorTest {
     PostRequest request = new PostRequest("api/issues/search");
     HttpConnector underTest = new HttpConnector.Builder().url(serverUrl).build();
 
-    try {
-      underTest.call(request);
-      fail();
-    } catch (HttpException e) {
-      assertThat(e.code()).isEqualTo(404);
-
-    }
-  }
-
-  @Test
-  public void intercept_request_and_response() {
-    final AtomicBoolean called = new AtomicBoolean(false);
-    Interceptor interceptor = new Interceptor() {
-      @Override
-      public Response intercept(Chain chain) throws IOException {
-        called.set(true);
-        return chain.proceed(chain.request());
-      }
-    };
-
-    answerHelloWorld();
-    HttpConnector underTest = new HttpConnector.Builder()
-      .url(serverUrl)
-      .interceptor(interceptor)
-      .build();
-    underTest.call(new GetRequest(""));
-
-    assertThat(called.get()).isTrue();
+    WsResponse wsResponse = underTest.call(request);
+    assertThat(wsResponse.code()).isEqualTo(404);
   }
 
   @Test
@@ -284,11 +254,11 @@ public class HttpConnectorTest {
 
     GetRequest request = new GetRequest("api/issues/search");
     answerHelloWorld();
-    assertThat(underTest.call(request).getRequestUrl()).isEqualTo(serverUrl + "sonar/api/issues/search");
+    assertThat(underTest.call(request).requestUrl()).isEqualTo(serverUrl + "sonar/api/issues/search");
 
     request = new GetRequest("/api/issues/search");
     answerHelloWorld();
-    assertThat(underTest.call(request).getRequestUrl()).isEqualTo(serverUrl + "sonar/api/issues/search");
+    assertThat(underTest.call(request).requestUrl()).isEqualTo(serverUrl + "sonar/api/issues/search");
   }
 
   private void answerHelloWorld() {
index 2db49ea358b17645aec48fa601eab36a5bed3122..0e8a325e7010146da72149f231970181098bc497 100644 (file)
@@ -26,9 +26,9 @@ import static org.assertj.core.api.Assertions.assertThat;
 public class HttpExceptionTest {
   @Test
   public void test_exception() throws Exception {
-    HttpException exception = new HttpException("http://localhost:9000/api/search", 500, "Not found");
+    HttpException exception = new HttpException("http://localhost:9000/api/search", 500);
     assertThat(exception.code()).isEqualTo(500);
     assertThat(exception.url()).isEqualTo("http://localhost:9000/api/search");
-    assertThat(exception.getMessage()).isEqualTo("Error 500 on http://localhost:9000/api/search : Not found");
+    assertThat(exception.getMessage()).isEqualTo("Error 500 on http://localhost:9000/api/search");
   }
 }