@@ -0,0 +1,89 @@ | |||
/* | |||
* 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; | |||
import javax.annotation.concurrent.Immutable; | |||
import org.sonar.alm.client.github.security.AccessToken; | |||
import static java.util.Objects.requireNonNull; | |||
/** | |||
* Token that provides access to the Github API on behalf of | |||
* the Github organization that installed the Github App. | |||
* | |||
* It expires after one hour. | |||
* | |||
* IMPORTANT | |||
* Rate limit is 5'000 API requests per hour for the Github organization. | |||
* Two different Github organizations don't share rate limits. | |||
* Two different instances of {@link AppInstallationToken} of the same Github organization | |||
* share the same quotas (two calls from the two different instances consume | |||
* two hits). | |||
* | |||
* The limit can be higher than 5'000, depending on the number of repositories | |||
* and users present in the organization. See | |||
* https://developer.github.com/apps/building-github-apps/understanding-rate-limits-for-github-apps/ | |||
* | |||
* When the token is expired, the rate limit is 60 calls per hour for the public IP | |||
* of the machine. BE CAREFUL, THAT SHOULD NEVER OCCUR. | |||
* | |||
* See https://developer.github.com/apps/building-github-apps/authenticating-with-github-apps/#authenticating-as-an-installation | |||
*/ | |||
@Immutable | |||
public class AppInstallationToken implements AccessToken { | |||
private final String token; | |||
public AppInstallationToken(String token) { | |||
this.token = requireNonNull(token, "token can't be null"); | |||
} | |||
@Override | |||
public String getValue() { | |||
return token; | |||
} | |||
@Override | |||
public String getAuthorizationHeaderPrefix() { | |||
return "Token"; | |||
} | |||
@Override | |||
public boolean equals(Object o) { | |||
if (this == o) { | |||
return true; | |||
} | |||
if (o == null || getClass() != o.getClass()) { | |||
return false; | |||
} | |||
AppInstallationToken that = (AppInstallationToken) o; | |||
return token.equals(that.token); | |||
} | |||
@Override | |||
public int hashCode() { | |||
return token.hashCode(); | |||
} | |||
@Override | |||
public String toString() { | |||
return token; | |||
} | |||
} |
@@ -44,6 +44,19 @@ public interface GithubApplicationClient { | |||
*/ | |||
UserAccessToken createUserAccessToken(String appUrl, String clientId, String clientSecret, String code); | |||
/** | |||
* Create an installation access token for the specified installation ID. | |||
* | |||
* IMPORTANT: each call consumes one hit of the App GLOBAL quotas (5'000 hits per hour). | |||
* | |||
* Token expires after one hour. | |||
* See https://developer.github.com/apps/building-github-apps/authenticating-with-github-apps/#authenticating-as-an-installation | |||
* | |||
* @return {@code Optional.empty()} if Github is not configured or if token failed to be | |||
* created (network issue, parsing error, Github error, ...). | |||
*/ | |||
Optional<AppInstallationToken> createAppInstallationToken(GithubAppConfiguration githubAppConfiguration, long installationId); | |||
GithubBinding.GsonApp getApp(GithubAppConfiguration githubAppConfiguration); | |||
/** | |||
@@ -78,7 +91,7 @@ public interface GithubApplicationClient { | |||
/** | |||
* Returns the repository identified by the repositoryKey owned by the provided organization. | |||
*/ | |||
Optional<Repository> getRepository(String appUrl, AccessToken accessToken, String organization, String repositoryKey); | |||
Optional<Repository> getRepository(String appUrl, AccessToken accessToken, String repositoryKey); | |||
class Repositories { | |||
private int total; |
@@ -71,6 +71,7 @@ public class GithubApplicationClientImpl implements GithubApplicationClient { | |||
protected final GithubAppSecurity appSecurity; | |||
private final GitHubSettings gitHubSettings; | |||
private final GithubPaginatedHttpClient githubPaginatedHttpClient; | |||
public GithubApplicationClientImpl(GithubApplicationHttpClient appHttpClient, GithubAppSecurity appSecurity, GitHubSettings gitHubSettings, | |||
GithubPaginatedHttpClient githubPaginatedHttpClient) { | |||
this.appHttpClient = appHttpClient; | |||
@@ -84,6 +85,25 @@ public class GithubApplicationClientImpl implements GithubApplicationClient { | |||
checkArgument(pageSize > 0 && pageSize <= 100, "'pageSize' must be a value larger than 0 and smaller or equal to 100."); | |||
} | |||
@Override | |||
public Optional<AppInstallationToken> createAppInstallationToken(GithubAppConfiguration githubAppConfiguration, long installationId) { | |||
AppToken appToken = appSecurity.createAppToken(githubAppConfiguration.getId(), githubAppConfiguration.getPrivateKey()); | |||
String endPoint = "/app/installations/" + installationId + "/access_tokens"; | |||
return post(githubAppConfiguration.getApiEndpoint(), appToken, endPoint, GithubBinding.GsonInstallationToken.class) | |||
.map(GithubBinding.GsonInstallationToken::getToken) | |||
.filter(Objects::nonNull) | |||
.map(AppInstallationToken::new); | |||
} | |||
private <T> Optional<T> post(String baseUrl, AccessToken token, String endPoint, Class<T> gsonClass) { | |||
try { | |||
GithubApplicationHttpClient.Response response = appHttpClient.post(baseUrl, token, endPoint); | |||
return handleResponse(response, endPoint, gsonClass); | |||
} catch (Exception e) { | |||
LOG.warn(FAILED_TO_REQUEST_BEGIN_MSG + endPoint, e); | |||
return Optional.empty(); | |||
} | |||
} | |||
@Override | |||
public void checkApiEndpoint(GithubAppConfiguration githubAppConfiguration) { | |||
@@ -150,7 +170,8 @@ public class GithubApplicationClientImpl implements GithubApplicationClient { | |||
AppToken appToken = appSecurity.createAppToken(githubAppConfiguration.getId(), githubAppConfiguration.getPrivateKey()); | |||
String endpoint = String.format("/repos/%s/installation", repositorySlug); | |||
return get(githubAppConfiguration.getApiEndpoint(), appToken, endpoint, GithubBinding.GsonInstallation.class) | |||
.map(GithubBinding.GsonInstallation::getId); | |||
.map(GithubBinding.GsonInstallation::getId) | |||
.filter(installationId -> installationId != 0L); | |||
} | |||
@Override | |||
@@ -216,7 +237,7 @@ public class GithubApplicationClientImpl implements GithubApplicationClient { | |||
} 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."); | |||
+ "It might be related to your GitHub App configuration or a connectivity problem."); | |||
} | |||
} | |||
@@ -261,14 +282,17 @@ public class GithubApplicationClientImpl implements GithubApplicationClient { | |||
} | |||
@Override | |||
public Optional<Repository> getRepository(String appUrl, AccessToken accessToken, String organization, String repositoryKey) { | |||
public Optional<Repository> getRepository(String appUrl, AccessToken accessToken, String organizationAndRepository) { | |||
try { | |||
GetResponse response = appHttpClient.get(appUrl, accessToken, String.format("/repos/%s", repositoryKey)); | |||
return response.getContent() | |||
GetResponse response = appHttpClient.get(appUrl, accessToken, String.format("/repos/%s", organizationAndRepository)); | |||
return Optional.of(response) | |||
.filter(r -> r.getCode() == HTTP_OK) | |||
.flatMap(GithubApplicationHttpClient.Response::getContent) | |||
.map(content -> GSON.fromJson(content, GsonGithubRepository.class)) | |||
.map(GsonGithubRepository::toRepository); | |||
} catch (Exception e) { | |||
throw new IllegalStateException(format("Failed to get repository '%s' of '%s' accessible by user access token on '%s'", repositoryKey, organization, appUrl), 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); | |||
} | |||
} | |||
@@ -295,9 +319,9 @@ public class GithubApplicationClientImpl implements GithubApplicationClient { | |||
Optional<String> content = response.getContent(); | |||
Optional<UserAccessToken> accessToken = content.flatMap(c -> Arrays.stream(c.split("&")) | |||
.filter(t -> t.startsWith("access_token=")) | |||
.map(t -> t.split("=")[1]) | |||
.findAny()) | |||
.filter(t -> t.startsWith("access_token=")) | |||
.map(t -> t.split("=")[1]) | |||
.findAny()) | |||
.map(UserAccessToken::new); | |||
if (accessToken.isPresent()) { | |||
@@ -331,7 +355,6 @@ public class GithubApplicationClientImpl implements GithubApplicationClient { | |||
} | |||
} | |||
protected static <T> Optional<T> handleResponse(GithubApplicationHttpClient.Response response, String endPoint, Class<T> gsonClass) { | |||
try { | |||
return response.getContent().map(c -> GSON.fromJson(c, gsonClass)); |
@@ -460,4 +460,13 @@ public class GithubBinding { | |||
return name; | |||
} | |||
} | |||
public static class GsonInstallationToken { | |||
@SerializedName("token") | |||
String token; | |||
public String getToken() { | |||
return token; | |||
} | |||
} | |||
} |
@@ -0,0 +1,50 @@ | |||
/* | |||
* 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; | |||
import org.junit.Test; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
public class AppInstallationTokenTest { | |||
@Test | |||
public void test_value() { | |||
AppInstallationToken underTest = new AppInstallationToken("foo"); | |||
assertThat(underTest.toString()) | |||
.isEqualTo(underTest.getValue()) | |||
.isEqualTo("foo"); | |||
assertThat(underTest.getAuthorizationHeaderPrefix()).isEqualTo("Token"); | |||
} | |||
@Test | |||
public void test_equals_hashCode() { | |||
AppInstallationToken foo = new AppInstallationToken("foo"); | |||
assertThat(foo.equals(foo)).isTrue(); | |||
assertThat(foo.equals(null)).isFalse(); | |||
assertThat(foo.equals(new AppInstallationToken("foo"))).isTrue(); | |||
assertThat(foo.equals(new AppInstallationToken("bar"))).isFalse(); | |||
assertThat(foo.equals("foo")).isFalse(); | |||
assertThat(foo).hasSameHashCodeAs(new AppInstallationToken("foo")); | |||
} | |||
} |
@@ -32,6 +32,7 @@ import org.junit.Before; | |||
import org.junit.ClassRule; | |||
import org.junit.Test; | |||
import org.junit.runner.RunWith; | |||
import org.slf4j.event.Level; | |||
import org.sonar.alm.client.github.GithubApplicationHttpClient.RateLimit; | |||
import org.sonar.alm.client.github.config.GithubAppConfiguration; | |||
import org.sonar.alm.client.github.config.GithubAppInstallation; | |||
@@ -39,11 +40,13 @@ import org.sonar.alm.client.github.security.AccessToken; | |||
import org.sonar.alm.client.github.security.AppToken; | |||
import org.sonar.alm.client.github.security.GithubAppSecurity; | |||
import org.sonar.alm.client.github.security.UserAccessToken; | |||
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.sonarqube.ws.client.HttpException; | |||
import static java.net.HttpURLConnection.HTTP_CREATED; | |||
import static java.net.HttpURLConnection.HTTP_FORBIDDEN; | |||
import static java.net.HttpURLConnection.HTTP_NOT_FOUND; | |||
import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; | |||
@@ -53,6 +56,7 @@ import static org.assertj.core.api.Assertions.assertThatCode; | |||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | |||
import static org.assertj.core.groups.Tuple.tuple; | |||
import static org.mockito.ArgumentMatchers.any; | |||
import static org.mockito.ArgumentMatchers.anyString; | |||
import static org.mockito.ArgumentMatchers.eq; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.verify; | |||
@@ -62,6 +66,7 @@ import static org.sonar.alm.client.github.GithubApplicationHttpClient.GetRespons | |||
@RunWith(DataProviderRunner.class) | |||
public class GithubApplicationClientImplTest { | |||
private static final int INSTALLATION_ID = 1; | |||
private static final String APP_JWT_TOKEN = "APP_TOKEN_JWT"; | |||
private static final String PAYLOAD_2_ORGS = """ | |||
[ | |||
@@ -204,12 +209,12 @@ public class GithubApplicationClientImplTest { | |||
AppToken appToken = mockAppToken(); | |||
String json = "{" | |||
+ " \"permissions\": {\n" | |||
+ " \"checks\": \"read\",\n" | |||
+ " \"metadata\": \"read\",\n" | |||
+ " \"pull_requests\": \"read\"\n" | |||
+ " }\n" | |||
+ "}"; | |||
+ " \"permissions\": {\n" | |||
+ " \"checks\": \"read\",\n" | |||
+ " \"metadata\": \"read\",\n" | |||
+ " \"pull_requests\": \"read\"\n" | |||
+ " }\n" | |||
+ "}"; | |||
when(httpClient.get(appUrl, appToken, "/app")).thenReturn(new OkGetResponse(json)); | |||
@@ -223,12 +228,12 @@ public class GithubApplicationClientImplTest { | |||
AppToken appToken = mockAppToken(); | |||
String json = "{" | |||
+ " \"permissions\": {\n" | |||
+ " \"checks\": \"write\",\n" | |||
+ " \"metadata\": \"read\",\n" | |||
+ " \"pull_requests\": \"write\"\n" | |||
+ " }\n" | |||
+ "}"; | |||
+ " \"permissions\": {\n" | |||
+ " \"checks\": \"write\",\n" | |||
+ " \"metadata\": \"read\",\n" | |||
+ " \"pull_requests\": \"write\"\n" | |||
+ " }\n" | |||
+ "}"; | |||
when(httpClient.get(appUrl, appToken, "/app")).thenReturn(new OkGetResponse(json)); | |||
@@ -391,8 +396,8 @@ public class GithubApplicationClientImplTest { | |||
String appUrl = "https://github.sonarsource.com"; | |||
AccessToken accessToken = new UserAccessToken(randomAlphanumeric(10)); | |||
String responseJson = "{\n" | |||
+ " \"total_count\": 0\n" | |||
+ "} "; | |||
+ " \"total_count\": 0\n" | |||
+ "} "; | |||
when(httpClient.get(appUrl, accessToken, String.format("/user/installations?page=%s&per_page=%s", 1, 100))) | |||
.thenReturn(new OkGetResponse(responseJson)); | |||
@@ -408,82 +413,82 @@ public class GithubApplicationClientImplTest { | |||
String appUrl = "https://github.sonarsource.com"; | |||
AccessToken accessToken = new UserAccessToken(randomAlphanumeric(10)); | |||
String responseJson = "{\n" | |||
+ " \"total_count\": 2,\n" | |||
+ " \"installations\": [\n" | |||
+ " {\n" | |||
+ " \"id\": 1,\n" | |||
+ " \"account\": {\n" | |||
+ " \"login\": \"github\",\n" | |||
+ " \"id\": 1,\n" | |||
+ " \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjE=\",\n" | |||
+ " \"url\": \"https://github.sonarsource.com/api/v3/orgs/github\",\n" | |||
+ " \"repos_url\": \"https://github.sonarsource.com/api/v3/orgs/github/repos\",\n" | |||
+ " \"events_url\": \"https://github.sonarsource.com/api/v3/orgs/github/events\",\n" | |||
+ " \"hooks_url\": \"https://github.sonarsource.com/api/v3/orgs/github/hooks\",\n" | |||
+ " \"issues_url\": \"https://github.sonarsource.com/api/v3/orgs/github/issues\",\n" | |||
+ " \"members_url\": \"https://github.sonarsource.com/api/v3/orgs/github/members{/member}\",\n" | |||
+ " \"public_members_url\": \"https://github.sonarsource.com/api/v3/orgs/github/public_members{/member}\",\n" | |||
+ " \"avatar_url\": \"https://github.com/images/error/octocat_happy.gif\",\n" | |||
+ " \"description\": \"A great organization\"\n" | |||
+ " },\n" | |||
+ " \"access_tokens_url\": \"https://github.sonarsource.com/api/v3/app/installations/1/access_tokens\",\n" | |||
+ " \"repositories_url\": \"https://github.sonarsource.com/api/v3/installation/repositories\",\n" | |||
+ " \"html_url\": \"https://github.com/organizations/github/settings/installations/1\",\n" | |||
+ " \"app_id\": 1,\n" | |||
+ " \"target_id\": 1,\n" | |||
+ " \"target_type\": \"Organization\",\n" | |||
+ " \"permissions\": {\n" | |||
+ " \"checks\": \"write\",\n" | |||
+ " \"metadata\": \"read\",\n" | |||
+ " \"contents\": \"read\"\n" | |||
+ " },\n" | |||
+ " \"events\": [\n" | |||
+ " \"push\",\n" | |||
+ " \"pull_request\"\n" | |||
+ " ],\n" | |||
+ " \"single_file_name\": \"config.yml\"\n" | |||
+ " },\n" | |||
+ " {\n" | |||
+ " \"id\": 3,\n" | |||
+ " \"account\": {\n" | |||
+ " \"login\": \"octocat\",\n" | |||
+ " \"id\": 2,\n" | |||
+ " \"node_id\": \"MDQ6VXNlcjE=\",\n" | |||
+ " \"avatar_url\": \"https://github.com/images/error/octocat_happy.gif\",\n" | |||
+ " \"gravatar_id\": \"\",\n" | |||
+ " \"url\": \"https://github.sonarsource.com/api/v3/users/octocat\",\n" | |||
+ " \"html_url\": \"https://github.com/octocat\",\n" | |||
+ " \"followers_url\": \"https://github.sonarsource.com/api/v3/users/octocat/followers\",\n" | |||
+ " \"following_url\": \"https://github.sonarsource.com/api/v3/users/octocat/following{/other_user}\",\n" | |||
+ " \"gists_url\": \"https://github.sonarsource.com/api/v3/users/octocat/gists{/gist_id}\",\n" | |||
+ " \"starred_url\": \"https://github.sonarsource.com/api/v3/users/octocat/starred{/owner}{/repo}\",\n" | |||
+ " \"subscriptions_url\": \"https://github.sonarsource.com/api/v3/users/octocat/subscriptions\",\n" | |||
+ " \"organizations_url\": \"https://github.sonarsource.com/api/v3/users/octocat/orgs\",\n" | |||
+ " \"repos_url\": \"https://github.sonarsource.com/api/v3/users/octocat/repos\",\n" | |||
+ " \"events_url\": \"https://github.sonarsource.com/api/v3/users/octocat/events{/privacy}\",\n" | |||
+ " \"received_events_url\": \"https://github.sonarsource.com/api/v3/users/octocat/received_events\",\n" | |||
+ " \"type\": \"User\",\n" | |||
+ " \"site_admin\": false\n" | |||
+ " },\n" | |||
+ " \"access_tokens_url\": \"https://github.sonarsource.com/api/v3/app/installations/1/access_tokens\",\n" | |||
+ " \"repositories_url\": \"https://github.sonarsource.com/api/v3/installation/repositories\",\n" | |||
+ " \"html_url\": \"https://github.com/organizations/github/settings/installations/1\",\n" | |||
+ " \"app_id\": 1,\n" | |||
+ " \"target_id\": 1,\n" | |||
+ " \"target_type\": \"Organization\",\n" | |||
+ " \"permissions\": {\n" | |||
+ " \"checks\": \"write\",\n" | |||
+ " \"metadata\": \"read\",\n" | |||
+ " \"contents\": \"read\"\n" | |||
+ " },\n" | |||
+ " \"events\": [\n" | |||
+ " \"push\",\n" | |||
+ " \"pull_request\"\n" | |||
+ " ],\n" | |||
+ " \"single_file_name\": \"config.yml\"\n" | |||
+ " }\n" | |||
+ " ]\n" | |||
+ "} "; | |||
+ " \"total_count\": 2,\n" | |||
+ " \"installations\": [\n" | |||
+ " {\n" | |||
+ " \"id\": 1,\n" | |||
+ " \"account\": {\n" | |||
+ " \"login\": \"github\",\n" | |||
+ " \"id\": 1,\n" | |||
+ " \"node_id\": \"MDEyOk9yZ2FuaXphdGlvbjE=\",\n" | |||
+ " \"url\": \"https://github.sonarsource.com/api/v3/orgs/github\",\n" | |||
+ " \"repos_url\": \"https://github.sonarsource.com/api/v3/orgs/github/repos\",\n" | |||
+ " \"events_url\": \"https://github.sonarsource.com/api/v3/orgs/github/events\",\n" | |||
+ " \"hooks_url\": \"https://github.sonarsource.com/api/v3/orgs/github/hooks\",\n" | |||
+ " \"issues_url\": \"https://github.sonarsource.com/api/v3/orgs/github/issues\",\n" | |||
+ " \"members_url\": \"https://github.sonarsource.com/api/v3/orgs/github/members{/member}\",\n" | |||
+ " \"public_members_url\": \"https://github.sonarsource.com/api/v3/orgs/github/public_members{/member}\",\n" | |||
+ " \"avatar_url\": \"https://github.com/images/error/octocat_happy.gif\",\n" | |||
+ " \"description\": \"A great organization\"\n" | |||
+ " },\n" | |||
+ " \"access_tokens_url\": \"https://github.sonarsource.com/api/v3/app/installations/1/access_tokens\",\n" | |||
+ " \"repositories_url\": \"https://github.sonarsource.com/api/v3/installation/repositories\",\n" | |||
+ " \"html_url\": \"https://github.com/organizations/github/settings/installations/1\",\n" | |||
+ " \"app_id\": 1,\n" | |||
+ " \"target_id\": 1,\n" | |||
+ " \"target_type\": \"Organization\",\n" | |||
+ " \"permissions\": {\n" | |||
+ " \"checks\": \"write\",\n" | |||
+ " \"metadata\": \"read\",\n" | |||
+ " \"contents\": \"read\"\n" | |||
+ " },\n" | |||
+ " \"events\": [\n" | |||
+ " \"push\",\n" | |||
+ " \"pull_request\"\n" | |||
+ " ],\n" | |||
+ " \"single_file_name\": \"config.yml\"\n" | |||
+ " },\n" | |||
+ " {\n" | |||
+ " \"id\": 3,\n" | |||
+ " \"account\": {\n" | |||
+ " \"login\": \"octocat\",\n" | |||
+ " \"id\": 2,\n" | |||
+ " \"node_id\": \"MDQ6VXNlcjE=\",\n" | |||
+ " \"avatar_url\": \"https://github.com/images/error/octocat_happy.gif\",\n" | |||
+ " \"gravatar_id\": \"\",\n" | |||
+ " \"url\": \"https://github.sonarsource.com/api/v3/users/octocat\",\n" | |||
+ " \"html_url\": \"https://github.com/octocat\",\n" | |||
+ " \"followers_url\": \"https://github.sonarsource.com/api/v3/users/octocat/followers\",\n" | |||
+ " \"following_url\": \"https://github.sonarsource.com/api/v3/users/octocat/following{/other_user}\",\n" | |||
+ " \"gists_url\": \"https://github.sonarsource.com/api/v3/users/octocat/gists{/gist_id}\",\n" | |||
+ " \"starred_url\": \"https://github.sonarsource.com/api/v3/users/octocat/starred{/owner}{/repo}\",\n" | |||
+ " \"subscriptions_url\": \"https://github.sonarsource.com/api/v3/users/octocat/subscriptions\",\n" | |||
+ " \"organizations_url\": \"https://github.sonarsource.com/api/v3/users/octocat/orgs\",\n" | |||
+ " \"repos_url\": \"https://github.sonarsource.com/api/v3/users/octocat/repos\",\n" | |||
+ " \"events_url\": \"https://github.sonarsource.com/api/v3/users/octocat/events{/privacy}\",\n" | |||
+ " \"received_events_url\": \"https://github.sonarsource.com/api/v3/users/octocat/received_events\",\n" | |||
+ " \"type\": \"User\",\n" | |||
+ " \"site_admin\": false\n" | |||
+ " },\n" | |||
+ " \"access_tokens_url\": \"https://github.sonarsource.com/api/v3/app/installations/1/access_tokens\",\n" | |||
+ " \"repositories_url\": \"https://github.sonarsource.com/api/v3/installation/repositories\",\n" | |||
+ " \"html_url\": \"https://github.com/organizations/github/settings/installations/1\",\n" | |||
+ " \"app_id\": 1,\n" | |||
+ " \"target_id\": 1,\n" | |||
+ " \"target_type\": \"Organization\",\n" | |||
+ " \"permissions\": {\n" | |||
+ " \"checks\": \"write\",\n" | |||
+ " \"metadata\": \"read\",\n" | |||
+ " \"contents\": \"read\"\n" | |||
+ " },\n" | |||
+ " \"events\": [\n" | |||
+ " \"push\",\n" | |||
+ " \"pull_request\"\n" | |||
+ " ],\n" | |||
+ " \"single_file_name\": \"config.yml\"\n" | |||
+ " }\n" | |||
+ " ]\n" | |||
+ "} "; | |||
when(httpClient.get(appUrl, accessToken, String.format("/user/installations?page=%s&per_page=%s", 1, 100))) | |||
.thenReturn(new OkGetResponse(responseJson)); | |||
@@ -565,12 +570,12 @@ public class GithubApplicationClientImplTest { | |||
public void getWhitelistedGithubAppInstallations_whenGithubReturnsError_shouldThrow() throws IOException { | |||
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 IOException("io exception")); | |||
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."); | |||
+ "It might be related to your GitHub App configuration or a connectivity problem."); | |||
} | |||
@Test | |||
@@ -610,8 +615,8 @@ public class GithubApplicationClientImplTest { | |||
String appUrl = "https://github.sonarsource.com"; | |||
AccessToken accessToken = new UserAccessToken(randomAlphanumeric(10)); | |||
String responseJson = "{\n" | |||
+ " \"total_count\": 0\n" | |||
+ "}"; | |||
+ " \"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))) | |||
.thenReturn(new OkGetResponse(responseJson)); | |||
@@ -627,79 +632,79 @@ public class GithubApplicationClientImplTest { | |||
String appUrl = "https://github.sonarsource.com"; | |||
AccessToken accessToken = new UserAccessToken(randomAlphanumeric(10)); | |||
String responseJson = "{\n" | |||
+ " \"total_count\": 2,\n" | |||
+ " \"incomplete_results\": false,\n" | |||
+ " \"items\": [\n" | |||
+ " {\n" | |||
+ " \"id\": 3081286,\n" | |||
+ " \"node_id\": \"MDEwOlJlcG9zaXRvcnkzMDgxMjg2\",\n" | |||
+ " \"name\": \"HelloWorld\",\n" | |||
+ " \"full_name\": \"github/HelloWorld\",\n" | |||
+ " \"owner\": {\n" | |||
+ " \"login\": \"github\",\n" | |||
+ " \"id\": 872147,\n" | |||
+ " \"node_id\": \"MDQ6VXNlcjg3MjE0Nw==\",\n" | |||
+ " \"avatar_url\": \"https://github.sonarsource.com/images/error/octocat_happy.gif\",\n" | |||
+ " \"gravatar_id\": \"\",\n" | |||
+ " \"url\": \"https://github.sonarsource.com/api/v3/users/github\",\n" | |||
+ " \"received_events_url\": \"https://github.sonarsource.com/api/v3/users/github/received_events\",\n" | |||
+ " \"type\": \"User\"\n" | |||
+ " },\n" | |||
+ " \"private\": false,\n" | |||
+ " \"html_url\": \"https://github.com/github/HelloWorld\",\n" | |||
+ " \"description\": \"A C implementation of HelloWorld\",\n" | |||
+ " \"fork\": false,\n" | |||
+ " \"url\": \"https://github.sonarsource.com/api/v3/repos/github/HelloWorld\",\n" | |||
+ " \"created_at\": \"2012-01-01T00:31:50Z\",\n" | |||
+ " \"updated_at\": \"2013-01-05T17:58:47Z\",\n" | |||
+ " \"pushed_at\": \"2012-01-01T00:37:02Z\",\n" | |||
+ " \"homepage\": \"\",\n" | |||
+ " \"size\": 524,\n" | |||
+ " \"stargazers_count\": 1,\n" | |||
+ " \"watchers_count\": 1,\n" | |||
+ " \"language\": \"Assembly\",\n" | |||
+ " \"forks_count\": 0,\n" | |||
+ " \"open_issues_count\": 0,\n" | |||
+ " \"master_branch\": \"master\",\n" | |||
+ " \"default_branch\": \"master\",\n" | |||
+ " \"score\": 1.0\n" | |||
+ " },\n" | |||
+ " {\n" | |||
+ " \"id\": 3081286,\n" | |||
+ " \"node_id\": \"MDEwOlJlcG9zaXRvcnkzMDgxMjg2\",\n" | |||
+ " \"name\": \"HelloUniverse\",\n" | |||
+ " \"full_name\": \"github/HelloUniverse\",\n" | |||
+ " \"owner\": {\n" | |||
+ " \"login\": \"github\",\n" | |||
+ " \"id\": 872147,\n" | |||
+ " \"node_id\": \"MDQ6VXNlcjg3MjE0Nw==\",\n" | |||
+ " \"avatar_url\": \"https://github.sonarsource.com/images/error/octocat_happy.gif\",\n" | |||
+ " \"gravatar_id\": \"\",\n" | |||
+ " \"url\": \"https://github.sonarsource.com/api/v3/users/github\",\n" | |||
+ " \"received_events_url\": \"https://github.sonarsource.com/api/v3/users/github/received_events\",\n" | |||
+ " \"type\": \"User\"\n" | |||
+ " },\n" | |||
+ " \"private\": false,\n" | |||
+ " \"html_url\": \"https://github.com/github/HelloUniverse\",\n" | |||
+ " \"description\": \"A C implementation of HelloUniverse\",\n" | |||
+ " \"fork\": false,\n" | |||
+ " \"url\": \"https://github.sonarsource.com/api/v3/repos/github/HelloUniverse\",\n" | |||
+ " \"created_at\": \"2012-01-01T00:31:50Z\",\n" | |||
+ " \"updated_at\": \"2013-01-05T17:58:47Z\",\n" | |||
+ " \"pushed_at\": \"2012-01-01T00:37:02Z\",\n" | |||
+ " \"homepage\": \"\",\n" | |||
+ " \"size\": 524,\n" | |||
+ " \"stargazers_count\": 1,\n" | |||
+ " \"watchers_count\": 1,\n" | |||
+ " \"language\": \"Assembly\",\n" | |||
+ " \"forks_count\": 0,\n" | |||
+ " \"open_issues_count\": 0,\n" | |||
+ " \"master_branch\": \"master\",\n" | |||
+ " \"default_branch\": \"master\",\n" | |||
+ " \"score\": 1.0\n" | |||
+ " }\n" | |||
+ " ]\n" | |||
+ "}"; | |||
+ " \"total_count\": 2,\n" | |||
+ " \"incomplete_results\": false,\n" | |||
+ " \"items\": [\n" | |||
+ " {\n" | |||
+ " \"id\": 3081286,\n" | |||
+ " \"node_id\": \"MDEwOlJlcG9zaXRvcnkzMDgxMjg2\",\n" | |||
+ " \"name\": \"HelloWorld\",\n" | |||
+ " \"full_name\": \"github/HelloWorld\",\n" | |||
+ " \"owner\": {\n" | |||
+ " \"login\": \"github\",\n" | |||
+ " \"id\": 872147,\n" | |||
+ " \"node_id\": \"MDQ6VXNlcjg3MjE0Nw==\",\n" | |||
+ " \"avatar_url\": \"https://github.sonarsource.com/images/error/octocat_happy.gif\",\n" | |||
+ " \"gravatar_id\": \"\",\n" | |||
+ " \"url\": \"https://github.sonarsource.com/api/v3/users/github\",\n" | |||
+ " \"received_events_url\": \"https://github.sonarsource.com/api/v3/users/github/received_events\",\n" | |||
+ " \"type\": \"User\"\n" | |||
+ " },\n" | |||
+ " \"private\": false,\n" | |||
+ " \"html_url\": \"https://github.com/github/HelloWorld\",\n" | |||
+ " \"description\": \"A C implementation of HelloWorld\",\n" | |||
+ " \"fork\": false,\n" | |||
+ " \"url\": \"https://github.sonarsource.com/api/v3/repos/github/HelloWorld\",\n" | |||
+ " \"created_at\": \"2012-01-01T00:31:50Z\",\n" | |||
+ " \"updated_at\": \"2013-01-05T17:58:47Z\",\n" | |||
+ " \"pushed_at\": \"2012-01-01T00:37:02Z\",\n" | |||
+ " \"homepage\": \"\",\n" | |||
+ " \"size\": 524,\n" | |||
+ " \"stargazers_count\": 1,\n" | |||
+ " \"watchers_count\": 1,\n" | |||
+ " \"language\": \"Assembly\",\n" | |||
+ " \"forks_count\": 0,\n" | |||
+ " \"open_issues_count\": 0,\n" | |||
+ " \"master_branch\": \"master\",\n" | |||
+ " \"default_branch\": \"master\",\n" | |||
+ " \"score\": 1.0\n" | |||
+ " },\n" | |||
+ " {\n" | |||
+ " \"id\": 3081286,\n" | |||
+ " \"node_id\": \"MDEwOlJlcG9zaXRvcnkzMDgxMjg2\",\n" | |||
+ " \"name\": \"HelloUniverse\",\n" | |||
+ " \"full_name\": \"github/HelloUniverse\",\n" | |||
+ " \"owner\": {\n" | |||
+ " \"login\": \"github\",\n" | |||
+ " \"id\": 872147,\n" | |||
+ " \"node_id\": \"MDQ6VXNlcjg3MjE0Nw==\",\n" | |||
+ " \"avatar_url\": \"https://github.sonarsource.com/images/error/octocat_happy.gif\",\n" | |||
+ " \"gravatar_id\": \"\",\n" | |||
+ " \"url\": \"https://github.sonarsource.com/api/v3/users/github\",\n" | |||
+ " \"received_events_url\": \"https://github.sonarsource.com/api/v3/users/github/received_events\",\n" | |||
+ " \"type\": \"User\"\n" | |||
+ " },\n" | |||
+ " \"private\": false,\n" | |||
+ " \"html_url\": \"https://github.com/github/HelloUniverse\",\n" | |||
+ " \"description\": \"A C implementation of HelloUniverse\",\n" | |||
+ " \"fork\": false,\n" | |||
+ " \"url\": \"https://github.sonarsource.com/api/v3/repos/github/HelloUniverse\",\n" | |||
+ " \"created_at\": \"2012-01-01T00:31:50Z\",\n" | |||
+ " \"updated_at\": \"2013-01-05T17:58:47Z\",\n" | |||
+ " \"pushed_at\": \"2012-01-01T00:37:02Z\",\n" | |||
+ " \"homepage\": \"\",\n" | |||
+ " \"size\": 524,\n" | |||
+ " \"stargazers_count\": 1,\n" | |||
+ " \"watchers_count\": 1,\n" | |||
+ " \"language\": \"Assembly\",\n" | |||
+ " \"forks_count\": 0,\n" | |||
+ " \"open_issues_count\": 0,\n" | |||
+ " \"master_branch\": \"master\",\n" | |||
+ " \"default_branch\": \"master\",\n" | |||
+ " \"score\": 1.0\n" | |||
+ " }\n" | |||
+ " ]\n" | |||
+ "}"; | |||
when(httpClient.get(appUrl, accessToken, String.format("/search/repositories?q=%s&page=%s&per_page=%s", "fork:true+org:github", 1, 100))) | |||
.thenReturn(new OkGetResponse(responseJson)); | |||
@@ -716,45 +721,45 @@ public class GithubApplicationClientImplTest { | |||
String appUrl = "https://github.sonarsource.com"; | |||
AccessToken accessToken = new UserAccessToken(randomAlphanumeric(10)); | |||
String responseJson = "{\n" | |||
+ " \"total_count\": 2,\n" | |||
+ " \"incomplete_results\": false,\n" | |||
+ " \"items\": [\n" | |||
+ " {\n" | |||
+ " \"id\": 3081286,\n" | |||
+ " \"node_id\": \"MDEwOlJlcG9zaXRvcnkzMDgxMjg2\",\n" | |||
+ " \"name\": \"HelloWorld\",\n" | |||
+ " \"full_name\": \"github/HelloWorld\",\n" | |||
+ " \"owner\": {\n" | |||
+ " \"login\": \"github\",\n" | |||
+ " \"id\": 872147,\n" | |||
+ " \"node_id\": \"MDQ6VXNlcjg3MjE0Nw==\",\n" | |||
+ " \"avatar_url\": \"https://github.sonarsource.com/images/error/octocat_happy.gif\",\n" | |||
+ " \"gravatar_id\": \"\",\n" | |||
+ " \"url\": \"https://github.sonarsource.com/api/v3/users/github\",\n" | |||
+ " \"received_events_url\": \"https://github.sonarsource.com/api/v3/users/github/received_events\",\n" | |||
+ " \"type\": \"User\"\n" | |||
+ " },\n" | |||
+ " \"private\": false,\n" | |||
+ " \"html_url\": \"https://github.com/github/HelloWorld\",\n" | |||
+ " \"description\": \"A C implementation of HelloWorld\",\n" | |||
+ " \"fork\": false,\n" | |||
+ " \"url\": \"https://github.sonarsource.com/api/v3/repos/github/HelloWorld\",\n" | |||
+ " \"created_at\": \"2012-01-01T00:31:50Z\",\n" | |||
+ " \"updated_at\": \"2013-01-05T17:58:47Z\",\n" | |||
+ " \"pushed_at\": \"2012-01-01T00:37:02Z\",\n" | |||
+ " \"homepage\": \"\",\n" | |||
+ " \"size\": 524,\n" | |||
+ " \"stargazers_count\": 1,\n" | |||
+ " \"watchers_count\": 1,\n" | |||
+ " \"language\": \"Assembly\",\n" | |||
+ " \"forks_count\": 0,\n" | |||
+ " \"open_issues_count\": 0,\n" | |||
+ " \"master_branch\": \"master\",\n" | |||
+ " \"default_branch\": \"master\",\n" | |||
+ " \"score\": 1.0\n" | |||
+ " }\n" | |||
+ " ]\n" | |||
+ "}"; | |||
+ " \"total_count\": 2,\n" | |||
+ " \"incomplete_results\": false,\n" | |||
+ " \"items\": [\n" | |||
+ " {\n" | |||
+ " \"id\": 3081286,\n" | |||
+ " \"node_id\": \"MDEwOlJlcG9zaXRvcnkzMDgxMjg2\",\n" | |||
+ " \"name\": \"HelloWorld\",\n" | |||
+ " \"full_name\": \"github/HelloWorld\",\n" | |||
+ " \"owner\": {\n" | |||
+ " \"login\": \"github\",\n" | |||
+ " \"id\": 872147,\n" | |||
+ " \"node_id\": \"MDQ6VXNlcjg3MjE0Nw==\",\n" | |||
+ " \"avatar_url\": \"https://github.sonarsource.com/images/error/octocat_happy.gif\",\n" | |||
+ " \"gravatar_id\": \"\",\n" | |||
+ " \"url\": \"https://github.sonarsource.com/api/v3/users/github\",\n" | |||
+ " \"received_events_url\": \"https://github.sonarsource.com/api/v3/users/github/received_events\",\n" | |||
+ " \"type\": \"User\"\n" | |||
+ " },\n" | |||
+ " \"private\": false,\n" | |||
+ " \"html_url\": \"https://github.com/github/HelloWorld\",\n" | |||
+ " \"description\": \"A C implementation of HelloWorld\",\n" | |||
+ " \"fork\": false,\n" | |||
+ " \"url\": \"https://github.sonarsource.com/api/v3/repos/github/HelloWorld\",\n" | |||
+ " \"created_at\": \"2012-01-01T00:31:50Z\",\n" | |||
+ " \"updated_at\": \"2013-01-05T17:58:47Z\",\n" | |||
+ " \"pushed_at\": \"2012-01-01T00:37:02Z\",\n" | |||
+ " \"homepage\": \"\",\n" | |||
+ " \"size\": 524,\n" | |||
+ " \"stargazers_count\": 1,\n" | |||
+ " \"watchers_count\": 1,\n" | |||
+ " \"language\": \"Assembly\",\n" | |||
+ " \"forks_count\": 0,\n" | |||
+ " \"open_issues_count\": 0,\n" | |||
+ " \"master_branch\": \"master\",\n" | |||
+ " \"default_branch\": \"master\",\n" | |||
+ " \"score\": 1.0\n" | |||
+ " }\n" | |||
+ " ]\n" | |||
+ "}"; | |||
when(httpClient.get(appUrl, accessToken, String.format("/search/repositories?q=%s&page=%s&per_page=%s", "world+fork:true+org:github", 1, 100))) | |||
.thenReturn(new GetResponse() { | |||
@@ -792,7 +797,7 @@ public class GithubApplicationClientImplTest { | |||
when(httpClient.get(any(), any(), any())) | |||
.thenReturn(new Response(404, null)); | |||
Optional<GithubApplicationClient.Repository> repository = underTest.getRepository(appUrl, new UserAccessToken("temp"), "octocat", "octocat/Hello-World"); | |||
Optional<GithubApplicationClient.Repository> repository = underTest.getRepository(appUrl, new UserAccessToken("temp"), "octocat/Hello-World"); | |||
assertThat(repository).isEmpty(); | |||
} | |||
@@ -800,15 +805,14 @@ public class GithubApplicationClientImplTest { | |||
@Test | |||
public void getRepository_fails_on_failure() throws IOException { | |||
String repositoryKey = "octocat/Hello-World"; | |||
String organization = "octocat"; | |||
when(httpClient.get(any(), any(), any())) | |||
.thenThrow(new IOException("OOPS")); | |||
UserAccessToken token = new UserAccessToken("temp"); | |||
assertThatThrownBy(() -> underTest.getRepository(appUrl, token, organization, repositoryKey)) | |||
assertThatThrownBy(() -> underTest.getRepository(appUrl, token, repositoryKey)) | |||
.isInstanceOf(IllegalStateException.class) | |||
.hasMessage("Failed to get repository '%s' of '%s' accessible by user access token on '%s'", repositoryKey, organization, appUrl); | |||
.hasMessage("Failed to get repository 'octocat/Hello-World' on 'Any URL' (this might be related to the GitHub App installation scope)"); | |||
} | |||
@Test | |||
@@ -816,142 +820,142 @@ public class GithubApplicationClientImplTest { | |||
String appUrl = "https://github.sonarsource.com"; | |||
AccessToken accessToken = new UserAccessToken(randomAlphanumeric(10)); | |||
String responseJson = "{\n" | |||
+ " \"id\": 1296269,\n" | |||
+ " \"node_id\": \"MDEwOlJlcG9zaXRvcnkxMjk2MjY5\",\n" | |||
+ " \"name\": \"Hello-World\",\n" | |||
+ " \"full_name\": \"octocat/Hello-World\",\n" | |||
+ " \"owner\": {\n" | |||
+ " \"login\": \"octocat\",\n" | |||
+ " \"id\": 1,\n" | |||
+ " \"node_id\": \"MDQ6VXNlcjE=\",\n" | |||
+ " \"avatar_url\": \"https://github.sonarsource.com/images/error/octocat_happy.gif\",\n" | |||
+ " \"gravatar_id\": \"\",\n" | |||
+ " \"url\": \"https://github.sonarsource.com/api/v3/users/octocat\",\n" | |||
+ " \"html_url\": \"https://github.com/octocat\",\n" | |||
+ " \"followers_url\": \"https://github.sonarsource.com/api/v3/users/octocat/followers\",\n" | |||
+ " \"following_url\": \"https://github.sonarsource.com/api/v3/users/octocat/following{/other_user}\",\n" | |||
+ " \"gists_url\": \"https://github.sonarsource.com/api/v3/users/octocat/gists{/gist_id}\",\n" | |||
+ " \"starred_url\": \"https://github.sonarsource.com/api/v3/users/octocat/starred{/owner}{/repo}\",\n" | |||
+ " \"subscriptions_url\": \"https://github.sonarsource.com/api/v3/users/octocat/subscriptions\",\n" | |||
+ " \"organizations_url\": \"https://github.sonarsource.com/api/v3/users/octocat/orgs\",\n" | |||
+ " \"repos_url\": \"https://github.sonarsource.com/api/v3/users/octocat/repos\",\n" | |||
+ " \"events_url\": \"https://github.sonarsource.com/api/v3/users/octocat/events{/privacy}\",\n" | |||
+ " \"received_events_url\": \"https://github.sonarsource.com/api/v3/users/octocat/received_events\",\n" | |||
+ " \"type\": \"User\",\n" | |||
+ " \"site_admin\": false\n" | |||
+ " },\n" | |||
+ " \"private\": false,\n" | |||
+ " \"html_url\": \"https://github.com/octocat/Hello-World\",\n" | |||
+ " \"description\": \"This your first repo!\",\n" | |||
+ " \"fork\": false,\n" | |||
+ " \"url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World\",\n" | |||
+ " \"archive_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/{archive_format}{/ref}\",\n" | |||
+ " \"assignees_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/assignees{/user}\",\n" | |||
+ " \"blobs_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/git/blobs{/sha}\",\n" | |||
+ " \"branches_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/branches{/branch}\",\n" | |||
+ " \"collaborators_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/collaborators{/collaborator}\",\n" | |||
+ " \"comments_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/comments{/number}\",\n" | |||
+ " \"commits_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/commits{/sha}\",\n" | |||
+ " \"compare_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/compare/{base}...{head}\",\n" | |||
+ " \"contents_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/contents/{+path}\",\n" | |||
+ " \"contributors_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/contributors\",\n" | |||
+ " \"deployments_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/deployments\",\n" | |||
+ " \"downloads_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/downloads\",\n" | |||
+ " \"events_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/events\",\n" | |||
+ " \"forks_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/forks\",\n" | |||
+ " \"git_commits_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/git/commits{/sha}\",\n" | |||
+ " \"git_refs_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/git/refs{/sha}\",\n" | |||
+ " \"git_tags_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/git/tags{/sha}\",\n" | |||
+ " \"git_url\": \"git:github.com/octocat/Hello-World.git\",\n" | |||
+ " \"issue_comment_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/issues/comments{/number}\",\n" | |||
+ " \"issue_events_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/issues/events{/number}\",\n" | |||
+ " \"issues_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/issues{/number}\",\n" | |||
+ " \"keys_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/keys{/key_id}\",\n" | |||
+ " \"labels_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/labels{/name}\",\n" | |||
+ " \"languages_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/languages\",\n" | |||
+ " \"merges_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/merges\",\n" | |||
+ " \"milestones_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/milestones{/number}\",\n" | |||
+ " \"notifications_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/notifications{?since,all,participating}\",\n" | |||
+ " \"pulls_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/pulls{/number}\",\n" | |||
+ " \"releases_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/releases{/id}\",\n" | |||
+ " \"ssh_url\": \"git@github.com:octocat/Hello-World.git\",\n" | |||
+ " \"stargazers_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/stargazers\",\n" | |||
+ " \"statuses_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/statuses/{sha}\",\n" | |||
+ " \"subscribers_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/subscribers\",\n" | |||
+ " \"subscription_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/subscription\",\n" | |||
+ " \"tags_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/tags\",\n" | |||
+ " \"teams_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/teams\",\n" | |||
+ " \"trees_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/git/trees{/sha}\",\n" | |||
+ " \"clone_url\": \"https://github.com/octocat/Hello-World.git\",\n" | |||
+ " \"mirror_url\": \"git:git.example.com/octocat/Hello-World\",\n" | |||
+ " \"hooks_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/hooks\",\n" | |||
+ " \"svn_url\": \"https://svn.github.com/octocat/Hello-World\",\n" | |||
+ " \"homepage\": \"https://github.com\",\n" | |||
+ " \"language\": null,\n" | |||
+ " \"forks_count\": 9,\n" | |||
+ " \"stargazers_count\": 80,\n" | |||
+ " \"watchers_count\": 80,\n" | |||
+ " \"size\": 108,\n" | |||
+ " \"default_branch\": \"master\",\n" | |||
+ " \"open_issues_count\": 0,\n" | |||
+ " \"is_template\": true,\n" | |||
+ " \"topics\": [\n" | |||
+ " \"octocat\",\n" | |||
+ " \"atom\",\n" | |||
+ " \"electron\",\n" | |||
+ " \"api\"\n" | |||
+ " ],\n" | |||
+ " \"has_issues\": true,\n" | |||
+ " \"has_projects\": true,\n" | |||
+ " \"has_wiki\": true,\n" | |||
+ " \"has_pages\": false,\n" | |||
+ " \"has_downloads\": true,\n" | |||
+ " \"archived\": false,\n" | |||
+ " \"disabled\": false,\n" | |||
+ " \"visibility\": \"public\",\n" | |||
+ " \"pushed_at\": \"2011-01-26T19:06:43Z\",\n" | |||
+ " \"created_at\": \"2011-01-26T19:01:12Z\",\n" | |||
+ " \"updated_at\": \"2011-01-26T19:14:43Z\",\n" | |||
+ " \"permissions\": {\n" | |||
+ " \"admin\": false,\n" | |||
+ " \"push\": false,\n" | |||
+ " \"pull\": true\n" | |||
+ " },\n" | |||
+ " \"allow_rebase_merge\": true,\n" | |||
+ " \"template_repository\": null,\n" | |||
+ " \"allow_squash_merge\": true,\n" | |||
+ " \"allow_merge_commit\": true,\n" | |||
+ " \"subscribers_count\": 42,\n" | |||
+ " \"network_count\": 0,\n" | |||
+ " \"anonymous_access_enabled\": false,\n" | |||
+ " \"license\": {\n" | |||
+ " \"key\": \"mit\",\n" | |||
+ " \"name\": \"MIT License\",\n" | |||
+ " \"spdx_id\": \"MIT\",\n" | |||
+ " \"url\": \"https://github.sonarsource.com/api/v3/licenses/mit\",\n" | |||
+ " \"node_id\": \"MDc6TGljZW5zZW1pdA==\"\n" | |||
+ " },\n" | |||
+ " \"organization\": {\n" | |||
+ " \"login\": \"octocat\",\n" | |||
+ " \"id\": 1,\n" | |||
+ " \"node_id\": \"MDQ6VXNlcjE=\",\n" | |||
+ " \"avatar_url\": \"https://github.com/images/error/octocat_happy.gif\",\n" | |||
+ " \"gravatar_id\": \"\",\n" | |||
+ " \"url\": \"https://github.sonarsource.com/api/v3/users/octocat\",\n" | |||
+ " \"html_url\": \"https://github.com/octocat\",\n" | |||
+ " \"followers_url\": \"https://github.sonarsource.com/api/v3/users/octocat/followers\",\n" | |||
+ " \"following_url\": \"https://github.sonarsource.com/api/v3/users/octocat/following{/other_user}\",\n" | |||
+ " \"gists_url\": \"https://github.sonarsource.com/api/v3/users/octocat/gists{/gist_id}\",\n" | |||
+ " \"starred_url\": \"https://github.sonarsource.com/api/v3/users/octocat/starred{/owner}{/repo}\",\n" | |||
+ " \"subscriptions_url\": \"https://github.sonarsource.com/api/v3/users/octocat/subscriptions\",\n" | |||
+ " \"organizations_url\": \"https://github.sonarsource.com/api/v3/users/octocat/orgs\",\n" | |||
+ " \"repos_url\": \"https://github.sonarsource.com/api/v3/users/octocat/repos\",\n" | |||
+ " \"events_url\": \"https://github.sonarsource.com/api/v3/users/octocat/events{/privacy}\",\n" | |||
+ " \"received_events_url\": \"https://github.sonarsource.com/api/v3/users/octocat/received_events\",\n" | |||
+ " \"type\": \"Organization\",\n" | |||
+ " \"site_admin\": false\n" | |||
+ " }" | |||
+ "}"; | |||
+ " \"id\": 1296269,\n" | |||
+ " \"node_id\": \"MDEwOlJlcG9zaXRvcnkxMjk2MjY5\",\n" | |||
+ " \"name\": \"Hello-World\",\n" | |||
+ " \"full_name\": \"octocat/Hello-World\",\n" | |||
+ " \"owner\": {\n" | |||
+ " \"login\": \"octocat\",\n" | |||
+ " \"id\": 1,\n" | |||
+ " \"node_id\": \"MDQ6VXNlcjE=\",\n" | |||
+ " \"avatar_url\": \"https://github.sonarsource.com/images/error/octocat_happy.gif\",\n" | |||
+ " \"gravatar_id\": \"\",\n" | |||
+ " \"url\": \"https://github.sonarsource.com/api/v3/users/octocat\",\n" | |||
+ " \"html_url\": \"https://github.com/octocat\",\n" | |||
+ " \"followers_url\": \"https://github.sonarsource.com/api/v3/users/octocat/followers\",\n" | |||
+ " \"following_url\": \"https://github.sonarsource.com/api/v3/users/octocat/following{/other_user}\",\n" | |||
+ " \"gists_url\": \"https://github.sonarsource.com/api/v3/users/octocat/gists{/gist_id}\",\n" | |||
+ " \"starred_url\": \"https://github.sonarsource.com/api/v3/users/octocat/starred{/owner}{/repo}\",\n" | |||
+ " \"subscriptions_url\": \"https://github.sonarsource.com/api/v3/users/octocat/subscriptions\",\n" | |||
+ " \"organizations_url\": \"https://github.sonarsource.com/api/v3/users/octocat/orgs\",\n" | |||
+ " \"repos_url\": \"https://github.sonarsource.com/api/v3/users/octocat/repos\",\n" | |||
+ " \"events_url\": \"https://github.sonarsource.com/api/v3/users/octocat/events{/privacy}\",\n" | |||
+ " \"received_events_url\": \"https://github.sonarsource.com/api/v3/users/octocat/received_events\",\n" | |||
+ " \"type\": \"User\",\n" | |||
+ " \"site_admin\": false\n" | |||
+ " },\n" | |||
+ " \"private\": false,\n" | |||
+ " \"html_url\": \"https://github.com/octocat/Hello-World\",\n" | |||
+ " \"description\": \"This your first repo!\",\n" | |||
+ " \"fork\": false,\n" | |||
+ " \"url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World\",\n" | |||
+ " \"archive_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/{archive_format}{/ref}\",\n" | |||
+ " \"assignees_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/assignees{/user}\",\n" | |||
+ " \"blobs_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/git/blobs{/sha}\",\n" | |||
+ " \"branches_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/branches{/branch}\",\n" | |||
+ " \"collaborators_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/collaborators{/collaborator}\",\n" | |||
+ " \"comments_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/comments{/number}\",\n" | |||
+ " \"commits_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/commits{/sha}\",\n" | |||
+ " \"compare_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/compare/{base}...{head}\",\n" | |||
+ " \"contents_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/contents/{+path}\",\n" | |||
+ " \"contributors_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/contributors\",\n" | |||
+ " \"deployments_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/deployments\",\n" | |||
+ " \"downloads_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/downloads\",\n" | |||
+ " \"events_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/events\",\n" | |||
+ " \"forks_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/forks\",\n" | |||
+ " \"git_commits_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/git/commits{/sha}\",\n" | |||
+ " \"git_refs_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/git/refs{/sha}\",\n" | |||
+ " \"git_tags_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/git/tags{/sha}\",\n" | |||
+ " \"git_url\": \"git:github.com/octocat/Hello-World.git\",\n" | |||
+ " \"issue_comment_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/issues/comments{/number}\",\n" | |||
+ " \"issue_events_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/issues/events{/number}\",\n" | |||
+ " \"issues_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/issues{/number}\",\n" | |||
+ " \"keys_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/keys{/key_id}\",\n" | |||
+ " \"labels_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/labels{/name}\",\n" | |||
+ " \"languages_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/languages\",\n" | |||
+ " \"merges_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/merges\",\n" | |||
+ " \"milestones_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/milestones{/number}\",\n" | |||
+ " \"notifications_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/notifications{?since,all,participating}\",\n" | |||
+ " \"pulls_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/pulls{/number}\",\n" | |||
+ " \"releases_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/releases{/id}\",\n" | |||
+ " \"ssh_url\": \"git@github.com:octocat/Hello-World.git\",\n" | |||
+ " \"stargazers_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/stargazers\",\n" | |||
+ " \"statuses_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/statuses/{sha}\",\n" | |||
+ " \"subscribers_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/subscribers\",\n" | |||
+ " \"subscription_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/subscription\",\n" | |||
+ " \"tags_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/tags\",\n" | |||
+ " \"teams_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/teams\",\n" | |||
+ " \"trees_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/git/trees{/sha}\",\n" | |||
+ " \"clone_url\": \"https://github.com/octocat/Hello-World.git\",\n" | |||
+ " \"mirror_url\": \"git:git.example.com/octocat/Hello-World\",\n" | |||
+ " \"hooks_url\": \"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World/hooks\",\n" | |||
+ " \"svn_url\": \"https://svn.github.com/octocat/Hello-World\",\n" | |||
+ " \"homepage\": \"https://github.com\",\n" | |||
+ " \"language\": null,\n" | |||
+ " \"forks_count\": 9,\n" | |||
+ " \"stargazers_count\": 80,\n" | |||
+ " \"watchers_count\": 80,\n" | |||
+ " \"size\": 108,\n" | |||
+ " \"default_branch\": \"master\",\n" | |||
+ " \"open_issues_count\": 0,\n" | |||
+ " \"is_template\": true,\n" | |||
+ " \"topics\": [\n" | |||
+ " \"octocat\",\n" | |||
+ " \"atom\",\n" | |||
+ " \"electron\",\n" | |||
+ " \"api\"\n" | |||
+ " ],\n" | |||
+ " \"has_issues\": true,\n" | |||
+ " \"has_projects\": true,\n" | |||
+ " \"has_wiki\": true,\n" | |||
+ " \"has_pages\": false,\n" | |||
+ " \"has_downloads\": true,\n" | |||
+ " \"archived\": false,\n" | |||
+ " \"disabled\": false,\n" | |||
+ " \"visibility\": \"public\",\n" | |||
+ " \"pushed_at\": \"2011-01-26T19:06:43Z\",\n" | |||
+ " \"created_at\": \"2011-01-26T19:01:12Z\",\n" | |||
+ " \"updated_at\": \"2011-01-26T19:14:43Z\",\n" | |||
+ " \"permissions\": {\n" | |||
+ " \"admin\": false,\n" | |||
+ " \"push\": false,\n" | |||
+ " \"pull\": true\n" | |||
+ " },\n" | |||
+ " \"allow_rebase_merge\": true,\n" | |||
+ " \"template_repository\": null,\n" | |||
+ " \"allow_squash_merge\": true,\n" | |||
+ " \"allow_merge_commit\": true,\n" | |||
+ " \"subscribers_count\": 42,\n" | |||
+ " \"network_count\": 0,\n" | |||
+ " \"anonymous_access_enabled\": false,\n" | |||
+ " \"license\": {\n" | |||
+ " \"key\": \"mit\",\n" | |||
+ " \"name\": \"MIT License\",\n" | |||
+ " \"spdx_id\": \"MIT\",\n" | |||
+ " \"url\": \"https://github.sonarsource.com/api/v3/licenses/mit\",\n" | |||
+ " \"node_id\": \"MDc6TGljZW5zZW1pdA==\"\n" | |||
+ " },\n" | |||
+ " \"organization\": {\n" | |||
+ " \"login\": \"octocat\",\n" | |||
+ " \"id\": 1,\n" | |||
+ " \"node_id\": \"MDQ6VXNlcjE=\",\n" | |||
+ " \"avatar_url\": \"https://github.com/images/error/octocat_happy.gif\",\n" | |||
+ " \"gravatar_id\": \"\",\n" | |||
+ " \"url\": \"https://github.sonarsource.com/api/v3/users/octocat\",\n" | |||
+ " \"html_url\": \"https://github.com/octocat\",\n" | |||
+ " \"followers_url\": \"https://github.sonarsource.com/api/v3/users/octocat/followers\",\n" | |||
+ " \"following_url\": \"https://github.sonarsource.com/api/v3/users/octocat/following{/other_user}\",\n" | |||
+ " \"gists_url\": \"https://github.sonarsource.com/api/v3/users/octocat/gists{/gist_id}\",\n" | |||
+ " \"starred_url\": \"https://github.sonarsource.com/api/v3/users/octocat/starred{/owner}{/repo}\",\n" | |||
+ " \"subscriptions_url\": \"https://github.sonarsource.com/api/v3/users/octocat/subscriptions\",\n" | |||
+ " \"organizations_url\": \"https://github.sonarsource.com/api/v3/users/octocat/orgs\",\n" | |||
+ " \"repos_url\": \"https://github.sonarsource.com/api/v3/users/octocat/repos\",\n" | |||
+ " \"events_url\": \"https://github.sonarsource.com/api/v3/users/octocat/events{/privacy}\",\n" | |||
+ " \"received_events_url\": \"https://github.sonarsource.com/api/v3/users/octocat/received_events\",\n" | |||
+ " \"type\": \"Organization\",\n" | |||
+ " \"site_admin\": false\n" | |||
+ " }" | |||
+ "}"; | |||
when(httpClient.get(appUrl, accessToken, "/repos/octocat/Hello-World")) | |||
.thenReturn(new GetResponse() { | |||
@@ -976,7 +980,7 @@ public class GithubApplicationClientImplTest { | |||
} | |||
}); | |||
Optional<GithubApplicationClient.Repository> repository = underTest.getRepository(appUrl, accessToken, "octocat", "octocat/Hello-World"); | |||
Optional<GithubApplicationClient.Repository> repository = underTest.getRepository(appUrl, accessToken, "octocat/Hello-World"); | |||
assertThat(repository) | |||
.isPresent() | |||
@@ -986,12 +990,74 @@ public class GithubApplicationClientImplTest { | |||
.containsOnly(1296269L, "Hello-World", "octocat/Hello-World", "https://github.com/octocat/Hello-World", false, "master"); | |||
} | |||
@Test | |||
public void createAppInstallationToken_throws_IAE_if_application_token_cant_be_created() { | |||
mockNoApplicationJwtToken(); | |||
assertThatThrownBy(() -> underTest.createAppInstallationToken(githubAppConfiguration, INSTALLATION_ID)) | |||
.isInstanceOf(IllegalArgumentException.class); | |||
} | |||
private void mockNoApplicationJwtToken() { | |||
when(appSecurity.createAppToken(githubAppConfiguration.getId(), githubAppConfiguration.getPrivateKey())).thenThrow(IllegalArgumentException.class); | |||
} | |||
@Test | |||
public void createAppInstallationToken_returns_empty_if_post_throws_IOE() throws IOException { | |||
mockAppToken(); | |||
when(httpClient.post(anyString(), any(AccessToken.class), anyString())).thenThrow(IOException.class); | |||
Optional<AppInstallationToken> accessToken = underTest.createAppInstallationToken(githubAppConfiguration, INSTALLATION_ID); | |||
assertThat(accessToken).isEmpty(); | |||
assertThat(logTester.getLogs(Level.WARN)).extracting(LogAndArguments::getRawMsg).anyMatch(s -> s.startsWith("Failed to request")); | |||
} | |||
@Test | |||
public void createAppInstallationToken_returns_empty_if_access_token_cant_be_created() throws IOException { | |||
AppToken appToken = mockAppToken(); | |||
mockAccessTokenCallingGithubFailure(); | |||
Optional<AppInstallationToken> accessToken = underTest.createAppInstallationToken(githubAppConfiguration, INSTALLATION_ID); | |||
assertThat(accessToken).isEmpty(); | |||
verify(httpClient).post(appUrl, appToken, "/app/installations/" + INSTALLATION_ID + "/access_tokens"); | |||
} | |||
@Test | |||
public void createAppInstallationToken_from_installation_id_returns_access_token() throws IOException { | |||
AppToken appToken = mockAppToken(); | |||
AppInstallationToken installToken = mockCreateAccessTokenCallingGithub(); | |||
Optional<AppInstallationToken> accessToken = underTest.createAppInstallationToken(githubAppConfiguration, INSTALLATION_ID); | |||
assertThat(accessToken).hasValue(installToken); | |||
verify(httpClient).post(appUrl, appToken, "/app/installations/" + INSTALLATION_ID + "/access_tokens"); | |||
} | |||
private void mockAccessTokenCallingGithubFailure() throws IOException { | |||
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); | |||
} | |||
private AppToken mockAppToken() { | |||
String jwt = randomAlphanumeric(5); | |||
when(appSecurity.createAppToken(githubAppConfiguration.getId(), githubAppConfiguration.getPrivateKey())).thenReturn(new AppToken(jwt)); | |||
return new AppToken(jwt); | |||
} | |||
private AppInstallationToken mockCreateAccessTokenCallingGithub() throws IOException { | |||
String token = randomAlphanumeric(5); | |||
Response response = mock(Response.class); | |||
when(response.getContent()).thenReturn(Optional.of("{" + | |||
" \"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); | |||
return new AppInstallationToken(token); | |||
} | |||
private static class OkGetResponse extends Response { | |||
private OkGetResponse(String content) { | |||
super(200, content); |
@@ -27,7 +27,8 @@ public enum CreationMethod { | |||
LOCAL_BROWSER(Category.LOCAL, true), | |||
ALM_IMPORT_API(Category.ALM_IMPORT, false), | |||
ALM_IMPORT_BROWSER(Category.ALM_IMPORT, true), | |||
SCANNER_API(Category.SCANNER, false); | |||
SCANNER_API(Category.SCANNER, false), | |||
SCANNER_API_DEVOPS_AUTO_CONFIG(Category.SCANNER, false); | |||
private final boolean isCreatedViaBrowser; | |||
private final Category category; |
@@ -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.server.component; | |||
import javax.annotation.ParametersAreNonnullByDefault; |
@@ -48,6 +48,7 @@ import org.sonar.db.project.ProjectDto; | |||
import org.sonar.db.user.UserDto; | |||
import org.sonar.server.almintegration.ws.ImportHelper; | |||
import org.sonar.server.almintegration.ws.ProjectKeyGenerator; | |||
import org.sonar.server.almsettings.ws.GitHubDevOpsPlatformService; | |||
import org.sonar.server.component.ComponentUpdater; | |||
import org.sonar.server.es.EsTester; | |||
import org.sonar.server.es.IndexersImpl; | |||
@@ -98,6 +99,7 @@ import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_COD | |||
public class ImportGithubProjectActionIT { | |||
private static final String PROJECT_KEY_NAME = "PROJECT_NAME"; | |||
private static final String GENERATED_PROJECT_KEY = "generated_" + PROJECT_KEY_NAME; | |||
@Rule | |||
public UserSessionRule userSession = standalone(); | |||
@@ -122,15 +124,16 @@ public class ImportGithubProjectActionIT { | |||
private final ImportHelper importHelper = new ImportHelper(db.getDbClient(), userSession); | |||
private final ProjectKeyGenerator projectKeyGenerator = mock(ProjectKeyGenerator.class); | |||
private final ProjectDefaultVisibility projectDefaultVisibility = mock(ProjectDefaultVisibility.class); | |||
private PlatformEditionProvider editionProvider = mock(PlatformEditionProvider.class); | |||
private final PlatformEditionProvider editionProvider = mock(PlatformEditionProvider.class); | |||
private final GitHubSettings gitHubSettings = mock(GitHubSettings.class); | |||
private NewCodeDefinitionResolver newCodeDefinitionResolver = new NewCodeDefinitionResolver(db.getDbClient(), editionProvider); | |||
private final NewCodeDefinitionResolver newCodeDefinitionResolver = new NewCodeDefinitionResolver(db.getDbClient(), editionProvider); | |||
private final ManagedProjectService managedProjectService = mock(ManagedProjectService.class); | |||
private final GitHubDevOpsPlatformService gitHubDevOpsPlatformService = new GitHubDevOpsPlatformService(db.getDbClient(), | |||
null, appClient, projectDefaultVisibility, projectKeyGenerator, userSession, componentUpdater, gitHubSettings); | |||
private final WsActionTester ws = new WsActionTester(new ImportGithubProjectAction(db.getDbClient(), managedProjectService, userSession, | |||
projectDefaultVisibility, appClient, componentUpdater, importHelper, projectKeyGenerator, newCodeDefinitionResolver, | |||
defaultBranchNameResolver, gitHubSettings)); | |||
componentUpdater, importHelper, newCodeDefinitionResolver, defaultBranchNameResolver, gitHubDevOpsPlatformService)); | |||
@Before | |||
public void before() { | |||
@@ -147,7 +150,7 @@ public class ImportGithubProjectActionIT { | |||
Projects.CreateWsResponse response = callWebService(githubAlmSetting); | |||
Projects.CreateWsResponse.Project result = response.getProject(); | |||
assertThat(result.getKey()).isEqualTo(PROJECT_KEY_NAME); | |||
assertThat(result.getKey()).isEqualTo(GENERATED_PROJECT_KEY); | |||
assertThat(result.getName()).isEqualTo(repository.getName()); | |||
Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey()); | |||
@@ -157,7 +160,7 @@ public class ImportGithubProjectActionIT { | |||
assertThat(mainBranch).isPresent(); | |||
assertThat(mainBranch.get().getKey()).isEqualTo("default-branch"); | |||
verify(managedProjectService).queuePermissionSyncTask(userSession.getUuid(), mainBranch.get().getUuid() , projectDto.get().getUuid()); | |||
verify(managedProjectService).queuePermissionSyncTask(userSession.getUuid(), mainBranch.get().getUuid(), projectDto.get().getUuid()); | |||
} | |||
@Test | |||
@@ -177,11 +180,12 @@ public class ImportGithubProjectActionIT { | |||
.executeProtobuf(Projects.CreateWsResponse.class); | |||
Projects.CreateWsResponse.Project result = response.getProject(); | |||
assertThat(result.getKey()).isEqualTo(GENERATED_PROJECT_KEY); | |||
Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey()); | |||
assertThat(projectDto).isPresent(); | |||
assertThat(db.getDbClient().newCodePeriodDao().selectByProject(db.getSession(), projectDto.get().getUuid())) | |||
assertThat(db.getDbClient().newCodePeriodDao().selectByProject(db.getSession(), projectDto.get().getUuid())) | |||
.isPresent() | |||
.get() | |||
.extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue, NewCodePeriodDto::getBranchUuid) | |||
@@ -205,6 +209,7 @@ public class ImportGithubProjectActionIT { | |||
.executeProtobuf(Projects.CreateWsResponse.class); | |||
Projects.CreateWsResponse.Project result = response.getProject(); | |||
assertThat(result.getKey()).isEqualTo(GENERATED_PROJECT_KEY); | |||
Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey()); | |||
assertThat(projectDto).isPresent(); | |||
@@ -225,11 +230,7 @@ public class ImportGithubProjectActionIT { | |||
AlmSettingDto githubAlmSetting = setupUserWithPatAndAlmSettings(); | |||
GithubApplicationClient.Repository repository = new GithubApplicationClient.Repository(1L, PROJECT_KEY_NAME, false, | |||
"octocat/" + PROJECT_KEY_NAME, | |||
"https://github.sonarsource.com/api/v3/repos/octocat/" + PROJECT_KEY_NAME, null); | |||
when(appClient.getRepository(any(), any(), any(), any())).thenReturn(Optional.of(repository)); | |||
when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn(PROJECT_KEY_NAME); | |||
mockGithubInteractions(); | |||
Projects.CreateWsResponse response = ws.newRequest() | |||
.setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey()) | |||
@@ -239,6 +240,7 @@ public class ImportGithubProjectActionIT { | |||
.executeProtobuf(Projects.CreateWsResponse.class); | |||
Projects.CreateWsResponse.Project result = response.getProject(); | |||
assertThat(result.getKey()).isEqualTo(GENERATED_PROJECT_KEY); | |||
Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey()); | |||
assertThat(projectDto).isPresent(); | |||
@@ -256,11 +258,7 @@ public class ImportGithubProjectActionIT { | |||
AlmSettingDto githubAlmSetting = setupUserWithPatAndAlmSettings(); | |||
GithubApplicationClient.Repository repository = new GithubApplicationClient.Repository(1L, PROJECT_KEY_NAME, false, | |||
"octocat/" + PROJECT_KEY_NAME, | |||
"https://github.sonarsource.com/api/v3/repos/octocat/" + PROJECT_KEY_NAME, "mainBranch"); | |||
when(appClient.getRepository(any(), any(), any(), any())).thenReturn(Optional.of(repository)); | |||
when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn(PROJECT_KEY_NAME); | |||
mockGithubInteractions(); | |||
Projects.CreateWsResponse response = ws.newRequest() | |||
.setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey()) | |||
@@ -270,6 +268,7 @@ public class ImportGithubProjectActionIT { | |||
.executeProtobuf(Projects.CreateWsResponse.class); | |||
Projects.CreateWsResponse.Project result = response.getProject(); | |||
assertThat(result.getKey()).isEqualTo(GENERATED_PROJECT_KEY); | |||
Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), result.getKey()); | |||
assertThat(projectDto).isPresent(); | |||
@@ -278,7 +277,7 @@ public class ImportGithubProjectActionIT { | |||
.isPresent() | |||
.get() | |||
.extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue) | |||
.containsExactly(REFERENCE_BRANCH, "mainBranch"); | |||
.containsExactly(REFERENCE_BRANCH, "default-branch"); | |||
} | |||
@Test | |||
@@ -286,10 +285,7 @@ public class ImportGithubProjectActionIT { | |||
AlmSettingDto githubAlmSetting = setupUserWithPatAndAlmSettings(); | |||
db.components().insertPublicProject(p -> p.setKey("Hello-World")).getMainBranchComponent(); | |||
GithubApplicationClient.Repository repository = new GithubApplicationClient.Repository(1L, "Hello-World", false, "Hello-World", | |||
"https://github.sonarsource.com/api/v3/repos/octocat/Hello-World", "main"); | |||
when(appClient.getRepository(any(), any(), any(), any())).thenReturn(Optional.of(repository)); | |||
when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn(PROJECT_KEY_NAME); | |||
GithubApplicationClient.Repository repository = mockGithubInteractions(); | |||
Projects.CreateWsResponse response = ws.newRequest() | |||
.setParam(PARAM_ALM_SETTING, githubAlmSetting.getKey()) | |||
@@ -298,7 +294,7 @@ public class ImportGithubProjectActionIT { | |||
.executeProtobuf(Projects.CreateWsResponse.class); | |||
Projects.CreateWsResponse.Project result = response.getProject(); | |||
assertThat(result.getKey()).isEqualTo(PROJECT_KEY_NAME); | |||
assertThat(result.getKey()).isEqualTo(GENERATED_PROJECT_KEY); | |||
assertThat(result.getName()).isEqualTo(repository.getName()); | |||
} | |||
@@ -318,7 +314,7 @@ public class ImportGithubProjectActionIT { | |||
ArgumentCaptor<EntityDto> projectDtoArgumentCaptor = ArgumentCaptor.forClass(EntityDto.class); | |||
verify(permissionTemplateService).applyDefaultToNewComponent(any(DbSession.class), projectDtoArgumentCaptor.capture(), eq(userSession.getUuid())); | |||
String projectKey = projectDtoArgumentCaptor.getValue().getKey(); | |||
assertThat(projectKey).isEqualTo(PROJECT_KEY_NAME); | |||
assertThat(projectKey).isEqualTo(GENERATED_PROJECT_KEY); | |||
} | |||
@@ -346,6 +342,7 @@ public class ImportGithubProjectActionIT { | |||
Projects.CreateWsResponse response = callWebService(githubAlmSetting); | |||
assertThat(response.getProject().getKey()).isEqualTo(GENERATED_PROJECT_KEY); | |||
Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), response.getProject().getKey()); | |||
assertThat(projectDto.orElseThrow().getCreationMethod()).isEqualTo(CreationMethod.ALM_IMPORT_API); | |||
} | |||
@@ -358,6 +355,7 @@ public class ImportGithubProjectActionIT { | |||
Projects.CreateWsResponse response = callWebService(githubAlmSetting); | |||
assertThat(response.getProject().getKey()).isEqualTo(GENERATED_PROJECT_KEY); | |||
Optional<ProjectDto> projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), response.getProject().getKey()); | |||
assertThat(projectDto.orElseThrow().getCreationMethod()).isEqualTo(CreationMethod.ALM_IMPORT_BROWSER); | |||
} | |||
@@ -431,8 +429,8 @@ public class ImportGithubProjectActionIT { | |||
GithubApplicationClient.Repository repository = new GithubApplicationClient.Repository(1L, PROJECT_KEY_NAME, false, | |||
"octocat/" + PROJECT_KEY_NAME, | |||
"https://github.sonarsource.com/api/v3/repos/octocat/" + PROJECT_KEY_NAME, "default-branch"); | |||
when(appClient.getRepository(any(), any(), any(), any())).thenReturn(Optional.of(repository)); | |||
when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn(PROJECT_KEY_NAME); | |||
when(appClient.getRepository(any(), any(), any())).thenReturn(Optional.of(repository)); | |||
when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn(GENERATED_PROJECT_KEY); | |||
return repository; | |||
} | |||
@@ -25,8 +25,6 @@ import java.nio.charset.StandardCharsets; | |||
import java.util.Map; | |||
import java.util.Optional; | |||
import java.util.Random; | |||
import java.util.stream.Collectors; | |||
import java.util.stream.IntStream; | |||
import org.apache.commons.io.IOUtils; | |||
import org.junit.Before; | |||
import org.junit.Rule; | |||
@@ -49,6 +47,8 @@ import org.sonar.db.component.ProjectData; | |||
import org.sonar.db.project.CreationMethod; | |||
import org.sonar.db.project.ProjectDto; | |||
import org.sonar.db.user.UserDto; | |||
import org.sonar.server.almsettings.ws.DevOpsPlatformService; | |||
import org.sonar.server.almsettings.ws.GitHubDevOpsPlatformService; | |||
import org.sonar.server.component.ComponentCreationData; | |||
import org.sonar.server.component.ComponentCreationParameters; | |||
import org.sonar.server.component.ComponentUpdater; | |||
@@ -74,6 +74,8 @@ import static org.mockito.Mockito.verify; | |||
import static org.mockito.Mockito.verifyNoInteractions; | |||
import static org.mockito.Mockito.verifyNoMoreInteractions; | |||
import static org.mockito.Mockito.when; | |||
import static org.sonar.core.ce.CeTaskCharacteristics.BRANCH; | |||
import static org.sonar.core.ce.CeTaskCharacteristics.BRANCH_TYPE; | |||
import static org.sonar.db.component.ComponentTesting.newBranchDto; | |||
import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto; | |||
import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS; | |||
@@ -86,6 +88,10 @@ import static org.sonar.db.permission.GlobalPermission.SCAN; | |||
public class BranchReportSubmitterIT { | |||
private static final String PROJECT_UUID = "PROJECT_UUID"; | |||
private static final Map<String, String> CHARACTERISTICS = Map.of( | |||
BRANCH, "branch_name", | |||
BRANCH_TYPE, "branch" | |||
); | |||
@Rule | |||
public final UserSessionRule userSession = UserSessionRule.standalone(); | |||
@Rule | |||
@@ -100,8 +106,11 @@ public class BranchReportSubmitterIT { | |||
private final BranchSupportDelegate branchSupportDelegate = mock(BranchSupportDelegate.class); | |||
private final BranchSupport branchSupport = spy(new BranchSupport(branchSupportDelegate)); | |||
private final DevOpsPlatformService devOpsPlatformService = new GitHubDevOpsPlatformService(db.getDbClient(), null, | |||
null, projectDefaultVisibility, null, userSession, componentUpdater, null); | |||
private final ReportSubmitter underTest = new ReportSubmitter(queue, userSession, componentUpdater, permissionTemplateService, db.getDbClient(), branchSupport, | |||
projectDefaultVisibility); | |||
projectDefaultVisibility, devOpsPlatformService); | |||
@Before | |||
public void before() { | |||
@@ -114,7 +123,7 @@ public class BranchReportSubmitterIT { | |||
ProjectDto project = projectData.getProjectDto(); | |||
UserDto user = db.users().insertUser(); | |||
userSession.logIn(user).addProjectPermission(SCAN.getKey(), project) | |||
.registerBranches(projectData.getMainBranchDto()); | |||
.registerBranches(projectData.getMainBranchDto()); | |||
mockSuccessfulPrepareSubmitCall(); | |||
InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8); | |||
@@ -132,21 +141,20 @@ public class BranchReportSubmitterIT { | |||
userSession.logIn(user).addProjectPermission(SCAN.getKey(), projectData.getProjectDto()) | |||
.registerBranches(projectData.getMainBranchDto()) | |||
.addProjectBranchMapping(projectData.projectUuid(), branch); | |||
Map<String, String> randomCharacteristics = randomNonEmptyMap(); | |||
BranchSupport.ComponentKey componentKey = createComponentKeyOfBranch(mainBranch.getKey(), "branch1"); | |||
when(branchSupportDelegate.createComponentKey(mainBranch.getKey(), randomCharacteristics)).thenReturn(componentKey); | |||
when(branchSupportDelegate.createComponentKey(mainBranch.getKey(), CHARACTERISTICS)).thenReturn(componentKey); | |||
InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8); | |||
String taskUuid = mockSuccessfulPrepareSubmitCall(); | |||
underTest.submit(mainBranch.getKey(), mainBranch.name(), randomCharacteristics, reportInput); | |||
underTest.submit(mainBranch.getKey(), mainBranch.name(), CHARACTERISTICS, reportInput); | |||
verifyNoInteractions(permissionTemplateService); | |||
verifyNoInteractions(favoriteUpdater); | |||
verify(branchSupport, times(0)).createBranchComponent(any(), any(), any(), any()); | |||
verify(branchSupportDelegate).createComponentKey(mainBranch.getKey(), randomCharacteristics); | |||
verify(branchSupportDelegate).createComponentKey(mainBranch.getKey(), CHARACTERISTICS); | |||
verify(branchSupportDelegate, times(0)).createBranchComponent(any(), any(), any(), any()); | |||
verifyNoMoreInteractions(branchSupportDelegate); | |||
verifyQueueSubmit(mainBranch, branch, user, randomCharacteristics, taskUuid); | |||
verifyQueueSubmit(mainBranch, branch, user, CHARACTERISTICS, taskUuid); | |||
ProjectDto projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), componentKey.getKey()).orElseThrow(); | |||
assertThat(projectDto.getCreationMethod()).isEqualTo(CreationMethod.LOCAL_API); | |||
@@ -160,25 +168,24 @@ public class BranchReportSubmitterIT { | |||
UserDto user = db.users().insertUser(); | |||
userSession.logIn(user).addProjectPermission(SCAN.getKey(), projectData.getProjectDto()) | |||
.registerBranches(projectData.getMainBranchDto()); | |||
Map<String, String> randomCharacteristics = randomNonEmptyMap(); | |||
ComponentDto createdBranch = createButDoNotInsertBranch(mainBranch, projectData.projectUuid()); | |||
userSession.addProjectBranchMapping(projectData.projectUuid(), createdBranch); | |||
BranchSupport.ComponentKey componentKey = createComponentKeyOfBranch(mainBranch.getKey(), "branch1"); | |||
when(branchSupportDelegate.createComponentKey(mainBranch.getKey(), randomCharacteristics)).thenReturn(componentKey); | |||
when(branchSupportDelegate.createComponentKey(mainBranch.getKey(), CHARACTERISTICS)).thenReturn(componentKey); | |||
when(branchSupportDelegate.createBranchComponent(any(DbSession.class), same(componentKey), eq(mainBranch), eq(exitingProjectMainBranch))).thenReturn(createdBranch); | |||
InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8); | |||
String taskUuid = mockSuccessfulPrepareSubmitCall(); | |||
underTest.submit(mainBranch.getKey(), mainBranch.name(), randomCharacteristics, reportInput); | |||
underTest.submit(mainBranch.getKey(), mainBranch.name(), CHARACTERISTICS, reportInput); | |||
verifyNoInteractions(permissionTemplateService); | |||
verifyNoInteractions(favoriteUpdater); | |||
verify(branchSupport).createBranchComponent(any(DbSession.class), same(componentKey), eq(mainBranch), eq(exitingProjectMainBranch)); | |||
verify(branchSupportDelegate).createComponentKey(mainBranch.getKey(), randomCharacteristics); | |||
verify(branchSupportDelegate).createComponentKey(mainBranch.getKey(), CHARACTERISTICS); | |||
verify(branchSupportDelegate).createBranchComponent(any(DbSession.class), same(componentKey), eq(mainBranch), eq(exitingProjectMainBranch)); | |||
verifyNoMoreInteractions(branchSupportDelegate); | |||
verify(componentUpdater, times(0)).commitAndIndex(any(), any()); | |||
verifyQueueSubmit(mainBranch, createdBranch, user, randomCharacteristics, taskUuid); | |||
verifyQueueSubmit(mainBranch, createdBranch, user, CHARACTERISTICS, taskUuid); | |||
} | |||
@Test | |||
@@ -189,10 +196,9 @@ public class BranchReportSubmitterIT { | |||
.addPermission(PROVISION_PROJECTS) | |||
.addPermission(SCAN); | |||
Map<String, String> randomCharacteristics = randomNonEmptyMap(); | |||
ComponentDto createdBranch = createButDoNotInsertBranch(nonExistingBranch, PROJECT_UUID); | |||
BranchSupport.ComponentKey componentKey = createComponentKeyOfBranch(nonExistingBranch.getKey()); | |||
when(branchSupportDelegate.createComponentKey(nonExistingBranch.getKey(), randomCharacteristics)).thenReturn(componentKey); | |||
when(branchSupportDelegate.createComponentKey(nonExistingBranch.getKey(), CHARACTERISTICS)).thenReturn(componentKey); | |||
ComponentCreationData componentCreationData = mock(ComponentCreationData.class); | |||
when(componentCreationData.mainBranchComponent()) | |||
.thenAnswer((Answer<ComponentDto>) invocation -> db.components().insertPrivateProject(PROJECT_UUID, nonExistingBranch).getMainBranchComponent()); | |||
@@ -202,14 +208,14 @@ public class BranchReportSubmitterIT { | |||
String taskUuid = mockSuccessfulPrepareSubmitCall(); | |||
InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8); | |||
underTest.submit(nonExistingBranch.getKey(), nonExistingBranch.name(), randomCharacteristics, reportInput); | |||
underTest.submit(nonExistingBranch.getKey(), nonExistingBranch.name(), CHARACTERISTICS, reportInput); | |||
BranchDto existingProjectMainBranch = db.getDbClient().branchDao().selectByUuid(db.getSession(), nonExistingBranch.uuid()).get(); | |||
verify(branchSupport).createBranchComponent(any(DbSession.class), same(componentKey), eq(nonExistingBranch), eq(existingProjectMainBranch)); | |||
verify(branchSupportDelegate).createComponentKey(nonExistingBranch.getKey(), randomCharacteristics); | |||
verify(branchSupportDelegate).createComponentKey(nonExistingBranch.getKey(), CHARACTERISTICS); | |||
verify(branchSupportDelegate).createBranchComponent(any(DbSession.class), same(componentKey), eq(nonExistingBranch), eq(existingProjectMainBranch)); | |||
verifyNoMoreInteractions(branchSupportDelegate); | |||
verifyQueueSubmit(nonExistingBranch, createdBranch, user, randomCharacteristics, taskUuid); | |||
verifyQueueSubmit(nonExistingBranch, createdBranch, user, CHARACTERISTICS, taskUuid); | |||
verify(componentUpdater).commitAndIndex(any(DbSession.class), eq(componentCreationData)); | |||
assertProjectCreatedWithCreationMethodEqualsScanner(); | |||
} | |||
@@ -226,13 +232,12 @@ public class BranchReportSubmitterIT { | |||
ComponentDto project = projectData.getMainBranchComponent(); | |||
UserDto user = db.users().insertUser(); | |||
userSession.logIn(user).addProjectPermission(SCAN.getKey(), projectData.getProjectDto()); | |||
Map<String, String> randomCharacteristics = randomNonEmptyMap(); | |||
InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8); | |||
RuntimeException expected = new RuntimeException("Faking an exception thrown by branchSupportDelegate"); | |||
when(branchSupportDelegate.createComponentKey(any(), any())).thenThrow(expected); | |||
try { | |||
underTest.submit(project.getKey(), project.name(), randomCharacteristics, reportInput); | |||
underTest.submit(project.getKey(), project.name(), CHARACTERISTICS, reportInput); | |||
fail("exception should have been thrown"); | |||
} catch (Exception e) { | |||
assertThat(e).isSameAs(expected); | |||
@@ -244,16 +249,15 @@ public class BranchReportSubmitterIT { | |||
ComponentDto nonExistingBranch = newPrivateProjectDto(); | |||
UserDto user = db.users().insertUser(); | |||
Map<String, String> randomCharacteristics = randomNonEmptyMap(); | |||
ComponentDto createdBranch = createButDoNotInsertBranch(nonExistingBranch, PROJECT_UUID); | |||
BranchSupport.ComponentKey componentKey = createComponentKeyOfBranch(nonExistingBranch.getKey()); | |||
String nonExistingProjectDbKey = nonExistingBranch.getKey(); | |||
when(branchSupportDelegate.createComponentKey(nonExistingProjectDbKey, randomCharacteristics)).thenReturn(componentKey); | |||
when(branchSupportDelegate.createComponentKey(nonExistingProjectDbKey, CHARACTERISTICS)).thenReturn(componentKey); | |||
when(branchSupportDelegate.createBranchComponent(any(DbSession.class), same(componentKey), any(), any())).thenReturn(createdBranch); | |||
InputStream reportInput = IOUtils.toInputStream("{binary}", StandardCharsets.UTF_8); | |||
String name = nonExistingBranch.name(); | |||
assertThatThrownBy(() -> underTest.submit(nonExistingProjectDbKey, name, randomCharacteristics, reportInput)) | |||
assertThatThrownBy(() -> underTest.submit(nonExistingProjectDbKey, name, CHARACTERISTICS, reportInput)) | |||
.isInstanceOf(ForbiddenException.class) | |||
.hasMessage("Insufficient privileges"); | |||
} | |||
@@ -300,10 +304,4 @@ public class BranchReportSubmitterIT { | |||
return componentKey; | |||
} | |||
private static Map<String, String> randomNonEmptyMap() { | |||
return IntStream.range(0, 1 + new Random().nextInt(5)) | |||
.boxed() | |||
.collect(Collectors.toMap(i -> "key_" + i, i1 -> "val_" + i1)); | |||
} | |||
} |
@@ -20,15 +20,21 @@ | |||
package org.sonar.server.ce.queue; | |||
import java.io.InputStream; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.Random; | |||
import java.util.stream.Collectors; | |||
import java.util.stream.IntStream; | |||
import java.util.Optional; | |||
import java.util.Set; | |||
import org.apache.commons.io.IOUtils; | |||
import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.sonar.alm.client.github.AppInstallationToken; | |||
import org.sonar.alm.client.github.GithubApplicationClient; | |||
import org.sonar.alm.client.github.GithubGlobalSettingsValidator; | |||
import org.sonar.alm.client.github.config.GithubAppConfiguration; | |||
import org.sonar.alm.client.github.config.GithubAppInstallation; | |||
import org.sonar.api.utils.System2; | |||
import org.sonar.auth.github.GitHubSettings; | |||
import org.sonar.ce.queue.CeQueue; | |||
import org.sonar.ce.queue.CeQueueImpl; | |||
import org.sonar.ce.queue.CeTaskSubmit; | |||
@@ -36,13 +42,21 @@ import org.sonar.core.i18n.I18n; | |||
import org.sonar.core.util.SequenceUuidFactory; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.DbTester; | |||
import org.sonar.db.alm.setting.ALM; | |||
import org.sonar.db.alm.setting.AlmSettingDto; | |||
import org.sonar.db.ce.CeTaskTypes; | |||
import org.sonar.db.component.BranchDto; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.component.ProjectData; | |||
import org.sonar.db.permission.GlobalPermission; | |||
import org.sonar.db.project.CreationMethod; | |||
import org.sonar.db.project.ProjectDto; | |||
import org.sonar.db.user.UserDto; | |||
import org.sonar.server.almintegration.ws.ProjectKeyGenerator; | |||
import org.sonar.server.almsettings.ws.DelegatingDevOpsPlatformService; | |||
import org.sonar.server.almsettings.ws.DevOpsPlatformService; | |||
import org.sonar.server.almsettings.ws.DevOpsProjectDescriptor; | |||
import org.sonar.server.almsettings.ws.GitHubDevOpsPlatformService; | |||
import org.sonar.server.component.ComponentUpdater; | |||
import org.sonar.server.es.TestIndexers; | |||
import org.sonar.server.exceptions.BadRequestException; | |||
@@ -60,16 +74,19 @@ import static java.lang.String.format; | |||
import static java.nio.charset.StandardCharsets.UTF_8; | |||
import static java.util.Collections.emptyMap; | |||
import static java.util.stream.IntStream.rangeClosed; | |||
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.mockito.ArgumentMatchers.any; | |||
import static org.mockito.ArgumentMatchers.anyLong; | |||
import static org.mockito.ArgumentMatchers.argThat; | |||
import static org.mockito.ArgumentMatchers.eq; | |||
import static org.mockito.Mockito.doReturn; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.spy; | |||
import static org.mockito.Mockito.verify; | |||
import static org.mockito.Mockito.verifyNoInteractions; | |||
import static org.mockito.Mockito.when; | |||
import static org.sonar.core.ce.CeTaskCharacteristics.BRANCH; | |||
import static org.sonar.db.component.BranchDto.DEFAULT_MAIN_BRANCH_NAME; | |||
import static org.sonar.db.component.ComponentTesting.newDirectory; | |||
import static org.sonar.db.permission.GlobalPermission.PROVISION_PROJECTS; | |||
@@ -98,8 +115,19 @@ public class ReportSubmitterIT { | |||
new FavoriteUpdater(db.getDbClient()), projectIndexers, new SequenceUuidFactory(), defaultBranchNameResolver, mock(PermissionUpdater.class), mock(PermissionService.class)); | |||
private final BranchSupport ossEditionBranchSupport = new BranchSupport(null); | |||
private final GithubApplicationClient githubApplicationClient = mock(); | |||
private final GithubGlobalSettingsValidator githubGlobalSettingsValidator = mock(); | |||
private final GitHubSettings gitHubSettings = mock(); | |||
private final ProjectKeyGenerator projectKeyGenerator = mock(); | |||
private final DevOpsPlatformService devOpsPlatformService = new DelegatingDevOpsPlatformService( | |||
Set.of(new GitHubDevOpsPlatformService(db.getDbClient(), githubGlobalSettingsValidator, | |||
githubApplicationClient, projectDefaultVisibility, projectKeyGenerator, userSession, componentUpdater, gitHubSettings))); | |||
private final DevOpsPlatformService devOpsPlatformServiceSpy = spy(devOpsPlatformService); | |||
private final ReportSubmitter underTest = new ReportSubmitter(queue, userSession, componentUpdater, permissionTemplateService, db.getDbClient(), ossEditionBranchSupport, | |||
projectDefaultVisibility); | |||
projectDefaultVisibility, devOpsPlatformServiceSpy); | |||
@Before | |||
public void before() { | |||
@@ -115,9 +143,7 @@ public class ReportSubmitterIT { | |||
mockSuccessfulPrepareSubmitCall(); | |||
when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(), any(), eq(PROJECT_KEY))) | |||
.thenReturn(true); | |||
Map<String, String> nonEmptyCharacteristics = IntStream.range(0, 1 + new Random().nextInt(5)) | |||
.boxed() | |||
.collect(Collectors.toMap(i -> randomAlphabetic(i + 10), i1 -> randomAlphabetic(i1 + 20))); | |||
Map<String, String> nonEmptyCharacteristics = Map.of(BRANCH, "branch1"); | |||
InputStream reportInput = IOUtils.toInputStream("{binary}", UTF_8); | |||
assertThatThrownBy(() -> underTest.submit(PROJECT_KEY, PROJECT_NAME, nonEmptyCharacteristics, reportInput)) | |||
@@ -152,9 +178,11 @@ public class ReportSubmitterIT { | |||
verifyReportIsPersisted(TASK_UUID); | |||
verifyNoInteractions(permissionTemplateService); | |||
verify(queue).submit(argThat(submit -> submit.getType().equals(CeTaskTypes.REPORT) | |||
&& submit.getComponent().filter(cpt -> cpt.getUuid().equals(project.getMainBranchComponent().uuid()) && cpt.getEntityUuid().equals(project.projectUuid())).isPresent() | |||
&& submit.getSubmitterUuid().equals(user.getUuid()) | |||
&& submit.getUuid().equals(TASK_UUID))); | |||
&& submit.getComponent() | |||
.filter(cpt -> cpt.getUuid().equals(project.getMainBranchComponent().uuid()) && cpt.getEntityUuid().equals(project.projectUuid())) | |||
.isPresent() | |||
&& submit.getSubmitterUuid().equals(user.getUuid()) | |||
&& submit.getUuid().equals(TASK_UUID))); | |||
} | |||
@Test | |||
@@ -173,8 +201,9 @@ public class ReportSubmitterIT { | |||
verifyReportIsPersisted(TASK_UUID); | |||
verify(queue).submit(argThat(submit -> submit.getType().equals(CeTaskTypes.REPORT) | |||
&& submit.getComponent().filter(cpt -> cpt.getUuid().equals(createdProject.uuid()) && cpt.getEntityUuid().equals(projectDto.getUuid())).isPresent() | |||
&& submit.getUuid().equals(TASK_UUID))); | |||
&& submit.getComponent().filter(cpt -> cpt.getUuid().equals(createdProject.uuid()) && cpt.getEntityUuid().equals(projectDto.getUuid())) | |||
.isPresent() | |||
&& submit.getUuid().equals(TASK_UUID))); | |||
assertThat(projectDto.getCreationMethod()).isEqualTo(CreationMethod.SCANNER_API); | |||
} | |||
@@ -231,17 +260,92 @@ public class ReportSubmitterIT { | |||
} | |||
@Test | |||
public void submit_a_report_on_new_project_with_scan_permission() { | |||
userSession | |||
.addPermission(GlobalPermission.SCAN) | |||
.addPermission(PROVISION_PROJECTS); | |||
public void submit_whenReportIsForANewProjectWithoutDevOpsMetadata_createsLocalProject() { | |||
userSession.addPermission(GlobalPermission.SCAN).addPermission(PROVISION_PROJECTS); | |||
mockSuccessfulPrepareSubmitCall(); | |||
when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), any(), eq(PROJECT_KEY))) | |||
.thenReturn(true); | |||
underTest.submit(PROJECT_KEY, PROJECT_NAME, emptyMap(), IOUtils.toInputStream("{binary}", UTF_8)); | |||
verify(queue).submit(any(CeTaskSubmit.class)); | |||
ProjectDto projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), PROJECT_KEY).orElseThrow(); | |||
assertThat(projectDto.getCreationMethod()).isEqualTo(CreationMethod.SCANNER_API); | |||
assertThat(projectDto.getName()).isEqualTo(PROJECT_NAME); | |||
BranchDto branchDto = db.getDbClient().branchDao().selectByBranchKey(db.getSession(), projectDto.getUuid(), "main").orElseThrow(); | |||
assertThat(branchDto.isMain()).isTrue(); | |||
} | |||
@Test | |||
public void submit_whenReportIsForANewProjectWithoutValidAlmSettings_createsProjectWithoutDevOpsBinding() { | |||
userSession.addPermission(GlobalPermission.SCAN).addPermission(PROVISION_PROJECTS); | |||
when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), any(), eq(PROJECT_KEY))).thenReturn(true); | |||
mockSuccessfulPrepareSubmitCall(); | |||
Map<String, String> characteristics = Map.of("random", "data"); | |||
DevOpsProjectDescriptor projectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, "apiUrl", "orga/repo"); | |||
when(devOpsPlatformServiceSpy.getDevOpsProjectDescriptor(characteristics)).thenReturn(Optional.of(projectDescriptor)); | |||
underTest.submit(PROJECT_KEY, PROJECT_NAME, characteristics, IOUtils.toInputStream("{binary}", UTF_8)); | |||
ProjectDto projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), PROJECT_KEY).orElseThrow(); | |||
assertThat(projectDto.getCreationMethod()).isEqualTo(CreationMethod.SCANNER_API); | |||
assertThat(projectDto.getName()).isEqualTo(PROJECT_NAME); | |||
BranchDto branchDto = db.getDbClient().branchDao().selectByBranchKey(db.getSession(), projectDto.getUuid(), "main").orElseThrow(); | |||
assertThat(branchDto.isMain()).isTrue(); | |||
assertThat(db.getDbClient().projectAlmSettingDao().selectByProject(db.getSession(), projectDto.getUuid())).isEmpty(); | |||
} | |||
@Test | |||
public void submit_whenReportIsForANewProjectWithValidAlmSettings_createsProjectWithDevOpsBinding() { | |||
userSession.addPermission(GlobalPermission.SCAN).addPermission(PROVISION_PROJECTS); | |||
when(permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(any(DbSession.class), any(), eq(PROJECT_KEY))).thenReturn(true); | |||
mockSuccessfulPrepareSubmitCall(); | |||
Map<String, String> characteristics = Map.of("random", "data"); | |||
DevOpsProjectDescriptor projectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, "apiUrl", "orga/repo"); | |||
mockInteractionsWithDevOpsPlatformServiceSpyBeforeProjectCreation(characteristics, projectDescriptor); | |||
underTest.submit(PROJECT_KEY, PROJECT_NAME, characteristics, IOUtils.toInputStream("{binary}", UTF_8)); | |||
ProjectDto projectDto = db.getDbClient().projectDao().selectProjectByKey(db.getSession(), PROJECT_KEY).orElseThrow(); | |||
assertThat(projectDto.getCreationMethod()).isEqualTo(CreationMethod.SCANNER_API_DEVOPS_AUTO_CONFIG); | |||
assertThat(projectDto.getName()).isEqualTo("repoName"); | |||
BranchDto branchDto = db.getDbClient().branchDao().selectByBranchKey(db.getSession(), projectDto.getUuid(), "defaultBranch").orElseThrow(); | |||
assertThat(branchDto.isMain()).isTrue(); | |||
assertThat(db.getDbClient().projectAlmSettingDao().selectByProject(db.getSession(), projectDto.getUuid())).isPresent(); | |||
} | |||
private void mockInteractionsWithDevOpsPlatformServiceSpyBeforeProjectCreation(Map<String, String> characteristics, DevOpsProjectDescriptor projectDescriptor) { | |||
doReturn(Optional.of(projectDescriptor)).when(devOpsPlatformServiceSpy).getDevOpsProjectDescriptor(characteristics); | |||
AlmSettingDto almSettingDto = mock(AlmSettingDto.class); | |||
when(almSettingDto.getAlm()).thenReturn(ALM.GITHUB); | |||
when(almSettingDto.getUrl()).thenReturn("https://www.toto.com"); | |||
when(almSettingDto.getUuid()).thenReturn("TEST_GH"); | |||
doReturn(Optional.of(almSettingDto)).when(devOpsPlatformServiceSpy).getValidAlmSettingDto(any(), eq(projectDescriptor)); | |||
mockGithubInteractions(almSettingDto); | |||
} | |||
private void mockGithubInteractions(AlmSettingDto almSettingDto) { | |||
GithubAppConfiguration githubAppConfiguration = mock(GithubAppConfiguration.class); | |||
when(githubGlobalSettingsValidator.validate(almSettingDto)).thenReturn(githubAppConfiguration); | |||
GithubAppInstallation githubAppInstallation = mock(GithubAppInstallation.class); | |||
when(githubAppInstallation.installationId()).thenReturn("5435345"); | |||
when(githubApplicationClient.getWhitelistedGithubAppInstallations(any())).thenReturn(List.of(githubAppInstallation)); | |||
when(githubApplicationClient.createAppInstallationToken(any(), anyLong())).thenReturn(Optional.of(mock(AppInstallationToken.class))); | |||
when(githubApplicationClient.createAppInstallationToken(any(), anyLong())).thenReturn(Optional.of(mock(AppInstallationToken.class))); | |||
when(githubApplicationClient.getInstallationId(eq(githubAppConfiguration), any())).thenReturn(Optional.of(5435345L)); | |||
GithubApplicationClient.Repository repository = mock(GithubApplicationClient.Repository.class); | |||
when(repository.getDefaultBranch()).thenReturn("defaultBranch"); | |||
when(repository.getFullName()).thenReturn("orga/repoName"); | |||
when(repository.getName()).thenReturn("repoName"); | |||
when(githubApplicationClient.getRepository(any(), any(), any())).thenReturn(Optional.of(repository)); | |||
when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn("projectKey"); | |||
} | |||
@Test | |||
@@ -300,7 +404,7 @@ public class ReportSubmitterIT { | |||
.extracting(throwable -> ((BadRequestException) throwable).errors()) | |||
.asList() | |||
.contains(format("The project '%s' is already defined in SonarQube but as a module of project '%s'. " + | |||
"If you really want to stop directly analysing project '%s', please first delete it from SonarQube and then relaunch the analysis of project '%s'.", | |||
"If you really want to stop directly analysing project '%s', please first delete it from SonarQube and then relaunch the analysis of project '%s'.", | |||
dir.getKey(), project.getKey(), project.getKey(), dir.getKey())); | |||
} | |||
@@ -19,49 +19,36 @@ | |||
*/ | |||
package org.sonar.server.almintegration.ws.github; | |||
import java.util.Objects; | |||
import java.util.Optional; | |||
import javax.inject.Inject; | |||
import org.sonar.alm.client.github.GithubApplicationClient; | |||
import org.sonar.alm.client.github.GithubApplicationClient.Repository; | |||
import org.sonar.alm.client.github.GithubApplicationClientImpl; | |||
import org.sonar.alm.client.github.security.AccessToken; | |||
import org.sonar.alm.client.github.security.UserAccessToken; | |||
import org.sonar.api.server.ws.Change; | |||
import org.sonar.api.server.ws.Request; | |||
import org.sonar.api.server.ws.Response; | |||
import org.sonar.api.server.ws.WebService; | |||
import org.sonar.auth.github.GitHubSettings; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.alm.pat.AlmPatDto; | |||
import org.sonar.db.alm.setting.ALM; | |||
import org.sonar.db.alm.setting.AlmSettingDto; | |||
import org.sonar.db.alm.setting.ProjectAlmSettingDto; | |||
import org.sonar.db.component.BranchDto; | |||
import org.sonar.db.project.ProjectDto; | |||
import org.sonar.server.almintegration.ws.AlmIntegrationsWsAction; | |||
import org.sonar.server.almintegration.ws.ImportHelper; | |||
import org.sonar.server.almintegration.ws.ProjectKeyGenerator; | |||
import org.sonar.server.almsettings.ws.DevOpsProjectDescriptor; | |||
import org.sonar.server.almsettings.ws.GitHubDevOpsPlatformService; | |||
import org.sonar.server.component.ComponentCreationData; | |||
import org.sonar.server.component.ComponentCreationParameters; | |||
import org.sonar.server.component.ComponentUpdater; | |||
import org.sonar.server.component.NewComponent; | |||
import org.sonar.server.exceptions.NotFoundException; | |||
import org.sonar.server.management.ManagedProjectService; | |||
import org.sonar.server.newcodeperiod.NewCodeDefinitionResolver; | |||
import org.sonar.server.project.DefaultBranchNameResolver; | |||
import org.sonar.server.project.ProjectDefaultVisibility; | |||
import org.sonar.server.user.UserSession; | |||
import org.sonarqube.ws.Projects; | |||
import static java.util.Objects.requireNonNull; | |||
import static org.sonar.api.resources.Qualifiers.PROJECT; | |||
import static org.sonar.db.project.CreationMethod.Category.ALM_IMPORT; | |||
import static org.sonar.db.project.CreationMethod.getCreationMethod; | |||
import static org.sonar.server.almintegration.ws.ImportHelper.PARAM_ALM_SETTING; | |||
import static org.sonar.server.almintegration.ws.ImportHelper.toCreateResponse; | |||
import static org.sonar.server.component.NewComponent.newComponentBuilder; | |||
import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION; | |||
import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.NEW_CODE_PERIOD_VALUE_DESCRIPTION_PROJECT_CREATION; | |||
import static org.sonar.server.newcodeperiod.NewCodeDefinitionResolver.checkNewCodeDefinitionParam; | |||
@@ -70,54 +57,48 @@ import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_COD | |||
import static org.sonarqube.ws.client.project.ProjectsWsParameters.PARAM_NEW_CODE_DEFINITION_VALUE; | |||
public class ImportGithubProjectAction implements AlmIntegrationsWsAction { | |||
public static final String PARAM_ORGANIZATION = "organization"; | |||
public static final String PARAM_REPOSITORY_KEY = "repositoryKey"; | |||
private final DbClient dbClient; | |||
private final ManagedProjectService managedProjectService; | |||
private final UserSession userSession; | |||
private final ProjectDefaultVisibility projectDefaultVisibility; | |||
private final GithubApplicationClient githubApplicationClient; | |||
private final ComponentUpdater componentUpdater; | |||
private final ImportHelper importHelper; | |||
private final ProjectKeyGenerator projectKeyGenerator; | |||
private final NewCodeDefinitionResolver newCodeDefinitionResolver; | |||
private final DefaultBranchNameResolver defaultBranchNameResolver; | |||
private final GitHubSettings gitHubSettings; | |||
private final GitHubDevOpsPlatformService gitHubDevOpsPlatformService; | |||
@Inject | |||
public ImportGithubProjectAction(DbClient dbClient, ManagedProjectService managedProjectService, UserSession userSession, ProjectDefaultVisibility projectDefaultVisibility, | |||
GithubApplicationClientImpl githubApplicationClient, ComponentUpdater componentUpdater, ImportHelper importHelper, | |||
ProjectKeyGenerator projectKeyGenerator, NewCodeDefinitionResolver newCodeDefinitionResolver, | |||
DefaultBranchNameResolver defaultBranchNameResolver, GitHubSettings gitHubSettings) { | |||
public ImportGithubProjectAction(DbClient dbClient, ManagedProjectService managedProjectService, UserSession userSession, | |||
ComponentUpdater componentUpdater, ImportHelper importHelper, | |||
NewCodeDefinitionResolver newCodeDefinitionResolver, | |||
DefaultBranchNameResolver defaultBranchNameResolver, GitHubDevOpsPlatformService gitHubDevOpsPlatformService) { | |||
this.dbClient = dbClient; | |||
this.managedProjectService = managedProjectService; | |||
this.userSession = userSession; | |||
this.projectDefaultVisibility = projectDefaultVisibility; | |||
this.githubApplicationClient = githubApplicationClient; | |||
this.componentUpdater = componentUpdater; | |||
this.importHelper = importHelper; | |||
this.projectKeyGenerator = projectKeyGenerator; | |||
this.newCodeDefinitionResolver = newCodeDefinitionResolver; | |||
this.defaultBranchNameResolver = defaultBranchNameResolver; | |||
this.gitHubSettings = gitHubSettings; | |||
this.gitHubDevOpsPlatformService = gitHubDevOpsPlatformService; | |||
} | |||
@Override | |||
public void define(WebService.NewController context) { | |||
WebService.NewAction action = context.createAction("import_github_project") | |||
.setDescription("Create a SonarQube project with the information from the provided GitHub repository.<br/>" + | |||
"Autoconfigure pull request decoration mechanism. If Automatic Provisioning is enable for GitHub, it will also synchronize permissions from the repository.<br/>" + | |||
"Requires the 'Create Projects' permission") | |||
"Autoconfigure pull request decoration mechanism. If Automatic Provisioning is enable for GitHub, " + | |||
"it will also synchronize permissions from the repository.<br/>" + | |||
"Requires the 'Create Projects' permission") | |||
.setPost(true) | |||
.setSince("8.4") | |||
.setHandler(this) | |||
.setChangelog( | |||
new Change("10.3", "Parameter organization is not necessary anymore"), | |||
new Change("10.3", String.format("Parameter %s becomes optional if you have only one configuration for GitHub", PARAM_ALM_SETTING)), | |||
new Change("10.3", "Endpoint visibility change from internal to public")); | |||
@@ -125,15 +106,10 @@ public class ImportGithubProjectAction implements AlmIntegrationsWsAction { | |||
.setMaximumLength(200) | |||
.setDescription("DevOps Platform configuration key. This parameter is optional if you have only one GitHub integration."); | |||
action.createParam(PARAM_ORGANIZATION) | |||
.setRequired(true) | |||
.setMaximumLength(200) | |||
.setDescription("GitHub organization"); | |||
action.createParam(PARAM_REPOSITORY_KEY) | |||
.setRequired(true) | |||
.setMaximumLength(256) | |||
.setDescription("GitHub repository key"); | |||
.setDescription("GitHub repository key (organization/repoSlug"); | |||
action.createParam(PARAM_NEW_CODE_DEFINITION_TYPE) | |||
.setDescription(NEW_CODE_PERIOD_TYPE_DESCRIPTION_PROJECT_CREATION) | |||
@@ -160,30 +136,27 @@ public class ImportGithubProjectAction implements AlmIntegrationsWsAction { | |||
AccessToken accessToken = getAccessToken(dbSession, almSettingDto); | |||
String githubOrganization = request.mandatoryParam(PARAM_ORGANIZATION); | |||
String repositoryKey = request.mandatoryParam(PARAM_REPOSITORY_KEY); | |||
String url = requireNonNull(almSettingDto.getUrl(), "DevOps Platform url cannot be null"); | |||
Repository repository = githubApplicationClient.getRepository(url, accessToken, githubOrganization, repositoryKey) | |||
.orElseThrow(() -> new NotFoundException(String.format("GitHub repository '%s' not found", repositoryKey))); | |||
DevOpsProjectDescriptor devOpsProjectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, url, repositoryKey); | |||
ComponentCreationData componentCreationData = gitHubDevOpsPlatformService.createProjectAndBindToDevOpsPlatform(dbSession, almSettingDto, accessToken, | |||
devOpsProjectDescriptor); | |||
checkNewCodeDefinitionParam(newCodeDefinitionType, newCodeDefinitionValue); | |||
ComponentCreationData componentCreationData = createProject(dbSession, repository, repository.getDefaultBranch()); | |||
ProjectDto projectDto = Optional.ofNullable(componentCreationData.projectDto()).orElseThrow(); | |||
BranchDto mainBranchDto = Optional.ofNullable(componentCreationData.mainBranchDto()).orElseThrow(); | |||
populatePRSetting(dbSession, repository, projectDto, almSettingDto); | |||
checkNewCodeDefinitionParam(newCodeDefinitionType, newCodeDefinitionValue); | |||
if (newCodeDefinitionType != null) { | |||
newCodeDefinitionResolver.createNewCodeDefinition(dbSession, projectDto.getUuid(), mainBranchDto.getUuid(), | |||
Optional.ofNullable(repository.getDefaultBranch()).orElse(defaultBranchNameResolver.getEffectiveMainBranchName()), | |||
Optional.ofNullable(mainBranchDto.getKey()).orElse(defaultBranchNameResolver.getEffectiveMainBranchName()), | |||
newCodeDefinitionType, newCodeDefinitionValue); | |||
} | |||
componentUpdater.commitAndIndex(dbSession, componentCreationData); | |||
String userUuid = Objects.requireNonNull(userSession.getUuid()); | |||
String userUuid = requireNonNull(userSession.getUuid()); | |||
managedProjectService.queuePermissionSyncTask(userUuid, mainBranchDto.getUuid(), projectDto.getUuid()); | |||
return toCreateResponse(projectDto); | |||
@@ -198,35 +171,4 @@ public class ImportGithubProjectAction implements AlmIntegrationsWsAction { | |||
.orElseThrow(() -> new IllegalArgumentException("No personal access token found")); | |||
} | |||
private ComponentCreationData createProject(DbSession dbSession, Repository repo, String mainBranchName) { | |||
boolean visibility = projectDefaultVisibility.get(dbSession).isPrivate(); | |||
String uniqueProjectKey = projectKeyGenerator.generateUniqueProjectKey(repo.getFullName()); | |||
NewComponent projectComponent = newComponentBuilder() | |||
.setKey(uniqueProjectKey) | |||
.setName(repo.getName()) | |||
.setPrivate(visibility) | |||
.setQualifier(PROJECT) | |||
.build(); | |||
ComponentCreationParameters componentCreationParameters = ComponentCreationParameters.builder() | |||
.newComponent(projectComponent) | |||
.userLogin(userSession.getLogin()) | |||
.userUuid(userSession.getUuid()) | |||
.mainBranchName(mainBranchName) | |||
.isManaged(gitHubSettings.isProvisioningEnabled()) | |||
.creationMethod(getCreationMethod(ALM_IMPORT, userSession.isAuthenticatedBrowserSession())) | |||
.build(); | |||
return componentUpdater.createWithoutCommit(dbSession, componentCreationParameters); | |||
} | |||
private void populatePRSetting(DbSession dbSession, Repository repo, ProjectDto projectDto, AlmSettingDto almSettingDto) { | |||
ProjectAlmSettingDto projectAlmSettingDto = new ProjectAlmSettingDto() | |||
.setAlmSettingUuid(almSettingDto.getUuid()) | |||
.setAlmRepo(repo.getFullName()) | |||
.setAlmSlug(null) | |||
.setProjectUuid(projectDto.getUuid()) | |||
.setSummaryCommentEnabled(true) | |||
.setMonorepo(false); | |||
dbClient.projectAlmSettingDao().insertOrUpdate(dbSession, projectAlmSettingDto, almSettingDto.getKey(), | |||
projectDto.getName(), projectDto.getKey()); | |||
} | |||
} |
@@ -24,10 +24,12 @@ import java.util.Optional; | |||
import java.util.Set; | |||
import javax.annotation.Priority; | |||
import org.apache.commons.lang.NotImplementedException; | |||
import org.sonar.alm.client.github.security.AccessToken; | |||
import org.sonar.api.server.ServerSide; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.alm.setting.ALM; | |||
import org.sonar.db.alm.setting.AlmSettingDto; | |||
import org.sonar.server.component.ComponentCreationData; | |||
@ServerSide | |||
@Priority(1) | |||
@@ -57,6 +59,22 @@ public class DelegatingDevOpsPlatformService implements DevOpsPlatformService { | |||
.flatMap(delegate -> delegate.getValidAlmSettingDto(dbSession, devOpsProjectDescriptor)); | |||
} | |||
@Override | |||
public ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, String projectKey, AlmSettingDto almSettingDto, | |||
DevOpsProjectDescriptor devOpsProjectDescriptor) { | |||
return findDelegate(almSettingDto.getAlm()) | |||
.map(delegate -> delegate.createProjectAndBindToDevOpsPlatform(dbSession, projectKey, almSettingDto, devOpsProjectDescriptor)) | |||
.orElseThrow(() -> new IllegalStateException("Impossible to bind project to ALM platform " + almSettingDto.getAlm())); | |||
} | |||
@Override | |||
public ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, AlmSettingDto almSettingDto, AccessToken accessToken, | |||
DevOpsProjectDescriptor devOpsProjectDescriptor) { | |||
return findDelegate(almSettingDto.getAlm()) | |||
.map(delegate -> delegate.createProjectAndBindToDevOpsPlatform(dbSession, almSettingDto, accessToken, devOpsProjectDescriptor)) | |||
.orElseThrow(() -> new IllegalStateException("Impossible to bind project to ALM platform " + almSettingDto.getAlm())); | |||
} | |||
private Optional<DevOpsPlatformService> findDelegate(ALM alm) { | |||
return delegates.stream() | |||
.filter(delegate -> delegate.getDevOpsPlatform().equals(alm)) |
@@ -21,9 +21,11 @@ package org.sonar.server.almsettings.ws; | |||
import java.util.Map; | |||
import java.util.Optional; | |||
import org.sonar.alm.client.github.security.AccessToken; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.alm.setting.ALM; | |||
import org.sonar.db.alm.setting.AlmSettingDto; | |||
import org.sonar.server.component.ComponentCreationData; | |||
public interface DevOpsPlatformService { | |||
@@ -33,4 +35,8 @@ public interface DevOpsPlatformService { | |||
Optional<AlmSettingDto> getValidAlmSettingDto(DbSession dbSession, DevOpsProjectDescriptor devOpsProjectDescriptor); | |||
ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, String projectKey, AlmSettingDto almSettingDto, DevOpsProjectDescriptor devOpsProjectDescriptor); | |||
ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, AlmSettingDto almSettingDto, AccessToken accessToken, | |||
DevOpsProjectDescriptor devOpsProjectDescriptor); | |||
} |
@@ -21,30 +21,67 @@ package org.sonar.server.almsettings.ws; | |||
import java.util.Map; | |||
import java.util.Optional; | |||
import javax.annotation.Nullable; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import org.sonar.alm.client.github.AppInstallationToken; | |||
import org.sonar.alm.client.github.GithubApplicationClient; | |||
import org.sonar.alm.client.github.GithubGlobalSettingsValidator; | |||
import org.sonar.alm.client.github.config.GithubAppConfiguration; | |||
import org.sonar.alm.client.github.security.AccessToken; | |||
import org.sonar.api.server.ServerSide; | |||
import org.sonar.auth.github.GitHubSettings; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.alm.setting.ALM; | |||
import org.sonar.db.alm.setting.AlmSettingDao; | |||
import org.sonar.db.alm.setting.AlmSettingDto; | |||
import org.sonar.db.alm.setting.ProjectAlmSettingDto; | |||
import org.sonar.db.project.CreationMethod; | |||
import org.sonar.db.project.ProjectDto; | |||
import org.sonar.server.almintegration.ws.ProjectKeyGenerator; | |||
import org.sonar.server.component.ComponentCreationData; | |||
import org.sonar.server.component.ComponentCreationParameters; | |||
import org.sonar.server.component.ComponentUpdater; | |||
import org.sonar.server.component.NewComponent; | |||
import org.sonar.server.exceptions.NotFoundException; | |||
import org.sonar.server.project.ProjectDefaultVisibility; | |||
import org.sonar.server.user.UserSession; | |||
import static java.lang.String.format; | |||
import static java.util.Objects.requireNonNull; | |||
import static org.sonar.api.resources.Qualifiers.PROJECT; | |||
import static org.sonar.db.project.CreationMethod.Category.ALM_IMPORT; | |||
import static org.sonar.db.project.CreationMethod.SCANNER_API_DEVOPS_AUTO_CONFIG; | |||
import static org.sonar.db.project.CreationMethod.getCreationMethod; | |||
import static org.sonar.server.component.NewComponent.newComponentBuilder; | |||
@ServerSide | |||
public class GitHubDevOpsPlatformService implements DevOpsPlatformService { | |||
private static final Logger LOG = LoggerFactory.getLogger(GitHubDevOpsPlatformService.class); | |||
public static final String DEVOPS_PLATFORM_URL = "devOpsPlatformUrl"; | |||
public static final String DEVOPS_PLATFORM_PROJECT_IDENTIFIER = "devOpsPlatformProjectIdentifier"; | |||
private final AlmSettingDao almSettingDao; | |||
private final DbClient dbClient; | |||
private final GithubGlobalSettingsValidator githubGlobalSettingsValidator; | |||
private final GithubApplicationClient githubApplicationClient; | |||
private final ProjectDefaultVisibility projectDefaultVisibility; | |||
private final ProjectKeyGenerator projectKeyGenerator; | |||
private final UserSession userSession; | |||
private final ComponentUpdater componentUpdater; | |||
private final GitHubSettings gitHubSettings; | |||
public GitHubDevOpsPlatformService(AlmSettingDao almSettingDao, GithubGlobalSettingsValidator githubGlobalSettingsValidator, | |||
GithubApplicationClient githubApplicationClient) { | |||
this.almSettingDao = almSettingDao; | |||
public GitHubDevOpsPlatformService(DbClient dbClient, GithubGlobalSettingsValidator githubGlobalSettingsValidator, | |||
GithubApplicationClient githubApplicationClient, ProjectDefaultVisibility projectDefaultVisibility, ProjectKeyGenerator projectKeyGenerator, UserSession userSession, | |||
ComponentUpdater componentUpdater, GitHubSettings gitHubSettings) { | |||
this.dbClient = dbClient; | |||
this.githubGlobalSettingsValidator = githubGlobalSettingsValidator; | |||
this.githubApplicationClient = githubApplicationClient; | |||
this.projectDefaultVisibility = projectDefaultVisibility; | |||
this.projectKeyGenerator = projectKeyGenerator; | |||
this.userSession = userSession; | |||
this.componentUpdater = componentUpdater; | |||
this.gitHubSettings = gitHubSettings; | |||
} | |||
@Override | |||
@@ -64,15 +101,104 @@ public class GitHubDevOpsPlatformService implements DevOpsPlatformService { | |||
@Override | |||
public Optional<AlmSettingDto> getValidAlmSettingDto(DbSession dbSession, DevOpsProjectDescriptor devOpsProjectDescriptor) { | |||
return almSettingDao.selectByAlm(dbSession, getDevOpsPlatform()).stream() | |||
Optional<AlmSettingDto> configurationToUse = dbClient.almSettingDao().selectByAlm(dbSession, getDevOpsPlatform()).stream() | |||
.filter(almSettingDto -> devOpsProjectDescriptor.url().equals(almSettingDto.getUrl())) | |||
.filter(almSettingDto -> hasAccessToRepo(almSettingDto, devOpsProjectDescriptor.projectIdentifier())) | |||
.filter(almSettingDto -> findInstallationIdToAccessRepo(almSettingDto, devOpsProjectDescriptor.projectIdentifier()).isPresent()) | |||
.findFirst(); | |||
if (configurationToUse.isPresent()) { | |||
LOG.info("DevOps configuration {} auto-detected", configurationToUse.get().getKey()); | |||
} else { | |||
LOG.info("Could not auto-detect a DevOps configuration for project {} (api url {})", | |||
devOpsProjectDescriptor.projectIdentifier(), devOpsProjectDescriptor.url()); | |||
} | |||
return configurationToUse; | |||
} | |||
private boolean hasAccessToRepo(AlmSettingDto almSettingDto, String repo) { | |||
@Override | |||
public ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, String projectKey, AlmSettingDto almSettingDto, | |||
DevOpsProjectDescriptor devOpsProjectDescriptor) { | |||
GithubAppConfiguration githubAppConfiguration = githubGlobalSettingsValidator.validate(almSettingDto); | |||
return githubApplicationClient.getInstallationId(githubAppConfiguration, repo).isPresent(); | |||
GithubApplicationClient.Repository repository = findInstallationIdToAccessRepo(almSettingDto, devOpsProjectDescriptor.projectIdentifier()) | |||
.flatMap(installationId -> findRepositoryOnGithub(devOpsProjectDescriptor.projectIdentifier(), githubAppConfiguration, installationId)) | |||
.orElseThrow(() -> new IllegalStateException(format("Impossible to find the repository %s on GitHub, using the devops config %s.", | |||
devOpsProjectDescriptor.projectIdentifier(), almSettingDto.getKey()))); | |||
return createProjectAndBindToDevOpsPlatform(dbSession, projectKey, almSettingDto, repository, SCANNER_API_DEVOPS_AUTO_CONFIG); | |||
} | |||
private Optional<Long> findInstallationIdToAccessRepo(AlmSettingDto almSettingDto, String repositoryKey) { | |||
try { | |||
GithubAppConfiguration githubAppConfiguration = githubGlobalSettingsValidator.validate(almSettingDto); | |||
return githubApplicationClient.getInstallationId(githubAppConfiguration, repositoryKey); | |||
} catch (Exception exception) { | |||
LOG.info(format("Could not use DevOps configuration '%s' to access repo %s. Error: %s", almSettingDto.getKey(), repositoryKey, exception.getMessage())); | |||
return Optional.empty(); | |||
} | |||
} | |||
private Optional<GithubApplicationClient.Repository> findRepositoryOnGithub(String organizationAndRepository, | |||
GithubAppConfiguration githubAppConfiguration, long installationId) { | |||
AppInstallationToken accessToken = generateAppInstallationToken(githubAppConfiguration, installationId); | |||
return githubApplicationClient.getRepository(githubAppConfiguration.getApiEndpoint(), accessToken, organizationAndRepository); | |||
} | |||
private AppInstallationToken generateAppInstallationToken(GithubAppConfiguration githubAppConfiguration, long installationId) { | |||
return githubApplicationClient.createAppInstallationToken(githubAppConfiguration, installationId) | |||
.orElseThrow(() -> new IllegalStateException(format("Error while generating token for GitHub Api Url %s (installation id: %s)", | |||
githubAppConfiguration.getApiEndpoint(), installationId))); | |||
} | |||
@Override | |||
public ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, AlmSettingDto almSettingDto, AccessToken accessToken, | |||
DevOpsProjectDescriptor devOpsProjectDescriptor) { | |||
String url = requireNonNull(almSettingDto.getUrl(), "DevOps Platform url cannot be null"); | |||
GithubApplicationClient.Repository repository = githubApplicationClient.getRepository(url, accessToken, devOpsProjectDescriptor.projectIdentifier()) | |||
.orElseThrow(() -> new NotFoundException(String.format("GitHub repository '%s' not found", devOpsProjectDescriptor))); | |||
CreationMethod creationMethod = getCreationMethod(ALM_IMPORT, userSession.isAuthenticatedBrowserSession()); | |||
return createProjectAndBindToDevOpsPlatform(dbSession, null, almSettingDto, repository, creationMethod); | |||
} | |||
private ComponentCreationData createProjectAndBindToDevOpsPlatform(DbSession dbSession, @Nullable String projectKey, AlmSettingDto almSettingDto, | |||
GithubApplicationClient.Repository repository, CreationMethod creationMethod) { | |||
ComponentCreationData componentCreationData = createProject(dbSession, projectKey, repository, creationMethod); | |||
ProjectDto projectDto = Optional.ofNullable(componentCreationData.projectDto()).orElseThrow(); | |||
createProjectAlmSettingDto(dbSession, repository, projectDto, almSettingDto); | |||
return componentCreationData; | |||
} | |||
private ComponentCreationData createProject(DbSession dbSession, @Nullable String projectKey, GithubApplicationClient.Repository repository, CreationMethod creationMethod) { | |||
boolean visibility = projectDefaultVisibility.get(dbSession).isPrivate(); | |||
NewComponent projectComponent = newComponentBuilder() | |||
.setKey(Optional.ofNullable(projectKey).orElse(getUniqueProjectKey(repository))) | |||
.setName(repository.getName()) | |||
.setPrivate(visibility) | |||
.setQualifier(PROJECT) | |||
.build(); | |||
ComponentCreationParameters componentCreationParameters = ComponentCreationParameters.builder() | |||
.newComponent(projectComponent) | |||
.userLogin(userSession.getLogin()) | |||
.userUuid(userSession.getUuid()) | |||
.mainBranchName(repository.getDefaultBranch()) | |||
.isManaged(gitHubSettings.isProvisioningEnabled()) | |||
.creationMethod(creationMethod) | |||
.build(); | |||
return componentUpdater.createWithoutCommit(dbSession, componentCreationParameters); | |||
} | |||
private String getUniqueProjectKey(GithubApplicationClient.Repository repository) { | |||
return projectKeyGenerator.generateUniqueProjectKey(repository.getFullName()); | |||
} | |||
private void createProjectAlmSettingDto(DbSession dbSession, GithubApplicationClient.Repository repo, ProjectDto projectDto, AlmSettingDto almSettingDto) { | |||
ProjectAlmSettingDto projectAlmSettingDto = new ProjectAlmSettingDto() | |||
.setAlmSettingUuid(almSettingDto.getUuid()) | |||
.setAlmRepo(repo.getFullName()) | |||
.setAlmSlug(null) | |||
.setProjectUuid(projectDto.getUuid()) | |||
.setSummaryCommentEnabled(true) | |||
.setMonorepo(false); | |||
dbClient.projectAlmSettingDao().insertOrUpdate(dbSession, projectAlmSettingDto, almSettingDto.getKey(), projectDto.getName(), projectDto.getKey()); | |||
} | |||
} |
@@ -22,6 +22,7 @@ package org.sonar.server.ce.queue; | |||
import java.util.Map; | |||
import java.util.Objects; | |||
import java.util.Optional; | |||
import java.util.Set; | |||
import javax.annotation.CheckForNull; | |||
import javax.annotation.Nullable; | |||
import org.sonar.api.server.ServerSide; | |||
@@ -30,6 +31,9 @@ import org.sonar.db.component.BranchDto; | |||
import org.sonar.db.component.ComponentDto; | |||
import static com.google.common.base.Preconditions.checkState; | |||
import static org.sonar.core.ce.CeTaskCharacteristics.BRANCH; | |||
import static org.sonar.core.ce.CeTaskCharacteristics.BRANCH_TYPE; | |||
import static org.sonar.core.ce.CeTaskCharacteristics.PULL_REQUEST; | |||
/** | |||
* Branch code for {@link ReportSubmitter}. | |||
@@ -38,6 +42,7 @@ import static com.google.common.base.Preconditions.checkState; | |||
*/ | |||
@ServerSide | |||
public class BranchSupport { | |||
private static final Set<String> BRANCH_CHARACTERISTICS = Set.of(BRANCH, BRANCH_TYPE, PULL_REQUEST); | |||
@CheckForNull | |||
private final BranchSupportDelegate delegate; | |||
@@ -46,13 +51,12 @@ public class BranchSupport { | |||
} | |||
ComponentKey createComponentKey(String projectKey, Map<String, String> characteristics) { | |||
if (characteristics.isEmpty()) { | |||
return new ComponentKeyImpl(projectKey); | |||
} else { | |||
boolean containsBranchCharacteristics = characteristics.keySet().stream().anyMatch(BRANCH_CHARACTERISTICS::contains); | |||
if (containsBranchCharacteristics) { | |||
checkState(delegate != null, "Current edition does not support branch feature"); | |||
return delegate.createComponentKey(projectKey, characteristics); | |||
} | |||
return delegate.createComponentKey(projectKey, characteristics); | |||
return new ComponentKeyImpl(projectKey); | |||
} | |||
ComponentDto createBranchComponent(DbSession dbSession, ComponentKey componentKey, ComponentDto mainComponentDto, BranchDto mainComponentBranchDto) { |
@@ -34,10 +34,13 @@ import org.sonar.ce.queue.CeTaskSubmit; | |||
import org.sonar.ce.task.CeTask; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.alm.setting.AlmSettingDto; | |||
import org.sonar.db.ce.CeTaskTypes; | |||
import org.sonar.db.component.BranchDto; | |||
import org.sonar.db.component.ComponentDto; | |||
import org.sonar.db.permission.GlobalPermission; | |||
import org.sonar.server.almsettings.ws.DevOpsPlatformService; | |||
import org.sonar.server.almsettings.ws.DevOpsProjectDescriptor; | |||
import org.sonar.server.component.ComponentCreationData; | |||
import org.sonar.server.component.ComponentCreationParameters; | |||
import org.sonar.server.component.ComponentUpdater; | |||
@@ -45,7 +48,6 @@ import org.sonar.server.component.NewComponent; | |||
import org.sonar.server.exceptions.BadRequestException; | |||
import org.sonar.server.permission.PermissionTemplateService; | |||
import org.sonar.server.project.ProjectDefaultVisibility; | |||
import org.sonar.server.project.Visibility; | |||
import org.sonar.server.user.UserSession; | |||
import static java.lang.String.format; | |||
@@ -64,9 +66,11 @@ public class ReportSubmitter { | |||
private final DbClient dbClient; | |||
private final BranchSupport branchSupport; | |||
private final ProjectDefaultVisibility projectDefaultVisibility; | |||
private final DevOpsPlatformService devOpsPlatformService; | |||
public ReportSubmitter(CeQueue queue, UserSession userSession, ComponentUpdater componentUpdater, | |||
PermissionTemplateService permissionTemplateService, DbClient dbClient, BranchSupport branchSupport, ProjectDefaultVisibility projectDefaultVisibility) { | |||
PermissionTemplateService permissionTemplateService, DbClient dbClient, BranchSupport branchSupport, ProjectDefaultVisibility projectDefaultVisibility, | |||
DevOpsPlatformService devOpsPlatformService) { | |||
this.queue = queue; | |||
this.userSession = userSession; | |||
this.componentUpdater = componentUpdater; | |||
@@ -74,6 +78,7 @@ public class ReportSubmitter { | |||
this.dbClient = dbClient; | |||
this.branchSupport = branchSupport; | |||
this.projectDefaultVisibility = projectDefaultVisibility; | |||
this.devOpsPlatformService = devOpsPlatformService; | |||
} | |||
public CeTask submit(String projectKey, @Nullable String projectName, Map<String, String> characteristics, InputStream reportInput) { | |||
@@ -89,7 +94,7 @@ public class ReportSubmitter { | |||
mainBranchComponent = mainBranchComponentOpt.get(); | |||
validateProject(dbSession, mainBranchComponent, projectKey); | |||
} else { | |||
componentCreationData = createProject(dbSession, componentKey.getKey(), projectName); | |||
componentCreationData = createProject(projectKey, projectName, characteristics, dbSession, componentKey); | |||
mainBranchComponent = componentCreationData.mainBranchComponent(); | |||
} | |||
@@ -98,7 +103,7 @@ public class ReportSubmitter { | |||
ComponentDto branchComponent; | |||
if (isMainBranch(componentKey, mainBranch)) { | |||
branchComponent = mainBranchComponent; | |||
} else if(componentKey.getBranchName().isPresent()) { | |||
} else if (componentKey.getBranchName().isPresent()) { | |||
branchComponent = dbClient.componentDao().selectByKeyAndBranch(dbSession, componentKey.getKey(), componentKey.getBranchName().get()) | |||
.orElseGet(() -> branchSupport.createBranchComponent(dbSession, componentKey, mainBranchComponent, mainBranch)); | |||
} else { | |||
@@ -154,35 +159,39 @@ public class ReportSubmitter { | |||
} | |||
} | |||
private ComponentCreationData createProject(DbSession dbSession, String projectKey, @Nullable String projectName) { | |||
private ComponentCreationData createProject(String projectKey, @Nullable String projectName, Map<String, String> characteristics, | |||
DbSession dbSession, BranchSupport.ComponentKey componentKey) { | |||
userSession.checkPermission(GlobalPermission.PROVISION_PROJECTS); | |||
String userUuid = userSession.getUuid(); | |||
String userName = userSession.getLogin(); | |||
boolean wouldCurrentUserHaveScanPermission = permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(dbSession, userUuid, projectKey); | |||
boolean wouldCurrentUserHaveScanPermission = permissionTemplateService.wouldUserHaveScanPermissionWithDefaultTemplate(dbSession, userSession.getUuid(), projectKey); | |||
if (!wouldCurrentUserHaveScanPermission) { | |||
throw insufficientPrivilegesException(); | |||
} | |||
Optional<DevOpsProjectDescriptor> devOpsProjectDescriptor = devOpsPlatformService.getDevOpsProjectDescriptor(characteristics); | |||
Optional<AlmSettingDto> almSettingDto = devOpsProjectDescriptor.flatMap(descriptor -> devOpsPlatformService.getValidAlmSettingDto(dbSession, descriptor)); | |||
if (almSettingDto.isPresent()) { | |||
return devOpsPlatformService.createProjectAndBindToDevOpsPlatform(dbSession, projectKey, almSettingDto.get(), devOpsProjectDescriptor.get()); | |||
} | |||
return createProject(dbSession, componentKey.getKey(), defaultIfBlank(projectName, projectKey)); | |||
} | |||
private ComponentCreationData createProject(DbSession dbSession, String projectKey, String projectName) { | |||
NewComponent newProject = newComponentBuilder() | |||
.setKey(projectKey) | |||
.setName(defaultIfBlank(projectName, projectKey)) | |||
.setQualifier(Qualifiers.PROJECT) | |||
.setPrivate(getDefaultVisibility(dbSession).isPrivate()) | |||
.setPrivate(projectDefaultVisibility.get(dbSession).isPrivate()) | |||
.build(); | |||
ComponentCreationParameters componentCreationParameters = ComponentCreationParameters.builder() | |||
.newComponent(newProject) | |||
.userLogin(userName) | |||
.userUuid(userUuid) | |||
.userLogin(userSession.getLogin()) | |||
.userUuid(userSession.getUuid()) | |||
.creationMethod(SCANNER_API) | |||
.build(); | |||
return componentUpdater.createWithoutCommit(dbSession, componentCreationParameters); | |||
} | |||
private Visibility getDefaultVisibility(DbSession dbSession) { | |||
return projectDefaultVisibility.get(dbSession); | |||
} | |||
private CeTask submitReport(DbSession dbSession, InputStream reportInput, ComponentDto branch, BranchDto mainBranch, Map<String, String> characteristics) { | |||
CeTaskSubmit.Builder submit = queue.prepareSubmit(); | |||
@@ -22,45 +22,107 @@ package org.sonar.server.almsettings.ws; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.Optional; | |||
import org.junit.Before; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.runner.RunWith; | |||
import org.mockito.Answers; | |||
import org.mockito.ArgumentCaptor; | |||
import org.mockito.Captor; | |||
import org.mockito.InjectMocks; | |||
import org.mockito.Mock; | |||
import org.mockito.junit.MockitoJUnitRunner; | |||
import org.sonar.alm.client.github.AppInstallationToken; | |||
import org.sonar.alm.client.github.GithubApplicationClient; | |||
import org.sonar.alm.client.github.GithubGlobalSettingsValidator; | |||
import org.sonar.alm.client.github.config.GithubAppConfiguration; | |||
import org.sonar.api.resources.Qualifiers; | |||
import org.sonar.api.testfixtures.log.LogTester; | |||
import org.sonar.api.utils.log.LoggerLevel; | |||
import org.sonar.auth.github.GitHubSettings; | |||
import org.sonar.db.DbClient; | |||
import org.sonar.db.DbSession; | |||
import org.sonar.db.alm.setting.ALM; | |||
import org.sonar.db.alm.setting.AlmSettingDao; | |||
import org.sonar.db.alm.setting.AlmSettingDto; | |||
import org.sonar.db.alm.setting.ProjectAlmSettingDao; | |||
import org.sonar.db.alm.setting.ProjectAlmSettingDto; | |||
import org.sonar.db.project.CreationMethod; | |||
import org.sonar.db.project.ProjectDto; | |||
import org.sonar.server.almintegration.ws.ProjectKeyGenerator; | |||
import org.sonar.server.component.ComponentCreationData; | |||
import org.sonar.server.component.ComponentCreationParameters; | |||
import org.sonar.server.component.ComponentUpdater; | |||
import org.sonar.server.component.NewComponent; | |||
import org.sonar.server.project.ProjectDefaultVisibility; | |||
import org.sonar.server.project.Visibility; | |||
import org.sonar.server.user.UserSession; | |||
import static java.util.Collections.emptyList; | |||
import static java.util.Objects.requireNonNull; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; | |||
import static org.mockito.ArgumentMatchers.any; | |||
import static org.mockito.ArgumentMatchers.anyLong; | |||
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.server.almsettings.ws.GitHubDevOpsPlatformService.DEVOPS_PLATFORM_PROJECT_IDENTIFIER; | |||
import static org.sonar.server.almsettings.ws.GitHubDevOpsPlatformService.DEVOPS_PLATFORM_URL; | |||
@RunWith(MockitoJUnitRunner.class) | |||
public class GitHubDevOpsPlatformServiceTest { | |||
@Rule | |||
public LogTester logTester = new LogTester().setLevel(LoggerLevel.WARN); | |||
private static final DevOpsProjectDescriptor GITHUB_PROJECT_DESCRIPTOR = new DevOpsProjectDescriptor(ALM.GITHUB, "url", "repo"); | |||
private static final long APP_INSTALLATION_ID = 534534534543L; | |||
private static final String USER_LOGIN = "user-login-1"; | |||
private static final String USER_UUID = "user-uuid-1"; | |||
private static final String PROJECT_KEY = "projectKey"; | |||
private static final String PROJECT_NAME = "projectName"; | |||
private static final String MAIN_BRANCH_NAME = "defaultBranch"; | |||
private static final String ORGANIZATION_NAME = "orgname"; | |||
private static final String GITHUB_REPO_FULL_NAME = ORGANIZATION_NAME + "/" + PROJECT_NAME; | |||
private static final String GITHUB_API_URL = "https://api.toto.com"; | |||
@Mock | |||
private DbSession dbSession; | |||
@Mock | |||
private AlmSettingDao almSettingDao; | |||
@Mock | |||
private GithubGlobalSettingsValidator githubGlobalSettingsValidator; | |||
@Mock | |||
private GithubApplicationClient githubApplicationClient; | |||
@Mock | |||
private ComponentUpdater componentUpdater; | |||
@Mock(answer = Answers.RETURNS_DEEP_STUBS) | |||
private DbClient dbClient; | |||
@Mock | |||
private UserSession userSession; | |||
@Mock(answer = Answers.RETURNS_DEEP_STUBS) | |||
private ProjectDefaultVisibility projectDefaultVisibility; | |||
@Mock | |||
private ProjectKeyGenerator projectKeyGenerator; | |||
@Mock | |||
private GitHubSettings gitHubSettings; | |||
@InjectMocks | |||
private GitHubDevOpsPlatformService gitHubDevOpsPlatformService; | |||
@Captor | |||
ArgumentCaptor<ComponentCreationParameters> componentCreationParametersCaptor; | |||
@Captor | |||
ArgumentCaptor<ProjectAlmSettingDto> projectAlmSettingDtoCaptor; | |||
@Before | |||
public void setup() { | |||
when(userSession.getLogin()).thenReturn(USER_LOGIN); | |||
when(userSession.getUuid()).thenReturn(USER_UUID); | |||
} | |||
@Test | |||
public void getDevOpsPlatform_shouldReturnGitHub() { | |||
assertThat(gitHubDevOpsPlatformService.getDevOpsPlatform()) | |||
@@ -88,8 +150,6 @@ public class GitHubDevOpsPlatformServiceTest { | |||
@Test | |||
public void getValidAlmSettingDto_whenNoAlmSetting_shouldReturnEmpty() { | |||
when(almSettingDao.selectByAlm(dbSession, ALM.GITHUB)).thenReturn(emptyList()); | |||
Optional<AlmSettingDto> almSettingDto = gitHubDevOpsPlatformService.getValidAlmSettingDto(dbSession, GITHUB_PROJECT_DESCRIPTOR); | |||
assertThat(almSettingDto).isEmpty(); | |||
@@ -99,7 +159,7 @@ public class GitHubDevOpsPlatformServiceTest { | |||
public void getValidAlmSettingDto_whenMultipleAlmSetting_shouldReturnTheRightOne() { | |||
AlmSettingDto mockGitHubAlmSettingDtoNoAccess = mockGitHubAlmSettingDto(false); | |||
AlmSettingDto mockGitHubAlmSettingDtoAccess = mockGitHubAlmSettingDto(true); | |||
when(almSettingDao.selectByAlm(dbSession, ALM.GITHUB)).thenReturn(List.of(mockGitHubAlmSettingDtoNoAccess, mockGitHubAlmSettingDtoAccess)); | |||
when(dbClient.almSettingDao().selectByAlm(dbSession, ALM.GITHUB)).thenReturn(List.of(mockGitHubAlmSettingDtoNoAccess, mockGitHubAlmSettingDtoAccess)); | |||
Optional<AlmSettingDto> almSettingDto = gitHubDevOpsPlatformService.getValidAlmSettingDto(dbSession, GITHUB_PROJECT_DESCRIPTOR); | |||
@@ -117,4 +177,167 @@ public class GitHubDevOpsPlatformServiceTest { | |||
return mockAlmSettingDto; | |||
} | |||
@Test | |||
public void createProjectAndBindToDevOpsPlatform_whenRepoNotFound_throws() { | |||
DevOpsProjectDescriptor devOpsProjectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, GITHUB_API_URL, GITHUB_REPO_FULL_NAME); | |||
AlmSettingDto almSettingDto = mockAlmSettingDto(devOpsProjectDescriptor); | |||
GithubAppConfiguration githubAppConfiguration = mockGitHubAppConfiguration(almSettingDto); | |||
when(githubApplicationClient.getInstallationId(githubAppConfiguration, GITHUB_REPO_FULL_NAME)).thenReturn(Optional.empty()); | |||
assertThatIllegalStateException().isThrownBy( | |||
() -> gitHubDevOpsPlatformService.createProjectAndBindToDevOpsPlatform(dbSession, PROJECT_KEY, almSettingDto, devOpsProjectDescriptor)) | |||
.withMessage("Impossible to find the repository orgname/projectName on GitHub, using the devops config devops-platform-config-1."); | |||
} | |||
@Test | |||
public void createProjectAndBindToDevOpsPlatform_whenRepoFoundOnGitHub_successfullyCreatesProject() { | |||
// given | |||
DevOpsProjectDescriptor devOpsProjectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, GITHUB_API_URL, GITHUB_REPO_FULL_NAME); | |||
AlmSettingDto almSettingDto = mockAlmSettingDto(devOpsProjectDescriptor); | |||
mockExistingGitHubRepository(almSettingDto); | |||
ComponentCreationData componentCreationData = mockProjectCreation(); | |||
ProjectAlmSettingDao projectAlmSettingDao = mock(); | |||
when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao); | |||
// when | |||
ComponentCreationData actualComponentCreationData = gitHubDevOpsPlatformService.createProjectAndBindToDevOpsPlatform(dbSession, PROJECT_KEY, almSettingDto, | |||
devOpsProjectDescriptor); | |||
// then | |||
assertThat(actualComponentCreationData).isEqualTo(componentCreationData); | |||
ComponentCreationParameters componentCreationParameters = componentCreationParametersCaptor.getValue(); | |||
assertComponentCreationParametersContainsCorrectInformation(componentCreationParameters); | |||
assertThat(componentCreationParameters.isManaged()).isFalse(); | |||
assertThat(componentCreationParameters.newComponent().isPrivate()).isFalse(); | |||
verify(projectAlmSettingDao).insertOrUpdate(eq(dbSession), projectAlmSettingDtoCaptor.capture(), eq("devops-platform-config-1"), eq(PROJECT_NAME), eq(PROJECT_KEY)); | |||
ProjectAlmSettingDto projectAlmSettingDto = projectAlmSettingDtoCaptor.getValue(); | |||
assertAlmSettingsDtoContainsCorrectInformation(almSettingDto, requireNonNull(componentCreationData.projectDto()), projectAlmSettingDto); | |||
assertThat(logTester.getLogs()).isEmpty(); | |||
} | |||
@Test | |||
public void createProjectAndBindToDevOpsPlatform_whenRepoFoundOnGitHubAndAutoProvisioningOn_successfullyCreatesProject() { | |||
// given | |||
DevOpsProjectDescriptor devOpsProjectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, GITHUB_API_URL, GITHUB_REPO_FULL_NAME); | |||
when(projectDefaultVisibility.get(any())).thenReturn(Visibility.PRIVATE); | |||
when(gitHubSettings.isProvisioningEnabled()).thenReturn(true); | |||
AlmSettingDto almSettingDto = mockAlmSettingDto(devOpsProjectDescriptor); | |||
mockExistingGitHubRepository(almSettingDto); | |||
ComponentCreationData componentCreationData = mockProjectCreation(); | |||
ProjectAlmSettingDao projectAlmSettingDao = mock(); | |||
when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao); | |||
// when | |||
ComponentCreationData actualComponentCreationData = gitHubDevOpsPlatformService.createProjectAndBindToDevOpsPlatform(dbSession, PROJECT_KEY, almSettingDto, | |||
devOpsProjectDescriptor); | |||
// then | |||
assertThat(actualComponentCreationData).isEqualTo(componentCreationData); | |||
ComponentCreationParameters componentCreationParameters = componentCreationParametersCaptor.getValue(); | |||
assertComponentCreationParametersContainsCorrectInformation(componentCreationParameters); | |||
assertThat(componentCreationParameters.isManaged()).isTrue(); | |||
assertThat(componentCreationParameters.newComponent().isPrivate()).isTrue(); | |||
verify(projectAlmSettingDao).insertOrUpdate(eq(dbSession), projectAlmSettingDtoCaptor.capture(), eq("devops-platform-config-1"), eq(PROJECT_NAME), eq(PROJECT_KEY)); | |||
ProjectAlmSettingDto projectAlmSettingDto = projectAlmSettingDtoCaptor.getValue(); | |||
assertAlmSettingsDtoContainsCorrectInformation(almSettingDto, requireNonNull(componentCreationData.projectDto()), projectAlmSettingDto); | |||
assertThat(logTester.getLogs()).isEmpty(); | |||
} | |||
@Test | |||
public void createProjectAndBindToDevOpsPlatform_whenWrongToken_throws() { | |||
DevOpsProjectDescriptor devOpsProjectDescriptor = new DevOpsProjectDescriptor(ALM.GITHUB, GITHUB_API_URL, GITHUB_REPO_FULL_NAME); | |||
AlmSettingDto almSettingDto = mockAlmSettingDto(devOpsProjectDescriptor); | |||
mockExistingGitHubRepository(almSettingDto); | |||
when(githubApplicationClient.createAppInstallationToken(any(), anyLong())).thenReturn(Optional.empty()); | |||
assertThatIllegalStateException().isThrownBy( | |||
() -> gitHubDevOpsPlatformService.createProjectAndBindToDevOpsPlatform(dbSession, PROJECT_KEY, almSettingDto, devOpsProjectDescriptor)) | |||
.withMessage("Error while generating token for GitHub Api Url https://api.toto.com (installation id: 534534534543)"); | |||
} | |||
private void mockExistingGitHubRepository(AlmSettingDto almSettingDto) { | |||
GithubAppConfiguration githubAppConfiguration = mockGitHubAppConfiguration(almSettingDto); | |||
when(githubApplicationClient.getInstallationId(githubAppConfiguration, GITHUB_REPO_FULL_NAME)).thenReturn(Optional.of(APP_INSTALLATION_ID)); | |||
AppInstallationToken appInstallationToken = mockAppInstallationToken(githubAppConfiguration, APP_INSTALLATION_ID); | |||
mockGitHubRepository(appInstallationToken); | |||
} | |||
private GithubAppConfiguration mockGitHubAppConfiguration(AlmSettingDto almSettingDto) { | |||
GithubAppConfiguration githubAppConfiguration = mock(); | |||
when(githubGlobalSettingsValidator.validate(almSettingDto)).thenReturn(githubAppConfiguration); | |||
when(githubAppConfiguration.getApiEndpoint()).thenReturn(GITHUB_API_URL); | |||
return githubAppConfiguration; | |||
} | |||
private void mockGitHubRepository(AppInstallationToken appInstallationToken) { | |||
GithubApplicationClient.Repository repository = mock(); | |||
when(repository.getDefaultBranch()).thenReturn(MAIN_BRANCH_NAME); | |||
when(repository.getName()).thenReturn(PROJECT_NAME); | |||
when(repository.getFullName()).thenReturn(GITHUB_REPO_FULL_NAME); | |||
when(githubApplicationClient.getRepository(GITHUB_API_URL, appInstallationToken, PROJECT_NAME)).thenReturn(Optional.of(repository)); | |||
when(projectKeyGenerator.generateUniqueProjectKey(repository.getFullName())).thenReturn("generated_" + PROJECT_KEY); | |||
} | |||
private AppInstallationToken mockAppInstallationToken(GithubAppConfiguration githubAppConfiguration, long appInstallationId) { | |||
AppInstallationToken appInstallationToken = mock(); | |||
when(githubApplicationClient.createAppInstallationToken(githubAppConfiguration, appInstallationId)).thenReturn(Optional.of(appInstallationToken)); | |||
return appInstallationToken; | |||
} | |||
private static AlmSettingDto mockAlmSettingDto(DevOpsProjectDescriptor devOpsProjectDescriptor) { | |||
AlmSettingDto almSettingDto = mock(); | |||
when(almSettingDto.getUuid()).thenReturn("almsetting-uuid-1"); | |||
when(almSettingDto.getKey()).thenReturn("devops-platform-config-1"); | |||
return almSettingDto; | |||
} | |||
private ComponentCreationData mockProjectCreation() { | |||
ComponentCreationData componentCreationData = mock(); | |||
mockProjectDto(componentCreationData); | |||
when(componentUpdater.createWithoutCommit(eq(dbSession), componentCreationParametersCaptor.capture())).thenReturn(componentCreationData); | |||
return componentCreationData; | |||
} | |||
private static ProjectDto mockProjectDto(ComponentCreationData componentCreationData) { | |||
ProjectDto projectDto = mock(); | |||
when(projectDto.getName()).thenReturn(PROJECT_NAME); | |||
when(projectDto.getKey()).thenReturn(PROJECT_KEY); | |||
when(projectDto.getUuid()).thenReturn("project-uuid-1"); | |||
when(componentCreationData.projectDto()).thenReturn(projectDto); | |||
return projectDto; | |||
} | |||
private static void assertComponentCreationParametersContainsCorrectInformation(ComponentCreationParameters componentCreationParameters) { | |||
assertThat(componentCreationParameters.creationMethod()).isEqualTo(CreationMethod.SCANNER_API_DEVOPS_AUTO_CONFIG); | |||
assertThat(componentCreationParameters.mainBranchName()).isEqualTo(MAIN_BRANCH_NAME); | |||
assertThat(componentCreationParameters.userLogin()).isEqualTo(USER_LOGIN); | |||
assertThat(componentCreationParameters.userUuid()).isEqualTo(USER_UUID); | |||
NewComponent newComponent = componentCreationParameters.newComponent(); | |||
assertThat(newComponent.isProject()).isTrue(); | |||
assertThat(newComponent.qualifier()).isEqualTo(Qualifiers.PROJECT); | |||
assertThat(newComponent.key()).isEqualTo(PROJECT_KEY); | |||
assertThat(newComponent.name()).isEqualTo(PROJECT_NAME); | |||
} | |||
private static void assertAlmSettingsDtoContainsCorrectInformation(AlmSettingDto almSettingDto, ProjectDto projectDto, ProjectAlmSettingDto projectAlmSettingDto) { | |||
assertThat(projectAlmSettingDto.getAlmRepo()).isEqualTo(GITHUB_REPO_FULL_NAME); | |||
assertThat(projectAlmSettingDto.getAlmSlug()).isNull(); | |||
assertThat(projectAlmSettingDto.getAlmSettingUuid()).isEqualTo(almSettingDto.getUuid()); | |||
assertThat(projectAlmSettingDto.getProjectUuid()).isEqualTo(projectDto.getUuid()); | |||
assertThat(projectAlmSettingDto.getMonorepo()).isFalse(); | |||
assertThat(projectAlmSettingDto.getSummaryCommentEnabled()).isTrue(); | |||
} | |||
} |
@@ -38,7 +38,9 @@ import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.verifyNoInteractions; | |||
import static org.mockito.Mockito.when; | |||
import static org.sonar.core.ce.CeTaskCharacteristics.PULL_REQUEST; | |||
@RunWith(DataProviderRunner.class) | |||
public class BranchSupportTest { | |||
@@ -54,17 +56,31 @@ public class BranchSupportTest { | |||
ComponentKey componentKey = underTestNoBranch.createComponentKey(projectKey, NO_CHARACTERISTICS); | |||
assertThat(componentKey) | |||
.isEqualTo(underTestWithBranch.createComponentKey(projectKey, NO_CHARACTERISTICS)); | |||
assertThat(componentKey).isEqualTo(underTestWithBranch.createComponentKey(projectKey, NO_CHARACTERISTICS)); | |||
assertThat(componentKey.getKey()).isEqualTo(projectKey); | |||
assertThat(componentKey.getBranchName()).isEmpty(); | |||
assertThat(componentKey.getPullRequestKey()).isEmpty(); | |||
verifyNoInteractions(branchSupportDelegate); | |||
} | |||
@Test | |||
public void createComponentKey_delegates_to_delegate_if_characteristics_is_not_empty() { | |||
public void createComponentKey_whenCharacteristicsIsRandom_returnsComponentKey() { | |||
String projectKey = randomAlphanumeric(12); | |||
Map<String, String> nonEmptyMap = newRandomNonEmptyMap(); | |||
ComponentKey componentKey = underTestWithBranch.createComponentKey(projectKey, nonEmptyMap); | |||
assertThat(componentKey).isEqualTo(underTestWithBranch.createComponentKey(projectKey, NO_CHARACTERISTICS)); | |||
assertThat(componentKey.getKey()).isEqualTo(projectKey); | |||
assertThat(componentKey.getBranchName()).isEmpty(); | |||
assertThat(componentKey.getPullRequestKey()).isEmpty(); | |||
verifyNoInteractions(branchSupportDelegate); | |||
} | |||
@Test | |||
public void createComponentKey_whenCharacteristicsIsBranchRelated_delegates() { | |||
String projectKey = randomAlphanumeric(12); | |||
Map<String, String> nonEmptyMap = Map.of(PULL_REQUEST, "PR-2"); | |||
ComponentKey expected = mock(ComponentKey.class); | |||
when(branchSupportDelegate.createComponentKey(projectKey, nonEmptyMap)).thenReturn(expected); | |||
@@ -0,0 +1,4 @@ | |||
@ParametersAreNonnullByDefault | |||
package org.sonar.core.ce; | |||
import javax.annotation.ParametersAreNonnullByDefault; |