]> source.dussan.org Git - sonarqube.git/blob
44516fbaeafd850e7ea49b36a06a17cbe6cf4c19
[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.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, application/vnd.github.machine-man-preview+json";
50   @Rule
51   public MockWebServer server = new MockWebServer();
52
53   private GithubApplicationHttpClientImpl underTest;
54
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;
59
60   @Before
61   public void setUp() {
62     this.appUrl = format("http://%s:%s", server.getHostName(), server.getPort());
63     this.underTest = new GithubApplicationHttpClientImpl(new ConstantTimeoutConfiguration(500));
64   }
65
66   @Test
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);
71   }
72
73   @Test
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'");
78   }
79
80   @Test
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");
85   }
86
87   @Test
88   public void get_adds_authentication_header_with_Bearer_type_and_Accept_header() throws IOException, InterruptedException {
89     server.enqueue(new MockResponse());
90
91     GetResponse response = underTest.get(appUrl, accessToken, randomEndPoint);
92
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);
99   }
100
101   @Test
102   public void get_returns_body_as_response_if_code_is_200() throws IOException {
103     server.enqueue(new MockResponse().setResponseCode(200).setBody(randomBody));
104
105     GetResponse response = underTest.get(appUrl, accessToken, randomEndPoint);
106
107     assertThat(response.getContent()).contains(randomBody);
108   }
109
110   @Test
111   public void get_timeout() {
112     server.enqueue(new MockResponse().setSocketPolicy(SocketPolicy.NO_RESPONSE));
113
114     try {
115       underTest.get(appUrl, accessToken, randomEndPoint);
116       fail("Expected timeout");
117     } catch (Exception e) {
118       assertThat(e).isInstanceOf(SocketTimeoutException.class);
119     }
120   }
121
122   @Test
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));
126
127     GetResponse response = underTest.get(appUrl, accessToken, randomEndPoint);
128
129     assertThat(response.getContent()).isEmpty();
130   }
131
132   @Test
133   public void get_returns_empty_endPoint_when_no_link_header() throws IOException {
134     server.enqueue(new MockResponse().setBody(randomBody));
135
136     GetResponse response = underTest.get(appUrl, accessToken, randomEndPoint);
137
138     assertThat(response.getNextEndPoint()).isEmpty();
139   }
140
141   @Test
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\""));
146
147     GetResponse response = underTest.get(appUrl, accessToken, randomEndPoint);
148
149     assertThat(response.getNextEndPoint()).isEmpty();
150   }
151
152   @Test
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));
157
158     GetResponse response = underTest.get(appUrl, accessToken, randomEndPoint);
159
160     assertThat(response.getNextEndPoint()).contains("https://api.github.com/installation/repositories?per_page=5&page=2");
161   }
162
163   @DataProvider
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\""},
175     };
176   }
177
178   @DataProvider
179   public static Object[][] someHttpCodesWithContentBut200() {
180     return new Object[][] {
181       {201},
182       {202},
183       {203},
184       {404},
185       {500}
186     };
187   }
188
189   @Test
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'");
194   }
195
196   @Test
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'");
201   }
202
203   @Test
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");
208   }
209
210   @Test
211   public void post_adds_authentication_header_with_Bearer_type_and_Accept_header() throws IOException, InterruptedException {
212     server.enqueue(new MockResponse());
213
214     Response response = underTest.post(appUrl, accessToken, randomEndPoint);
215
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);
222   }
223
224   @Test
225   public void post_returns_body_as_response_if_code_is_200() throws IOException {
226     server.enqueue(new MockResponse().setResponseCode(200).setBody(randomBody));
227
228     Response response = underTest.post(appUrl, accessToken, randomEndPoint);
229
230     assertThat(response.getContent()).contains(randomBody);
231   }
232
233   @Test
234   public void post_returns_body_as_response_if_code_is_201() throws IOException {
235     server.enqueue(new MockResponse().setResponseCode(201).setBody(randomBody));
236
237     Response response = underTest.post(appUrl, accessToken, randomEndPoint);
238
239     assertThat(response.getContent()).contains(randomBody);
240   }
241
242   @Test
243   public void post_returns_empty_response_if_code_is_204() throws IOException {
244     server.enqueue(new MockResponse().setResponseCode(204));
245
246     Response response = underTest.post(appUrl, accessToken, randomEndPoint);
247
248     assertThat(response.getContent()).isEmpty();
249   }
250
251   @Test
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));
255
256     Response response = underTest.post(appUrl, accessToken, randomEndPoint);
257
258     assertThat(response.getContent()).contains(randomBody);
259   }
260
261   @DataProvider
262   public static Object[][] httpCodesBut200_201And204() {
263     return new Object[][] {
264       {202},
265       {203},
266       {400},
267       {401},
268       {403},
269       {404},
270       {500}
271     };
272   }
273
274   @Test
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);
279
280     assertThat(response).isNotNull();
281     RecordedRequest recordedRequest = server.takeRequest();
282     assertThat(recordedRequest.getBody().readUtf8()).isEqualTo(jsonBody);
283   }
284
285   @Test
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\"}";
289
290     Response response = underTest.patch(appUrl, accessToken, randomEndPoint, jsonBody);
291
292     assertThat(response).isNotNull();
293     RecordedRequest recordedRequest = server.takeRequest();
294     assertThat(recordedRequest.getBody().readUtf8()).isEqualTo(jsonBody);
295   }
296
297   @Test
298   public void patch_returns_body_as_response_if_code_is_200() throws IOException {
299     server.enqueue(new MockResponse().setResponseCode(200).setBody(randomBody));
300
301     Response response = underTest.patch(appUrl, accessToken, randomEndPoint, "{}");
302
303     assertThat(response.getContent()).contains(randomBody);
304   }
305
306   @Test
307   public void patch_returns_empty_response_if_code_is_204() throws IOException {
308     server.enqueue(new MockResponse().setResponseCode(204));
309
310     Response response = underTest.patch(appUrl, accessToken, randomEndPoint, "{}");
311
312     assertThat(response.getContent()).isEmpty();
313   }
314
315   @Test
316   public void delete_returns_empty_response_if_code_is_204() throws IOException {
317     server.enqueue(new MockResponse().setResponseCode(204));
318
319     Response response = underTest.delete(appUrl, accessToken, randomEndPoint);
320
321     assertThat(response.getContent()).isEmpty();
322   }
323
324   @DataProvider
325   public static Object[][] httpCodesBut204() {
326     return new Object[][] {
327       {200},
328       {201},
329       {202},
330       {203},
331       {400},
332       {401},
333       {403},
334       {404},
335       {500}
336     };
337   }
338
339   @Test
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));
343
344     Response response = underTest.delete(appUrl, accessToken, randomEndPoint);
345
346     assertThat(response.getContent()).hasValue(randomBody);
347   }
348
349   @DataProvider
350   public static Object[][] httpCodesBut200And204() {
351     return new Object[][] {
352       {201},
353       {202},
354       {203},
355       {400},
356       {401},
357       {403},
358       {404},
359       {500}
360     };
361   }
362
363   @Test
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));
367
368     Response response = underTest.patch(appUrl, accessToken, randomEndPoint, "{}");
369
370     assertThat(response.getContent()).contains(randomBody);
371   }
372
373 }