]> source.dussan.org Git - sonarqube.git/blob
ba3e0bdf8a22c0ab28cc6b7555e7ad23447c6f3f
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2021 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 3 of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software Foundation,
18  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
19  */
20 package org.sonar.alm.client.bitbucket.bitbucketcloud;
21
22 import java.io.IOException;
23 import okhttp3.Call;
24 import okhttp3.OkHttpClient;
25 import okhttp3.Protocol;
26 import okhttp3.Request;
27 import okhttp3.Response;
28 import okhttp3.mockwebserver.MockResponse;
29 import okhttp3.mockwebserver.MockWebServer;
30 import okhttp3.mockwebserver.RecordedRequest;
31 import okhttp3.mockwebserver.SocketPolicy;
32 import org.junit.After;
33 import org.junit.Before;
34 import org.junit.Test;
35 import org.sonarqube.ws.client.OkHttpClientBuilder;
36
37 import static org.assertj.core.api.Assertions.assertThat;
38 import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
39 import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
40 import static org.mockito.ArgumentMatchers.any;
41 import static org.mockito.Mockito.mock;
42 import static org.mockito.Mockito.when;
43 import static org.sonar.alm.client.bitbucket.bitbucketcloud.BitbucketCloudRestClient.JSON_MEDIA_TYPE;
44
45 public class BitbucketCloudRestClientTest {
46   private final MockWebServer server = new MockWebServer();
47   private BitbucketCloudRestClient underTest;
48
49   @Before
50   public void prepare() throws IOException {
51     server.start();
52
53     underTest = new BitbucketCloudRestClient(new OkHttpClientBuilder().build(), server.url("/").toString(), server.url("/").toString());
54   }
55
56   @After
57   public void stopServer() throws IOException {
58     server.shutdown();
59   }
60
61   @Test
62   public void failIfUnauthorized() {
63     server.enqueue(new MockResponse().setResponseCode(401).setBody("Unauthorized"));
64
65     assertThatIllegalArgumentException()
66       .isThrownBy(() -> underTest.validate("clientId", "clientSecret", "workspace"))
67       .withMessage("Unable to contact Bitbucket Cloud servers");
68   }
69
70   @Test
71   public void validate_fails_with_IAE_if_timeout() {
72     server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.NO_RESPONSE));
73
74     assertThatIllegalArgumentException()
75       .isThrownBy(() -> underTest.validate("clientId", "clientSecret", "workspace"));
76   }
77
78   @Test
79   public void validate_success() throws Exception {
80     String tokenResponse = "{\"scopes\": \"webhook pullrequest:write\", \"access_token\": \"token\", \"expires_in\": 7200, "
81       + "\"token_type\": \"bearer\", \"state\": \"client_credentials\", \"refresh_token\": \"abc\"}";
82
83     server.enqueue(new MockResponse().setBody(tokenResponse));
84     server.enqueue(new MockResponse().setBody("OK"));
85
86     underTest.validate("clientId", "clientSecret", "workspace");
87
88     RecordedRequest request = server.takeRequest();
89     assertThat(request.getPath()).isEqualTo("/");
90     assertThat(request.getHeader("Authorization")).isNotNull();
91     assertThat(request.getBody().readUtf8()).isEqualTo("grant_type=client_credentials");
92   }
93
94   @Test
95   public void validate_with_invalid_workspace() {
96     String tokenResponse = "{\"scopes\": \"webhook pullrequest:write\", \"access_token\": \"token\", \"expires_in\": 7200, "
97       + "\"token_type\": \"bearer\", \"state\": \"client_credentials\", \"refresh_token\": \"abc\"}";
98     server.enqueue(new MockResponse().setBody(tokenResponse).setResponseCode(200).setHeader("Content-Type", JSON_MEDIA_TYPE));
99     String response = "{\"type\": \"error\", \"error\": {\"message\": \"No workspace with identifier 'workspace'.\"}}";
100
101     server.enqueue(new MockResponse().setBody(response).setResponseCode(404).setHeader("Content-Type", JSON_MEDIA_TYPE));
102
103     assertThatExceptionOfType(IllegalArgumentException.class)
104       .isThrownBy(() -> underTest.validate("clientId", "clientSecret", "workspace"))
105       .withMessage("No workspace with identifier 'workspace'.");
106   }
107
108   @Test
109   public void validate_with_private_consumer() {
110     String response = "{\"error_description\": \"Cannot use client_credentials with a consumer marked as \\\"public\\\". "
111       + "Calls for auto generated consumers should use urn:bitbucket:oauth2:jwt instead.\", \"error\": \"invalid_grant\"}";
112
113     server.enqueue(new MockResponse().setBody(response).setResponseCode(400).setHeader("Content-Type", JSON_MEDIA_TYPE));
114
115     assertThatExceptionOfType(IllegalArgumentException.class)
116       .isThrownBy(() -> underTest.validate("clientId", "clientSecret", "workspace"))
117       .withMessage("Unable to contact Bitbucket Cloud servers: Configure the OAuth consumer in the Bitbucket workspace to be a private consumer");
118   }
119
120   @Test
121   public void validate_with_invalid_credentials() {
122     String response = "{\"error_description\": \"Invalid OAuth client credentials\", \"error\": \"unauthorized_client\"}";
123
124     server.enqueue(new MockResponse().setBody(response).setResponseCode(400).setHeader("Content-Type", JSON_MEDIA_TYPE));
125
126     assertThatExceptionOfType(IllegalArgumentException.class)
127       .isThrownBy(() -> underTest.validate("clientId", "clientSecret", "workspace"))
128       .withMessage("Unable to contact Bitbucket Cloud servers: Check your credentials");
129   }
130
131   @Test
132   public void validate_with_insufficient_privileges() {
133     String tokenResponse = "{\"scopes\": \"webhook pullrequest:write\", \"access_token\": \"token\", \"expires_in\": 7200, "
134       + "\"token_type\": \"bearer\", \"state\": \"client_credentials\", \"refresh_token\": \"abc\"}";
135     server.enqueue(new MockResponse().setBody(tokenResponse).setResponseCode(200).setHeader("Content-Type", JSON_MEDIA_TYPE));
136
137     String error = "{\"type\": \"error\", \"error\": {\"message\": \"Your credentials lack one or more required privilege scopes.\", \"detail\": "
138       + "{\"granted\": [\"email\"], \"required\": [\"account\"]}}}\n";
139     server.enqueue(new MockResponse().setBody(error).setResponseCode(400).setHeader("Content-Type", JSON_MEDIA_TYPE));
140
141     assertThatExceptionOfType(IllegalArgumentException.class)
142       .isThrownBy(() -> underTest.validate("clientId", "clientSecret", "workspace"))
143       .withMessage("Unable to contact Bitbucket Cloud servers: Your credentials lack one or more required privilege scopes.");
144   }
145
146   @Test
147   public void nullErrorBodyIsSupported() throws IOException {
148     OkHttpClient clientMock = mock(OkHttpClient.class);
149     Call callMock = mock(Call.class);
150
151     when(callMock.execute()).thenReturn(new Response.Builder()
152       .request(new Request.Builder().url("http://any.test").build())
153       .protocol(Protocol.HTTP_1_1)
154       .code(500)
155       .message("")
156       .build());
157     when(clientMock.newCall(any())).thenReturn(callMock);
158
159     underTest = new BitbucketCloudRestClient(clientMock);
160
161     assertThatIllegalArgumentException()
162       .isThrownBy(() -> underTest.validate("clientId", "clientSecret", "workspace"))
163       .withMessage("Unable to contact Bitbucket Cloud servers");
164   }
165
166   @Test
167   public void invalidJsonResponseBodyIsSupported() {
168     server.enqueue(new MockResponse().setResponseCode(500)
169       .setHeader("content-type", "application/json; charset=utf-8")
170       .setBody("not a JSON string"));
171
172     assertThatIllegalArgumentException()
173       .isThrownBy(() -> underTest.validate("clientId", "clientSecret", "workspace"))
174       .withMessage("Unable to contact Bitbucket Cloud servers");
175   }
176
177   @Test
178   public void responseBodyWithoutErrorFieldIsSupported() {
179     server.enqueue(new MockResponse().setResponseCode(500)
180       .setHeader("content-type", "application/json; charset=utf-8")
181       .setBody("{\"foo\": \"bar\"}"));
182
183     assertThatIllegalArgumentException()
184       .isThrownBy(() -> underTest.validate("clientId", "clientSecret", "workspace"))
185       .withMessage("Unable to contact Bitbucket Cloud servers");
186   }
187 }