]> source.dussan.org Git - sonarqube.git/blob
bc11a17e53153a352dfa4841a39e13a94424d4aa
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2023 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.google.gson.Gson;
23 import com.google.gson.reflect.TypeToken;
24 import java.io.IOException;
25 import java.lang.reflect.Type;
26 import java.util.List;
27 import java.util.Optional;
28 import org.junit.Rule;
29 import org.junit.Test;
30 import org.junit.runner.RunWith;
31 import org.kohsuke.github.GHRateLimit;
32 import org.mockito.ArgumentCaptor;
33 import org.mockito.InjectMocks;
34 import org.mockito.Mock;
35 import org.mockito.junit.MockitoJUnitRunner;
36 import org.slf4j.event.Level;
37 import org.sonar.alm.client.github.security.AccessToken;
38 import org.sonar.api.testfixtures.log.LogTester;
39
40 import static org.assertj.core.api.Assertions.assertThat;
41 import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
42 import static org.assertj.core.api.Assertions.assertThatNoException;
43 import static org.mockito.ArgumentMatchers.any;
44 import static org.mockito.ArgumentMatchers.anyLong;
45 import static org.mockito.ArgumentMatchers.eq;
46 import static org.mockito.Mockito.doThrow;
47 import static org.mockito.Mockito.mock;
48 import static org.mockito.Mockito.verify;
49 import static org.mockito.Mockito.when;
50 import static org.sonar.alm.client.github.GithubApplicationHttpClient.GetResponse;
51
52 @RunWith(MockitoJUnitRunner.class)
53 public class GithubPaginatedHttpClientImplTest {
54
55   private static final String APP_URL = "https://github.com/";
56
57   private static final String ENDPOINT = "/test-endpoint";
58
59   private static final Type STRING_LIST_TYPE = TypeToken.getParameterized(List.class, String.class).getType();
60
61   private Gson gson = new Gson();
62
63   @Rule
64   public LogTester logTester = new LogTester();
65
66   @Mock
67   private AccessToken accessToken;
68
69   @Mock
70   RatioBasedRateLimitChecker rateLimitChecker;
71
72   @Mock
73   GithubApplicationHttpClient appHttpClient;
74
75   @InjectMocks
76   private GithubPaginatedHttpClientImpl underTest;
77
78   @Test
79   public void get_whenNoPagination_ReturnsCorrectResponse() throws IOException {
80
81     GetResponse response = mockResponseWithoutPagination("[\"result1\", \"result2\"]");
82     when(appHttpClient.get(APP_URL, accessToken, ENDPOINT + "?per_page=100")).thenReturn(response);
83
84     List<String> results = underTest.get(APP_URL, accessToken, ENDPOINT, result -> gson.fromJson(result, STRING_LIST_TYPE));
85
86     assertThat(results)
87       .containsExactly("result1", "result2");
88   }
89
90   @Test
91   public void get_whenEndpointAlreadyContainsPathParameter_shouldAddANewParameter() throws IOException {
92     ArgumentCaptor<String> urlCaptor = ArgumentCaptor.forClass(String.class);
93
94     GetResponse response = mockResponseWithoutPagination("[\"result1\", \"result2\"]");
95     when(appHttpClient.get(eq(APP_URL), eq(accessToken), urlCaptor.capture())).thenReturn(response);
96
97     underTest.get(APP_URL, accessToken, ENDPOINT + "?alreadyExistingArg=2", result -> gson.fromJson(result, STRING_LIST_TYPE));
98
99     assertThat(urlCaptor.getValue()).isEqualTo(ENDPOINT + "?alreadyExistingArg=2&per_page=100");
100   }
101
102   private static GetResponse mockResponseWithoutPagination(String content) {
103     GetResponse response = mock(GetResponse.class);
104     when(response.getCode()).thenReturn(200);
105     when(response.getContent()).thenReturn(Optional.of(content));
106     return response;
107   }
108
109   @Test
110   public void get_whenPaginationAndRateLimiting_returnsResponseFromAllPages() throws IOException, InterruptedException {
111     GetResponse response1 = mockResponseWithPaginationAndRateLimit("[\"result1\", \"result2\"]", "/next-endpoint");
112     GetResponse response2 = mockResponseWithoutPagination("[\"result3\"]");
113     when(appHttpClient.get(APP_URL, accessToken, ENDPOINT + "?per_page=100")).thenReturn(response1);
114     when(appHttpClient.get(APP_URL, accessToken, "/next-endpoint")).thenReturn(response2);
115
116     List<String> results = underTest.get(APP_URL, accessToken, ENDPOINT, result -> gson.fromJson(result, STRING_LIST_TYPE));
117
118     assertThat(results)
119       .containsExactly("result1", "result2", "result3");
120
121     ArgumentCaptor<GHRateLimit.Record> rateLimitRecordCaptor = ArgumentCaptor.forClass(GHRateLimit.Record.class);
122     verify(rateLimitChecker).checkRateLimit(rateLimitRecordCaptor.capture(), eq(0L));
123     GHRateLimit.Record rateLimitRecord = rateLimitRecordCaptor.getValue();
124     assertThat(rateLimitRecord.getLimit()).isEqualTo(10);
125     assertThat(rateLimitRecord.getRemaining()).isEqualTo(1);
126     assertThat(rateLimitRecord.getResetEpochSeconds()).isZero();
127   }
128
129   private static GetResponse mockResponseWithPaginationAndRateLimit(String content, String nextEndpoint) {
130     GetResponse response = mockResponseWithoutPagination(content);
131     when(response.getCode()).thenReturn(200);
132     when(response.getNextEndPoint()).thenReturn(Optional.of(nextEndpoint));
133     when(response.getRateLimit()).thenReturn(new GithubApplicationHttpClient.RateLimit(1, 10, 0L));
134     return response;
135   }
136
137   @Test
138   public void get_whenGitHubReturnsNonSuccessCode_shouldThrow() throws IOException {
139     GetResponse response1 = mockResponseWithPaginationAndRateLimit("[\"result1\", \"result2\"]", "/next-endpoint");
140     GetResponse response2 = mockFailedResponse("failed");
141     when(appHttpClient.get(APP_URL, accessToken, ENDPOINT + "?per_page=100")).thenReturn(response1);
142     when(appHttpClient.get(APP_URL, accessToken, "/next-endpoint")).thenReturn(response2);
143
144     assertThatIllegalStateException()
145       .isThrownBy(() -> underTest.get(APP_URL, accessToken, ENDPOINT, result -> gson.fromJson(result, STRING_LIST_TYPE)))
146       .withMessage("Error while executing a call to GitHub. Return code 400. Error message: failed.");
147   }
148
149   private static GetResponse mockFailedResponse(String content) {
150     GetResponse response = mock(GetResponse.class);
151     when(response.getCode()).thenReturn(400);
152     when(response.getContent()).thenReturn(Optional.of(content));
153     return response;
154   }
155
156   @Test
157   public void getRepositoryTeams_whenRateLimitCheckerThrowsInterruptedException_shouldSucceed() throws IOException, InterruptedException {
158     GetResponse response1 = mockResponseWithPaginationAndRateLimit("[\"result1\", \"result2\"]", "/next-endpoint");
159     GetResponse response2 = mockResponseWithoutPagination("[\"result3\"]");
160     when(appHttpClient.get(APP_URL, accessToken, ENDPOINT + "?per_page=100")).thenReturn(response1);
161     when(appHttpClient.get(APP_URL, accessToken, "/next-endpoint")).thenReturn(response2);
162     doThrow(new InterruptedException("interrupted")).when(rateLimitChecker).checkRateLimit(any(GHRateLimit.Record.class), anyLong());
163
164     assertThatNoException()
165       .isThrownBy(() -> underTest.get(APP_URL, accessToken, ENDPOINT, result -> gson.fromJson(result, STRING_LIST_TYPE)));
166
167     assertThat(logTester.logs()).hasSize(1);
168     assertThat(logTester.logs(Level.WARN))
169       .containsExactly("Thread interrupted: interrupted");
170   }
171 }