--- /dev/null
+/*
+ * 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;
+ }
+}
*/
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);
/**
/**
* 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;
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;
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) {
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
} 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.");
}
}
}
@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);
}
}
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()) {
}
}
-
protected static <T> Optional<T> handleResponse(GithubApplicationHttpClient.Response response, String endPoint, Class<T> gsonClass) {
try {
return response.getContent().map(c -> GSON.fromJson(c, gsonClass));
return name;
}
}
+
+ public static class GsonInstallationToken {
+ @SerializedName("token")
+ String token;
+
+ public String getToken() {
+ return token;
+ }
+ }
}
--- /dev/null
+/*
+ * 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"));
+ }
+}
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;
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;
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;
@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 = """
[
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));
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));
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));
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));
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
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));
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));
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() {
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();
}
@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
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() {
}
});
- 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()
.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);
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;
--- /dev/null
+/*
+ * 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.server.component;
+
+import javax.annotation.Nullable;
+import org.sonar.db.component.BranchDto;
+import org.sonar.db.component.ComponentDto;
+import org.sonar.db.portfolio.PortfolioDto;
+import org.sonar.db.project.ProjectDto;
+
+public record ComponentCreationData(ComponentDto mainBranchComponent, @Nullable PortfolioDto portfolioDto, @Nullable BranchDto mainBranchDto,
+ @Nullable ProjectDto projectDto) {
+}
--- /dev/null
+/*
+ * 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;
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;
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();
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() {
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());
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
.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)
.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();
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())
.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();
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())
.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();
.isPresent()
.get()
.extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue)
- .containsExactly(REFERENCE_BRANCH, "mainBranch");
+ .containsExactly(REFERENCE_BRANCH, "default-branch");
}
@Test
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())
.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());
}
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);
}
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);
}
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);
}
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;
}
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;
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;
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;
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
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() {
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);
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);
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
.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());
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();
}
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);
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");
}
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));
- }
-
}
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;
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;
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;
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() {
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))
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
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);
}
}
@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
.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()));
}
*/
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;
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"));
.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)
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);
.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());
- }
}
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)
.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))
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 {
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);
}
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
@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());
}
}
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;
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}.
*/
@ServerSide
public class BranchSupport {
+ private static final Set<String> BRANCH_CHARACTERISTICS = Set.of(BRANCH, BRANCH_TYPE, PULL_REQUEST);
@CheckForNull
private final BranchSupportDelegate delegate;
}
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) {
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;
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;
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;
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) {
mainBranchComponent = mainBranchComponentOpt.get();
validateProject(dbSession, mainBranchComponent, projectKey);
} else {
- componentCreationData = createProject(dbSession, componentKey.getKey(), projectName);
+ componentCreationData = createProject(projectKey, projectName, characteristics, dbSession, componentKey);
mainBranchComponent = componentCreationData.mainBranchComponent();
}
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 {
}
}
- 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();
+++ /dev/null
-/*
- * 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.server.component;
-
-import javax.annotation.Nullable;
-import org.sonar.db.component.BranchDto;
-import org.sonar.db.component.ComponentDto;
-import org.sonar.db.portfolio.PortfolioDto;
-import org.sonar.db.project.ProjectDto;
-
-public record ComponentCreationData(ComponentDto mainBranchComponent, @Nullable PortfolioDto portfolioDto, @Nullable BranchDto mainBranchDto,
- @Nullable ProjectDto projectDto) {
-}
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())
@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();
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);
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();
+ }
}
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 {
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);
--- /dev/null
+@ParametersAreNonnullByDefault
+package org.sonar.core.ce;
+
+import javax.annotation.ParametersAreNonnullByDefault;