3 * Copyright (C) 2009-2021 SonarSource SA
4 * mailto:info AT sonarsource DOT com
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.
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.
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.
20 package org.sonar.alm.client.github;
22 import com.tngtech.java.junit.dataprovider.DataProvider;
23 import com.tngtech.java.junit.dataprovider.DataProviderRunner;
24 import com.tngtech.java.junit.dataprovider.UseDataProvider;
25 import java.io.IOException;
26 import java.net.SocketTimeoutException;
27 import okhttp3.mockwebserver.MockResponse;
28 import okhttp3.mockwebserver.MockWebServer;
29 import okhttp3.mockwebserver.RecordedRequest;
30 import okhttp3.mockwebserver.SocketPolicy;
31 import org.junit.Before;
32 import org.junit.Rule;
33 import org.junit.Test;
34 import org.junit.runner.RunWith;
35 import org.sonar.alm.client.ConstantTimeoutConfiguration;
36 import org.sonar.alm.client.github.GithubApplicationHttpClient.GetResponse;
37 import org.sonar.alm.client.github.GithubApplicationHttpClient.Response;
38 import org.sonar.alm.client.github.security.AccessToken;
39 import org.sonar.alm.client.github.security.UserAccessToken;
41 import static java.lang.String.format;
42 import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
43 import static org.assertj.core.api.Assertions.assertThat;
44 import static org.assertj.core.api.Assertions.assertThatThrownBy;
45 import static org.junit.Assert.fail;
47 @RunWith(DataProviderRunner.class)
48 public class GithubApplicationHttpClientImplTest {
49 private static final String BETA_API_HEADER = "application/vnd.github.antiope-preview+json, application/vnd.github.machine-man-preview+json";
51 public MockWebServer server = new MockWebServer();
53 private GithubApplicationHttpClientImpl underTest;
55 private final AccessToken accessToken = new UserAccessToken(randomAlphabetic(10));
56 private final String randomEndPoint = "/" + randomAlphabetic(10);
57 private final String randomBody = randomAlphabetic(40);
58 private String appUrl;
62 this.appUrl = format("http://%s:%s", server.getHostName(), server.getPort());
63 this.underTest = new GithubApplicationHttpClientImpl(new ConstantTimeoutConfiguration(500));
67 public void get_fails_if_endpoint_does_not_start_with_slash() throws IOException {
68 assertThatThrownBy(() -> underTest.get(appUrl, accessToken, "api/foo/bar"))
69 .hasMessage("endpoint must start with '/' or 'http'")
70 .isInstanceOf(IllegalArgumentException.class);
74 public void get_fails_if_endpoint_does_not_start_with_http() throws IOException {
75 assertThatThrownBy(() -> underTest.get(appUrl, accessToken, "ttp://api/foo/bar"))
76 .isInstanceOf(IllegalArgumentException.class)
77 .hasMessage("endpoint must start with '/' or 'http'");
81 public void get_fails_if_github_endpoint_is_invalid() throws IOException {
82 assertThatThrownBy(() -> underTest.get("invalidUrl", accessToken, "/endpoint"))
83 .isInstanceOf(IllegalArgumentException.class)
84 .hasMessage("invalidUrl/endpoint is not a valid url");
88 public void get_adds_authentication_header_with_Bearer_type_and_Accept_header() throws IOException, InterruptedException {
89 server.enqueue(new MockResponse());
91 GetResponse response = underTest.get(appUrl, accessToken, randomEndPoint);
93 assertThat(response).isNotNull();
94 RecordedRequest recordedRequest = server.takeRequest();
95 assertThat(recordedRequest.getMethod()).isEqualTo("GET");
96 assertThat(recordedRequest.getPath()).isEqualTo(randomEndPoint);
97 assertThat(recordedRequest.getHeader("Authorization")).isEqualTo("token " + accessToken.getValue());
98 assertThat(recordedRequest.getHeader("Accept")).isEqualTo(BETA_API_HEADER);
102 public void get_returns_body_as_response_if_code_is_200() throws IOException {
103 server.enqueue(new MockResponse().setResponseCode(200).setBody(randomBody));
105 GetResponse response = underTest.get(appUrl, accessToken, randomEndPoint);
107 assertThat(response.getContent()).contains(randomBody);
111 public void get_timeout() {
112 server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.NO_RESPONSE));
115 underTest.get(appUrl, accessToken, randomEndPoint);
116 fail("Expected timeout");
117 } catch (Exception e) {
118 assertThat(e).isInstanceOf(SocketTimeoutException.class);
123 @UseDataProvider("someHttpCodesWithContentBut200")
124 public void get_empty_response_if_code_is_not_200(int code) throws IOException {
125 server.enqueue(new MockResponse().setResponseCode(code).setBody(randomBody));
127 GetResponse response = underTest.get(appUrl, accessToken, randomEndPoint);
129 assertThat(response.getContent()).isEmpty();
133 public void get_returns_empty_endPoint_when_no_link_header() throws IOException {
134 server.enqueue(new MockResponse().setBody(randomBody));
136 GetResponse response = underTest.get(appUrl, accessToken, randomEndPoint);
138 assertThat(response.getNextEndPoint()).isEmpty();
142 public void get_returns_empty_endPoint_when_link_header_does_not_have_next_rel() throws IOException {
143 server.enqueue(new MockResponse().setBody(randomBody)
144 .setHeader("link", "<https://api.github.com/installation/repositories?per_page=5&page=4>; rel=\"prev\", " +
145 "<https://api.github.com/installation/repositories?per_page=5&page=1>; rel=\"first\""));
147 GetResponse response = underTest.get(appUrl, accessToken, randomEndPoint);
149 assertThat(response.getNextEndPoint()).isEmpty();
153 @UseDataProvider("linkHeadersWithNextRel")
154 public void get_returns_endPoint_when_link_header_has_next_rel(String linkHeader) throws IOException {
155 server.enqueue(new MockResponse().setBody(randomBody)
156 .setHeader("link", linkHeader));
158 GetResponse response = underTest.get(appUrl, accessToken, randomEndPoint);
160 assertThat(response.getNextEndPoint()).contains("https://api.github.com/installation/repositories?per_page=5&page=2");
164 public static Object[][] linkHeadersWithNextRel() {
165 String expected = "https://api.github.com/installation/repositories?per_page=5&page=2";
166 return new Object[][] {
167 {"<" + expected + ">; rel=\"next\""},
168 {"<" + expected + ">; rel=\"next\", " +
169 "<https://api.github.com/installation/repositories?per_page=5&page=1>; rel=\"first\""},
170 {"<https://api.github.com/installation/repositories?per_page=5&page=1>; rel=\"first\", " +
171 "<" + expected + ">; rel=\"next\""},
172 {"<https://api.github.com/installation/repositories?per_page=5&page=1>; rel=\"first\", " +
173 "<" + expected + ">; rel=\"next\", " +
174 "<https://api.github.com/installation/repositories?per_page=5&page=5>; rel=\"last\""},
179 public static Object[][] someHttpCodesWithContentBut200() {
180 return new Object[][] {
190 public void post_fails_if_endpoint_does_not_start_with_slash() throws IOException {
191 assertThatThrownBy(() -> underTest.post(appUrl, accessToken, "api/foo/bar"))
192 .isInstanceOf(IllegalArgumentException.class)
193 .hasMessage("endpoint must start with '/' or 'http'");
197 public void post_fails_if_endpoint_does_not_start_with_http() throws IOException {
198 assertThatThrownBy(() -> underTest.post(appUrl, accessToken, "ttp://api/foo/bar"))
199 .isInstanceOf(IllegalArgumentException.class)
200 .hasMessage("endpoint must start with '/' or 'http'");
204 public void post_fails_if_github_endpoint_is_invalid() throws IOException {
205 assertThatThrownBy(() -> underTest.post("invalidUrl", accessToken, "/endpoint"))
206 .isInstanceOf(IllegalArgumentException.class)
207 .hasMessage("invalidUrl/endpoint is not a valid url");
211 public void post_adds_authentication_header_with_Bearer_type_and_Accept_header() throws IOException, InterruptedException {
212 server.enqueue(new MockResponse());
214 Response response = underTest.post(appUrl, accessToken, randomEndPoint);
216 assertThat(response).isNotNull();
217 RecordedRequest recordedRequest = server.takeRequest();
218 assertThat(recordedRequest.getMethod()).isEqualTo("POST");
219 assertThat(recordedRequest.getPath()).isEqualTo(randomEndPoint);
220 assertThat(recordedRequest.getHeader("Authorization")).isEqualTo("token " + accessToken.getValue());
221 assertThat(recordedRequest.getHeader("Accept")).isEqualTo(BETA_API_HEADER);
225 public void post_returns_body_as_response_if_code_is_200() throws IOException {
226 server.enqueue(new MockResponse().setResponseCode(200).setBody(randomBody));
228 Response response = underTest.post(appUrl, accessToken, randomEndPoint);
230 assertThat(response.getContent()).contains(randomBody);
234 public void post_returns_body_as_response_if_code_is_201() throws IOException {
235 server.enqueue(new MockResponse().setResponseCode(201).setBody(randomBody));
237 Response response = underTest.post(appUrl, accessToken, randomEndPoint);
239 assertThat(response.getContent()).contains(randomBody);
243 public void post_returns_empty_response_if_code_is_204() throws IOException {
244 server.enqueue(new MockResponse().setResponseCode(204));
246 Response response = underTest.post(appUrl, accessToken, randomEndPoint);
248 assertThat(response.getContent()).isEmpty();
252 @UseDataProvider("httpCodesBut200_201And204")
253 public void post_has_json_error_in_body_if_code_is_neither_200_201_nor_204(int code) throws IOException {
254 server.enqueue(new MockResponse().setResponseCode(code).setBody(randomBody));
256 Response response = underTest.post(appUrl, accessToken, randomEndPoint);
258 assertThat(response.getContent()).contains(randomBody);
262 public static Object[][] httpCodesBut200_201And204() {
263 return new Object[][] {
275 public void post_with_json_body_adds_json_to_body_request() throws IOException, InterruptedException {
276 server.enqueue(new MockResponse());
277 String jsonBody = "{\"foo\": \"bar\"}";
278 Response response = underTest.post(appUrl, accessToken, randomEndPoint, jsonBody);
280 assertThat(response).isNotNull();
281 RecordedRequest recordedRequest = server.takeRequest();
282 assertThat(recordedRequest.getBody().readUtf8()).isEqualTo(jsonBody);
286 public void patch_with_json_body_adds_json_to_body_request() throws IOException, InterruptedException {
287 server.enqueue(new MockResponse());
288 String jsonBody = "{\"foo\": \"bar\"}";
290 Response response = underTest.patch(appUrl, accessToken, randomEndPoint, jsonBody);
292 assertThat(response).isNotNull();
293 RecordedRequest recordedRequest = server.takeRequest();
294 assertThat(recordedRequest.getBody().readUtf8()).isEqualTo(jsonBody);
298 public void patch_returns_body_as_response_if_code_is_200() throws IOException {
299 server.enqueue(new MockResponse().setResponseCode(200).setBody(randomBody));
301 Response response = underTest.patch(appUrl, accessToken, randomEndPoint, "{}");
303 assertThat(response.getContent()).contains(randomBody);
307 public void patch_returns_empty_response_if_code_is_204() throws IOException {
308 server.enqueue(new MockResponse().setResponseCode(204));
310 Response response = underTest.patch(appUrl, accessToken, randomEndPoint, "{}");
312 assertThat(response.getContent()).isEmpty();
316 public void delete_returns_empty_response_if_code_is_204() throws IOException {
317 server.enqueue(new MockResponse().setResponseCode(204));
319 Response response = underTest.delete(appUrl, accessToken, randomEndPoint);
321 assertThat(response.getContent()).isEmpty();
325 public static Object[][] httpCodesBut204() {
326 return new Object[][] {
340 @UseDataProvider("httpCodesBut204")
341 public void delete_returns_response_if_code_is_not_204(int code) throws IOException {
342 server.enqueue(new MockResponse().setResponseCode(code).setBody(randomBody));
344 Response response = underTest.delete(appUrl, accessToken, randomEndPoint);
346 assertThat(response.getContent()).hasValue(randomBody);
350 public static Object[][] httpCodesBut200And204() {
351 return new Object[][] {
364 @UseDataProvider("httpCodesBut200And204")
365 public void patch_has_json_error_in_body_if_code_is_neither_200_nor_204(int code) throws IOException {
366 server.enqueue(new MockResponse().setResponseCode(code).setBody(randomBody));
368 Response response = underTest.patch(appUrl, accessToken, randomEndPoint, "{}");
370 assertThat(response.getContent()).contains(randomBody);