diff options
author | Aurelien Poscia <aurelien.poscia@sonarsource.com> | 2023-11-30 09:58:20 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-12-22 20:03:01 +0000 |
commit | 68de595dc688e9fd09bd8925c94d0bba830c3869 (patch) | |
tree | 6ca9621267a13fcc2a752dc2a882b60057ee7761 /server/sonar-alm-client/src/test/java | |
parent | c0c9226eb421d0581b269c74a083aad00a7ad679 (diff) | |
download | sonarqube-68de595dc688e9fd09bd8925c94d0bba830c3869.tar.gz sonarqube-68de595dc688e9fd09bd8925c94d0bba830c3869.zip |
SONAR-21119 Provide method to get groups for GitLab & refactored GithubPaginatedHttpClient and GithubApplicationHttpClient to make them generic
Diffstat (limited to 'server/sonar-alm-client/src/test/java')
6 files changed, 181 insertions, 94 deletions
diff --git a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/GithubPaginatedHttpClientImplTest.java b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/GenericPaginatedHttpClientImplTest.java index 5df514c857e..f9589290d53 100644 --- a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/GithubPaginatedHttpClientImplTest.java +++ b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/GenericPaginatedHttpClientImplTest.java @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.alm.client.github; +package org.sonar.alm.client; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; @@ -45,10 +45,10 @@ import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.sonar.alm.client.github.ApplicationHttpClient.GetResponse; +import static org.sonar.alm.client.ApplicationHttpClient.GetResponse; @RunWith(MockitoJUnitRunner.class) -public class GithubPaginatedHttpClientImplTest { +public class GenericPaginatedHttpClientImplTest { private static final String APP_URL = "https://github.com/"; @@ -71,7 +71,13 @@ public class GithubPaginatedHttpClientImplTest { ApplicationHttpClient appHttpClient; @InjectMocks - private GithubPaginatedHttpClient underTest; + private TestPaginatedHttpClient underTest; + + private static class TestPaginatedHttpClient extends GenericPaginatedHttpClient { + protected TestPaginatedHttpClient(ApplicationHttpClient appHttpClient, RatioBasedRateLimitChecker rateLimitChecker) { + super(appHttpClient, rateLimitChecker); + } + } @Test public void get_whenNoPagination_ReturnsCorrectResponse() throws IOException { @@ -141,7 +147,8 @@ public class GithubPaginatedHttpClientImplTest { assertThatIllegalStateException() .isThrownBy(() -> underTest.get(APP_URL, accessToken, ENDPOINT, result -> gson.fromJson(result, STRING_LIST_TYPE))) - .withMessage("Error while executing a call to GitHub. Return code 400. Error message: failed."); + .withMessage("SonarQube was not able to retrieve resources from external system. Error while executing a paginated call to https://github.com/, endpoint:/next-endpoint. " + + "Error while executing a call to https://github.com/. Return code 400. Error message: failed."); } private static GetResponse mockFailedResponse(String content) { @@ -166,4 +173,21 @@ public class GithubPaginatedHttpClientImplTest { assertThat(logTester.logs(Level.WARN)) .containsExactly("Thread interrupted: interrupted"); } + + @Test + public void getRepositoryCollaborators_whenDevOpsPlatformCallThrowsIOException_shouldLogAndReThrow() throws IOException { + AccessToken accessToken = mock(); + when(appHttpClient.get(APP_URL, accessToken, "query?per_page=100")).thenThrow(new IOException("error")); + + assertThatIllegalStateException() + .isThrownBy(() -> underTest.get(APP_URL, accessToken, "query", mock())) + .isInstanceOf(IllegalStateException.class) + .withMessage("SonarQube was not able to retrieve resources from external system. Error while executing a paginated call to https://github.com/, " + + "endpoint:query?per_page=100. error"); + + assertThat(logTester.logs()).hasSize(1); + assertThat(logTester.logs(Level.WARN)) + .containsExactly("SonarQube was not able to retrieve resources from external system. " + + "Error while executing a paginated call to https://github.com/, endpoint:query?per_page=100."); + } } diff --git a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/RatioBasedRateLimitCheckerTest.java b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/RatioBasedRateLimitCheckerTest.java index d10633365c6..d6d2bfff6e5 100644 --- a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/RatioBasedRateLimitCheckerTest.java +++ b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/RatioBasedRateLimitCheckerTest.java @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.alm.client.github; +package org.sonar.alm.client; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; @@ -32,7 +32,7 @@ import static java.lang.String.format; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static org.sonar.alm.client.github.RatioBasedRateLimitChecker.RATE_RATIO_EXCEEDED_MESSAGE; +import static org.sonar.alm.client.RatioBasedRateLimitChecker.RATE_RATIO_EXCEEDED_MESSAGE; @RunWith(DataProviderRunner.class) public class RatioBasedRateLimitCheckerTest { diff --git a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/GenericApplicationHttpClientTest.java b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/GenericApplicationHttpClientTest.java index 53a6fcf08c9..dcd2fdab87c 100644 --- a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/GenericApplicationHttpClientTest.java +++ b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/GenericApplicationHttpClientTest.java @@ -36,9 +36,11 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.slf4j.event.Level; import org.sonar.alm.client.ConstantTimeoutConfiguration; +import org.sonar.alm.client.DevopsPlatformHeaders; +import org.sonar.alm.client.GenericApplicationHttpClient; import org.sonar.alm.client.TimeoutConfiguration; -import org.sonar.alm.client.github.ApplicationHttpClient.GetResponse; -import org.sonar.alm.client.github.ApplicationHttpClient.Response; +import org.sonar.alm.client.ApplicationHttpClient.GetResponse; +import org.sonar.alm.client.ApplicationHttpClient.Response; import org.sonar.alm.client.github.security.AccessToken; import org.sonar.alm.client.github.security.UserAccessToken; import org.sonar.api.testfixtures.log.LogTester; @@ -49,7 +51,7 @@ import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.Assert.fail; -import static org.sonar.alm.client.github.ApplicationHttpClient.RateLimit; +import static org.sonar.alm.client.ApplicationHttpClient.RateLimit; @RunWith(DataProviderRunner.class) public class GenericApplicationHttpClientTest { @@ -76,7 +78,7 @@ public class GenericApplicationHttpClientTest { logTester.clear(); } - private class TestApplicationHttpClient extends GenericApplicationHttpClient { + private static class TestApplicationHttpClient extends GenericApplicationHttpClient { public TestApplicationHttpClient(DevopsPlatformHeaders devopsPlatformHeaders, TimeoutConfiguration timeoutConfiguration) { super(devopsPlatformHeaders, timeoutConfiguration); } @@ -183,7 +185,7 @@ public class GenericApplicationHttpClientTest { public void get_returns_empty_endPoint_when_link_header_does_not_have_next_rel() throws IOException { server.enqueue(new MockResponse().setBody(randomBody) .setHeader("link", "<https://api.github.com/installation/repositories?per_page=5&page=4>; rel=\"prev\", " + - "<https://api.github.com/installation/repositories?per_page=5&page=1>; rel=\"first\"")); + "<https://api.github.com/installation/repositories?per_page=5&page=1>; rel=\"first\"")); GetResponse response = underTest.get(appUrl, accessToken, randomEndPoint); @@ -212,18 +214,30 @@ public class GenericApplicationHttpClientTest { assertThat(response.getNextEndPoint()).contains("https://api.github.com/installation/repositories?per_page=5&page=2"); } + @Test + public void get_returns_endPoint_when_link_header_is_from_gitlab() throws IOException { + String linkHeader = "<https://gitlab.com/api/v4/groups?all_available=false&order_by=name&owned=false&page=2&per_page=2&sort=asc&statistics=false&with_custom_attributes=false>; rel=\"next\", <https://gitlab.com/api/v4/groups?all_available=false&order_by=name&owned=false&page=1&per_page=2&sort=asc&statistics=false&with_custom_attributes=false>; rel=\"first\", <https://gitlab.com/api/v4/groups?all_available=false&order_by=name&owned=false&page=8&per_page=2&sort=asc&statistics=false&with_custom_attributes=false>; rel=\"last\""; + server.enqueue(new MockResponse().setBody(randomBody) + .setHeader("link", linkHeader)); + + GetResponse response = underTest.get(appUrl, accessToken, randomEndPoint); + + assertThat(response.getNextEndPoint()).contains("https://gitlab.com/api/v4/groups?all_available=false" + + "&order_by=name&owned=false&page=2&per_page=2&sort=asc&statistics=false&with_custom_attributes=false"); + } + @DataProvider public static Object[][] linkHeadersWithNextRel() { String expected = "https://api.github.com/installation/repositories?per_page=5&page=2"; return new Object[][] { {"<" + expected + ">; rel=\"next\""}, {"<" + expected + ">; rel=\"next\", " + - "<https://api.github.com/installation/repositories?per_page=5&page=1>; rel=\"first\""}, + "<https://api.github.com/installation/repositories?per_page=5&page=1>; rel=\"first\""}, {"<https://api.github.com/installation/repositories?per_page=5&page=1>; rel=\"first\", " + - "<" + expected + ">; rel=\"next\""}, + "<" + expected + ">; rel=\"next\""}, {"<https://api.github.com/installation/repositories?per_page=5&page=1>; rel=\"first\", " + - "<" + expected + ">; rel=\"next\", " + - "<https://api.github.com/installation/repositories?per_page=5&page=5>; rel=\"last\""}, + "<" + expected + ">; rel=\"next\", " + + "<https://api.github.com/installation/repositories?per_page=5&page=5>; rel=\"last\""}, }; } @@ -416,14 +430,19 @@ public class GenericApplicationHttpClientTest { @Test public void get_whenRateLimitHeadersArePresent_returnsRateLimit() throws Exception { - testRateLimitHeader(() -> underTest.get(appUrl, accessToken, randomEndPoint)); + testRateLimitHeader(() -> underTest.get(appUrl, accessToken, randomEndPoint), false); + } + + @Test + public void get_whenRateLimitHeadersArePresentAndUppercased_returnsRateLimit() throws Exception { + testRateLimitHeader(() -> underTest.get(appUrl, accessToken, randomEndPoint), true); } - private void testRateLimitHeader(Callable<Response> request ) throws Exception { + private void testRateLimitHeader(Callable<Response> request, boolean uppercasedHeaders) throws Exception { server.enqueue(new MockResponse().setBody(randomBody) - .setHeader("x-ratelimit-remaining", "1") - .setHeader("x-ratelimit-limit", "10") - .setHeader("x-ratelimit-reset", "1000")); + .setHeader(uppercasedHeaders ? "x-ratelimit-remaining" : "x-ratelimit-REMAINING", "1") + .setHeader(uppercasedHeaders ? "x-ratelimit-limit" : "X-RATELIMIT-LIMIT", "10") + .setHeader(uppercasedHeaders ? "x-ratelimit-reset" : "X-ratelimit-reset", "1000")); Response response = request.call(); @@ -438,7 +457,7 @@ public class GenericApplicationHttpClientTest { } - private void testMissingRateLimitHeader(Callable<Response> request ) throws Exception { + private void testMissingRateLimitHeader(Callable<Response> request) throws Exception { server.enqueue(new MockResponse().setBody(randomBody)); Response response = request.call(); @@ -448,7 +467,7 @@ public class GenericApplicationHttpClientTest { @Test public void delete_whenRateLimitHeadersArePresent_returnsRateLimit() throws Exception { - testRateLimitHeader(() -> underTest.delete(appUrl, accessToken, randomEndPoint)); + testRateLimitHeader(() -> underTest.delete(appUrl, accessToken, randomEndPoint), false); } @@ -460,7 +479,7 @@ public class GenericApplicationHttpClientTest { @Test public void patch_whenRateLimitHeadersArePresent_returnsRateLimit() throws Exception { - testRateLimitHeader(() -> underTest.patch(appUrl, accessToken, randomEndPoint, "body")); + testRateLimitHeader(() -> underTest.patch(appUrl, accessToken, randomEndPoint, "body"), false); } @Test @@ -470,7 +489,7 @@ public class GenericApplicationHttpClientTest { @Test public void post_whenRateLimitHeadersArePresent_returnsRateLimit() throws Exception { - testRateLimitHeader(() -> underTest.post(appUrl, accessToken, randomEndPoint)); + testRateLimitHeader(() -> underTest.post(appUrl, accessToken, randomEndPoint), false); } @Test diff --git a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/GithubApplicationClientImplTest.java b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/GithubApplicationClientImplTest.java index 6cf2f71dfc9..a7a3d2203d2 100644 --- a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/GithubApplicationClientImplTest.java +++ b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/GithubApplicationClientImplTest.java @@ -36,7 +36,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.slf4j.event.Level; -import org.sonar.alm.client.github.ApplicationHttpClient.RateLimit; +import org.sonar.alm.client.ApplicationHttpClient.RateLimit; import org.sonar.alm.client.github.api.GsonRepositoryCollaborator; import org.sonar.alm.client.github.api.GsonRepositoryTeam; import org.sonar.alm.client.github.config.GithubAppConfiguration; @@ -68,7 +68,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.sonar.alm.client.github.ApplicationHttpClient.GetResponse; +import static org.sonar.alm.client.ApplicationHttpClient.GetResponse; @RunWith(DataProviderRunner.class) public class GithubApplicationClientImplTest { @@ -114,12 +114,12 @@ public class GithubApplicationClientImplTest { @ClassRule public static LogTester logTester = new LogTester().setLevel(LoggerLevel.WARN); - private GenericApplicationHttpClient httpClient = mock(); + private GithubApplicationHttpClient githubApplicationHttpClient = mock(); private GithubAppSecurity appSecurity = mock(); private GithubAppConfiguration githubAppConfiguration = mock(); private GitHubSettings gitHubSettings = mock(); - private PaginatedHttpClient githubPaginatedHttpClient = mock(); + private GithubPaginatedHttpClient githubPaginatedHttpClient = mock(); private AppInstallationToken appInstallationToken = mock(); private GithubApplicationClient underTest; @@ -129,7 +129,7 @@ public class GithubApplicationClientImplTest { @Before public void setup() { when(githubAppConfiguration.getApiEndpoint()).thenReturn(appUrl); - underTest = new GithubApplicationClientImpl(httpClient, appSecurity, gitHubSettings, githubPaginatedHttpClient); + underTest = new GithubApplicationClientImpl(githubApplicationHttpClient, appSecurity, gitHubSettings, githubPaginatedHttpClient); logTester.clear(); } @@ -179,7 +179,7 @@ public class GithubApplicationClientImplTest { public void checkAppPermissions_IOException() throws IOException { AppToken appToken = mockAppToken(); - when(httpClient.get(appUrl, appToken, "/app")).thenThrow(new IOException("OOPS")); + when(githubApplicationHttpClient.get(appUrl, appToken, "/app")).thenThrow(new IOException("OOPS")); assertThatThrownBy(() -> underTest.checkAppPermissions(githubAppConfiguration)) .isInstanceOf(IllegalArgumentException.class) @@ -191,7 +191,7 @@ public class GithubApplicationClientImplTest { public void checkAppPermissions_ErrorCodes(int errorCode, String expectedMessage) throws IOException { AppToken appToken = mockAppToken(); - when(httpClient.get(appUrl, appToken, "/app")).thenReturn(new ErrorGetResponse(errorCode, null)); + when(githubApplicationHttpClient.get(appUrl, appToken, "/app")).thenReturn(new ErrorGetResponse(errorCode, null)); assertThatThrownBy(() -> underTest.checkAppPermissions(githubAppConfiguration)) .isInstanceOf(IllegalArgumentException.class) @@ -211,7 +211,7 @@ public class GithubApplicationClientImplTest { public void checkAppPermissions_MissingPermissions() throws IOException { AppToken appToken = mockAppToken(); - when(httpClient.get(appUrl, appToken, "/app")).thenReturn(new OkGetResponse("{}")); + when(githubApplicationHttpClient.get(appUrl, appToken, "/app")).thenReturn(new OkGetResponse("{}")); assertThatThrownBy(() -> underTest.checkAppPermissions(githubAppConfiguration)) .isInstanceOf(IllegalArgumentException.class) @@ -230,7 +230,7 @@ public class GithubApplicationClientImplTest { + " }\n" + "}"; - when(httpClient.get(appUrl, appToken, "/app")).thenReturn(new OkGetResponse(json)); + when(githubApplicationHttpClient.get(appUrl, appToken, "/app")).thenReturn(new OkGetResponse(json)); assertThatThrownBy(() -> underTest.checkAppPermissions(githubAppConfiguration)) .isInstanceOf(IllegalArgumentException.class) @@ -249,7 +249,7 @@ public class GithubApplicationClientImplTest { + " }\n" + "}"; - when(httpClient.get(appUrl, appToken, "/app")).thenReturn(new OkGetResponse(json)); + when(githubApplicationHttpClient.get(appUrl, appToken, "/app")).thenReturn(new OkGetResponse(json)); assertThatCode(() -> underTest.checkAppPermissions(githubAppConfiguration)).isNull(); } @@ -258,7 +258,7 @@ public class GithubApplicationClientImplTest { public void getInstallationId_returns_installation_id_of_given_account() throws IOException { AppToken appToken = new AppToken(APP_JWT_TOKEN); when(appSecurity.createAppToken(githubAppConfiguration.getId(), githubAppConfiguration.getPrivateKey())).thenReturn(appToken); - when(httpClient.get(appUrl, appToken, "/repos/torvalds/linux/installation")) + when(githubApplicationHttpClient.get(appUrl, appToken, "/repos/torvalds/linux/installation")) .thenReturn(new OkGetResponse("{" + " \"id\": 2," + " \"account\": {" + @@ -281,7 +281,7 @@ public class GithubApplicationClientImplTest { public void getInstallationId_return_empty_if_no_installation_found_for_githubAccount() throws IOException { AppToken appToken = new AppToken(APP_JWT_TOKEN); when(appSecurity.createAppToken(githubAppConfiguration.getId(), githubAppConfiguration.getPrivateKey())).thenReturn(appToken); - when(httpClient.get(appUrl, appToken, "/repos/torvalds/linux/installation")) + when(githubApplicationHttpClient.get(appUrl, appToken, "/repos/torvalds/linux/installation")) .thenReturn(new ErrorGetResponse(404, null)); assertThat(underTest.getInstallationId(githubAppConfiguration, "torvalds")).isEmpty(); @@ -290,44 +290,44 @@ public class GithubApplicationClientImplTest { @Test @UseDataProvider("githubServers") public void createUserAccessToken_returns_empty_if_access_token_cant_be_created(String apiUrl, String appUrl) throws IOException { - when(httpClient.post(appUrl, null, "/login/oauth/access_token?client_id=clientId&client_secret=clientSecret&code=code")) + when(githubApplicationHttpClient.post(appUrl, null, "/login/oauth/access_token?client_id=clientId&client_secret=clientSecret&code=code")) .thenReturn(new Response(400, null)); assertThatThrownBy(() -> underTest.createUserAccessToken(appUrl, "clientId", "clientSecret", "code")) .isInstanceOf(IllegalStateException.class); - verify(httpClient).post(appUrl, null, "/login/oauth/access_token?client_id=clientId&client_secret=clientSecret&code=code"); + verify(githubApplicationHttpClient).post(appUrl, null, "/login/oauth/access_token?client_id=clientId&client_secret=clientSecret&code=code"); } @Test @UseDataProvider("githubServers") public void createUserAccessToken_fail_if_access_token_request_fails(String apiUrl, String appUrl) throws IOException { - when(httpClient.post(appUrl, null, "/login/oauth/access_token?client_id=clientId&client_secret=clientSecret&code=code")) + when(githubApplicationHttpClient.post(appUrl, null, "/login/oauth/access_token?client_id=clientId&client_secret=clientSecret&code=code")) .thenThrow(new IOException("OOPS")); assertThatThrownBy(() -> underTest.createUserAccessToken(apiUrl, "clientId", "clientSecret", "code")) .isInstanceOf(IllegalStateException.class) .hasMessage("Failed to create GitHub's user access token"); - verify(httpClient).post(appUrl, null, "/login/oauth/access_token?client_id=clientId&client_secret=clientSecret&code=code"); + verify(githubApplicationHttpClient).post(appUrl, null, "/login/oauth/access_token?client_id=clientId&client_secret=clientSecret&code=code"); } @Test @UseDataProvider("githubServers") public void createUserAccessToken_throws_illegal_argument_exception_if_access_token_code_is_expired(String apiUrl, String appUrl) throws IOException { - when(httpClient.post(appUrl, null, "/login/oauth/access_token?client_id=clientId&client_secret=clientSecret&code=code")) + when(githubApplicationHttpClient.post(appUrl, null, "/login/oauth/access_token?client_id=clientId&client_secret=clientSecret&code=code")) .thenReturn(new OkGetResponse("error_code=100&error=expired_or_invalid")); assertThatThrownBy(() -> underTest.createUserAccessToken(apiUrl, "clientId", "clientSecret", "code")) .isInstanceOf(IllegalArgumentException.class); - verify(httpClient).post(appUrl, null, "/login/oauth/access_token?client_id=clientId&client_secret=clientSecret&code=code"); + verify(githubApplicationHttpClient).post(appUrl, null, "/login/oauth/access_token?client_id=clientId&client_secret=clientSecret&code=code"); } @Test @UseDataProvider("githubServers") public void createUserAccessToken_from_authorization_code_returns_access_token(String apiUrl, String appUrl) throws IOException { String token = randomAlphanumeric(10); - when(httpClient.post(appUrl, null, "/login/oauth/access_token?client_id=clientId&client_secret=clientSecret&code=code")) + when(githubApplicationHttpClient.post(appUrl, null, "/login/oauth/access_token?client_id=clientId&client_secret=clientSecret&code=code")) .thenReturn(new OkGetResponse("access_token=" + token + "&status=")); UserAccessToken userAccessToken = underTest.createUserAccessToken(apiUrl, "clientId", "clientSecret", "code"); @@ -335,14 +335,14 @@ public class GithubApplicationClientImplTest { assertThat(userAccessToken) .extracting(UserAccessToken::getValue, UserAccessToken::getAuthorizationHeaderPrefix) .containsOnly(token, "token"); - verify(httpClient).post(appUrl, null, "/login/oauth/access_token?client_id=clientId&client_secret=clientSecret&code=code"); + verify(githubApplicationHttpClient).post(appUrl, null, "/login/oauth/access_token?client_id=clientId&client_secret=clientSecret&code=code"); } @Test public void getApp_returns_id() throws IOException { AppToken appToken = new AppToken(APP_JWT_TOKEN); when(appSecurity.createAppToken(githubAppConfiguration.getId(), githubAppConfiguration.getPrivateKey())).thenReturn(appToken); - when(httpClient.get(appUrl, appToken, "/app")) + when(githubApplicationHttpClient.get(appUrl, appToken, "/app")) .thenReturn(new OkGetResponse("{\"installations_count\": 2}")); assertThat(underTest.getApp(githubAppConfiguration).getInstallationsCount()).isEqualTo(2L); @@ -352,7 +352,7 @@ public class GithubApplicationClientImplTest { public void getApp_whenStatusCodeIsNotOk_shouldThrowHttpException() throws IOException { AppToken appToken = new AppToken(APP_JWT_TOKEN); when(appSecurity.createAppToken(githubAppConfiguration.getId(), githubAppConfiguration.getPrivateKey())).thenReturn(appToken); - when(httpClient.get(appUrl, appToken, "/app")) + when(githubApplicationHttpClient.get(appUrl, appToken, "/app")) .thenReturn(new ErrorGetResponse(418, "I'm a teapot")); assertThatThrownBy(() -> underTest.getApp(githubAppConfiguration)) @@ -378,7 +378,7 @@ public class GithubApplicationClientImplTest { String appUrl = "https://github.sonarsource.com"; AccessToken accessToken = new UserAccessToken(randomAlphanumeric(10)); - when(httpClient.get(appUrl, accessToken, String.format("/user/installations?page=%s&per_page=%s", 1, 100))) + when(githubApplicationHttpClient.get(appUrl, accessToken, String.format("/user/installations?page=%s&per_page=%s", 1, 100))) .thenThrow(new IOException("OOPS")); assertThatThrownBy(() -> underTest.listOrganizations(appUrl, accessToken, 1, 100)) @@ -413,7 +413,7 @@ public class GithubApplicationClientImplTest { + " \"total_count\": 0\n" + "} "; - when(httpClient.get(appUrl, accessToken, String.format("/user/installations?page=%s&per_page=%s", 1, 100))) + when(githubApplicationHttpClient.get(appUrl, accessToken, String.format("/user/installations?page=%s&per_page=%s", 1, 100))) .thenReturn(new OkGetResponse(responseJson)); GithubApplicationClient.Organizations organizations = underTest.listOrganizations(appUrl, accessToken, 1, 100); @@ -504,7 +504,7 @@ public class GithubApplicationClientImplTest { + " ]\n" + "} "; - when(httpClient.get(appUrl, accessToken, String.format("/user/installations?page=%s&per_page=%s", 1, 100))) + when(githubApplicationHttpClient.get(appUrl, accessToken, String.format("/user/installations?page=%s&per_page=%s", 1, 100))) .thenReturn(new OkGetResponse(responseJson)); GithubApplicationClient.Organizations organizations = underTest.listOrganizations(appUrl, accessToken, 1, 100); @@ -581,18 +581,14 @@ public class GithubApplicationClientImplTest { } @Test - public void getWhitelistedGithubAppInstallations_whenGithubReturnsError_shouldThrow() throws IOException { + public void getWhitelistedGithubAppInstallations_whenGithubReturnsError_shouldReThrow() { AppToken appToken = new AppToken(APP_JWT_TOKEN); when(appSecurity.createAppToken(githubAppConfiguration.getId(), githubAppConfiguration.getPrivateKey())).thenReturn(appToken); - when(githubPaginatedHttpClient.get(any(), any(), any(), any())).thenThrow(new IOException("io exception")); + when(githubPaginatedHttpClient.get(any(), any(), any(), any())).thenThrow(new IllegalStateException("exception")); assertThatThrownBy(() -> underTest.getWhitelistedGithubAppInstallations(githubAppConfiguration)) .isInstanceOf(IllegalStateException.class) - .hasMessage( - "SonarQube was not able to retrieve resources from GitHub. " - + "This is likely due to a connectivity problem or a temporary network outage: " - + "Error while executing a paginated call to GitHub - appUrl: Any URL, path: /app/installations. io exception" - ); + .hasMessage("exception"); } @Test @@ -600,7 +596,7 @@ public class GithubApplicationClientImplTest { String appUrl = "https://github.sonarsource.com"; AccessToken accessToken = new UserAccessToken(randomAlphanumeric(10)); - when(httpClient.get(appUrl, accessToken, String.format("/search/repositories?q=%s&page=%s&per_page=%s", "org:test", 1, 100))) + when(githubApplicationHttpClient.get(appUrl, accessToken, String.format("/search/repositories?q=%s&page=%s&per_page=%s", "org:test", 1, 100))) .thenThrow(new IOException("OOPS")); assertThatThrownBy(() -> underTest.listRepositories(appUrl, accessToken, "test", null, 1, 100)) @@ -635,7 +631,7 @@ public class GithubApplicationClientImplTest { + " \"total_count\": 0\n" + "}"; - when(httpClient.get(appUrl, accessToken, String.format("/search/repositories?q=%s&page=%s&per_page=%s", "fork:true+org:github", 1, 100))) + when(githubApplicationHttpClient.get(appUrl, accessToken, String.format("/search/repositories?q=%s&page=%s&per_page=%s", "fork:true+org:github", 1, 100))) .thenReturn(new OkGetResponse(responseJson)); GithubApplicationClient.Repositories repositories = underTest.listRepositories(appUrl, accessToken, "github", null, 1, 100); @@ -723,7 +719,7 @@ public class GithubApplicationClientImplTest { + " ]\n" + "}"; - when(httpClient.get(appUrl, accessToken, String.format("/search/repositories?q=%s&page=%s&per_page=%s", "fork:true+org:github", 1, 100))) + when(githubApplicationHttpClient.get(appUrl, accessToken, String.format("/search/repositories?q=%s&page=%s&per_page=%s", "fork:true+org:github", 1, 100))) .thenReturn(new OkGetResponse(responseJson)); GithubApplicationClient.Repositories repositories = underTest.listRepositories(appUrl, accessToken, "github", null, 1, 100); @@ -778,7 +774,7 @@ public class GithubApplicationClientImplTest { + " ]\n" + "}"; - when(httpClient.get(appUrl, accessToken, String.format("/search/repositories?q=%s&page=%s&per_page=%s", "world+fork:true+org:github", 1, 100))) + when(githubApplicationHttpClient.get(appUrl, accessToken, String.format("/search/repositories?q=%s&page=%s&per_page=%s", "world+fork:true+org:github", 1, 100))) .thenReturn(new GetResponse() { @Override public Optional<String> getNextEndPoint() { @@ -811,7 +807,7 @@ public class GithubApplicationClientImplTest { @Test public void getRepository_returns_empty_when_repository_doesnt_exist() throws IOException { - when(httpClient.get(any(), any(), any())) + when(githubApplicationHttpClient.get(any(), any(), any())) .thenReturn(new Response(404, null)); Optional<GithubApplicationClient.Repository> repository = underTest.getRepository(appUrl, new UserAccessToken("temp"), "octocat/Hello-World"); @@ -823,7 +819,7 @@ public class GithubApplicationClientImplTest { public void getRepository_fails_on_failure() throws IOException { String repositoryKey = "octocat/Hello-World"; - when(httpClient.get(any(), any(), any())) + when(githubApplicationHttpClient.get(any(), any(), any())) .thenThrow(new IOException("OOPS")); UserAccessToken token = new UserAccessToken("temp"); @@ -974,7 +970,7 @@ public class GithubApplicationClientImplTest { + " }" + "}"; - when(httpClient.get(appUrl, accessToken, "/repos/octocat/Hello-World")) + when(githubApplicationHttpClient.get(appUrl, accessToken, "/repos/octocat/Hello-World")) .thenReturn(new GetResponse() { @Override public Optional<String> getNextEndPoint() { @@ -1022,7 +1018,7 @@ public class GithubApplicationClientImplTest { @Test public void createAppInstallationToken_returns_empty_if_post_throws_IOE() throws IOException { mockAppToken(); - when(httpClient.post(anyString(), any(AccessToken.class), anyString())).thenThrow(IOException.class); + when(githubApplicationHttpClient.post(anyString(), any(AccessToken.class), anyString())).thenThrow(IOException.class); Optional<AppInstallationToken> accessToken = underTest.createAppInstallationToken(githubAppConfiguration, INSTALLATION_ID); assertThat(accessToken).isEmpty(); @@ -1037,7 +1033,7 @@ public class GithubApplicationClientImplTest { Optional<AppInstallationToken> accessToken = underTest.createAppInstallationToken(githubAppConfiguration, INSTALLATION_ID); assertThat(accessToken).isEmpty(); - verify(httpClient).post(appUrl, appToken, "/app/installations/" + INSTALLATION_ID + "/access_tokens"); + verify(githubApplicationHttpClient).post(appUrl, appToken, "/app/installations/" + INSTALLATION_ID + "/access_tokens"); } @Test @@ -1048,7 +1044,7 @@ public class GithubApplicationClientImplTest { Optional<AppInstallationToken> accessToken = underTest.createAppInstallationToken(githubAppConfiguration, INSTALLATION_ID); assertThat(accessToken).hasValue(installToken); - verify(httpClient).post(appUrl, appToken, "/app/installations/" + INSTALLATION_ID + "/access_tokens"); + verify(githubApplicationHttpClient).post(appUrl, appToken, "/app/installations/" + INSTALLATION_ID + "/access_tokens"); } @Test @@ -1067,18 +1063,12 @@ public class GithubApplicationClientImplTest { } @Test - public void getRepositoryTeams_whenGitHubCallThrowsIOException_shouldLogAndThrow() throws IOException { - when(githubPaginatedHttpClient.get(eq(APP_URL), eq(appInstallationToken), eq(REPO_TEAMS_ENDPOINT), any())).thenThrow(new IOException("error")); + public void getRepositoryTeams_whenGitHubCallThrowsException_shouldRethrow() { + when(githubPaginatedHttpClient.get(eq(APP_URL), eq(appInstallationToken), eq(REPO_TEAMS_ENDPOINT), any())).thenThrow(new IllegalStateException("error")); assertThatIllegalStateException() .isThrownBy(() -> underTest.getRepositoryTeams(APP_URL, appInstallationToken, ORG_NAME, REPO_NAME)) - .isInstanceOf(IllegalStateException.class) - .withMessage( - "SonarQube was not able to retrieve resources from GitHub. This is likely due to a connectivity problem or a temporary network outage: Error while executing a paginated call to GitHub - appUrl: https://github.com/, path: /repos/ORG_NAME/repo1/teams. error"); - - assertThat(logTester.logs()).hasSize(1); - assertThat(logTester.logs(Level.WARN)) - .containsExactly("Error while executing a paginated call to GitHub - appUrl: https://github.com/, path: /repos/ORG_NAME/repo1/teams."); + .withMessage("error"); } private static List<GsonRepositoryTeam> expectedTeams() { @@ -1104,19 +1094,12 @@ public class GithubApplicationClientImplTest { } @Test - public void getRepositoryCollaborators_whenGitHubCallThrowsIOException_shouldLogAndThrow() throws IOException { - when(githubPaginatedHttpClient.get(eq(APP_URL), eq(appInstallationToken), eq(REPO_COLLABORATORS_ENDPOINT), any())).thenThrow(new IOException("error")); + public void getRepositoryCollaborators_whenGitHubCallThrowsException_shouldRethrow() { + when(githubPaginatedHttpClient.get(eq(APP_URL), eq(appInstallationToken), eq(REPO_COLLABORATORS_ENDPOINT), any())).thenThrow(new IllegalStateException("error")); assertThatIllegalStateException() .isThrownBy(() -> underTest.getRepositoryCollaborators(APP_URL, appInstallationToken, ORG_NAME, REPO_NAME)) - .isInstanceOf(IllegalStateException.class) - .withMessage( - "SonarQube was not able to retrieve resources from GitHub. This is likely due to a connectivity problem or a temporary network outage: " - + "Error while executing a paginated call to GitHub - appUrl: https://github.com/, path: /repos/ORG_NAME/repo1/collaborators?affiliation=direct. error"); - - assertThat(logTester.logs()).hasSize(1); - assertThat(logTester.logs(Level.WARN)) - .containsExactly("Error while executing a paginated call to GitHub - appUrl: https://github.com/, path: /repos/ORG_NAME/repo1/collaborators?affiliation=direct."); + .withMessage("error"); } private static String getResponseContent(String path) throws IOException { @@ -1133,7 +1116,7 @@ public class GithubApplicationClientImplTest { Response response = mock(Response.class); when(response.getContent()).thenReturn(Optional.empty()); when(response.getCode()).thenReturn(HTTP_UNAUTHORIZED); - when(httpClient.post(eq(appUrl), any(AppToken.class), eq("/app/installations/" + INSTALLATION_ID + "/access_tokens"))).thenReturn(response); + when(githubApplicationHttpClient.post(eq(appUrl), any(AppToken.class), eq("/app/installations/" + INSTALLATION_ID + "/access_tokens"))).thenReturn(response); } private AppToken mockAppToken() { @@ -1149,7 +1132,7 @@ public class GithubApplicationClientImplTest { " \"token\": \"" + token + "\"" + "}")); when(response.getCode()).thenReturn(HTTP_CREATED); - when(httpClient.post(eq(appUrl), any(AppToken.class), eq("/app/installations/" + INSTALLATION_ID + "/access_tokens"))).thenReturn(response); + when(githubApplicationHttpClient.post(eq(appUrl), any(AppToken.class), eq("/app/installations/" + INSTALLATION_ID + "/access_tokens"))).thenReturn(response); return new AppInstallationToken(token); } diff --git a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/gitlab/GitlabHttpClientTest.java b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/gitlab/GitlabApplicationClientTest.java index 48c0b2d0067..4ec9f5d33fe 100644 --- a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/gitlab/GitlabHttpClientTest.java +++ b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/gitlab/GitlabApplicationClientTest.java @@ -20,31 +20,46 @@ package org.sonar.alm.client.gitlab; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.function.Function; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.RecordedRequest; +import org.apache.commons.io.IOUtils; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.mockito.ArgumentCaptor; import org.slf4j.event.Level; import org.sonar.alm.client.ConstantTimeoutConfiguration; import org.sonar.alm.client.TimeoutConfiguration; import org.sonar.api.testfixtures.log.LogTester; +import org.sonar.auth.gitlab.GsonGroup; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.tuple; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; -public class GitlabHttpClientTest { +public class GitlabApplicationClientTest { @Rule public LogTester logTester = new LogTester(); + + private GitlabPaginatedHttpClient gitlabPaginatedHttpClient = mock(); + private final MockWebServer server = new MockWebServer(); - private GitlabHttpClient underTest; + private GitlabApplicationClient underTest; private String gitlabUrl; @Before @@ -54,7 +69,7 @@ public class GitlabHttpClientTest { gitlabUrl = urlWithEndingSlash.substring(0, urlWithEndingSlash.length() - 1); TimeoutConfiguration timeoutConfiguration = new ConstantTimeoutConfiguration(10_000); - underTest = new GitlabHttpClient(timeoutConfiguration); + underTest = new GitlabApplicationClient(gitlabPaginatedHttpClient, timeoutConfiguration); } @After @@ -523,4 +538,52 @@ public class GitlabHttpClientTest { + "] " + "failed with error message : [Failed to connect to " + server.getHostName()); } + + @Test + public void getGroups_whenCallIsInError_rethrows() throws IOException { + String token = "token-toto"; + GitlabToken gitlabToken = new GitlabToken(token); + when(gitlabPaginatedHttpClient.get(eq(gitlabUrl), eq(gitlabToken), eq("/groups"), any())).thenThrow(new IllegalStateException("exception")); + + assertThatIllegalStateException() + .isThrownBy(() -> underTest.getGroups(gitlabUrl, token)) + .withMessage("exception"); + } + + @Test + public void getGroups_whenCallIsSuccessful_deserializesAndReturnsCorrectlyGroups() throws IOException { + ArgumentCaptor<Function<String, List<GsonGroup>>> deserializerCaptor = ArgumentCaptor.forClass(Function.class); + + String token = "token-toto"; + GitlabToken gitlabToken = new GitlabToken(token); + List<GsonGroup> expectedGroups = expectedGroups(); + when(gitlabPaginatedHttpClient.get(eq(gitlabUrl), eq(gitlabToken), eq("/groups"), deserializerCaptor.capture())).thenReturn(expectedGroups); + + Set<GsonGroup> groups = underTest.getGroups(gitlabUrl, token); + assertThat(groups).containsExactlyInAnyOrderElementsOf(expectedGroups); + + String responseContent = getResponseContent("groups-full-response.json"); + + List<GsonGroup> deserializedGroups = deserializerCaptor.getValue().apply(responseContent); + assertThat(deserializedGroups).usingRecursiveComparison().isEqualTo(expectedGroups); + } + + private static List<GsonGroup> expectedGroups() { + GsonGroup gsonGroup = createGsonGroup("56232243", "sonarsource/cfamily", "this is a long description"); + GsonGroup gsonGroup2 = createGsonGroup("78902256", "sonarsource/sonarqube/mmf-3052-ant1", ""); + return List.of(gsonGroup, gsonGroup2); + } + + private static GsonGroup createGsonGroup(String number, String fullPath, String description) { + GsonGroup gsonGroup = mock(GsonGroup.class); + when(gsonGroup.getId()).thenReturn(number); + when(gsonGroup.getFullPath()).thenReturn(fullPath); + when(gsonGroup.getDescription()).thenReturn(description); + return gsonGroup; + } + + private static String getResponseContent(String path) throws IOException { + return IOUtils.toString(GitlabApplicationClientTest.class.getResourceAsStream(path), StandardCharsets.UTF_8); + } + } diff --git a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/gitlab/GitlabGlobalSettingsValidatorTest.java b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/gitlab/GitlabGlobalSettingsValidatorTest.java index 6582f503024..13f1c4c9c47 100644 --- a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/gitlab/GitlabGlobalSettingsValidatorTest.java +++ b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/gitlab/GitlabGlobalSettingsValidatorTest.java @@ -21,8 +21,6 @@ package org.sonar.alm.client.gitlab; import org.junit.BeforeClass; import org.junit.Test; -import org.sonar.alm.client.gitlab.GitlabGlobalSettingsValidator; -import org.sonar.alm.client.gitlab.GitlabHttpClient; import org.sonar.api.config.internal.Encryption; import org.sonar.api.config.internal.Settings; import org.sonar.db.alm.setting.AlmSettingDto; @@ -37,7 +35,7 @@ public class GitlabGlobalSettingsValidatorTest { private static final Encryption encryption = mock(Encryption.class); private static final Settings settings = mock(Settings.class); - private final GitlabHttpClient gitlabHttpClient = mock(GitlabHttpClient.class); + private final GitlabApplicationClient gitlabHttpClient = mock(GitlabApplicationClient.class); private final GitlabGlobalSettingsValidator underTest = new GitlabGlobalSettingsValidator(gitlabHttpClient, settings); |