diff options
author | Zipeng WU <zipeng.wu@sonarsource.com> | 2021-01-26 18:22:53 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2021-02-04 20:07:07 +0000 |
commit | c364b859dd4b1610dd7537b77f1152e620c5525a (patch) | |
tree | a3fbe7c270df158c7161d9875551a59be560de35 /server/sonar-alm-client/src | |
parent | 1f7132f59445a3a5a7c51724b770396dd8192c5e (diff) | |
download | sonarqube-c364b859dd4b1610dd7537b77f1152e620c5525a.tar.gz sonarqube-c364b859dd4b1610dd7537b77f1152e620c5525a.zip |
SONAR-14371 move Azure Devops client and WS actions to CE
Diffstat (limited to 'server/sonar-alm-client/src')
7 files changed, 688 insertions, 0 deletions
diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/azure/AzureDevOpsHttpClient.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/azure/AzureDevOpsHttpClient.java new file mode 100644 index 00000000000..c4f2f4f0c57 --- /dev/null +++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/azure/AzureDevOpsHttpClient.java @@ -0,0 +1,158 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * 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 02110-1301, USA. + */ +package org.sonar.alm.client.azure; + +import com.google.common.base.Strings; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonSyntaxException; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.nio.charset.StandardCharsets; +import java.util.function.Function; +import javax.annotation.Nullable; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.ResponseBody; +import org.apache.commons.codec.binary.Base64; +import org.sonar.alm.client.TimeoutConfiguration; +import org.sonar.api.server.ServerSide; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonarqube.ws.client.OkHttpClientBuilder; + +import static org.sonar.api.internal.apachecommons.lang.StringUtils.isBlank; +import static org.sonar.api.internal.apachecommons.lang.StringUtils.substringBeforeLast; + +@ServerSide +public class AzureDevOpsHttpClient { + private static final Logger LOG = Loggers.get(AzureDevOpsHttpClient.class); + + protected static final String GET = "GET"; + protected static final String UNABLE_TO_CONTACT_AZURE_SERVER = "Unable to contact Azure DevOps server"; + + protected final OkHttpClient client; + + public AzureDevOpsHttpClient(TimeoutConfiguration timeoutConfiguration) { + client = new OkHttpClientBuilder() + .setConnectTimeoutMs(timeoutConfiguration.getConnectTimeout()) + .setReadTimeoutMs(timeoutConfiguration.getReadTimeout()) + .build(); + } + + public GsonAzureProjectList getProjects(String serverUrl, String token) { + String url = String.format("%s/_apis/projects", getTrimmedUrl(serverUrl)); + LOG.debug(String.format("get projects : [%s]", url)); + return doGet(token, url, r -> buildGson().fromJson(r.body().charStream(), GsonAzureProjectList.class)); + } + + + public GsonAzureRepoList getRepos(String serverUrl, String token, @Nullable String projectName) { + String url; + if (projectName != null && !projectName.isEmpty()) { + url = String.format("%s/%s/_apis/git/repositories", getTrimmedUrl(serverUrl), projectName); + } else { + url = String.format("%s/_apis/git/repositories", getTrimmedUrl(serverUrl)); + } + LOG.debug(String.format("get repos : [%s]", url)); + return doGet(token, url, r -> buildGson().fromJson(r.body().charStream(), GsonAzureRepoList.class)); + } + + public GsonAzureRepo getRepo(String serverUrl, String token, String projectName, String repositoryName) { + String url = String.format("%s/%s/_apis/git/repositories/%s", getTrimmedUrl(serverUrl), projectName, repositoryName); + LOG.debug(String.format("get repo : [%s]", url)); + return doGet(token, url, r -> buildGson().fromJson(r.body().charStream(), GsonAzureRepo.class)); + } + + protected <G> G doGet(String token, String url, Function<Response, G> handler) { + Request request = prepareRequestWithToken(token, GET, url, null); + return doCall(request, handler); + } + + protected <G> G doCall(Request request, Function<Response, G> handler) { + try (Response response = client.newCall(request).execute()) { + checkResponseIsSuccessful(response); + return handler.apply(response); + } catch (JsonSyntaxException e) { + throw new IllegalArgumentException(UNABLE_TO_CONTACT_AZURE_SERVER + ", got an unexpected response", e); + } catch (IOException e) { + throw new IllegalArgumentException(UNABLE_TO_CONTACT_AZURE_SERVER, e); + } + } + + protected static Request prepareRequestWithToken(String token, String method, String url, @Nullable RequestBody body) { + return new Request.Builder() + .method(method, body) + .url(url) + .addHeader("Authorization", encodeToken("accessToken:" + token)) + .build(); + } + + protected static void checkResponseIsSuccessful(Response response) throws IOException { + if (!response.isSuccessful()) { + LOG.debug(UNABLE_TO_CONTACT_AZURE_SERVER + ": {} {}", response.request().url().toString(), response.code()); + if (response.code() == HttpURLConnection.HTTP_UNAUTHORIZED) { + throw new IllegalArgumentException("Invalid personal access token"); + } + ResponseBody responseBody = response.body(); + String body = responseBody == null ? "" : responseBody.string(); + String errorMessage = generateErrorMessage(body, UNABLE_TO_CONTACT_AZURE_SERVER); + LOG.info(String.format("Azure API call to [%s] failed with %s http code. Azure response content : [%s]", response.request().url().toString(), response.code(), body)); + throw new IllegalArgumentException(errorMessage); + } + } + + protected static String generateErrorMessage(String body, String defaultMessage) { + GsonAzureError gsonAzureError = null; + try { + gsonAzureError = buildGson().fromJson(body, GsonAzureError.class); + } catch (JsonSyntaxException e) { + // not a json payload, ignore the error + } + if (gsonAzureError != null && !Strings.isNullOrEmpty(gsonAzureError.message())) { + return defaultMessage + " : " + gsonAzureError.message(); + } else { + return defaultMessage; + } + } + + protected static String getTrimmedUrl(String rawUrl) { + if (isBlank(rawUrl)) { + return rawUrl; + } + if (rawUrl.endsWith("/")) { + return substringBeforeLast(rawUrl, "/"); + } + return rawUrl; + } + + protected static String encodeToken(String token) { + return String.format("BASIC %s", Base64.encodeBase64String(token.getBytes(StandardCharsets.UTF_8))); + } + + protected static Gson buildGson() { + return new GsonBuilder() + .create(); + } +} diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/azure/GsonAzureError.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/azure/GsonAzureError.java new file mode 100644 index 00000000000..e4e91799e24 --- /dev/null +++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/azure/GsonAzureError.java @@ -0,0 +1,41 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * 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 02110-1301, USA. + */ +package org.sonar.alm.client.azure; + +import com.google.gson.annotations.SerializedName; +import javax.annotation.Nullable; + +public class GsonAzureError { + @SerializedName("message") + private final String message; + + public GsonAzureError(@Nullable String message) { + this.message = message; + } + + public GsonAzureError() { + // http://stackoverflow.com/a/18645370/229031 + this(null); + } + + public String message() { + return message; + } +} diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/azure/GsonAzureProject.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/azure/GsonAzureProject.java new file mode 100644 index 00000000000..c206f02d61d --- /dev/null +++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/azure/GsonAzureProject.java @@ -0,0 +1,48 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * 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 02110-1301, USA. + */ +package org.sonar.alm.client.azure; + +import com.google.gson.annotations.SerializedName; + +public class GsonAzureProject { + + @SerializedName("name") + private String name; + + @SerializedName("description") + private String description; + + public GsonAzureProject() { + // http://stackoverflow.com/a/18645370/229031 + } + + public GsonAzureProject(String name, String description) { + this.name = name; + this.description = description; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } +} diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/azure/GsonAzureProjectList.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/azure/GsonAzureProjectList.java new file mode 100644 index 00000000000..e594bbbf90a --- /dev/null +++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/azure/GsonAzureProjectList.java @@ -0,0 +1,56 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * 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 02110-1301, USA. + */ +package org.sonar.alm.client.azure; + +import com.google.gson.annotations.SerializedName; + +import java.util.ArrayList; +import java.util.List; + +public class GsonAzureProjectList { + + @SerializedName("value") + private List<GsonAzureProject> values; + + public GsonAzureProjectList() { + // http://stackoverflow.com/a/18645370/229031 + this(new ArrayList<>()); + } + + public GsonAzureProjectList(List<GsonAzureProject> values) { + this.values = values; + } + + public List<GsonAzureProject> getValues() { + return values; + } + + public GsonAzureProjectList setValues(List<GsonAzureProject> values) { + this.values = values; + return this; + } + + @Override + public String toString() { + return "{" + + "values=" + values + + '}'; + } +} diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/azure/GsonAzureRepo.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/azure/GsonAzureRepo.java new file mode 100644 index 00000000000..60327fc5c63 --- /dev/null +++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/azure/GsonAzureRepo.java @@ -0,0 +1,63 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * 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 02110-1301, USA. + */ +package org.sonar.alm.client.azure; + +import com.google.gson.annotations.SerializedName; + +public class GsonAzureRepo { + @SerializedName("id") + private String id; + + @SerializedName("name") + private String name; + + @SerializedName("url") + private String url; + + @SerializedName("project") + private GsonAzureProject project; + + public GsonAzureRepo() { + // http://stackoverflow.com/a/18645370/229031 + } + + public GsonAzureRepo(String id, String name, String url, GsonAzureProject project) { + this.id = id; + this.name = name; + this.url = url; + this.project = project; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public String getUrl() { + return url; + } + + public GsonAzureProject getProject() { + return project; + } +} diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/azure/GsonAzureRepoList.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/azure/GsonAzureRepoList.java new file mode 100644 index 00000000000..eaeffc8a5c6 --- /dev/null +++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/azure/GsonAzureRepoList.java @@ -0,0 +1,49 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * 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 02110-1301, USA. + */ +package org.sonar.alm.client.azure; + +import com.google.gson.Gson; +import com.google.gson.annotations.SerializedName; +import java.util.ArrayList; +import java.util.List; + +public class GsonAzureRepoList { + + @SerializedName("value") + private List<GsonAzureRepo> values; + + public GsonAzureRepoList() { + // http://stackoverflow.com/a/18645370/229031 + this(new ArrayList<>()); + } + + public GsonAzureRepoList(List<GsonAzureRepo> values) { + this.values = values; + } + + static GsonAzureRepoList parse(String json) { + return new Gson().fromJson(json, GsonAzureRepoList.class); + } + + public List<GsonAzureRepo> getValues() { + return values; + } + +} diff --git a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/azure/AzureDevOpsHttpClientTest.java b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/azure/AzureDevOpsHttpClientTest.java new file mode 100644 index 00000000000..c296f79b5af --- /dev/null +++ b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/azure/AzureDevOpsHttpClientTest.java @@ -0,0 +1,273 @@ +/* + * SonarQube + * Copyright (C) 2009-2021 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * 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 02110-1301, USA. + */ +package org.sonar.alm.client.azure; + +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.alm.client.ConstantTimeoutConfiguration; +import org.sonar.alm.client.TimeoutConfiguration; +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.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.tuple; +import javax.annotation.Nullable; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +public class AzureDevOpsHttpClientTest { + public static final String UNABLE_TO_CONTACT_AZURE = "Unable to contact Azure DevOps server, got an unexpected response"; + @Rule + public LogTester logTester = new LogTester(); + + private static final String NON_JSON_PAYLOAD = "non json payload"; + private final MockWebServer server = new MockWebServer(); + private AzureDevOpsHttpClient underTest; + + @Before + public void prepare() throws IOException { + server.start(); + + TimeoutConfiguration timeoutConfiguration = new ConstantTimeoutConfiguration(10_000); + underTest = new AzureDevOpsHttpClient(timeoutConfiguration); + } + + @After + public void stopServer() throws IOException { + server.shutdown(); + } + + @Test + public void get_projects() throws InterruptedException { + enqueueResponse(200, " { \"count\": 2,\n" + + " \"value\": [\n" + + " {\n" + + " \"id\": \"3311cd05-3f00-4a5e-b47f-df94a9982b6e\",\n" + + " \"name\": \"Project 1\",\n" + + " \"description\": \"Project Description\",\n" + + " \"url\": \"https://ado.sonarqube.com/DefaultCollection/_apis/projects/3311cd05-3f00-4a5e-b47f-df94a9982b6e\",\n" + + " \"state\": \"wellFormed\",\n" + + " \"revision\": 63,\n" + + " \"visibility\": \"private\"\n" + + " }," + + "{\n" + + " \"id\": \"3be0f34d-c931-4ff8-8d37-18a83663bd3c\",\n" + + " \"name\": \"Project 2\",\n" + + " \"url\": \"https://ado.sonarqube.com/DefaultCollection/_apis/projects/3be0f34d-c931-4ff8-8d37-18a83663bd3c\",\n" + + " \"state\": \"wellFormed\",\n" + + " \"revision\": 52,\n" + + " \"visibility\": \"private\"\n" + + " }]}"); + + GsonAzureProjectList projects = underTest.getProjects(server.url("").toString(), "token"); + + RecordedRequest request = server.takeRequest(10, TimeUnit.SECONDS); + String azureDevOpsUrlCall = request.getRequestUrl().toString(); + assertThat(azureDevOpsUrlCall).isEqualTo(server.url("") + "_apis/projects"); + assertThat(request.getMethod()).isEqualTo("GET"); + + assertThat(logTester.logs(LoggerLevel.DEBUG)).hasSize(1); + assertThat(logTester.logs(LoggerLevel.DEBUG)) + .contains("get projects : [" + server.url("").toString() + "_apis/projects]"); + assertThat(projects.getValues()).hasSize(2); + assertThat(projects.getValues()) + .extracting(GsonAzureProject::getName, GsonAzureProject::getDescription) + .containsExactly(tuple("Project 1", "Project Description"), tuple("Project 2", null)); + } + + @Test + public void get_projects_non_json_payload() { + enqueueResponse(200, NON_JSON_PAYLOAD); + + assertThatThrownBy(() -> underTest.getProjects(server.url("").toString(), "token")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(UNABLE_TO_CONTACT_AZURE); + } + + @Test + public void get_projects_with_invalid_pat() { + enqueueResponse(401); + + assertThatThrownBy(() -> underTest.getProjects(server.url("").toString(), "invalid-token")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Invalid personal access token"); + } + + @Test + public void get_projects_with_server_error() { + enqueueResponse(500); + + assertThatThrownBy(() -> underTest.getProjects(server.url("").toString(), "token")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Unable to contact Azure DevOps server"); + } + + @Test + public void get_repos_with_project_name() throws InterruptedException { + enqueueResponse(200, "{\n" + + " \"value\": [\n" + + " {\n" + + " \"id\": \"741248a4-285e-4a6d-af52-1a49d8070638\",\n" + + " \"name\": \"Repository 1\",\n" + + " \"url\": \"https://ado.sonarqube.com/repositories/\",\n" + + " \"project\": {\n" + + " \"id\": \"c88ddb32-ced8-420d-ab34-764133038b34\",\n" + + " \"name\": \"projectName\",\n" + + " \"url\": \"https://ado.sonarqube.com/DefaultCollection/_apis/projects/c88ddb32-ced8-420d-ab34-764133038b34\",\n" + + " \"state\": \"wellFormed\",\n" + + " \"revision\": 29,\n" + + " \"visibility\": \"private\",\n" + + " \"lastUpdateTime\": \"2020-11-11T09:38:03.3Z\"\n" + + " },\n" + + " \"size\": 0\n" + + " }\n" + + " ],\n" + + " \"count\": 1\n" + + "}"); + + GsonAzureRepoList repos = underTest.getRepos(server.url("").toString(), "token", "projectName"); + + RecordedRequest request = server.takeRequest(10, TimeUnit.SECONDS); + String azureDevOpsUrlCall = request.getRequestUrl().toString(); + assertThat(azureDevOpsUrlCall).isEqualTo(server.url("") + "projectName/_apis/git/repositories"); + assertThat(request.getMethod()).isEqualTo("GET"); + + assertThat(logTester.logs(LoggerLevel.DEBUG)).hasSize(1); + assertThat(logTester.logs(LoggerLevel.DEBUG)) + .contains("get repos : [" + server.url("").toString() + "projectName/_apis/git/repositories]"); + assertThat(repos.getValues()).hasSize(1); + assertThat(repos.getValues()) + .extracting(GsonAzureRepo::getName, GsonAzureRepo::getUrl, r -> r.getProject().getName()) + .containsExactly(tuple("Repository 1", "https://ado.sonarqube.com/repositories/", "projectName")); + } + + @Test + public void get_repos_without_project_name() throws InterruptedException { + enqueueResponse(200, "{ \"value\": [], \"count\": 0 }"); + + GsonAzureRepoList repos = underTest.getRepos(server.url("").toString(), "token", null); + + RecordedRequest request = server.takeRequest(10, TimeUnit.SECONDS); + String azureDevOpsUrlCall = request.getRequestUrl().toString(); + assertThat(azureDevOpsUrlCall).isEqualTo(server.url("") + "_apis/git/repositories"); + assertThat(request.getMethod()).isEqualTo("GET"); + assertThat(repos.getValues()).isEmpty(); + } + + @Test + public void get_repos_non_json_payload() { + enqueueResponse(200, NON_JSON_PAYLOAD); + + assertThatThrownBy(() -> underTest.getRepos(server.url("").toString(), "token", null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(UNABLE_TO_CONTACT_AZURE); + } + + @Test + public void get_repo() throws InterruptedException { + enqueueResponse(200, "{ " + + " \"id\": \"Repo-Id-1\",\n" + + " \"name\": \"Repo-Name-1\",\n" + + " \"url\": \"https://ado.sonarqube.com/DefaultCollection/Repo-Id-1\",\n" + + " \"project\": {\n" + + " \"id\": \"84ea9d51-0c8a-44ad-be92-b2af7fe2c299\",\n" + + " \"name\": \"Project-Name\",\n" + + " \"description\": \"Project's description\" \n" + + " },\n" + + " \"size\": 0" + + "}"); + + GsonAzureRepo repo = underTest.getRepo(server.url("").toString(), "token", "Project-Name", "Repo-Name-1"); + + RecordedRequest request = server.takeRequest(10, TimeUnit.SECONDS); + String azureDevOpsUrlCall = request.getRequestUrl().toString(); + assertThat(azureDevOpsUrlCall).isEqualTo(server.url("") + "Project-Name/_apis/git/repositories/Repo-Name-1"); + assertThat(request.getMethod()).isEqualTo("GET"); + + assertThat(logTester.logs(LoggerLevel.DEBUG)).hasSize(1); + assertThat(logTester.logs(LoggerLevel.DEBUG)) + .contains("get repo : [" + server.url("").toString() + "Project-Name/_apis/git/repositories/Repo-Name-1]"); + assertThat(repo.getId()).isEqualTo("Repo-Id-1"); + assertThat(repo.getName()).isEqualTo("Repo-Name-1"); + assertThat(repo.getUrl()).isEqualTo("https://ado.sonarqube.com/DefaultCollection/Repo-Id-1"); + assertThat(repo.getProject().getName()).isEqualTo("Project-Name"); + } + + @Test + public void get_repo_non_json_payload() { + enqueueResponse(200, NON_JSON_PAYLOAD); + + assertThatThrownBy(() -> underTest.getRepo(server.url("").toString(), "token", "projectName", "repoName")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage(UNABLE_TO_CONTACT_AZURE); + } + + @Test + public void get_repo_json_error_payload() { + enqueueResponse(400, + "{'message':'TF200016: The following project does not exist: projectName. Verify that the name of the project is correct and that the project exists on the specified Azure DevOps Server.'}"); + + assertThatThrownBy(() -> underTest.getRepo(server.url("").toString(), "token", "projectName", "repoName")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage( + "Unable to contact Azure DevOps server : TF200016: The following project does not exist: projectName. Verify that the name of the project is correct and that the project exists on the specified Azure DevOps Server."); + } + + private void enqueueResponse(int responseCode) { + enqueueResponse(responseCode, ""); + } + + private void enqueueResponse(int responseCode, @Nullable String body) { + server.enqueue(new MockResponse() + .setHeader("Content-Type", "application/json;charset=UTF-8") + .setResponseCode(responseCode) + .setBody(body)); + } + + @Test + public void trim_url() { + assertThat(AzureDevOpsHttpClient.getTrimmedUrl("http://localhost:4564/")) + .isEqualTo("http://localhost:4564"); + } + + @Test + public void trim_url_without_ending_slash() { + assertThat(AzureDevOpsHttpClient.getTrimmedUrl("http://localhost:4564")) + .isEqualTo("http://localhost:4564"); + } + + @Test + public void trim_null_url() { + assertThat(AzureDevOpsHttpClient.getTrimmedUrl(null)) + .isNull(); + } + + @Test + public void trim_empty_url() { + assertThat(AzureDevOpsHttpClient.getTrimmedUrl("")) + .isEmpty(); + } +} |