aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-alm-client
diff options
context:
space:
mode:
authorAurelien Poscia <aurelien.poscia@sonarsource.com>2023-10-16 11:09:16 +0200
committersonartech <sonartech@sonarsource.com>2023-10-20 20:02:40 +0000
commit8445ca39a9f46b228f693fbb259f623ba6d05414 (patch)
treefa2d714624e16a1254c931057d6f8d2386f3a8fc /server/sonar-alm-client
parentf5f71fdbe1482d7b132a9923419672459d762aad (diff)
downloadsonarqube-8445ca39a9f46b228f693fbb259f623ba6d05414.tar.gz
sonarqube-8445ca39a9f46b228f693fbb259f623ba6d05414.zip
SONAR-20700 Move getRepositoryCollaborators/getRepositoryTeams from GithubUserClient to GithubApplicationClient (and make it accessible for commmunity edition)
Diffstat (limited to 'server/sonar-alm-client')
-rw-r--r--server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubApplicationClient.java9
-rw-r--r--server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubApplicationClientImpl.java58
-rw-r--r--server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/api/GsonRepositoryCollaborator.java29
-rw-r--r--server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/api/GsonRepositoryTeam.java31
-rw-r--r--server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/api/package-info.java23
-rw-r--r--server/sonar-alm-client/src/test/java/org/sonar/alm/client/github/GithubApplicationClientImplTest.java101
-rw-r--r--server/sonar-alm-client/src/test/resources/org/sonar/alm/client/github/repo-collaborators-full-response.json26
-rw-r--r--server/sonar-alm-client/src/test/resources/org/sonar/alm/client/github/repo-teams-full-response.json28
8 files changed, 294 insertions, 11 deletions
diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubApplicationClient.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubApplicationClient.java
index 9a7db826bb3..7ef05182cd0 100644
--- a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubApplicationClient.java
+++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubApplicationClient.java
@@ -22,9 +22,12 @@ package org.sonar.alm.client.github;
import com.google.gson.annotations.SerializedName;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
+import org.sonar.alm.client.github.api.GsonRepositoryCollaborator;
+import org.sonar.alm.client.github.api.GsonRepositoryTeam;
import org.sonar.alm.client.github.config.GithubAppConfiguration;
import org.sonar.alm.client.github.config.GithubAppInstallation;
import org.sonar.alm.client.github.security.AccessToken;
@@ -93,6 +96,12 @@ public interface GithubApplicationClient {
*/
Optional<Repository> getRepository(String appUrl, AccessToken accessToken, String repositoryKey);
+
+
+ Set<GsonRepositoryTeam> getRepositoryTeams(String appUrl, AppInstallationToken accessToken, String orgName, String repoName);
+
+ Set<GsonRepositoryCollaborator> getRepositoryCollaborators(String appUrl, AppInstallationToken accessToken, String orgName, String repoName);
+
class Repositories {
private int total;
private List<Repository> repositories;
diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubApplicationClientImpl.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubApplicationClientImpl.java
index be6914fa8a5..9403377c59f 100644
--- a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubApplicationClientImpl.java
+++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/GithubApplicationClientImpl.java
@@ -32,6 +32,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import java.util.function.Function;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.slf4j.Logger;
@@ -40,6 +41,8 @@ import org.sonar.alm.client.github.GithubApplicationHttpClient.GetResponse;
import org.sonar.alm.client.github.GithubBinding.GsonGithubRepository;
import org.sonar.alm.client.github.GithubBinding.GsonInstallations;
import org.sonar.alm.client.github.GithubBinding.GsonRepositorySearch;
+import org.sonar.alm.client.github.api.GsonRepositoryCollaborator;
+import org.sonar.alm.client.github.api.GsonRepositoryTeam;
import org.sonar.alm.client.github.config.GithubAppConfiguration;
import org.sonar.alm.client.github.config.GithubAppInstallation;
import org.sonar.alm.client.github.security.AccessToken;
@@ -66,6 +69,12 @@ public class GithubApplicationClientImpl implements GithubApplicationClient {
protected static final String WRITE_PERMISSION_NAME = "write";
protected static final String READ_PERMISSION_NAME = "read";
protected static final String FAILED_TO_REQUEST_BEGIN_MSG = "Failed to request ";
+
+ private static final String EXCEPTION_MESSAGE = "SonarQube was not able to retrieve resources from GitHub. "
+ + "This is likely due to a connectivity problem or a temporary network outage";
+
+ private static final Type REPOSITORY_TEAM_LIST_TYPE = TypeToken.getParameterized(List.class, GsonRepositoryTeam.class).getType();
+ private static final Type REPOSITORY_COLLABORATORS_LIST_TYPE = TypeToken.getParameterized(List.class, GsonRepositoryCollaborator.class).getType();
private static final Type ORGANIZATION_LIST_TYPE = TypeToken.getParameterized(List.class, GithubBinding.GsonInstallation.class).getType();
protected final GithubApplicationHttpClient appHttpClient;
protected final GithubAppSecurity appSecurity;
@@ -232,13 +241,8 @@ public class GithubApplicationClientImpl implements GithubApplicationClient {
private List<GithubBinding.GsonInstallation> fetchAppInstallationsFromGithub(GithubAppConfiguration githubAppConfiguration) {
AppToken appToken = appSecurity.createAppToken(githubAppConfiguration.getId(), githubAppConfiguration.getPrivateKey());
String endpoint = "/app/installations";
- try {
- return githubPaginatedHttpClient.get(githubAppConfiguration.getApiEndpoint(), appToken, endpoint, resp -> GSON.fromJson(resp, ORGANIZATION_LIST_TYPE));
- } catch (IOException e) {
- LOG.warn(FAILED_TO_REQUEST_BEGIN_MSG + endpoint, e);
- throw new IllegalStateException("An error occurred when retrieving your GitHup App installations. "
- + "It might be related to your GitHub App configuration or a connectivity problem.");
- }
+
+ return executePaginatedQuery(githubAppConfiguration.getApiEndpoint(), appToken, endpoint, resp -> GSON.fromJson(resp, ORGANIZATION_LIST_TYPE));
}
protected <T> Optional<T> get(String baseUrl, AccessToken token, String endPoint, Class<T> gsonClass) {
@@ -292,7 +296,7 @@ public class GithubApplicationClientImpl implements GithubApplicationClient {
.map(GsonGithubRepository::toRepository);
} catch (Exception e) {
throw new IllegalStateException(format("Failed to get repository '%s' on '%s' (this might be related to the GitHub App installation scope)",
- organizationAndRepository, appUrl), e);
+ organizationAndRepository, appUrl), e);
}
}
@@ -363,4 +367,42 @@ public class GithubApplicationClientImpl implements GithubApplicationClient {
return Optional.empty();
}
}
+
+ @Override
+ public Set<GsonRepositoryTeam> getRepositoryTeams(String appUrl, AppInstallationToken accessToken, String orgName, String repoName) {
+ return Set
+ .copyOf(executePaginatedQuery(appUrl, accessToken, format("/repos/%s/%s/teams", orgName, repoName), resp -> GSON.fromJson(resp, REPOSITORY_TEAM_LIST_TYPE)));
+ }
+
+ @Override
+ public Set<GsonRepositoryCollaborator> getRepositoryCollaborators(String appUrl, AppInstallationToken accessToken, String orgName, String repoName) {
+ return Set
+ .copyOf(
+ executePaginatedQuery(
+ appUrl,
+ accessToken,
+ format("/repos/%s/%s/collaborators?affiliation=direct", orgName, repoName),
+ resp -> GSON.fromJson(resp, REPOSITORY_COLLABORATORS_LIST_TYPE)));
+ }
+
+ private <E> List<E> executePaginatedQuery(String appUrl, AccessToken token, String query, Function<String, List<E>> responseDeserializer) {
+ try {
+ return githubPaginatedHttpClient.get(appUrl, token, query, responseDeserializer);
+ } catch (IOException ioException) {
+ throw logAndCreateException(ioException, format("Error while executing a paginated call to GitHub - appUrl: %s, path: %s.", appUrl, query));
+ }
+ }
+
+ private static IllegalStateException logAndCreateException(IOException ioException, String errorMessage) {
+ log(errorMessage, ioException);
+ return new IllegalStateException(EXCEPTION_MESSAGE + ": " + errorMessage + " " + ioException.getMessage());
+ }
+
+ private static void log(String message, Exception e) {
+ if (LOG.isDebugEnabled()) {
+ LOG.warn(message, e);
+ } else {
+ LOG.warn(message);
+ }
+ }
}
diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/api/GsonRepositoryCollaborator.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/api/GsonRepositoryCollaborator.java
new file mode 100644
index 00000000000..d7f43cc4e12
--- /dev/null
+++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/api/GsonRepositoryCollaborator.java
@@ -0,0 +1,29 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * 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.api;
+
+import com.google.gson.annotations.SerializedName;
+import org.sonar.auth.github.GsonRepositoryPermissions;
+
+public record GsonRepositoryCollaborator(@SerializedName("login") String name,
+ @SerializedName("id") Integer id,
+ @SerializedName("role_name") String roleName,
+ @SerializedName("permissions") GsonRepositoryPermissions permissions) {
+}
diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/api/GsonRepositoryTeam.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/api/GsonRepositoryTeam.java
new file mode 100644
index 00000000000..806b5de9e27
--- /dev/null
+++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/api/GsonRepositoryTeam.java
@@ -0,0 +1,31 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * 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.api;
+
+import com.google.gson.annotations.SerializedName;
+import org.sonar.auth.github.GsonRepositoryPermissions;
+
+public record GsonRepositoryTeam(
+ @SerializedName("name") String name,
+ @SerializedName("id") Integer id,
+ @SerializedName("slug") String slug,
+ @SerializedName("permission") String permission,
+ @SerializedName("permissions") GsonRepositoryPermissions permissions) {
+}
diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/api/package-info.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/api/package-info.java
new file mode 100644
index 00000000000..4c1a2f9b0a9
--- /dev/null
+++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/github/api/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.alm.client.github.api;
+
+import javax.annotation.ParametersAreNonnullByDefault;
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 0429beebdc5..cbe044b9bfe 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
@@ -23,17 +23,22 @@ import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import javax.annotation.Nullable;
+import org.apache.commons.io.IOUtils;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.slf4j.event.Level;
import org.sonar.alm.client.github.GithubApplicationHttpClient.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;
import org.sonar.alm.client.github.config.GithubAppInstallation;
import org.sonar.alm.client.github.security.AccessToken;
@@ -44,6 +49,7 @@ import org.sonar.api.testfixtures.log.LogAndArguments;
import org.sonar.api.testfixtures.log.LogTester;
import org.sonar.api.utils.log.LoggerLevel;
import org.sonar.auth.github.GitHubSettings;
+import org.sonar.auth.github.GsonRepositoryPermissions;
import org.sonarqube.ws.client.HttpException;
import static java.net.HttpURLConnection.HTTP_CREATED;
@@ -53,6 +59,7 @@ import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED;
import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.groups.Tuple.tuple;
import static org.mockito.ArgumentMatchers.any;
@@ -65,7 +72,12 @@ import static org.sonar.alm.client.github.GithubApplicationHttpClient.GetRespons
@RunWith(DataProviderRunner.class)
public class GithubApplicationClientImplTest {
-
+ private static final String ORG_NAME = "ORG_NAME";
+ private static final String TEAM_NAME = "team1";
+ private static final String REPO_NAME = "repo1";
+ private static final String APP_URL = "https://github.com/";
+ private static final String REPO_TEAMS_ENDPOINT = "/repos/ORG_NAME/repo1/teams";
+ private static final String REPO_COLLABORATORS_ENDPOINT = "/repos/ORG_NAME/repo1/collaborators?affiliation=direct";
private static final int INSTALLATION_ID = 1;
private static final String APP_JWT_TOKEN = "APP_TOKEN_JWT";
private static final String PAYLOAD_2_ORGS = """
@@ -108,8 +120,10 @@ public class GithubApplicationClientImplTest {
private GitHubSettings gitHubSettings = mock();
private GithubPaginatedHttpClient githubPaginatedHttpClient = mock();
+ private AppInstallationToken appInstallationToken = mock();
private GithubApplicationClient underTest;
+
private String appUrl = "Any URL";
@Before
@@ -574,8 +588,11 @@ public class GithubApplicationClientImplTest {
assertThatThrownBy(() -> underTest.getWhitelistedGithubAppInstallations(githubAppConfiguration))
.isInstanceOf(IllegalStateException.class)
- .hasMessage("An error occurred when retrieving your GitHup App installations. "
- + "It might be related to your GitHub App configuration or a connectivity problem.");
+ .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"
+ );
}
@Test
@@ -1034,6 +1051,84 @@ public class GithubApplicationClientImplTest {
verify(httpClient).post(appUrl, appToken, "/app/installations/" + INSTALLATION_ID + "/access_tokens");
}
+ @Test
+ public void getRepositoryTeams_returnsRepositoryTeams() throws IOException {
+ ArgumentCaptor<Function<String, List<GsonRepositoryTeam>>> deserializerCaptor = ArgumentCaptor.forClass(Function.class);
+
+ when(githubPaginatedHttpClient.get(eq(APP_URL), eq(appInstallationToken), eq(REPO_TEAMS_ENDPOINT), deserializerCaptor.capture())).thenReturn(expectedTeams());
+
+ Set<GsonRepositoryTeam> repoTeams = underTest.getRepositoryTeams(APP_URL, appInstallationToken, ORG_NAME, REPO_NAME);
+
+ assertThat(repoTeams)
+ .containsExactlyInAnyOrderElementsOf(expectedTeams());
+
+ String responseContent = getResponseContent("repo-teams-full-response.json");
+ assertThat(deserializerCaptor.getValue().apply(responseContent)).containsExactlyElementsOf(expectedTeams());
+ }
+
+ @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"));
+
+ 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.");
+ }
+
+ private static List<GsonRepositoryTeam> expectedTeams() {
+ return List.of(
+ new GsonRepositoryTeam("team1", 1, "team1", "pull", new GsonRepositoryPermissions(true, true, true, true, true)),
+ new GsonRepositoryTeam("team2", 2, "team2", "push", new GsonRepositoryPermissions(false, false, true, true, true)));
+ }
+
+ @Test
+ public void getRepositoryCollaborators_returnsCollaboratorsFromGithub() throws IOException {
+ ArgumentCaptor<Function<String, List<GsonRepositoryCollaborator>>> deserializerCaptor = ArgumentCaptor.forClass(Function.class);
+
+ when(githubPaginatedHttpClient.get(eq(APP_URL), eq(appInstallationToken), eq(REPO_COLLABORATORS_ENDPOINT), deserializerCaptor.capture())).thenReturn(expectedCollaborators());
+
+ Set<GsonRepositoryCollaborator> repoTeams = underTest.getRepositoryCollaborators(APP_URL, appInstallationToken, ORG_NAME, REPO_NAME);
+
+ assertThat(repoTeams)
+ .containsExactlyInAnyOrderElementsOf(expectedCollaborators());
+
+ String responseContent = getResponseContent("repo-collaborators-full-response.json");
+ assertThat(deserializerCaptor.getValue().apply(responseContent)).containsExactlyElementsOf(expectedCollaborators());
+
+ }
+
+ @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"));
+
+ 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.");
+ }
+
+ private static String getResponseContent(String path) throws IOException {
+ return IOUtils.toString(GithubApplicationClientImplTest.class.getResourceAsStream(path), StandardCharsets.UTF_8);
+ }
+
+ private static List<GsonRepositoryCollaborator> expectedCollaborators() {
+ return List.of(
+ new GsonRepositoryCollaborator("jean-michel", 1, "role1", new GsonRepositoryPermissions(true, true, true, true, true)),
+ new GsonRepositoryCollaborator("jean-pierre", 2, "role2", new GsonRepositoryPermissions(false, false, true, true, true)));
+ }
+
private void mockAccessTokenCallingGithubFailure() throws IOException {
Response response = mock(Response.class);
when(response.getContent()).thenReturn(Optional.empty());
diff --git a/server/sonar-alm-client/src/test/resources/org/sonar/alm/client/github/repo-collaborators-full-response.json b/server/sonar-alm-client/src/test/resources/org/sonar/alm/client/github/repo-collaborators-full-response.json
new file mode 100644
index 00000000000..644b9083d56
--- /dev/null
+++ b/server/sonar-alm-client/src/test/resources/org/sonar/alm/client/github/repo-collaborators-full-response.json
@@ -0,0 +1,26 @@
+[
+ {
+ "login": "jean-michel",
+ "id": 1,
+ "role_name": "role1",
+ "permissions": {
+ "admin": true,
+ "maintain": true,
+ "push": true,
+ "triage": true,
+ "pull": true
+ }
+ },
+ {
+ "login": "jean-pierre",
+ "id": 2,
+ "role_name": "role2",
+ "permissions": {
+ "admin": false,
+ "maintain": false,
+ "push": true,
+ "triage": true,
+ "pull": true
+ }
+ }
+]
diff --git a/server/sonar-alm-client/src/test/resources/org/sonar/alm/client/github/repo-teams-full-response.json b/server/sonar-alm-client/src/test/resources/org/sonar/alm/client/github/repo-teams-full-response.json
new file mode 100644
index 00000000000..842eed2666e
--- /dev/null
+++ b/server/sonar-alm-client/src/test/resources/org/sonar/alm/client/github/repo-teams-full-response.json
@@ -0,0 +1,28 @@
+[
+ {
+ "name": "team1",
+ "id": 1,
+ "slug": "team1",
+ "permission": "pull",
+ "permissions": {
+ "admin": true,
+ "maintain": true,
+ "push": true,
+ "triage": true,
+ "pull": true
+ }
+ },
+ {
+ "name": "team2",
+ "id": 2,
+ "slug": "team2",
+ "permission": "push",
+ "permissions": {
+ "admin": false,
+ "maintain": false,
+ "push": true,
+ "triage": true,
+ "pull": true
+ }
+ }
+]