]> source.dussan.org Git - sonarqube.git/blob
64ceedb12127df506065e0adfdfd656a10dfb4f6
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2022 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.github;
21
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;
40
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;
46
47 @RunWith(DataProviderRunner.class)
48 public class GithubApplicationHttpClientImplTest {
49   private static final String BETA_API_HEADER = "application/vnd.github.antiope-preview+json, " +
50     "application/vnd.github.machine-man-preview+json, " +
51     "application/vnd.github.v3+json";
52   @Rule
53   public MockWebServer server = new MockWebServer();
54
55   private GithubApplicationHttpClientImpl underTest;
56
57   private final AccessToken accessToken = new UserAccessToken(randomAlphabetic(10));
58   private final String randomEndPoint = "/" + randomAlphabetic(10);
59   private final String randomBody = randomAlphabetic(40);
60   private String appUrl;
61
62   @Before
63   public void setUp() {
64     this.appUrl = format("http://%s:%s", server.getHostName(), server.getPort());
65     this.underTest = new GithubApplicationHttpClientImpl(new ConstantTimeoutConfiguration(500));
66   }
67
68   @Test
69   public void get_fails_if_endpoint_does_not_start_with_slash() throws IOException {
70     assertThatThrownBy(() -> underTest.get(appUrl, accessToken, "api/foo/bar"))
71       .hasMessage("endpoint must start with '/' or 'http'")
72       .isInstanceOf(IllegalArgumentException.class);
73   }
74
75   @Test
76   public void get_fails_if_endpoint_does_not_start_with_http() throws IOException {
77     assertThatThrownBy(() -> underTest.get(appUrl, accessToken, "ttp://api/foo/bar"))
78       .isInstanceOf(IllegalArgumentException.class)
79       .hasMessage("endpoint must start with '/' or 'http'");
80   }
81
82   @Test
83   public void get_fails_if_github_endpoint_is_invalid() throws IOException {
84     assertThatThrownBy(() -> underTest.get("invalidUrl", accessToken, "/endpoint"))
85       .isInstanceOf(IllegalArgumentException.class)
86       .hasMessage("invalidUrl/endpoint is not a valid url");
87   }
88
89   @Test
90   public void get_adds_authentication_header_with_Bearer_type_and_Accept_header() throws IOException, InterruptedException {
91     server.enqueue(new MockResponse());
92
93     GetResponse response = underTest.get(appUrl, accessToken, randomEndPoint);
94
95     assertThat(response).isNotNull();
96     RecordedRequest recordedRequest = server.takeRequest();
97     assertThat(recordedRequest.getMethod()).isEqualTo("GET");
98     assertThat(recordedRequest.getPath()).isEqualTo(randomEndPoint);
99     assertThat(recordedRequest.getHeader("Authorization")).isEqualTo("token " + accessToken.getValue());
100     assertThat(recordedRequest.getHeader("Accept")).isEqualTo(BETA_API_HEADER);
101   }
102
103   @Test
104   public void get_returns_body_as_response_if_code_is_200() throws IOException {
105     server.enqueue(new MockResponse().setResponseCode(200).setBody(randomBody));
106
107     GetResponse response = underTest.get(appUrl, accessToken, randomEndPoint);
108
109     assertThat(response.getContent()).contains(randomBody);
110   }
111
112   @Test
113   public void get_timeout() {
114     server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.NO_RESPONSE));
115
116     try {
117       underTest.get(appUrl, accessToken, randomEndPoint);
118       fail("Expected timeout");
119     } catch (Exception e) {
120       assertThat(e).isInstanceOf(SocketTimeoutException.class);
121     }
122   }
123
124   @Test
125   @UseDataProvider("someHttpCodesWithContentBut200")
126   public void get_empty_response_if_code_is_not_200(int code) throws IOException {
127     server.enqueue(new MockResponse().setResponseCode(code).setBody(randomBody));
128
129     GetResponse response = underTest.get(appUrl, accessToken, randomEndPoint);
130
131     assertThat(response.getContent()).isEmpty();
132   }
133
134   @Test
135   public void get_returns_empty_endPoint_when_no_link_header() throws IOException {
136     server.enqueue(new MockResponse().setBody(randomBody));
137
138     GetResponse response = underTest.get(appUrl, accessToken, randomEndPoint);
139
140     assertThat(response.getNextEndPoint()).isEmpty();
141   }
142
143   @Test
144   public void get_returns_empty_endPoint_when_link_header_does_not_have_next_rel() throws IOException {
145     server.enqueue(new MockResponse().setBody(randomBody)
146       .setHeader("link", "<https://api.github.com/installation/repositories?per_page=5&page=4>; rel=\"prev\", " +
147         "<https://api.github.com/installation/repositories?per_page=5&page=1>; rel=\"first\""));
148
149     GetResponse response = underTest.get(appUrl, accessToken, randomEndPoint);
150
151     assertThat(response.getNextEndPoint()).isEmpty();
152   }
153
154   @Test
155   @UseDataProvider("linkHeadersWithNextRel")
156   public void get_returns_endPoint_when_link_header_has_next_rel(String linkHeader) throws IOException {
157     server.enqueue(new MockResponse().setBody(randomBody)
158       .setHeader("link", linkHeader));
159
160     GetResponse response = underTest.get(appUrl, accessToken, randomEndPoint);
161
162     assertThat(response.getNextEndPoint()).contains("https://api.github.com/installation/repositories?per_page=5&page=2");
163   }
164
165   @Test
166   public void get_returns_endPoint_when_link_header_has_next_rel_different_case() throws IOException {
167     String linkHeader = "<https://api.github.com/installation/repositories?per_page=5&page=2>; rel=\"next\"";
168     server.enqueue(new MockResponse().setBody(randomBody)
169       .setHeader("Link", linkHeader));
170
171     GetResponse response = underTest.get(appUrl, accessToken, randomEndPoint);
172
173     assertThat(response.getNextEndPoint()).contains("https://api.github.com/installation/repositories?per_page=5&page=2");
174   }
175
176   @DataProvider
177   public static Object[][] linkHeadersWithNextRel() {
178     String expected = "https://api.github.com/installation/repositories?per_page=5&page=2";
179     return new Object[][] {
180       {"<" + expected + ">; rel=\"next\""},
181       {"<" + expected + ">; rel=\"next\", " +
182         "<https://api.github.com/installation/repositories?per_page=5&page=1>; rel=\"first\""},
183       {"<https://api.github.com/installation/repositories?per_page=5&page=1>; rel=\"first\", " +
184         "<" + expected + ">; rel=\"next\""},
185       {"<https://api.github.com/installation/repositories?per_page=5&page=1>; rel=\"first\", " +
186         "<" + expected + ">; rel=\"next\", " +
187         "<https://api.github.com/installation/repositories?per_page=5&page=5>; rel=\"last\""},
188     };
189   }
190
191   @DataProvider
192   public static Object[][] someHttpCodesWithContentBut200() {
193     return new Object[][] {
194       {201},
195       {202},
196       {203},
197       {404},
198       {500}
199     };
200   }
201
202   @Test
203   public void post_fails_if_endpoint_does_not_start_with_slash() throws IOException {
204     assertThatThrownBy(() -> underTest.post(appUrl, accessToken, "api/foo/bar"))
205       .isInstanceOf(IllegalArgumentException.class)
206       .hasMessage("endpoint must start with '/' or 'http'");
207   }
208
209   @Test
210   public void post_fails_if_endpoint_does_not_start_with_http() throws IOException {
211     assertThatThrownBy(() -> underTest.post(appUrl, accessToken, "ttp://api/foo/bar"))
212       .isInstanceOf(IllegalArgumentException.class)
213       .hasMessage("endpoint must start with '/' or 'http'");
214   }
215
216   @Test
217   public void post_fails_if_github_endpoint_is_invalid() throws IOException {
218     assertThatThrownBy(() -> underTest.post("invalidUrl", accessToken, "/endpoint"))
219       .isInstanceOf(IllegalArgumentException.class)
220       .hasMessage("invalidUrl/endpoint is not a valid url");
221   }
222
223   @Test
224   public void post_adds_authentication_header_with_Bearer_type_and_Accept_header() throws IOException, InterruptedException {
225     server.enqueue(new MockResponse());
226
227     Response response = underTest.post(appUrl, accessToken, randomEndPoint);
228
229     assertThat(response).isNotNull();
230     RecordedRequest recordedRequest = server.takeRequest();
231     assertThat(recordedRequest.getMethod()).isEqualTo("POST");
232     assertThat(recordedRequest.getPath()).isEqualTo(randomEndPoint);
233     assertThat(recordedRequest.getHeader("Authorization")).isEqualTo("token " + accessToken.getValue());
234     assertThat(recordedRequest.getHeader("Accept")).isEqualTo(BETA_API_HEADER);
235   }
236
237   @Test
238   @DataProvider({"200", "201", "202"})
239   public void post_returns_body_as_response_if_success(int code) throws IOException {
240     server.enqueue(new MockResponse().setResponseCode(code).setBody(randomBody));
241
242     Response response = underTest.post(appUrl, accessToken, randomEndPoint);
243
244     assertThat(response.getContent()).contains(randomBody);
245   }
246
247   @Test
248   public void post_returns_empty_response_if_code_is_204() throws IOException {
249     server.enqueue(new MockResponse().setResponseCode(204));
250
251     Response response = underTest.post(appUrl, accessToken, randomEndPoint);
252
253     assertThat(response.getContent()).isEmpty();
254   }
255
256   @Test
257   @UseDataProvider("httpCodesBut200_201And204")
258   public void post_has_json_error_in_body_if_code_is_neither_200_201_nor_204(int code) throws IOException {
259     server.enqueue(new MockResponse().setResponseCode(code).setBody(randomBody));
260
261     Response response = underTest.post(appUrl, accessToken, randomEndPoint);
262
263     assertThat(response.getContent()).contains(randomBody);
264   }
265
266   @DataProvider
267   public static Object[][] httpCodesBut200_201And204() {
268     return new Object[][] {
269       {202},
270       {203},
271       {400},
272       {401},
273       {403},
274       {404},
275       {500}
276     };
277   }
278
279   @Test
280   public void post_with_json_body_adds_json_to_body_request() throws IOException, InterruptedException {
281     server.enqueue(new MockResponse());
282     String jsonBody = "{\"foo\": \"bar\"}";
283     Response response = underTest.post(appUrl, accessToken, randomEndPoint, jsonBody);
284
285     assertThat(response).isNotNull();
286     RecordedRequest recordedRequest = server.takeRequest();
287     assertThat(recordedRequest.getBody().readUtf8()).isEqualTo(jsonBody);
288   }
289
290   @Test
291   public void patch_with_json_body_adds_json_to_body_request() throws IOException, InterruptedException {
292     server.enqueue(new MockResponse());
293     String jsonBody = "{\"foo\": \"bar\"}";
294
295     Response response = underTest.patch(appUrl, accessToken, randomEndPoint, jsonBody);
296
297     assertThat(response).isNotNull();
298     RecordedRequest recordedRequest = server.takeRequest();
299     assertThat(recordedRequest.getBody().readUtf8()).isEqualTo(jsonBody);
300   }
301
302   @Test
303   public void patch_returns_body_as_response_if_code_is_200() throws IOException {
304     server.enqueue(new MockResponse().setResponseCode(200).setBody(randomBody));
305
306     Response response = underTest.patch(appUrl, accessToken, randomEndPoint, "{}");
307
308     assertThat(response.getContent()).contains(randomBody);
309   }
310
311   @Test
312   public void patch_returns_empty_response_if_code_is_204() throws IOException {
313     server.enqueue(new MockResponse().setResponseCode(204));
314
315     Response response = underTest.patch(appUrl, accessToken, randomEndPoint, "{}");
316
317     assertThat(response.getContent()).isEmpty();
318   }
319
320   @Test
321   public void delete_returns_empty_response_if_code_is_204() throws IOException {
322     server.enqueue(new MockResponse().setResponseCode(204));
323
324     Response response = underTest.delete(appUrl, accessToken, randomEndPoint);
325
326     assertThat(response.getContent()).isEmpty();
327   }
328
329   @DataProvider
330   public static Object[][] httpCodesBut204() {
331     return new Object[][] {
332       {200},
333       {201},
334       {202},
335       {203},
336       {400},
337       {401},
338       {403},
339       {404},
340       {500}
341     };
342   }
343
344   @Test
345   @UseDataProvider("httpCodesBut204")
346   public void delete_returns_response_if_code_is_not_204(int code) throws IOException {
347     server.enqueue(new MockResponse().setResponseCode(code).setBody(randomBody));
348
349     Response response = underTest.delete(appUrl, accessToken, randomEndPoint);
350
351     assertThat(response.getContent()).hasValue(randomBody);
352   }
353
354   @DataProvider
355   public static Object[][] httpCodesBut200And204() {
356     return new Object[][] {
357       {201},
358       {202},
359       {203},
360       {400},
361       {401},
362       {403},
363       {404},
364       {500}
365     };
366   }
367
368   @Test
369   @UseDataProvider("httpCodesBut200And204")
370   public void patch_has_json_error_in_body_if_code_is_neither_200_nor_204(int code) throws IOException {
371     server.enqueue(new MockResponse().setResponseCode(code).setBody(randomBody));
372
373     Response response = underTest.patch(appUrl, accessToken, randomEndPoint, "{}");
374
375     assertThat(response.getContent()).contains(randomBody);
376   }
377
378 }