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.util.Optional;
27 import javax.annotation.Nullable;
28 import org.junit.Before;
29 import org.junit.ClassRule;
30 import org.junit.Test;
31 import org.junit.runner.RunWith;
32 import org.sonar.alm.client.github.security.AccessToken;
33 import org.sonar.alm.client.github.security.UserAccessToken;
34 import org.sonar.api.utils.log.LogTester;
35 import org.sonar.api.utils.log.LoggerLevel;
37 import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
38 import static org.assertj.core.api.Assertions.assertThat;
39 import static org.assertj.core.api.Assertions.assertThatThrownBy;
40 import static org.assertj.core.groups.Tuple.tuple;
41 import static org.mockito.ArgumentMatchers.any;
42 import static org.mockito.Mockito.mock;
43 import static org.mockito.Mockito.verify;
44 import static org.mockito.Mockito.when;
46 @RunWith(DataProviderRunner.class)
47 public class GithubApplicationClientImplTest {
50 public static LogTester logTester = new LogTester().setLevel(LoggerLevel.WARN);
52 private GithubApplicationHttpClientImpl httpClient = mock(GithubApplicationHttpClientImpl.class);
53 private GithubApplicationClient underTest;
55 private String appUrl = "Any URL";
59 underTest = new GithubApplicationClientImpl(httpClient);
64 @UseDataProvider("githubServers")
65 public void createUserAccessToken_returns_empty_if_access_token_cant_be_created(String apiUrl, String appUrl) throws IOException {
66 when(httpClient.post(appUrl, null, "/login/oauth/access_token?client_id=clientId&client_secret=clientSecret&code=code"))
67 .thenReturn(new Response(400, null));
69 assertThatThrownBy(() -> underTest.createUserAccessToken(appUrl, "clientId", "clientSecret", "code"))
70 .isInstanceOf(IllegalStateException.class);
71 verify(httpClient).post(appUrl, null, "/login/oauth/access_token?client_id=clientId&client_secret=clientSecret&code=code");
75 @UseDataProvider("githubServers")
76 public void createUserAccessToken_fail_if_access_token_request_fails(String apiUrl, String appUrl) throws IOException {
77 when(httpClient.post(appUrl, null, "/login/oauth/access_token?client_id=clientId&client_secret=clientSecret&code=code"))
78 .thenThrow(new IOException("OOPS"));
80 assertThatThrownBy(() -> underTest.createUserAccessToken(apiUrl, "clientId", "clientSecret", "code"))
81 .isInstanceOf(IllegalStateException.class)
82 .hasMessage("Failed to create GitHub's user access token");
84 verify(httpClient).post(appUrl, null, "/login/oauth/access_token?client_id=clientId&client_secret=clientSecret&code=code");
88 @UseDataProvider("githubServers")
89 public void createUserAccessToken_throws_illegal_argument_exception_if_access_token_code_is_expired(String apiUrl, String appUrl) throws IOException {
90 when(httpClient.post(appUrl, null, "/login/oauth/access_token?client_id=clientId&client_secret=clientSecret&code=code"))
91 .thenReturn(new OkGetResponse("error_code=100&error=expired_or_invalid"));
93 assertThatThrownBy(() -> underTest.createUserAccessToken(apiUrl, "clientId", "clientSecret", "code"))
94 .isInstanceOf(IllegalArgumentException.class);
96 verify(httpClient).post(appUrl, null, "/login/oauth/access_token?client_id=clientId&client_secret=clientSecret&code=code");
100 @UseDataProvider("githubServers")
101 public void createUserAccessToken_from_authorization_code_returns_access_token(String apiUrl, String appUrl) throws IOException {
102 String token = randomAlphanumeric(10);
103 when(httpClient.post(appUrl, null, "/login/oauth/access_token?client_id=clientId&client_secret=clientSecret&code=code"))
104 .thenReturn(new OkGetResponse("access_token=" + token + "&status="));
106 UserAccessToken userAccessToken = underTest.createUserAccessToken(apiUrl, "clientId", "clientSecret", "code");
108 assertThat(userAccessToken)
109 .extracting(UserAccessToken::getValue, UserAccessToken::getAuthorizationHeaderPrefix)
110 .containsOnly(token, "token");
111 verify(httpClient).post(appUrl, null, "/login/oauth/access_token?client_id=clientId&client_secret=clientSecret&code=code");
115 public static Object[][] githubServers() {
116 return new Object[][] {
117 {"https://github.sonarsource.com/api/v3", "https://github.sonarsource.com"},
118 {"https://api.github.com", "https://github.com"},
119 {"https://github.sonarsource.com/api/v3/", "https://github.sonarsource.com"},
120 {"https://api.github.com/", "https://github.com"},
125 public void listOrganizations_fail_on_failure() throws IOException {
126 String appUrl = "https://github.sonarsource.com";
127 AccessToken accessToken = new UserAccessToken(randomAlphanumeric(10));
129 when(httpClient.get(appUrl, accessToken, String.format("/user/installations?page=%s&per_page=%s", 1, 100)))
130 .thenThrow(new IOException("OOPS"));
132 assertThatThrownBy(() -> underTest.listOrganizations(appUrl, accessToken, 1, 100))
133 .isInstanceOf(IllegalStateException.class)
134 .hasMessage("Failed to list all organizations accessible by user access token on %s", appUrl);
138 public void listOrganizations_fail_if_pageIndex_out_of_bounds() {
139 UserAccessToken token = new UserAccessToken("token");
140 assertThatThrownBy(() -> underTest.listOrganizations(appUrl, token, 0, 100))
141 .isInstanceOf(IllegalArgumentException.class)
142 .hasMessage("'page' must be larger than 0.");
146 public void listOrganizations_fail_if_pageSize_out_of_bounds() {
147 UserAccessToken token = new UserAccessToken("token");
148 assertThatThrownBy(() -> underTest.listOrganizations(appUrl, token, 1, 0))
149 .isInstanceOf(IllegalArgumentException.class)
150 .hasMessage("'pageSize' must be a value larger than 0 and smaller or equal to 100.");
151 assertThatThrownBy(() -> underTest.listOrganizations("", token, 1, 101))
152 .isInstanceOf(IllegalArgumentException.class)
153 .hasMessage("'pageSize' must be a value larger than 0 and smaller or equal to 100.");
157 public void listOrganizations_returns_no_installations() throws IOException {
158 String appUrl = "https://github.sonarsource.com";
159 AccessToken accessToken = new UserAccessToken(randomAlphanumeric(10));
160 String responseJson = "{\n"
161 + " \"total_count\": 0\n"
164 when(httpClient.get(appUrl, accessToken, String.format("/user/installations?page=%s&per_page=%s", 1, 100)))
165 .thenReturn(new OkGetResponse(responseJson));
167 GithubApplicationClient.Organizations organizations = underTest.listOrganizations(appUrl, accessToken, 1, 100);
169 assertThat(organizations.getTotal()).isZero();
170 assertThat(organizations.getOrganizations()).isNull();
174 public void listOrganizations_returns_pages_results() throws IOException {
175 String appUrl = "https://github.sonarsource.com";
176 AccessToken accessToken = new UserAccessToken(randomAlphanumeric(10));
177 String responseJson = "{\n"
178 + " \"total_count\": 2,\n"
179 + " \"installations\": [\n"
182 + " \"account\": {\n"
183 + " \"login\": \"github\",\n"
185 + " \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjE=\",\n"
186 + " \"url\": \"https://github.sonarsource.com/api/v3/orgs/github\",\n"
187 + " \"repos_url\": \"https://github.sonarsource.com/api/v3/orgs/github/repos\",\n"
188 + " \"events_url\": \"https://github.sonarsource.com/api/v3/orgs/github/events\",\n"
189 + " \"hooks_url\": \"https://github.sonarsource.com/api/v3/orgs/github/hooks\",\n"
190 + " \"issues_url\": \"https://github.sonarsource.com/api/v3/orgs/github/issues\",\n"
191 + " \"members_url\": \"https://github.sonarsource.com/api/v3/orgs/github/members{/member}\",\n"
192 + " \"public_members_url\": \"https://github.sonarsource.com/api/v3/orgs/github/public_members{/member}\",\n"
193 + " \"avatar_url\": \"https://github.com/images/error/octocat_happy.gif\",\n"
194 + " \"description\": \"A great organization\"\n"
196 + " \"access_tokens_url\": \"https://github.sonarsource.com/api/v3/app/installations/1/access_tokens\",\n"
197 + " \"repositories_url\": \"https://github.sonarsource.com/api/v3/installation/repositories\",\n"
198 + " \"html_url\": \"https://github.com/organizations/github/settings/installations/1\",\n"
199 + " \"app_id\": 1,\n"
200 + " \"target_id\": 1,\n"
201 + " \"target_type\": \"Organization\",\n"
202 + " \"permissions\": {\n"
203 + " \"checks\": \"write\",\n"
204 + " \"metadata\": \"read\",\n"
205 + " \"contents\": \"read\"\n"
209 + " \"pull_request\"\n"
211 + " \"single_file_name\": \"config.yml\"\n"
215 + " \"account\": {\n"
216 + " \"login\": \"octocat\",\n"
218 + " \"node_id\": \"MDQ6VXNlcjE=\",\n"
219 + " \"avatar_url\": \"https://github.com/images/error/octocat_happy.gif\",\n"
220 + " \"gravatar_id\": \"\",\n"
221 + " \"url\": \"https://github.sonarsource.com/api/v3/users/octocat\",\n"
222 + " \"html_url\": \"https://github.com/octocat\",\n"
223 + " \"followers_url\": \"https://github.sonarsource.com/api/v3/users/octocat/followers\",\n"
224 + " \"following_url\": \"https://github.sonarsource.com/api/v3/users/octocat/following{/other_user}\",\n"
225 + " \"gists_url\": \"https://github.sonarsource.com/api/v3/users/octocat/gists{/gist_id}\",\n"
226 + " \"starred_url\": \"https://github.sonarsource.com/api/v3/users/octocat/starred{/owner}{/repo}\",\n"
227 + " \"subscriptions_url\": \"https://github.sonarsource.com/api/v3/users/octocat/subscriptions\",\n"
228 + " \"organizations_url\": \"https://github.sonarsource.com/api/v3/users/octocat/orgs\",\n"
229 + " \"repos_url\": \"https://github.sonarsource.com/api/v3/users/octocat/repos\",\n"
230 + " \"events_url\": \"https://github.sonarsource.com/api/v3/users/octocat/events{/privacy}\",\n"
231 + " \"received_events_url\": \"https://github.sonarsource.com/api/v3/users/octocat/received_events\",\n"
232 + " \"type\": \"User\",\n"
233 + " \"site_admin\": false\n"
235 + " \"access_tokens_url\": \"https://github.sonarsource.com/api/v3/app/installations/1/access_tokens\",\n"
236 + " \"repositories_url\": \"https://github.sonarsource.com/api/v3/installation/repositories\",\n"
237 + " \"html_url\": \"https://github.com/organizations/github/settings/installations/1\",\n"
238 + " \"app_id\": 1,\n"
239 + " \"target_id\": 1,\n"
240 + " \"target_type\": \"Organization\",\n"
241 + " \"permissions\": {\n"
242 + " \"checks\": \"write\",\n"
243 + " \"metadata\": \"read\",\n"
244 + " \"contents\": \"read\"\n"
248 + " \"pull_request\"\n"
250 + " \"single_file_name\": \"config.yml\"\n"
255 when(httpClient.get(appUrl, accessToken, String.format("/user/installations?page=%s&per_page=%s", 1, 100)))
256 .thenReturn(new OkGetResponse(responseJson));
258 GithubApplicationClient.Organizations organizations = underTest.listOrganizations(appUrl, accessToken, 1, 100);
260 assertThat(organizations.getTotal()).isEqualTo(2);
261 assertThat(organizations.getOrganizations()).extracting(GithubApplicationClient.Organization::getLogin).containsOnly("github", "octocat");
265 public void listRepositories_fail_on_failure() throws IOException {
266 String appUrl = "https://github.sonarsource.com";
267 AccessToken accessToken = new UserAccessToken(randomAlphanumeric(10));
269 when(httpClient.get(appUrl, accessToken, String.format("/search/repositories?q=%s&page=%s&per_page=%s", "org:test", 1, 100)))
270 .thenThrow(new IOException("OOPS"));
272 assertThatThrownBy(() -> underTest.listRepositories(appUrl, accessToken, "test", null, 1, 100))
273 .isInstanceOf(IllegalStateException.class)
274 .hasMessage("Failed to list all repositories of 'test' accessible by user access token on 'https://github.sonarsource.com' using query 'org:test'");
278 public void listRepositories_fail_if_pageIndex_out_of_bounds() {
279 UserAccessToken token = new UserAccessToken("token");
280 assertThatThrownBy(() -> underTest.listRepositories(appUrl, token, "test", null, 0, 100))
281 .isInstanceOf(IllegalArgumentException.class)
282 .hasMessage("'page' must be larger than 0.");
286 public void listRepositories_fail_if_pageSize_out_of_bounds() {
287 UserAccessToken token = new UserAccessToken("token");
288 assertThatThrownBy(() -> underTest.listRepositories(appUrl, token, "test", null, 1, 0))
289 .isInstanceOf(IllegalArgumentException.class)
290 .hasMessage("'pageSize' must be a value larger than 0 and smaller or equal to 100.");
291 assertThatThrownBy(() -> underTest.listRepositories("", token, "test", null, 1, 101))
292 .isInstanceOf(IllegalArgumentException.class)
293 .hasMessage("'pageSize' must be a value larger than 0 and smaller or equal to 100.");
297 public void listRepositories_returns_empty_results() throws IOException {
298 String appUrl = "https://github.sonarsource.com";
299 AccessToken accessToken = new UserAccessToken(randomAlphanumeric(10));
300 String responseJson = "{\n"
301 + " \"total_count\": 0\n"
304 when(httpClient.get(appUrl, accessToken, String.format("/search/repositories?q=%s&page=%s&per_page=%s", "org:github", 1, 100)))
305 .thenReturn(new OkGetResponse(responseJson));
307 GithubApplicationClient.Repositories repositories = underTest.listRepositories(appUrl, accessToken, "github", null, 1, 100);
309 assertThat(repositories.getTotal()).isZero();
310 assertThat(repositories.getRepositories()).isNull();
314 public void listRepositories_returns_pages_results() throws IOException {
315 String appUrl = "https://github.sonarsource.com";
316 AccessToken accessToken = new UserAccessToken(randomAlphanumeric(10));
317 String responseJson = "{\n"
318 + " \"total_count\": 2,\n"
319 + " \"incomplete_results\": false,\n"
322 + " \"id\": 3081286,\n"
323 + " \"node_id\": \"MDEwOlJlcG9zaXRvcnkzMDgxMjg2\",\n"
324 + " \"name\": \"HelloWorld\",\n"
325 + " \"full_name\": \"github/HelloWorld\",\n"
327 + " \"login\": \"github\",\n"
328 + " \"id\": 872147,\n"
329 + " \"node_id\": \"MDQ6VXNlcjg3MjE0Nw==\",\n"
330 + " \"avatar_url\": \"https://github.sonarsource.com/images/error/octocat_happy.gif\",\n"
331 + " \"gravatar_id\": \"\",\n"
332 + " \"url\": \"https://github.sonarsource.com/api/v3/users/github\",\n"
333 + " \"received_events_url\": \"https://github.sonarsource.com/api/v3/users/github/received_events\",\n"
334 + " \"type\": \"User\"\n"
336 + " \"private\": false,\n"
337 + " \"html_url\": \"https://github.com/github/HelloWorld\",\n"
338 + " \"description\": \"A C implementation of HelloWorld\",\n"
339 + " \"fork\": false,\n"
340 + " \"url\": \"https://github.sonarsource.com/api/v3/repos/github/HelloWorld\",\n"
341 + " \"created_at\": \"2012-01-01T00:31:50Z\",\n"
342 + " \"updated_at\": \"2013-01-05T17:58:47Z\",\n"
343 + " \"pushed_at\": \"2012-01-01T00:37:02Z\",\n"
344 + " \"homepage\": \"\",\n"
345 + " \"size\": 524,\n"
346 + " \"stargazers_count\": 1,\n"
347 + " \"watchers_count\": 1,\n"
348 + " \"language\": \"Assembly\",\n"
349 + " \"forks_count\": 0,\n"
350 + " \"open_issues_count\": 0,\n"
351 + " \"master_branch\": \"master\",\n"
352 + " \"default_branch\": \"master\",\n"
353 + " \"score\": 1.0\n"
356 + " \"id\": 3081286,\n"
357 + " \"node_id\": \"MDEwOlJlcG9zaXRvcnkzMDgxMjg2\",\n"
358 + " \"name\": \"HelloUniverse\",\n"
359 + " \"full_name\": \"github/HelloUniverse\",\n"
361 + " \"login\": \"github\",\n"
362 + " \"id\": 872147,\n"
363 + " \"node_id\": \"MDQ6VXNlcjg3MjE0Nw==\",\n"
364 + " \"avatar_url\": \"https://github.sonarsource.com/images/error/octocat_happy.gif\",\n"
365 + " \"gravatar_id\": \"\",\n"
366 + " \"url\": \"https://github.sonarsource.com/api/v3/users/github\",\n"
367 + " \"received_events_url\": \"https://github.sonarsource.com/api/v3/users/github/received_events\",\n"
368 + " \"type\": \"User\"\n"
370 + " \"private\": false,\n"
371 + " \"html_url\": \"https://github.com/github/HelloUniverse\",\n"
372 + " \"description\": \"A C implementation of HelloUniverse\",\n"
373 + " \"fork\": false,\n"
374 + " \"url\": \"https://github.sonarsource.com/api/v3/repos/github/HelloUniverse\",\n"
375 + " \"created_at\": \"2012-01-01T00:31:50Z\",\n"
376 + " \"updated_at\": \"2013-01-05T17:58:47Z\",\n"
377 + " \"pushed_at\": \"2012-01-01T00:37:02Z\",\n"
378 + " \"homepage\": \"\",\n"
379 + " \"size\": 524,\n"
380 + " \"stargazers_count\": 1,\n"
381 + " \"watchers_count\": 1,\n"
382 + " \"language\": \"Assembly\",\n"
383 + " \"forks_count\": 0,\n"
384 + " \"open_issues_count\": 0,\n"
385 + " \"master_branch\": \"master\",\n"
386 + " \"default_branch\": \"master\",\n"
387 + " \"score\": 1.0\n"
392 when(httpClient.get(appUrl, accessToken, String.format("/search/repositories?q=%s&page=%s&per_page=%s", "org:github", 1, 100)))
393 .thenReturn(new OkGetResponse(responseJson));
394 GithubApplicationClient.Repositories repositories = underTest.listRepositories(appUrl, accessToken, "github", null, 1, 100);
396 assertThat(repositories.getTotal()).isEqualTo(2);
397 assertThat(repositories.getRepositories())
398 .extracting(GithubApplicationClient.Repository::getName, GithubApplicationClient.Repository::getFullName)
399 .containsOnly(tuple("HelloWorld", "github/HelloWorld"), tuple("HelloUniverse", "github/HelloUniverse"));
403 public void listRepositories_returns_search_results() throws IOException {
404 String appUrl = "https://github.sonarsource.com";
405 AccessToken accessToken = new UserAccessToken(randomAlphanumeric(10));
406 String responseJson = "{\n"
407 + " \"total_count\": 2,\n"
408 + " \"incomplete_results\": false,\n"
411 + " \"id\": 3081286,\n"
412 + " \"node_id\": \"MDEwOlJlcG9zaXRvcnkzMDgxMjg2\",\n"
413 + " \"name\": \"HelloWorld\",\n"
414 + " \"full_name\": \"github/HelloWorld\",\n"
416 + " \"login\": \"github\",\n"
417 + " \"id\": 872147,\n"
418 + " \"node_id\": \"MDQ6VXNlcjg3MjE0Nw==\",\n"
419 + " \"avatar_url\": \"https://github.sonarsource.com/images/error/octocat_happy.gif\",\n"
420 + " \"gravatar_id\": \"\",\n"
421 + " \"url\": \"https://github.sonarsource.com/api/v3/users/github\",\n"
422 + " \"received_events_url\": \"https://github.sonarsource.com/api/v3/users/github/received_events\",\n"
423 + " \"type\": \"User\"\n"
425 + " \"private\": false,\n"
426 + " \"html_url\": \"https://github.com/github/HelloWorld\",\n"
427 + " \"description\": \"A C implementation of HelloWorld\",\n"
428 + " \"fork\": false,\n"
429 + " \"url\": \"https://github.sonarsource.com/api/v3/repos/github/HelloWorld\",\n"
430 + " \"created_at\": \"2012-01-01T00:31:50Z\",\n"
431 + " \"updated_at\": \"2013-01-05T17:58:47Z\",\n"
432 + " \"pushed_at\": \"2012-01-01T00:37:02Z\",\n"
433 + " \"homepage\": \"\",\n"
434 + " \"size\": 524,\n"
435 + " \"stargazers_count\": 1,\n"
436 + " \"watchers_count\": 1,\n"
437 + " \"language\": \"Assembly\",\n"
438 + " \"forks_count\": 0,\n"
439 + " \"open_issues_count\": 0,\n"
440 + " \"master_branch\": \"master\",\n"
441 + " \"default_branch\": \"master\",\n"
442 + " \"score\": 1.0\n"
447 when(httpClient.get(appUrl, accessToken, String.format("/search/repositories?q=%s&page=%s&per_page=%s", "world+org:github", 1, 100)))
448 .thenReturn(new GithubApplicationHttpClient.GetResponse() {
450 public Optional<String> getNextEndPoint() {
451 return Optional.empty();
455 public int getCode() {
460 public Optional<String> getContent() {
461 return Optional.of(responseJson);
465 GithubApplicationClient.Repositories repositories = underTest.listRepositories(appUrl, accessToken, "github", "world", 1, 100);
467 assertThat(repositories.getTotal()).isEqualTo(2);
468 assertThat(repositories.getRepositories())
469 .extracting(GithubApplicationClient.Repository::getName, GithubApplicationClient.Repository::getFullName)
470 .containsOnly(tuple("HelloWorld", "github/HelloWorld"));
474 public void getRepository_returns_empty_when_repository_doesnt_exist() throws IOException {
475 when(httpClient.get(any(), any(), any()))
476 .thenReturn(new Response(404, null));
478 Optional<GithubApplicationClient.Repository> repository = underTest.getRepository(appUrl, new UserAccessToken("temp"), "octocat", "octocat/Hello-World");
480 assertThat(repository).isEmpty();
484 public void getRepository_fails_on_failure() throws IOException {
485 String repositoryKey = "octocat/Hello-World";
486 String organization = "octocat";
488 when(httpClient.get(any(), any(), any()))
489 .thenThrow(new IOException("OOPS"));
491 UserAccessToken token = new UserAccessToken("temp");
492 assertThatThrownBy(() -> underTest.getRepository(appUrl, token, organization, repositoryKey))
493 .isInstanceOf(IllegalStateException.class)
494 .hasMessage("Failed to get repository '%s' of '%s' accessible by user access token on '%s'", repositoryKey, organization, appUrl);
498 public void getRepository_returns_repository() throws IOException {
499 String appUrl = "https://github.sonarsource.com";
500 AccessToken accessToken = new UserAccessToken(randomAlphanumeric(10));
501 String responseJson = "{\n"
502 + " \"id\": 1296269,\n"
503 + " \"node_id\": \"MDEwOlJlcG9zaXRvcnkxMjk2MjY5\",\n"
504 + " \"name\": \"Hello-World\",\n"
505 + " \"full_name\": \"octocat/Hello-World\",\n"
507 + " \"login\": \"octocat\",\n"
509 + " \"node_id\": \"MDQ6VXNlcjE=\",\n"
510 + " \"avatar_url\": \"https://github.sonarsource.com/images/error/octocat_happy.gif\",\n"
511 + " \"gravatar_id\": \"\",\n"
512 + " \"url\": \"https://github.sonarsource.com/api/v3/users/octocat\",\n"
513 + " \"html_url\": \"https://github.com/octocat\",\n"
514 + " \"followers_url\": \"https://github.sonarsource.com/api/v3/users/octocat/followers\",\n"
515 + " \"following_url\": \"https://github.sonarsource.com/api/v3/users/octocat/following{/other_user}\",\n"
516 + " \"gists_url\": \"https://github.sonarsource.com/api/v3/users/octocat/gists{/gist_id}\",\n"
517 + " \"starred_url\": \"https://github.sonarsource.com/api/v3/users/octocat/starred{/owner}{/repo}\",\n"
518 + " \"subscriptions_url\": \"https://github.sonarsource.com/api/v3/users/octocat/subscriptions\",\n"
519 + " \"organizations_url\": \"https://github.sonarsource.com/api/v3/users/octocat/orgs\",\n"
520 + " \"repos_url\": \"https://github.sonarsource.com/api/v3/users/octocat/repos\",\n"
521 + " \"events_url\": \"https://github.sonarsource.com/api/v3/users/octocat/events{/privacy}\",\n"
522 + " \"received_events_url\": \"https://github.sonarsource.com/api/v3/users/octocat/received_events\",\n"
523 + " \"type\": \"User\",\n"
524 + " \"site_admin\": false\n"
526 + " \"private\": false,\n"
527 + " \"html_url\": \"https://github.com/octocat/Hello-World\",\n"
528 + " \"description\": \"This your first repo!\",\n"
529 + " \"fork\": false,\n"
530 + " \"url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World\",\n"
531 + " \"archive_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/{archive_format}{/ref}\",\n"
532 + " \"assignees_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/assignees{/user}\",\n"
533 + " \"blobs_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/git/blobs{/sha}\",\n"
534 + " \"branches_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/branches{/branch}\",\n"
535 + " \"collaborators_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/collaborators{/collaborator}\",\n"
536 + " \"comments_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/comments{/number}\",\n"
537 + " \"commits_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/commits{/sha}\",\n"
538 + " \"compare_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/compare/{base}...{head}\",\n"
539 + " \"contents_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/contents/{+path}\",\n"
540 + " \"contributors_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/contributors\",\n"
541 + " \"deployments_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/deployments\",\n"
542 + " \"downloads_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/downloads\",\n"
543 + " \"events_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/events\",\n"
544 + " \"forks_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/forks\",\n"
545 + " \"git_commits_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/git/commits{/sha}\",\n"
546 + " \"git_refs_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/git/refs{/sha}\",\n"
547 + " \"git_tags_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/git/tags{/sha}\",\n"
548 + " \"git_url\": \"git:github.com/octocat/Hello-World.git\",\n"
549 + " \"issue_comment_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/issues/comments{/number}\",\n"
550 + " \"issue_events_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/issues/events{/number}\",\n"
551 + " \"issues_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/issues{/number}\",\n"
552 + " \"keys_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/keys{/key_id}\",\n"
553 + " \"labels_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/labels{/name}\",\n"
554 + " \"languages_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/languages\",\n"
555 + " \"merges_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/merges\",\n"
556 + " \"milestones_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/milestones{/number}\",\n"
557 + " \"notifications_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/notifications{?since,all,participating}\",\n"
558 + " \"pulls_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/pulls{/number}\",\n"
559 + " \"releases_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/releases{/id}\",\n"
560 + " \"ssh_url\": \"git@github.com:octocat/Hello-World.git\",\n"
561 + " \"stargazers_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/stargazers\",\n"
562 + " \"statuses_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/statuses/{sha}\",\n"
563 + " \"subscribers_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/subscribers\",\n"
564 + " \"subscription_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/subscription\",\n"
565 + " \"tags_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/tags\",\n"
566 + " \"teams_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/teams\",\n"
567 + " \"trees_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/git/trees{/sha}\",\n"
568 + " \"clone_url\": \"https://github.com/octocat/Hello-World.git\",\n"
569 + " \"mirror_url\": \"git:git.example.com/octocat/Hello-World\",\n"
570 + " \"hooks_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/hooks\",\n"
571 + " \"svn_url\": \"https://svn.github.com/octocat/Hello-World\",\n"
572 + " \"homepage\": \"https://github.com\",\n"
573 + " \"language\": null,\n"
574 + " \"forks_count\": 9,\n"
575 + " \"stargazers_count\": 80,\n"
576 + " \"watchers_count\": 80,\n"
577 + " \"size\": 108,\n"
578 + " \"default_branch\": \"master\",\n"
579 + " \"open_issues_count\": 0,\n"
580 + " \"is_template\": true,\n"
587 + " \"has_issues\": true,\n"
588 + " \"has_projects\": true,\n"
589 + " \"has_wiki\": true,\n"
590 + " \"has_pages\": false,\n"
591 + " \"has_downloads\": true,\n"
592 + " \"archived\": false,\n"
593 + " \"disabled\": false,\n"
594 + " \"visibility\": \"public\",\n"
595 + " \"pushed_at\": \"2011-01-26T19:06:43Z\",\n"
596 + " \"created_at\": \"2011-01-26T19:01:12Z\",\n"
597 + " \"updated_at\": \"2011-01-26T19:14:43Z\",\n"
598 + " \"permissions\": {\n"
599 + " \"admin\": false,\n"
600 + " \"push\": false,\n"
601 + " \"pull\": true\n"
603 + " \"allow_rebase_merge\": true,\n"
604 + " \"template_repository\": null,\n"
605 + " \"allow_squash_merge\": true,\n"
606 + " \"allow_merge_commit\": true,\n"
607 + " \"subscribers_count\": 42,\n"
608 + " \"network_count\": 0,\n"
609 + " \"anonymous_access_enabled\": false,\n"
610 + " \"license\": {\n"
611 + " \"key\": \"mit\",\n"
612 + " \"name\": \"MIT License\",\n"
613 + " \"spdx_id\": \"MIT\",\n"
614 + " \"url\": \"https://github.sonarsource.com/api/v3/licenses/mit\",\n"
615 + " \"node_id\": \"MDc6TGljZW5zZW1pdA==\"\n"
617 + " \"organization\": {\n"
618 + " \"login\": \"octocat\",\n"
620 + " \"node_id\": \"MDQ6VXNlcjE=\",\n"
621 + " \"avatar_url\": \"https://github.com/images/error/octocat_happy.gif\",\n"
622 + " \"gravatar_id\": \"\",\n"
623 + " \"url\": \"https://github.sonarsource.com/api/v3/users/octocat\",\n"
624 + " \"html_url\": \"https://github.com/octocat\",\n"
625 + " \"followers_url\": \"https://github.sonarsource.com/api/v3/users/octocat/followers\",\n"
626 + " \"following_url\": \"https://github.sonarsource.com/api/v3/users/octocat/following{/other_user}\",\n"
627 + " \"gists_url\": \"https://github.sonarsource.com/api/v3/users/octocat/gists{/gist_id}\",\n"
628 + " \"starred_url\": \"https://github.sonarsource.com/api/v3/users/octocat/starred{/owner}{/repo}\",\n"
629 + " \"subscriptions_url\": \"https://github.sonarsource.com/api/v3/users/octocat/subscriptions\",\n"
630 + " \"organizations_url\": \"https://github.sonarsource.com/api/v3/users/octocat/orgs\",\n"
631 + " \"repos_url\": \"https://github.sonarsource.com/api/v3/users/octocat/repos\",\n"
632 + " \"events_url\": \"https://github.sonarsource.com/api/v3/users/octocat/events{/privacy}\",\n"
633 + " \"received_events_url\": \"https://github.sonarsource.com/api/v3/users/octocat/received_events\",\n"
634 + " \"type\": \"Organization\",\n"
635 + " \"site_admin\": false\n"
639 when(httpClient.get(appUrl, accessToken, "/repos/octocat/Hello-World"))
640 .thenReturn(new GithubApplicationHttpClient.GetResponse() {
642 public Optional<String> getNextEndPoint() {
643 return Optional.empty();
647 public int getCode() {
652 public Optional<String> getContent() {
653 return Optional.of(responseJson);
657 Optional<GithubApplicationClient.Repository> repository = underTest.getRepository(appUrl, accessToken, "octocat", "octocat/Hello-World");
659 assertThat(repository)
662 .extracting(GithubApplicationClient.Repository::getId, GithubApplicationClient.Repository::getName, GithubApplicationClient.Repository::getFullName,
663 GithubApplicationClient.Repository::getUrl, GithubApplicationClient.Repository::isPrivate)
664 .containsOnly(1296269L, "Hello-World", "octocat/Hello-World", "https://github.sonarsource.com/api/v3/repos/octocat/Hello-World", false);
667 private static class OkGetResponse extends Response {
668 private OkGetResponse(String content) {
673 private static class Response implements GithubApplicationHttpClient.GetResponse {
674 private final int code;
675 private final String content;
676 private final String nextEndPoint;
678 private Response(int code, @Nullable String content) {
679 this(code, content, null);
682 private Response(int code, @Nullable String content, @Nullable String nextEndPoint) {
684 this.content = content;
685 this.nextEndPoint = nextEndPoint;
689 public int getCode() {
694 public Optional<String> getContent() {
695 return Optional.ofNullable(content);
699 public Optional<String> getNextEndPoint() {
700 return Optional.ofNullable(nextEndPoint);