|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588 |
- /*
- * 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.bitbucketserver;
-
- import java.io.IOException;
- import okhttp3.mockwebserver.MockResponse;
- import okhttp3.mockwebserver.MockWebServer;
- 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.api.utils.log.LogTester;
-
- import static org.assertj.core.api.Assertions.assertThat;
- import static org.assertj.core.api.Assertions.assertThatThrownBy;
- import static org.assertj.core.api.Assertions.tuple;
-
- public class BitbucketServerRestClientTest {
- private final MockWebServer server = new MockWebServer();
- private static final String REPOS_BODY = "{\n" +
- " \"isLastPage\": true,\n" +
- " \"values\": [\n" +
- " {\n" +
- " \"slug\": \"banana\",\n" +
- " \"id\": 2,\n" +
- " \"name\": \"banana\",\n" +
- " \"project\": {\n" +
- " \"key\": \"HOY\",\n" +
- " \"id\": 2,\n" +
- " \"name\": \"hoy\"\n" +
- " }\n" +
- " },\n" +
- " {\n" +
- " \"slug\": \"potato\",\n" +
- " \"id\": 1,\n" +
- " \"name\": \"potato\",\n" +
- " \"project\": {\n" +
- " \"key\": \"HEY\",\n" +
- " \"id\": 1,\n" +
- " \"name\": \"hey\"\n" +
- " }\n" +
- " }\n" +
- " ]\n" +
- "}";
-
- @Rule
- public LogTester logTester = new LogTester();
-
- private BitbucketServerRestClient underTest;
-
- @Before
- public void prepare() throws IOException {
- server.start();
-
- underTest = new BitbucketServerRestClient(new ConstantTimeoutConfiguration(500));
- }
-
- @After
- public void stopServer() throws IOException {
- server.shutdown();
- }
-
- @Test
- public void get_repos() {
- server.enqueue(new MockResponse()
- .setHeader("Content-Type", "application/json;charset=UTF-8")
- .setBody("{\n" +
- " \"isLastPage\": true,\n" +
- " \"values\": [\n" +
- " {\n" +
- " \"slug\": \"banana\",\n" +
- " \"id\": 2,\n" +
- " \"name\": \"banana\",\n" +
- " \"project\": {\n" +
- " \"key\": \"HOY\",\n" +
- " \"id\": 2,\n" +
- " \"name\": \"hoy\"\n" +
- " }\n" +
- " },\n" +
- " {\n" +
- " \"slug\": \"potato\",\n" +
- " \"id\": 1,\n" +
- " \"name\": \"potato\",\n" +
- " \"project\": {\n" +
- " \"key\": \"HEY\",\n" +
- " \"id\": 1,\n" +
- " \"name\": \"hey\"\n" +
- " }\n" +
- " }\n" +
- " ]\n" +
- "}"));
-
- RepositoryList gsonBBSRepoList = underTest.getRepos(server.url("/").toString(), "token", "", "");
- assertThat(gsonBBSRepoList.isLastPage()).isTrue();
- assertThat(gsonBBSRepoList.getValues()).hasSize(2);
- assertThat(gsonBBSRepoList.getValues()).extracting(Repository::getId, Repository::getName, Repository::getSlug,
- g -> g.getProject().getId(), g -> g.getProject().getKey(), g -> g.getProject().getName())
- .containsExactlyInAnyOrder(
- tuple(2L, "banana", "banana", 2L, "HOY", "hoy"),
- tuple(1L, "potato", "potato", 1L, "HEY", "hey"));
- }
-
- @Test
- public void get_recent_repos() {
- server.enqueue(new MockResponse()
- .setHeader("Content-Type", "application/json;charset=UTF-8")
- .setBody("{\n" +
- " \"isLastPage\": true,\n" +
- " \"values\": [\n" +
- " {\n" +
- " \"slug\": \"banana\",\n" +
- " \"id\": 2,\n" +
- " \"name\": \"banana\",\n" +
- " \"project\": {\n" +
- " \"key\": \"HOY\",\n" +
- " \"id\": 2,\n" +
- " \"name\": \"hoy\"\n" +
- " }\n" +
- " },\n" +
- " {\n" +
- " \"slug\": \"potato\",\n" +
- " \"id\": 1,\n" +
- " \"name\": \"potato\",\n" +
- " \"project\": {\n" +
- " \"key\": \"HEY\",\n" +
- " \"id\": 1,\n" +
- " \"name\": \"hey\"\n" +
- " }\n" +
- " }\n" +
- " ]\n" +
- "}"));
-
- RepositoryList gsonBBSRepoList = underTest.getRecentRepo(server.url("/").toString(), "token");
- assertThat(gsonBBSRepoList.isLastPage()).isTrue();
- assertThat(gsonBBSRepoList.getValues()).hasSize(2);
- assertThat(gsonBBSRepoList.getValues()).extracting(Repository::getId, Repository::getName, Repository::getSlug,
- g -> g.getProject().getId(), g -> g.getProject().getKey(), g -> g.getProject().getName())
- .containsExactlyInAnyOrder(
- tuple(2L, "banana", "banana", 2L, "HOY", "hoy"),
- tuple(1L, "potato", "potato", 1L, "HEY", "hey"));
- }
-
- @Test
- public void get_repo() {
- server.enqueue(new MockResponse()
- .setHeader("Content-Type", "application/json;charset=UTF-8")
- .setBody(
- " {" +
- " \"slug\": \"banana-slug\"," +
- " \"id\": 2,\n" +
- " \"name\": \"banana\"," +
- " \"project\": {\n" +
- " \"key\": \"HOY\"," +
- " \"id\": 3,\n" +
- " \"name\": \"hoy\"" +
- " }" +
- " }"));
-
- Repository repository = underTest.getRepo(server.url("/").toString(), "token", "", "");
- assertThat(repository.getId()).isEqualTo(2L);
- assertThat(repository.getName()).isEqualTo("banana");
- assertThat(repository.getSlug()).isEqualTo("banana-slug");
- assertThat(repository.getProject())
- .extracting(Project::getId, Project::getKey, Project::getName)
- .contains(3L, "HOY", "hoy");
- }
-
- @Test
- public void get_projects() {
- server.enqueue(new MockResponse()
- .setHeader("Content-Type", "application/json;charset=UTF-8")
- .setBody("{\n" +
- " \"isLastPage\": true,\n" +
- " \"values\": [\n" +
- " {\n" +
- " \"key\": \"HEY\",\n" +
- " \"id\": 1,\n" +
- " \"name\": \"hey\"\n" +
- " },\n" +
- " {\n" +
- " \"key\": \"HOY\",\n" +
- " \"id\": 2,\n" +
- " \"name\": \"hoy\"\n" +
- " }\n" +
- " ]\n" +
- "}"));
-
- final ProjectList gsonBBSProjectList = underTest.getProjects(server.url("/").toString(), "token");
- assertThat(gsonBBSProjectList.getValues()).hasSize(2);
- assertThat(gsonBBSProjectList.getValues()).extracting(Project::getId, Project::getKey, Project::getName)
- .containsExactlyInAnyOrder(
- tuple(1L, "HEY", "hey"),
- tuple(2L, "HOY", "hoy"));
- }
-
- @Test
- public void getBranches_given0Branches_returnEmptyList() {
- String bodyWith0Branches = "{\n" +
- " \"size\": 0,\n" +
- " \"limit\": 25,\n" +
- " \"isLastPage\": true,\n" +
- " \"values\": [],\n" +
- " \"start\": 0\n" +
- "}";
- server.enqueue(new MockResponse()
- .setHeader("Content-Type", "application/json;charset=UTF-8")
- .setBody(bodyWith0Branches));
-
- BranchesList branches = underTest.getBranches(server.url("/").toString(), "token", "projectSlug", "repoSlug");
-
- assertThat(branches.getBranches()).isEmpty();
- }
-
- @Test
- public void getBranches_given1Branch_returnListWithOneBranch() {
- String bodyWith1Branch = "{\n" +
- " \"size\": 1,\n" +
- " \"limit\": 25,\n" +
- " \"isLastPage\": true,\n" +
- " \"values\": [{\n" +
- " \"id\": \"refs/heads/demo\",\n" +
- " \"displayId\": \"demo\",\n" +
- " \"type\": \"BRANCH\",\n" +
- " \"latestCommit\": \"3e30a6701af6f29f976e9a6609a6076b32a69ac3\",\n" +
- " \"latestChangeset\": \"3e30a6701af6f29f976e9a6609a6076b32a69ac3\",\n" +
- " \"isDefault\": false\n" +
- " }],\n" +
- " \"start\": 0\n" +
- "}";
- server.enqueue(new MockResponse()
- .setHeader("Content-Type", "application/json;charset=UTF-8")
- .setBody(bodyWith1Branch));
-
- BranchesList branches = underTest.getBranches(server.url("/").toString(), "token", "projectSlug", "repoSlug");
- assertThat(branches.getBranches()).hasSize(1);
-
- Branch branch = branches.getBranches().get(0);
- assertThat(branch.getName()).isEqualTo("demo");
- assertThat(branch.isDefault()).isFalse();
-
- }
-
- @Test
- public void getBranches_given2Branches_returnListWithTwoBranches() {
- String bodyWith2Branches = "{\n" +
- " \"size\": 2,\n" +
- " \"limit\": 25,\n" +
- " \"isLastPage\": true,\n" +
- " \"values\": [{\n" +
- " \"id\": \"refs/heads/demo\",\n" +
- " \"displayId\": \"demo\",\n" +
- " \"type\": \"BRANCH\",\n" +
- " \"latestCommit\": \"3e30a6701af6f29f976e9a6609a6076b32a69ac3\",\n" +
- " \"latestChangeset\": \"3e30a6701af6f29f976e9a6609a6076b32a69ac3\",\n" +
- " \"isDefault\": false\n" +
- " }, {\n" +
- " \"id\": \"refs/heads/master\",\n" +
- " \"displayId\": \"master\",\n" +
- " \"type\": \"BRANCH\",\n" +
- " \"latestCommit\": \"66633864d27c531ff43892f6dfea6d91632682fa\",\n" +
- " \"latestChangeset\": \"66633864d27c531ff43892f6dfea6d91632682fa\",\n" +
- " \"isDefault\": true\n" +
- " }],\n" +
- " \"start\": 0\n" +
- "}";
- server.enqueue(new MockResponse()
- .setHeader("Content-Type", "application/json;charset=UTF-8")
- .setBody(bodyWith2Branches));
-
- BranchesList branches = underTest.getBranches(server.url("/").toString(), "token", "projectSlug", "repoSlug");
-
- assertThat(branches.getBranches()).hasSize(2);
- }
-
- @Test
- public void invalid_url() {
- assertThatThrownBy(() -> BitbucketServerRestClient.buildUrl("file://wrong-url", ""))
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessage("url must start with http:// or https://");
- }
-
- @Test
- public void malformed_json() {
- server.enqueue(new MockResponse()
- .setHeader("Content-Type", "application/json;charset=UTF-8")
- .setBody(
- "I'm malformed JSON"));
-
- String serverUrl = server.url("/").toString();
- assertThatThrownBy(() -> underTest.getRepo(serverUrl, "token", "", ""))
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Unable to contact Bitbucket server, got an unexpected response");
- }
-
- @Test
- public void error_handling() {
- server.enqueue(new MockResponse()
- .setHeader("Content-Type", "application/json;charset=UTF-8")
- .setResponseCode(400)
- .setBody("{\n" +
- " \"errors\": [\n" +
- " {\n" +
- " \"context\": null,\n" +
- " \"message\": \"Bad message\",\n" +
- " \"exceptionName\": \"com.atlassian.bitbucket.auth.BadException\"\n" +
- " }\n" +
- " ]\n" +
- "}"));
-
- String serverUrl = server.url("/").toString();
- assertThatThrownBy(() -> underTest.getRepo(serverUrl, "token", "", ""))
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Unable to contact Bitbucket server");
- }
-
- @Test
- public void unauthorized_error() {
- server.enqueue(new MockResponse()
- .setHeader("Content-Type", "application/json;charset=UTF-8")
- .setResponseCode(401)
- .setBody("{\n" +
- " \"errors\": [\n" +
- " {\n" +
- " \"context\": null,\n" +
- " \"message\": \"Bad message\",\n" +
- " \"exceptionName\": \"com.atlassian.bitbucket.auth.BadException\"\n" +
- " }\n" +
- " ]\n" +
- "}"));
-
- String serverUrl = server.url("/").toString();
- assertThatThrownBy(() -> underTest.getRepo(serverUrl, "token", "", ""))
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Invalid personal access token");
- }
-
- @Test
- public void fail_validate_on_io_exception() throws IOException {
- server.shutdown();
-
- String serverUrl = server.url("/").toString();
- assertThatThrownBy(() -> underTest.validateUrl(serverUrl))
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Unable to contact Bitbucket server");
-
- assertThat(String.join(", ", logTester.logs())).contains("Unable to contact Bitbucket server: Failed to connect");
- }
-
- @Test
- public void fail_validate_url_on_non_json_result_log_correctly_the_response() {
- server.enqueue(new MockResponse()
- .setHeader("Content-Type", "application/json;charset=UTF-8")
- .setResponseCode(500)
- .setBody("not json"));
-
- String serverUrl = server.url("/").toString();
- assertThatThrownBy(() -> underTest.validateReadPermission(serverUrl, "token"))
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Unable to contact Bitbucket server");
-
- assertThat(String.join(", ", logTester.logs())).contains("Unable to contact Bitbucket server: 500 not json");
- }
-
- @Test
- public void fail_validate_url_on_text_result_log_the_returned_payload() {
- server.enqueue(new MockResponse()
- .setResponseCode(500)
- .setBody("this is a text payload"));
-
- String serverUrl = server.url("/").toString();
- assertThatThrownBy(() -> underTest.validateReadPermission(serverUrl, "token"))
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Unable to contact Bitbucket server");
-
- assertThat(String.join(", ", logTester.logs())).contains("Unable to contact Bitbucket server: 500 this is a text payload");
- }
-
- @Test
- public void validate_url_success() {
- server.enqueue(new MockResponse().setResponseCode(200)
- .setBody(REPOS_BODY));
-
- underTest.validateUrl(server.url("/").toString());
- }
-
- @Test
- public void validate_url_fail_when_not_starting_with_protocol() {
- assertThatThrownBy(() -> underTest.validateUrl("any_url_not_starting_with_http.com"))
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessage("url must start with http:// or https://");
- }
-
- @Test
- public void validate_token_success() {
- server.enqueue(new MockResponse().setResponseCode(200)
- .setBody("{\n" +
- " \"size\":10,\n" +
- " \"limit\":25,\n" +
- " \"isLastPage\":true,\n" +
- " \"values\":[\n" +
- " {\n" +
- " \"name\":\"jean.michel\",\n" +
- " \"emailAddress\":\"jean.michel@sonarsource.com\",\n" +
- " \"id\":2,\n" +
- " \"displayName\":\"Jean Michel\",\n" +
- " \"active\":true,\n" +
- " \"slug\":\"jean.michel\",\n" +
- " \"type\":\"NORMAL\",\n" +
- " \"links\":{\n" +
- " \"self\":[\n" +
- " {\n" +
- " \"href\":\"https://bitbucket-testing.valiantys.sonarsource.com/users/jean.michel\"\n" +
- " }\n" +
- " ]\n" +
- " }\n" +
- " },\n" +
- " {\n" +
- " \"name\":\"prince.de.lu\",\n" +
- " \"emailAddress\":\"prince.de.lu@sonarsource.com\",\n" +
- " \"id\":103,\n" +
- " \"displayName\":\"Prince de Lu\",\n" +
- " \"active\":true,\n" +
- " \"slug\":\"prince.de.lu\",\n" +
- " \"type\":\"NORMAL\",\n" +
- " \"links\":{\n" +
- " \"self\":[\n" +
- " {\n" +
- " \"href\":\"https://bitbucket-testing.valiantys.sonarsource.com/users/prince.de.lu\"\n" +
- " }\n" +
- " ]\n" +
- " }\n" +
- " },\n" +
- " ],\n" +
- " \"start\":0\n" +
- "}"));
-
- underTest.validateToken(server.url("/").toString(), "token");
- }
-
- @Test
- public void validate_read_permission_success() {
- server.enqueue(new MockResponse().setResponseCode(200)
- .setBody(REPOS_BODY));
-
- underTest.validateReadPermission(server.url("/").toString(), "token");
- }
-
- @Test
- public void fail_validate_url_when_on_http_error() {
- server.enqueue(new MockResponse().setResponseCode(500)
- .setBody("something unexpected"));
-
- String serverUrl = server.url("/").toString();
- assertThatThrownBy(() -> underTest.validateUrl(serverUrl))
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Unable to contact Bitbucket server");
- }
-
- @Test
- public void fail_validate_url_when_not_found_is_returned() {
- server.enqueue(new MockResponse().setResponseCode(404)
- .setBody("something unexpected"));
-
- String serverUrl = server.url("/").toString();
- assertThatThrownBy(() -> underTest.validateUrl(serverUrl))
- .isInstanceOf(BitbucketServerException.class)
- .hasMessage("something unexpected")
- .extracting(e -> ((BitbucketServerException) e).getHttpStatus()).isEqualTo(404);
- }
-
- @Test
- public void fail_validate_url_when_body_is_empty() {
- server.enqueue(new MockResponse().setResponseCode(404).setBody(""));
-
- String serverUrl = server.url("/").toString();
- assertThatThrownBy(() -> underTest.validateUrl(serverUrl))
- .isInstanceOf(BitbucketServerException.class)
- .hasMessage("")
- .extracting(e -> ((BitbucketServerException) e).getHttpStatus()).isEqualTo(404);
- }
-
- @Test
- public void fail_validate_url_when_validate_url_return_non_json_payload() {
- server.enqueue(new MockResponse().setResponseCode(400)
- .setBody("this is not a json payload"));
-
- String serverUrl = server.url("/").toString();
- assertThatThrownBy(() -> underTest.validateUrl(serverUrl))
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Unable to contact Bitbucket server");
- }
-
- @Test
- public void fail_validate_url_when_returning_non_json_payload_with_a_200_code() {
- server.enqueue(new MockResponse().setResponseCode(200)
- .setBody("this is not a json payload"));
-
- String serverUrl = server.url("/").toString();
- assertThatThrownBy(() -> underTest.validateUrl(serverUrl))
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Unable to contact Bitbucket server, got an unexpected response");
- }
-
- @Test
- public void fail_validate_token_when_server_return_non_json_payload() {
- server.enqueue(new MockResponse().setResponseCode(400)
- .setBody("this is not a json payload"));
-
- String serverUrl = server.url("/").toString();
- assertThatThrownBy(() -> underTest.validateToken(serverUrl, "token"))
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Unable to contact Bitbucket server");
- }
-
- @Test
- public void fail_validate_token_when_returning_non_json_payload_with_a_200_code() {
- server.enqueue(new MockResponse().setResponseCode(200)
- .setBody("this is not a json payload"));
-
- String serverUrl = server.url("/").toString();
- assertThatThrownBy(() -> underTest.validateToken(serverUrl, "token"))
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Unable to contact Bitbucket server, got an unexpected response");
- }
-
- @Test
- public void fail_validate_token_when_using_an_invalid_token() {
- server.enqueue(new MockResponse().setResponseCode(401)
- .setBody("com.atlassian.bitbucket.AuthorisationException You are not permitted to access this resource"));
-
- String serverUrl = server.url("/").toString();
- assertThatThrownBy(() -> underTest.validateToken(serverUrl, "token"))
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Invalid personal access token");
- }
-
- @Test
- public void fail_validate_read_permission_when_server_return_non_json_payload() {
- server.enqueue(new MockResponse().setResponseCode(400)
- .setBody("this is not a json payload"));
-
- String serverUrl = server.url("/").toString();
- assertThatThrownBy(() -> underTest.validateReadPermission(serverUrl, "token"))
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Unable to contact Bitbucket server");
- }
-
- @Test
- public void fail_validate_read_permission_when_returning_non_json_payload_with_a_200_code() {
- server.enqueue(new MockResponse().setResponseCode(200)
- .setBody("this is not a json payload"));
-
- String serverUrl = server.url("/").toString();
- assertThatThrownBy(() -> underTest.validateReadPermission(serverUrl, "token"))
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Unable to contact Bitbucket server, got an unexpected response");
- }
-
- @Test
- public void fail_validate_read_permission_when_permissions_are_not_granted() {
- server.enqueue(new MockResponse().setResponseCode(401)
- .setBody("com.atlassian.bitbucket.AuthorisationException You are not permitted to access this resource"));
-
- String serverUrl = server.url("/").toString();
- assertThatThrownBy(() -> underTest.validateReadPermission(serverUrl, "token"))
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessage("Invalid personal access token");
- }
-
- }
|