diff --git a/build.gradle b/build.gradle index c8163e003..791c54c8d 100644 --- a/build.gradle +++ b/build.gradle @@ -48,6 +48,7 @@ sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 configurations { zip + customTestRuntime } compileJava { @@ -60,14 +61,13 @@ tasks.withType(JavaCompile) { dependencies { + customTestRuntime('org.kohsuke:github-api-unbridged:1.326') compileOnly(fileTree(dir: sonarLibraries, include: '**/*.jar', exclude: 'extensions/*.jar')) testImplementation(fileTree(dir: sonarLibraries, include: '**/*.jar', exclude: 'extensions/*.jar')) testImplementation('org.mockito:mockito-core:5.14.2') testImplementation('org.assertj:assertj-core:3.26.3') testImplementation('org.wiremock:wiremock:3.9.2') zip("sonarqube:sonarqube:${sonarqubeVersion}@zip") - implementation('org.bouncycastle:bcpkix-jdk15on:1.70') - implementation(files('lib/nodes-0.5.0.jar')) runtimeOnly('com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.1') compileOnly('com.google.code.findbugs:jsr305:3.0.2') implementation('org.javassist:javassist:3.30.2-GA') @@ -78,6 +78,7 @@ dependencies { testRuntimeOnly('org.junit.vintage:junit-vintage-engine') } +sourceSets.test.runtimeClasspath = configurations.customTestRuntime + sourceSets.test.runtimeClasspath project.afterEvaluate { if (file("${sonarLibraries}").exists()) { diff --git a/docker-compose.yml b/docker-compose.yml index 22e57a524..2d2d67362 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,9 +1,8 @@ -version: "3.8" - services: sonarqube: depends_on: - - db + db: + condition: service_healthy image: mc1arke/sonarqube-with-community-branch-plugin:${SONARQUBE_VERSION} build: context: . @@ -13,7 +12,7 @@ services: PLUGIN_VERSION: ${PLUGIN_VERSION} container_name: sonarqube ports: - - 9000:9000 + - "9000:9000" networks: - sonarnet environment: @@ -24,7 +23,13 @@ services: - sonarqube_conf:/opt/sonarqube/conf - sonarqube_data:/opt/sonarqube/data db: - image: postgres:11 + image: postgres:16 + healthcheck: + test: [ "CMD-SHELL", "pg_isready -U sonar" ] + interval: 10s + timeout: 5s + retries: 5 + hostname: db container_name: postgres networks: - sonarnet diff --git a/lib/nodes-0.5.0.jar b/lib/nodes-0.5.0.jar deleted file mode 100644 index ff269010f..000000000 Binary files a/lib/nodes-0.5.0.jar and /dev/null differ diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchPlugin.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchPlugin.java index be1822654..3b5cbf9ca 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchPlugin.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchPlugin.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2022 Michael Clarke + * Copyright (C) 2020-2024 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -18,15 +18,21 @@ */ package com.github.mc1arke.sonarqube.plugin; -import com.github.mc1arke.sonarqube.plugin.almclient.DefaultLinkHeaderReader; +import org.sonar.api.CoreProperties; +import org.sonar.api.Plugin; +import org.sonar.api.PropertyType; +import org.sonar.api.SonarQubeSide; +import org.sonar.api.config.PropertyDefinition; +import org.sonar.api.resources.Qualifiers; +import org.sonar.core.config.PurgeConstants; +import org.sonar.core.extension.CoreExtension; + import com.github.mc1arke.sonarqube.plugin.almclient.azuredevops.DefaultAzureDevopsClientFactory; import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.DefaultBitbucketClientFactory; import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.HttpClientBuilderFactory; -import com.github.mc1arke.sonarqube.plugin.almclient.github.DefaultGithubClientFactory; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v3.DefaultUrlConnectionProvider; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v3.RestApplicationAuthenticationProvider; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v4.DefaultGraphqlProvider; +import com.github.mc1arke.sonarqube.plugin.almclient.github.GithubClientFactory; import com.github.mc1arke.sonarqube.plugin.almclient.gitlab.DefaultGitlabClientFactory; +import com.github.mc1arke.sonarqube.plugin.almclient.gitlab.DefaultLinkHeaderReader; import com.github.mc1arke.sonarqube.plugin.ce.CommunityReportAnalysisComponentProvider; import com.github.mc1arke.sonarqube.plugin.scanner.BranchConfigurationFactory; import com.github.mc1arke.sonarqube.plugin.scanner.CommunityBranchConfigurationLoader; @@ -58,15 +64,6 @@ import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.pullrequest.action.DeleteAction; import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.pullrequest.action.ListAction; -import org.sonar.api.CoreProperties; -import org.sonar.api.Plugin; -import org.sonar.api.PropertyType; -import org.sonar.api.SonarQubeSide; -import org.sonar.api.config.PropertyDefinition; -import org.sonar.api.resources.Qualifiers; -import org.sonar.core.config.PurgeConstants; -import org.sonar.core.extension.CoreExtension; - /** * @author Michael Clarke */ @@ -97,11 +94,8 @@ public void load(CoreExtension.Context context) { PullRequestWs.class, GithubValidator.class, - DefaultGraphqlProvider.class, - DefaultGithubClientFactory.class, + GithubClientFactory.class, DefaultLinkHeaderReader.class, - DefaultUrlConnectionProvider.class, - RestApplicationAuthenticationProvider.class, HttpClientBuilderFactory.class, DefaultBitbucketClientFactory.class, BitbucketValidator.class, diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/DefaultGithubClientFactory.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/DefaultGithubClientFactory.java deleted file mode 100644 index 99d2c44fa..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/DefaultGithubClientFactory.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2021-2022 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github; - -import com.github.mc1arke.sonarqube.plugin.InvalidConfigurationException; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v4.GraphqlGithubClient; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v4.GraphqlProvider; -import org.sonar.api.ce.ComputeEngineSide; -import org.sonar.api.config.internal.Settings; -import org.sonar.api.server.ServerSide; -import org.sonar.db.alm.setting.AlmSettingDto; -import org.sonar.db.alm.setting.ProjectAlmSettingDto; - -import java.io.IOException; -import java.util.Optional; - -@ServerSide -@ComputeEngineSide -public class DefaultGithubClientFactory implements GithubClientFactory { - - private final GithubApplicationAuthenticationProvider githubApplicationAuthenticationProvider; - private final Settings settings; - private final GraphqlProvider graphqlProvider; - - public DefaultGithubClientFactory(GithubApplicationAuthenticationProvider githubApplicationAuthenticationProvider, Settings settings, GraphqlProvider graphqlProvider) { - this.githubApplicationAuthenticationProvider = githubApplicationAuthenticationProvider; - this.settings = settings; - this.graphqlProvider = graphqlProvider; - } - - @Override - public GithubClient createClient(ProjectAlmSettingDto projectAlmSettingDto, AlmSettingDto almSettingDto) { - String apiUrl = Optional.ofNullable(almSettingDto.getUrl()).orElseThrow(() -> new InvalidConfigurationException(InvalidConfigurationException.Scope.GLOBAL, "No URL has been set for Github connections")); - String apiPrivateKey = Optional.ofNullable(almSettingDto.getDecryptedPrivateKey(settings.getEncryption())).orElseThrow(() -> new InvalidConfigurationException(InvalidConfigurationException.Scope.GLOBAL, "No private key has been set for Github connections")); - String projectPath = Optional.ofNullable(projectAlmSettingDto.getAlmRepo()).orElseThrow(() -> new InvalidConfigurationException(InvalidConfigurationException.Scope.PROJECT, "No repository name has been set for Github connections")); - String appId = Optional.ofNullable(almSettingDto.getAppId()).orElseThrow(() -> new InvalidConfigurationException(InvalidConfigurationException.Scope.GLOBAL, "No App ID has been set for Github connections")); - - try { - RepositoryAuthenticationToken repositoryAuthenticationToken = - githubApplicationAuthenticationProvider.getInstallationToken(apiUrl, appId, apiPrivateKey, projectPath); - - return new GraphqlGithubClient(graphqlProvider, apiUrl, repositoryAuthenticationToken); - } catch (IOException ex) { - throw new InvalidConfigurationException(InvalidConfigurationException.Scope.PROJECT, "Could not create Github client - " + ex.getMessage(), ex); - } - - } -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/GithubApplicationAuthenticationProvider.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/GithubApplicationAuthenticationProvider.java deleted file mode 100644 index e480309a5..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/GithubApplicationAuthenticationProvider.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2019-2021 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github; - -import java.io.IOException; - -public interface GithubApplicationAuthenticationProvider { - - RepositoryAuthenticationToken getInstallationToken(String apiUrl, String appId, String apiPrivateKey, - String projectPath) throws IOException; - -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/GithubClient.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/GithubClient.java deleted file mode 100644 index ef6e7c461..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/GithubClient.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2020-2022 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github; - -import com.github.mc1arke.sonarqube.plugin.almclient.github.model.CheckRunDetails; - -import java.io.IOException; - -public interface GithubClient { - String createCheckRun(CheckRunDetails checkRunDetails, boolean postSummaryComment) throws IOException; - - String getRepositoryUrl(); -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/GithubClientFactory.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/GithubClientFactory.java index ba47414c2..a912bafa0 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/GithubClientFactory.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/GithubClientFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Michael Clarke + * Copyright (C) 2021-2024 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -18,11 +18,102 @@ */ package com.github.mc1arke.sonarqube.plugin.almclient.github; +import java.io.IOException; +import java.io.StringReader; +import java.security.PrivateKey; +import java.time.Clock; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Date; +import java.util.Optional; +import java.util.function.Supplier; + +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.kohsuke.github.GHAppInstallationToken; +import org.kohsuke.github.GitHub; +import org.kohsuke.github.GitHubBuilder; +import org.kohsuke.github.extras.okhttp3.OkHttpGitHubConnector; +import org.sonar.api.ce.ComputeEngineSide; +import org.sonar.api.config.internal.Settings; +import org.sonar.api.server.ServerSide; import org.sonar.db.alm.setting.AlmSettingDto; import org.sonar.db.alm.setting.ProjectAlmSettingDto; +import org.springframework.beans.factory.annotation.Autowired; + +import com.github.mc1arke.sonarqube.plugin.InvalidConfigurationException; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.impl.DefaultJwtBuilder; +import okhttp3.OkHttpClient; + +@ServerSide +@ComputeEngineSide +public class GithubClientFactory { + + private final Clock clock; + private final Settings settings; + private final Supplier gitHubBuilderSupplier; + + @Autowired + public GithubClientFactory(Clock clock, Settings settings) { + this(clock, settings, GitHubBuilder::new); + } + + GithubClientFactory(Clock clock, Settings settings, Supplier gitHubBuilderSupplier) { + this.clock = clock; + this.settings = settings; + this.gitHubBuilderSupplier = gitHubBuilderSupplier; + } + + public GitHub createClient(AlmSettingDto almSettingDto, ProjectAlmSettingDto projectAlmSettingDto) throws IOException { + GHAppInstallationToken repositoryAuthenticationToken = authenticate(projectAlmSettingDto, almSettingDto); + + return gitHubBuilderSupplier.get() + .withConnector(new OkHttpGitHubConnector(new OkHttpClient())) + .withEndpoint(almSettingDto.getUrl()) + .withAppInstallationToken(repositoryAuthenticationToken.getToken()) + .build(); + } + + private GHAppInstallationToken authenticate(ProjectAlmSettingDto projectAlmSettingDto, AlmSettingDto almSettingDto) { + String apiUrl = Optional.ofNullable(almSettingDto.getUrl()).orElseThrow(() -> new InvalidConfigurationException(InvalidConfigurationException.Scope.GLOBAL, "No URL has been set for Github connections")); + String apiPrivateKey = Optional.ofNullable(almSettingDto.getDecryptedPrivateKey(settings.getEncryption())).orElseThrow(() -> new InvalidConfigurationException(InvalidConfigurationException.Scope.GLOBAL, "No private key has been set for Github connections")); + String projectPath = Optional.ofNullable(projectAlmSettingDto.getAlmRepo()).orElseThrow(() -> new InvalidConfigurationException(InvalidConfigurationException.Scope.PROJECT, "No repository name has been set for Github connections")); + String appId = Optional.ofNullable(almSettingDto.getAppId()).orElseThrow(() -> new InvalidConfigurationException(InvalidConfigurationException.Scope.GLOBAL, "No App ID has been set for Github connections")); + + try { + return getInstallationToken(apiUrl, appId, apiPrivateKey, projectPath); + } catch (IOException ex) { + throw new InvalidConfigurationException(InvalidConfigurationException.Scope.PROJECT, "Could not create Github client - " + ex.getMessage(), ex); + } + + } + + private GHAppInstallationToken getInstallationToken(String apiUrl, String appId, String apiPrivateKey, String projectPath) throws IOException { + Instant issued = clock.instant().minus(10, ChronoUnit.SECONDS); + Instant expiry = issued.plus(2, ChronoUnit.MINUTES); + String jwtToken = new DefaultJwtBuilder().issuedAt(Date.from(issued)).expiration(Date.from(expiry)) + .claim("iss", appId).signWith(createPrivateKey(apiPrivateKey), Jwts.SIG.RS256).compact(); -public interface GithubClientFactory { + if (!projectPath.contains("/")) { + throw new InvalidConfigurationException(InvalidConfigurationException.Scope.PROJECT, "Repository name must be in the format 'owner/repo'"); + } + String owner = projectPath.split("/")[0]; + String repo = projectPath.split("/")[1]; + GitHub github = gitHubBuilderSupplier.get() + .withEndpoint(apiUrl) + .withConnector(new OkHttpGitHubConnector(new OkHttpClient())) + .withJwtToken(jwtToken) + .build(); - GithubClient createClient(ProjectAlmSettingDto projectAlmSettingDto, AlmSettingDto almSettingDto); + return github.getApp().getInstallationByRepository(owner, repo).createToken().create(); + } + private static PrivateKey createPrivateKey(String apiPrivateKey) throws IOException { + try (PEMParser pemParser = new PEMParser(new StringReader(apiPrivateKey))) { + return new JcaPEMKeyConverter().getPrivateKey(((PEMKeyPair) Optional.ofNullable(pemParser.readObject()).orElseThrow(() -> new InvalidConfigurationException(InvalidConfigurationException.Scope.GLOBAL, "Private key could not be parsed"))).getPrivateKeyInfo()); + } + } } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/RepositoryAuthenticationToken.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/RepositoryAuthenticationToken.java deleted file mode 100644 index 24aa3073a..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/RepositoryAuthenticationToken.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2019-2022 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github; - -public class RepositoryAuthenticationToken { - - private final String repositoryId; - private final String authenticationToken; - private final String repositoryUrl; - private final String repositoryName; - private final String ownerName; - - public RepositoryAuthenticationToken(String repositoryId, String authenticationToken, String repositoryUrl, String repositoryName, String ownerName) { - super(); - this.repositoryId = repositoryId; - this.authenticationToken = authenticationToken; - this.repositoryUrl = repositoryUrl; - this.repositoryName = repositoryName; - this.ownerName = ownerName; - } - - public String getRepositoryId() { - return repositoryId; - } - - public String getAuthenticationToken() { - return authenticationToken; - } - - public String getRepositoryUrl() { - return repositoryUrl; - } - - public String getRepositoryName() { - return repositoryName; - } - - public String getOwnerName() { - return ownerName; - } -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/model/Annotation.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/model/Annotation.java deleted file mode 100644 index 51e2c109d..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/model/Annotation.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2022 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.model; - -import com.github.mc1arke.sonarqube.plugin.almclient.github.v4.model.CheckAnnotationLevel; - -public class Annotation { - - private final Integer line; - private final String scmPath; - private final CheckAnnotationLevel severity; - private final String message; - - private Annotation(Builder builder) { - line = builder.line; - scmPath = builder.scmPath; - severity = builder.severity; - message = builder.message; - } - - public Integer getLine() { - return line; - } - - public String getScmPath() { - return scmPath; - } - - public CheckAnnotationLevel getSeverity() { - return severity; - } - - public String getMessage() { - return message; - } - - public static Builder builder() { - return new Builder(); - } - - public static final class Builder { - private Integer line; - private String scmPath; - private CheckAnnotationLevel severity; - private String message; - - private Builder() { - super(); - } - - public Builder withLine(Integer line) { - this.line = line; - return this; - } - - public Builder withScmPath(String scmPath) { - this.scmPath = scmPath; - return this; - } - - public Builder withSeverity(CheckAnnotationLevel severity) { - this.severity = severity; - return this; - } - - public Builder withMessage(String message) { - this.message = message; - return this; - } - - public Annotation build() { - return new Annotation(this); - } - } -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/model/CheckRunDetails.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/model/CheckRunDetails.java deleted file mode 100644 index 502598bdb..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/model/CheckRunDetails.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) 2022 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.model; - -import com.github.mc1arke.sonarqube.plugin.almclient.github.v4.model.CheckConclusionState; - -import java.time.ZonedDateTime; -import java.util.List; - -public class CheckRunDetails { - - private final String summary; - private final String title; - private final String name; - private final String dashboardUrl; - private final ZonedDateTime startTime; - private final ZonedDateTime endTime; - private final String externalId; - private final String commitId; - private final List annotations; - private final CheckConclusionState checkConclusionState; - private final int pullRequestId; - private final String projectKey; - - private CheckRunDetails(Builder builder) { - summary = builder.summary; - title = builder.title; - name = builder.name; - dashboardUrl = builder.dashboardUrl; - startTime = builder.startTime; - endTime = builder.endTime; - externalId = builder.externalId; - commitId = builder.commitId; - annotations = builder.annotations; - checkConclusionState = builder.checkConclusionState; - pullRequestId = builder.pullRequestId; - projectKey = builder.projectKey; - } - - public String getSummary() { - return summary; - } - - public String getTitle() { - return title; - } - - public String getName() { - return name; - } - - public String getDashboardUrl() { - return dashboardUrl; - } - - public ZonedDateTime getStartTime() { - return startTime; - } - - public ZonedDateTime getEndTime() { - return endTime; - } - - public String getExternalId() { - return externalId; - } - - public String getCommitId() { - return commitId; - } - - public List getAnnotations() { - return annotations; - } - - public CheckConclusionState getCheckConclusionState() { - return checkConclusionState; - } - - public int getPullRequestId() { - return pullRequestId; - } - - public String getProjectKey() { - return projectKey; - } - - public static Builder builder() { - return new Builder(); - } - - public static final class Builder { - private String summary; - private String title; - private String name; - private String dashboardUrl; - private ZonedDateTime startTime; - private ZonedDateTime endTime; - private String externalId; - private String commitId; - private List annotations; - private CheckConclusionState checkConclusionState; - private int pullRequestId; - private String projectKey; - - private Builder() { - super(); - } - - public Builder withSummary(String summary) { - this.summary = summary; - return this; - } - - public Builder withTitle(String title) { - this.title = title; - return this; - } - - public Builder withName(String name) { - this.name = name; - return this; - } - - public Builder withDashboardUrl(String dashboardUrl) { - this.dashboardUrl = dashboardUrl; - return this; - } - - public Builder withStartTime(ZonedDateTime startTime) { - this.startTime = startTime; - return this; - } - - public Builder withEndTime(ZonedDateTime endTime) { - this.endTime = endTime; - return this; - } - - public Builder withExternalId(String externalId) { - this.externalId = externalId; - return this; - } - - public Builder withCommitId(String commitId) { - this.commitId = commitId; - return this; - } - - public Builder withAnnotations(List annotations) { - this.annotations = annotations; - return this; - } - - public Builder withCheckConclusionState(CheckConclusionState checkConclusionState) { - this.checkConclusionState = checkConclusionState; - return this; - } - - public Builder withPullRequestId(int pullRequestId) { - this.pullRequestId = pullRequestId; - return this; - } - - public Builder withProjectKey(String projectKey) { - this.projectKey = projectKey; - return this; - } - - public CheckRunDetails build() { - return new CheckRunDetails(this); - } - } -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v3/DefaultUrlConnectionProvider.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v3/DefaultUrlConnectionProvider.java deleted file mode 100644 index 45ef78896..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v3/DefaultUrlConnectionProvider.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2019-2022 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v3; - -import org.sonar.api.ce.ComputeEngineSide; -import org.sonar.api.server.ServerSide; - -import java.io.IOException; -import java.net.URL; -import java.net.URLConnection; - -@ComputeEngineSide -@ServerSide -public final class DefaultUrlConnectionProvider implements UrlConnectionProvider { - - @Override - public URLConnection createUrlConnection(String url) throws IOException { - return new URL(url).openConnection(); - } - -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v3/RestApplicationAuthenticationProvider.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v3/RestApplicationAuthenticationProvider.java deleted file mode 100644 index 40f8c6075..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v3/RestApplicationAuthenticationProvider.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2020-2023 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v3; - -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.mc1arke.sonarqube.plugin.InvalidConfigurationException; -import com.github.mc1arke.sonarqube.plugin.almclient.LinkHeaderReader; -import com.github.mc1arke.sonarqube.plugin.almclient.github.GithubApplicationAuthenticationProvider; -import com.github.mc1arke.sonarqube.plugin.almclient.github.RepositoryAuthenticationToken; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v3.model.AppInstallation; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v3.model.AppToken; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v3.model.InstallationRepositories; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v3.model.Repository; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.impl.DefaultJwtBuilder; -import org.bouncycastle.openssl.PEMKeyPair; -import org.bouncycastle.openssl.PEMParser; -import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; -import org.sonar.api.ce.ComputeEngineSide; -import org.sonar.api.server.ServerSide; - -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.Reader; -import java.io.StringReader; -import java.net.HttpURLConnection; -import java.net.URLConnection; -import java.security.PrivateKey; -import java.time.Clock; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Date; -import java.util.Optional; - -@ServerSide -@ComputeEngineSide -public class RestApplicationAuthenticationProvider implements GithubApplicationAuthenticationProvider { - - private static final String ACCEPT_HEADER = "Accept"; - private static final String AUTHORIZATION_HEADER = "Authorization"; - private static final String BEARER_AUTHORIZATION_HEADER_PREFIX = "Bearer "; - - private static final String APP_PREVIEW_ACCEPT_HEADER = "application/vnd.github.machine-man-preview+json"; - - private final Clock clock; - private final LinkHeaderReader linkHeaderReader; - private final UrlConnectionProvider urlProvider; - private final ObjectMapper objectMapper; - - public RestApplicationAuthenticationProvider(Clock clock, LinkHeaderReader linkHeaderReader, UrlConnectionProvider urlProvider) { - super(); - this.clock = clock; - this.urlProvider = urlProvider; - this.linkHeaderReader = linkHeaderReader; - this.objectMapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); - } - - @Override - public RepositoryAuthenticationToken getInstallationToken(String apiUrl, String appId, String apiPrivateKey, - String projectPath) throws IOException { - - Instant issued = clock.instant().minus(10, ChronoUnit.SECONDS); - Instant expiry = issued.plus(2, ChronoUnit.MINUTES); - String jwtToken = new DefaultJwtBuilder().issuedAt(Date.from(issued)).expiration(Date.from(expiry)) - .claim("iss", appId).signWith(createPrivateKey(apiPrivateKey), Jwts.SIG.RS256).compact(); - - Optional repositoryAuthenticationToken = findTokenFromAppInstallationList(getV3Url(apiUrl) + "/app/installations", jwtToken, projectPath); - - return repositoryAuthenticationToken.orElseThrow(() -> new InvalidConfigurationException(InvalidConfigurationException.Scope.PROJECT, - "No token could be found with access to the requested repository using the given application ID and key")); - } - - private Optional findTokenFromAppInstallationList(String apiUrl, String jwtToken, String projectPath) throws IOException { - URLConnection appConnection = urlProvider.createUrlConnection(apiUrl); - appConnection.setRequestProperty(ACCEPT_HEADER, APP_PREVIEW_ACCEPT_HEADER); - appConnection.setRequestProperty(AUTHORIZATION_HEADER, BEARER_AUTHORIZATION_HEADER_PREFIX + jwtToken); - - try (Reader reader = new InputStreamReader(appConnection.getInputStream())) { - AppInstallation[] appInstallations = objectMapper.readerFor(AppInstallation[].class).readValue(reader); - for (AppInstallation appInstallation : appInstallations) { - Optional repositoryAuthenticationToken = findAppTokenFromAppInstallation(appInstallation, jwtToken, projectPath); - - if (repositoryAuthenticationToken.isPresent()) { - return repositoryAuthenticationToken; - } - } - } - - Optional nextLink = linkHeaderReader.findNextLink(appConnection.getHeaderField("Link")); - if (nextLink.isEmpty()) { - return Optional.empty(); - } - - return findTokenFromAppInstallationList(nextLink.get(), jwtToken, projectPath); - } - - private Optional findAppTokenFromAppInstallation(AppInstallation installation, String jwtToken, String projectPath) throws IOException { - URLConnection accessTokenConnection = urlProvider.createUrlConnection(installation.getAccessTokensUrl()); - ((HttpURLConnection) accessTokenConnection).setRequestMethod("POST"); - accessTokenConnection.setRequestProperty(ACCEPT_HEADER, APP_PREVIEW_ACCEPT_HEADER); - accessTokenConnection - .setRequestProperty(AUTHORIZATION_HEADER, BEARER_AUTHORIZATION_HEADER_PREFIX + jwtToken); - - try (Reader reader = new InputStreamReader(accessTokenConnection.getInputStream())) { - AppToken appToken = objectMapper.readerFor(AppToken.class).readValue(reader); - - String targetUrl = installation.getRepositoriesUrl(); - - Optional potentialRepositoryAuthenticationToken = findRepositoryAuthenticationToken(appToken, targetUrl, projectPath); - - if (potentialRepositoryAuthenticationToken.isPresent()) { - return potentialRepositoryAuthenticationToken; - } - - } - - return Optional.empty(); - } - - private Optional findRepositoryAuthenticationToken(AppToken appToken, String targetUrl, - String projectPath) throws IOException { - URLConnection installationRepositoriesConnection = urlProvider.createUrlConnection(targetUrl); - ((HttpURLConnection) installationRepositoriesConnection).setRequestMethod("GET"); - installationRepositoriesConnection.setRequestProperty(ACCEPT_HEADER, APP_PREVIEW_ACCEPT_HEADER); - installationRepositoriesConnection.setRequestProperty(AUTHORIZATION_HEADER, - BEARER_AUTHORIZATION_HEADER_PREFIX + appToken.getToken()); - - try (Reader installationRepositoriesReader = new InputStreamReader( - installationRepositoriesConnection.getInputStream())) { - InstallationRepositories installationRepositories = - objectMapper.readerFor(InstallationRepositories.class).readValue(installationRepositoriesReader); - for (Repository repository : installationRepositories.getRepositories()) { - if (projectPath.equals(repository.getFullName())) { - return Optional.of(new RepositoryAuthenticationToken(repository.getNodeId(), appToken.getToken(), repository.getHtmlUrl(), repository.getName(), repository.getOwner().getLogin())); - } - } - - } - - Optional nextLink = linkHeaderReader.findNextLink(installationRepositoriesConnection.getHeaderField("Link")); - - if (nextLink.isEmpty()) { - return Optional.empty(); - } - - return findRepositoryAuthenticationToken(appToken, nextLink.get(), projectPath); - } - - private static String getV3Url(String apiUrl) { - if (apiUrl.endsWith("/")) { - apiUrl = apiUrl.substring(0, apiUrl.length() - 1); - } - if (apiUrl.endsWith("/api")) { - apiUrl = apiUrl + "/v3"; - } - return apiUrl; - } - - private static PrivateKey createPrivateKey(String apiPrivateKey) throws IOException { - try (PEMParser pemParser = new PEMParser(new StringReader(apiPrivateKey))) { - return new JcaPEMKeyConverter().getPrivateKey(((PEMKeyPair) pemParser.readObject()).getPrivateKeyInfo()); - } - } -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v3/UrlConnectionProvider.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v3/UrlConnectionProvider.java deleted file mode 100644 index 8fa40ef96..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v3/UrlConnectionProvider.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2019 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v3; - -import java.io.IOException; -import java.net.URLConnection; - -interface UrlConnectionProvider { - - URLConnection createUrlConnection(String url) throws IOException; -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v3/model/AppInstallation.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v3/model/AppInstallation.java deleted file mode 100644 index 83ae751b1..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v3/model/AppInstallation.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2019 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v3.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -public class AppInstallation { - - private final String repositoriesUrl; - private final String accessTokensUrl; - - @JsonCreator - public AppInstallation(@JsonProperty("repositories_url") String repositoriesUrl, - @JsonProperty("access_tokens_url") String accessTokensUrl) { - super(); - this.repositoriesUrl = repositoriesUrl; - this.accessTokensUrl = accessTokensUrl; - } - - public String getRepositoriesUrl() { - return repositoriesUrl; - } - - public String getAccessTokensUrl() { - return accessTokensUrl; - } -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v3/model/AppToken.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v3/model/AppToken.java deleted file mode 100644 index 3c0bb2afe..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v3/model/AppToken.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2019 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v3.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -public class AppToken { - - private final String token; - - @JsonCreator - public AppToken(@JsonProperty("token") String token) { - super(); - this.token = token; - } - - public String getToken() { - return token; - } - -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v3/model/InstallationRepositories.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v3/model/InstallationRepositories.java deleted file mode 100644 index c7da4591a..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v3/model/InstallationRepositories.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2019 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v3.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -public class InstallationRepositories { - - private final Repository[] repositories; - - @JsonCreator - public InstallationRepositories(@JsonProperty("repositories") Repository[] repositories) { - this.repositories = repositories; - } - - public Repository[] getRepositories() { - return repositories; - } -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v3/model/Owner.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v3/model/Owner.java deleted file mode 100644 index 1643d966e..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v3/model/Owner.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2022 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v3.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -public class Owner { - - private final String login; - - @JsonCreator - public Owner(@JsonProperty("login") String login) { - this.login = login; - } - - public String getLogin() { - return login; - } -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v3/model/Repository.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v3/model/Repository.java deleted file mode 100644 index 025e1d6cb..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v3/model/Repository.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2019-2022 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v3.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -public class Repository { - - private final String nodeId; - private final String fullName; - private final String htmlUrl; - private final String name; - private final Owner owner; - - @JsonCreator - public Repository(@JsonProperty("node_id") String nodeId, @JsonProperty("full_name") String fullName, @JsonProperty("html_url") String htmlUrl, @JsonProperty("name") String name, @JsonProperty("owner") Owner owner) { - this.nodeId = nodeId; - this.fullName = fullName; - this.htmlUrl = htmlUrl; - this.name = name; - this.owner = owner; - } - - public String getFullName() { - return fullName; - } - - public String getNodeId() { - return nodeId; - } - - public String getHtmlUrl() { - return htmlUrl; - } - - public String getName() { - return name; - } - - public Owner getOwner() { - return owner; - } -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/Actor.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/Actor.java deleted file mode 100644 index ea9129ccc..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/Actor.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2021 Julien Roy - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v4; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import io.aexp.nodes.graphql.annotations.GraphQLProperty; - -public class Actor { - @GraphQLProperty(name = "__typename") - private final String type; - private final String login; - - @JsonCreator - public Actor(@JsonProperty("__typename") String type, @JsonProperty("login") String login) { - this.type = type; - this.login = login; - } - - public String getType() { - return type; - } - - public String getLogin() { - return login; - } -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/AddComment.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/AddComment.java deleted file mode 100644 index 4b75b54f5..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/AddComment.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2021 Julien Roy - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v4; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import io.aexp.nodes.graphql.annotations.GraphQLArgument; -import io.aexp.nodes.graphql.annotations.GraphQLProperty; - -@GraphQLProperty(name = "addComment", arguments = {@GraphQLArgument(name = "input")}) -public class AddComment { - - private final String clientMutationId; - - @JsonCreator - public AddComment(@JsonProperty("clientMutationId") String clientMutationId) { - this.clientMutationId = clientMutationId; - } - - public String getClientMutationId() { - return clientMutationId; - } - -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/Comments.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/Comments.java deleted file mode 100644 index 7331a7b31..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/Comments.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2021 Julien Roy, Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v4; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import io.aexp.nodes.graphql.annotations.GraphQLProperty; - -import java.util.List; - -public class Comments { - private final List nodes; - private final PageInfo pageInfo; - - @JsonCreator - public Comments(@JsonProperty("nodes") List nodes, @JsonProperty("pageInfo") PageInfo pageInfo) { - this.nodes = nodes; - this.pageInfo = pageInfo; - } - - public List getNodes() { - return nodes; - } - - public PageInfo getPageInfo() { - return pageInfo; - } - - - public static class CommentNode { - - private final String id; - private final Actor author; - @GraphQLProperty(name = "isMinimized") - private final boolean minimized; - private final String body; - - @JsonCreator - public CommentNode(@JsonProperty("id") String id, @JsonProperty("author") Actor author, @JsonProperty("isMinimized") boolean minimized, @JsonProperty("body") String body) { - this.id = id; - this.author = author; - this.minimized = minimized; - this.body = body; - } - - public String getId() { - return id; - } - - public Actor getAuthor() { - return author; - } - - public boolean isMinimized() { - return minimized; - } - - public String getBody() { - return body; - } - } -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/CreateCheckRun.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/CreateCheckRun.java deleted file mode 100644 index 143cc85b0..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/CreateCheckRun.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2019 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v4; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v4.model.CheckRun; -import io.aexp.nodes.graphql.annotations.GraphQLArgument; -import io.aexp.nodes.graphql.annotations.GraphQLProperty; - -@GraphQLProperty(name = "createCheckRun", arguments = {@GraphQLArgument(name = "input")}) -public class CreateCheckRun { - - private final String clientMutationId; - private final CheckRun checkRun; - - @JsonCreator - public CreateCheckRun(@JsonProperty("clientMutationId") String clientMutationId, - @JsonProperty("checkRun") CheckRun checkRun) { - this.clientMutationId = clientMutationId; - this.checkRun = checkRun; - } - - public String getClientMutationId() { - return clientMutationId; - } - - public CheckRun getCheckRun() { - return checkRun; - } - -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/DefaultGraphqlProvider.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/DefaultGraphqlProvider.java deleted file mode 100644 index 372d6748b..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/DefaultGraphqlProvider.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2019-2024 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v4; - -import io.aexp.nodes.graphql.GraphQLRequestEntity; -import io.aexp.nodes.graphql.GraphQLTemplate; -import org.sonar.api.ce.ComputeEngineSide; -import org.sonar.api.server.ServerSide; - -@ComputeEngineSide -@ServerSide -public final class DefaultGraphqlProvider implements GraphqlProvider { - - @Override - public GraphQLTemplate createGraphQLTemplate() { - return new GraphQLTemplate(); - } - - @Override - public GraphQLRequestEntity.RequestBuilder createRequestBuilder() { - return GraphQLRequestEntity.Builder(); - } - - @Override - public InputObject.Builder createInputObject() { - return new InputObject.Builder<>(); - } -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/GetRepository.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/GetRepository.java deleted file mode 100644 index a8fc0cec1..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/GetRepository.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2021-2022 Julien Roy, Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v4; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import io.aexp.nodes.graphql.annotations.GraphQLArgument; -import io.aexp.nodes.graphql.annotations.GraphQLProperty; - -@GraphQLProperty(name = "repository", arguments = {@GraphQLArgument(name = "owner"), @GraphQLArgument(name = "name")}) -public class GetRepository { - - private final String url; - - @GraphQLProperty(name = "pullRequest", arguments = {@GraphQLArgument(name = "number")}) - private final PullRequest pullRequest; - - @JsonCreator - public GetRepository(@JsonProperty("url") String url, @JsonProperty("pullRequest") PullRequest pullRequest) { - this.url = url; - this.pullRequest = pullRequest; - } - - public String getUrl() { - return url; - } - - public PullRequest getPullRequest() { - return pullRequest; - } - - public static class PullRequest { - - private final String id; - - @GraphQLProperty(name = "comments", arguments = {@GraphQLArgument(name = "first", optional = true, type = "Integer"), @GraphQLArgument(name = "after", optional = true, type = "String")}) - private final Comments comments; - - @JsonCreator - public PullRequest(@JsonProperty("id") String id, @JsonProperty("comments") Comments comments) { - this.id = id; - this.comments = comments; - } - - public String getId() { - return id; - } - - public Comments getComments() { - return comments; - } - } - -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/GraphqlGithubClient.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/GraphqlGithubClient.java deleted file mode 100644 index 0c20d6be1..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/GraphqlGithubClient.java +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright (C) 2020-2024 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v4; - -import com.github.mc1arke.sonarqube.plugin.almclient.github.GithubClient; -import com.github.mc1arke.sonarqube.plugin.almclient.github.RepositoryAuthenticationToken; -import com.github.mc1arke.sonarqube.plugin.almclient.github.model.Annotation; -import com.github.mc1arke.sonarqube.plugin.almclient.github.model.CheckRunDetails; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v4.model.CommentClassifiers; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v4.model.RequestableCheckStatusState; -import io.aexp.nodes.graphql.Argument; -import io.aexp.nodes.graphql.Arguments; -import io.aexp.nodes.graphql.GraphQLRequestEntity; -import io.aexp.nodes.graphql.GraphQLResponseEntity; -import io.aexp.nodes.graphql.GraphQLTemplate; -import io.aexp.nodes.graphql.internal.Error; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.net.MalformedURLException; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.function.BiFunction; -import java.util.stream.Collectors; - -import static org.apache.commons.lang.ArrayUtils.isEmpty; - -public class GraphqlGithubClient implements GithubClient { - - private static final Logger LOGGER = LoggerFactory.getLogger(GraphqlGithubClient.class); - private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX") - .withZone(ZoneId.of("UTC")); - private static final String INPUT = "input"; - - private final GraphqlProvider graphqlProvider; - private final RepositoryAuthenticationToken repositoryAuthenticationToken; - private final String apiUrl; - - - public GraphqlGithubClient(GraphqlProvider graphqlProvider, String apiUrl, - RepositoryAuthenticationToken repositoryAuthenticationToken) { - super(); - this.graphqlProvider = graphqlProvider; - this.apiUrl = apiUrl; - this.repositoryAuthenticationToken = repositoryAuthenticationToken; - } - - @Override - public String createCheckRun(CheckRunDetails checkRunDetails, boolean postSummaryComment) throws IOException { - Map headers = new HashMap<>(); - headers.put("Authorization", "Bearer " + repositoryAuthenticationToken.getAuthenticationToken()); - headers.put("Accept", "application/vnd.github.antiope-preview+json"); - - List> annotations = createAnnotations(checkRunDetails.getAnnotations()); - - InputObject.Builder checkRunOutputContentBuilder = graphqlProvider.createInputObject().put("title", checkRunDetails.getTitle()) - .put("summary", checkRunDetails.getSummary()) - .put("annotations", annotations); - - Map inputObjectArguments = new HashMap<>(); - inputObjectArguments.put("repositoryId", repositoryAuthenticationToken.getRepositoryId()); - inputObjectArguments.put("name", checkRunDetails.getName()); - inputObjectArguments.put("status", RequestableCheckStatusState.COMPLETED); - inputObjectArguments.put("conclusion", checkRunDetails.getCheckConclusionState()); - inputObjectArguments.put("detailsUrl", checkRunDetails.getDashboardUrl()); - inputObjectArguments.put("startedAt", DATE_TIME_FORMATTER.format(checkRunDetails.getStartTime())); - inputObjectArguments.put("completedAt", DATE_TIME_FORMATTER.format(checkRunDetails.getEndTime())); - inputObjectArguments.put("externalId", checkRunDetails.getExternalId()); - inputObjectArguments.put("output", checkRunOutputContentBuilder.build()); - inputObjectArguments.put("headSha", checkRunDetails.getCommitId()); - - InputObject.Builder repositoryInputObjectBuilder = graphqlProvider.createInputObject(); - inputObjectArguments.forEach(repositoryInputObjectBuilder::put); - - String graphqlUrl = getGraphqlUrl(apiUrl); - - GraphQLRequestEntity.RequestBuilder graphQLRequestEntityBuilder = - graphqlProvider.createRequestBuilder() - .url(graphqlUrl) - .headers(headers) - .request(CreateCheckRun.class) - .arguments(new Arguments("createCheckRun", new Argument<>(INPUT, repositoryInputObjectBuilder.build()))) - .requestMethod(GraphQLTemplate.GraphQLMethod.MUTATE); - - GraphQLRequestEntity graphQLRequestEntity = graphQLRequestEntityBuilder.build(); - - GraphQLResponseEntity graphQLResponseEntity = executeRequest((r, t) -> graphqlProvider.createGraphQLTemplate().mutate(r, t), - graphQLRequestEntity, CreateCheckRun.class); - - reportRemainingAnnotations(checkRunDetails.getAnnotations(), graphQLResponseEntity.getResponse().getCheckRun().getId(), - inputObjectArguments, checkRunOutputContentBuilder, graphQLRequestEntityBuilder); - - - if (postSummaryComment) { - postSummaryComment(graphqlUrl, headers, checkRunDetails.getPullRequestId(), checkRunDetails.getSummary(), checkRunDetails.getProjectKey()); - } - - return graphQLResponseEntity.getResponse().getCheckRun().getId(); - - } - - @Override - public String getRepositoryUrl() { - return repositoryAuthenticationToken.getRepositoryUrl(); - } - - private void postSummaryComment(String graphqlUrl, Map headers, int pullRequestKey, String summary, String projectId) throws IOException { - String login = getLogin(graphqlUrl, headers); - - GetRepository.PullRequest pullRequest = getPullRequest(graphqlUrl, headers, pullRequestKey); - String pullRequestId = pullRequest.getId(); - String projectCommentMarker = String.format("**Project ID:** %s%n", projectId); - - getComments(pullRequest, graphqlUrl, headers, pullRequestKey).stream() - .filter(c -> "Bot".equalsIgnoreCase(c.getAuthor().getType()) && login.equalsIgnoreCase(c.getAuthor().getLogin())) - .filter(c -> !c.isMinimized()) - .filter(c -> c.getBody().contains(projectCommentMarker)) - .map(Comments.CommentNode::getId) - .forEach(commentId -> this.minimizeComment(graphqlUrl, headers, commentId)); - - InputObject.Builder repositoryInputObjectBuilder = graphqlProvider.createInputObject(); - - InputObject input = repositoryInputObjectBuilder - .put("body", summary) - .put("subjectId", pullRequestId) - .build(); - - GraphQLRequestEntity graphQLRequestEntity = - graphqlProvider.createRequestBuilder() - .url(graphqlUrl) - .headers(headers) - .request(AddComment.class) - .arguments(new Arguments("addComment", new Argument<>(INPUT, input))) - .requestMethod(GraphQLTemplate.GraphQLMethod.MUTATE) - .build(); - - executeRequest((r, t) -> graphqlProvider.createGraphQLTemplate().mutate(r, t), graphQLRequestEntity, AddComment.class); - - } - - private List getComments(GetRepository.PullRequest pullRequest, String graphqlUrl, Map headers, int pullRequestKey) throws MalformedURLException { - List comments = new ArrayList<>(pullRequest.getComments().getNodes()); - - PageInfo currentPageInfo = pullRequest.getComments().getPageInfo(); - if (currentPageInfo.hasNextPage()) { - GetRepository.PullRequest response = getPullRequest(graphqlUrl, headers, pullRequestKey, currentPageInfo); - comments.addAll(getComments(response, graphqlUrl, headers, pullRequestKey)); - } - - return comments; - } - - private GetRepository.PullRequest getPullRequest(String graphqlUrl, Map headers, int pullRequestKey) throws MalformedURLException { - return getPullRequest(graphqlUrl, headers, pullRequestKey, null); - } - - private GetRepository.PullRequest getPullRequest(String graphqlUrl, Map headers, int pullRequestKey, PageInfo pageInfo) throws MalformedURLException { - GraphQLRequestEntity getPullRequest = - graphqlProvider.createRequestBuilder() - .url(graphqlUrl) - .headers(headers) - .request(GetRepository.class) - .arguments( - new Arguments("repository", new Argument<>("owner", repositoryAuthenticationToken.getOwnerName()), new Argument<>("name", repositoryAuthenticationToken.getRepositoryName())), - new Arguments("repository.pullRequest", new Argument<>("number", pullRequestKey)), - new Arguments("repository.pullRequest.comments", new Argument<>("first", 100), new Argument<>("after", Optional.ofNullable(pageInfo).map(PageInfo::getEndCursor).orElse(null))) - ) - .build(); - - return executeRequest((r, t) -> graphqlProvider.createGraphQLTemplate().query(r, t), getPullRequest, GetRepository.class).getResponse().getPullRequest(); - } - - private void minimizeComment(String graphqlUrl, Map headers, String commentId) { - InputObject input = graphqlProvider.createInputObject() - .put("subjectId", commentId) - .put("classifier", CommentClassifiers.OUTDATED) - .build(); - - try { - - GraphQLRequestEntity graphQLRequestEntity = graphqlProvider.createRequestBuilder() - .url(graphqlUrl) - .headers(headers) - .request(MinimizeComment.class) - .arguments(new Arguments("minimizeComment", new Argument<>(INPUT, input))) - .requestMethod(GraphQLTemplate.GraphQLMethod.MUTATE) - .build(); - - executeRequest((r, t) -> graphqlProvider.createGraphQLTemplate().mutate(r, t), graphQLRequestEntity, MinimizeComment.class); - - } catch (IOException e) { - LOGGER.error("Error during minimize comment", e); - } - } - - private String getLogin(String graphqlUrl, Map headers) throws IOException { - GraphQLRequestEntity viewerQuery = graphqlProvider.createRequestBuilder() - .url(graphqlUrl) - .headers(headers) - .request(Viewer.class) - .build(); - - GraphQLResponseEntity response = - executeRequest((r, t) -> graphqlProvider.createGraphQLTemplate().query(r, t), viewerQuery, Viewer.class); - - return response.getResponse().getLogin().replace("[bot]", ""); - } - - private static GraphQLResponseEntity executeRequest( - BiFunction, GraphQLResponseEntity> executor, GraphQLRequestEntity graphQLRequestEntity, Class responseType) { - LOGGER.atDebug().setMessage("Using request: {}").addArgument(graphQLRequestEntity::getRequest).log(); - - GraphQLResponseEntity response = executor.apply(graphQLRequestEntity, responseType); - - LOGGER.debug("Received response: {}", response); - - if (!isEmpty(response.getErrors())) { - List errors = new ArrayList<>(); - for (Error error : response.getErrors()) { - errors.add("- " + error.toString()); - } - throw new IllegalStateException( - "An error was returned in the response from the Github API:" + System.lineSeparator() + - errors.stream().collect(Collectors.joining(System.lineSeparator()))); - } - - return response; - } - - private void reportRemainingAnnotations(List outstandingAnnotations, String checkRunId, - Map repositoryInputArguments, InputObject.Builder outputObjectBuilder, - GraphQLRequestEntity.RequestBuilder graphQLRequestEntityBuilder) { - - if (outstandingAnnotations.size() <= 50) { - return; - } - - List annotations = outstandingAnnotations.subList(50, outstandingAnnotations.size()); - - InputObject outputObject = outputObjectBuilder - .put("annotations", createAnnotations(annotations)) - .build(); - - InputObject.Builder repositoryInputObjectBuilder = graphqlProvider.createInputObject(); - repositoryInputArguments.forEach(repositoryInputObjectBuilder::put); - - InputObject repositoryInputObject = repositoryInputObjectBuilder - .put("checkRunId", checkRunId) - .put("output", outputObject) - .build(); - - GraphQLRequestEntity graphQLRequestEntity = graphQLRequestEntityBuilder - .request(UpdateCheckRun.class) - .arguments(new Arguments("updateCheckRun", new Argument<>(INPUT, repositoryInputObject))) - .requestMethod(GraphQLTemplate.GraphQLMethod.MUTATE) - .build(); - - executeRequest((r, t) -> graphqlProvider.createGraphQLTemplate().mutate(r, t), graphQLRequestEntity, UpdateCheckRun.class); - - reportRemainingAnnotations(annotations, checkRunId, repositoryInputArguments, outputObjectBuilder, graphQLRequestEntityBuilder); - } - - private List> createAnnotations(List annotations) { - return annotations.stream() - .limit(50) - .map(annotation -> { - InputObject issueLocation = graphqlProvider.createInputObject() - .put("startLine", Optional.ofNullable(annotation.getLine()).orElse(0)) - .put("endLine", Optional.ofNullable(annotation.getLine()).orElse(0)) - .build(); - return graphqlProvider.createInputObject() - .put("path", annotation.getScmPath()) - .put("location", issueLocation) - .put("annotationLevel", annotation.getSeverity()) - .put("message", annotation.getMessage()) - .build(); - }).collect(Collectors.toList()); - } - - private static String getGraphqlUrl(String apiUrl) { - if (apiUrl.endsWith("/")) { - apiUrl = apiUrl.substring(0, apiUrl.length() - 1); - } - if (apiUrl.endsWith("/v3")) { - apiUrl = apiUrl.substring(0, apiUrl.length() - 3); - } - apiUrl = apiUrl + "/graphql"; - - return apiUrl; - } - -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/GraphqlProvider.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/GraphqlProvider.java deleted file mode 100644 index 54beba5de..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/GraphqlProvider.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2019-2022 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v4; - -import io.aexp.nodes.graphql.GraphQLRequestEntity; -import io.aexp.nodes.graphql.GraphQLTemplate; - -public interface GraphqlProvider { - - GraphQLTemplate createGraphQLTemplate(); - - GraphQLRequestEntity.RequestBuilder createRequestBuilder(); - - InputObject.Builder createInputObject(); -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/InputObject.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/InputObject.java deleted file mode 100644 index f59ab2920..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/InputObject.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (c) 2018-2024 American Express Travel Related Services Company, Inc, Michael Clarke - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ -package com.github.mc1arke.sonarqube.plugin.almclient.github.v4; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public final class InputObject { - private final Map map; - - InputObject(Builder builder) { - this.map = builder.map; - } - - String getMessage() { - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append("{"); - for (Map.Entry entry : map.entrySet()) { - if (stringBuilder.length() != 1) stringBuilder.append(","); - stringBuilder.append(formatGraphQLParameter(entry.getKey(), entry.getValue())); - } - stringBuilder.append("}"); - return stringBuilder.toString(); - } - - @Override - public String toString() { - return getMessage(); - } - - public Map getMap() { - return this.map; - } - - public static class Builder { - - private final Map map = new HashMap<>(); - - public Builder put(String key, T value) { - this.map.put(key, value); - return this; - } - - public InputObject build() { - return new InputObject<>(this); - } - } - - - private static String formatGraphQLParameter(String key, T value) { - StringBuilder stringBuilder = new StringBuilder(); - Pattern pattern = Pattern.compile("^\\$"); - Matcher matcher = pattern.matcher("" + value); - if (value instanceof String && ((String) value).contains("\n")) { - stringBuilder.append(key).append(":\"\"\"").append(value).append("\"\"\""); - } else if (value instanceof String && !matcher.find()) { - stringBuilder.append(key).append(":\"").append(value).append("\""); - } else if (value instanceof List) { - stringBuilder.append(key).append(":").append(formatGraphQLArgumentList((List)value)); - } else if (value instanceof com.github.mc1arke.sonarqube.plugin.almclient.github.v4.InputObject) { - stringBuilder.append(key).append(":").append(((com.github.mc1arke.sonarqube.plugin.almclient.github.v4.InputObject)value).getMessage()); - } else { - stringBuilder.append(key).append(":").append(value); - } - - return stringBuilder.toString(); - } - - private static String formatGraphQLArgumentList(List values) { - StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.append("["); - Pattern p = Pattern.compile("^\\$"); - for (Object value: values) { - if (stringBuilder.length() != 1) stringBuilder.append(","); - Matcher m = p.matcher("" + value); - if (value instanceof String && !m.find()) { - stringBuilder.append("\"").append(value).append("\""); - } else if (value instanceof com.github.mc1arke.sonarqube.plugin.almclient.github.v4.InputObject) { - stringBuilder.append(((InputObject) value).getMessage()); - } else { - stringBuilder.append(value); - } - } - stringBuilder.append("]"); - return stringBuilder.toString(); - } - -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/MinimizeComment.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/MinimizeComment.java deleted file mode 100644 index 5f7cbfa4b..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/MinimizeComment.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2021 Julien Roy - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v4; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import io.aexp.nodes.graphql.annotations.GraphQLArgument; -import io.aexp.nodes.graphql.annotations.GraphQLProperty; - -@GraphQLProperty(name = "minimizeComment", arguments = {@GraphQLArgument(name = "input")}) -public class MinimizeComment { - - private final String clientMutationId; - - @JsonCreator - public MinimizeComment(@JsonProperty("clientMutationId") String clientMutationId) { - this.clientMutationId = clientMutationId; - } - - public String getClientMutationId() { - return clientMutationId; - } - -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/PageInfo.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/PageInfo.java deleted file mode 100644 index bddc1ddcc..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/PageInfo.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2021 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v4; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -public class PageInfo { - private final boolean hasNextPage; - private final String endCursor; - - @JsonCreator - public PageInfo(@JsonProperty("hasNextPage") boolean hasNextPage, @JsonProperty("endCursor") String endCursor) { - this.hasNextPage = hasNextPage; - this.endCursor = endCursor; - } - - public boolean hasNextPage() { - return hasNextPage; - } - - public String getEndCursor() { - return endCursor; - } -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/UpdateCheckRun.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/UpdateCheckRun.java deleted file mode 100644 index 1c65bcba4..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/UpdateCheckRun.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2020 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v4; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v4.model.CheckRun; -import io.aexp.nodes.graphql.annotations.GraphQLArgument; -import io.aexp.nodes.graphql.annotations.GraphQLProperty; - -@GraphQLProperty(name = "updateCheckRun", arguments = {@GraphQLArgument(name = "input")}) -public class UpdateCheckRun { - - private final CheckRun checkRun; - - @JsonCreator - public UpdateCheckRun(@JsonProperty("checkRun") CheckRun checkRun) { - this.checkRun = checkRun; - } - - public CheckRun getCheckRun() { - return checkRun; - } - -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/Viewer.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/Viewer.java deleted file mode 100644 index 8d0cd4761..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/Viewer.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2021 Julien Roy - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v4; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import io.aexp.nodes.graphql.annotations.GraphQLProperty; - -@GraphQLProperty(name = "viewer") -public class Viewer { - - private final String login; - - @JsonCreator - public Viewer(@JsonProperty("login") String login) { - this.login = login; - } - - public String getLogin() { - return login; - } -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/model/CheckAnnotationLevel.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/model/CheckAnnotationLevel.java deleted file mode 100644 index 96b4f677e..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/model/CheckAnnotationLevel.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2019 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v4.model; - -public enum CheckAnnotationLevel { - - NOTICE, WARNING, FAILURE -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/model/CheckConclusionState.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/model/CheckConclusionState.java deleted file mode 100644 index 5e7bd33ad..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/model/CheckConclusionState.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2019 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v4.model; - -public enum CheckConclusionState { - ACTION_REQUIRED, CANCELLED, FAILURE, NEUTRAL, SUCCESS, TIMEOUT -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/model/CheckRun.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/model/CheckRun.java deleted file mode 100644 index 04a7299e1..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/model/CheckRun.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2019 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v4.model; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -public class CheckRun { - - private final String id; - - @JsonCreator - public CheckRun(@JsonProperty("id") String id) { - this.id = id; - } - - public String getId() { - return id; - } - -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/model/CommentClassifiers.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/model/CommentClassifiers.java deleted file mode 100644 index 00c3b210c..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/model/CommentClassifiers.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2021 Julien Roy - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v4.model; - -public enum CommentClassifiers { - OUTDATED -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/model/RequestableCheckStatusState.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/model/RequestableCheckStatusState.java deleted file mode 100644 index c2c91f952..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/model/RequestableCheckStatusState.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2019 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v4.model; - -public enum RequestableCheckStatusState { - - COMPLETED, IN_PROGRESS, QUEUED -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/gitlab/DefaultGitlabClientFactory.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/gitlab/DefaultGitlabClientFactory.java index 0285f331a..e60eebfb7 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/gitlab/DefaultGitlabClientFactory.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/gitlab/DefaultGitlabClientFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Michael Clarke + * Copyright (C) 2021-2024 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -21,7 +21,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.mc1arke.sonarqube.plugin.InvalidConfigurationException; -import com.github.mc1arke.sonarqube.plugin.almclient.LinkHeaderReader; + import org.apache.commons.lang.StringUtils; import org.apache.http.impl.client.HttpClients; import org.sonar.api.ce.ComputeEngineSide; diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/DefaultLinkHeaderReader.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/gitlab/DefaultLinkHeaderReader.java similarity index 94% rename from src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/DefaultLinkHeaderReader.java rename to src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/gitlab/DefaultLinkHeaderReader.java index 08350bca9..f9874e420 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/DefaultLinkHeaderReader.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/gitlab/DefaultLinkHeaderReader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2021 Michael Clarke + * Copyright (C) 2020-2024 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -16,7 +16,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ -package com.github.mc1arke.sonarqube.plugin.almclient; +package com.github.mc1arke.sonarqube.plugin.almclient.gitlab; import org.sonar.api.ce.ComputeEngineSide; import org.sonar.api.server.ServerSide; diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/gitlab/GitlabRestClient.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/gitlab/GitlabRestClient.java index a0db63a9f..cc2a0997e 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/gitlab/GitlabRestClient.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/gitlab/GitlabRestClient.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021-2023 Michael Clarke + * Copyright (C) 2021-2024 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -19,7 +19,6 @@ package com.github.mc1arke.sonarqube.plugin.almclient.gitlab; import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.mc1arke.sonarqube.plugin.almclient.LinkHeaderReader; import com.github.mc1arke.sonarqube.plugin.almclient.gitlab.model.Commit; import com.github.mc1arke.sonarqube.plugin.almclient.gitlab.model.CommitNote; import com.github.mc1arke.sonarqube.plugin.almclient.gitlab.model.Discussion; diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/LinkHeaderReader.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/gitlab/LinkHeaderReader.java similarity index 89% rename from src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/LinkHeaderReader.java rename to src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/gitlab/LinkHeaderReader.java index ade72658b..1c6669990 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/LinkHeaderReader.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/gitlab/LinkHeaderReader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2021 Michael Clarke + * Copyright (C) 2020-2024 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -16,7 +16,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ -package com.github.mc1arke.sonarqube.plugin.almclient; +package com.github.mc1arke.sonarqube.plugin.almclient.gitlab; import java.util.Optional; diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityReportAnalysisComponentProvider.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityReportAnalysisComponentProvider.java index 39f367a07..19c118d83 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityReportAnalysisComponentProvider.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityReportAnalysisComponentProvider.java @@ -18,15 +18,17 @@ */ package com.github.mc1arke.sonarqube.plugin.ce; -import com.github.mc1arke.sonarqube.plugin.almclient.DefaultLinkHeaderReader; +import java.util.Arrays; +import java.util.List; + +import org.sonar.ce.task.projectanalysis.container.ReportAnalysisComponentProvider; + import com.github.mc1arke.sonarqube.plugin.almclient.azuredevops.DefaultAzureDevopsClientFactory; import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.DefaultBitbucketClientFactory; import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.HttpClientBuilderFactory; -import com.github.mc1arke.sonarqube.plugin.almclient.github.DefaultGithubClientFactory; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v3.DefaultUrlConnectionProvider; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v3.RestApplicationAuthenticationProvider; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v4.DefaultGraphqlProvider; +import com.github.mc1arke.sonarqube.plugin.almclient.github.GithubClientFactory; import com.github.mc1arke.sonarqube.plugin.almclient.gitlab.DefaultGitlabClientFactory; +import com.github.mc1arke.sonarqube.plugin.almclient.gitlab.DefaultLinkHeaderReader; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.PostAnalysisIssueVisitor; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.PullRequestFixedIssuesIssueVisitor; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.PullRequestPostAnalysisTask; @@ -36,10 +38,6 @@ import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.gitlab.GitlabMergeRequestDecorator; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.markup.MarkdownFormatterFactory; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.report.ReportGenerator; -import org.sonar.ce.task.projectanalysis.container.ReportAnalysisComponentProvider; - -import java.util.Arrays; -import java.util.List; /** * @author Michael Clarke @@ -50,8 +48,8 @@ public class CommunityReportAnalysisComponentProvider implements ReportAnalysisC public List getComponents() { return Arrays.asList(CommunityBranchLoaderDelegate.class, PullRequestPostAnalysisTask.class, PostAnalysisIssueVisitor.class, DefaultLinkHeaderReader.class, ReportGenerator.class, - MarkdownFormatterFactory.class, DefaultGraphqlProvider.class, DefaultUrlConnectionProvider.class, - DefaultGithubClientFactory.class, RestApplicationAuthenticationProvider.class, GithubPullRequestDecorator.class, + MarkdownFormatterFactory.class, + GithubClientFactory.class, GithubPullRequestDecorator.class, HttpClientBuilderFactory.class, DefaultBitbucketClientFactory.class, BitbucketPullRequestDecorator.class, DefaultGitlabClientFactory.class, GitlabMergeRequestDecorator.class, DefaultAzureDevopsClientFactory.class, AzureDevOpsPullRequestDecorator.class, diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/GithubPullRequestDecorator.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/GithubPullRequestDecorator.java index bacf7dc3b..682ec51bd 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/GithubPullRequestDecorator.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/GithubPullRequestDecorator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2022 Michael Clarke + * Copyright (C) 2020-2024 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -18,12 +18,25 @@ */ package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.github; -import com.github.mc1arke.sonarqube.plugin.almclient.github.GithubClient; +import java.io.IOException; +import java.time.Clock; +import java.util.Date; +import java.util.List; +import java.util.Optional; + +import org.kohsuke.github.GHCheckRun; +import org.kohsuke.github.GHCheckRunBuilder; +import org.kohsuke.github.GHIssueComment; +import org.kohsuke.github.GHPullRequest; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GitHub; +import org.sonar.api.ce.posttask.QualityGate; +import org.sonar.api.rule.Severity; +import org.sonar.db.alm.setting.ALM; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.alm.setting.ProjectAlmSettingDto; + import com.github.mc1arke.sonarqube.plugin.almclient.github.GithubClientFactory; -import com.github.mc1arke.sonarqube.plugin.almclient.github.model.Annotation; -import com.github.mc1arke.sonarqube.plugin.almclient.github.model.CheckRunDetails; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v4.model.CheckAnnotationLevel; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v4.model.CheckConclusionState; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.AnalysisDetails; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.DecorationResult; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.PostAnalysisIssueVisitor; @@ -31,19 +44,6 @@ import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.markup.MarkdownFormatterFactory; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.report.AnalysisSummary; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.report.ReportGenerator; -import org.sonar.api.ce.posttask.QualityGate; -import org.sonar.api.rule.Severity; -import org.sonar.db.alm.setting.ALM; -import org.sonar.db.alm.setting.AlmSettingDto; -import org.sonar.db.alm.setting.ProjectAlmSettingDto; - -import java.time.Clock; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; public class GithubPullRequestDecorator implements PullRequestBuildStatusDecorator { @@ -63,34 +63,14 @@ public GithubPullRequestDecorator(GithubClientFactory githubClientFactory, Repor @Override public DecorationResult decorateQualityGateStatus(AnalysisDetails analysisDetails, AlmSettingDto almSettingDto, ProjectAlmSettingDto projectAlmSettingDto) { - AnalysisSummary analysisSummary = reportGenerator.createAnalysisSummary(analysisDetails); - - CheckRunDetails checkRunDetails = CheckRunDetails.builder() - .withAnnotations(analysisDetails.getScmReportableIssues().stream() - .map(GithubPullRequestDecorator::createAnnotation) - .collect(Collectors.toList())) - .withCheckConclusionState(analysisDetails.getQualityGateStatus() == QualityGate.Status.OK ? CheckConclusionState.SUCCESS : CheckConclusionState.FAILURE) - .withCommitId(analysisDetails.getCommitSha()) - .withSummary(analysisSummary.format(markdownFormatterFactory)) - .withDashboardUrl(analysisSummary.getDashboardUrl()) - .withPullRequestId(Integer.parseInt(analysisDetails.getPullRequestId())) - .withStartTime(analysisDetails.getAnalysisDate().toInstant().atZone(ZoneId.of("UTC"))) - .withEndTime(ZonedDateTime.now(clock)) - .withExternalId(analysisDetails.getAnalysisId()) - .withName(String.format("%s Sonarqube Results", analysisDetails.getAnalysisProjectName())) - .withTitle("Quality Gate " + (analysisDetails.getQualityGateStatus() == QualityGate.Status.OK ? "success" : "failed")) - .withProjectKey(analysisDetails.getAnalysisProjectKey()) - .build(); - try { - GithubClient githubClient = githubClientFactory.createClient(projectAlmSettingDto, almSettingDto); + GitHub github = githubClientFactory.createClient(almSettingDto, projectAlmSettingDto); + GHRepository repository = github.getRepository(projectAlmSettingDto.getAlmRepo()); - githubClient.createCheckRun(checkRunDetails, - Optional.ofNullable(projectAlmSettingDto.getSummaryCommentEnabled()) - .orElse(false)); + GHPullRequest pullRequest = createCheckRun(repository, analysisDetails, Optional.ofNullable(projectAlmSettingDto.getSummaryCommentEnabled()).orElse(false)); return DecorationResult.builder() - .withPullRequestUrl(githubClient.getRepositoryUrl() + "/pull/" + checkRunDetails.getPullRequestId()) + .withPullRequestUrl(pullRequest.getHtmlUrl().toExternalForm()) .build(); } catch (Exception ex) { throw new IllegalStateException("Could not decorate Pull Request on Github", ex); @@ -100,28 +80,67 @@ public DecorationResult decorateQualityGateStatus(AnalysisDetails analysisDetail @Override public List alm() { - return Collections.singletonList(ALM.GITHUB); + return List.of(ALM.GITHUB); } - private static Annotation createAnnotation(PostAnalysisIssueVisitor.ComponentIssue componentIssue) { - return Annotation.builder() - .withLine(Optional.ofNullable(componentIssue.getIssue().getLine()).orElse(0)) - .withScmPath(componentIssue.getScmPath().orElseThrow()) - .withMessage(Optional.ofNullable(componentIssue.getIssue().getMessage()).orElseThrow().replace("\\","\\\\").replace("\"", "\\\"")) - .withSeverity(mapToGithubAnnotationLevel(componentIssue.getIssue().severity())) - .build(); + + private GHPullRequest createCheckRun(GHRepository repository, AnalysisDetails analysisDetails, boolean postSummaryComment) throws IOException { + AnalysisSummary analysisSummary = reportGenerator.createAnalysisSummary(analysisDetails); + String summary = analysisSummary.format(markdownFormatterFactory); + + GHCheckRunBuilder.Output output = new GHCheckRunBuilder.Output("Quality Gate " + (analysisDetails.getQualityGateStatus() == QualityGate.Status.OK ? "success" : "failed"), summary); + for (PostAnalysisIssueVisitor.ComponentIssue componentIssue : analysisDetails.getScmReportableIssues()) { + output.add(new GHCheckRunBuilder.Annotation( + componentIssue.getScmPath().orElseThrow(), + Optional.ofNullable(componentIssue.getIssue().getLine()).orElse(0), + mapToGithubAnnotationLevel(componentIssue.getIssue().severity()), + Optional.ofNullable(componentIssue.getIssue().getMessage()).orElseThrow()) + ); + } + + repository.createCheckRun(String.format("%s Sonarqube Results", analysisDetails.getAnalysisProjectName()), analysisDetails.getCommitSha()) + .withStartedAt(analysisDetails.getAnalysisDate()) + .withCompletedAt(Date.from(clock.instant())) + .withStatus(GHCheckRun.Status.COMPLETED) + .withConclusion(analysisDetails.getQualityGateStatus() == QualityGate.Status.OK ? GHCheckRun.Conclusion.SUCCESS : GHCheckRun.Conclusion.FAILURE) + .withDetailsURL(analysisSummary.getDashboardUrl()) + .withExternalID(analysisDetails.getAnalysisId()) + .add(output) + .create(); + + GHPullRequest pullRequest = repository.getPullRequest(Integer.parseInt(analysisDetails.getPullRequestId())); + if (postSummaryComment) { + postSummaryComment(pullRequest, summary, analysisDetails.getAnalysisProjectKey()); + } + return pullRequest; + } + + private void postSummaryComment(GHPullRequest pullRequest, String summary, String projectId) throws IOException { + String projectCommentMarker = String.format("**Project ID:** %s", projectId); + + GHIssueComment summaryComment = pullRequest.comment(summary); + + for (GHIssueComment comment : pullRequest.getComments()) { + if ("Bot".equalsIgnoreCase(comment.getUser().getType()) + && summaryComment.getUser().getId() == comment.getUser().getId() + && (comment.getBody().contains(projectCommentMarker + "\n") || comment.getBody().contains(projectCommentMarker + "\r")) + && comment.getId() != summaryComment.getId()) { + comment.delete(); + } + } + } - private static CheckAnnotationLevel mapToGithubAnnotationLevel(String sonarqubeSeverity) { + private static GHCheckRun.AnnotationLevel mapToGithubAnnotationLevel(String sonarqubeSeverity) { switch (sonarqubeSeverity) { case Severity.INFO: - return CheckAnnotationLevel.NOTICE; + return GHCheckRun.AnnotationLevel.NOTICE; case Severity.MINOR: case Severity.MAJOR: - return CheckAnnotationLevel.WARNING; + return GHCheckRun.AnnotationLevel.WARNING; case Severity.CRITICAL: case Severity.BLOCKER: - return CheckAnnotationLevel.FAILURE; + return GHCheckRun.AnnotationLevel.FAILURE; default: throw new IllegalArgumentException("Unknown severity value: " + sonarqubeSeverity); } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/validator/GithubValidator.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/validator/GithubValidator.java index a1ab6b1d3..e01ad4cbd 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/validator/GithubValidator.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/validator/GithubValidator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Michael Clarke + * Copyright (C) 2021-2024 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -18,15 +18,15 @@ */ package com.github.mc1arke.sonarqube.plugin.server.pullrequest.validator; -import com.github.mc1arke.sonarqube.plugin.InvalidConfigurationException; -import com.github.mc1arke.sonarqube.plugin.almclient.github.GithubClientFactory; +import java.util.List; + import org.sonar.api.server.ServerSide; import org.sonar.db.alm.setting.ALM; import org.sonar.db.alm.setting.AlmSettingDto; import org.sonar.db.alm.setting.ProjectAlmSettingDto; -import java.util.Collections; -import java.util.List; +import com.github.mc1arke.sonarqube.plugin.InvalidConfigurationException; +import com.github.mc1arke.sonarqube.plugin.almclient.github.GithubClientFactory; @ServerSide public class GithubValidator implements Validator { @@ -40,17 +40,17 @@ public GithubValidator(GithubClientFactory githubClientFactory) { @Override public void validate(ProjectAlmSettingDto projectAlmSettingDto, AlmSettingDto almSettingDto) { try { - githubClientFactory.createClient(projectAlmSettingDto, almSettingDto); + githubClientFactory.createClient(almSettingDto, projectAlmSettingDto).getRepository(projectAlmSettingDto.getAlmRepo()); } catch (InvalidConfigurationException ex) { throw ex; - } catch (RuntimeException ex) { + } catch (Exception ex) { throw new InvalidConfigurationException(InvalidConfigurationException.Scope.PROJECT, "Could not create Github client - " + ex.getMessage(), ex); } } @Override public List alm() { - return Collections.singletonList(ALM.GITHUB); + return List.of(ALM.GITHUB); } } diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchPluginTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchPluginTest.java index 22fd3a21b..646244b9c 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchPluginTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchPluginTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2023 Michael Clarke + * Copyright (C) 2020-2024 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -18,14 +18,11 @@ */ package com.github.mc1arke.sonarqube.plugin; -import com.github.mc1arke.sonarqube.plugin.almclient.DefaultLinkHeaderReader; +import com.github.mc1arke.sonarqube.plugin.almclient.gitlab.DefaultLinkHeaderReader; import com.github.mc1arke.sonarqube.plugin.almclient.azuredevops.DefaultAzureDevopsClientFactory; import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.DefaultBitbucketClientFactory; import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.HttpClientBuilderFactory; -import com.github.mc1arke.sonarqube.plugin.almclient.github.DefaultGithubClientFactory; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v3.DefaultUrlConnectionProvider; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v3.RestApplicationAuthenticationProvider; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v4.DefaultGraphqlProvider; +import com.github.mc1arke.sonarqube.plugin.almclient.github.GithubClientFactory; import com.github.mc1arke.sonarqube.plugin.almclient.gitlab.DefaultGitlabClientFactory; import com.github.mc1arke.sonarqube.plugin.ce.CommunityReportAnalysisComponentProvider; import com.github.mc1arke.sonarqube.plugin.scanner.BranchConfigurationFactory; @@ -144,11 +141,8 @@ void shouldAddExtensionsForServerSideLoad() { eq(ListAction.class), eq(PullRequestWs.class), eq(GithubValidator.class), - eq(DefaultGraphqlProvider.class), - eq(DefaultGithubClientFactory.class), + eq(GithubClientFactory.class), eq(DefaultLinkHeaderReader.class), - eq(DefaultUrlConnectionProvider.class), - eq(RestApplicationAuthenticationProvider.class), eq(HttpClientBuilderFactory.class), eq(DefaultBitbucketClientFactory.class), eq(BitbucketValidator.class), diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/github/DefaultGithubClientFactoryTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/github/DefaultGithubClientFactoryTest.java deleted file mode 100644 index 29f7f434f..000000000 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/github/DefaultGithubClientFactoryTest.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2022 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github; - -import com.github.mc1arke.sonarqube.plugin.InvalidConfigurationException; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v3.RestApplicationAuthenticationProvider; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v4.GraphqlGithubClient; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v4.GraphqlProvider; -import org.assertj.core.api.Condition; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.sonar.api.config.internal.Encryption; -import org.sonar.api.config.internal.Settings; -import org.sonar.db.alm.setting.AlmSettingDto; -import org.sonar.db.alm.setting.ProjectAlmSettingDto; - -import java.io.IOException; - -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.Mockito.mock; -import static org.mockito.Mockito.when; - -class DefaultGithubClientFactoryTest { - - private final AlmSettingDto almSettingDto = mock(AlmSettingDto.class); - private final ProjectAlmSettingDto projectAlmSettingDto = mock(ProjectAlmSettingDto.class); - private final RestApplicationAuthenticationProvider restApplicationAuthenticationProvider = mock(RestApplicationAuthenticationProvider.class); - private final Settings settings = mock(Settings.class); - private final GraphqlProvider graphqlProvider = mock(GraphqlProvider.class); - - @BeforeEach - public void setUp() { - when(almSettingDto.getUrl()).thenReturn("url"); - when(almSettingDto.getDecryptedPrivateKey(any())).thenReturn("privateKey"); - when(projectAlmSettingDto.getAlmRepo()).thenReturn("repo"); - when(almSettingDto.getAppId()).thenReturn("appId"); - when(settings.getEncryption()).thenReturn(mock(Encryption.class)); - } - - @Test - void testExceptionThrownIfUrlMissing() { - when(almSettingDto.getUrl()).thenReturn(null); - DefaultGithubClientFactory underTest = new DefaultGithubClientFactory(restApplicationAuthenticationProvider, settings, graphqlProvider); - assertThatThrownBy(() -> underTest.createClient(projectAlmSettingDto, almSettingDto)) - .isInstanceOf(InvalidConfigurationException.class) - .hasMessage("No URL has been set for Github connections") - .has(new Condition<>(t -> ((InvalidConfigurationException) t).getScope() == InvalidConfigurationException.Scope.GLOBAL, "GLOBAL Scope for exception")); - } - - @Test - void testExceptionThrownIfPrivateKeyMissing() { - when(almSettingDto.getDecryptedPrivateKey(any())).thenReturn(null); - DefaultGithubClientFactory underTest = new DefaultGithubClientFactory(restApplicationAuthenticationProvider, settings, graphqlProvider); - assertThatThrownBy(() -> underTest.createClient(projectAlmSettingDto, almSettingDto)) - .isInstanceOf(InvalidConfigurationException.class) - .hasMessage("No private key has been set for Github connections") - .has(new Condition<>(t -> ((InvalidConfigurationException) t).getScope() == InvalidConfigurationException.Scope.GLOBAL, "GLOBAL Scope for exception")); - } - - @Test - void testExceptionThrownIfAlmRepoMissing() { - when(projectAlmSettingDto.getAlmRepo()).thenReturn(null); - DefaultGithubClientFactory underTest = new DefaultGithubClientFactory(restApplicationAuthenticationProvider, settings, graphqlProvider); - assertThatThrownBy(() -> underTest.createClient(projectAlmSettingDto, almSettingDto)) - .isInstanceOf(InvalidConfigurationException.class) - .hasMessage("No repository name has been set for Github connections") - .has(new Condition<>(t -> ((InvalidConfigurationException) t).getScope() == InvalidConfigurationException.Scope.PROJECT, "PROJECT Scope for exception")); - } - - @Test - void testExceptionThrownIfAppIdMissing() { - when(almSettingDto.getAppId()).thenReturn(null); - DefaultGithubClientFactory underTest = new DefaultGithubClientFactory(restApplicationAuthenticationProvider, settings, graphqlProvider); - assertThatThrownBy(() -> underTest.createClient(projectAlmSettingDto, almSettingDto)) - .isInstanceOf(InvalidConfigurationException.class) - .hasMessage("No App ID has been set for Github connections") - .has(new Condition<>(t -> ((InvalidConfigurationException) t).getScope() == InvalidConfigurationException.Scope.GLOBAL, "GLOBAL Scope for exception")); - } - - @Test - void testExceptionThrownIfAuthenticationProviderThrowsException() throws IOException { - DefaultGithubClientFactory underTest = new DefaultGithubClientFactory(restApplicationAuthenticationProvider, settings, graphqlProvider); - when(restApplicationAuthenticationProvider.getInstallationToken(any(), any(), any(), any())).thenThrow(new IOException("dummy")); - assertThatThrownBy(() -> underTest.createClient(projectAlmSettingDto, almSettingDto)) - .isInstanceOf(InvalidConfigurationException.class) - .hasMessage("Could not create Github client - dummy") - .has(new Condition<>(t -> ((InvalidConfigurationException) t).getScope() == InvalidConfigurationException.Scope.PROJECT, "PROJECT Scope for exception")); - } - - @Test - void testHappyPath() throws IOException { - DefaultGithubClientFactory underTest = new DefaultGithubClientFactory(restApplicationAuthenticationProvider, settings, graphqlProvider); - when(projectAlmSettingDto.getAlmRepo()).thenReturn("alm/slug"); - - RepositoryAuthenticationToken repositoryAuthenticationToken = mock(RepositoryAuthenticationToken.class); - when(restApplicationAuthenticationProvider.getInstallationToken(any(), any(), any(), any())).thenReturn(repositoryAuthenticationToken); - assertThat(underTest.createClient(projectAlmSettingDto, almSettingDto)).usingRecursiveComparison().isEqualTo(new GraphqlGithubClient(graphqlProvider, "url", repositoryAuthenticationToken)); - } -} diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/github/GithubClientFactoryTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/github/GithubClientFactoryTest.java new file mode 100644 index 000000000..8f354a808 --- /dev/null +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/github/GithubClientFactoryTest.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2022-2024 Michael Clarke + * + * 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 com.github.mc1arke.sonarqube.plugin.almclient.github; + +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.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; + +import org.assertj.core.api.Condition; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.kohsuke.github.GHApp; +import org.kohsuke.github.GHAppCreateTokenBuilder; +import org.kohsuke.github.GHAppInstallation; +import org.kohsuke.github.GHAppInstallationToken; +import org.kohsuke.github.GitHub; +import org.kohsuke.github.GitHubBuilder; +import org.kohsuke.github.connector.GitHubConnector; +import org.sonar.api.config.internal.Encryption; +import org.sonar.api.config.internal.Settings; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.alm.setting.ProjectAlmSettingDto; + +import com.github.mc1arke.sonarqube.plugin.InvalidConfigurationException; + +class GithubClientFactoryTest { + + private static final String PRIVATE_KEY = "-----BEGIN RSA PRIVATE KEY-----\n" + + "MIIEpAIBAAKCAQEAoA3dIxtKx7qpUAhfoJeYAYZzVdRvOW9s3GtxJVuBI6GOob2V\n" + + "J7ia1i/h3za591NggqsW+QjjknmhPmqlENCKsyU5FQMkM8eV04xwHxFwjIk6SACB\n" + + "JigRSgw95b+EKN2p0caTeOiL/+LQ3pauAf+52iFqeWAKvgjxx3+rlSXzgWCBb9uk\n" + + "1XcDoeRzeXkXvOj9ZQ8wKn8FAounmyjKaIy+u7bJCgVWHRfXwjFWsIWZJ9UY1eAR\n" + + "ADoPHIHj4WrNmAvE/kOBXNSqmkY+Yu170tQA5iVeHgezrlkM08Qj4+4pqYOPHWZ2\n" + + "0TNsRn82fuGSyejLhKATbqiOxierHzECIGGUQQIDAQABAoIBADrJjNFRu3BL89dl\n" + + "E/aw55Cb2S4L1oSClDoLrqXZi7/SHcjzkN7jk9+a+7wYZkrdEYQ9IjV7WdcZnKuH\n" + + "0TQxXNh7EhHRMxFfu/zVRvNqXOwJlWIP6V/h9KO9hlimNP0rma3m4ZDV3WIx5aT0\n" + + "NFqgmptvjaOiLp/pOiEcGCIyq9N3UZgGTeJTRdvNiEJDiFPJaJvkJuspbCAIrqFT\n" + + "hzua3aJyi47GD0nebfAfJyC8Cq9VgKzvVYoKejU51cw9+ikCVSZZiOCVWOkZTPjU\n" + + "LCuhIAtPppG5s5DxvXtHdWUlpRKRbI1tIVaTk05kBm1SOH0a3H2GiEv4jQvsyZMz\n" + + "UQQQe78CgYEAw0Ixmsgy8meCKMxH8oWO/ceyZ4uVQCGy4C/q2JmGHL0DkHF6QrZi\n" + + "DuUrqDoI88lhBL/0JyGlRTy3Ddy2hZdjUdvd6HEyLJbQvy6BmUI5AGXU0+ICHfZ2\n" + + "YDP+3zY1m4SctM1duJ6XcPPefDapbl4WMxVLs1DTHCAK4dvI9HW9WYcCgYEA0dgY\n" + + "oihENXtew/s3vlBk+ZN+5xjnRwThUouxZFnzHBeCdE1237y6lUMK6UfjTmstGxWI\n" + + "g5KoawjZJScH8UDO7OiGTuI93TUzrR1Xi63dTynfE5z5BvKKGKjM4Y3aTvAsbk4q\n" + + "b+U65srTgvtZG4ASv/4vCxGL8wTJbd8oLWhu9fcCgYEArvUKA5ntZJzw2OOqeBnK\n" + + "dYVRS0ycMHnBkPX+pZRywh9vKSc1GL/Zf2VDSBqwWNkR0LK677FLKI3trEMfXPa4\n" + + "bOnondWH0sJUS2o9f/kBoGSeXji+EuD7UtpkPteRE0exLqRxnPKl2fT6XyyPhrBR\n" + + "jfY//W2nrCTd+2D3YGx7fNMCgYEAyGK6i0c2c3f/M9lXDvcIpcfyvE5stMX1QXVC\n" + + "jdjTrfTJT7SVmuxHpLej2MccSktQhHeYqERJbgTCD5dpHznLIDKf5v5nIzFlyp+l\n" + + "dS4vkyQh8UHKEJdVxlyTYaSrXww88YzVO4tEJxZyyrapDfjMbukVFVXJNeVRUQlz\n" + + "/YCnzVsCgYBipCwzPZzVlK7WdH8jsPC19ZEFd7UIbXM8kqiWhhQp4zL7kJ81Q+zf\n" + + "xmiRGsJl9uUjypDDOp1qLejoH5EObg3MlNoOq6aqSu1ZaY0rOnALlJwmZS10G2vC\n" + + "4eO4MsTrF0fH8PnJLK/nbrGX2Ll+PyY5Zn8rfKeTVvmwjSTMFPSORw==\n" + + "-----END RSA PRIVATE KEY-----"; + + private final AlmSettingDto almSettingDto = mock(); + private final ProjectAlmSettingDto projectAlmSettingDto = mock(); + private final Settings settings = mock(); + private final Clock clock = Clock.fixed(Instant.ofEpochSecond(123456789123L), ZoneId.of("UTC")); + private final GitHubBuilder githubBuilder = mock(); + private final Encryption encryption = mock(); + + @BeforeEach + void setUp() { + when(almSettingDto.getUrl()).thenReturn("url"); + when(almSettingDto.getDecryptedPrivateKey(any())).thenReturn("privateKey"); + when(projectAlmSettingDto.getAlmRepo()).thenReturn("owner/repo"); + when(almSettingDto.getAppId()).thenReturn("appId"); + when(settings.getEncryption()).thenReturn(encryption); + when(almSettingDto.getDecryptedPrivateKey(any())).thenReturn(PRIVATE_KEY); + } + + @Test + void shouldThrowExceptionIfUrlMissingInAlmSettings() { + when(almSettingDto.getUrl()).thenReturn(null); + GithubClientFactory underTest = new GithubClientFactory(clock, settings, () -> githubBuilder); + assertThatThrownBy(() -> underTest.createClient(almSettingDto, projectAlmSettingDto)) + .isInstanceOf(InvalidConfigurationException.class) + .hasMessage("No URL has been set for Github connections") + .has(new Condition<>(t -> ((InvalidConfigurationException) t).getScope() == InvalidConfigurationException.Scope.GLOBAL, "GLOBAL Scope for exception")); + } + + @Test + void shouldThrowExceptionIsPrivateKeyMissingInAlmSettings() { + when(almSettingDto.getDecryptedPrivateKey(any())).thenReturn(null); + GithubClientFactory underTest = new GithubClientFactory(clock, settings, () -> githubBuilder); + assertThatThrownBy(() -> underTest.createClient(almSettingDto, projectAlmSettingDto)) + .isInstanceOf(InvalidConfigurationException.class) + .hasMessage("No private key has been set for Github connections") + .has(new Condition<>(t -> ((InvalidConfigurationException) t).getScope() == InvalidConfigurationException.Scope.GLOBAL, "GLOBAL Scope for exception")); + } + + @Test + void shouldThrowExceptionIfRepoMissingInAlmSettings() { + when(projectAlmSettingDto.getAlmRepo()).thenReturn(null); + GithubClientFactory underTest = new GithubClientFactory(clock, settings, () -> githubBuilder); + assertThatThrownBy(() -> underTest.createClient(almSettingDto, projectAlmSettingDto)) + .isInstanceOf(InvalidConfigurationException.class) + .hasMessage("No repository name has been set for Github connections") + .has(new Condition<>(t -> ((InvalidConfigurationException) t).getScope() == InvalidConfigurationException.Scope.PROJECT, "PROJECT Scope for exception")); + } + + @Test + void shouldThrowExceptionIfAppIdMissingInAlmSettings() { + when(almSettingDto.getAppId()).thenReturn(null); + GithubClientFactory underTest = new GithubClientFactory(clock, settings, () -> githubBuilder); + assertThatThrownBy(() -> underTest.createClient(almSettingDto, projectAlmSettingDto)) + .isInstanceOf(InvalidConfigurationException.class) + .hasMessage("No App ID has been set for Github connections") + .has(new Condition<>(t -> ((InvalidConfigurationException) t).getScope() == InvalidConfigurationException.Scope.GLOBAL, "GLOBAL Scope for exception")); + } + + @Test + void shouldThrowExceptionIfGithubCallFails() throws IOException { + GithubClientFactory underTest = new GithubClientFactory(clock, settings, () -> githubBuilder); + GitHub github = mock(); + when(github.getApp()).thenThrow(new IOException("dummy")); + when(githubBuilder.build()).thenReturn(github); + when(githubBuilder.withEndpoint(any())).thenReturn(githubBuilder); + when(githubBuilder.withJwtToken(any())).thenReturn(githubBuilder); + when(githubBuilder.withConnector(any(GitHubConnector.class))).thenReturn(githubBuilder); + assertThatThrownBy(() -> underTest.createClient(almSettingDto, projectAlmSettingDto)) + .isInstanceOf(InvalidConfigurationException.class) + .hasMessage("Could not create Github client - dummy") + .has(new Condition<>(t -> ((InvalidConfigurationException) t).getScope() == InvalidConfigurationException.Scope.PROJECT, "PROJECT Scope for exception")); + } + + @Test + void shouldThrowExceptionIfRepoNameDoesNotContainSlash() { + when(projectAlmSettingDto.getAlmRepo()).thenReturn("repo"); + GithubClientFactory underTest = new GithubClientFactory(clock, settings, () -> githubBuilder); + assertThatThrownBy(() -> underTest.createClient(almSettingDto, projectAlmSettingDto)) + .usingRecursiveComparison() + .isEqualTo(new InvalidConfigurationException(InvalidConfigurationException.Scope.PROJECT, "Repository name must be in the format owner/repo")); + } + + @Test + void shouldThrowExceptionIfRsaKeyIsNotParseable() { + when(almSettingDto.getDecryptedPrivateKey(any())).thenReturn("invalid"); + GithubClientFactory underTest = new GithubClientFactory(clock, settings, () -> githubBuilder); + assertThatThrownBy(() -> underTest.createClient(almSettingDto, projectAlmSettingDto)) + .usingRecursiveComparison() + .isEqualTo(new InvalidConfigurationException(InvalidConfigurationException.Scope.GLOBAL, "Private key could not be parsed")); + } + + @Test + void shouldReturnValidGithubTokenWhenCalledWithCorrectParameters() throws IOException { + GithubClientFactory underTest = new GithubClientFactory(clock, settings, () -> githubBuilder); + when(projectAlmSettingDto.getAlmRepo()).thenReturn("alm/slug"); + + GitHub github = mock(); + when(githubBuilder.withEndpoint(any())).thenReturn(githubBuilder); + when(githubBuilder.withJwtToken(any())).thenReturn(githubBuilder); + when(githubBuilder.withConnector(any(GitHubConnector.class))).thenReturn(githubBuilder); + when(githubBuilder.withAppInstallationToken(any())).thenReturn(githubBuilder); + when(githubBuilder.build()).thenReturn(github); + GHApp ghApp = mock(); + when(github.getApp()).thenReturn(ghApp); + GHAppInstallation ghAppInstallation = mock(); + when(ghApp.getInstallationByRepository(any(), any())).thenReturn(ghAppInstallation); + GHAppCreateTokenBuilder tokenBuilder = mock(); + when(ghAppInstallation.createToken()).thenReturn(tokenBuilder); + GHAppInstallationToken ghAppInstallationToken = mock(); + when(ghAppInstallationToken.getToken()).thenReturn("token"); + when(tokenBuilder.create()).thenReturn(ghAppInstallationToken); + when(tokenBuilder.repositories(any())).thenReturn(tokenBuilder); + + assertThat(underTest.createClient(almSettingDto, projectAlmSettingDto)).isSameAs(github); + verify(tokenBuilder).create(); + verify(githubBuilder, times(2)).withEndpoint("url"); + verify(githubBuilder).withJwtToken("eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjEyMzQ1Njc4OTExMywiZXhwIjoxMjM0NTY3ODkyMzMsImlzcyI6ImFwcElkIn0.Ut0Cf_eKvDNHLIzFx1q-8yweUKOPhA1eSKALpUVtmlY7LHK3G_btuRUnIH4sfKsjEIS-3urq0FxXW7EcmGSxz8ARUNFe26mhnVtY7odMEJip2GhbC397WkIgf-_HV0M8JnMRQ0InFiGMiXKpMM08w02QgHdIV0kPa41HfMiZLYS3VdmbYU4NX8gvVN0mrAHV1brqcdUGJLwSYJBsz3yd8vTtb_M0Kl4TY9jH5Wvh4wRbSpjGmo2vTID3ioHL9JVSBi6El0gKL3AGk5n5BXfVlYvPEIwbtQp0GB-E2azEnQYxyshNid9K1d2Y032AtF_AQ0cXCssoLClUUcke6yiW8Q"); + verify(githubBuilder, times(2)).withConnector(any(GitHubConnector.class)); + verify(githubBuilder).withAppInstallationToken("token"); + verify(githubBuilder, times(2)).build(); + verifyNoMoreInteractions(githubBuilder); + + } +} diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v3/DefaultUrlConnectionProviderTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v3/DefaultUrlConnectionProviderTest.java deleted file mode 100644 index fdfd2ec0b..000000000 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v3/DefaultUrlConnectionProviderTest.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2019 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v3; - -import org.junit.Test; - -import java.io.IOException; - -import static org.junit.Assert.assertEquals; - -public class DefaultUrlConnectionProviderTest { - - @Test - public void testValidInputStreamReturned() throws IOException { - DefaultUrlConnectionProvider testCase = new DefaultUrlConnectionProvider(); - assertEquals("http://localhost/", testCase.createUrlConnection("http://localhost/").getURL().toString()); - } -} diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v3/RestApplicationAuthenticationProviderTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v3/RestApplicationAuthenticationProviderTest.java deleted file mode 100644 index e22f4dde5..000000000 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v3/RestApplicationAuthenticationProviderTest.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright (C) 2020-2023 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v3; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.mc1arke.sonarqube.plugin.InvalidConfigurationException; -import com.github.mc1arke.sonarqube.plugin.almclient.LinkHeaderReader; -import com.github.mc1arke.sonarqube.plugin.almclient.github.RepositoryAuthenticationToken; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v3.model.AppInstallation; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v3.model.AppToken; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v3.model.InstallationRepositories; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v3.model.Owner; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v3.model.Repository; -import org.apache.commons.io.IOUtils; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.URLConnection; -import java.nio.charset.StandardCharsets; -import java.time.Clock; -import java.time.Instant; -import java.time.ZoneId; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; - -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.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -class RestApplicationAuthenticationProviderTest { - - @Test - void shouldReturnTokenForPaginatedInstallationsAndTokens() throws IOException { - ObjectMapper objectMapper = new ObjectMapper(); - UrlConnectionProvider urlConnectionProvider = mock(UrlConnectionProvider.class); - Clock clock = Clock.fixed(Instant.ofEpochMilli(123456789L), ZoneId.of("UTC")); - LinkHeaderReader linkHeaderReader = mock(LinkHeaderReader.class); - - when(linkHeaderReader.findNextLink(any())).thenAnswer(i -> Optional.ofNullable(i.getArgument(0))); - - String apiUrl = "https://api.url/api/v3"; - String appId = "appID"; - String apiPrivateKey; - try (InputStream inputStream = Optional.ofNullable(getClass().getResourceAsStream("/rsa-private-key.pem")).orElseThrow()) { - apiPrivateKey = IOUtils.toString(inputStream, StandardCharsets.UTF_8); - } - String projectPath = "path/repo-49.3"; - - List appPageUrlConnections = new ArrayList<>(); - List appTokenConnections = new ArrayList<>(); - List appRepositoryConnections = new ArrayList<>(); - for (int appPage = 0; appPage < 5; appPage++) { - List appPageContents = new ArrayList<>(); - for (int i = 0; i < 10; i++) { - int itemNumber = (appPage * 10) + i; - appPageContents.add(new AppInstallation("http://repository.url/item-" + itemNumber, "http://acccess-token.url/item-" + itemNumber)); - - HttpURLConnection appTokenConnection = mock(HttpURLConnection.class); - when(appTokenConnection.getInputStream()).thenReturn(new ByteArrayInputStream(objectMapper.writeValueAsBytes(new AppToken("token-" + itemNumber)))); - when(urlConnectionProvider.createUrlConnection("http://acccess-token.url/item-" + itemNumber)).thenReturn(appTokenConnection); - appTokenConnections.add(appTokenConnection); - - List repositories = new ArrayList<>(); - for (int x = 0; x < 5; x++) { - repositories.add(new Repository("nodeId", "path/repo-" + itemNumber + "." + x, "url", "repo-" + itemNumber + "." + x, new Owner("login"))); - } - - HttpURLConnection repositoryConnection = mock(HttpURLConnection.class); - when(repositoryConnection.getInputStream()).thenAnswer(invocation -> new ByteArrayInputStream(objectMapper.writeValueAsBytes(new InstallationRepositories(repositories.toArray(new Repository[0]))))); - when(urlConnectionProvider.createUrlConnection("http://repository.url/item-" + itemNumber)).thenReturn(repositoryConnection); - if (i == 1 && appPage == 1) { - when(repositoryConnection.getHeaderField("Link")).thenReturn("http://repository.url/item-" + (itemNumber + 1)); - } - appRepositoryConnections.add(repositoryConnection); - } - HttpURLConnection appPageUrlConnection = mock(HttpURLConnection.class); - when(appPageUrlConnection.getInputStream()).thenReturn(new ByteArrayInputStream(objectMapper.writeValueAsBytes(appPageContents))); - if (appPage < 4) { - when(appPageUrlConnection.getHeaderField("Link")).thenReturn(apiUrl + "/app/installations?page=" + (appPage + 1)); - } - appPageUrlConnections.add(appPageUrlConnection); - if (appPage == 0) { - when(urlConnectionProvider.createUrlConnection(apiUrl + "/app/installations")).thenReturn(appPageUrlConnection); - } else { - when(urlConnectionProvider.createUrlConnection(apiUrl + "/app/installations?page=" + appPage)).thenReturn(appPageUrlConnection); - } - } - - RepositoryAuthenticationToken expected = new RepositoryAuthenticationToken("nodeId", "token-49", "url", "repo-49.3", "login"); - - RestApplicationAuthenticationProvider restApplicationAuthenticationProvider = new RestApplicationAuthenticationProvider(clock, linkHeaderReader, urlConnectionProvider); - - RepositoryAuthenticationToken repositoryAuthenticationToken = restApplicationAuthenticationProvider.getInstallationToken("https://api.url/api/", appId, apiPrivateKey, projectPath); - assertThat(repositoryAuthenticationToken).usingRecursiveComparison().isEqualTo(expected); - - - for (URLConnection installationsUrlConnection : appPageUrlConnections) { - ArgumentCaptor requestPropertyArgumentCaptor = ArgumentCaptor.forClass(String.class); - verify(installationsUrlConnection, times(2)) - .setRequestProperty(requestPropertyArgumentCaptor.capture(), requestPropertyArgumentCaptor.capture()); - assertThat(requestPropertyArgumentCaptor.getAllValues()).isEqualTo(Arrays.asList("Accept", "application/vnd.github.machine-man-preview+json", "Authorization", - "Bearer eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjEyMzQ0NiwiZXhwIjoxMjM1NjYsImlzcyI6ImFwcElEIn0.yMvAoUmmAHli-Mc-RidLbqlX2Cvc2RwPBwkgY6n1R2ZkV-IaY8uBO4s7pp0-3hcJvY4F7-UGnAi1dteGOODY8cOmx86DsSASJIHJ3wxaRxyLGOq2Z8A1KSWZj-F8O6wFf5pm2xzumm0gSSwdd3gQR0FiSn2TIHemjyoieNJfzvG2kgtHPBNIVaJcS8LqkVYBlvAujnAt1nQ1hIAbeQJyEmyVyb_NRMPQZZioBraobTlWdPWdnTQoNTWjmjcopIbUFw8s21uhMcDpA_6lS1iAZcoZKcpzMqsItEvQaiwYQWRccfZT69M_zWaVRjw2-eKsTuFXzumVyq3MnAoxy6R2Xw")); - } - - for (HttpURLConnection accessTokensUrlConnection : appTokenConnections) { - ArgumentCaptor requestPropertyArgumentCaptor = ArgumentCaptor.forClass(String.class); - verify(accessTokensUrlConnection, times(2)) - .setRequestProperty(requestPropertyArgumentCaptor.capture(), requestPropertyArgumentCaptor.capture()); - verify(accessTokensUrlConnection).setRequestMethod("POST"); - assertThat(requestPropertyArgumentCaptor.getAllValues()).isEqualTo(Arrays.asList("Accept", "application/vnd.github.machine-man-preview+json", - "Authorization", "Bearer eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjEyMzQ0NiwiZXhwIjoxMjM1NjYsImlzcyI6ImFwcElEIn0.yMvAoUmmAHli-Mc-RidLbqlX2Cvc2RwPBwkgY6n1R2ZkV-IaY8uBO4s7pp0-3hcJvY4F7-UGnAi1dteGOODY8cOmx86DsSASJIHJ3wxaRxyLGOq2Z8A1KSWZj-F8O6wFf5pm2xzumm0gSSwdd3gQR0FiSn2TIHemjyoieNJfzvG2kgtHPBNIVaJcS8LqkVYBlvAujnAt1nQ1hIAbeQJyEmyVyb_NRMPQZZioBraobTlWdPWdnTQoNTWjmjcopIbUFw8s21uhMcDpA_6lS1iAZcoZKcpzMqsItEvQaiwYQWRccfZT69M_zWaVRjw2-eKsTuFXzumVyq3MnAoxy6R2Xw")); - - } - - for (int i = 0; i < appRepositoryConnections.size(); i++) { - HttpURLConnection appRepositoryConnection = appRepositoryConnections.get(i); - ArgumentCaptor requestPropertyArgumentCaptor = ArgumentCaptor.forClass(String.class); - if (i == 12) { - verify(appRepositoryConnection, times(4)) - .setRequestProperty(requestPropertyArgumentCaptor.capture(), requestPropertyArgumentCaptor.capture()); - assertThat(requestPropertyArgumentCaptor.getAllValues()).isEqualTo(Arrays.asList("Accept", "application/vnd.github.machine-man-preview+json", - "Authorization", "Bearer token-11", - "Accept", "application/vnd.github.machine-man-preview+json", - "Authorization", "Bearer token-12")); - } else { - verify(appRepositoryConnection, times(2)) - .setRequestProperty(requestPropertyArgumentCaptor.capture(), requestPropertyArgumentCaptor.capture()); - assertThat(requestPropertyArgumentCaptor.getAllValues()).isEqualTo(Arrays.asList("Accept", "application/vnd.github.machine-man-preview+json", - "Authorization", "Bearer token-" + i)); - } - - } - } - - @Test - void shouldThrowExceptionIfNotTokensMatch() throws IOException { - UrlConnectionProvider urlProvider = mock(UrlConnectionProvider.class); - Clock clock = Clock.fixed(Instant.ofEpochMilli(123456789L), ZoneId.of("UTC")); - - String expectedAuthenticationToken = "expected authentication token"; - String projectPath = "project path"; - String expectedRepositoryId = "expected repository Id"; - - URLConnection installationsUrlConnection = mock(URLConnection.class); - doReturn(new ByteArrayInputStream( - "[{\"repositories_url\": \"repositories_url\", \"access_tokens_url\": \"tokens_url\"}]" - .getBytes(StandardCharsets.UTF_8))).when(installationsUrlConnection).getInputStream(); - - HttpURLConnection accessTokensUrlConnection = mock(HttpURLConnection.class); - doReturn(new ByteArrayInputStream( - ("{\"token\": \"" + expectedAuthenticationToken + "\"}").getBytes(StandardCharsets.UTF_8))) - .when(accessTokensUrlConnection).getInputStream(); - doReturn(accessTokensUrlConnection).when(urlProvider).createUrlConnection("tokens_url"); - - - HttpURLConnection repositoriesUrlConnection = mock(HttpURLConnection.class); - doReturn(new ByteArrayInputStream(("{\"repositories\": [{\"node_id\": \"" + expectedRepositoryId + - "\", \"full_name\": \"different_path\"}]}") - .getBytes(StandardCharsets.UTF_8))).when(repositoriesUrlConnection) - .getInputStream(); - doReturn(repositoriesUrlConnection).when(urlProvider).createUrlConnection("repositories_url"); - - String apiUrl = "apiUrl"; - doReturn(installationsUrlConnection).when(urlProvider).createUrlConnection(apiUrl + "/app/installations"); - - String appId = "appID"; - - String apiPrivateKey; - try (InputStream inputStream = Optional.ofNullable(getClass().getResourceAsStream("/rsa-private-key.pem")).orElseThrow()) { - apiPrivateKey = IOUtils.toString(inputStream, StandardCharsets.UTF_8); - } - - RestApplicationAuthenticationProvider testCase = new RestApplicationAuthenticationProvider(clock, h -> Optional.empty(), urlProvider); - assertThatThrownBy(() -> testCase.getInstallationToken(apiUrl, appId, apiPrivateKey, projectPath)).hasMessage( - "No token could be found with access to the requested repository using the given application ID and key") - .isExactlyInstanceOf(InvalidConfigurationException.class); - - ArgumentCaptor requestPropertyArgumentCaptor = ArgumentCaptor.forClass(String.class); - verify(installationsUrlConnection, times(2)) - .setRequestProperty(requestPropertyArgumentCaptor.capture(), requestPropertyArgumentCaptor.capture()); - assertThat(requestPropertyArgumentCaptor.getAllValues()).isEqualTo(Arrays.asList("Accept", "application/vnd.github.machine-man-preview+json", "Authorization", - "Bearer eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjEyMzQ0NiwiZXhwIjoxMjM1NjYsImlzcyI6ImFwcElEIn0.yMvAoUmmAHli-Mc-RidLbqlX2Cvc2RwPBwkgY6n1R2ZkV-IaY8uBO4s7pp0-3hcJvY4F7-UGnAi1dteGOODY8cOmx86DsSASJIHJ3wxaRxyLGOq2Z8A1KSWZj-F8O6wFf5pm2xzumm0gSSwdd3gQR0FiSn2TIHemjyoieNJfzvG2kgtHPBNIVaJcS8LqkVYBlvAujnAt1nQ1hIAbeQJyEmyVyb_NRMPQZZioBraobTlWdPWdnTQoNTWjmjcopIbUFw8s21uhMcDpA_6lS1iAZcoZKcpzMqsItEvQaiwYQWRccfZT69M_zWaVRjw2-eKsTuFXzumVyq3MnAoxy6R2Xw")); - - requestPropertyArgumentCaptor = ArgumentCaptor.forClass(String.class); - verify(accessTokensUrlConnection, times(2)) - .setRequestProperty(requestPropertyArgumentCaptor.capture(), requestPropertyArgumentCaptor.capture()); - verify(accessTokensUrlConnection).setRequestMethod("POST"); - assertThat(requestPropertyArgumentCaptor.getAllValues()).isEqualTo(Arrays.asList("Accept", "application/vnd.github.machine-man-preview+json", "Authorization", - "Bearer eyJhbGciOiJSUzI1NiJ9.eyJpYXQiOjEyMzQ0NiwiZXhwIjoxMjM1NjYsImlzcyI6ImFwcElEIn0.yMvAoUmmAHli-Mc-RidLbqlX2Cvc2RwPBwkgY6n1R2ZkV-IaY8uBO4s7pp0-3hcJvY4F7-UGnAi1dteGOODY8cOmx86DsSASJIHJ3wxaRxyLGOq2Z8A1KSWZj-F8O6wFf5pm2xzumm0gSSwdd3gQR0FiSn2TIHemjyoieNJfzvG2kgtHPBNIVaJcS8LqkVYBlvAujnAt1nQ1hIAbeQJyEmyVyb_NRMPQZZioBraobTlWdPWdnTQoNTWjmjcopIbUFw8s21uhMcDpA_6lS1iAZcoZKcpzMqsItEvQaiwYQWRccfZT69M_zWaVRjw2-eKsTuFXzumVyq3MnAoxy6R2Xw")); - - requestPropertyArgumentCaptor = ArgumentCaptor.forClass(String.class); - verify(repositoriesUrlConnection, times(2)) - .setRequestProperty(requestPropertyArgumentCaptor.capture(), requestPropertyArgumentCaptor.capture()); - verify(repositoriesUrlConnection).setRequestMethod("GET"); - assertThat(requestPropertyArgumentCaptor.getAllValues()).isEqualTo(Arrays.asList("Accept", "application/vnd.github.machine-man-preview+json", "Authorization", - "Bearer " + expectedAuthenticationToken)); - - } -} diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/CreateCheckRunTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/CreateCheckRunTest.java deleted file mode 100644 index 5b69dbf60..000000000 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/CreateCheckRunTest.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2019 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v4; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v4.model.CheckRun; -import org.junit.Test; - -import java.io.IOException; - -import static org.junit.Assert.assertEquals; - -public class CreateCheckRunTest { - - @Test - public void deserialiseReturnsSerialiseInput() throws IOException { - CreateCheckRun testCase = new CreateCheckRun("mutation ID", new CheckRun("check run ID")); - - ObjectMapper objectMapper = new ObjectMapper(); - String serialised = objectMapper.writeValueAsString(testCase); - - CreateCheckRun deserialised = objectMapper.readerFor(CreateCheckRun.class).readValue(serialised); - - assertEquals("mutation ID", deserialised.getClientMutationId()); - assertEquals("check run ID", deserialised.getCheckRun().getId()); - } -} diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/DefaultGraphqlProviderTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/DefaultGraphqlProviderTest.java deleted file mode 100644 index 3de1a2bb8..000000000 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/DefaultGraphqlProviderTest.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2019 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v4; - -import org.junit.Test; - -import static org.junit.Assert.assertNotSame; - -public class DefaultGraphqlProviderTest { - - @Test - public void newInstancesShouldBeReturnedOnRepeatCalls() { - DefaultGraphqlProvider testCase = new DefaultGraphqlProvider(); - - assertNotSame(testCase.createGraphQLTemplate(), testCase.createGraphQLTemplate()); - assertNotSame(testCase.createInputObject(), testCase.createInputObject()); - assertNotSame(testCase.createRequestBuilder(), testCase.createRequestBuilder()); - } -} diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/GraphqlGithubClientTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/GraphqlGithubClientTest.java deleted file mode 100644 index dce9dc427..000000000 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/GraphqlGithubClientTest.java +++ /dev/null @@ -1,407 +0,0 @@ -/* - * Copyright (C) 2020-2024 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v4; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.mc1arke.sonarqube.plugin.almclient.github.RepositoryAuthenticationToken; -import com.github.mc1arke.sonarqube.plugin.almclient.github.model.Annotation; -import com.github.mc1arke.sonarqube.plugin.almclient.github.model.CheckRunDetails; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v4.model.CheckAnnotationLevel; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v4.model.CheckConclusionState; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v4.model.RequestableCheckStatusState; -import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.AnalysisDetails; -import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.PostAnalysisIssueVisitor; -import io.aexp.nodes.graphql.Arguments; -import io.aexp.nodes.graphql.GraphQLRequestEntity; -import io.aexp.nodes.graphql.GraphQLResponseEntity; -import io.aexp.nodes.graphql.GraphQLTemplate; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.sonar.api.issue.Issue; -import org.sonar.api.rule.Severity; -import org.sonar.ce.task.projectanalysis.component.Component; -import org.sonar.ce.task.projectanalysis.component.ReportAttributes; - -import java.io.IOException; -import java.time.Clock; -import java.time.Instant; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.RETURNS_DEEP_STUBS; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -class GraphqlGithubClientTest { - - private final GraphqlProvider graphqlProvider = mock(GraphqlProvider.class, RETURNS_DEEP_STUBS); - private final Clock clock = Clock.fixed(Instant.ofEpochSecond(1234567890), ZoneId.of("UTC")); - - @Test - void createCheckRunExceptionOnErrorResponse() throws IOException { - RepositoryAuthenticationToken repositoryAuthenticationToken = mock(RepositoryAuthenticationToken.class); - when(repositoryAuthenticationToken.getAuthenticationToken()).thenReturn("dummyAuthToken"); - when(repositoryAuthenticationToken.getRepositoryId()).thenReturn("repository ID"); - - when(graphqlProvider.createRequestBuilder()).thenReturn(GraphQLRequestEntity.Builder()); - - GraphQLResponseEntity graphQLResponseEntity = - new ObjectMapper().readerFor(GraphQLResponseEntity.class) - .readValue("{\"errors\": [{\"message\":\"example message\", \"locations\": []}]}"); - GraphQLTemplate graphQLTemplate = mock(GraphQLTemplate.class); - when(graphQLTemplate.mutate(any(), eq(CreateCheckRun.class))).thenReturn(graphQLResponseEntity); - when(graphqlProvider.createGraphQLTemplate()).thenReturn(graphQLTemplate); - - GraphqlGithubClient testCase = - new GraphqlGithubClient(graphqlProvider, "https://api.url", repositoryAuthenticationToken); - CheckRunDetails checkRunDetails = CheckRunDetails.builder().withAnnotations(List.of()).withStartTime(ZonedDateTime.now()).withEndTime(ZonedDateTime.now()).build(); - assertThatThrownBy(() -> testCase.createCheckRun(checkRunDetails, true)) - .hasMessage( - "An error was returned in the response from the Github API:" + System.lineSeparator() + - "- Error{message='example message', locations=[]}").isExactlyInstanceOf(IllegalStateException.class); - } - - @Test - void verifyCheckRunSubmitsCorrectAnnotations() throws IOException { - RepositoryAuthenticationToken repositoryAuthenticationToken = mock(RepositoryAuthenticationToken.class); - when(repositoryAuthenticationToken.getAuthenticationToken()).thenReturn("dummyAuthToken"); - when(repositoryAuthenticationToken.getRepositoryId()).thenReturn("repository ID"); - when(repositoryAuthenticationToken.getOwnerName()).thenReturn("owner"); - when(repositoryAuthenticationToken.getRepositoryName()).thenReturn("repository"); - - List> inputObjectBuilders = new ArrayList<>(); - List> inputObjects = new ArrayList<>(); - doAnswer(i -> { - InputObject.Builder builder = spy(new InputObject.Builder<>()); - inputObjectBuilders.add(builder); - doAnswer(i2 -> { - InputObject inputObject = (InputObject) i2.callRealMethod(); - inputObjects.add(inputObject); - return inputObject; - }).when(builder).build(); - return builder; - }).when(graphqlProvider).createInputObject(); - - List requestBuilders = new ArrayList<>(); - List requestEntities = new ArrayList<>(); - doAnswer(i -> { - GraphQLRequestEntity.RequestBuilder requestBuilder = spy(GraphQLRequestEntity.Builder()); - requestBuilders.add(requestBuilder); - doAnswer(i2 -> { - GraphQLRequestEntity graphQLRequestEntity = (GraphQLRequestEntity) i2.callRealMethod(); - requestEntities.add(graphQLRequestEntity); - return graphQLRequestEntity; - }).when(requestBuilder).build(); - return requestBuilder; - }).when(graphqlProvider).createRequestBuilder(); - - ObjectMapper objectMapper = new ObjectMapper(); - GraphQLResponseEntity graphQLResponseEntity = - objectMapper.readValue("{\"response\": {\"checkRun\": {\"id\": \"ABC\"}}}", objectMapper.getTypeFactory().constructParametricType(GraphQLResponseEntity.class, CreateCheckRun.class)); - - ArgumentCaptor requestEntityArgumentCaptor = ArgumentCaptor.forClass(GraphQLRequestEntity.class); - - GraphQLTemplate graphQLTemplate = mock(GraphQLTemplate.class); - when(graphQLTemplate.mutate(requestEntityArgumentCaptor.capture(), eq(CreateCheckRun.class))).thenReturn(graphQLResponseEntity); - - GraphQLResponseEntity viewerResponseEntity = objectMapper.readValue("{" + - " \"response\": {" + - " \"login\": \"test-sonar[bot]\"" + - " }" + - "}", - objectMapper.getTypeFactory().constructParametricType(GraphQLResponseEntity.class, Viewer.class)); - ArgumentCaptor getViewer = ArgumentCaptor.forClass(GraphQLRequestEntity.class); - when(graphQLTemplate.query(getViewer.capture(), eq(Viewer.class))).thenReturn(viewerResponseEntity); - - String bodyString = objectMapper.writeValueAsString("**Project ID:** project-key-test" + System.lineSeparator()); - GraphQLResponseEntity getPullRequestResponseEntity = - objectMapper.readValue("{" + - "\"response\": " + - " {" + - " \"pullRequest\": {" + - " \"id\": \"MDExOlB1bGxSZXF1ZXN0MzUzNDc=\"," + - " \"comments\": {" + - " \"nodes\": [" + - " {" + - " \"id\": \"MDEyOklzc3VlQ29tbWVudDE1MDE3\"," + - " \"isMinimized\": false," + - " \"body\": " + bodyString + "," + - " \"author\": {" + - " \"__typename\": \"Bot\"," + - " \"login\": \"test-sonar\"" + - " }" + - " }"+ - " ],"+ - " \"pageInfo\": {" + - " \"hasNextPage\": false" + - " } " + - " }"+ - " }" + - " }" + - "}", objectMapper.getTypeFactory().constructParametricType(GraphQLResponseEntity.class, GetRepository.class)); - - ArgumentCaptor getPullRequestRequestEntityArgumentCaptor = ArgumentCaptor.forClass(GraphQLRequestEntity.class); - when(graphQLTemplate.query(getPullRequestRequestEntityArgumentCaptor.capture(), eq(GetRepository.class))).thenReturn(getPullRequestResponseEntity); - - GraphQLResponseEntity minimizeCommentResponseEntity = - objectMapper.readValue("{\"response\":{}}", objectMapper.getTypeFactory().constructParametricType(GraphQLResponseEntity.class, MinimizeComment.class)); - - ArgumentCaptor minimizeCommentRequestEntityArgumentCaptor = ArgumentCaptor.forClass(GraphQLRequestEntity.class); - when(graphQLTemplate.mutate(minimizeCommentRequestEntityArgumentCaptor.capture(), eq(MinimizeComment.class))).thenReturn(minimizeCommentResponseEntity); - - GraphQLResponseEntity addCommentResponseEntity = - objectMapper.readValue("{\"response\":{}}", objectMapper.getTypeFactory().constructParametricType(GraphQLResponseEntity.class, AddComment.class)); - - ArgumentCaptor addCommentRequestEntityArgumentCaptor = ArgumentCaptor.forClass(GraphQLRequestEntity.class); - when(graphQLTemplate.mutate(addCommentRequestEntityArgumentCaptor.capture(), eq(AddComment.class))).thenReturn(addCommentResponseEntity); - - when(graphqlProvider.createGraphQLTemplate()).thenReturn(graphQLTemplate); - - CheckRunDetails checkRunDetails = CheckRunDetails.builder() - .withAnnotations(IntStream.range(0, 30).mapToObj(i -> Annotation.builder() - .withLine(i) - .withSeverity(CheckAnnotationLevel.WARNING) - .withScmPath("scmPath" + i) - .withMessage("annotationMessage " + i) - .build()).collect(Collectors.toList())) - .withCheckConclusionState(CheckConclusionState.SUCCESS) - .withCommitId("commit-id") - .withSummary("Summary message") - .withDashboardUrl("dashboard-url") - .withStartTime(clock.instant().atZone(ZoneId.of("UTC")).minus(1, ChronoUnit.MINUTES)) - .withEndTime(clock.instant().atZone(ZoneId.of("UTC"))) - .withExternalId("external-id") - .withName("Name") - .withTitle("Title") - .withPullRequestId(999) - .withProjectKey("project-key-test") - .build(); - - - GraphqlGithubClient testCase = - new GraphqlGithubClient(graphqlProvider, "http://api.target.domain/api", repositoryAuthenticationToken); - testCase.createCheckRun(checkRunDetails, true); - - assertEquals(5, requestBuilders.size()); - - Map headers = new HashMap<>(); - headers.put("Authorization", "Bearer dummyAuthToken"); - headers.put("Accept", "application/vnd.github.antiope-preview+json"); - - verify(requestBuilders.get(0)).requestMethod(GraphQLTemplate.GraphQLMethod.MUTATE); - assertEquals(requestEntities.get(0), requestEntityArgumentCaptor.getValue()); - - ArgumentCaptor argumentsArgumentCaptor = ArgumentCaptor.forClass(Arguments.class); - verify(requestBuilders.get(0)).arguments(argumentsArgumentCaptor.capture()); - assertEquals("createCheckRun", argumentsArgumentCaptor.getValue().getDotPath()); - assertEquals(1, argumentsArgumentCaptor.getValue().getArguments().size()); - assertEquals("input", argumentsArgumentCaptor.getValue().getArguments().get(0).getKey()); - - List> expectedAnnotationObjects = new ArrayList<>(); - int position = 0; - for (Annotation annotation : checkRunDetails.getAnnotations()) { - int line = annotation.getLine(); - - assertThat(inputObjectBuilders.get(position).build()) - .usingRecursiveComparison() - .isEqualTo(new InputObject.Builder<>() - .put("startLine", line) - .put("endLine", line) - .build()); - position++; - - String path = annotation.getScmPath(); - InputObject fileBuilder = inputObjectBuilders.get(position).build(); - assertThat(fileBuilder).usingRecursiveComparison() - .isEqualTo(new InputObject.Builder<>() - .put("path", path) - .put("location", inputObjects.get(position - 1)) - .put("annotationLevel", annotation.getSeverity()) - .put("message", annotation.getMessage()) - .build()); - - expectedAnnotationObjects.add(inputObjects.get(position)); - - position++; - } - - assertEquals(4 + position, inputObjectBuilders.size()); - - assertThat(inputObjectBuilders.get(position).build()) - .usingRecursiveComparison() - .isEqualTo(new InputObject.Builder<>() - .put("title", "Title") - .put("summary", "Summary message") - .put("annotations", expectedAnnotationObjects) - .build()); - - assertThat(inputObjectBuilders.get(position + 1).build()) - .usingRecursiveComparison() - .isEqualTo(new InputObject.Builder<>() - .put("repositoryId", "repository ID") - .put("name", "Name") - .put("headSha", "commit-id") - .put("status", RequestableCheckStatusState.COMPLETED) - .put("conclusion", CheckConclusionState.SUCCESS) - .put("detailsUrl", "dashboard-url") - .put("startedAt", "2009-02-13T23:30:30Z") - .put("completedAt", "2009-02-13T23:31:30Z") - .put("externalId", "external-id") - .put("output", inputObjects.get(position)) - .build()); - - for (int i = 0; i < 5; i++) { - verify(requestBuilders.get(i)).url("http://api.target.domain/api/graphql"); - verify(requestBuilders.get(i)).headers(headers); - verify(requestBuilders.get(i)).build(); - } - - // Verify GetViewer - assertEquals(requestEntities.get(1), getViewer.getValue()); - assertEquals( - "query { viewer { login } } ", - getViewer.getValue().getRequest() - ); - - // Verify GetPullRequest - assertEquals(requestEntities.get(2), getPullRequestRequestEntityArgumentCaptor.getValue()); - assertEquals( - "query { repository (owner:\"owner\",name:\"repository\") { url pullRequest : pullRequest (number:999) { comments : comments (first:100) { nodes" + - " { author { type : __typename login } id minimized : isMinimized body } pageInfo { hasNextPage endCursor } } id } } } ", - getPullRequestRequestEntityArgumentCaptor.getValue().getRequest() - ); - - // Validate Minimize Comment - assertEquals(requestEntities.get(3), minimizeCommentRequestEntityArgumentCaptor.getValue()); - assertEquals( - "mutation { minimizeComment (input:{classifier:OUTDATED,subjectId:\"MDEyOklzc3VlQ29tbWVudDE1MDE3\"}) { clientMutationId } } ", - minimizeCommentRequestEntityArgumentCaptor.getValue().getRequest() - ); - - // Validate AddComment - assertEquals(requestEntities.get(4), addCommentRequestEntityArgumentCaptor.getValue()); - assertEquals( - "mutation { addComment (input:{body:\"Summary message\",subjectId:\"MDExOlB1bGxSZXF1ZXN0MzUzNDc=\"}) { clientMutationId } } ", - addCommentRequestEntityArgumentCaptor.getValue().getRequest() - ); - - } - - @Test - void checkExcessIssuesCorrectlyReported() throws IOException { - ReportAttributes reportAttributes = mock(ReportAttributes.class); - when(reportAttributes.getScmPath()).thenReturn(Optional.of("abc")); - Component component = mock(Component.class); - when(component.getType()).thenReturn(Component.Type.FILE); - when(component.getReportAttributes()).thenReturn(reportAttributes); - List issues = IntStream.range(0, 120) - .mapToObj(i -> { - PostAnalysisIssueVisitor.LightIssue defaultIssue = mock(PostAnalysisIssueVisitor.LightIssue.class); - when(defaultIssue.severity()).thenReturn(Severity.INFO); - when(defaultIssue.getMessage()).thenReturn("message"); - when(defaultIssue.status()).thenReturn(Issue.STATUS_OPEN); - when(defaultIssue.resolution()).thenReturn(null); - return defaultIssue; - }) - .map(i -> { - PostAnalysisIssueVisitor.ComponentIssue componentIssue = mock( - PostAnalysisIssueVisitor.ComponentIssue.class); - when(componentIssue.getComponent()).thenReturn(component); - when(componentIssue.getIssue()).thenReturn(i); - return componentIssue; - }).collect(Collectors.toList()); - - AnalysisDetails analysisDetails = mock(AnalysisDetails.class); - when(analysisDetails.getScmReportableIssues()).thenReturn(issues); - when(analysisDetails.getPullRequestId()).thenReturn("13579"); - when(analysisDetails.getAnalysisProjectKey()).thenReturn("projectKey"); - when(analysisDetails.getAnalysisDate()).thenReturn(new Date()); - - List builders = new ArrayList<>(); - - GraphqlProvider graphqlProvider = mock(GraphqlProvider.class); - when(graphqlProvider.createInputObject()).thenAnswer(i -> { - InputObject.Builder builder = spy(new InputObject.Builder<>()); - builders.add(builder); - return builder; - }); - - GraphQLRequestEntity.RequestBuilder requestBuilder = GraphQLRequestEntity.Builder(); - ObjectMapper objectMapper = new ObjectMapper(); - - when(graphqlProvider.createRequestBuilder()).thenReturn(requestBuilder); - - GraphQLTemplate graphQLTemplate = mock(GraphQLTemplate.class); - GraphQLResponseEntity graphQLResponseEntity = - new ObjectMapper().readValue("{\"response\": {\"checkRun\": {\"id\": \"ABC\"}}}", objectMapper.getTypeFactory().constructParametricType(GraphQLResponseEntity.class, CreateCheckRun.class)); - when(graphQLTemplate.mutate(any(), eq(CreateCheckRun.class))).thenReturn(graphQLResponseEntity); - GraphQLResponseEntity graphQLResponseEntity2 = - new ObjectMapper().readValue("{\"response\": {\"checkRun\": {\"id\": \"ABC\"}}}", objectMapper.getTypeFactory().constructParametricType(GraphQLResponseEntity.class, UpdateCheckRun.class)); - when(graphQLTemplate.mutate(any(), eq(UpdateCheckRun.class))).thenReturn(graphQLResponseEntity2); - when(graphqlProvider.createGraphQLTemplate()).thenReturn(graphQLTemplate); - - Clock clock = Clock.fixed(Instant.now(), ZoneId.of("UTC")); - - RepositoryAuthenticationToken repositoryAuthenticationToken = mock(RepositoryAuthenticationToken.class); - when(repositoryAuthenticationToken.getAuthenticationToken()).thenReturn("dummy"); - - CheckRunDetails checkRunDetails = mock(CheckRunDetails.class); - when(checkRunDetails.getAnnotations()).thenReturn(IntStream.range(0, 120).mapToObj(i -> Annotation.builder() - .withLine(i).withMessage("message " + i) - .withSeverity(CheckAnnotationLevel.NOTICE) - .withScmPath("path " + i) - .build()) - .collect(Collectors.toList())); - when(checkRunDetails.getStartTime()).thenReturn(clock.instant().atZone(ZoneId.of("UTC"))); - when(checkRunDetails.getEndTime()).thenReturn(clock.instant().atZone(ZoneId.of("UTC"))); - - GraphqlGithubClient testCase = new GraphqlGithubClient(graphqlProvider, "https://api.url/path", repositoryAuthenticationToken); - testCase.createCheckRun(checkRunDetails, false); - - ArgumentCaptor> classArgumentCaptor = ArgumentCaptor.forClass(Class.class); - verify(graphQLTemplate, times(3)).mutate(any(GraphQLRequestEntity.class), classArgumentCaptor.capture()); - - assertThat(classArgumentCaptor.getAllValues()).containsExactly(CreateCheckRun.class, UpdateCheckRun.class, UpdateCheckRun.class); - - ArgumentCaptor> annotationsArgumentCaptor = ArgumentCaptor.forClass(List.class); - verify(builders.get(100), times(3)).put(eq("annotations"), annotationsArgumentCaptor.capture()); - assertThat(annotationsArgumentCaptor.getAllValues().get(0)).hasSize(50); - assertThat(annotationsArgumentCaptor.getAllValues().get(1)).hasSize(50); - assertThat(annotationsArgumentCaptor.getAllValues().get(2)).hasSize(20); - } - -} diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/UpdateCheckRunTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/UpdateCheckRunTest.java deleted file mode 100644 index ff2088c8b..000000000 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/github/v4/UpdateCheckRunTest.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2020 Michael Clarke - * - * 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 com.github.mc1arke.sonarqube.plugin.almclient.github.v4; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v4.model.CheckRun; -import org.junit.Test; - -import java.io.IOException; - -import static org.junit.Assert.assertEquals; - -public class UpdateCheckRunTest { - - @Test - public void deserialiseReturnsSerialiseInput() throws IOException { - UpdateCheckRun testCase = new UpdateCheckRun(new CheckRun("check run ID")); - - ObjectMapper objectMapper = new ObjectMapper(); - String serialised = objectMapper.writeValueAsString(testCase); - - UpdateCheckRun deserialised = objectMapper.readerFor(UpdateCheckRun.class).readValue(serialised); - - assertEquals("check run ID", deserialised.getCheckRun().getId()); - } -} diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/DefaultLinkHeaderReaderTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/gitlab/DefaultLinkHeaderReaderTest.java similarity index 95% rename from src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/DefaultLinkHeaderReaderTest.java rename to src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/gitlab/DefaultLinkHeaderReaderTest.java index c89ab83a4..38d3003d7 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/DefaultLinkHeaderReaderTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/gitlab/DefaultLinkHeaderReaderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Michael Clarke + * Copyright (C) 2021-2024 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -16,7 +16,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ -package com.github.mc1arke.sonarqube.plugin.almclient; +package com.github.mc1arke.sonarqube.plugin.almclient.gitlab; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/gitlab/GitlabRestClientTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/gitlab/GitlabRestClientTest.java index 972159f37..7182e145b 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/gitlab/GitlabRestClientTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/gitlab/GitlabRestClientTest.java @@ -1,22 +1,35 @@ +/* + * Copyright (C) 2021-2024 Michael Clarke + * + * 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 com.github.mc1arke.sonarqube.plugin.almclient.gitlab; import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.mc1arke.sonarqube.plugin.almclient.LinkHeaderReader; import com.github.mc1arke.sonarqube.plugin.almclient.gitlab.model.MergeRequestNote; import org.apache.http.HttpEntity; import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.StatusLine; -import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.message.BasicNameValuePair; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.List; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityReportAnalysisComponentProviderTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityReportAnalysisComponentProviderTest.java index 8feeed4d9..b19df3150 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityReportAnalysisComponentProviderTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityReportAnalysisComponentProviderTest.java @@ -24,14 +24,11 @@ import org.junit.jupiter.api.Test; -import com.github.mc1arke.sonarqube.plugin.almclient.DefaultLinkHeaderReader; +import com.github.mc1arke.sonarqube.plugin.almclient.gitlab.DefaultLinkHeaderReader; import com.github.mc1arke.sonarqube.plugin.almclient.azuredevops.DefaultAzureDevopsClientFactory; import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.DefaultBitbucketClientFactory; import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.HttpClientBuilderFactory; -import com.github.mc1arke.sonarqube.plugin.almclient.github.DefaultGithubClientFactory; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v3.DefaultUrlConnectionProvider; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v3.RestApplicationAuthenticationProvider; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v4.DefaultGraphqlProvider; +import com.github.mc1arke.sonarqube.plugin.almclient.github.GithubClientFactory; import com.github.mc1arke.sonarqube.plugin.almclient.gitlab.DefaultGitlabClientFactory; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.PostAnalysisIssueVisitor; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.PullRequestFixedIssuesIssueVisitor; @@ -53,8 +50,8 @@ void shouldReturnAllRegisteredReportComponents() { List result = new CommunityReportAnalysisComponentProvider().getComponents(); assertThat(result).containsExactly(CommunityBranchLoaderDelegate.class, PullRequestPostAnalysisTask.class, PostAnalysisIssueVisitor.class, DefaultLinkHeaderReader.class, ReportGenerator.class, - MarkdownFormatterFactory.class, DefaultGraphqlProvider.class, DefaultUrlConnectionProvider.class, - DefaultGithubClientFactory.class, RestApplicationAuthenticationProvider.class, GithubPullRequestDecorator.class, + MarkdownFormatterFactory.class, + GithubClientFactory.class, GithubPullRequestDecorator.class, HttpClientBuilderFactory.class, DefaultBitbucketClientFactory.class, BitbucketPullRequestDecorator.class, DefaultGitlabClientFactory.class, GitlabMergeRequestDecorator.class, DefaultAzureDevopsClientFactory.class, AzureDevOpsPullRequestDecorator.class, diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/GithubPullRequestDecoratorTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/GithubPullRequestDecoratorTest.java index b60b40171..6129771b6 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/GithubPullRequestDecoratorTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/GithubPullRequestDecoratorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2022 Michael Clarke + * Copyright (C) 2020-2024 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -18,63 +18,71 @@ */ package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.github; -import com.github.mc1arke.sonarqube.plugin.almclient.github.GithubClient; -import com.github.mc1arke.sonarqube.plugin.almclient.github.GithubClientFactory; -import com.github.mc1arke.sonarqube.plugin.almclient.github.model.Annotation; -import com.github.mc1arke.sonarqube.plugin.almclient.github.model.CheckRunDetails; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v4.model.CheckAnnotationLevel; -import com.github.mc1arke.sonarqube.plugin.almclient.github.v4.model.CheckConclusionState; -import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.AnalysisDetails; -import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.DecorationResult; -import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.PostAnalysisIssueVisitor; -import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.markup.MarkdownFormatterFactory; -import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.report.AnalysisSummary; -import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.report.ReportGenerator; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.sonar.api.ce.posttask.QualityGate; -import org.sonar.api.rule.Severity; -import org.sonar.ce.task.projectanalysis.component.Component; -import org.sonar.db.alm.setting.ALM; -import org.sonar.db.alm.setting.AlmSettingDto; -import org.sonar.db.alm.setting.ProjectAlmSettingDto; +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.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; import java.io.IOException; +import java.net.URL; import java.time.Clock; import java.time.Instant; import java.time.ZoneId; -import java.util.Collections; import java.util.Date; +import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.IntStream; -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.anyBoolean; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.kohsuke.github.GHCheckRun; +import org.kohsuke.github.GHCheckRunBuilder; +import org.kohsuke.github.GHIssueComment; +import org.kohsuke.github.GHPullRequest; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GHUser; +import org.kohsuke.github.GitHub; +import org.mockito.ArgumentCaptor; +import org.mockito.invocation.InvocationOnMock; +import org.sonar.api.ce.posttask.QualityGate; +import org.sonar.api.rule.Severity; +import org.sonar.ce.task.projectanalysis.component.Component; +import org.sonar.db.alm.setting.ALM; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.alm.setting.ProjectAlmSettingDto; + +import com.github.mc1arke.sonarqube.plugin.almclient.github.GithubClientFactory; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.AnalysisDetails; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.DecorationResult; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.PostAnalysisIssueVisitor; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.markup.MarkdownFormatterFactory; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.report.AnalysisSummary; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.report.ReportGenerator; class GithubPullRequestDecoratorTest { - private final GithubClient githubClient = mock(GithubClient.class); - private final AnalysisDetails analysisDetails = mock(AnalysisDetails.class); - private final GithubClientFactory githubClientFactory = mock(GithubClientFactory.class); - private final ReportGenerator reportGenerator = mock(ReportGenerator.class); - private final MarkdownFormatterFactory markdownFormatterFactory = mock(MarkdownFormatterFactory.class); + private final AnalysisDetails analysisDetails = mock(); + private final GithubClientFactory githubClientFactory = mock(); + private final ReportGenerator reportGenerator = mock(); + private final MarkdownFormatterFactory markdownFormatterFactory = mock(); private final Clock clock = Clock.fixed(Instant.ofEpochSecond(102030405), ZoneId.of("UTC")); private final GithubPullRequestDecorator testCase = new GithubPullRequestDecorator(githubClientFactory, reportGenerator, markdownFormatterFactory, clock); - private final ProjectAlmSettingDto projectAlmSettingDto = mock(ProjectAlmSettingDto.class); - private final AlmSettingDto almSettingDto = mock(AlmSettingDto.class); - private final AnalysisSummary analysisSummary = mock(AnalysisSummary.class); + private final ProjectAlmSettingDto projectAlmSettingDto = mock(); + private final AlmSettingDto almSettingDto = mock(); + private final AnalysisSummary analysisSummary = mock(); + private final GitHub gitHub = mock(); @BeforeEach - void setUp() { + void setUp() throws IOException { + doReturn("alm-repo").when(projectAlmSettingDto).getAlmRepo(); doReturn("123").when(analysisDetails).getPullRequestId(); doReturn(Date.from(clock.instant())).when(analysisDetails).getAnalysisDate(); doReturn("analysis-id").when(analysisDetails).getAnalysisId(); @@ -98,18 +106,18 @@ void setUp() { doReturn(analysisSummary).when(reportGenerator).createAnalysisSummary(any()); doReturn("dashboard-url").when(analysisSummary).getDashboardUrl(); doReturn("report summary").when(analysisSummary).format(any()); + doReturn(gitHub).when(githubClientFactory).createClient(any(), any()); } @Test - void verifyCorrectNameReturned() { - assertThat(testCase.alm()).isEqualTo(Collections.singletonList(ALM.GITHUB)); + void shouldReturnCorrectAlms() { + assertThat(testCase.alm()).isEqualTo(List.of(ALM.GITHUB)); } @Test - void verifyClientExceptionPropagated() throws IOException { + void shouldThrowExceptionIfClientCreationFails() throws IOException { Exception dummyException = new IOException("Dummy Exception"); - doReturn(githubClient).when(githubClientFactory).createClient(any(), any()); - doThrow(dummyException).when(githubClient).createCheckRun(any(), anyBoolean()); + doThrow(dummyException).when(githubClientFactory).createClient(any(), any()); assertThatThrownBy(() -> testCase.decorateQualityGateStatus(analysisDetails, almSettingDto, projectAlmSettingDto)) .hasMessage("Could not decorate Pull Request on Github") @@ -117,38 +125,131 @@ void verifyClientExceptionPropagated() throws IOException { } @Test - void verifyCorrectArgumentsAndReturnValuesUsed() throws IOException { + void shouldDecoratePullRequestWithCorrectAnalysisAndSummaryCommentWhenEnabled() throws IOException { doReturn(true).when(projectAlmSettingDto).getSummaryCommentEnabled(); - DecorationResult expectedResult = DecorationResult.builder().withPullRequestUrl("http://github.url/repo/path/pull/123").build(); - doReturn(githubClient).when(githubClientFactory).createClient(any(), any()); - doReturn("checkRunId").when(githubClient).createCheckRun(any(), anyBoolean()); - doReturn("http://github.url/repo/path").when(githubClient).getRepositoryUrl(); + GHRepository repository = mock(); + doReturn(repository).when(gitHub).getRepository(any()); + GHCheckRunBuilder checkRunBuilder = mock(InvocationOnMock::getMock); + doReturn(null).when(checkRunBuilder).create(); + doReturn(checkRunBuilder).when(repository).createCheckRun(any(), any()); + GHPullRequest pullRequest = mock(); + GHIssueComment comment1 = createComment("summary comment from current bot user, no project ID", "Bot", 123, 1); + GHIssueComment comment2 = createComment("summary comment from non bot user with no project ID", "User", 321, 2); + GHIssueComment comment3 = createComment("summary comment from current bot user, with project ID. **Project ID:** project-key\n", "Bot", 123, 3); + GHIssueComment comment4 = createComment("summary comment from other bot user, with project ID. **Project ID:** project-key\n", "Bot", 999, 4); + GHIssueComment comment5 = createComment("summary comment from other bot user, with project ID. **Project ID:** project-key\n", "User", 111, 5); + GHIssueComment summaryComment = createComment("summary comment from current bot user, with project ID. **Project ID:** project-key\r", "Bot", 123, 6); + doReturn(List.of(comment1, comment2, comment3, comment4, comment5, summaryComment)).when(pullRequest).getComments(); + doReturn(summaryComment).when(pullRequest).comment(any(String.class)); + doReturn(pullRequest).when(repository).getPullRequest(anyInt()); + doReturn(new URL("http://url.of/pull/request")).when(pullRequest).getHtmlUrl(); DecorationResult decorationResult = testCase.decorateQualityGateStatus(analysisDetails, almSettingDto, projectAlmSettingDto); - ArgumentCaptor checkRunDetailsArgumentCaptor = ArgumentCaptor.forClass(CheckRunDetails.class); - verify(githubClient).createCheckRun(checkRunDetailsArgumentCaptor.capture(), eq(true)); - - assertThat(checkRunDetailsArgumentCaptor.getValue()) - .usingRecursiveComparison() - .isEqualTo(CheckRunDetails.builder() - .withTitle("Quality Gate success") - .withName("Project Name Sonarqube Results") - .withExternalId("analysis-id") - .withPullRequestId(123) - .withStartTime(clock.instant().atZone(ZoneId.of("UTC"))) - .withEndTime(clock.instant().atZone(ZoneId.of("UTC"))) - .withDashboardUrl("dashboard-url") - .withSummary("report summary") - .withCommitId("commit-sha") - .withAnnotations(IntStream.range(0, 20).mapToObj(i -> Annotation.builder() - .withScmPath("path" + i) - .withLine(i) - .withMessage("issue message " + i) - .withSeverity(i % 5 < 1 ? CheckAnnotationLevel.NOTICE : i % 5 > 2 ? CheckAnnotationLevel.FAILURE : CheckAnnotationLevel.WARNING) - .build()).collect(Collectors.toList())) - .withCheckConclusionState(CheckConclusionState.SUCCESS) - .withProjectKey(analysisDetails.getAnalysisProjectKey()) - .build()); + verify(gitHub).getRepository("alm-repo"); + verify(repository).createCheckRun("Project Name Sonarqube Results", "commit-sha"); + + ArgumentCaptor outputCaptor = ArgumentCaptor.captor(); + + verify(checkRunBuilder).add(outputCaptor.capture()); + verify(checkRunBuilder).withExternalID("analysis-id"); + verify(checkRunBuilder).withStartedAt(Date.from(clock.instant())); + verify(checkRunBuilder).withCompletedAt(Date.from(clock.instant())); + verify(checkRunBuilder).withDetailsURL("dashboard-url"); + verify(checkRunBuilder).withStatus(GHCheckRun.Status.COMPLETED); + verify(checkRunBuilder).withConclusion(GHCheckRun.Conclusion.SUCCESS); + verify(checkRunBuilder).create(); + + verifyNoMoreInteractions(checkRunBuilder); + + GHCheckRunBuilder.Output output = new GHCheckRunBuilder.Output("Quality Gate success", "report summary"); + for (int i = 0; i < 20; i++) { + output.add(new GHCheckRunBuilder.Annotation( + "path" + i, + i, + i % 5 < 1 ? GHCheckRun.AnnotationLevel.NOTICE : i % 5 > 2 ? GHCheckRun.AnnotationLevel.FAILURE : GHCheckRun.AnnotationLevel.WARNING, + "issue message " + i)); + } + + assertThat(outputCaptor.getValue()).usingRecursiveComparison().isEqualTo(output); + + DecorationResult expectedResult = DecorationResult.builder().withPullRequestUrl("http://url.of/pull/request").build(); assertThat(decorationResult).usingRecursiveComparison().isEqualTo(expectedResult); + + verifyNoMoreInteractions(gitHub); + + verify(comment1, never()).delete(); + verify(comment2, never()).delete(); + verify(comment3).delete(); + verify(comment4, never()).delete(); + verify(comment5, never()).delete(); + verify(summaryComment, never()).delete(); + + verify(pullRequest).comment("report summary"); + verify(pullRequest).getHtmlUrl(); + verify(pullRequest).getComments(); + verifyNoMoreInteractions(pullRequest); + } + + + @Test + void shouldDecoratePullRequestWithCorrectAnalysisAndNoSummaryCommentWhenDisabled() throws IOException { + doReturn(false).when(projectAlmSettingDto).getSummaryCommentEnabled(); + GHRepository repository = mock(); + doReturn(repository).when(gitHub).getRepository(any()); + GHCheckRunBuilder checkRunBuilder = mock(InvocationOnMock::getMock); + doReturn(null).when(checkRunBuilder).create(); + doReturn(checkRunBuilder).when(repository).createCheckRun(any(), any()); + GHPullRequest pullRequest = mock(); + doReturn(pullRequest).when(repository).getPullRequest(anyInt()); + doReturn(new URL("http://url.of/pull/request")).when(pullRequest).getHtmlUrl(); + doReturn(QualityGate.Status.ERROR).when(analysisDetails).getQualityGateStatus(); + + DecorationResult decorationResult = testCase.decorateQualityGateStatus(analysisDetails, almSettingDto, projectAlmSettingDto); + + verify(gitHub).getRepository("alm-repo"); + verify(repository).createCheckRun("Project Name Sonarqube Results", "commit-sha"); + + ArgumentCaptor outputCaptor = ArgumentCaptor.captor(); + + verify(checkRunBuilder).add(outputCaptor.capture()); + verify(checkRunBuilder).withExternalID("analysis-id"); + verify(checkRunBuilder).withStartedAt(Date.from(clock.instant())); + verify(checkRunBuilder).withCompletedAt(Date.from(clock.instant())); + verify(checkRunBuilder).withDetailsURL("dashboard-url"); + verify(checkRunBuilder).withStatus(GHCheckRun.Status.COMPLETED); + verify(checkRunBuilder).withConclusion(GHCheckRun.Conclusion.FAILURE); + verify(checkRunBuilder).create(); + + verifyNoMoreInteractions(checkRunBuilder); + + GHCheckRunBuilder.Output output = new GHCheckRunBuilder.Output("Quality Gate failed", "report summary"); + for (int i = 0; i < 20; i++) { + output.add(new GHCheckRunBuilder.Annotation( + "path" + i, + i, + i % 5 < 1 ? GHCheckRun.AnnotationLevel.NOTICE : i % 5 > 2 ? GHCheckRun.AnnotationLevel.FAILURE : GHCheckRun.AnnotationLevel.WARNING, + "issue message " + i)); + } + + assertThat(outputCaptor.getValue()).usingRecursiveComparison().isEqualTo(output); + + DecorationResult expectedResult = DecorationResult.builder().withPullRequestUrl("http://url.of/pull/request").build(); + assertThat(decorationResult).usingRecursiveComparison().isEqualTo(expectedResult); + + verifyNoMoreInteractions(gitHub); + + verify(pullRequest).getHtmlUrl(); + verifyNoMoreInteractions(pullRequest); + } + + private static GHIssueComment createComment(String body, String userType, long userId, long commentId) throws IOException { + GHIssueComment comment = mock(); + when(comment.getBody()).thenReturn(body); + when(comment.getId()).thenReturn(commentId); + GHUser user = mock(); + when(user.getId()).thenReturn(userId); + when(user.getType()).thenReturn(userType); + when(comment.getUser()).thenReturn(user); + return comment; } } diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabMergeRequestDecoratorIntegrationTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabMergeRequestDecoratorIntegrationTest.java index 9ceb16be8..273698f10 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabMergeRequestDecoratorIntegrationTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabMergeRequestDecoratorIntegrationTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019-2022 Markus Heberling, Michael Clarke + * Copyright (C) 2019-2024 Markus Heberling, Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -18,7 +18,7 @@ */ package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.gitlab; -import com.github.mc1arke.sonarqube.plugin.almclient.LinkHeaderReader; +import com.github.mc1arke.sonarqube.plugin.almclient.gitlab.LinkHeaderReader; import com.github.mc1arke.sonarqube.plugin.almclient.gitlab.DefaultGitlabClientFactory; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.AnalysisDetails; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.PostAnalysisIssueVisitor; diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/validator/GithubValidatorTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/validator/GithubValidatorTest.java index e88f53b5d..734628fc0 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/validator/GithubValidatorTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/validator/GithubValidatorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Michael Clarke + * Copyright (C) 2021-2024 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -18,15 +18,6 @@ */ package com.github.mc1arke.sonarqube.plugin.server.pullrequest.validator; -import com.github.mc1arke.sonarqube.plugin.InvalidConfigurationException; -import com.github.mc1arke.sonarqube.plugin.almclient.github.GithubClient; -import com.github.mc1arke.sonarqube.plugin.almclient.github.GithubClientFactory; -import org.assertj.core.api.Condition; -import org.junit.jupiter.api.Test; -import org.sonar.db.alm.setting.ALM; -import org.sonar.db.alm.setting.AlmSettingDto; -import org.sonar.db.alm.setting.ProjectAlmSettingDto; - import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; @@ -34,20 +25,32 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import java.io.IOException; + +import org.assertj.core.api.Condition; +import org.junit.jupiter.api.Test; +import org.kohsuke.github.GitHub; +import org.sonar.db.alm.setting.ALM; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.alm.setting.ProjectAlmSettingDto; + +import com.github.mc1arke.sonarqube.plugin.InvalidConfigurationException; +import com.github.mc1arke.sonarqube.plugin.almclient.github.GithubClientFactory; + class GithubValidatorTest { - private final GithubClientFactory githubClientFactory = mock(GithubClientFactory.class); - private final ProjectAlmSettingDto projectAlmSettingDto = mock(ProjectAlmSettingDto.class); - private final AlmSettingDto almSettingDto = mock(AlmSettingDto.class); + private final GithubClientFactory githubClientFactory = mock(); + private final ProjectAlmSettingDto projectAlmSettingDto = mock(); + private final AlmSettingDto almSettingDto = mock(); @Test - void testCorrectAlmReturnedForValidator() { + void shouldReturnCorrectAlm() { GithubValidator underTest = new GithubValidator(githubClientFactory); assertThat(underTest.alm()).containsOnly(ALM.GITHUB); } @Test - void testInvalidConfigurationExceptionThrownIfCreateClientFails() { + void shouldThrowInvalidConfigurationExceptionIfCreateClientFails() throws IOException { GithubValidator underTest = new GithubValidator(githubClientFactory); when(githubClientFactory.createClient(any(), any())).thenThrow(new IllegalStateException("dummy")); assertThatThrownBy(() -> underTest.validate(projectAlmSettingDto, almSettingDto)) @@ -57,7 +60,7 @@ void testInvalidConfigurationExceptionThrownIfCreateClientFails() { } @Test - void testInvalidConfigurationExceptionRethrownIfCreateClientThrowsInvalidConfigurationException() { + void shouldRethrownConfigrationExceptionIfThrownFromCreateClient() throws IOException { GithubValidator underTest = new GithubValidator(githubClientFactory); when(githubClientFactory.createClient(any(), any())).thenThrow(new InvalidConfigurationException(InvalidConfigurationException.Scope.PROJECT, "dummy")); assertThatThrownBy(() -> underTest.validate(projectAlmSettingDto, almSettingDto)) @@ -67,13 +70,15 @@ void testInvalidConfigurationExceptionRethrownIfCreateClientThrowsInvalidConfigu } @Test - void testHappyPath() { + void shouldNotThrowExceptionIfRepoRetrieveSuccessfully() throws IOException { GithubValidator underTest = new GithubValidator(githubClientFactory); - GithubClient githubClient = mock(GithubClient.class); - when(githubClientFactory.createClient(any(), any())).thenReturn(githubClient); + GitHub gitHub = mock(); + when(githubClientFactory.createClient(any(), any())).thenReturn(gitHub); + when(projectAlmSettingDto.getAlmRepo()).thenReturn("reponame"); underTest.validate(projectAlmSettingDto, almSettingDto); - verify(githubClientFactory).createClient(any(), any()); + verify(githubClientFactory).createClient(almSettingDto, projectAlmSettingDto); + verify(gitHub).getRepository("reponame"); } }