diff --git a/build.gradle b/build.gradle index b32434f3a..709c0bd89 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Michael Clarke + * 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 @@ -40,7 +40,7 @@ repositories { } } -def sonarqubeVersion = '7.8' +def sonarqubeVersion = '8.1.0.31237' def sonarqubeLibDir = "${projectDir}/sonarqube-lib" def sonarLibraries = "${sonarqubeLibDir}/sonarqube-${sonarqubeVersion}/lib" 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 adcf2f739..22a40d550 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) 2019 Michael Clarke + * 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 @@ -20,21 +20,37 @@ import com.github.mc1arke.sonarqube.plugin.ce.CommunityBranchEditionProvider; import com.github.mc1arke.sonarqube.plugin.ce.CommunityReportAnalysisComponentProvider; -import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.PullRequestBuildStatusDecorator; -import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.server.BitbucketServerPullRequestDecorator; -import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.gitlab.GitlabServerPullRequestDecorator; import com.github.mc1arke.sonarqube.plugin.scanner.CommunityBranchConfigurationLoader; import com.github.mc1arke.sonarqube.plugin.scanner.CommunityBranchParamsValidator; import com.github.mc1arke.sonarqube.plugin.scanner.CommunityProjectBranchesLoader; import com.github.mc1arke.sonarqube.plugin.scanner.CommunityProjectPullRequestsLoader; +import com.github.mc1arke.sonarqube.plugin.scanner.ScannerPullRequestPropertySensor; import com.github.mc1arke.sonarqube.plugin.server.CommunityBranchFeatureExtension; import com.github.mc1arke.sonarqube.plugin.server.CommunityBranchSupportDelegate; +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.AlmSettingsWs; +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.CountBindingAction; +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.DeleteAction; +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.DeleteBindingAction; +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.GetBindingAction; +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.ListAction; +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.ListDefinitionsAction; +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.azure.CreateAzureAction; +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.azure.SetAzureBindingAction; +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.azure.UpdateAzureAction; +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.bitbucket.CreateBitBucketAction; +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.bitbucket.SetBitbucketBindingAction; +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.bitbucket.UpdateBitbucketAction; +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.github.CreateGithubAction; +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.github.SetGithubBindingAction; +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.github.UpdateGitHubAction; +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.gitlab.CreateGitlabAction; +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.gitlab.SetGitlabBindingAction; +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.gitlab.UpdateGitlabAction; 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; @@ -43,11 +59,6 @@ */ public class CommunityBranchPlugin implements Plugin, CoreExtension { - private static final String PULL_REQUEST_CATEGORY_LABEL = "Pull Request"; - private static final String GITHUB_INTEGRATION_SUBCATEGORY_LABEL = "Integration With Github"; - private static final String GENERAL = "General"; - private static final String BITBUCKET_INTEGRATION_SUBCATEGORY_LABEL = "Integration With Bitbucket"; - private static final String GITLAB_INTEGRATION_SUBCATEGORY_LABEL = "Integration With Gitlab"; @Override public String getName() { @@ -61,131 +72,46 @@ public void load(CoreExtension.Context context) { } else if (SonarQubeSide.SERVER == context.getRuntime().getSonarQubeSide()) { context.addExtensions(CommunityBranchFeatureExtension.class, CommunityBranchSupportDelegate.class, - /* org.sonar.db.purge.PurgeConfiguration uses the value for the this property if it's configured, so it only - needs to be specified here, but doesn't need any additional classes to perform the relevant purge/cleanup - */ + AlmSettingsWs.class, CountBindingAction.class, DeleteAction.class, + DeleteBindingAction.class, ListAction.class, ListDefinitionsAction.class, + GetBindingAction.class, + + CreateGithubAction.class, SetGithubBindingAction.class, UpdateGitHubAction.class, + + CreateAzureAction.class, SetAzureBindingAction.class, UpdateAzureAction.class, + + CreateBitBucketAction.class, SetBitbucketBindingAction.class, + UpdateBitbucketAction.class, + + CreateGitlabAction.class, SetGitlabBindingAction.class, UpdateGitlabAction.class, + + /* org.sonar.db.purge.PurgeConfiguration uses the value for the this property if it's configured, so it only + needs to be specified here, but doesn't need any additional classes to perform the relevant purge/cleanup + */ PropertyDefinition - .builder(PurgeConstants.DAYS_BEFORE_DELETING_INACTIVE_SHORT_LIVING_BRANCHES) + .builder(PurgeConstants.DAYS_BEFORE_DELETING_INACTIVE_BRANCHES_AND_PRS) .name("Number of days before purging inactive short living branches") .description( "Short living branches are permanently deleted when there are no analysis for the configured number of days.") - .category(CoreProperties.CATEGORY_GENERAL) - .subCategory(CoreProperties.SUBCATEGORY_DATABASE_CLEANER).defaultValue("30") - .type(PropertyType.INTEGER).build(), - - //the name and description shown on the UI are automatically loaded from core.properties so don't need to be specified here - PropertyDefinition.builder(CoreProperties.LONG_LIVED_BRANCHES_REGEX) - .onQualifiers(Qualifiers.PROJECT).category(CoreProperties.CATEGORY_GENERAL) - .subCategory(CoreProperties.SUBCATEGORY_BRANCHES) - .defaultValue(CommunityBranchConfigurationLoader.DEFAULT_BRANCH_REGEX).build() + .category(CoreProperties.CATEGORY_HOUSEKEEPING) + .subCategory(CoreProperties.SUBCATEGORY_GENERAL).defaultValue("30") + .type(PropertyType.INTEGER).build() ); - } - if (SonarQubeSide.COMPUTE_ENGINE == context.getRuntime().getSonarQubeSide() || - SonarQubeSide.SERVER == context.getRuntime().getSonarQubeSide()) { - context.addExtensions( - PropertyDefinition.builder("sonar.pullrequest.provider").category(PULL_REQUEST_CATEGORY_LABEL) - .subCategory("General").onQualifiers(Qualifiers.PROJECT).name("Provider") - .type(PropertyType.SINGLE_SELECT_LIST).options("Github", "BitbucketServer", "GitlabServer").build(), - - PropertyDefinition.builder("sonar.alm.github.app.privateKey.secured") - .category(PULL_REQUEST_CATEGORY_LABEL).subCategory(GITHUB_INTEGRATION_SUBCATEGORY_LABEL) - .onQualifiers(Qualifiers.APP).name("App Private Key").type(PropertyType.TEXT).build(), - - PropertyDefinition.builder("sonar.alm.github.app.name").category(PULL_REQUEST_CATEGORY_LABEL) - .subCategory(GITHUB_INTEGRATION_SUBCATEGORY_LABEL).onQualifiers(Qualifiers.APP) - .name("App Name").defaultValue("SonarQube Community Pull Request Analysis") - .type(PropertyType.STRING).build(), - - PropertyDefinition.builder("sonar.alm.github.app.id").category(PULL_REQUEST_CATEGORY_LABEL) - .subCategory(GITHUB_INTEGRATION_SUBCATEGORY_LABEL).onQualifiers(Qualifiers.APP) - .name("App ID").type(PropertyType.STRING).build(), - - PropertyDefinition.builder("sonar.pullrequest.github.repository") - .category(PULL_REQUEST_CATEGORY_LABEL).subCategory(GITHUB_INTEGRATION_SUBCATEGORY_LABEL) - .onlyOnQualifiers(Qualifiers.PROJECT).name("Repository identifier") - .description("Example: SonarSource/sonarqube").type(PropertyType.STRING).build(), - - PropertyDefinition.builder("sonar.pullrequest.github.endpoint") - .category(PULL_REQUEST_CATEGORY_LABEL).subCategory(GITHUB_INTEGRATION_SUBCATEGORY_LABEL) - .onQualifiers(Qualifiers.APP).name("The API URL for a GitHub instance").description( - "The API url for a GitHub instance. https://api.github.com/ for github.com, https://github.company.com/api/ when using GitHub Enterprise") - .type(PropertyType.STRING).defaultValue("https://api.github.com").build(), - - PropertyDefinition.builder(PullRequestBuildStatusDecorator.PULL_REQUEST_COMMENT_SUMMARY_ENABLED).category(PULL_REQUEST_CATEGORY_LABEL).subCategory(GENERAL) - .onQualifiers(Qualifiers.PROJECT).name("Enable summary comment").description("This enables the summary comment (if implemented).") - .type(PropertyType.BOOLEAN).defaultValue("true").build(), - - PropertyDefinition.builder(PullRequestBuildStatusDecorator.PULL_REQUEST_FILE_COMMENT_ENABLED).category(PULL_REQUEST_CATEGORY_LABEL).subCategory(GENERAL) - .onQualifiers(Qualifiers.PROJECT).name("Enable file comment").description("This enables commenting (if implemented).").type(PropertyType.BOOLEAN) - .defaultValue("true").build(), - - PropertyDefinition.builder(PullRequestBuildStatusDecorator.PULL_REQUEST_DELETE_COMMENTS_ENABLED).category(PULL_REQUEST_CATEGORY_LABEL).subCategory(GENERAL) - .onQualifiers(Qualifiers.PROJECT).name("Enable deleting comments").description("This cleans up the comments from previous runs (if implemented).") - .type(PropertyType.BOOLEAN).defaultValue("false").build(), - - PropertyDefinition.builder(BitbucketServerPullRequestDecorator.PULL_REQUEST_BITBUCKET_URL).category(PULL_REQUEST_CATEGORY_LABEL).subCategory(BITBUCKET_INTEGRATION_SUBCATEGORY_LABEL) - .onQualifiers(Qualifiers.PROJECT).name("URL for Bitbucket (Server or Cloud) instance").description("Example: http://bitbucket.local").type(PropertyType.STRING).build(), - - PropertyDefinition.builder(BitbucketServerPullRequestDecorator.PULL_REQUEST_BITBUCKET_TOKEN).category(PULL_REQUEST_CATEGORY_LABEL).subCategory(BITBUCKET_INTEGRATION_SUBCATEGORY_LABEL) - .onQualifiers(Qualifiers.PROJECT).name("The token for the user to comment to the PR on Bitbucket (Server or Cloud) instance") - .description("Token used for authentication and commenting to your Bitbucket instance").type(PropertyType.STRING).build(), - - PropertyDefinition.builder(BitbucketServerPullRequestDecorator.PULL_REQUEST_BITBUCKET_COMMENT_USER_SLUG) - .category(PULL_REQUEST_CATEGORY_LABEL).subCategory(BITBUCKET_INTEGRATION_SUBCATEGORY_LABEL).onQualifiers(Qualifiers.PROJECT).name("Comment User Slug") - .description("User slug for the comment user. Needed only for comment deletion.").type(PropertyType.STRING).build(), - - PropertyDefinition.builder(BitbucketServerPullRequestDecorator.PULL_REQUEST_BITBUCKET_REPOSITORY_SLUG) - .category(PULL_REQUEST_CATEGORY_LABEL).subCategory(BITBUCKET_INTEGRATION_SUBCATEGORY_LABEL).onlyOnQualifiers(Qualifiers.PROJECT).name("Repository Slug").description( - "Repository Slug see for example https://docs.atlassian.com/bitbucket-server/rest/latest/bitbucket-rest.html") - .type(PropertyType.STRING).build(), - - PropertyDefinition.builder(BitbucketServerPullRequestDecorator.PULL_REQUEST_BITBUCKET_USER_SLUG).category(PULL_REQUEST_CATEGORY_LABEL).subCategory(BITBUCKET_INTEGRATION_SUBCATEGORY_LABEL) - .onlyOnQualifiers(Qualifiers.PROJECT).name("User Slug").description("This is used for '/users' repos. Only set one User Slug or ProjectKey!") - .type(PropertyType.STRING).index(2).build(), - - PropertyDefinition.builder(BitbucketServerPullRequestDecorator.PULL_REQUEST_BITBUCKET_PROJECT_KEY).category(PULL_REQUEST_CATEGORY_LABEL).subCategory(BITBUCKET_INTEGRATION_SUBCATEGORY_LABEL) - .onlyOnQualifiers(Qualifiers.PROJECT).name("ProjectKey").description("This is used for '/projects' repos. Only set one User Slug or ProjectKey!") - .type(PropertyType.STRING).index(1).build(), - - PropertyDefinition.builder(GitlabServerPullRequestDecorator.PULLREQUEST_GITLAB_URL) - .category(PULL_REQUEST_CATEGORY_LABEL) - .subCategory(GITLAB_INTEGRATION_SUBCATEGORY_LABEL) - .onQualifiers(Qualifiers.PROJECT) - .name("URL for Gitlab (Server or Cloud) instance") - .description("Example: https://ci-server.local/gitlab") - .type(PropertyType.STRING) - .defaultValue("https://gitlab.com") - .build(), - - PropertyDefinition.builder(GitlabServerPullRequestDecorator.PULLREQUEST_GITLAB_TOKEN) - .category(PULL_REQUEST_CATEGORY_LABEL) - .subCategory(GITLAB_INTEGRATION_SUBCATEGORY_LABEL) - .onQualifiers(Qualifiers.PROJECT) - .name("The token for the user to comment to the PR on Gitlab (Server or Cloud) instance") - .description("Token used for authentication and commenting to your Gitlab instance") - .type(PropertyType.STRING) - .build(), - - PropertyDefinition.builder(GitlabServerPullRequestDecorator.PULLREQUEST_GITLAB_REPOSITORY_SLUG) - .category(PULL_REQUEST_CATEGORY_LABEL) - .subCategory(GITLAB_INTEGRATION_SUBCATEGORY_LABEL) - .onlyOnQualifiers(Qualifiers.PROJECT) - .name("Repository Slug for the Gitlab (Server or Cloud) instance") - .description("The repository slug can be either in the form of user/repo or it can be the Project ID") - .type(PropertyType.STRING) - .build() - ); } + } @Override public void define(Plugin.Context context) { + + if (SonarQubeSide.SCANNER == context.getRuntime().getSonarQubeSide()) { context.addExtensions(CommunityProjectBranchesLoader.class, CommunityProjectPullRequestsLoader.class, - CommunityBranchConfigurationLoader.class, CommunityBranchParamsValidator.class); + CommunityBranchConfigurationLoader.class, CommunityBranchParamsValidator.class, + ScannerPullRequestPropertySensor.class); } } } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/SonarqubeCompatibility.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/SonarqubeCompatibility.java deleted file mode 100644 index 1c13fb670..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/SonarqubeCompatibility.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.github.mc1arke.sonarqube.plugin; - -/** - * Provides a marker to indicate a class is implementing a feature to remain compatible with a specific version of Sonarqube. - * Classes should not normally implement this interface directly, but should implement an interface that extends from one - * of the sub-interfaces included in this interface. - * - * When creating the compatibility interfaces for individual features, mark methods as {@link Deprecated} if they're only - * there to provide compatibility with historic builds of SonarQube, otherwise put a comment on the method to make it clear - * it's a temporary marker to allow {@link Override} annotations and allow suporession of build warnings when building - * against older SonarQube versions. - * - * @author Michael Clarke - */ -public interface SonarqubeCompatibility { - - /** - * A marker for all features needed from SonarQube v7 that are no longer present in later v7 releases, or in v8, or - * features that have been specifically written to allow support for running a plugin against newer version when - * built against v7. - */ - interface Major7 extends SonarqubeCompatibility { - - /** - * A marker for all features needed from SonarQube v7.8 that are no longer present in v7.9. - */ - interface Minor8 extends Major7 { - - } - - /** - * A marker for all features needed from SonarQube v7.9 that are no longer present in v8.0 - */ - interface Minor9 extends Major7 { - - } - - } - - /** - * A marker for all features needed from SonarQube v8 that are no longer present in later v8 releases. - */ - interface Major8 extends SonarqubeCompatibility { - - /** - * A marker for all features needed from SonarQube v8.0 that are no longer present in v8.1. - */ - interface Minor0 extends Major8 { - - } - } -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityBranch.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityBranch.java index dbe0e34f5..dae22eaf6 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityBranch.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityBranch.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Michael Clarke + * 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 @@ -27,22 +27,22 @@ /** * @author Michael Clarke */ -public class CommunityBranch implements Branch, BranchCompatibility.BranchCompatibilityMajor7.BranchCompatibilityMinor9 { +public class CommunityBranch implements Branch { private final String name; private final BranchType branchType; private final boolean main; - private final String mergeBranchUuid; + private final String referenceBranchUuid; private final String pullRequestKey; private final String targetBranchName; - public CommunityBranch(String name, BranchType branchType, boolean main, String mergeBranchUuid, + public CommunityBranch(String name, BranchType branchType, boolean main, String referenceBranchUuid, String pullRequestKey, String targetBranchName) { super(); this.name = name; this.branchType = branchType; this.main = main; - this.mergeBranchUuid = mergeBranchUuid; + this.referenceBranchUuid = referenceBranchUuid; this.pullRequestKey = pullRequestKey; this.targetBranchName = targetBranchName; } @@ -63,13 +63,8 @@ public boolean isMain() { } @Override - public boolean isLegacyFeature() { - return false; - } - - @Override - public String getMergeBranchUuid() { - return mergeBranchUuid; + public String getReferenceBranchUuid() { + return referenceBranchUuid; } @Override diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityBranchLoaderDelegate.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityBranchLoaderDelegate.java index a1c972299..a37d5462a 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityBranchLoaderDelegate.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityBranchLoaderDelegate.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Michael Clarke + * 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 @@ -29,6 +29,7 @@ import org.sonar.scanner.protocol.output.ScannerReport; import org.sonar.server.project.Project; +import javax.annotation.Nonnull; import java.util.Optional; /** @@ -45,7 +46,7 @@ public CommunityBranchLoaderDelegate(DbClient dbClient, MutableAnalysisMetadataH } @Override - public void load(ScannerReport.Metadata metadata) { + public void load(@Nonnull ScannerReport.Metadata metadata) { Branch branch = load(metadata, metadataHolder.getProject(), dbClient); metadataHolder.setBranch(branch); @@ -67,7 +68,7 @@ private static Branch load(ScannerReport.Metadata metadata, Project project, DbC throw new IllegalStateException("Could not find main branch"); } } else { - String targetBranch = StringUtils.trimToNull(metadata.getMergeBranchName()); + String targetBranch = StringUtils.trimToNull(metadata.getReferenceBranchName()); ScannerReport.Metadata.BranchType branchType = metadata.getBranchType(); if (null == targetBranchName) { targetBranchName = targetBranch; @@ -75,9 +76,8 @@ private static Branch load(ScannerReport.Metadata metadata, Project project, DbC if (ScannerReport.Metadata.BranchType.PULL_REQUEST == branchType) { return createPullRequest(metadata, dbClient, branchName, projectUuid, targetBranch, targetBranchName); - } else if (ScannerReport.Metadata.BranchType.LONG == branchType || - ScannerReport.Metadata.BranchType.SHORT == branchType) { - return createBranch(dbClient, branchName, projectUuid, targetBranch, branchType, targetBranchName); + } else if (ScannerReport.Metadata.BranchType.BRANCH == branchType) { + return createBranch(dbClient, branchName, projectUuid, targetBranch, targetBranchName); } else { throw new IllegalStateException(String.format("Invalid branch type '%s'", branchType.name())); } @@ -100,7 +100,7 @@ private static Branch createPullRequest(ScannerReport.Metadata metadata, DbClien } private static Branch createBranch(DbClient dbClient, String branchName, String projectUuid, String targetBranch, - ScannerReport.Metadata.BranchType branchType, String targetBranchName) { + String targetBranchName) { String targetUuid; if (null == targetBranch) { targetUuid = projectUuid; @@ -113,8 +113,7 @@ private static Branch createBranch(DbClient dbClient, String branchName, String String.format("Could not find target branch '%s' in project", targetBranch)); } } - return new CommunityBranch(branchName, ScannerReport.Metadata.BranchType.LONG == branchType ? BranchType.LONG : - BranchType.SHORT, + return new CommunityBranch(branchName, BranchType.BRANCH, findBranchByKey(projectUuid, branchName, dbClient).map(BranchDto::isMain) .orElse(false), targetUuid, null, targetBranchName); } 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 104bf8b54..89632b696 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 @@ -20,6 +20,7 @@ import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.PostAnalysisIssueVisitor; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.PullRequestPostAnalysisTask; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.azuredevops.AzureDevOpsServerPullRequestDecorator; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.github.GithubPullRequestDecorator; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.github.v3.RestApplicationAuthenticationProvider; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.github.v4.GraphqlCheckRunProvider; @@ -40,7 +41,8 @@ public List getComponents() { return Arrays.asList(CommunityBranchLoaderDelegate.class, PullRequestPostAnalysisTask.class, PostAnalysisIssueVisitor.class, GithubPullRequestDecorator.class, GraphqlCheckRunProvider.class, RestApplicationAuthenticationProvider.class, - BitbucketServerPullRequestDecorator.class, GitlabServerPullRequestDecorator.class); + BitbucketServerPullRequestDecorator.class, GitlabServerPullRequestDecorator.class, + AzureDevOpsServerPullRequestDecorator.class); } } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/AnalysisDetails.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/AnalysisDetails.java index cdfbfa486..2919f60b3 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/AnalysisDetails.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/AnalysisDetails.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Michael Clarke + * 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 @@ -19,8 +19,6 @@ package com.github.mc1arke.sonarqube.plugin.ce.pullrequest; -import com.github.mc1arke.sonarqube.plugin.SonarqubeCompatibility; -import org.apache.commons.lang.StringUtils; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.markup.Document; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.markup.FormatterFactory; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.markup.Heading; @@ -30,10 +28,12 @@ import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.markup.Node; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.markup.Paragraph; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.markup.Text; +import org.apache.commons.lang.StringUtils; import org.sonar.api.ce.posttask.Analysis; import org.sonar.api.ce.posttask.Project; import org.sonar.api.ce.posttask.QualityGate; import org.sonar.api.ce.posttask.QualityGate.EvaluationStatus; +import org.sonar.api.ce.posttask.ScannerContext; import org.sonar.api.config.Configuration; import org.sonar.api.issue.Issue; import org.sonar.api.measures.CoreMetrics; @@ -47,8 +47,11 @@ import org.sonar.core.issue.DefaultIssue; import org.sonar.server.measure.Rating; +import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.net.URLEncoder; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.NumberFormat; @@ -83,10 +86,11 @@ public class AnalysisDetails { private final Analysis analysis; private final Project project; private final Configuration configuration; + private final ScannerContext scannerContext; AnalysisDetails(BranchDetails branchDetails, PostAnalysisIssueVisitor postAnalysisIssueVisitor, QualityGate qualityGate, MeasuresHolder measuresHolder, Analysis analysis, Project project, - Configuration configuration, String publicRootURL) { + Configuration configuration, String publicRootURL, ScannerContext scannerContext) { super(); this.publicRootURL = publicRootURL; this.branchDetails = branchDetails; @@ -96,6 +100,7 @@ public class AnalysisDetails { this.analysis = analysis; this.project = project; this.configuration = configuration; + this.scannerContext = scannerContext; } public String getBranchName() { @@ -110,6 +115,10 @@ public QualityGate.Status getQualityGateStatus() { return qualityGate.getStatus(); } + public Optional getScannerProperty(String propertyName) { + return Optional.ofNullable(scannerContext.getProperties().get(propertyName)); + } + public String createAnalysisSummary(FormatterFactory formatterFactory) { BigDecimal newCoverage = @@ -119,7 +128,7 @@ public String createAnalysisSummary(FormatterFactory formatterFactory) { .map(BigDecimal::new) .orElse(null); - double coverage = findMeasure(CoreMetrics.COVERAGE_KEY).map(MeasureWrapper::getDoubleValue).orElse(0D); + double coverage = findMeasure(CoreMetrics.COVERAGE_KEY).map(Measure::getDoubleValue).orElse(0D); BigDecimal newDuplications = findQualityGateCondition(CoreMetrics.NEW_DUPLICATED_LINES_DENSITY_KEY) .filter(condition -> condition.getStatus() != EvaluationStatus.NO_VALUE) @@ -128,7 +137,7 @@ public String createAnalysisSummary(FormatterFactory formatterFactory) { .orElse(null); double duplications = - findMeasure(CoreMetrics.DUPLICATED_LINES_DENSITY_KEY).map(MeasureWrapper::getDoubleValue).orElse(0D); + findMeasure(CoreMetrics.DUPLICATED_LINES_DENSITY_KEY).map(Measure::getDoubleValue).orElse(0D); NumberFormat decimalFormat = new DecimalFormat("#0.00", DecimalFormatSymbols.getInstance(Locale.ENGLISH)); @@ -184,8 +193,9 @@ public String createAnalysisSummary(FormatterFactory formatterFactory) { .map(i -> i + "% Duplicated Code") .orElse("No duplication information") + " (" + decimalFormat.format(duplications) + - "% Estimated after merge)"))), - new Link(publicRootURL + "/dashboard?id=" + URLEncoder.encode(project.getKey()) + "&pullRequest=" + branchDetails.getBranchName(), new Text("View in SonarQube"))); + "% Estimated after merge)"))), new Link( + publicRootURL + "/dashboard?id=" + encode(project.getKey(), StandardCharsets.UTF_8) + "&pullRequest=" + + branchDetails.getBranchName(), new Text("View in SonarQube"))); return formatterFactory.documentFormatter().format(document, formatterFactory); } @@ -206,9 +216,10 @@ public String createAnalysisIssueSummary(PostAnalysisIssueVisitor.ComponentIssue new Paragraph(new Text(String.format("**Type:** %s ", issue.type().name())), new Image(issue.type().name(), String.format("%s/checks/IssueType/%s.svg?sanitize=true", baseImageUrl, issue.type().name().toLowerCase()))), new Paragraph(new Text(String.format("**Severity:** %s ", issue.severity())), new Image(issue.severity(), String.format("%s/checks/Severity/%s.svg?sanitize=true", baseImageUrl, issue.severity().toLowerCase()))), new Paragraph(new Text(String.format("**Message:** %s", issue.getMessage()))), - effortNode, - resolutionNode, - new Link(publicRootURL + "/project/issues?id=" + URLEncoder.encode(project.getKey()) + "&pullRequest=" + branchDetails.getBranchName() + "&issues=" + issue.key() + "&open=" + issue.key(), new Text("View in SonarQube")) + effortNode, resolutionNode, new Link( + publicRootURL + "/project/issues?id=" + encode(project.getKey(), StandardCharsets.UTF_8) + + "&pullRequest=" + branchDetails.getBranchName() + "&issues=" + issue.key() + "&open=" + issue.key(), + new Text("View in SonarQube")) ); return formatterFactory.documentFormatter().format(document, formatterFactory); } @@ -276,11 +287,10 @@ private List findFailedConditions() { .collect(Collectors.toList()); } - private Optional findMeasure(String metricKey) { + private Optional findMeasure(String metricKey) { return measuresHolder.getMeasureRepository().getRawMeasure(measuresHolder.getTreeRootHolder().getRoot(), measuresHolder.getMetricRepository() - .getByKey(metricKey)) - .map(MeasureWrapper::new); + .getByKey(metricKey)); } public Optional findQualityGateCondition(String metricKey) { @@ -322,6 +332,14 @@ private static String format(QualityGate.Condition condition) { } } + private static String encode(String original, Charset charset) { + try { + return URLEncoder.encode(original, charset.name()); + } catch (UnsupportedEncodingException ex) { + throw new IllegalStateException("Could not encode input", ex); + } + } + public static class BranchDetails { private final String branchName; @@ -388,23 +406,4 @@ private String getImageName() { } } - private static class MeasureWrapper implements SonarqubeCompatibility.Major7.Minor9 { - - private final Measure measure; - - MeasureWrapper(Measure measure) { - super(); - this.measure = measure; - } - - Double getDoubleValue() { - try { - return (Double) Measure.class.getDeclaredMethod("getDoubleValue").invoke(measure); - } catch (ReflectiveOperationException ex) { - throw new IllegalStateException("Could not invoke getDoubleValue", ex); - } - } - - } - } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/PostProjectAnalysisTaskCompatibility.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/PostProjectAnalysisTaskCompatibility.java deleted file mode 100644 index 54398f4ac..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/PostProjectAnalysisTaskCompatibility.java +++ /dev/null @@ -1,53 +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.ce.pullrequest; - -import com.github.mc1arke.sonarqube.plugin.SonarqubeCompatibility; -import org.sonar.api.ce.posttask.PostProjectAnalysisTask; - -public interface PostProjectAnalysisTaskCompatibility extends SonarqubeCompatibility { - - interface PostProjectAnalysisTaskCompatibilityMajor8 - extends PostProjectAnalysisTaskCompatibility, SonarqubeCompatibility.Major8 { - - interface PostProjectAnalysisTaskCompatibilityMinor0 - extends PostProjectAnalysisTaskCompatibilityMajor8, SonarqubeCompatibility.Major8.Minor0 { - - String getDescription(); - - /** - * Setup and perform any post analysis task (e.g. pull request decoration) - * - * @param projectAnalysis the details of the task that was performed (quality gate, branches etc) - *

- * See {@link PostProjectAnalysisTask#finished(PostProjectAnalysisTask.ProjectAnalysis)} for implementation - * details - * @deprecated We can't introduce the new finished(Context context) method until we compile against - * SonarQube 8, since the Context class was only introduced at that point. Once our base is SonarQube 8 or - * above, we'll replace the implementation of this method with the non-deprecated equivalent. Marking this - * as Deprecated in the meantime so it's clear work will be needed here. - */ - @Deprecated - void finished(PostProjectAnalysisTask.ProjectAnalysis projectAnalysis); - - } - - } - -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/PullRequestBuildStatusDecorator.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/PullRequestBuildStatusDecorator.java index 3b11e1d46..ba4fb9771 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/PullRequestBuildStatusDecorator.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/PullRequestBuildStatusDecorator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Michael Clarke + * 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 @@ -18,15 +18,16 @@ */ package com.github.mc1arke.sonarqube.plugin.ce.pullrequest; -public interface PullRequestBuildStatusDecorator { - - String PULL_REQUEST_COMMENT_SUMMARY_ENABLED = "com.github.mc1arke.sonarqube.plugin.branch.pullrequest.comment.summary.enabled"; - - String PULL_REQUEST_FILE_COMMENT_ENABLED = "com.github.mc1arke.sonarqube.plugin.branch.pullrequest.file.comment.enabled"; +import org.sonar.db.alm.setting.ALM; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.alm.setting.ProjectAlmSettingDto; - String PULL_REQUEST_DELETE_COMMENTS_ENABLED = "com.github.mc1arke.sonarqube.plugin.branch.pullrequest.delete.comments.enabled"; +public interface PullRequestBuildStatusDecorator { String name(); - void decorateQualityGateStatus(AnalysisDetails analysisDetails); + void decorateQualityGateStatus(AnalysisDetails analysisDetails, AlmSettingDto almSettingDto, + ProjectAlmSettingDto projectAlmSettingDto); + + ALM alm(); } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/PullRequestPostAnalysisTask.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/PullRequestPostAnalysisTask.java index 6bd076d18..e39400e5d 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/PullRequestPostAnalysisTask.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/PullRequestPostAnalysisTask.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Michael Clarke + * 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 @@ -18,6 +18,7 @@ */ package com.github.mc1arke.sonarqube.plugin.ce.pullrequest; +import org.sonar.api.ce.posttask.ScannerContext; import org.sonar.api.ce.posttask.Analysis; import org.sonar.api.ce.posttask.Branch; import org.sonar.api.ce.posttask.PostProjectAnalysisTask; @@ -30,12 +31,20 @@ import org.sonar.ce.task.projectanalysis.component.TreeRootHolder; import org.sonar.ce.task.projectanalysis.measure.MeasureRepository; import org.sonar.ce.task.projectanalysis.metric.MetricRepository; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.alm.setting.ALM; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.alm.setting.ProjectAlmSettingDto; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; -public class PullRequestPostAnalysisTask implements PostProjectAnalysisTask, - PostProjectAnalysisTaskCompatibility.PostProjectAnalysisTaskCompatibilityMajor8.PostProjectAnalysisTaskCompatibilityMinor0 { +public class PullRequestPostAnalysisTask implements PostProjectAnalysisTask { private static final Logger LOGGER = Loggers.get(PullRequestPostAnalysisTask.class); @@ -46,13 +55,14 @@ public class PullRequestPostAnalysisTask implements PostProjectAnalysisTask, private final MetricRepository metricRepository; private final MeasureRepository measureRepository; private final TreeRootHolder treeRootHolder; + private final DbClient dbClient; public PullRequestPostAnalysisTask(Server server, ConfigurationRepository configurationRepository, List pullRequestDecorators, PostAnalysisIssueVisitor postAnalysisIssueVisitor, MetricRepository metricRepository, MeasureRepository measureRepository, - TreeRootHolder treeRootHolder) { + TreeRootHolder treeRootHolder, DbClient dbClient) { super(); this.server = server; this.configurationRepository = configurationRepository; @@ -61,6 +71,7 @@ public PullRequestPostAnalysisTask(Server server, this.metricRepository = metricRepository; this.measureRepository = measureRepository; this.treeRootHolder = treeRootHolder; + this.dbClient = dbClient; } @Override @@ -68,9 +79,9 @@ public String getDescription() { return "Pull Request Decoration"; } - @Deprecated @Override - public void finished(PostProjectAnalysisTask.ProjectAnalysis projectAnalysis) { + public void finished(Context context) { + ProjectAnalysis projectAnalysis = context.getProjectAnalysis(); LOGGER.debug("found " + pullRequestDecorators.size() + " pull request decorators"); Optional optionalPullRequest = projectAnalysis.getBranch().filter(branch -> Branch.Type.PULL_REQUEST == branch.getType()); @@ -87,8 +98,31 @@ public void finished(PostProjectAnalysisTask.ProjectAnalysis projectAnalysis) { Configuration configuration = configurationRepository.getConfiguration(); + ProjectAlmSettingDto projectAlmSettingDto; + Optional optionalAlmSettingDto; + try (DbSession dbSession = dbClient.openSession(false)) { + + Optional optionalProjectAlmSettingDto = + dbClient.projectAlmSettingDao().selectByProject(dbSession, projectAnalysis.getProject().getUuid()); + + if (!optionalProjectAlmSettingDto.isPresent()) { + LOGGER.debug("No ALM has been set on the current project"); + return; + } + + projectAlmSettingDto = optionalProjectAlmSettingDto.get(); + String almSettingUuid = projectAlmSettingDto.getAlmSettingUuid(); + optionalAlmSettingDto = dbClient.almSettingDao().selectByUuid(dbSession, almSettingUuid); + } + + if (!optionalAlmSettingDto.isPresent()) { + LOGGER.warn("The ALM configured for this project could not be found"); + return; + } + + AlmSettingDto almSettingDto = optionalAlmSettingDto.get(); Optional optionalPullRequestDecorator = - findCurrentPullRequestStatusDecorator(configuration, pullRequestDecorators); + findCurrentPullRequestStatusDecorator(almSettingDto, pullRequestDecorators); if (!optionalPullRequestDecorator.isPresent()) { LOGGER.info("No decorator found for this Pull Request"); @@ -118,37 +152,69 @@ public void finished(PostProjectAnalysisTask.ProjectAnalysis projectAnalysis) { String commitId = revision.get(); + ScannerContext scannerContext = projectAnalysis.getScannerContext().getProperties().size() > 0 + ? projectAnalysis.getScannerContext() + : getScannerContext(projectAnalysis.getCeTask().getId()); + AnalysisDetails analysisDetails = new AnalysisDetails(new AnalysisDetails.BranchDetails(optionalBranchName.get(), commitId), postAnalysisIssueVisitor, qualityGate, new AnalysisDetails.MeasuresHolder(metricRepository, measureRepository, treeRootHolder), analysis, - projectAnalysis.getProject(), configuration, server.getPublicRootUrl()); + projectAnalysis.getProject(), configuration, server.getPublicRootUrl(), + scannerContext); PullRequestBuildStatusDecorator pullRequestDecorator = optionalPullRequestDecorator.get(); LOGGER.info("using pull request decorator" + pullRequestDecorator.name()); - pullRequestDecorator.decorateQualityGateStatus(analysisDetails); + pullRequestDecorator.decorateQualityGateStatus(analysisDetails, almSettingDto, projectAlmSettingDto); } - private static Optional findCurrentPullRequestStatusDecorator( - Configuration configuration, List pullRequestDecorators) { + private ScannerContext getScannerContext(String ceTaskId) + { + ScannerContext scannerContext = new ScannerContext() { + private final Map props = new HashMap(); + @Override + public Map getProperties() { return this.props; } + }; + + try (DbSession dbSession = dbClient.openSession(false)) { + + try { + String scannerContextStr = dbClient.ceScannerContextDao().selectScannerContext(dbSession, ceTaskId).orElse(""); + + LOGGER.trace("GetScannerContext: ScannerContextStr =" + scannerContextStr); + + Pattern p = Pattern.compile("(\\w.+)=(.+)"); + Matcher m = p.matcher(scannerContextStr); + HashMap map = new HashMap(); - Optional optionalImplementationName = configuration.get("sonar.pullrequest.provider"); + while (m.find()) { + map.put(m.group(1), m.group(2)); + } + LOGGER.trace("GetScannerContext: map =" + map); - if (!optionalImplementationName.isPresent()) { - LOGGER.debug("'sonar.pullrequest.provider' property not set"); - return Optional.empty(); + scannerContext.getProperties().putAll(map); + + } catch (Exception ex) + { + LOGGER.error("GetScannerContext: " + ex); + } + return scannerContext; } + } - String implementationName = optionalImplementationName.get(); + + private static Optional findCurrentPullRequestStatusDecorator( + AlmSettingDto almSetting, List pullRequestDecorators) { + ALM alm = almSetting.getAlm(); for (PullRequestBuildStatusDecorator pullRequestDecorator : pullRequestDecorators) { - if (pullRequestDecorator.name().equals(implementationName)) { + if (alm == pullRequestDecorator.alm()) { return Optional.of(pullRequestDecorator); } } - LOGGER.warn("No decorator could be found matching " + implementationName); + LOGGER.warn("No decorator could be found matching " + alm); return Optional.empty(); } } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/AzureDevOpsServerPullRequestDecorator.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/AzureDevOpsServerPullRequestDecorator.java new file mode 100644 index 000000000..f6b3cab07 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/AzureDevOpsServerPullRequestDecorator.java @@ -0,0 +1,391 @@ +package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.azuredevops; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.AnalysisDetails; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.PostAnalysisIssueVisitor; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.PullRequestBuildStatusDecorator; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.azuredevops.model.Comment; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.azuredevops.model.CommentThread; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.azuredevops.model.GitStatusContext; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.azuredevops.model.GitPullRequestStatus; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.azuredevops.model.CommentThreadResponse; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.azuredevops.model.enums.CommentThreadStatus; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.azuredevops.model.mappers.GitStatusStateMapper; +import org.apache.commons.io.IOUtils; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPatch; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; +import org.sonar.api.issue.Issue; +import org.sonar.api.platform.Server; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.ce.task.projectanalysis.scm.ScmInfoRepository; +import org.sonar.db.alm.setting.ALM; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.alm.setting.ProjectAlmSettingDto; +import org.sonar.db.protobuf.DbIssues; + +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + + + +public class AzureDevOpsServerPullRequestDecorator implements PullRequestBuildStatusDecorator { + + private String authorizationHeader; + private static final String AZURE_API_VERSION = "?api-version=5.0-preview.1"; + private static final Logger LOGGER = Loggers.get(AzureDevOpsServerPullRequestDecorator.class); + + private String azureUrl = ""; + private String baseBranch = ""; + private String branch = ""; + private String pullRequestId = ""; + private String azureRepositoryName = ""; + private String azureProjectId = ""; + + /*private static final List OPEN_ISSUE_STATUSES = + Issue.STATUSES.stream().filter(s -> !Issue.STATUS_CLOSED.equals(s) && !Issue.STATUS_RESOLVED.equals(s)) + .collect(Collectors.toList());*/ + // SCANNER PROPERTY + public static final String PULLREQUEST_AZUREDEVOPS_INSTANCE_URL = "sonar.pullrequest.vsts.instanceUrl"; // sonar.pullrequest.vsts.instanceUrl=https://dev.azure.com/fabrikam/ + public static final String PULLREQUEST_AZUREDEVOPS_BASE_BRANCH = "sonar.pullrequest.base"; // sonar.pullrequest.base=master + public static final String PULLREQUEST_AZUREDEVOPS_BRANCH = "sonar.pullrequest.branch"; // sonar.pullrequest.branch=feature/some-feature + public static final String PULLREQUEST_AZUREDEVOPS_PULLREQUEST_ID = "sonar.pullrequest.key"; // sonar.pullrequest.key=222 + public static final String PULLREQUEST_AZUREDEVOPS_PROJECT_ID = "sonar.pullrequest.vsts.project"; // sonar.pullrequest.vsts.project=MyProject + public static final String PULLREQUEST_AZUREDEVOPS_REPOSITORY_NAME = "sonar.pullrequest.vsts.repository";//sonar.pullrequest.vsts.repository=MyReposytory + // SONAR URL MASK + public static final String SONAR_ISSUE_URL_MASK = "%s/project/issues?id=%s&issues=%s&open=%s&pullRequest=%s"; //http://localhost/project/issues?id=ProjId&issues=AXCuh6CgT2BpyN1RPU03&open=AXCuh6CgT2BpyN1RPU03&pullRequest=8513 + public static final String SONAR_RULE_URL_MASK = "%s/coding_rules?open=%s&rule_key=%s"; //http://localhost/coding_rules?open=csharpsquid%3AS1135&rule_key=csharpsquid%3AS1135 + + private final Server server; + private final ScmInfoRepository scmInfoRepository; + + public AzureDevOpsServerPullRequestDecorator(Server server, ScmInfoRepository scmInfoRepository) { + super(); + this.server = server; + this.scmInfoRepository = scmInfoRepository; + } + + @Override + public void decorateQualityGateStatus(AnalysisDetails analysisDetails, AlmSettingDto almSettingDto, ProjectAlmSettingDto projectAlmSettingDto) { + LOGGER.info("starting to analyze with " + analysisDetails.toString()); + + try { + azureUrl = analysisDetails.getScannerProperty(PULLREQUEST_AZUREDEVOPS_INSTANCE_URL).orElseThrow( + () -> new IllegalStateException(String.format( + "Could not decorate AzureDevOps pullRequest. '%s' has not been set in scanner properties", + PULLREQUEST_AZUREDEVOPS_INSTANCE_URL))); + baseBranch = analysisDetails.getScannerProperty(PULLREQUEST_AZUREDEVOPS_BASE_BRANCH).orElseThrow( + () -> new IllegalStateException(String.format( + "Could not decorate AzureDevOps pullRequest. '%s' has not been set in scanner properties", + PULLREQUEST_AZUREDEVOPS_BASE_BRANCH))); + branch = analysisDetails.getScannerProperty(PULLREQUEST_AZUREDEVOPS_BRANCH).orElseThrow( + () -> new IllegalStateException(String.format( + "Could not decorate AzureDevOps pullRequest. '%s' has not been set in scanner properties", + PULLREQUEST_AZUREDEVOPS_BRANCH))); + pullRequestId = analysisDetails.getScannerProperty(PULLREQUEST_AZUREDEVOPS_PULLREQUEST_ID).orElseThrow( + () -> new IllegalStateException(String.format( + "Could not decorate AzureDevOps pullRequest. '%s' has not been set in scanner properties", + PULLREQUEST_AZUREDEVOPS_PULLREQUEST_ID))); + azureRepositoryName = analysisDetails.getScannerProperty(PULLREQUEST_AZUREDEVOPS_REPOSITORY_NAME).orElseThrow( + () -> new IllegalStateException(String.format( + "Could not decorate AzureDevOps pullRequest. '%s' has not been set in scanner properties", + PULLREQUEST_AZUREDEVOPS_REPOSITORY_NAME))); + azureProjectId = analysisDetails.getScannerProperty(PULLREQUEST_AZUREDEVOPS_PROJECT_ID).orElseThrow( + () -> new IllegalStateException(String.format( + "Could not decorate AzureDevOps pullRequest. '%s' has not been set in scanner properties", + PULLREQUEST_AZUREDEVOPS_PROJECT_ID))); + if (almSettingDto.getPersonalAccessToken() == null) { + throw new IllegalStateException("Could not decorate AzureDevOps pullRequest. Access token has not been set"); + } + setAuthorizationHeader(almSettingDto.getPersonalAccessToken()); + + LOGGER.trace(String.format("azureUrl is: %s ", azureUrl)); + LOGGER.trace(String.format("baseBranch is: %s ", baseBranch)); + LOGGER.trace(String.format("branch is: %s ", branch)); + LOGGER.trace(String.format("pullRequestId is: %s ", pullRequestId)); + LOGGER.trace(String.format("azureProjectId is: %s ", azureProjectId)); + LOGGER.trace(String.format("azureRepositoryName is: %s ", azureRepositoryName)); + + sendPost( + getStatusApiUrl(), + getGitPullRequestStatus(analysisDetails), + "Status set successfully" + ); + + List openIssues = analysisDetails.getPostAnalysisIssueVisitor() + .getIssues(); + /*.stream() + .filter(i -> OPEN_ISSUE_STATUSES.contains(i.getIssue().getStatus())) + .collect(Collectors.toList());*/ + LOGGER.trace(String.format("Analyze issue count: %s ", openIssues.size())); + + ArrayList azureCommentThreads = new ArrayList(Arrays.asList(sendGet(getThreadApiUrl(), CommentThreadResponse.class).getValue())); + LOGGER.trace(String.format("Azure commentThreads count: %s ", azureCommentThreads.size())); + azureCommentThreads.removeIf(x -> x.getThreadContext() == null || x.isDeleted()); + LOGGER.trace(String.format("Azure commentThreads AFTER REMOVE count: %s ", azureCommentThreads.size())); + + for (PostAnalysisIssueVisitor.ComponentIssue issue : openIssues) { + String filePath = analysisDetails.getSCMPathForIssue(issue).orElse(null); + Integer line = issue.getIssue().getLine(); + if (filePath != null) { + try { + filePath = "/" + filePath; + LOGGER.trace(String.format("ISSUE: authorLogin: %s ", issue.getIssue().authorLogin())); + LOGGER.trace(String.format("ISSUE: key: %s ", issue.getIssue().key())); + LOGGER.trace(String.format("ISSUE: type: %s ", issue.getIssue().type().toString())); + LOGGER.trace(String.format("ISSUE: severity: %s ", issue.getIssue().severity())); + LOGGER.trace(String.format("ISSUE: componentKey: %s ", issue.getIssue().componentKey())); + LOGGER.trace(String.format("ISSUE: getLocations: %s ", Objects.requireNonNull(issue.getIssue().getLocations()).toString())); + LOGGER.trace(String.format("ISSUE: getRuleKey: %s ", issue.getIssue().getRuleKey())); + LOGGER.trace(String.format("COMPONENT: getDescription: %s ", issue.getComponent().getDescription())); + DbIssues.Locations locate = Objects.requireNonNull(issue.getIssue().getLocations()); + boolean isExitsThread = false; + for (CommentThread azureThread : azureCommentThreads) { + LOGGER.trace(String.format("azureFilePath: %s", azureThread.getThreadContext().getFilePath())); + LOGGER.trace(String.format("filePath: %s (%s)", filePath, azureThread.getThreadContext().getFilePath().equals(filePath))); + LOGGER.trace(String.format("azureLine: %d", azureThread.getThreadContext().getRightFileStart().getLine())); + LOGGER.trace(String.format("line: %d (%s)", line, azureThread.getThreadContext().getRightFileStart().getLine() == locate.getTextRange().getEndLine())); + + if (azureThread.getThreadContext().getFilePath().equals(filePath) + && azureThread.getComments() + .stream() + .filter(c -> c.getContent().contains(issue.getIssue().key())) + .count() > 0 ) { + + if(!issue.getIssue().getStatus().equals(Issue.STATUS_OPEN) + && azureThread.getStatus().equals(CommentThreadStatus.ACTIVE)) { + Comment comment = new Comment("Closed in SonarQube"); + LOGGER.info("Issue closed in Sonar. try close in Azure"); + sendPost( + azureThread.getLinks().getSelf().getHref() + "/comments" + AZURE_API_VERSION, + new ObjectMapper().writeValueAsString(comment), + "Comment added success" + ); + sendPatch( + azureThread.getLinks().getSelf().getHref() + AZURE_API_VERSION, + "{\"status\":\"closed\"}" + ); + } + isExitsThread = true; + break; + } + } + if (!issue.getIssue().getStatus().equals(Issue.STATUS_OPEN)) { + LOGGER.info(String.format("SKIPPED ISSUE: Issue status is %s", issue.getIssue().getStatus())); + continue; + } + + if (isExitsThread || !issue.getIssue().getStatus().equals(Issue.STATUS_OPEN)) { + LOGGER.info(String.format("SKIPPED ISSUE: %s" + + System.lineSeparator() + + "File: %s" + + System.lineSeparator() + + "Line: %d" + + System.lineSeparator() + + "Issue is already exist in azure", + issue.getIssue().getMessage(), + filePath, + line)); + continue; + } + + String message = String.format("%s: %s ([rule](%s))" + System.lineSeparator() + + System.lineSeparator() + + "[See in SonarQube](%s)", + issue.getIssue().type().name(), + issue.getIssue().getMessage(), + getRuleUrlWithRuleKey(issue.getIssue().getRuleKey().toString()), + getIssueUrl( + analysisDetails.getAnalysisProjectKey(), + issue.getIssue().key(), + pullRequestId) + ); + + CommentThread thread = new CommentThread(filePath, locate, message); + LOGGER.info(String.format("Creating thread: %s ", new ObjectMapper().writeValueAsString(thread))); + sendPost( + getThreadApiUrl(), + new ObjectMapper().writeValueAsString(thread), + "Thread created successfully" + ); + } catch (Exception e) { + LOGGER.error(e.toString()); + throw new IllegalStateException(e); // Uncomment to run unit tests + } + } + } + } catch (IOException ex) { + throw new IllegalStateException("Could not decorate Pull Request on AzureDevOps Server", ex); + } + } + + private void setAuthorizationHeader(String apiToken) { + String encodeBytes = Base64.getEncoder().encodeToString((":" + apiToken).getBytes()); + authorizationHeader = "Basic " + encodeBytes; + } + + public String getIssueUrl(String sonarProjectKey, String issueKey, String pullRequestId) throws IOException { + //ISSUE http://localhost/project/issues?id=ProjId&issues=AXCuh6CgT2BpyN1RPU03&open=AXCuh6CgT2BpyN1RPU03&pullRequest=8513 + return String.format(SONAR_ISSUE_URL_MASK, + server.getPublicRootUrl(), + URLEncoder.encode(sonarProjectKey, StandardCharsets.UTF_8.name()), + URLEncoder.encode(issueKey, StandardCharsets.UTF_8.name()), + URLEncoder.encode(issueKey, StandardCharsets.UTF_8.name()), + URLEncoder.encode(pullRequestId, StandardCharsets.UTF_8.name()) + ); + } + + public String getRuleUrlWithRuleKey(String ruleKey) throws IOException { + //RULE http://localhost/coding_rules?open=csharpsquid%3AS1135&rule_key=csharpsquid%3AS1135 + return String.format(SONAR_RULE_URL_MASK, + server.getPublicRootUrl(), + URLEncoder.encode(ruleKey, StandardCharsets.UTF_8.name()), + URLEncoder.encode(ruleKey, StandardCharsets.UTF_8.name()) + ); + } + + private String getStatusApiUrl() { + // POST https://{instance}/{collection}/{project}/_apis/git/repositories/{repositoryId}/pullRequests/{pullRequestId}/statuses?api-version=5.0-preview.1 + return azureUrl + azureProjectId + + "/_apis/git/repositories/" + + azureRepositoryName + + "/pullRequests/" + + pullRequestId + + "/statuses" + + AZURE_API_VERSION; + } + + private String getThreadApiUrl(){ + //POST https://{instance}/{collection}/{project}/_apis/git/repositories/{repositoryId}/pullRequests/{pullRequestId}/threads?api-version=5.0 + return azureUrl + azureProjectId + + "/_apis/git/repositories/" + + azureRepositoryName + + "/pullRequests/" + + pullRequestId + + "/threads" + + AZURE_API_VERSION; + } + + private String getGitPullRequestStatus(AnalysisDetails analysisDetails) throws IOException { + final String GIT_STATUS_CONTEXT_GENRE = "SonarQube"; + final String GIT_STATUS_CONTEXT_NAME = "QualityGate"; + final String GIT_STATUS_DESCRIPTION = "SonarQube Gate"; + + GitPullRequestStatus status = new GitPullRequestStatus( + GitStatusStateMapper.toGitStatusState(analysisDetails.getQualityGateStatus()), + GIT_STATUS_DESCRIPTION, + new GitStatusContext(GIT_STATUS_CONTEXT_GENRE, GIT_STATUS_CONTEXT_NAME), // "SonarQube/PullRequestDecoration", + String.format("%s/dashboard?id=%s&pullRequest=%s", server.getPublicRootUrl(), + URLEncoder.encode(analysisDetails.getAnalysisProjectKey(), + StandardCharsets.UTF_8.name()), + URLEncoder.encode(analysisDetails.getBranchName(), + StandardCharsets.UTF_8.name()) + ) + ); + return new ObjectMapper().writeValueAsString(status); + } + + private void sendPost(String apiUrl, String body, String successMessage) throws IOException { + LOGGER.trace(String.format("sendPost: URL: %s ", apiUrl)); + LOGGER.trace(String.format("sendPost: BODY: %s ", body)); + HttpPost httpPost = new HttpPost(apiUrl); + httpPost.addHeader("Accept", "application/json"); + httpPost.addHeader("Content-Type", "application/json; charset=utf-8"); + httpPost.addHeader("Authorization", authorizationHeader); + StringEntity entity = new StringEntity(body, StandardCharsets.UTF_8); + httpPost.setEntity(entity); + + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + HttpResponse httpResponse = httpClient.execute(httpPost); + if (null != httpResponse && httpResponse.getStatusLine().getStatusCode() != 200) { + LOGGER.error("sendPost: " + httpResponse.toString()); + LOGGER.error("sendPost: " + EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8)); + throw new IllegalStateException("An error was returned in the response from the Azure DevOps server. See the previous log messages for details"); + } else if (null != httpResponse) { + LOGGER.debug("sendPost: " + httpResponse.toString()); + LOGGER.info("sendPost: " + successMessage); + } + } + } + + private T sendGet(String apiUrl, Class type) throws IOException { + LOGGER.info(String.format("sendGet: URL: %s ", apiUrl)); + HttpGet httpGet = new HttpGet(apiUrl); + httpGet.addHeader("Accept", "application/json"); + httpGet.addHeader("Content-Type", "application/json; charset=utf-8"); + httpGet.addHeader("Authorization", authorizationHeader); + + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + HttpResponse httpResponse = httpClient.execute(httpGet); + if (null != httpResponse && httpResponse.getStatusLine().getStatusCode() != 200) { + LOGGER.error(httpResponse.toString()); + LOGGER.error(EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8)); + throw new IllegalStateException("An error was returned in the response from the Azure DevOps server. See the previous log messages for details"); + } else if (null != httpResponse) { + //LOGGER.info(httpResponse.toString()); + HttpEntity entity = httpResponse.getEntity(); + T obj = new ObjectMapper() + .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true) + .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, true) + .configure(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, true) + .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true) + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .readValue(IOUtils.toString(entity.getContent(), StandardCharsets.UTF_8), type); + + LOGGER.info(type + " received"); + + return obj; + } else { + throw new IOException("No response reveived"); + } + } + } + + private void sendPatch(String apiUrl, String body) throws IOException { + LOGGER.trace(String.format("sendPatch: URL: %s ", apiUrl)); + LOGGER.trace(String.format("sendPatch: BODY: %s ", body)); + HttpPatch httpPatch = new HttpPatch(apiUrl); + httpPatch.addHeader("Accept", "application/json"); + httpPatch.addHeader("Content-Type", "application/json; charset=utf-8"); + httpPatch.addHeader("Authorization", authorizationHeader); + StringEntity entity = new StringEntity(body, StandardCharsets.UTF_8); + httpPatch.setEntity(entity); + + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + HttpResponse httpResponse = httpClient.execute(httpPatch); + if (null != httpResponse && httpResponse.getStatusLine().getStatusCode() != 200) { + LOGGER.error("sendPatch: " + httpResponse.toString()); + LOGGER.error("sendPatch: " + EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8)); + throw new IllegalStateException("An error was returned in the response from the Azure DevOps server. See the previous log messages for details"); + } else if (null != httpResponse) { + LOGGER.debug("sendPatch: " + httpResponse.toString()); + LOGGER.info("sendPatch: Patch success!"); + } + } + } + + @Override + public String name() { + return "Azure"; + } + + @Override + public ALM alm() { + return ALM.AZURE_DEVOPS; + } +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/Comment.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/Comment.java new file mode 100644 index 000000000..a3cfa5fbb --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/Comment.java @@ -0,0 +1,113 @@ +package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.azuredevops.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.azuredevops.model.enums.CommentType; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + + +/** + * Represents a comment which is one of potentially many in a comment thread. + */ +public class Comment implements Serializable { + + private String content; + private CommentType commentType; + private Integer parentCommentId; + private int id; + private int threadId; + private IdentityRef author; + private Date publishedDate; + private Date lastUpdatedDate; + private Date lastContentUpdatedDate; + @JsonProperty("isDeleted") + private Boolean deleted; + private List usersLiked; + @JsonProperty("_links") + private ReferenceLinks links; + + public Comment() { + } + + public Comment(String content) { + this.content = content; + this.parentCommentId = 0; + this.commentType = CommentType.TEXT; + } + + /** + * The ID of the parent comment. This is used for replies. + */ + public Integer getParentCommentId() { + return this.parentCommentId; + } + /** + * The comment content. + */ + public String getContent() { + return this.content; + } + /** + * The comment type at the time of creation. + */ + public CommentType getCommentType() { + return this.commentType; + } + /** + * The comment ID. IDs start at 1 and are unique to a pull request. + */ + public int getId() { + return this.id; + } + /** + * The parent thread ID. Used for internal server purposes only -- note + * that this field is not exposed to the REST client. + */ + public int getThreadId() { + return this.threadId; + } + /** + * The author of the comment. + */ + public IdentityRef getAuthor() { + return this.author; + } + /** + * The date the comment was first published.; + */ + public Date getPublishedDate() { + return this.publishedDate; + } + /** + * The date the comment was last updated. + */ + public Date getLastUpdatedDate() { + return this.lastUpdatedDate; + } + /** + * The date the comment's content was last updated. + */ + public Date getLastContentUpdatedDate() { + return this.lastContentUpdatedDate; + } + /** + * Whether or not this comment was soft-deleted. + */ + public Boolean isDeleted() { + return this.deleted; + } + /** + * A list of the users who have liked this comment. + */ + public List getUsersLiked() { + return this.usersLiked; + } + /** + * Links to other related objects. + */ + public ReferenceLinks getLinks() { + return this.links; + } +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/CommentPosition.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/CommentPosition.java new file mode 100644 index 000000000..ad5a4ef93 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/CommentPosition.java @@ -0,0 +1,31 @@ +package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.azuredevops.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.io.Serializable; + +public class CommentPosition implements Serializable { + private final int line; + private final int offset; + + @JsonCreator + public CommentPosition(@JsonProperty("line") int line, @JsonProperty("offset") int offset){ + this.line = line; + this.offset = offset + 1; + } + /** + *The line number of a thread's position. Starts at 1. /// + */ + public int getLine() + { + return this.line; + }; + + /** + *The character offset of a thread's position inside of a line. Starts at 0. + */ + public int getOffset(){ + return this.offset; + }; +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/CommentThread.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/CommentThread.java new file mode 100644 index 000000000..5af791d17 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/CommentThread.java @@ -0,0 +1,103 @@ +package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.azuredevops.model; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.azuredevops.model.enums.CommentThreadStatus; +import org.sonar.db.protobuf.DbIssues; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.List; + +/** + * Represents a comment thread of a pull request. A thread contains meta data about the file + * it was left on along with one or more comments (an initial comment and the subsequent replies). + */ +public class CommentThread implements Serializable { + + private CommentThreadStatus status; + private List comments; + private CommentThreadContext threadContext; + private int id; + private Date publishedDate; + private Date lastUpdatedDate; + private HashMap identities; + @JsonProperty("isDeleted") + private Boolean deleted; + @JsonProperty("_links") + private ReferenceLinks links; + public CommentThread(){}; + + public CommentThread(String filePath, DbIssues.Locations locations, String message){ + comments = Arrays.asList( + new Comment(message) + ); + status = CommentThreadStatus.ACTIVE; //CommentThreadStatusMapper.toCommentThreadStatus(issue.status()); + threadContext = new CommentThreadContext( + filePath, + locations + ); + + } + /** + * A list of the comments. + */ + public List getComments(){ + return this.comments; + }; + /** + * The status of the comment thread. + */ + public CommentThreadStatus getStatus(){ + return this.status; + }; + /** + * Specify thread context such as position in left/right file. + */ + public CommentThreadContext getThreadContext() { + return this.threadContext; + }; + /** + * The comment thread id. + */ + public int getId() + { + return this.id; + }; + /** + * The time this thread was published. + */ + public Date getPublishedDate() + { + return this.publishedDate; + }; + /** + * The time this thread was last updated. + */ + public Date getLastUpdatedDate() + { + return this.lastUpdatedDate; + }; + /** + * Set of identities related to this thread + */ + public HashMap getIdentities() + { + return this.identities; + }; + /** + * Specify if the thread is deleted which happens when all comments are deleted. + */ + public Boolean isDeleted() + { + return this.deleted; + }; + /** + * Links to other related objects. + */ + public ReferenceLinks getLinks() + { + return this.links; + }; +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/CommentThreadContext.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/CommentThreadContext.java new file mode 100644 index 000000000..dd86626eb --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/CommentThreadContext.java @@ -0,0 +1,60 @@ +package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.azuredevops.model; + +import org.sonar.db.protobuf.DbIssues; + +import java.io.Serializable; + +public class CommentThreadContext implements Serializable { + + private String filePath; + private CommentPosition leftFileStart; + private CommentPosition leftFileEnd; + private CommentPosition rightFileStart; + private CommentPosition rightFileEnd; + + public CommentThreadContext() {}; + + public CommentThreadContext(String filePath, DbIssues.Locations locations){ + this.filePath = filePath; + this.leftFileEnd = null; + this.leftFileStart = null; + this.rightFileEnd = new CommentPosition( + locations.getTextRange().getEndLine(), + locations.getTextRange().getEndOffset() + ); + this.rightFileStart = new CommentPosition( + locations.getTextRange().getStartLine(), + locations.getTextRange().getStartOffset() + ); + } + /** + * File path relative to the root of the repository. It's up to the client to + */ + public String getFilePath(){ + return this.filePath; + }; + /** + * Position of first character of the thread's span in left file. /// + */ + public CommentPosition getLeftFileStart(){ + return this.leftFileStart; + }; + /** + * Position of last character of the thread's span in left file. /// + */ + public CommentPosition getLeftFileEnd(){ + return this.leftFileEnd; + }; + /** + * Position of first character of the thread's span in right file. /// + */ + public CommentPosition getRightFileStart(){ + return this.rightFileStart; + }; + /** + * Position of last character of the thread's span in right file. /// + */ + public CommentPosition getRightFileEnd(){ + return this.rightFileEnd; + }; +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/CommentThreadResponse.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/CommentThreadResponse.java new file mode 100644 index 000000000..0c90ccfaf --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/CommentThreadResponse.java @@ -0,0 +1,16 @@ +package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.azuredevops.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class CommentThreadResponse { + private final CommentThread[] value; + + @JsonCreator + public CommentThreadResponse(@JsonProperty("value") CommentThread[] value){ + this.value = value; + } + public CommentThread[] getValue(){ + return this.value; + } +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/GitPullRequestStatus.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/GitPullRequestStatus.java new file mode 100644 index 000000000..17562fb69 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/GitPullRequestStatus.java @@ -0,0 +1,54 @@ +package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.azuredevops.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.azuredevops.model.enums.GitStatusState; + +public class GitPullRequestStatus { + final GitStatusState state; + final String description; + final GitStatusContext context; + final String targetUrl; + + @JsonCreator + public GitPullRequestStatus( + @JsonProperty("state") GitStatusState state, + @JsonProperty("description") String description, + @JsonProperty("context") GitStatusContext context, + @JsonProperty("targetUrl") String targetUrl) + { + this.state = state; + this.description = description; + this.context = context; + this.targetUrl = targetUrl; + } + + /** + * State of the status. + */ + public GitStatusState getState() + { + return this.state; + } + /** + * Description of the status + */ + public String getDescription() + { + return this.description; + } + /** + * Status context that uniquely identifies the status. + */ + public GitStatusContext getContext() + { + return this.context; + } + /** + * TargetUrl of the status + */ + public String getTargetUrl() + { + return this.targetUrl; + } +} \ No newline at end of file diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/GitStatusContext.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/GitStatusContext.java new file mode 100644 index 000000000..7be1db5b6 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/GitStatusContext.java @@ -0,0 +1,31 @@ +package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.azuredevops.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Status context that uniquely identifies the status. + */ +public class GitStatusContext { + final String name; + final String genre; + @JsonCreator + public GitStatusContext(@JsonProperty("genre") String genre, @JsonProperty("name") String name){ + this.genre = genre; + this.name = name; + } + /** + * Genre of the status. Typically name of the service/tool generating the status, can be empty. + */ + public String getGenre() + { + return this.genre; + }; + /** + * Name identifier of the status, cannot be null or empty. + */ + public String getName() + { + return this.name; + }; +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/IdentityRef.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/IdentityRef.java new file mode 100644 index 000000000..541a35543 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/IdentityRef.java @@ -0,0 +1,117 @@ +package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.azuredevops.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.io.Serializable; + +public class IdentityRef implements Serializable { + private String displayName; + private String url; + @JsonProperty("_links") + private ReferenceLinks links; + private String id; + private String uniqueName; + private String directoryAlias; + private String profileUrl; + private String imageUrl; + @JsonProperty("isContainer") + private Boolean container; + @JsonProperty("isAadIdentity") + private Boolean aadIdentity; + @JsonProperty("isInactive") + private Boolean inactive; + @JsonProperty("isDeletedInOrigin") + private Boolean deletedInOrigin; + private String displayNameForXmlSerialization; + private String urlForXmlSerialization; + + public IdentityRef() + {}; + + public String getDisplayName() + { + return this.displayName; + } + public String getUrl() + { + return this.url; + } + public ReferenceLinks getLinks() + { + return this.links; + } + public String getId() + { + return this.id; + } + /** + * Deprecated - use Domain+PrincipalName instead + */ + public String getUniqueName() + { + return this.uniqueName; + } + /** + * Deprecated - Can be retrieved by querying the Graph user referenced in the + * "self" entry of the IdentityRef "_links" dictionary + */ + public String getDirectoryAlias() + { + return this.directoryAlias; + } + /** + * Deprecated - not in use in most preexisting implementations of ToIdentityRef + */ + public String getProfileUrl() + { + return this.profileUrl; + } + /** + * Deprecated - Available in the "avatar" entry of the IdentityRef "_links" dictionary + */ + public String getImageUrl() + { + return this.imageUrl; + } + /** + * Deprecated - Can be inferred from the subject type of the descriptor (Descriptor.IsGroupType) + */ + public Boolean isContainer() + { + return this.container; + } + + /** + * Deprecated - Can be inferred from the subject type of the descriptor (Descriptor.IsAadUserType/Descriptor.IsAadGroupType) + */ + public Boolean isAadIdentity() + { + return this.aadIdentity; + } + /** + * Deprecated - Can be retrieved by querying the Graph membership state referenced + * in the "membershipState" entry of the GraphUser "_links" dictionary + */ + public Boolean getInactive() + { + return this.inactive; + } + public Boolean getIsDeletedInOrigin() + { + return this.deletedInOrigin; + } + /** + * This property is for xml compat only. + */ + public String getdisplayNameForXmlSerialization() + { + return this.displayNameForXmlSerialization; + } + /** + * This property is for xml compat only. + */ + public String getUrlForXmlSerialization() + { + return this.urlForXmlSerialization; + } +} \ No newline at end of file diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/Link.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/Link.java new file mode 100644 index 000000000..091086c97 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/Link.java @@ -0,0 +1,17 @@ +package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.azuredevops.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class Link { + final String href; + @JsonCreator + public Link(@JsonProperty("href") String href) + { + this.href = href; + }; + public String getHref() + { + return this.href; + } +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/ReferenceLinks.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/ReferenceLinks.java new file mode 100644 index 000000000..23c2c72c5 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/ReferenceLinks.java @@ -0,0 +1,17 @@ +package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.azuredevops.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ReferenceLinks { + final Link self; + @JsonCreator + public ReferenceLinks(@JsonProperty("self") Link self) + { + this.self = self; + } + public Link getSelf() + { + return this.self; + } +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/enums/CommentThreadStatus.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/enums/CommentThreadStatus.java new file mode 100644 index 000000000..78e2b105e --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/enums/CommentThreadStatus.java @@ -0,0 +1,36 @@ +package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.azuredevops.model.enums; + +/** + * The status of a comment thread + */ +public enum CommentThreadStatus { + /** + * The thread status is unknown. + */ + UNKNOWN, + /** + * The thread status is active. + */ + ACTIVE, + /** + * The thread status is resolved as fixed. + */ + FIXED, + /** + * The thread status is resolved as won't fix. + */ + WONT_FIX, + /** + * The thread status is closed. + */ + CLOSED, + /** + * The thread status is resolved as by design. + */ + BY_DESIGN, + /** + * The thread status is pending. + */ + PENDING +} + diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/enums/CommentType.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/enums/CommentType.java new file mode 100644 index 000000000..50810d432 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/enums/CommentType.java @@ -0,0 +1,20 @@ +package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.azuredevops.model.enums; + +public enum CommentType { + /** + * The comment type is not known. + */ + UNKNOWN, + /** + * This is a regular user comment. + */ + TEXT, + /** + * The comment comes as a result of a code change. + */ + CODE_CHANGE, + /** + * The comment represents a system message. + */ + SYSTEM +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/enums/GitStatusState.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/enums/GitStatusState.java new file mode 100644 index 000000000..2df916e07 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/enums/GitStatusState.java @@ -0,0 +1,32 @@ +package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.azuredevops.model.enums; + +/** + * State of the status. + */ +public enum GitStatusState +{ + /** + * Status state not set. Default state. + */ + NOT_SET, + /** + * Status pending. + */ + PENDING, + /** + * Status succeeded. + */ + SUCCEEDED, + /** + * Status failed. + */ + FAILED, + /** + * Status with an error. + */ + ERROR, + /** + * Status is not applicable to the target object. + */ + NOT_APPLICABLE +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/mappers/CommentThreadStatusMapper.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/mappers/CommentThreadStatusMapper.java new file mode 100644 index 000000000..aa939f9ca --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/mappers/CommentThreadStatusMapper.java @@ -0,0 +1,31 @@ +package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.azuredevops.model.mappers; + +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.azuredevops.model.enums.CommentThreadStatus; + +import static org.sonar.api.issue.Issue.STATUS_OPEN; +import static org.sonar.api.issue.Issue.STATUS_CLOSED; + + +public final class CommentThreadStatusMapper { + + private CommentThreadStatusMapper() { + super(); + } + + public static CommentThreadStatus toCommentThreadStatus(String issueStatus) { + switch (issueStatus) { + case STATUS_OPEN: + return CommentThreadStatus.ACTIVE; + default: + return CommentThreadStatus.FIXED; + } + } + public static String toIssueStatus(CommentThreadStatus commentThreadStatus) { + switch (commentThreadStatus) { + case ACTIVE: + return STATUS_OPEN; + default: + return STATUS_CLOSED; + } + } +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/mappers/GitStatusStateMapper.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/mappers/GitStatusStateMapper.java new file mode 100644 index 000000000..1874967de --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/model/mappers/GitStatusStateMapper.java @@ -0,0 +1,22 @@ +package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.azuredevops.model.mappers; + +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.azuredevops.model.enums.GitStatusState; +import org.sonar.api.ce.posttask.QualityGate; + +public final class GitStatusStateMapper { + + private GitStatusStateMapper() { + super(); + } + + public static GitStatusState toGitStatusState(QualityGate.Status AnnalyzeStatus) { + switch (AnnalyzeStatus) { + case OK: + return GitStatusState.SUCCEEDED; + case ERROR: + return GitStatusState.ERROR; + default: + return GitStatusState.NOT_SET; + } + } +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/response/activity/Activity.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/response/activity/Activity.java deleted file mode 100644 index 05d586036..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/response/activity/Activity.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2019 Oliver Jedinger - * - * 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.ce.pullrequest.bitbucket.response.activity; - -import java.io.Serializable; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -public class Activity implements Serializable { - private final int id; - - private final User user; - - private final Comment comment; - - @JsonCreator - public Activity(@JsonProperty("id") final int id, @JsonProperty("user") final User user, @JsonProperty("comment") final Comment comment) { - this.id = id; - this.user = user; - this.comment = comment; - } - - public int getId() { - return id; - } - - public User getUser() { - return user; - } - - public Comment getComment() { - return comment; - } -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/response/activity/ActivityPage.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/response/activity/ActivityPage.java deleted file mode 100644 index 63eff791d..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/response/activity/ActivityPage.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2019 Oliver Jedinger - * - * 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.ce.pullrequest.bitbucket.response.activity; - -import java.io.Serializable; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -public class ActivityPage implements Serializable { - private final int size; - - private final int limit; - - private final boolean isLastPage; - - private final int start; - - private final int nextPageStart; - - private final Activity[] values; - - @JsonCreator - public ActivityPage(@JsonProperty("size") final int size, @JsonProperty("limit") final int limit, @JsonProperty("isLastPage") final boolean isLastPage, @JsonProperty("start") final int start, @JsonProperty("nextPageStart") final int nextPageStart, @JsonProperty("values") final Activity[] values) { - this.size = size; - this.limit = limit; - this.isLastPage = isLastPage; - this.start = start; - this.nextPageStart = nextPageStart; - this.values = values; - } - - public int getSize() { - return size; - } - - public int getLimit() { - return limit; - } - - public boolean isLastPage() { - return isLastPage; - } - - public int getStart() { - return start; - } - - public int getNextPageStart() { - return nextPageStart; - } - - public Activity[] getValues() { - return values; - } -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/response/activity/Comment.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/response/activity/Comment.java deleted file mode 100644 index 97ebdbed4..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/response/activity/Comment.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2019 Oliver Jedinger - * - * 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.ce.pullrequest.bitbucket.response.activity; - -import java.io.Serializable; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -public class Comment implements Serializable { - private final int id; - - private final int version; - - private final String text; - - private final User author; - - @JsonCreator - public Comment(@JsonProperty("id") final int id, @JsonProperty("version") final int version, @JsonProperty("text") final String text, @JsonProperty("author") final User author) { - this.id = id; - this.version = version; - this.text = text; - this.author = author; - } - - public int getId() { - return id; - } - - public int getVersion() { - return version; - } - - public String getText() { - return text; - } - - public User getAuthor() { - return author; - } -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/response/activity/User.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/response/activity/User.java deleted file mode 100644 index e0ef00055..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/response/activity/User.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2019 Oliver Jedinger - * - * 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.ce.pullrequest.bitbucket.response.activity; - -import java.io.Serializable; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -public class User implements Serializable { - private final String name; - - private final String slug; - - @JsonCreator - public User(@JsonProperty("name") final String name, @JsonProperty("slug") final String slug) { - this.name = name; - this.slug = slug; - } - - public String getName() { - return name; - } - - public String getSlug() { - return slug; - } -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/server/BitbucketServerPullRequestDecorator.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/server/BitbucketServerPullRequestDecorator.java index 1d5f3a4b6..87852d68a 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/server/BitbucketServerPullRequestDecorator.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/server/BitbucketServerPullRequestDecorator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Oliver Jedinger + * Copyright (C) 2020 Oliver Jedinger, Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -23,12 +23,9 @@ import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.AnalysisDetails; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.PostAnalysisIssueVisitor; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.PullRequestBuildStatusDecorator; -import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.response.activity.Activity; -import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.response.activity.ActivityPage; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.Anchor; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.FileComment; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.SummaryComment; -import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.response.activity.Comment; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.response.diff.Diff; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.response.diff.DiffLine; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.response.diff.DiffPage; @@ -39,7 +36,6 @@ import org.apache.commons.lang.StringUtils; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; @@ -47,16 +43,16 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; -import org.sonar.api.config.Configuration; import org.sonar.api.issue.Issue; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; -import org.sonar.ce.task.projectanalysis.component.ConfigurationRepository; import org.sonar.core.issue.DefaultIssue; +import org.sonar.db.alm.setting.ALM; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.alm.setting.ProjectAlmSettingDto; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -65,91 +61,44 @@ public class BitbucketServerPullRequestDecorator implements PullRequestBuildStatusDecorator { - public static final String PULL_REQUEST_BITBUCKET_URL = "com.github.mc1arke.sonarqube.plugin.branch.pullrequest.bitbucket.url"; - - public static final String PULL_REQUEST_BITBUCKET_TOKEN = "com.github.mc1arke.sonarqube.plugin.branch.pullrequest.bitbucket.token"; - - public static final String PULL_REQUEST_BITBUCKET_PROJECT_KEY = "com.github.mc1arke.sonarqube.plugin.branch.pullrequest.bitbucket.projectKey"; - - public static final String PULL_REQUEST_BITBUCKET_USER_SLUG = "com.github.mc1arke.sonarqube.plugin.branch.pullrequest.bitbucket.userSlug"; - - public static final String PULL_REQUEST_BITBUCKET_REPOSITORY_SLUG = "com.github.mc1arke.sonarqube.plugin.branch.pullrequest.bitbucket.repositorySlug"; - - public static final String PULL_REQUEST_BITBUCKET_COMMENT_USER_SLUG = "com.github.mc1arke.sonarqube.plugin.branch.pullrequest.bitbucket.comment.userSlug"; - private static final Logger LOGGER = Loggers.get(BitbucketServerPullRequestDecorator.class); private static final List OPEN_ISSUE_STATUSES = Issue.STATUSES.stream().filter(s -> !Issue.STATUS_CLOSED.equals(s) && !Issue.STATUS_RESOLVED.equals(s)) .collect(Collectors.toList()); private static final String REST_API = "/rest/api/1.0/"; - private static final String USER_PR_API = "users/%s/repos/%s/pull-requests/%s/"; private static final String PROJECT_PR_API = "projects/%s/repos/%s/pull-requests/%s/"; private static final String COMMENTS_API = "comments"; private static final String DIFF_API = "diff"; - private static final String ACTIVITIES = "activities?limit=%s"; private static final String FULL_PR_COMMENT_API = "%s" + REST_API + PROJECT_PR_API + COMMENTS_API; - private static final String FULL_PR_COMMENT_USER_API = "%s" + REST_API + USER_PR_API + COMMENTS_API; - - private static final String FULL_PR_ACTIVITIES_API = "%s" + REST_API + PROJECT_PR_API + ACTIVITIES; - private static final String FULL_PR_ACTIVITIES_USER_API = "%s" + REST_API + USER_PR_API + ACTIVITIES; - private static final String FULL_PR_DIFF_API = "%s" + REST_API + PROJECT_PR_API + DIFF_API; - private static final String FULL_PR_DIFF_USER_API = "%s" + REST_API + USER_PR_API + DIFF_API; - - - private final ConfigurationRepository configurationRepository; - - public BitbucketServerPullRequestDecorator(ConfigurationRepository configurationRepository) { - super(); - this.configurationRepository = configurationRepository; - } @Override - public void decorateQualityGateStatus(AnalysisDetails analysisDetails) { + public void decorateQualityGateStatus(AnalysisDetails analysisDetails, AlmSettingDto almSettingDto, + ProjectAlmSettingDto projectAlmSettingDto) { LOGGER.info("starting to analyze with " + analysisDetails.toString()); try { - Configuration configuration = configurationRepository.getConfiguration(); - final String hostURL = getMandatoryProperty(PULL_REQUEST_BITBUCKET_URL, configuration); - final String apiToken = getMandatoryProperty(PULL_REQUEST_BITBUCKET_TOKEN, configuration); - final String repositorySlug = getMandatoryProperty(PULL_REQUEST_BITBUCKET_REPOSITORY_SLUG, configuration); + final String hostURL = almSettingDto.getUrl(); + final String apiToken = almSettingDto.getPersonalAccessToken(); + final String repositorySlug = projectAlmSettingDto.getAlmSlug(); final String pullRequestId = analysisDetails.getBranchName(); - final String userSlug = configuration.get(PULL_REQUEST_BITBUCKET_USER_SLUG).orElse(StringUtils.EMPTY); - final String projectKey = configuration.get(PULL_REQUEST_BITBUCKET_PROJECT_KEY).orElse(StringUtils.EMPTY); - final String commentUserSlug = configuration.get(PULL_REQUEST_BITBUCKET_COMMENT_USER_SLUG).orElse(StringUtils.EMPTY); + final String projectKey = projectAlmSettingDto.getAlmRepo(); - final boolean summaryCommentEnabled = Boolean.parseBoolean(getMandatoryProperty(PULL_REQUEST_COMMENT_SUMMARY_ENABLED, configuration)); - final boolean fileCommentEnabled = Boolean.parseBoolean(getMandatoryProperty(PULL_REQUEST_FILE_COMMENT_ENABLED, configuration)); - final boolean deleteCommentsEnabled = Boolean.parseBoolean(getMandatoryProperty(PULL_REQUEST_DELETE_COMMENTS_ENABLED, configuration)); + String commentUrl = String.format(FULL_PR_COMMENT_API, hostURL, projectKey, repositorySlug, pullRequestId); + String diffUrl = String.format(FULL_PR_DIFF_API, hostURL, projectKey, repositorySlug, pullRequestId); - final String commentUrl; - final String activityUrl; - final String diffUrl; - if (StringUtils.isNotBlank(userSlug)) { - commentUrl = String.format(FULL_PR_COMMENT_USER_API, hostURL, userSlug, repositorySlug, pullRequestId); - diffUrl = String.format(FULL_PR_DIFF_USER_API, hostURL, userSlug, repositorySlug, pullRequestId); - activityUrl = String.format(FULL_PR_ACTIVITIES_USER_API, hostURL, userSlug, repositorySlug, pullRequestId, 250); - } else if (StringUtils.isNotBlank(projectKey)) { - commentUrl = String.format(FULL_PR_COMMENT_API, hostURL, projectKey, repositorySlug, pullRequestId); - diffUrl = String.format(FULL_PR_DIFF_API, hostURL, projectKey, repositorySlug, pullRequestId); - activityUrl = String.format(FULL_PR_ACTIVITIES_API, hostURL, projectKey, repositorySlug, pullRequestId, 250); - } else { - throw new IllegalStateException(String.format("Property userSlug (%s) for /user repo or projectKey (%s) for /projects repo needs to be set.", PULL_REQUEST_BITBUCKET_USER_SLUG, PULL_REQUEST_BITBUCKET_PROJECT_KEY)); - } LOGGER.debug(String.format("Comment URL is: %s ", commentUrl)); - LOGGER.debug(String.format("Activity URL is: %s ", activityUrl)); LOGGER.debug(String.format("Diff URL is: %s ", diffUrl)); Map headers = new HashMap<>(); headers.put("Authorization", String.format("Bearer %s", apiToken)); headers.put("Accept", "application/json"); - deleteComments(activityUrl, commentUrl, commentUserSlug, headers, deleteCommentsEnabled); String analysisSummary = analysisDetails.createAnalysisSummary(new MarkdownFormatterFactory()); StringEntity summaryCommentEntity = new StringEntity(new ObjectMapper().writeValueAsString(new SummaryComment(analysisSummary)), ContentType.APPLICATION_JSON); - postComment(commentUrl, headers, summaryCommentEntity, summaryCommentEnabled); + postComment(commentUrl, headers, summaryCommentEntity); DiffPage diffPage = getPage(diffUrl, headers, DiffPage.class); List componentIssues = analysisDetails.getPostAnalysisIssueVisitor().getIssues().stream().filter(i -> OPEN_ISSUE_STATUSES.contains(i.getIssue().status())).collect(Collectors.toList()); @@ -166,7 +115,7 @@ public void decorateQualityGateStatus(AnalysisDetails analysisDetails) { StringEntity fileCommentEntity = new StringEntity( new ObjectMapper().writeValueAsString(new FileComment(analysisIssueSummary, new Anchor(issueLine, issueType, issuePath, fileType))), ContentType.APPLICATION_JSON ); - postComment(commentUrl, headers, fileCommentEntity, fileCommentEnabled); + postComment(commentUrl, headers, fileCommentEntity); } } catch (IOException ex) { throw new IllegalStateException("Could not decorate Pull Request on Bitbucket Server", ex); @@ -206,66 +155,6 @@ private String getExtractIssueType(int issueLine, String issueType, List h return issueType; } - protected boolean deleteComments(String activityUrl, String commentUrl, String userSlug, Map headers, boolean deleteCommentsEnabled) { - if (!deleteCommentsEnabled) { - return false; - } - if (StringUtils.isEmpty(userSlug)) { - LOGGER.info("No comments deleted cause property comment.userSlug is not set."); - return false; - } - boolean commentsRemoved = false; - final ActivityPage activityPage = getPage(activityUrl, headers, ActivityPage.class); - if (activityPage != null) { - final List commentsToDelete = getCommentsToDelete(userSlug, activityPage); - LOGGER.debug(String.format("Deleting %s comments", commentsToDelete)); - for (Comment comment : commentsToDelete) { - try { - boolean commentDeleted = deleteComment(commentUrl, headers, comment); - if (commentDeleted) { - commentsRemoved = true; - } - } catch (IOException ex) { - LOGGER.error("Could not delete comment from Bitbucket Server", ex); - } - } - - } - return commentsRemoved; - } - - private boolean deleteComment(String commentUrl, Map headers, Comment comment) throws IOException { - boolean commentDeleted = false; - String deleteCommentUrl = commentUrl + "/%s?version=%s"; - HttpDelete httpDelete = new HttpDelete(String.format(deleteCommentUrl, comment.getId(), comment.getVersion())); - LOGGER.debug("delete " + comment.getId() + " " + comment.getVersion()); - for (Map.Entry entry : headers.entrySet()) { - httpDelete.addHeader(entry.getKey(), entry.getValue()); - } - try (CloseableHttpClient closeableHttpClient = HttpClients.createDefault()) { - HttpResponse deleteResponse = closeableHttpClient.execute(httpDelete); - if (null == deleteResponse) { - LOGGER.error("HttpResponse for deleting comment was null"); - } else if (deleteResponse.getStatusLine().getStatusCode() != 204) { - LOGGER.error(IOUtils.toString(deleteResponse.getEntity().getContent(), StandardCharsets.UTF_8.name())); - LOGGER.error("An error was returned in the response from the Bitbucket API. See the previous log messages for details"); - } else { - LOGGER.debug(String.format("Comment %s version %s deleted", comment.getId(), comment.getVersion())); - commentDeleted = true; - } - } - return commentDeleted; - } - - protected List getCommentsToDelete(String userSlug, ActivityPage activityPage) { - return Arrays.stream(activityPage.getValues()) - .filter(a -> a.getComment() != null) - .filter(a -> a.getComment().getAuthor() != null) - .filter(a -> userSlug.equals(a.getComment().getAuthor().getSlug())) - .map(Activity::getComment) - .collect(Collectors.toList()); - } - protected T getPage(String diffUrl, Map headers, Class type) { T page = null; try (CloseableHttpClient closeableHttpClient = HttpClients.createDefault()) { @@ -296,7 +185,8 @@ protected T getPage(String diffUrl, Map headers, Class ty return type.cast(page); } - protected boolean postComment(String commentUrl, Map headers, StringEntity requestEntity, boolean sendRequest) throws IOException { + protected boolean postComment(String commentUrl, Map headers, StringEntity requestEntity) + throws IOException { boolean commentPosted = false; HttpPost httpPost = new HttpPost(commentUrl); for (Map.Entry entry : headers.entrySet()) { @@ -304,31 +194,29 @@ protected boolean postComment(String commentUrl, Map headers, St } httpPost.setEntity(requestEntity); LOGGER.debug(EntityUtils.toString(requestEntity)); - if (sendRequest) { - try (CloseableHttpClient closeableHttpClient = HttpClients.createDefault()) { - HttpResponse httpResponse = closeableHttpClient.execute(httpPost); - if (null == httpResponse) { - LOGGER.error("HttpResponse for posting comment was null"); - } else if (httpResponse.getStatusLine().getStatusCode() != 201) { - HttpEntity entity = httpResponse.getEntity(); - LOGGER.error(IOUtils.toString(entity.getContent(), StandardCharsets.UTF_8.name())); - } else { - HttpEntity entity = httpResponse.getEntity(); - LOGGER.debug(IOUtils.toString(entity.getContent(), StandardCharsets.UTF_8.name())); - commentPosted = true; - } + try (CloseableHttpClient closeableHttpClient = HttpClients.createDefault()) { + HttpResponse httpResponse = closeableHttpClient.execute(httpPost); + if (null == httpResponse) { + LOGGER.error("HttpResponse for posting comment was null"); + } else if (httpResponse.getStatusLine().getStatusCode() != 201) { + HttpEntity entity = httpResponse.getEntity(); + LOGGER.error(IOUtils.toString(entity.getContent(), StandardCharsets.UTF_8.name())); + } else { + HttpEntity entity = httpResponse.getEntity(); + LOGGER.debug(IOUtils.toString(entity.getContent(), StandardCharsets.UTF_8.name())); + commentPosted = true; } } return commentPosted; } - private static String getMandatoryProperty(String propertyName, Configuration configuration) { - return configuration.get(propertyName).orElseThrow(() -> new IllegalStateException( - String.format("%s must be specified in the project configuration", propertyName))); - } - @Override public String name() { return "BitbucketServer"; } + + @Override + public ALM alm() { + return ALM.BITBUCKET; + } } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/CheckRunProvider.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/CheckRunProvider.java index 01ba8b2e6..572f9c4ec 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/CheckRunProvider.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/CheckRunProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Michael Clarke + * 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 @@ -19,10 +19,13 @@ package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.github; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.AnalysisDetails; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.alm.setting.ProjectAlmSettingDto; import java.io.IOException; import java.security.GeneralSecurityException; public interface CheckRunProvider { - void createCheckRun(AnalysisDetails analysisDetails) throws IOException, GeneralSecurityException; + void createCheckRun(AnalysisDetails analysisDetails, AlmSettingDto almSettingDto, + ProjectAlmSettingDto projectAlmSettingDto) throws IOException, GeneralSecurityException; } 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 cddaf058b..086559b65 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) 2019 Michael Clarke + * 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 @@ -20,6 +20,9 @@ import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.AnalysisDetails; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.PullRequestBuildStatusDecorator; +import org.sonar.db.alm.setting.ALM; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.alm.setting.ProjectAlmSettingDto; public class GithubPullRequestDecorator implements PullRequestBuildStatusDecorator { @@ -30,15 +33,21 @@ public GithubPullRequestDecorator(CheckRunProvider checkRunProvider) { } @Override - public void decorateQualityGateStatus(AnalysisDetails analysisDetails) { + public void decorateQualityGateStatus(AnalysisDetails analysisDetails, AlmSettingDto almSettingDto, + ProjectAlmSettingDto projectAlmSettingDto) { try { - checkRunProvider.createCheckRun(analysisDetails); + checkRunProvider.createCheckRun(analysisDetails, almSettingDto, projectAlmSettingDto); } catch (Exception ex) { throw new IllegalStateException("Could not decorate Pull Request on Github", ex); } } + @Override + public ALM alm() { + return ALM.GITHUB; + } + @Override public String name() { return "Github"; diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v4/GraphqlCheckRunProvider.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v4/GraphqlCheckRunProvider.java index 875227fa9..ac400d407 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v4/GraphqlCheckRunProvider.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v4/GraphqlCheckRunProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Michael Clarke + * 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 @@ -34,15 +34,13 @@ import io.aexp.nodes.graphql.InputObject; import io.aexp.nodes.graphql.internal.Error; import org.sonar.api.ce.posttask.QualityGate; -import org.sonar.api.config.Configuration; -import org.sonar.api.config.PropertyDefinition; -import org.sonar.api.config.PropertyDefinitions; import org.sonar.api.platform.Server; import org.sonar.api.rule.Severity; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; import org.sonar.ce.task.projectanalysis.component.Component; -import org.sonar.ce.task.projectanalysis.component.ConfigurationRepository; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.alm.setting.ProjectAlmSettingDto; import java.io.IOException; import java.net.URLEncoder; @@ -69,40 +67,30 @@ public class GraphqlCheckRunProvider implements CheckRunProvider { private final Clock clock; private final GithubApplicationAuthenticationProvider githubApplicationAuthenticationProvider; private final Server server; - private final ConfigurationRepository configurationRepository; - private final PropertyDefinitions propertyDefinitions; public GraphqlCheckRunProvider(Clock clock, GithubApplicationAuthenticationProvider githubApplicationAuthenticationProvider, - Server server, ConfigurationRepository configurationRepository, - PropertyDefinitions propertyDefinitions) { - this(new DefaultGraphqlProvider(), clock, githubApplicationAuthenticationProvider, server, - configurationRepository, propertyDefinitions); + Server server) { + this(new DefaultGraphqlProvider(), clock, githubApplicationAuthenticationProvider, server); } GraphqlCheckRunProvider(GraphqlProvider graphqlProvider, Clock clock, GithubApplicationAuthenticationProvider githubApplicationAuthenticationProvider, - Server server, ConfigurationRepository configurationRepository, - PropertyDefinitions propertyDefinitions) { + Server server) { super(); this.graphqlProvider = graphqlProvider; this.clock = clock; this.githubApplicationAuthenticationProvider = githubApplicationAuthenticationProvider; this.server = server; - this.configurationRepository = configurationRepository; - this.propertyDefinitions = propertyDefinitions; } @Override - public void createCheckRun(AnalysisDetails analysisDetails) throws IOException, GeneralSecurityException { - Configuration configuration = configurationRepository.getConfiguration(); - String apiUrl = getMandatoryProperty("sonar.pullrequest.github.endpoint", configuration, propertyDefinitions); - String apiPrivateKey = - getMandatoryProperty("sonar.alm.github.app.privateKey.secured", configuration, propertyDefinitions); - String projectPath = - getMandatoryProperty("sonar.pullrequest.github.repository", configuration, propertyDefinitions); - String appId = getMandatoryProperty("sonar.alm.github.app.id", configuration, propertyDefinitions); - String appName = getMandatoryProperty("sonar.alm.github.app.name", configuration, propertyDefinitions); + public void createCheckRun(AnalysisDetails analysisDetails, AlmSettingDto almSettingDto, + ProjectAlmSettingDto projectAlmSettingDto) throws IOException, GeneralSecurityException { + String apiUrl = almSettingDto.getUrl(); + String apiPrivateKey = almSettingDto.getPrivateKey(); + String projectPath = projectAlmSettingDto.getAlmRepo(); + String appId = almSettingDto.getAppId(); RepositoryAuthenticationToken repositoryAuthenticationToken = githubApplicationAuthenticationProvider.getInstallationToken(apiUrl, appId, apiPrivateKey, projectPath); @@ -138,7 +126,7 @@ public void createCheckRun(AnalysisDetails analysisDetails) throws IOException, InputObject repositoryInputObject = graphqlProvider.createInputObject().put("repositoryId", repositoryAuthenticationToken.getRepositoryId()) - .put("name", appName + " Results").put("headSha", analysisDetails.getCommitSha()) + .put("name", "Sonarqube Results").put("headSha", analysisDetails.getCommitSha()) .put("status", RequestableCheckStatusState.COMPLETED).put("conclusion", QualityGate.Status.OK == analysisDetails .getQualityGateStatus() ? @@ -196,12 +184,4 @@ private static CheckAnnotationLevel mapToGithubAnnotationLevel(String sonarqubeS } } - - private static String getMandatoryProperty(String propertyName, Configuration configuration, - PropertyDefinitions propertyDefinitions) { - return configuration.get(propertyName).orElseGet( - () -> Optional.ofNullable(propertyDefinitions.get(propertyName)).map(PropertyDefinition::defaultValue) - .map(v -> "".equals(v) ? null : v).orElseThrow(() -> new IllegalStateException( - String.format("%s must be specified in the project configuration", propertyName)))); - } } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabServerPullRequestDecorator.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabServerPullRequestDecorator.java index 3eedc6e41..29a51a6db 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabServerPullRequestDecorator.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabServerPullRequestDecorator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Markus Heberling + * Copyright (C) 2020 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,20 +18,6 @@ */ package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.gitlab; -import java.io.IOException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; @@ -53,67 +39,86 @@ import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; +import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import org.sonar.api.ce.posttask.QualityGate; -import org.sonar.api.config.Configuration; import org.sonar.api.issue.Issue; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.platform.Server; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; -import org.sonar.ce.task.projectanalysis.component.ConfigurationRepository; import org.sonar.ce.task.projectanalysis.scm.Changeset; import org.sonar.ce.task.projectanalysis.scm.ScmInfoRepository; +import org.sonar.db.alm.setting.ALM; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.alm.setting.ProjectAlmSettingDto; + +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; public class GitlabServerPullRequestDecorator implements PullRequestBuildStatusDecorator { + public static final String PULLREQUEST_GITLAB_INSTANCE_URL = + "sonar.pullrequest.gitlab.instanceUrl"; + public static final String PULLREQUEST_GITLAB_PROJECT_ID = + "sonar.pullrequest.gitlab.projectId"; + public static final String PULLREQUEST_GITLAB_PROJECT_URL = + "sonar.pullrequest.gitlab.projectUrl"; + public static final String PULLREQUEST_GITLAB_PIPELINE_ID = + "com.github.mc1arke.sonarqube.plugin.branch.pullrequest.gitlab.pipelineId"; + private static final Logger LOGGER = Loggers.get(GitlabServerPullRequestDecorator.class); private static final List OPEN_ISSUE_STATUSES = Issue.STATUSES.stream().filter(s -> !Issue.STATUS_CLOSED.equals(s) && !Issue.STATUS_RESOLVED.equals(s)) .collect(Collectors.toList()); - public static final String PULLREQUEST_GITLAB_URL = "com.github.mc1arke.sonarqube.plugin.branch.pullrequest.gitlab.url"; - public static final String PULLREQUEST_GITLAB_TOKEN = "com.github.mc1arke.sonarqube.plugin.branch.pullrequest.gitlab.token"; - public static final String PULLREQUEST_GITLAB_REPOSITORY_SLUG = "com.github.mc1arke.sonarqube.plugin.branch.pullrequest.gitlab.repositorySlug"; - - private final ConfigurationRepository configurationRepository; private final Server server; private final ScmInfoRepository scmInfoRepository; - public GitlabServerPullRequestDecorator(Server server, ConfigurationRepository configurationRepository, ScmInfoRepository scmInfoRepository) { + public GitlabServerPullRequestDecorator(Server server, ScmInfoRepository scmInfoRepository) { super(); - this.configurationRepository = configurationRepository; this.server = server; this.scmInfoRepository = scmInfoRepository; } @Override - public void decorateQualityGateStatus(AnalysisDetails analysis) { + public void decorateQualityGateStatus(AnalysisDetails analysis, AlmSettingDto almSettingDto, + ProjectAlmSettingDto projectAlmSettingDto) { LOGGER.info("starting to analyze with " + analysis.toString()); String revision = analysis.getCommitSha(); try { - Configuration configuration = configurationRepository.getConfiguration(); - final String hostURL = getMandatoryProperty(PULLREQUEST_GITLAB_URL, configuration); - final String apiToken = getMandatoryProperty(PULLREQUEST_GITLAB_TOKEN, configuration); - final String repositorySlug = getMandatoryProperty(PULLREQUEST_GITLAB_REPOSITORY_SLUG, configuration); + final String apiURL = analysis.getScannerProperty(PULLREQUEST_GITLAB_INSTANCE_URL).orElseThrow( + () -> new IllegalStateException(String.format( + "Could not decorate Gitlab merge request. '%s' has not been set in scanner properties", + PULLREQUEST_GITLAB_INSTANCE_URL))); + final String apiToken = almSettingDto.getPersonalAccessToken(); + final String projectId = analysis.getScannerProperty(PULLREQUEST_GITLAB_PROJECT_ID).orElseThrow( + () -> new IllegalStateException(String.format( + "Could not decorate Gitlab merge request. '%s' has not been set in scanner properties", + PULLREQUEST_GITLAB_PROJECT_ID))); final String pullRequestId = analysis.getBranchName(); - final boolean summaryCommentEnabled = Boolean.parseBoolean(getMandatoryProperty(PULL_REQUEST_COMMENT_SUMMARY_ENABLED, configuration)); - final boolean fileCommentEnabled = Boolean.parseBoolean(getMandatoryProperty(PULL_REQUEST_FILE_COMMENT_ENABLED, configuration)); - final boolean deleteCommentsEnabled = Boolean.parseBoolean(getMandatoryProperty(PULL_REQUEST_DELETE_COMMENTS_ENABLED, configuration)); - - final String restURL = String.format("%s/api/v4", hostURL); - final String userURL = restURL + "/user"; - final String projectURL = restURL + String.format("/projects/%s", URLEncoder.encode(repositorySlug, StandardCharsets.UTF_8.name())); + final String projectURL = apiURL + String.format("/projects/%s", URLEncoder.encode(projectId, StandardCharsets.UTF_8.name())); + final String userURL = apiURL + "/user"; final String statusUrl = projectURL + String.format("/statuses/%s", revision); final String mergeRequestURl = projectURL + String.format("/merge_requests/%s", pullRequestId); final String prCommitsURL = mergeRequestURl + "/commits"; final String mergeRequestDiscussionURL = mergeRequestURl + "/discussions"; - LOGGER.info(String.format("Status url is: %s ", statusUrl)); LOGGER.info(String.format("PR commits url is: %s ", prCommitsURL)); LOGGER.info(String.format("MR discussion url is: %s ", mergeRequestDiscussionURL)); @@ -126,11 +131,11 @@ public void decorateQualityGateStatus(AnalysisDetails analysis) { User user = getSingle(userURL, headers, User.class); LOGGER.info(String.format("Using user: %s ", user.getUsername())); - List commits = getPagedList(prCommitsURL, headers, deleteCommentsEnabled, new TypeReference>() { + List commits = getPagedList(prCommitsURL, headers, new TypeReference>() { }).stream().map(Commit::getId).collect(Collectors.toList()); MergeRequest mergeRequest = getSingle(mergeRequestURl, headers, MergeRequest.class); - List discussions = getPagedList(mergeRequestDiscussionURL, headers, deleteCommentsEnabled, new TypeReference>() { + List discussions = getPagedList(mergeRequestDiscussionURL, headers, new TypeReference>() { }); LOGGER.info(String.format("Discussions in MR: %s ", discussions @@ -145,24 +150,24 @@ public void decorateQualityGateStatus(AnalysisDetails analysis) { deleteCommitDiscussionNote(mergeRequestDiscussionURL + String.format("/%s/notes/%s", discussion.getId(), note.getId()), - headers, deleteCommentsEnabled); + headers); } } } - QualityGate.Condition newCoverageCondition = analysis.findQualityGateCondition(CoreMetrics.NEW_COVERAGE_KEY) - .orElseThrow(() -> new IllegalStateException("Could not find New Coverage Condition in analysis")); - String coverageValue = newCoverageCondition.getStatus().equals(QualityGate.EvaluationStatus.NO_VALUE) ? "0" : newCoverageCondition.getValue(); - + String coverageValue = analysis.findQualityGateCondition(CoreMetrics.NEW_COVERAGE_KEY) + .filter(condition -> condition.getStatus() != QualityGate.EvaluationStatus.NO_VALUE) + .map(QualityGate.Condition::getValue) + .orElse("0"); List openIssues = analysis.getPostAnalysisIssueVisitor().getIssues().stream().filter(i -> OPEN_ISSUE_STATUSES.contains(i.getIssue().getStatus())).collect(Collectors.toList()); String summaryComment = analysis.createAnalysisSummary(new MarkdownFormatterFactory()); List summaryContentParams = Collections.singletonList(new BasicNameValuePair("body", summaryComment)); - postStatus(statusUrl, headers, analysis, coverageValue, true); + postStatus(new StringBuilder(statusUrl), headers, analysis, coverageValue); - postCommitComment(mergeRequestDiscussionURL, headers, summaryContentParams, summaryCommentEnabled); + postCommitComment(mergeRequestDiscussionURL, headers, summaryContentParams); for (PostAnalysisIssueVisitor.ComponentIssue issue : openIssues) { String path = analysis.getSCMPathForIssue(issue).orElse(null); @@ -182,10 +187,11 @@ public void decorateQualityGateStatus(AnalysisDetails analysis) { new BasicNameValuePair("position[base_sha]", mergeRequest.getDiffRefs().getBaseSha()), new BasicNameValuePair("position[start_sha]", mergeRequest.getDiffRefs().getStartSha()), new BasicNameValuePair("position[head_sha]", mergeRequest.getDiffRefs().getHeadSha()), + new BasicNameValuePair("position[old_path]", path), new BasicNameValuePair("position[new_path]", path), new BasicNameValuePair("position[new_line]", String.valueOf(issue.getIssue().getLine())), new BasicNameValuePair("position[position_type]", "text")); - postCommitComment(mergeRequestDiscussionURL, headers, fileContentParams, fileCommentEnabled); + postCommitComment(mergeRequestDiscussionURL, headers, fileContentParams); } else { LOGGER.info(String.format("Skipping %s:%d since the commit does not belong to the MR", path, issue.getIssue().getLine())); } @@ -197,34 +203,43 @@ public void decorateQualityGateStatus(AnalysisDetails analysis) { } + @Override + public ALM alm() { + return ALM.GITLAB; + } + private X getSingle(String userURL, Map headers, Class type) throws IOException { HttpGet httpGet = new HttpGet(userURL); for (Map.Entry entry : headers.entrySet()) { httpGet.addHeader(entry.getKey(), entry.getValue()); } - HttpResponse httpResponse = HttpClients.createDefault().execute(httpGet); - if (null != httpResponse && httpResponse.getStatusLine().getStatusCode() != 200) { - LOGGER.error(httpResponse.toString()); - LOGGER.error(EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8)); - throw new IllegalStateException("An error was returned in the response from the Gitlab API. See the previous log messages for details"); - } else if (null != httpResponse) { - LOGGER.debug(httpResponse.toString()); - HttpEntity entity = httpResponse.getEntity(); - X user = new ObjectMapper() + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + HttpResponse httpResponse = httpClient.execute(httpGet); + if (null != httpResponse && httpResponse.getStatusLine().getStatusCode() != 200) { + LOGGER.error(httpResponse.toString()); + LOGGER.error(EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8)); + throw new IllegalStateException( + "An error was returned in the response from the Gitlab API. See the previous log messages for details"); + } else if (null != httpResponse) { + LOGGER.debug(httpResponse.toString()); + HttpEntity entity = httpResponse.getEntity(); + X user = new ObjectMapper() .configure(DeserializationFeature.ACCEPT_EMPTY_ARRAY_AS_NULL_OBJECT, true) .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true) .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) .readValue(IOUtils.toString(entity.getContent(), StandardCharsets.UTF_8), type); - LOGGER.info(type + " received"); + LOGGER.info(type + " received"); - return user; - } else { - throw new IOException("No response reveived"); + return user; + } else { + throw new IOException("No response reveived"); + } } } - private List getPagedList(String commitDiscussionURL, Map headers, boolean sendRequest, TypeReference> typeRef) throws IOException { + private List getPagedList(String commitDiscussionURL, Map headers, + TypeReference> typeRef) throws IOException { HttpGet httpGet = new HttpGet(commitDiscussionURL); for (Map.Entry entry : headers.entrySet()) { httpGet.addHeader(entry.getKey(), entry.getValue()); @@ -232,8 +247,8 @@ private List getPagedList(String commitDiscussionURL, Map List discussions = new ArrayList<>(); - if (sendRequest) { - HttpResponse httpResponse = HttpClients.createDefault().execute(httpGet); + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + HttpResponse httpResponse = httpClient.execute(httpGet); if (null != httpResponse && httpResponse.getStatusLine().getStatusCode() != 200) { LOGGER.error(httpResponse.toString()); LOGGER.error(EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8)); @@ -251,29 +266,29 @@ private List getPagedList(String commitDiscussionURL, Map Optional nextURL = getNextUrl(httpResponse); if (nextURL.isPresent()) { LOGGER.info("Getting next page"); - discussions.addAll(getPagedList(nextURL.get(), headers, sendRequest, typeRef)); + discussions.addAll(getPagedList(nextURL.get(), headers, typeRef)); } } } return discussions; } - private void deleteCommitDiscussionNote(String commitDiscussionNoteURL, Map headers, boolean sendRequest) throws IOException { + private void deleteCommitDiscussionNote(String commitDiscussionNoteURL, Map headers) throws IOException { //https://docs.gitlab.com/ee/api/discussions.html#delete-a-commit-thread-note HttpDelete httpDelete = new HttpDelete(commitDiscussionNoteURL); for (Map.Entry entry : headers.entrySet()) { httpDelete.addHeader(entry.getKey(), entry.getValue()); } - if (sendRequest) { + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { LOGGER.info("Deleting {} with headers {}", commitDiscussionNoteURL, headers); - HttpResponse httpResponse = HttpClients.createDefault().execute(httpDelete); + HttpResponse httpResponse = httpClient.execute(httpDelete); validateGitlabResponse(httpResponse, 204, "Commit discussions note deleted"); } } - private void postCommitComment(String commitCommentUrl, Map headers, List params, boolean sendRequest) throws IOException { + private void postCommitComment(String commitCommentUrl, Map headers, List params) throws IOException { //https://docs.gitlab.com/ee/api/commits.html#post-comment-to-commit HttpPost httpPost = new HttpPost(commitCommentUrl); for (Map.Entry entry : headers.entrySet()) { @@ -281,34 +296,36 @@ private void postCommitComment(String commitCommentUrl, Map head } httpPost.setEntity(new UrlEncodedFormEntity(params)); - if (sendRequest) { - LOGGER.info("Posting {} with headers {} to {}", params, headers, commitCommentUrl); + LOGGER.info("Posting {} with headers {} to {}", params, headers, commitCommentUrl); - HttpResponse httpResponse = HttpClients.createDefault().execute(httpPost); + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + HttpResponse httpResponse = httpClient.execute(httpPost); validateGitlabResponse(httpResponse, 201, "Comment posted"); } } - private void postStatus(String statusPostUrl, Map headers, AnalysisDetails analysis, String coverage, boolean sendRequest) throws IOException{ + private void postStatus(StringBuilder statusPostUrl, Map headers, AnalysisDetails analysis, + String coverage) throws IOException { //See https://docs.gitlab.com/ee/api/commits.html#post-the-build-status-to-a-commit - statusPostUrl += "?name=SonarQube"; + statusPostUrl.append("?name=SonarQube"); String status = (analysis.getQualityGateStatus() == QualityGate.Status.OK ? "success" : "failed"); - statusPostUrl += "&state=" + status; - statusPostUrl += "&target_url=" + URLEncoder.encode(String.format("%s/dashboard?id=%s&pullRequest=%s", server.getPublicRootUrl(), + statusPostUrl.append("&state=").append(status); + statusPostUrl.append("&target_url=").append(URLEncoder.encode(String.format("%s/dashboard?id=%s&pullRequest=%s", server.getPublicRootUrl(), URLEncoder.encode(analysis.getAnalysisProjectKey(), StandardCharsets.UTF_8.name()), URLEncoder .encode(analysis.getBranchName(), - StandardCharsets.UTF_8.name())), StandardCharsets.UTF_8.name()); - statusPostUrl+="&description="+URLEncoder.encode("SonarQube Status", StandardCharsets.UTF_8.name()); - statusPostUrl+="&coverage="+coverage; - //TODO: add pipelineId if we have it + StandardCharsets.UTF_8.name())), StandardCharsets.UTF_8.name())); + statusPostUrl.append("&description=").append(URLEncoder.encode("SonarQube Status", StandardCharsets.UTF_8.name())); + statusPostUrl.append("&coverage=").append(coverage); + analysis.getScannerProperty(PULLREQUEST_GITLAB_PIPELINE_ID).ifPresent(pipelineId -> statusPostUrl.append("&pipeline_id=").append(pipelineId)); - HttpPost httpPost = new HttpPost(statusPostUrl); + HttpPost httpPost = new HttpPost(statusPostUrl.toString()); for (Map.Entry entry : headers.entrySet()) { httpPost.addHeader(entry.getKey(), entry.getValue()); } - if (sendRequest) { - HttpResponse httpResponse = HttpClients.createDefault().execute(httpPost); + + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + HttpResponse httpResponse = httpClient.execute(httpPost); if (null != httpResponse && httpResponse.toString().contains("Cannot transition status")) { // Workaround for https://gitlab.com/gitlab-org/gitlab-ce/issues/25807 LOGGER.debug("Transition status is already {}", status); @@ -329,11 +346,6 @@ private void validateGitlabResponse(HttpResponse httpResponse, int expectedStatu } } - private static String getMandatoryProperty(String propertyName, Configuration configuration) { - return configuration.get(propertyName).orElseThrow(() -> new IllegalStateException( - String.format("%s must be specified in the project configuration", propertyName))); - } - private static Optional getNextUrl(HttpResponse httpResponse) { Header linkHeader = httpResponse.getFirstHeader("Link"); if (linkHeader != null) { diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/BranchConfigurationLoaderCompatibility.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/BranchConfigurationLoaderCompatibility.java deleted file mode 100644 index 451be0659..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/BranchConfigurationLoaderCompatibility.java +++ /dev/null @@ -1,49 +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.scanner; - -import com.github.mc1arke.sonarqube.plugin.SonarqubeCompatibility; -import org.sonar.scanner.scan.branch.BranchConfiguration; -import org.sonar.scanner.scan.branch.ProjectBranches; -import org.sonar.scanner.scan.branch.ProjectPullRequests; - -import java.util.Map; -import java.util.function.Supplier; - -public interface BranchConfigurationLoaderCompatibility extends SonarqubeCompatibility { - - interface BranchConfigurationLoaderCompatibilityMajor7 extends SonarqubeCompatibility.Major7, BranchConfigurationLoaderCompatibility { - - interface BranchConfigurationLoaderCompatibilityMinor8 extends BranchConfigurationLoaderCompatibilityMajor7, SonarqubeCompatibility.Major7.Minor8 { - - BranchConfiguration load(Map localSettings, Supplier> supplier, - ProjectBranches projectBranches, ProjectPullRequests projectPullRequests); - - } - - interface BranchConfigurationLoaderCompatibilityMinor9 extends BranchParamsValidatorCompatibility.BranchParamsValidatorCompatibilityMajor7, SonarqubeCompatibility.Major7.Minor9 { - - BranchConfiguration load(Map localSettings, ProjectBranches projectBranches, - ProjectPullRequests pullRequests); - } - - } - - -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/BranchParamsValidatorCompatibility.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/BranchParamsValidatorCompatibility.java deleted file mode 100644 index 453700f23..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/BranchParamsValidatorCompatibility.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.scanner; - -import com.github.mc1arke.sonarqube.plugin.SonarqubeCompatibility; - -import java.util.List; - -public interface BranchParamsValidatorCompatibility extends SonarqubeCompatibility { - - interface BranchParamsValidatorCompatibilityMajor7 extends BranchParamsValidatorCompatibility, SonarqubeCompatibility.Major7 { - - interface BranchParamsValidatorCompatibilityMinor9 extends BranchParamsValidatorCompatibilityMajor7, SonarqubeCompatibility.Major7.Minor9 { - - void validate(List validationMessages, String deprecatedBranchName); - - } - - } - - interface BranchParamsValidatorCompatibilityMajor8 extends SonarqubeCompatibility.Major8, BranchParamsValidatorCompatibility { - - interface BranchParamsValidatorCompatibilityMinor0 extends BranchParamsValidatorCompatibilityMajor8, SonarqubeCompatibility.Major8.Minor0 { - - void validate(List validationMessages); - } - } -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityBranchConfiguration.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityBranchConfiguration.java index 337f8a0b1..1d890292e 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityBranchConfiguration.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityBranchConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Michael Clarke + * 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 @@ -28,16 +28,16 @@ public class CommunityBranchConfiguration implements BranchConfiguration { private final String branchName; private final BranchType branchType; - private final String longLivingSonarReferenceBranch; + private final String referenceBranchName; private final String targetScmBranch; private final String pullRequestKey; - /*package*/ CommunityBranchConfiguration(String branchName, BranchType branchType, - String longLivingSonarReferenceBranch, String targetScmBranch, + /*package*/ CommunityBranchConfiguration(String branchName, BranchType branchType, String referenceBranchName, + String targetScmBranch, String pullRequestKey) { this.branchName = branchName; this.branchType = branchType; - this.longLivingSonarReferenceBranch = longLivingSonarReferenceBranch; + this.referenceBranchName = referenceBranchName; this.targetScmBranch = targetScmBranch; this.pullRequestKey = pullRequestKey; } @@ -53,8 +53,8 @@ public String branchName() { } @Override - public String longLivingSonarReferenceBranch() { - return longLivingSonarReferenceBranch; + public String referenceBranchName() { + return referenceBranchName; } @Override diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityBranchConfigurationLoader.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityBranchConfigurationLoader.java index d43d168be..10cca9e61 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityBranchConfigurationLoader.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityBranchConfigurationLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Michael Clarke + * 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 @@ -19,8 +19,8 @@ package com.github.mc1arke.sonarqube.plugin.scanner; import org.apache.commons.lang.StringUtils; -import org.sonar.api.CoreProperties; import org.sonar.api.utils.MessageException; +import org.sonar.api.utils.System2; import org.sonar.core.config.ScannerProperties; import org.sonar.scanner.scan.branch.BranchConfiguration; import org.sonar.scanner.scan.branch.BranchConfigurationLoader; @@ -31,18 +31,17 @@ import org.sonar.scanner.scan.branch.ProjectPullRequests; import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Optional; import java.util.Set; -import java.util.function.Supplier; /** * @author Michael Clarke */ -public class CommunityBranchConfigurationLoader implements BranchConfigurationLoader, BranchConfigurationLoaderCompatibility.BranchConfigurationLoaderCompatibilityMajor7.BranchConfigurationLoaderCompatibilityMinor8, BranchConfigurationLoaderCompatibility.BranchConfigurationLoaderCompatibilityMajor7.BranchConfigurationLoaderCompatibilityMinor9 { - - public static final String DEFAULT_BRANCH_REGEX = "(branch|release).*"; - +public class CommunityBranchConfigurationLoader implements BranchConfigurationLoader { private static final Set BRANCH_ANALYSIS_PARAMETERS = new HashSet<>(Arrays.asList(ScannerProperties.BRANCH_NAME, ScannerProperties.BRANCH_TARGET)); @@ -50,10 +49,18 @@ public class CommunityBranchConfigurationLoader implements BranchConfigurationLo private static final Set PULL_REQUEST_ANALYSIS_PARAMETERS = new HashSet<>( Arrays.asList(ScannerProperties.PULL_REQUEST_BRANCH, ScannerProperties.PULL_REQUEST_KEY, ScannerProperties.PULL_REQUEST_BASE)); + private final System2 system2; + + public CommunityBranchConfigurationLoader(System2 system2) { + this.system2 = system2; + } + @Override - public BranchConfiguration load(Map localSettings, Supplier> supplier, - ProjectBranches projectBranches, ProjectPullRequests projectPullRequests) { + public BranchConfiguration load(Map localSettings, ProjectBranches projectBranches, + ProjectPullRequests pullRequests) { + localSettings = autoConfigure(localSettings); + if (projectBranches.isEmpty()) { if (isTargetingDefaultBranch(localSettings)) { return new DefaultBranchConfiguration(); @@ -67,7 +74,6 @@ public BranchConfiguration load(Map localSettings, Supplier localSettings, Supplier localSettings, ProjectBranches projectBranches, - ProjectPullRequests pullRequests) { - if (projectBranches.isEmpty()) { - if (isTargetingDefaultBranch(localSettings)) { - return new DefaultBranchConfiguration(); + private Map autoConfigure(Map localSettings) { + Map mutableLocalSettings=new HashMap<>(localSettings); + if (Boolean.parseBoolean(system2.envVariable("GITLAB_CI"))) { + //GitLab CI auto configuration + if (system2.envVariable("CI_MERGE_REQUEST_IID") != null) { + // we are inside a merge request + Optional.ofNullable(system2.envVariable("CI_MERGE_REQUEST_IID")).ifPresent( + v -> mutableLocalSettings.putIfAbsent(ScannerProperties.PULL_REQUEST_KEY, v)); + Optional.ofNullable(system2.envVariable("CI_MERGE_REQUEST_SOURCE_BRANCH_NAME")).ifPresent( + v -> mutableLocalSettings.putIfAbsent(ScannerProperties.PULL_REQUEST_BRANCH, v)); + Optional.ofNullable(system2.envVariable("CI_MERGE_REQUEST_TARGET_BRANCH_NAME")).ifPresent( + v -> mutableLocalSettings.putIfAbsent(ScannerProperties.PULL_REQUEST_BASE, v)); } else { - // it would be nice to identify the 'primary' branch directly, but different projects work differently: using any of master, develop, main etc as primary - // A project/global configuration entry could be used to drive this in the future, but the current documented SonarQube parameters need followed for now - throw MessageException - .of("No branches currently exist in this project. Please scan the main branch without passing any branch parameters."); + // branch or tag + Optional.ofNullable(system2.envVariable("CI_COMMIT_REF_NAME")).ifPresent( + v -> mutableLocalSettings.putIfAbsent(ScannerProperties.BRANCH_NAME, v)); } } - if (BRANCH_ANALYSIS_PARAMETERS.stream().anyMatch(localSettings::containsKey)) { - return createBranchConfiguration(localSettings.get(ScannerProperties.BRANCH_NAME), - localSettings.get(ScannerProperties.BRANCH_TARGET), - localSettings.get(CoreProperties.LONG_LIVED_BRANCHES_REGEX), - projectBranches); - } else if (PULL_REQUEST_ANALYSIS_PARAMETERS.stream().anyMatch(localSettings::containsKey)) { - return createPullRequestConfiguration(localSettings.get(ScannerProperties.PULL_REQUEST_KEY), - localSettings.get(ScannerProperties.PULL_REQUEST_BRANCH), - localSettings.get(ScannerProperties.PULL_REQUEST_BASE), - projectBranches); - } - - return new DefaultBranchConfiguration(); + return Collections.unmodifiableMap(mutableLocalSettings); } private static boolean isTargetingDefaultBranch(Map localSettings) { @@ -115,7 +114,6 @@ private static boolean isTargetingDefaultBranch(Map localSetting } private static BranchConfiguration createBranchConfiguration(String branchName, String branchTarget, - String longLivedBranchesRegex, ProjectBranches branches) { if (null == branchTarget || branchTarget.isEmpty()) { branchTarget = branches.defaultBranchName(); @@ -124,28 +122,12 @@ private static BranchConfiguration createBranchConfiguration(String branchName, BranchInfo existingBranch = branches.get(branchName); if (null == existingBranch) { - final BranchType branchType = computeBranchType(longLivedBranchesRegex, branchName); final BranchInfo targetBranch = findTargetBranch(branchTarget, branches); - return new CommunityBranchConfiguration(branchName, branchType, targetBranch.name(), branchTarget, null); - } - - if (BranchType.LONG == existingBranch.type()) { - return new CommunityBranchConfiguration(branchName, existingBranch.type(), branchName, null, null); - } else { - return new CommunityBranchConfiguration(branchName, existingBranch.type(), branchTarget, branchTarget, + return new CommunityBranchConfiguration(branchName, BranchType.BRANCH, targetBranch.name(), branchTarget, null); } - } - private static BranchType computeBranchType(String longLivedBranchesRegex, String branchName) { - if (null == longLivedBranchesRegex) { - longLivedBranchesRegex = DEFAULT_BRANCH_REGEX; - } - if (branchName.matches(longLivedBranchesRegex)) { - return BranchType.LONG; - } else { - return BranchType.SHORT; - } + return new CommunityBranchConfiguration(branchName, existingBranch.type(), branchName, null, null); } private static BranchConfiguration createPullRequestConfiguration(String pullRequestKey, String pullRequestBranch, @@ -168,13 +150,7 @@ private static BranchInfo findTargetBranch(String targetBranch, ProjectBranches String.format("Target branch '%s' does not exist", targetBranch))); } - if (BranchType.LONG == target.type()) { - return target; - } else { - throw MessageException.of("Could not target requested branch", new IllegalStateException( - String.format("Expected branch type of %s but got %s", BranchType.LONG.name(), - target.type().name()))); - } + return target; } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityBranchParamsValidator.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityBranchParamsValidator.java index 84e881f74..6d175b291 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityBranchParamsValidator.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityBranchParamsValidator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Michael Clarke + * 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 @@ -18,8 +18,6 @@ */ package com.github.mc1arke.sonarqube.plugin.scanner; -import org.sonar.core.config.ScannerProperties; -import org.sonar.scanner.bootstrap.GlobalConfiguration; import org.sonar.scanner.scan.branch.BranchParamsValidator; import java.util.List; @@ -27,24 +25,8 @@ /** * @author Michael Clarke */ -public class CommunityBranchParamsValidator implements BranchParamsValidator, BranchParamsValidatorCompatibility.BranchParamsValidatorCompatibilityMajor7.BranchParamsValidatorCompatibilityMinor9, BranchParamsValidatorCompatibility.BranchParamsValidatorCompatibilityMajor8.BranchParamsValidatorCompatibilityMinor0 { +public class CommunityBranchParamsValidator implements BranchParamsValidator { - private final GlobalConfiguration globalConfiguration; - - public CommunityBranchParamsValidator(GlobalConfiguration globalConfiguration) { - super(); - this.globalConfiguration = globalConfiguration; - } - - @Override - public void validate(List validationMessages, String deprecatedBranchName) { - if (null != deprecatedBranchName && (globalConfiguration.hasKey(ScannerProperties.BRANCH_NAME) || - globalConfiguration.hasKey(ScannerProperties.BRANCH_TARGET))) { - validationMessages.add(String.format( - "The legacy 'sonar.branch' parameter cannot be used at the same time as '%s' or '%s'", - ScannerProperties.BRANCH_NAME, ScannerProperties.BRANCH_TARGET)); - } - } @Override public void validate(List validationMessages) { diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityProjectBranchesLoader.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityProjectBranchesLoader.java index c5579fb63..b2f51526e 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityProjectBranchesLoader.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityProjectBranchesLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Michael Clarke + * 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 @@ -48,12 +48,12 @@ public class CommunityProjectBranchesLoader implements ProjectBranchesLoader { String.format("/%s/%s?%s=", ProjectBranchesParameters.CONTROLLER, ProjectBranchesParameters.ACTION_LIST, ProjectBranchesParameters.PARAM_PROJECT); - private final ScannerWsClientWrapper scannerWsClient; + private final ScannerWsClient scannerWsClient; private final Gson gson; public CommunityProjectBranchesLoader(ScannerWsClient scannerWsClient) { super(); - this.scannerWsClient = new ScannerWsClientWrapper(scannerWsClient); + this.scannerWsClient = scannerWsClient; this.gson = GsonHelper.create(); } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityProjectPullRequestsLoader.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityProjectPullRequestsLoader.java index 1fd876e5f..bc79505b9 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityProjectPullRequestsLoader.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityProjectPullRequestsLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Michael Clarke + * 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 @@ -54,12 +54,12 @@ public class CommunityProjectPullRequestsLoader implements ProjectPullRequestsLo private static final Logger LOGGER = Loggers.get(CommunityProjectPullRequestsLoader.class); private static final String PROJECT_PULL_REQUESTS_URL = "/api/project_pull_requests/list?project="; - private final ScannerWsClientWrapper scannerWsClient; + private final ScannerWsClient scannerWsClient; private final Gson gson; public CommunityProjectPullRequestsLoader(ScannerWsClient scannerWsClient) { super(); - this.scannerWsClient = new ScannerWsClientWrapper(scannerWsClient); + this.scannerWsClient = scannerWsClient; this.gson = new GsonBuilder().registerTypeAdapter(PullRequestInfo.class, createPullRequestInfoJsonDeserialiser()) .create(); diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/ScannerPullRequestPropertySensor.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/ScannerPullRequestPropertySensor.java new file mode 100644 index 000000000..b17bb71b1 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/ScannerPullRequestPropertySensor.java @@ -0,0 +1,66 @@ +/* + * 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.scanner; + +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.gitlab.GitlabServerPullRequestDecorator; +import org.sonar.api.batch.sensor.Sensor; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.SensorDescriptor; +import org.sonar.api.utils.System2; + +import java.util.Optional; + +public class ScannerPullRequestPropertySensor implements Sensor { + + private final System2 system2; + + public ScannerPullRequestPropertySensor(System2 system2) { + super(); + this.system2 = system2; + } + + @Override + public void describe(SensorDescriptor sensorDescriptor) { + sensorDescriptor.name(getClass().getName()); + } + + @Override + public void execute(SensorContext sensorContext) { + if (Boolean.parseBoolean(system2.envVariable("GITLAB_CI"))) { + Optional.ofNullable(system2.envVariable("CI_API_V4_URL")).ifPresent(v -> sensorContext + .addContextProperty(GitlabServerPullRequestDecorator.PULLREQUEST_GITLAB_INSTANCE_URL, v)); + Optional.ofNullable(system2.envVariable("CI_PROJECT_PATH")).ifPresent(v -> sensorContext + .addContextProperty(GitlabServerPullRequestDecorator.PULLREQUEST_GITLAB_PROJECT_ID, v)); + Optional.ofNullable(system2.envVariable("CI_MERGE_REQUEST_PROJECT_URL")).ifPresent(v -> sensorContext + .addContextProperty(GitlabServerPullRequestDecorator.PULLREQUEST_GITLAB_PROJECT_URL, v)); + Optional.ofNullable(system2.envVariable("CI_PIPELINE_ID")).ifPresent(v -> sensorContext + .addContextProperty(GitlabServerPullRequestDecorator.PULLREQUEST_GITLAB_PIPELINE_ID, v)); + } + + Optional.ofNullable(system2.property(GitlabServerPullRequestDecorator.PULLREQUEST_GITLAB_INSTANCE_URL)).ifPresent( + v -> sensorContext.addContextProperty(GitlabServerPullRequestDecorator.PULLREQUEST_GITLAB_INSTANCE_URL, v)); + Optional.ofNullable(system2.property(GitlabServerPullRequestDecorator.PULLREQUEST_GITLAB_PROJECT_ID)).ifPresent( + v -> sensorContext.addContextProperty(GitlabServerPullRequestDecorator.PULLREQUEST_GITLAB_PROJECT_ID, v)); + Optional.ofNullable(system2.property(GitlabServerPullRequestDecorator.PULLREQUEST_GITLAB_PROJECT_URL)).ifPresent( + v -> sensorContext.addContextProperty(GitlabServerPullRequestDecorator.PULLREQUEST_GITLAB_PROJECT_URL, v)); + Optional.ofNullable(system2.property(GitlabServerPullRequestDecorator.PULLREQUEST_GITLAB_PIPELINE_ID)).ifPresent( + v -> sensorContext.addContextProperty(GitlabServerPullRequestDecorator.PULLREQUEST_GITLAB_PIPELINE_ID, v)); + } + +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/ScannerWsClientWrapper.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/ScannerWsClientWrapper.java deleted file mode 100644 index 7fd35ec9d..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/ScannerWsClientWrapper.java +++ /dev/null @@ -1,61 +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.scanner; - -import org.sonar.scanner.bootstrap.ScannerWsClient; -import org.sonarqube.ws.client.WsRequest; -import org.sonarqube.ws.client.WsResponse; - -import java.lang.reflect.InvocationTargetException; - -import com.github.mc1arke.sonarqube.plugin.SonarqubeCompatibility; - -/** - * Provides a way of invoking {@link ScannerWsClient} between SonarQube versions where it changed from being a class - * to being an interface. - */ -/*package*/ class ScannerWsClientWrapper implements SonarqubeCompatibility.Major7.Minor9 { - - private final Object wsClient; - - /*package*/ ScannerWsClientWrapper(Object wsClient) { - this.wsClient = wsClient; - } - - WsResponse call(WsRequest request) { - try { - return (WsResponse) wsClient.getClass().getMethod("call", WsRequest.class).invoke(wsClient, request); - } catch (ReflectiveOperationException ex) { - handleIfInvocationException(ex); - throw new IllegalStateException("Could not execute ScannerWsClient", ex); - } - } - - private static void handleIfInvocationException(ReflectiveOperationException ex) { - if (!(ex instanceof InvocationTargetException)) { - return; - } - Throwable cause = ex.getCause(); - if (cause instanceof Error) { - throw (Error) cause; - } else if (cause instanceof RuntimeException) { - throw (RuntimeException) cause; - } - } -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/CommunityBranchSupportDelegate.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/CommunityBranchSupportDelegate.java index 76647ff54..997ca50c6 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/CommunityBranchSupportDelegate.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/CommunityBranchSupportDelegate.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Michael Clarke + * 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 @@ -72,15 +72,12 @@ public CommunityComponentKey createComponentKey(String projectKey, Map getDeprecatedBranchName() { - return Optional.ofNullable(deprecatedBranchName); - } - @Override public Optional getBranch() { return Optional.ofNullable(branch); diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/ComponentKeyCompatibility.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/AlmSettingsWs.java similarity index 51% rename from src/main/java/com/github/mc1arke/sonarqube/plugin/server/ComponentKeyCompatibility.java rename to src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/AlmSettingsWs.java index 36efee88e..e4b93497c 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/ComponentKeyCompatibility.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/AlmSettingsWs.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Michael Clarke + * 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 @@ -16,19 +16,29 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ -package com.github.mc1arke.sonarqube.plugin.server; +package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws; -import com.github.mc1arke.sonarqube.plugin.SonarqubeCompatibility; +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.AlmSettingsWsAction; +import org.sonar.api.server.ws.WebService; -import java.util.Optional; +import java.util.List; -public interface ComponentKeyCompatibility extends SonarqubeCompatibility { +public class AlmSettingsWs implements WebService { + private final List actions; - interface ComponentKeyCompatibilityMajor7 extends ComponentKeyCompatibility, SonarqubeCompatibility.Major7 { + public AlmSettingsWs(List actions) { + super(); + this.actions = actions; + } - interface ComponentKeyCompatibilityMinor9 extends ComponentKeyCompatibilityMajor7, SonarqubeCompatibility.Major7.Minor9 { + @Override + public void define(Context context) { + NewController controller = context.createController("api/alm_settings"); - Optional getDeprecatedBranchName(); + for (AlmSettingsWsAction action : actions) { + action.define(controller); } + + controller.done(); } } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/BranchCompatibility.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/AlmTypeMapper.java similarity index 50% rename from src/main/java/com/github/mc1arke/sonarqube/plugin/ce/BranchCompatibility.java rename to src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/AlmTypeMapper.java index 679d67465..a390aaaf0 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/BranchCompatibility.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/AlmTypeMapper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Michael Clarke + * 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 @@ -16,17 +16,29 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ -package com.github.mc1arke.sonarqube.plugin.ce; +package com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws; -import com.github.mc1arke.sonarqube.plugin.SonarqubeCompatibility; +import org.sonar.db.alm.setting.ALM; +import org.sonarqube.ws.AlmSettings; -public interface BranchCompatibility extends SonarqubeCompatibility { +public final class AlmTypeMapper { - interface BranchCompatibilityMajor7 extends BranchCompatibility, SonarqubeCompatibility.Major7 { - - interface BranchCompatibilityMinor9 extends BranchCompatibilityMajor7, SonarqubeCompatibility.Major7.Minor9 { + private AlmTypeMapper() { + super(); + } - boolean isLegacyFeature(); + public static AlmSettings.Alm toAlmWs(ALM alm) { + switch (alm) { + case AZURE_DEVOPS: + return AlmSettings.Alm.azure; + case BITBUCKET: + return AlmSettings.Alm.bitbucket; + case GITHUB: + return AlmSettings.Alm.github; + case GITLAB: + return AlmSettings.Alm.gitlab; + default: + throw new IllegalStateException(String.format("Unknown ALM '%s'", alm.name())); } } } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/SetBindingAlmSettingsWsAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/SetBindingAlmSettingsWsAction.java new file mode 100644 index 000000000..bde6121a6 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/SetBindingAlmSettingsWsAction.java @@ -0,0 +1,46 @@ +/* + * 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.server.pullrequest.ws; + +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.AlmSettingsWsAction; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.component.ComponentDto; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.user.UserSession; + +import static org.sonar.api.web.UserRole.ADMIN; + +public abstract class SetBindingAlmSettingsWsAction extends AlmSettingsWsAction { + + private final ComponentFinder componentFinder; + private final UserSession userSession; + + public SetBindingAlmSettingsWsAction(DbClient dbClient, ComponentFinder componentFinder, UserSession userSession) { + super(dbClient); + this.componentFinder = componentFinder; + this.userSession = userSession; + } + + protected ComponentDto getProject(DbSession dbSession, String projectKey) { + ComponentDto project = componentFinder.getByKey(dbSession, projectKey); + userSession.checkComponentPermission(ADMIN, project); + return project; + } +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/AlmSettingsWsAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/AlmSettingsWsAction.java new file mode 100644 index 000000000..6e11c9a63 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/AlmSettingsWsAction.java @@ -0,0 +1,48 @@ +/* + * 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.server.pullrequest.ws.action; + +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.ws.WsAction; + +import static java.lang.String.format; + +public abstract class AlmSettingsWsAction implements WsAction { + + private final DbClient dbClient; + + protected AlmSettingsWsAction(DbClient dbClient) { + super(); + this.dbClient = dbClient; + } + + protected AlmSettingDto getAlmSetting(DbSession dbSession, String almSetting) { + return dbClient.almSettingDao().selectByKey(dbSession, almSetting) + .orElseThrow(() -> new NotFoundException(format("ALM setting '%s' could not be found", almSetting))); + } + + protected void checkAlmSettingDoesNotAlreadyExist(DbSession dbSession, String almSetting) { + dbClient.almSettingDao().selectByKey(dbSession, almSetting).ifPresent(a -> { + throw new IllegalArgumentException(format("ALM setting '%s' already exists", a.getKey())); + }); + } +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/CountBindingAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/CountBindingAction.java new file mode 100644 index 000000000..09bd1b394 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/CountBindingAction.java @@ -0,0 +1,69 @@ +/* + * 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.server.pullrequest.ws.action; + +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.server.user.UserSession; +import org.sonarqube.ws.AlmSettings.CountBindingWsResponse; + +import static org.sonar.server.ws.WsUtils.writeProtobuf; + + +public class CountBindingAction extends AlmSettingsWsAction { + + private static final String PARAM_ALM_SETTING = "almSetting"; + + private final DbClient dbClient; + private final UserSession userSession; + + public CountBindingAction(DbClient dbClient, UserSession userSession) { + super(dbClient); + this.dbClient = dbClient; + this.userSession = userSession; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction("count_binding").setHandler(this); + + action.createParam(PARAM_ALM_SETTING).setRequired(true); + } + + @Override + public void handle(Request request, Response response) { + userSession.checkIsSystemAdministrator(); + + String almSettingKey = request.mandatoryParam(PARAM_ALM_SETTING); + try (DbSession dbSession = dbClient.openSession(false)) { + AlmSettingDto almSetting = getAlmSetting(dbSession, almSettingKey); + int projectsBound = dbClient.projectAlmSettingDao().countByAlmSetting(dbSession, almSetting); + CountBindingWsResponse.Builder builder = + CountBindingWsResponse.newBuilder().setKey(almSetting.getKey()).setProjects(projectsBound); + + writeProtobuf(builder.build(), request, response); + } + + } + +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/DeleteAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/DeleteAction.java new file mode 100644 index 000000000..4c44d9ead --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/DeleteAction.java @@ -0,0 +1,64 @@ +/* + * 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.server.pullrequest.ws.action; + +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.server.user.UserSession; + +public class DeleteAction extends AlmSettingsWsAction { + + private static final String PARAM_KEY = "key"; + + private final DbClient dbClient; + private final UserSession userSession; + + public DeleteAction(DbClient dbClient, UserSession userSession) { + super(dbClient); + this.dbClient = dbClient; + this.userSession = userSession; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction("delete").setPost(true).setHandler(this); + + action.createParam(PARAM_KEY).setRequired(true); + } + + @Override + public void handle(Request request, Response response) { + userSession.checkIsSystemAdministrator(); + + String key = request.mandatoryParam(PARAM_KEY); + try (DbSession dbSession = dbClient.openSession(false)) { + AlmSettingDto almSettingDto = getAlmSetting(dbSession, key); + dbClient.projectAlmSettingDao().deleteByAlmSetting(dbSession, almSettingDto); + dbClient.almSettingDao().delete(dbSession, almSettingDto); + dbSession.commit(); + } + + response.noContent(); + } + +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/DeleteBindingAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/DeleteBindingAction.java new file mode 100644 index 000000000..f58dc2efa --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/DeleteBindingAction.java @@ -0,0 +1,67 @@ +/* + * 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.server.pullrequest.ws.action; + +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.component.ComponentDto; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.user.UserSession; + +import static org.sonar.api.web.UserRole.ADMIN; + +public class DeleteBindingAction extends AlmSettingsWsAction { + + private static final String PARAM_PROJECT = "project"; + + private final DbClient dbClient; + private final UserSession userSession; + private final ComponentFinder componentFinder; + + public DeleteBindingAction(DbClient dbClient, UserSession userSession, ComponentFinder componentFinder) { + super(dbClient); + this.dbClient = dbClient; + this.userSession = userSession; + this.componentFinder = componentFinder; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction("delete_binding").setPost(true).setHandler(this); + + action.createParam(PARAM_PROJECT).setRequired(true); + } + + @Override + public void handle(Request request, Response response) { + String projectKey = request.mandatoryParam(PARAM_PROJECT); + try (DbSession dbSession = dbClient.openSession(false)) { + ComponentDto project = componentFinder.getByKey(dbSession, projectKey); + userSession.checkComponentPermission(ADMIN, project); + dbClient.projectAlmSettingDao().deleteByProject(dbSession, project); + dbSession.commit(); + } + + response.noContent(); + } + +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/GetBindingAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/GetBindingAction.java new file mode 100644 index 000000000..67466481e --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/GetBindingAction.java @@ -0,0 +1,88 @@ +/* + * 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.server.pullrequest.ws.action; + +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.AlmTypeMapper; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.alm.setting.ProjectAlmSettingDto; +import org.sonar.db.component.ComponentDto; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.exceptions.NotFoundException; +import org.sonar.server.user.UserSession; +import org.sonarqube.ws.AlmSettings.GetBindingWsResponse; + +import static java.lang.String.format; +import static java.util.Optional.ofNullable; +import static org.sonar.api.web.UserRole.ADMIN; +import static org.sonar.server.ws.WsUtils.writeProtobuf; + +public class GetBindingAction extends AlmSettingsWsAction { + + private static final String PARAM_PROJECT = "project"; + + private final DbClient dbClient; + private final UserSession userSession; + private final ComponentFinder componentFinder; + + public GetBindingAction(DbClient dbClient, UserSession userSession, ComponentFinder componentFinder) { + super(dbClient); + this.dbClient = dbClient; + this.userSession = userSession; + this.componentFinder = componentFinder; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction("get_binding").setHandler(this); + + action.createParam(PARAM_PROJECT).setRequired(true); + } + + @Override + public void handle(Request request, Response response) { + + String projectKey = request.mandatoryParam(PARAM_PROJECT); + try (DbSession dbSession = dbClient.openSession(false)) { + ComponentDto project = componentFinder.getByKey(dbSession, projectKey); + userSession.checkComponentPermission(ADMIN, project); + ProjectAlmSettingDto projectAlmSetting = dbClient.projectAlmSettingDao().selectByProject(dbSession, project) + .orElseThrow(() -> new NotFoundException( + format("Project '%s' is not bound to any ALM", project.getKey()))); + AlmSettingDto almSetting = + dbClient.almSettingDao().selectByUuid(dbSession, projectAlmSetting.getAlmSettingUuid()).orElseThrow( + () -> new IllegalStateException( + format("ALM setting '%s' cannot be found", projectAlmSetting.getAlmSettingUuid()))); + + GetBindingWsResponse.Builder builder = + GetBindingWsResponse.newBuilder().setAlm(AlmTypeMapper.toAlmWs(almSetting.getAlm())) + .setKey(almSetting.getKey()); + ofNullable(projectAlmSetting.getAlmRepo()).ifPresent(builder::setRepository); + ofNullable(almSetting.getUrl()).ifPresent(builder::setUrl); + ofNullable(projectAlmSetting.getAlmSlug()).ifPresent(builder::setSlug); + writeProtobuf(builder.build(), request, response); + } + + } + +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/ListAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/ListAction.java new file mode 100644 index 000000000..063b5352d --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/ListAction.java @@ -0,0 +1,83 @@ +/* + * 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.server.pullrequest.ws.action; + +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.AlmTypeMapper; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.component.ComponentDto; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.user.UserSession; +import org.sonarqube.ws.AlmSettings.AlmSetting; +import org.sonarqube.ws.AlmSettings.ListWsResponse; + +import java.util.List; +import java.util.stream.Collectors; + +import static java.util.Optional.ofNullable; +import static org.sonar.api.web.UserRole.ADMIN; +import static org.sonar.server.ws.WsUtils.writeProtobuf; + +public class ListAction extends AlmSettingsWsAction { + + private static final String PARAM_PROJECT = "project"; + + private final DbClient dbClient; + private final UserSession userSession; + private final ComponentFinder componentFinder; + + public ListAction(DbClient dbClient, UserSession userSession, ComponentFinder componentFinder) { + super(dbClient); + this.dbClient = dbClient; + this.userSession = userSession; + this.componentFinder = componentFinder; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction("list").setHandler(this); + + action.createParam(PARAM_PROJECT).setRequired(true); + } + + @Override + public void handle(Request request, Response response) { + String projectKey = request.mandatoryParam(PARAM_PROJECT); + try (DbSession dbSession = dbClient.openSession(false)) { + ComponentDto project = componentFinder.getByKey(dbSession, projectKey); + userSession.checkComponentPermission(ADMIN, project); + List settings = dbClient.almSettingDao().selectAll(dbSession); + List wsAlmSettings = settings.stream().map(almSetting -> { + AlmSetting.Builder almSettingBuilder = AlmSetting.newBuilder().setKey(almSetting.getKey()) + .setAlm(AlmTypeMapper.toAlmWs(almSetting.getAlm())); + ofNullable(almSetting.getUrl()).ifPresent(almSettingBuilder::setUrl); + return almSettingBuilder.build(); + }).collect(Collectors.toList()); + ListWsResponse.Builder builder = ListWsResponse.newBuilder().addAllAlmSettings(wsAlmSettings); + + writeProtobuf(builder.build(), request, response); + } + } + + +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/ListDefinitionsAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/ListDefinitionsAction.java new file mode 100644 index 000000000..3ba66707c --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/ListDefinitionsAction.java @@ -0,0 +1,115 @@ +/* + * 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.server.pullrequest.ws.action; + +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.alm.setting.ALM; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.server.user.UserSession; +import org.sonarqube.ws.AlmSettings; +import org.sonarqube.ws.AlmSettings.AlmSettingGithub; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static java.util.Collections.emptyList; +import static java.util.Objects.requireNonNull; +import static org.sonar.server.ws.WsUtils.writeProtobuf; +import static org.sonarqube.ws.AlmSettings.AlmSettingAzure; +import static org.sonarqube.ws.AlmSettings.AlmSettingBitbucket; +import static org.sonarqube.ws.AlmSettings.ListDefinitionsWsResponse; + +public class ListDefinitionsAction extends AlmSettingsWsAction { + + private final DbClient dbClient; + private final UserSession userSession; + + public ListDefinitionsAction(DbClient dbClient, UserSession userSession) { + super(dbClient); + this.dbClient = dbClient; + this.userSession = userSession; + } + + @Override + public void define(WebService.NewController context) { + context.createAction("list_definitions").setHandler(this); + } + + @Override + public void handle(Request request, Response response) { + userSession.checkIsSystemAdministrator(); + + try (DbSession dbSession = dbClient.openSession(false)) { + List settings = dbClient.almSettingDao().selectAll(dbSession); + Map> settingsByAlm = + settings.stream().collect(Collectors.groupingBy(AlmSettingDto::getAlm)); + List githubSettings = + settingsByAlm.getOrDefault(ALM.GITHUB, emptyList()).stream().map(ListDefinitionsAction::toGitHub) + .collect(Collectors.toList()); + List azureSettings = settingsByAlm.getOrDefault(ALM.AZURE_DEVOPS, emptyList()).stream() + .map(ListDefinitionsAction::toAzure).collect(Collectors.toList()); + List bitbucketSettings = + settingsByAlm.getOrDefault(ALM.BITBUCKET, emptyList()).stream() + .map(ListDefinitionsAction::toBitbucket).collect(Collectors.toList()); + List gitlabSettings = + settingsByAlm.getOrDefault(ALM.GITLAB, emptyList()).stream().map(ListDefinitionsAction::toGitlab) + .collect(Collectors.toList()); + ListDefinitionsWsResponse.Builder builder = + ListDefinitionsWsResponse.newBuilder().addAllGithub(githubSettings).addAllAzure(azureSettings) + .addAllBitbucket(bitbucketSettings).addAllGitlab(gitlabSettings); + + writeProtobuf(builder.build(), request, response); + } + + } + + private static AlmSettingGithub toGitHub(AlmSettingDto settingDto) { + return AlmSettingGithub.newBuilder().setKey(settingDto.getKey()) + .setUrl(requireNonNull(settingDto.getUrl(), "URL cannot be null for GitHub ALM setting")) + .setAppId(requireNonNull(settingDto.getAppId(), "App ID cannot be null for GitHub ALM setting")) + .setPrivateKey( + requireNonNull(settingDto.getPrivateKey(), "Private Key cannot be null for GitHub ALM setting")) + .build(); + } + + private static AlmSettingAzure toAzure(AlmSettingDto settingDto) { + return AlmSettingAzure.newBuilder().setKey(settingDto.getKey()).setPersonalAccessToken( + requireNonNull(settingDto.getPersonalAccessToken(), + "Personal Access Token cannot be null for Azure ALM setting")).build(); + } + + private static AlmSettingBitbucket toBitbucket(AlmSettingDto settingDto) { + return AlmSettingBitbucket.newBuilder().setKey(settingDto.getKey()) + .setUrl(requireNonNull(settingDto.getUrl(), "URL cannot be null for Bitbucket ALM setting")) + .setPersonalAccessToken(requireNonNull(settingDto.getPersonalAccessToken(), + "Personal Access Token cannot be null for Bitbucket ALM setting")) + .build(); + } + + private static AlmSettings.AlmSettingGitlab toGitlab(AlmSettingDto settingDto) { + return AlmSettings.AlmSettingGitlab.newBuilder().setKey(settingDto.getKey()).setPersonalAccessToken( + requireNonNull(settingDto.getPersonalAccessToken(), + "Personal Access Token cannot be null for Gitlab ALM setting")).build(); + } +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/azure/CreateAzureAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/azure/CreateAzureAction.java new file mode 100644 index 000000000..c87bdd205 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/azure/CreateAzureAction.java @@ -0,0 +1,71 @@ +/* + * 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.server.pullrequest.ws.action.azure; + +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.AlmSettingsWsAction; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.server.user.UserSession; + +import static org.sonar.db.alm.setting.ALM.AZURE_DEVOPS; + +public class CreateAzureAction extends AlmSettingsWsAction { + + private static final String PARAM_KEY = "key"; + private static final String PARAM_PERSONAL_ACCESS_TOKEN = "personalAccessToken"; + + private final DbClient dbClient; + private UserSession userSession; + + public CreateAzureAction(DbClient dbClient, UserSession userSession) { + super(dbClient); + this.dbClient = dbClient; + this.userSession = userSession; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction("create_azure").setPost(true).setHandler(this); + + action.createParam(PARAM_KEY).setRequired(true).setMaximumLength(200); + action.createParam(PARAM_PERSONAL_ACCESS_TOKEN).setRequired(true).setMaximumLength(2000); + } + + @Override + public void handle(Request request, Response response) { + userSession.checkIsSystemAdministrator(); + + String key = request.mandatoryParam(PARAM_KEY); + String token = request.mandatoryParam(PARAM_PERSONAL_ACCESS_TOKEN); + + try (DbSession dbSession = dbClient.openSession(false)) { + checkAlmSettingDoesNotAlreadyExist(dbSession, key); + dbClient.almSettingDao().insert(dbSession, new AlmSettingDto().setAlm(AZURE_DEVOPS).setKey(key) + .setPersonalAccessToken(token)); + dbSession.commit(); + } + + response.noContent(); + } + +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/azure/SetAzureBindingAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/azure/SetAzureBindingAction.java new file mode 100644 index 000000000..5d33e5db1 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/azure/SetAzureBindingAction.java @@ -0,0 +1,71 @@ +/* + * 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.server.pullrequest.ws.action.azure; + +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.SetBindingAlmSettingsWsAction; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.alm.setting.ProjectAlmSettingDto; +import org.sonar.db.component.ComponentDto; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.user.UserSession; + +public class SetAzureBindingAction extends SetBindingAlmSettingsWsAction { + + private static final String PARAM_ALM_SETTING = "almSetting"; + private static final String PARAM_PROJECT = "project"; + + private final DbClient dbClient; + + public SetAzureBindingAction(DbClient dbClient, ComponentFinder componentFinder, UserSession userSession) { + super(dbClient, componentFinder, userSession); + this.dbClient = dbClient; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction("set_azure_binding").setPost(true).setHandler(this); + + action.createParam(PARAM_ALM_SETTING).setRequired(true); + action.createParam(PARAM_PROJECT).setRequired(true); + } + + @Override + public void handle(Request request, Response response) { + String almSetting = request.mandatoryParam(PARAM_ALM_SETTING); + String projectKey = request.mandatoryParam(PARAM_PROJECT); + + try (DbSession dbSession = dbClient.openSession(false)) { + ComponentDto project = getProject(dbSession, projectKey); + AlmSettingDto almSettingDto = getAlmSetting(dbSession, almSetting); + dbClient.projectAlmSettingDao().insertOrUpdate(dbSession, + new ProjectAlmSettingDto().setProjectUuid(project.uuid()) + .setAlmSettingUuid(almSettingDto.getUuid()) + .setAlmRepo(null).setAlmSlug(null)); + dbSession.commit(); + } + + response.noContent(); + } + +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/azure/UpdateAzureAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/azure/UpdateAzureAction.java new file mode 100644 index 000000000..cee303294 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/azure/UpdateAzureAction.java @@ -0,0 +1,77 @@ +/* + * 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.server.pullrequest.ws.action.azure; + +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.AlmSettingsWsAction; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.server.user.UserSession; + +import static org.apache.commons.lang.StringUtils.isNotBlank; + +public class UpdateAzureAction extends AlmSettingsWsAction { + + private static final String PARAM_KEY = "key"; + private static final String PARAM_NEW_KEY = "newKey"; + private static final String PARAM_PERSONAL_ACCESS_TOKEN = "personalAccessToken"; + + private final DbClient dbClient; + private final UserSession userSession; + + public UpdateAzureAction(DbClient dbClient, UserSession userSession) { + super(dbClient); + this.dbClient = dbClient; + this.userSession = userSession; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction("update_azure").setPost(true).setHandler(this); + + action.createParam(PARAM_KEY).setRequired(true).setMaximumLength(200); + action.createParam(PARAM_NEW_KEY).setMaximumLength(200); + action.createParam(PARAM_PERSONAL_ACCESS_TOKEN).setRequired(true).setMaximumLength(2000); + } + + @Override + public void handle(Request request, Response response) { + userSession.checkIsSystemAdministrator(); + + String key = request.mandatoryParam(PARAM_KEY); + String newKey = request.param(PARAM_NEW_KEY); + String token = request.mandatoryParam(PARAM_PERSONAL_ACCESS_TOKEN); + + try (DbSession dbSession = dbClient.openSession(false)) { + AlmSettingDto almSettingDto = getAlmSetting(dbSession, key); + if (isNotBlank(newKey) && !newKey.equals(key)) { + checkAlmSettingDoesNotAlreadyExist(dbSession, newKey); + } + dbClient.almSettingDao().update(dbSession, almSettingDto.setKey(isNotBlank(newKey) ? newKey : key) + .setPersonalAccessToken(token)); + dbSession.commit(); + } + + response.noContent(); + } + +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/bitbucket/CreateBitBucketAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/bitbucket/CreateBitBucketAction.java new file mode 100644 index 000000000..9d95a96fb --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/bitbucket/CreateBitBucketAction.java @@ -0,0 +1,75 @@ +/* + * 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.server.pullrequest.ws.action.bitbucket; + +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.AlmSettingsWsAction; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.server.user.UserSession; + +import static org.sonar.db.alm.setting.ALM.BITBUCKET; + +public class CreateBitBucketAction extends AlmSettingsWsAction { + + private static final String PARAM_KEY = "key"; + private static final String PARAM_URL = "url"; + private static final String PARAM_PERSONAL_ACCESS_TOKEN = "personalAccessToken"; + + private final DbClient dbClient; + private UserSession userSession; + + public CreateBitBucketAction(DbClient dbClient, UserSession userSession) { + super(dbClient); + this.dbClient = dbClient; + this.userSession = userSession; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction("create_bitbucket").setPost(true).setHandler(this); + + action.createParam(PARAM_KEY).setRequired(true).setMaximumLength(200); + action.createParam(PARAM_URL).setRequired(true).setMaximumLength(2000); + action.createParam(PARAM_PERSONAL_ACCESS_TOKEN).setRequired(true).setMaximumLength(2000); + } + + @Override + public void handle(Request request, Response response) { + userSession.checkIsSystemAdministrator(); + + String key = request.mandatoryParam(PARAM_KEY); + String url = request.mandatoryParam(PARAM_URL); + String token = request.mandatoryParam(PARAM_PERSONAL_ACCESS_TOKEN); + + try (DbSession dbSession = dbClient.openSession(false)) { + checkAlmSettingDoesNotAlreadyExist(dbSession, key); + dbClient.almSettingDao().insert(dbSession, new AlmSettingDto().setAlm(BITBUCKET).setKey(key).setUrl(url) + .setPersonalAccessToken(token)); + dbSession.commit(); + } + + response.noContent(); + } + + +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/bitbucket/SetBitbucketBindingAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/bitbucket/SetBitbucketBindingAction.java new file mode 100644 index 000000000..7b02f46c2 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/bitbucket/SetBitbucketBindingAction.java @@ -0,0 +1,77 @@ +/* + * 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.server.pullrequest.ws.action.bitbucket; + +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.SetBindingAlmSettingsWsAction; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.alm.setting.ProjectAlmSettingDto; +import org.sonar.db.component.ComponentDto; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.user.UserSession; + +public class SetBitbucketBindingAction extends SetBindingAlmSettingsWsAction { + + private static final String PARAM_ALM_SETTING = "almSetting"; + private static final String PARAM_PROJECT = "project"; + private static final String PARAM_REPOSITORY = "repository"; + private static final String PARAM_SLUG = "slug"; + + private final DbClient dbClient; + + public SetBitbucketBindingAction(DbClient dbClient, ComponentFinder componentFinder, UserSession userSession) { + super(dbClient, componentFinder, userSession); + this.dbClient = dbClient; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction("set_bitbucket_binding").setPost(true).setHandler(this); + + action.createParam(PARAM_ALM_SETTING).setRequired(true); + action.createParam(PARAM_PROJECT).setRequired(true); + action.createParam(PARAM_REPOSITORY).setRequired(true); + action.createParam(PARAM_SLUG).setRequired(true); + } + + @Override + public void handle(Request request, Response response) { + String almSetting = request.mandatoryParam(PARAM_ALM_SETTING); + String projectKey = request.mandatoryParam(PARAM_PROJECT); + String repository = request.mandatoryParam(PARAM_REPOSITORY); + String slug = request.mandatoryParam(PARAM_SLUG); + + try (DbSession dbSession = dbClient.openSession(false)) { + ComponentDto project = getProject(dbSession, projectKey); + AlmSettingDto almSettingDto = getAlmSetting(dbSession, almSetting); + dbClient.projectAlmSettingDao().insertOrUpdate(dbSession, + new ProjectAlmSettingDto().setProjectUuid(project.uuid()) + .setAlmSettingUuid(almSettingDto.getUuid()) + .setAlmRepo(repository).setAlmSlug(slug)); + dbSession.commit(); + } + + response.noContent(); + } + +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/bitbucket/UpdateBitbucketAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/bitbucket/UpdateBitbucketAction.java new file mode 100644 index 000000000..ec19d1c10 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/bitbucket/UpdateBitbucketAction.java @@ -0,0 +1,81 @@ +/* + * 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.server.pullrequest.ws.action.bitbucket; + +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.AlmSettingsWsAction; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.server.user.UserSession; + +import static org.apache.commons.lang.StringUtils.isNotBlank; + +public class UpdateBitbucketAction extends AlmSettingsWsAction { + + private static final String PARAM_KEY = "key"; + private static final String PARAM_NEW_KEY = "newKey"; + private static final String PARAM_URL = "url"; + private static final String PARAM_PERSONAL_ACCESS_TOKEN = "personalAccessToken"; + + private final DbClient dbClient; + private UserSession userSession; + + public UpdateBitbucketAction(DbClient dbClient, UserSession userSession) { + super(dbClient); + this.dbClient = dbClient; + this.userSession = userSession; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction("update_bitbucket").setPost(true).setHandler(this); + + action.createParam(PARAM_KEY).setRequired(true).setMaximumLength(200); + action.createParam(PARAM_NEW_KEY).setMaximumLength(200); + action.createParam(PARAM_URL).setRequired(true).setMaximumLength(2000); + action.createParam(PARAM_PERSONAL_ACCESS_TOKEN).setRequired(true).setMaximumLength(2000); + } + + @Override + public void handle(Request request, Response response) { + userSession.checkIsSystemAdministrator(); + + String key = request.mandatoryParam(PARAM_KEY); + String newKey = request.param(PARAM_NEW_KEY); + String url = request.mandatoryParam(PARAM_URL); + String token = request.mandatoryParam(PARAM_PERSONAL_ACCESS_TOKEN); + + try (DbSession dbSession = dbClient.openSession(false)) { + AlmSettingDto almSettingDto = getAlmSetting(dbSession, key); + if (isNotBlank(newKey) && !newKey.equals(key)) { + checkAlmSettingDoesNotAlreadyExist(dbSession, newKey); + } + dbClient.almSettingDao().update(dbSession, + almSettingDto.setKey(isNotBlank(newKey) ? newKey : key).setUrl(url) + .setPersonalAccessToken(token)); + dbSession.commit(); + } + + response.noContent(); + } + +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/github/CreateGithubAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/github/CreateGithubAction.java new file mode 100644 index 000000000..511cc6593 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/github/CreateGithubAction.java @@ -0,0 +1,79 @@ +/* + * 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.server.pullrequest.ws.action.github; + +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.AlmSettingsWsAction; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.server.user.UserSession; + +import static org.sonar.db.alm.setting.ALM.GITHUB; + +public class CreateGithubAction extends AlmSettingsWsAction { + + private static final String PARAM_KEY = "key"; + private static final String PARAM_URL = "url"; + private static final String PARAM_APP_ID = "appId"; + private static final String PARAM_PRIVATE_KEY = "privateKey"; + + private final DbClient dbClient; + private final UserSession userSession; + + public CreateGithubAction(DbClient dbClient, UserSession userSession) { + super(dbClient); + this.dbClient = dbClient; + this.userSession = userSession; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction("create_github").setPost(true).setHandler(this); + + action.createParam(PARAM_KEY).setRequired(true).setMaximumLength(200); + action.createParam(PARAM_URL).setRequired(true).setMaximumLength(2000); + action.createParam(PARAM_APP_ID).setRequired(true).setMaximumLength(80); + action.createParam(PARAM_PRIVATE_KEY).setRequired(true).setMaximumLength(2000); + } + + @Override + public void handle(Request request, Response response) { + userSession.checkIsSystemAdministrator(); + + String key = request.mandatoryParam(PARAM_KEY); + String url = request.mandatoryParam(PARAM_URL); + String appId = request.mandatoryParam(PARAM_APP_ID); + String privateKey = request.mandatoryParam(PARAM_PRIVATE_KEY); + + try (DbSession dbSession = dbClient.openSession(false)) { + checkAlmSettingDoesNotAlreadyExist(dbSession, key); + dbClient.almSettingDao().insert(dbSession, + new AlmSettingDto().setAlm(GITHUB).setKey(key).setUrl(url).setAppId(appId) + .setPrivateKey(privateKey)); + dbSession.commit(); + } + + + response.noContent(); + } + +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/github/SetGithubBindingAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/github/SetGithubBindingAction.java new file mode 100644 index 000000000..5809bc5d7 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/github/SetGithubBindingAction.java @@ -0,0 +1,74 @@ +/* + * 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.server.pullrequest.ws.action.github; + +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.SetBindingAlmSettingsWsAction; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.alm.setting.ProjectAlmSettingDto; +import org.sonar.db.component.ComponentDto; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.user.UserSession; + +public class SetGithubBindingAction extends SetBindingAlmSettingsWsAction { + + private static final String PARAM_ALM_SETTING = "almSetting"; + private static final String PARAM_PROJECT = "project"; + private static final String PARAM_REPOSITORY = "repository"; + + private final DbClient dbClient; + + public SetGithubBindingAction(DbClient dbClient, ComponentFinder componentFinder, UserSession userSession) { + super(dbClient, componentFinder, userSession); + this.dbClient = dbClient; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction("set_github_binding").setPost(true).setHandler(this); + + action.createParam(PARAM_ALM_SETTING).setRequired(true); + action.createParam(PARAM_PROJECT).setRequired(true); + action.createParam(PARAM_REPOSITORY).setRequired(true).setMaximumLength(256); + } + + @Override + public void handle(Request request, Response response) { + String almSetting = request.mandatoryParam(PARAM_ALM_SETTING); + String projectKey = request.mandatoryParam(PARAM_PROJECT); + String repository = request.mandatoryParam(PARAM_REPOSITORY); + + try (DbSession dbSession = dbClient.openSession(false)) { + ComponentDto project = getProject(dbSession, projectKey); + AlmSettingDto almSettingDto = getAlmSetting(dbSession, almSetting); + dbClient.projectAlmSettingDao().insertOrUpdate(dbSession, + new ProjectAlmSettingDto().setProjectUuid(project.uuid()) + .setAlmSettingUuid(almSettingDto.getUuid()) + .setAlmRepo(repository).setAlmSlug(null)); + dbSession.commit(); + } + + response.noContent(); + } + +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/github/UpdateGitHubAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/github/UpdateGitHubAction.java new file mode 100644 index 000000000..f5f627ef4 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/github/UpdateGitHubAction.java @@ -0,0 +1,85 @@ +/* + * 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.server.pullrequest.ws.action.github; + +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.AlmSettingsWsAction; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.server.user.UserSession; + +import static org.apache.commons.lang.StringUtils.isNotBlank; + +public class UpdateGitHubAction extends AlmSettingsWsAction { + + private static final String PARAM_KEY = "key"; + private static final String PARAM_NEW_KEY = "newKey"; + private static final String PARAM_URL = "url"; + private static final String PARAM_APP_ID = "appId"; + private static final String PARAM_PRIVATE_KEY = "privateKey"; + + private final DbClient dbClient; + private final UserSession userSession; + + public UpdateGitHubAction(DbClient dbClient, UserSession userSession) { + super(dbClient); + this.dbClient = dbClient; + this.userSession = userSession; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction("update_github").setPost(true).setHandler(this); + + action.createParam(PARAM_KEY).setRequired(true).setMaximumLength(200); + action.createParam(PARAM_NEW_KEY).setMaximumLength(200); + action.createParam(PARAM_URL).setRequired(true).setMaximumLength(2000); + action.createParam(PARAM_APP_ID).setRequired(true).setMaximumLength(80); + action.createParam(PARAM_PRIVATE_KEY).setRequired(true).setMaximumLength(2000); + } + + @Override + public void handle(Request request, Response response) { + userSession.checkIsSystemAdministrator(); + + String key = request.mandatoryParam(PARAM_KEY); + String newKey = request.param(PARAM_NEW_KEY); + String url = request.mandatoryParam(PARAM_URL); + String appId = request.mandatoryParam(PARAM_APP_ID); + String privateKey = request.mandatoryParam(PARAM_PRIVATE_KEY); + + try (DbSession dbSession = dbClient.openSession(false)) { + AlmSettingDto almSettingDto = getAlmSetting(dbSession, key); + if (isNotBlank(newKey) && !newKey.equals(key)) { + checkAlmSettingDoesNotAlreadyExist(dbSession, newKey); + } + dbClient.almSettingDao().update(dbSession, + almSettingDto.setKey(isNotBlank(newKey) ? newKey : key).setUrl(url) + .setAppId(appId).setPrivateKey(privateKey)); + dbSession.commit(); + } + + response.noContent(); + } + + +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/gitlab/CreateGitlabAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/gitlab/CreateGitlabAction.java new file mode 100644 index 000000000..685d2233a --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/gitlab/CreateGitlabAction.java @@ -0,0 +1,71 @@ +/* + * 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.server.pullrequest.ws.action.gitlab; + +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.AlmSettingsWsAction; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.server.user.UserSession; + +import static org.sonar.db.alm.setting.ALM.GITLAB; + +public class CreateGitlabAction extends AlmSettingsWsAction { + + private static final String PARAM_KEY = "key"; + private static final String PARAM_PERSONAL_ACCESS_TOKEN = "personalAccessToken"; + + private final DbClient dbClient; + private final UserSession userSession; + + public CreateGitlabAction(DbClient dbClient, UserSession userSession) { + super(dbClient); + this.dbClient = dbClient; + this.userSession = userSession; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction("create_gitlab").setPost(true).setHandler(this); + + action.createParam(PARAM_KEY).setRequired(true).setMaximumLength(200); + action.createParam(PARAM_PERSONAL_ACCESS_TOKEN).setRequired(true).setMaximumLength(2000); + } + + @Override + public void handle(Request request, Response response) { + userSession.checkIsSystemAdministrator(); + + String key = request.mandatoryParam(PARAM_KEY); + String accessToken = request.mandatoryParam(PARAM_PERSONAL_ACCESS_TOKEN); + + try (DbSession dbSession = dbClient.openSession(false)) { + checkAlmSettingDoesNotAlreadyExist(dbSession, key); + dbClient.almSettingDao().insert(dbSession, new AlmSettingDto().setAlm(GITLAB).setKey(key) + .setPersonalAccessToken(accessToken)); + dbSession.commit(); + } + + response.noContent(); + } + +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/gitlab/SetGitlabBindingAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/gitlab/SetGitlabBindingAction.java new file mode 100644 index 000000000..79851f28b --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/gitlab/SetGitlabBindingAction.java @@ -0,0 +1,71 @@ +/* + * 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.server.pullrequest.ws.action.gitlab; + +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.SetBindingAlmSettingsWsAction; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.alm.setting.ProjectAlmSettingDto; +import org.sonar.db.component.ComponentDto; +import org.sonar.server.component.ComponentFinder; +import org.sonar.server.user.UserSession; + +public class SetGitlabBindingAction extends SetBindingAlmSettingsWsAction { + + private static final String PARAM_ALM_SETTING = "almSetting"; + private static final String PARAM_PROJECT = "project"; + + private final DbClient dbClient; + + public SetGitlabBindingAction(DbClient dbClient, ComponentFinder componentFinder, UserSession userSession) { + super(dbClient, componentFinder, userSession); + this.dbClient = dbClient; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction("set_gitlab_binding").setPost(true).setHandler(this); + + action.createParam(PARAM_ALM_SETTING).setRequired(true); + action.createParam(PARAM_PROJECT).setRequired(true); + } + + @Override + public void handle(Request request, Response response) { + String almSetting = request.mandatoryParam(PARAM_ALM_SETTING); + String projectKey = request.mandatoryParam(PARAM_PROJECT); + + try (DbSession dbSession = dbClient.openSession(false)) { + ComponentDto project = getProject(dbSession, projectKey); + AlmSettingDto almSettingDto = getAlmSetting(dbSession, almSetting); + dbClient.projectAlmSettingDao().insertOrUpdate(dbSession, + new ProjectAlmSettingDto().setProjectUuid(project.uuid()) + .setAlmSettingUuid(almSettingDto.getUuid()) + .setAlmRepo(null).setAlmSlug(null)); + dbSession.commit(); + } + + response.noContent(); + } + +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/gitlab/UpdateGitlabAction.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/gitlab/UpdateGitlabAction.java new file mode 100644 index 000000000..17828997e --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/server/pullrequest/ws/action/gitlab/UpdateGitlabAction.java @@ -0,0 +1,77 @@ +/* + * 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.server.pullrequest.ws.action.gitlab; + +import com.github.mc1arke.sonarqube.plugin.server.pullrequest.ws.action.AlmSettingsWsAction; +import org.sonar.api.server.ws.Request; +import org.sonar.api.server.ws.Response; +import org.sonar.api.server.ws.WebService; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.server.user.UserSession; + +import static org.apache.commons.lang.StringUtils.isNotBlank; + +public class UpdateGitlabAction extends AlmSettingsWsAction { + + private static final String PARAM_KEY = "key"; + private static final String PARAM_NEW_KEY = "newKey"; + private static final String PARAM_PERSONAL_ACCESS_TOKEN = "personalAccessToken"; + + private final DbClient dbClient; + private final UserSession userSession; + + public UpdateGitlabAction(DbClient dbClient, UserSession userSession) { + super(dbClient); + this.dbClient = dbClient; + this.userSession = userSession; + } + + @Override + public void define(WebService.NewController context) { + WebService.NewAction action = context.createAction("update_gitlab").setPost(true).setHandler(this); + + action.createParam(PARAM_KEY).setRequired(true).setMaximumLength(200); + action.createParam(PARAM_NEW_KEY).setMaximumLength(200); + action.createParam(PARAM_PERSONAL_ACCESS_TOKEN).setRequired(true).setMaximumLength(2000); + } + + @Override + public void handle(Request request, Response response) { + userSession.checkIsSystemAdministrator(); + + String key = request.mandatoryParam(PARAM_KEY); + String newKey = request.param(PARAM_NEW_KEY); + String token = request.mandatoryParam(PARAM_PERSONAL_ACCESS_TOKEN); + + try (DbSession dbSession = dbClient.openSession(false)) { + AlmSettingDto almSettingDto = getAlmSetting(dbSession, key); + if (isNotBlank(newKey) && !newKey.equals(key)) { + checkAlmSettingDoesNotAlreadyExist(dbSession, newKey); + } + dbClient.almSettingDao().update(dbSession, almSettingDto.setKey(isNotBlank(newKey) ? newKey : key) + .setPersonalAccessToken(token)); + dbSession.commit(); + } + + response.noContent(); + } + +} 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 b77039317..90d6e9b78 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) 2019 Michael Clarke + * 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 @@ -99,7 +99,7 @@ public void testComputeEngineSideLoad() { testCase.load(context); final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Class.class); - verify(context, times(2)).addExtensions(argumentCaptor.capture(), argumentCaptor.capture()); + verify(context).addExtensions(argumentCaptor.capture(), argumentCaptor.capture()); assertEquals(Arrays.asList(CommunityReportAnalysisComponentProvider.class, CommunityBranchEditionProvider.class), @@ -117,7 +117,7 @@ public void testServerSideLoad() { testCase.load(context); final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Object.class); - verify(context, times(2)).addExtensions(argumentCaptor.capture(), argumentCaptor.capture()); + verify(context).addExtensions(argumentCaptor.capture(), argumentCaptor.capture()); assertEquals(22, argumentCaptor.getAllValues().size()); diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityBranchLoaderDelegateTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityBranchLoaderDelegateTest.java index 6b34d31cf..72772a3cf 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityBranchLoaderDelegateTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityBranchLoaderDelegateTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Michael Clarke + * 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 @@ -77,7 +77,7 @@ public void testNoBranchDetailsNoExistingBranchThrowsException() { @Test public void testNoBranchDetailsExistingBranchMatch() { BranchDto branchDto = mock(BranchDto.class); - when(branchDto.getBranchType()).thenReturn(BranchType.SHORT); + when(branchDto.getBranchType()).thenReturn(BranchType.BRANCH); when(branchDto.getKey()).thenReturn("branchKey"); when(branchDto.getMergeBranchUuid()).thenReturn("mergeBranchUuid"); when(branchDto.getProjectUuid()).thenReturn("projectUuid"); @@ -97,10 +97,9 @@ public void testNoBranchDetailsExistingBranchMatch() { ArgumentCaptor branchArgumentCaptor = ArgumentCaptor.forClass(CommunityBranch.class); verify(metadataHolder).setBranch(branchArgumentCaptor.capture()); - assertEquals(BranchType.SHORT, branchArgumentCaptor.getValue().getType()); - assertNull(branchArgumentCaptor.getValue().getMergeBranchUuid()); + assertEquals(BranchType.BRANCH, branchArgumentCaptor.getValue().getType()); + assertNull(branchArgumentCaptor.getValue().getTargetBranchName()); assertEquals("branchKey", branchArgumentCaptor.getValue().getName()); - assertFalse(branchArgumentCaptor.getValue().isLegacyFeature()); assertFalse(branchArgumentCaptor.getValue().isMain()); assertFalse(branchArgumentCaptor.getValue().supportsCrossProjectCpd()); assertNull(branchArgumentCaptor.getValue().getTargetBranchName()); @@ -128,9 +127,9 @@ public void testBranchNameNoMatchingBranch() { } @Test - public void testBranchNameMatchingShortBranch() { + public void testBranchNameMatchingBranch() { BranchDto branchDto = mock(BranchDto.class); - when(branchDto.getBranchType()).thenReturn(BranchType.SHORT); + when(branchDto.getBranchType()).thenReturn(BranchType.BRANCH); when(branchDto.getKey()).thenReturn("branchKey"); when(branchDto.getMergeBranchUuid()).thenReturn("mergeBranchUuid"); when(branchDto.getProjectUuid()).thenReturn("projectUuid"); @@ -141,7 +140,7 @@ public void testBranchNameMatchingShortBranch() { ScannerReport.Metadata metadata = ScannerReport.Metadata.getDefaultInstance().toBuilder().setBranchName("branch") - .setBranchType(ScannerReport.Metadata.BranchType.SHORT).build(); + .setBranchType(ScannerReport.Metadata.BranchType.BRANCH).build(); DbClient dbClient = mock(DbClient.class); when(dbClient.branchDao()).thenReturn(branchDao); @@ -154,52 +153,14 @@ public void testBranchNameMatchingShortBranch() { ArgumentCaptor branchArgumentCaptor = ArgumentCaptor.forClass(CommunityBranch.class); verify(metadataHolder).setBranch(branchArgumentCaptor.capture()); - assertEquals(BranchType.SHORT, branchArgumentCaptor.getValue().getType()); - assertEquals("projectUuid", branchArgumentCaptor.getValue().getMergeBranchUuid()); + assertEquals(BranchType.BRANCH, branchArgumentCaptor.getValue().getType()); + assertEquals("projectUuid", branchArgumentCaptor.getValue().getReferenceBranchUuid()); assertEquals("branch", branchArgumentCaptor.getValue().getName()); - assertFalse(branchArgumentCaptor.getValue().isLegacyFeature()); assertFalse(branchArgumentCaptor.getValue().isMain()); assertFalse(branchArgumentCaptor.getValue().supportsCrossProjectCpd()); assertNull(branchArgumentCaptor.getValue().getTargetBranchName()); } - @Test - public void testBranchNameMatchingLongBranch() { - BranchDto branchDto = mock(BranchDto.class); - when(branchDto.getBranchType()).thenReturn(BranchType.LONG); - when(branchDto.getKey()).thenReturn("branchKey"); - when(branchDto.getMergeBranchUuid()).thenReturn("mergeBranchUuid"); - when(branchDto.getProjectUuid()).thenReturn("projectUuid"); - when(branchDto.getUuid()).thenReturn("branchUuid"); - - BranchDao branchDao = mock(BranchDao.class); - when(branchDao.selectByBranchKey(any(), eq("projectUuid"), eq("branch"))).thenReturn(Optional.of(branchDto)); - - ScannerReport.Metadata metadata = - ScannerReport.Metadata.getDefaultInstance().toBuilder().setBranchName("branch") - .setTargetBranchName("targetBranchName") - .setBranchType(ScannerReport.Metadata.BranchType.LONG).build(); - - DbClient dbClient = mock(DbClient.class); - when(dbClient.branchDao()).thenReturn(branchDao); - - MutableAnalysisMetadataHolder metadataHolder = mock(MutableAnalysisMetadataHolder.class); - when(metadataHolder.getProject()).thenReturn(new Project("projectUuid", "key", "name", "description", new ArrayList<>())); - - new CommunityBranchLoaderDelegate(dbClient, metadataHolder).load(metadata); - - ArgumentCaptor branchArgumentCaptor = ArgumentCaptor.forClass(CommunityBranch.class); - - verify(metadataHolder).setBranch(branchArgumentCaptor.capture()); - assertEquals(BranchType.LONG, branchArgumentCaptor.getValue().getType()); - assertEquals("projectUuid", branchArgumentCaptor.getValue().getMergeBranchUuid()); - assertEquals("branch", branchArgumentCaptor.getValue().getName()); - assertFalse(branchArgumentCaptor.getValue().isLegacyFeature()); - assertFalse(branchArgumentCaptor.getValue().isMain()); - assertFalse(branchArgumentCaptor.getValue().supportsCrossProjectCpd()); - assertEquals("targetBranchName", branchArgumentCaptor.getValue().getTargetBranchName()); - } - @Test public void testBranchNamePullRequest() { BranchDto branchDto = mock(BranchDto.class); @@ -213,7 +174,7 @@ public void testBranchNamePullRequest() { ScannerReport.Metadata metadata = ScannerReport.Metadata.getDefaultInstance().toBuilder().setBranchName("sourceBranch") - .setMergeBranchName("branch").setBranchType(ScannerReport.Metadata.BranchType.PULL_REQUEST) + .setReferenceBranchName("branch").setBranchType(ScannerReport.Metadata.BranchType.PULL_REQUEST) .setTargetBranchName("") .build(); @@ -229,9 +190,8 @@ public void testBranchNamePullRequest() { verify(metadataHolder).setBranch(branchArgumentCaptor.capture()); assertEquals(BranchType.PULL_REQUEST, branchArgumentCaptor.getValue().getType()); - assertEquals("mergeBranchUuid", branchArgumentCaptor.getValue().getMergeBranchUuid()); + assertEquals("mergeBranchUuid", branchArgumentCaptor.getValue().getReferenceBranchUuid()); assertEquals("sourceBranch", branchArgumentCaptor.getValue().getName()); - assertFalse(branchArgumentCaptor.getValue().isLegacyFeature()); assertFalse(branchArgumentCaptor.getValue().isMain()); assertFalse(branchArgumentCaptor.getValue().supportsCrossProjectCpd()); assertEquals("branch", branchArgumentCaptor.getValue().getTargetBranchName()); @@ -244,7 +204,7 @@ public void testBranchNamePullRequestNoSuchTarget() { ScannerReport.Metadata metadata = ScannerReport.Metadata.getDefaultInstance().toBuilder().setBranchName("sourceBranch") - .setMergeBranchName("branch").setBranchType(ScannerReport.Metadata.BranchType.PULL_REQUEST) + .setReferenceBranchName("branch").setBranchType(ScannerReport.Metadata.BranchType.PULL_REQUEST) .build(); DbClient dbClient = mock(DbClient.class); @@ -262,16 +222,16 @@ public void testBranchNamePullRequestNoSuchTarget() { @Test - public void testBranchNameMatchingShortBranchWithTargetBranch() { + public void testBranchNameMatchingBranchWithTargetBranch() { BranchDto sourceBranchDto = mock(BranchDto.class); - when(sourceBranchDto.getBranchType()).thenReturn(BranchType.SHORT); + when(sourceBranchDto.getBranchType()).thenReturn(BranchType.BRANCH); when(sourceBranchDto.getKey()).thenReturn("branchKey"); when(sourceBranchDto.getMergeBranchUuid()).thenReturn("mergeBranchUuid"); when(sourceBranchDto.getProjectUuid()).thenReturn("projectUuid"); when(sourceBranchDto.getUuid()).thenReturn("branchUuid"); BranchDto targetBranchDto = mock(BranchDto.class); - when(targetBranchDto.getBranchType()).thenReturn(BranchType.LONG); + when(targetBranchDto.getBranchType()).thenReturn(BranchType.PULL_REQUEST); when(targetBranchDto.getKey()).thenReturn("targetBranchKey"); when(targetBranchDto.getMergeBranchUuid()).thenReturn("targetMergeBranchUuid"); when(targetBranchDto.getProjectUuid()).thenReturn("projectUuid"); @@ -285,7 +245,8 @@ public void testBranchNameMatchingShortBranchWithTargetBranch() { ScannerReport.Metadata metadata = ScannerReport.Metadata.getDefaultInstance().toBuilder().setBranchName("branch") - .setBranchType(ScannerReport.Metadata.BranchType.SHORT).setMergeBranchName("mergeBranchName") + .setBranchType(ScannerReport.Metadata.BranchType.BRANCH) + .setReferenceBranchName("mergeBranchName") .setTargetBranchName("targetBranchThatDoesNotMatchMergeBranch") .build(); @@ -300,8 +261,8 @@ public void testBranchNameMatchingShortBranchWithTargetBranch() { ArgumentCaptor branchArgumentCaptor = ArgumentCaptor.forClass(Branch.class); verify(metadataHolder).setBranch(branchArgumentCaptor.capture()); - assertEquals(BranchType.SHORT, branchArgumentCaptor.getValue().getType()); - assertEquals("targetBranchUuid", branchArgumentCaptor.getValue().getMergeBranchUuid()); + assertEquals(BranchType.BRANCH, branchArgumentCaptor.getValue().getType()); + assertEquals("targetBranchUuid", branchArgumentCaptor.getValue().getReferenceBranchUuid()); assertEquals("branch", branchArgumentCaptor.getValue().getName()); assertFalse(branchArgumentCaptor.getValue().isMain()); assertFalse(branchArgumentCaptor.getValue().supportsCrossProjectCpd()); @@ -309,9 +270,9 @@ public void testBranchNameMatchingShortBranchWithTargetBranch() { } @Test - public void testBranchNameMatchingShortBranchWithTargetBranchMissingTargetBranch() { + public void testBranchNameMatchingBranchWithTargetBranchMissingTargetBranch() { BranchDto sourceBranchDto = mock(BranchDto.class); - when(sourceBranchDto.getBranchType()).thenReturn(BranchType.SHORT); + when(sourceBranchDto.getBranchType()).thenReturn(BranchType.BRANCH); when(sourceBranchDto.getKey()).thenReturn("branchKey"); when(sourceBranchDto.getMergeBranchUuid()).thenReturn("mergeBranchUuid"); when(sourceBranchDto.getProjectUuid()).thenReturn("projectUuid"); @@ -324,7 +285,8 @@ public void testBranchNameMatchingShortBranchWithTargetBranchMissingTargetBranch ScannerReport.Metadata metadata = ScannerReport.Metadata.getDefaultInstance().toBuilder().setBranchName("branch") - .setBranchType(ScannerReport.Metadata.BranchType.SHORT).setMergeBranchName("mergeBranchName") + .setBranchType(ScannerReport.Metadata.BranchType.BRANCH) + .setReferenceBranchName("mergeBranchName") .build(); DbClient dbClient = mock(DbClient.class); diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityBranchTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityBranchTest.java index cacd64d92..e3a9b72c8 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityBranchTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/CommunityBranchTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Michael Clarke + * 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 @@ -68,8 +68,8 @@ public void testGenerateKeyNonMainBranchNonNullFileOfPathContentPullRequest() { } @Test - public void testGenerateKeyNonMainBranchNonNullFileOfPathContentShortBranch() { - CommunityBranch testCase = new CommunityBranch("name", BranchType.SHORT, false, null, null, null); + public void testGenerateKeyNonMainBranchNonNullFileOfPathContentBranch() { + CommunityBranch testCase = new CommunityBranch("name", BranchType.BRANCH, false, null, null, null); assertEquals("projectKey:path:BRANCH:name", testCase.generateKey("projectKey", "path")); } @@ -87,7 +87,7 @@ public void testGetPulRequestKeyNonPullRequest() { .expectMessage(IsEqual.equalTo("Only a branch of type PULL_REQUEST can have a pull request ID")); expectedException.expect(IllegalStateException.class); - new CommunityBranch("name", BranchType.SHORT, false, null, "prKey", null).getPullRequestKey(); + new CommunityBranch("name", BranchType.BRANCH, false, null, "prKey", null).getPullRequestKey(); } } 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 3e2802bf8..5737399a3 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 @@ -32,7 +32,7 @@ public class CommunityReportAnalysisComponentProviderTest { @Test public void testGetComponents() { List result = new CommunityReportAnalysisComponentProvider().getComponents(); - assertEquals(8, result.size()); + assertEquals(9, result.size()); assertEquals(CommunityBranchLoaderDelegate.class, result.get(0)); } } diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/AnalysisDetailsTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/AnalysisDetailsTest.java index 923f13fdf..208cfa44e 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/AnalysisDetailsTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/AnalysisDetailsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Michael Clarke + * 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 @@ -28,13 +28,12 @@ import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.markup.ListItem; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.markup.Paragraph; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.markup.Text; -import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.sonar.api.ce.posttask.Analysis; import org.sonar.api.ce.posttask.Project; import org.sonar.api.ce.posttask.QualityGate; +import org.sonar.api.ce.posttask.ScannerContext; import org.sonar.api.config.Configuration; import org.sonar.api.issue.Issue; import org.sonar.api.measures.CoreMetrics; @@ -47,7 +46,6 @@ import org.sonar.ce.task.projectanalysis.metric.MetricRepository; import org.sonar.core.issue.DefaultIssue; -import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -57,7 +55,6 @@ import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.mockito.ArgumentMatchers.any; @@ -80,10 +77,11 @@ public void testGetBranchName() { Analysis analysis = mock(Analysis.class); Project project = mock(Project.class); Configuration configuration = mock(Configuration.class); + ScannerContext scannerContext = mock(ScannerContext.class); AnalysisDetails testCase = new AnalysisDetails(branchDetails, postAnalysisIssueVisitor, qualityGate, measuresHolder, analysis, - project, configuration, null); + project, configuration, null, scannerContext); assertEquals("branchName", testCase.getBranchName()); } @@ -99,10 +97,11 @@ public void testGetCommitSha() { Analysis analysis = mock(Analysis.class); Project project = mock(Project.class); Configuration configuration = mock(Configuration.class); + ScannerContext scannerContext = mock(ScannerContext.class); AnalysisDetails testCase = new AnalysisDetails(branchDetails, postAnalysisIssueVisitor, qualityGate, measuresHolder, analysis, - project, configuration, null); + project, configuration, null, scannerContext); assertEquals("commitId", testCase.getCommitSha()); } @@ -117,10 +116,11 @@ public void testGetQualityGateStatus() { Analysis analysis = mock(Analysis.class); Project project = mock(Project.class); Configuration configuration = mock(Configuration.class); + ScannerContext scannerContext = mock(ScannerContext.class); AnalysisDetails testCase = new AnalysisDetails(branchDetails, postAnalysisIssueVisitor, qualityGate, measuresHolder, analysis, - project, configuration, null); + project, configuration, null, scannerContext); assertEquals(QualityGate.Status.ERROR, testCase.getQualityGateStatus()); } @@ -135,10 +135,11 @@ public void testGetAnalysisDate() { doReturn(new Date()).when(analysis).getDate(); Project project = mock(Project.class); Configuration configuration = mock(Configuration.class); + ScannerContext scannerContext = mock(ScannerContext.class); AnalysisDetails testCase = new AnalysisDetails(branchDetails, postAnalysisIssueVisitor, qualityGate, measuresHolder, analysis, - project, configuration, null); + project, configuration, null, scannerContext); assertEquals(analysis.getDate(), testCase.getAnalysisDate()); } @@ -153,10 +154,11 @@ public void testGetAnalysisId() { doReturn("Analysis ID").when(analysis).getAnalysisUuid(); Project project = mock(Project.class); Configuration configuration = mock(Configuration.class); + ScannerContext scannerContext = mock(ScannerContext.class); AnalysisDetails testCase = new AnalysisDetails(branchDetails, postAnalysisIssueVisitor, qualityGate, measuresHolder, analysis, - project, configuration, null); + project, configuration, null, scannerContext); assertEquals("Analysis ID", testCase.getAnalysisId()); } @@ -171,10 +173,11 @@ public void testGetAnalysisProjectKey() { Project project = mock(Project.class); doReturn("Project Key").when(project).getKey(); Configuration configuration = mock(Configuration.class); + ScannerContext scannerContext = mock(ScannerContext.class); AnalysisDetails testCase = new AnalysisDetails(branchDetails, postAnalysisIssueVisitor, qualityGate, measuresHolder, analysis, - project, configuration, null); + project, configuration, null, scannerContext); assertEquals("Project Key", testCase.getAnalysisProjectKey()); } @@ -290,9 +293,11 @@ public void testCreateAnalysisSummary() { Configuration configuration = mock(Configuration.class); + ScannerContext scannerContext = mock(ScannerContext.class); + AnalysisDetails testCase = new AnalysisDetails(branchDetails, postAnalysisIssueVisitor, qualityGate, measuresHolder, analysis, - project, configuration, "http://localhost:9000"); + project, configuration, "http://localhost:9000", scannerContext); Formatter formatter = mock(Formatter.class); doReturn("formatted content").when(formatter).format(any(), any()); @@ -399,9 +404,11 @@ public void testCreateAnalysisSummary2() { Configuration configuration = mock(Configuration.class); + ScannerContext scannerContext = mock(ScannerContext.class); + AnalysisDetails testCase = new AnalysisDetails(branchDetails, postAnalysisIssueVisitor, qualityGate, measuresHolder, analysis, - project, configuration, "http://localhost:9000"); + project, configuration, "http://localhost:9000", scannerContext); Formatter formatter = mock(Formatter.class); doReturn("formatted content").when(formatter).format(any(), any()); @@ -504,9 +511,11 @@ public void testCreateAnalysisSummary3() { doReturn(Optional.of("http://host.name/path")).when(configuration) .get(eq(AnalysisDetails.IMAGE_URL_BASE)); + ScannerContext scannerContext = mock(ScannerContext.class); + AnalysisDetails testCase = new AnalysisDetails(branchDetails, postAnalysisIssueVisitor, qualityGate, measuresHolder, analysis, - project, configuration, "http://localhost:9000"); + project, configuration, "http://localhost:9000", scannerContext); Formatter formatter = mock(Formatter.class); doReturn("formatted content").when(formatter).format(any(), any()); @@ -601,9 +610,11 @@ public void testCreateAnalysisSummary4() { Configuration configuration = mock(Configuration.class); + ScannerContext scannerContext = mock(ScannerContext.class); + AnalysisDetails testCase = new AnalysisDetails(branchDetails, postAnalysisIssueVisitor, qualityGate, measuresHolder, analysis, - project, configuration, "http://localhost:9000"); + project, configuration, "http://localhost:9000", scannerContext); Formatter formatter = mock(Formatter.class); doReturn("formatted content").when(formatter).format(any(), any()); @@ -697,7 +708,8 @@ public void testCorrectPostAnalysisIssueVisitorReturned() { AnalysisDetails analysisDetails = new AnalysisDetails(mock(AnalysisDetails.BranchDetails.class), postAnalysisIssueVisitor, mock(QualityGate.class), mock(AnalysisDetails.MeasuresHolder.class), - mock(Analysis.class), mock(Project.class), mock(Configuration.class), null); + mock(Analysis.class), mock(Project.class), mock(Configuration.class), null, + mock(ScannerContext.class)); assertSame(postAnalysisIssueVisitor, analysisDetails.getPostAnalysisIssueVisitor()); } @@ -708,26 +720,4 @@ public void testCorrectBranchDetailsReturned() { assertEquals("commitId", branchDetails.getCommitId()); } - @Test - public void testReflectiveOperationPropagated() { - AnalysisDetails.MeasuresHolder measuresHolder = mock(AnalysisDetails.MeasuresHolder.class); - MeasureRepository measureRepository = mock(MeasureRepository.class); - doReturn(Optional.of(Measure.newMeasureBuilder().create(2))).when(measureRepository) - .getRawMeasure(any(), any()); - doReturn(mock(TreeRootHolder.class)).when(measuresHolder).getTreeRootHolder(); - doReturn(mock(MetricRepository.class)).when(measuresHolder).getMetricRepository(); - doReturn(measureRepository).when(measuresHolder).getMeasureRepository(); - - QualityGate qualityGate = mock(QualityGate.class); - doReturn(new ArrayList<>()).when(qualityGate).getConditions(); - - AnalysisDetails testCase = - new AnalysisDetails(mock(AnalysisDetails.BranchDetails.class), mock(PostAnalysisIssueVisitor.class), - qualityGate, measuresHolder, mock(Analysis.class), mock(Project.class), - mock(Configuration.class), null); - assertThatThrownBy(() -> testCase.createAnalysisSummary(mock(FormatterFactory.class))) - .hasMessage("Could not invoke getDoubleValue").isExactlyInstanceOf(IllegalStateException.class) - .hasCauseExactlyInstanceOf(InvocationTargetException.class); - } - } diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/PullRequestPostAnalysisTaskTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/PullRequestPostAnalysisTaskTest.java index 2ccaa38f1..df1bd5d4d 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/PullRequestPostAnalysisTaskTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/PullRequestPostAnalysisTaskTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Michael Clarke + * 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 @@ -25,25 +25,37 @@ import org.sonar.api.ce.posttask.PostProjectAnalysisTask; import org.sonar.api.ce.posttask.Project; import org.sonar.api.ce.posttask.QualityGate; +import org.sonar.api.ce.posttask.ScannerContext; import org.sonar.api.config.Configuration; import org.sonar.api.platform.Server; import org.sonar.ce.task.projectanalysis.component.ConfigurationRepository; import org.sonar.ce.task.projectanalysis.component.TreeRootHolder; import org.sonar.ce.task.projectanalysis.measure.MeasureRepository; import org.sonar.ce.task.projectanalysis.metric.MetricRepository; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.alm.setting.ALM; +import org.sonar.db.alm.setting.AlmSettingDao; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.alm.setting.ProjectAlmSettingDao; +import org.sonar.db.alm.setting.ProjectAlmSettingDto; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Optional; +import java.util.HashMap; +import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; public class PullRequestPostAnalysisTaskTest { @@ -51,9 +63,12 @@ public class PullRequestPostAnalysisTaskTest { public void testFinishedNonPullRequest() { PostProjectAnalysisTask.ProjectAnalysis projectAnalysis = mock(PostProjectAnalysisTask.ProjectAnalysis.class); Branch branch = mock(Branch.class); - doReturn(Branch.Type.LONG).when(branch).getType(); + doReturn(Branch.Type.BRANCH).when(branch).getType(); doReturn(Optional.of(branch)).when(projectAnalysis).getBranch(); + PostProjectAnalysisTask.Context context = mock(PostProjectAnalysisTask.Context.class); + doReturn(projectAnalysis).when(context).getProjectAnalysis(); + Server server = mock(Server.class); ConfigurationRepository configurationRepository = mock(ConfigurationRepository.class); List pullRequestBuildStatusDecorators = new ArrayList<>(); @@ -62,11 +77,13 @@ public void testFinishedNonPullRequest() { MeasureRepository measureRepository = mock(MeasureRepository.class); TreeRootHolder treeRootHolder = mock(TreeRootHolder.class); + DbClient dbClient = mock(DbClient.class); + PullRequestPostAnalysisTask testCase = new PullRequestPostAnalysisTask(server, configurationRepository, pullRequestBuildStatusDecorators, postAnalysisIssueVisitor, metricRepository, measureRepository, - treeRootHolder); - testCase.finished(projectAnalysis); + treeRootHolder, dbClient); + testCase.finished(context); verify(branch).getType(); verify(branch, never()).getName(); @@ -80,6 +97,9 @@ public void testFinishedNoBranchName() { doReturn(Optional.empty()).when(branch).getName(); doReturn(Optional.of(branch)).when(projectAnalysis).getBranch(); + PostProjectAnalysisTask.Context context = mock(PostProjectAnalysisTask.Context.class); + doReturn(projectAnalysis).when(context).getProjectAnalysis(); + Server server = mock(Server.class); ConfigurationRepository configurationRepository = mock(ConfigurationRepository.class); List pullRequestBuildStatusDecorators = new ArrayList<>(); @@ -88,11 +108,13 @@ public void testFinishedNoBranchName() { MeasureRepository measureRepository = mock(MeasureRepository.class); TreeRootHolder treeRootHolder = mock(TreeRootHolder.class); + DbClient dbClient = mock(DbClient.class); + PullRequestPostAnalysisTask testCase = new PullRequestPostAnalysisTask(server, configurationRepository, pullRequestBuildStatusDecorators, postAnalysisIssueVisitor, metricRepository, measureRepository, - treeRootHolder); - testCase.finished(projectAnalysis); + treeRootHolder, dbClient); + testCase.finished(context); verify(branch).getName(); verify(configurationRepository, never()).getConfiguration(); @@ -106,6 +128,13 @@ public void testFinishedNoProviderSet() { doReturn(Optional.of("branchName")).when(branch).getName(); doReturn(Optional.of(branch)).when(projectAnalysis).getBranch(); + Project project = mock(Project.class); + doReturn("projectUuid").when(project).getUuid(); + doReturn(project).when(projectAnalysis).getProject(); + + PostProjectAnalysisTask.Context context = mock(PostProjectAnalysisTask.Context.class); + doReturn(projectAnalysis).when(context).getProjectAnalysis(); + Server server = mock(Server.class); ConfigurationRepository configurationRepository = mock(ConfigurationRepository.class); List pullRequestBuildStatusDecorators = new ArrayList<>(); @@ -115,17 +144,37 @@ public void testFinishedNoProviderSet() { TreeRootHolder treeRootHolder = mock(TreeRootHolder.class); Configuration configuration = mock(Configuration.class); - doReturn(Optional.empty()).when(configuration).get(eq("sonar.pullrequest.provider")); doReturn(configuration).when(configurationRepository).getConfiguration(); + DbClient dbClient = mock(DbClient.class); + + ProjectAlmSettingDto projectAlmSettingDto = mock(ProjectAlmSettingDto.class); + when(projectAlmSettingDto.getAlmSlug()).thenReturn("dummy/repo"); + when(projectAlmSettingDto.getAlmSettingUuid()).thenReturn("almUuid"); + AlmSettingDto almSettingDto = mock(AlmSettingDto.class); + when(almSettingDto.getUrl()).thenReturn("http://host.name"); + when(almSettingDto.getAppId()).thenReturn("app id"); + when(almSettingDto.getPrivateKey()).thenReturn("private key"); + when(almSettingDto.getAlm()).thenReturn(ALM.GITHUB); + when(dbClient.openSession(anyBoolean())).thenReturn(mock(DbSession.class)); + AlmSettingDao almSettingDao = mock(AlmSettingDao.class); + when(almSettingDao.selectByUuid(any(), any())).thenReturn(Optional.of(almSettingDto)); + when(dbClient.almSettingDao()).thenReturn(almSettingDao); + ProjectAlmSettingDao projectAlmSettingDao = mock(ProjectAlmSettingDao.class); + when(projectAlmSettingDao.selectByProject(any(), anyString())).thenReturn(Optional.of(projectAlmSettingDto)); + when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao); + + ScannerContext scannerContext = mock(ScannerContext.class); + doReturn(scannerContext).when(projectAnalysis).getScannerContext(); + + PullRequestPostAnalysisTask testCase = new PullRequestPostAnalysisTask(server, configurationRepository, pullRequestBuildStatusDecorators, postAnalysisIssueVisitor, metricRepository, measureRepository, - treeRootHolder); - testCase.finished(projectAnalysis); + treeRootHolder, dbClient); + testCase.finished(context); verify(configurationRepository).getConfiguration(); - verify(configuration).get("sonar.pullrequest.provider"); verify(projectAnalysis, never()).getAnalysis(); } @@ -137,6 +186,13 @@ public void testFinishedNoProviderMatchingName() { doReturn(Optional.of("branchName")).when(branch).getName(); doReturn(Optional.of(branch)).when(projectAnalysis).getBranch(); + Project project = mock(Project.class); + doReturn("projectUuid").when(project).getUuid(); + doReturn(project).when(projectAnalysis).getProject(); + + PostProjectAnalysisTask.Context context = mock(PostProjectAnalysisTask.Context.class); + doReturn(projectAnalysis).when(context).getProjectAnalysis(); + Server server = mock(Server.class); ConfigurationRepository configurationRepository = mock(ConfigurationRepository.class); List pullRequestBuildStatusDecorators = new ArrayList<>(); @@ -151,22 +207,43 @@ public void testFinishedNoProviderMatchingName() { PullRequestBuildStatusDecorator decorator2 = mock(PullRequestBuildStatusDecorator.class); doReturn("decorator-name-2").when(decorator2).name(); + doReturn(ALM.GITHUB).when(decorator2).alm(); pullRequestBuildStatusDecorators.add(decorator2); Configuration configuration = mock(Configuration.class); - doReturn(Optional.of("missing-provider")).when(configuration).get(eq("sonar.pullrequest.provider")); doReturn(configuration).when(configurationRepository).getConfiguration(); + DbClient dbClient = mock(DbClient.class); + + ProjectAlmSettingDto projectAlmSettingDto = mock(ProjectAlmSettingDto.class); + when(projectAlmSettingDto.getAlmSlug()).thenReturn("dummy/repo"); + when(projectAlmSettingDto.getAlmSettingUuid()).thenReturn("almUuid"); + AlmSettingDto almSettingDto = mock(AlmSettingDto.class); + when(almSettingDto.getUrl()).thenReturn("http://host.name"); + when(almSettingDto.getAppId()).thenReturn("app id"); + when(almSettingDto.getPrivateKey()).thenReturn("private key"); + when(almSettingDto.getAlm()).thenReturn(ALM.AZURE_DEVOPS); + when(dbClient.openSession(anyBoolean())).thenReturn(mock(DbSession.class)); + AlmSettingDao almSettingDao = mock(AlmSettingDao.class); + when(almSettingDao.selectByUuid(any(), any())).thenReturn(Optional.of(almSettingDto)); + when(dbClient.almSettingDao()).thenReturn(almSettingDao); + ProjectAlmSettingDao projectAlmSettingDao = mock(ProjectAlmSettingDao.class); + when(projectAlmSettingDao.selectByProject(any(), anyString())).thenReturn(Optional.of(projectAlmSettingDto)); + when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao); + + ScannerContext scannerContext = mock(ScannerContext.class); + doReturn(scannerContext).when(projectAnalysis).getScannerContext(); + + PullRequestPostAnalysisTask testCase = new PullRequestPostAnalysisTask(server, configurationRepository, pullRequestBuildStatusDecorators, postAnalysisIssueVisitor, metricRepository, measureRepository, - treeRootHolder); - testCase.finished(projectAnalysis); + treeRootHolder, dbClient); + testCase.finished(context); verify(configurationRepository).getConfiguration(); - verify(configuration).get("sonar.pullrequest.provider"); - verify(decorator1).name(); - verify(decorator2).name(); + verify(decorator1).alm(); + verify(decorator2).alm(); verify(projectAnalysis, never()).getAnalysis(); } @@ -178,6 +255,13 @@ public void testFinishedNoAnalysis() { doReturn(Optional.of("pull-request")).when(branch).getName(); doReturn(Optional.of(branch)).when(projectAnalysis).getBranch(); + Project project = mock(Project.class); + doReturn("projectUuid").when(project).getUuid(); + doReturn(project).when(projectAnalysis).getProject(); + + PostProjectAnalysisTask.Context context = mock(PostProjectAnalysisTask.Context.class); + doReturn(projectAnalysis).when(context).getProjectAnalysis(); + Server server = mock(Server.class); ConfigurationRepository configurationRepository = mock(ConfigurationRepository.class); List pullRequestBuildStatusDecorators = new ArrayList<>(); @@ -194,21 +278,42 @@ public void testFinishedNoAnalysis() { PullRequestBuildStatusDecorator decorator2 = mock(PullRequestBuildStatusDecorator.class); doReturn("decorator-name-2").when(decorator2).name(); + doReturn(ALM.GITHUB).when(decorator2).alm(); pullRequestBuildStatusDecorators.add(decorator2); Configuration configuration = mock(Configuration.class); - doReturn(Optional.of("decorator-name-2")).when(configuration).get(eq("sonar.pullrequest.provider")); doReturn(configuration).when(configurationRepository).getConfiguration(); + DbClient dbClient = mock(DbClient.class); + + ProjectAlmSettingDto projectAlmSettingDto = mock(ProjectAlmSettingDto.class); + when(projectAlmSettingDto.getAlmSlug()).thenReturn("dummy/repo"); + when(projectAlmSettingDto.getAlmSettingUuid()).thenReturn("almUuid"); + AlmSettingDto almSettingDto = mock(AlmSettingDto.class); + when(almSettingDto.getUrl()).thenReturn("http://host.name"); + when(almSettingDto.getAppId()).thenReturn("app id"); + when(almSettingDto.getPrivateKey()).thenReturn("private key"); + when(almSettingDto.getAlm()).thenReturn(ALM.GITHUB); + when(dbClient.openSession(anyBoolean())).thenReturn(mock(DbSession.class)); + AlmSettingDao almSettingDao = mock(AlmSettingDao.class); + when(almSettingDao.selectByUuid(any(), any())).thenReturn(Optional.of(almSettingDto)); + when(dbClient.almSettingDao()).thenReturn(almSettingDao); + ProjectAlmSettingDao projectAlmSettingDao = mock(ProjectAlmSettingDao.class); + when(projectAlmSettingDao.selectByProject(any(), anyString())).thenReturn(Optional.of(projectAlmSettingDto)); + when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao); + + ScannerContext scannerContext = mock(ScannerContext.class); + doReturn(scannerContext).when(projectAnalysis).getScannerContext(); + PullRequestPostAnalysisTask testCase = new PullRequestPostAnalysisTask(server, configurationRepository, pullRequestBuildStatusDecorators, postAnalysisIssueVisitor, metricRepository, measureRepository, - treeRootHolder); - testCase.finished(projectAnalysis); + treeRootHolder, dbClient); + testCase.finished(context); verify(configurationRepository).getConfiguration(); verify(projectAnalysis).getAnalysis(); - verify(decorator2, never()).decorateQualityGateStatus(any()); + verify(decorator2, never()).decorateQualityGateStatus(any(), any(), any()); } @@ -220,6 +325,13 @@ public void testFinishedAnalysisWithNoRevision() { doReturn(Optional.of("pull-request")).when(branch).getName(); doReturn(Optional.of(branch)).when(projectAnalysis).getBranch(); + Project project = mock(Project.class); + doReturn("projectUuid").when(project).getUuid(); + doReturn(project).when(projectAnalysis).getProject(); + + PostProjectAnalysisTask.Context context = mock(PostProjectAnalysisTask.Context.class); + doReturn(projectAnalysis).when(context).getProjectAnalysis(); + Server server = mock(Server.class); ConfigurationRepository configurationRepository = mock(ConfigurationRepository.class); List pullRequestBuildStatusDecorators = new ArrayList<>(); @@ -238,22 +350,44 @@ public void testFinishedAnalysisWithNoRevision() { PullRequestBuildStatusDecorator decorator2 = mock(PullRequestBuildStatusDecorator.class); doReturn("decorator-name-2").when(decorator2).name(); + doReturn(ALM.GITHUB).when(decorator2).alm(); pullRequestBuildStatusDecorators.add(decorator2); Configuration configuration = mock(Configuration.class); - doReturn(Optional.of("decorator-name-2")).when(configuration).get(eq("sonar.pullrequest.provider")); doReturn(configuration).when(configurationRepository).getConfiguration(); + DbClient dbClient = mock(DbClient.class); + + ProjectAlmSettingDto projectAlmSettingDto = mock(ProjectAlmSettingDto.class); + when(projectAlmSettingDto.getAlmSlug()).thenReturn("dummy/repo"); + when(projectAlmSettingDto.getAlmSettingUuid()).thenReturn("almUuid"); + AlmSettingDto almSettingDto = mock(AlmSettingDto.class); + when(almSettingDto.getUrl()).thenReturn("http://host.name"); + when(almSettingDto.getAppId()).thenReturn("app id"); + when(almSettingDto.getPrivateKey()).thenReturn("private key"); + when(almSettingDto.getAlm()).thenReturn(ALM.GITHUB); + when(dbClient.openSession(anyBoolean())).thenReturn(mock(DbSession.class)); + AlmSettingDao almSettingDao = mock(AlmSettingDao.class); + when(almSettingDao.selectByUuid(any(), any())).thenReturn(Optional.of(almSettingDto)); + when(dbClient.almSettingDao()).thenReturn(almSettingDao); + ProjectAlmSettingDao projectAlmSettingDao = mock(ProjectAlmSettingDao.class); + when(projectAlmSettingDao.selectByProject(any(), anyString())).thenReturn(Optional.of(projectAlmSettingDto)); + when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao); + + ScannerContext scannerContext = mock(ScannerContext.class); + doReturn(scannerContext).when(projectAnalysis).getScannerContext(); + + PullRequestPostAnalysisTask testCase = new PullRequestPostAnalysisTask(server, configurationRepository, pullRequestBuildStatusDecorators, postAnalysisIssueVisitor, metricRepository, measureRepository, - treeRootHolder); - testCase.finished(projectAnalysis); + treeRootHolder, dbClient); + testCase.finished(context); verify(configurationRepository).getConfiguration(); verify(projectAnalysis).getAnalysis(); verify(projectAnalysis, never()).getQualityGate(); - verify(decorator2, never()).decorateQualityGateStatus(any()); + verify(decorator2, never()).decorateQualityGateStatus(any(), any(), any()); } @Test @@ -264,6 +398,13 @@ public void testFinishedAnalysisWithNoQualityGate() { doReturn(Optional.of("pull-request")).when(branch).getName(); doReturn(Optional.of(branch)).when(projectAnalysis).getBranch(); + Project project = mock(Project.class); + doReturn("projectUuid").when(project).getUuid(); + doReturn(project).when(projectAnalysis).getProject(); + + PostProjectAnalysisTask.Context context = mock(PostProjectAnalysisTask.Context.class); + doReturn(projectAnalysis).when(context).getProjectAnalysis(); + Server server = mock(Server.class); ConfigurationRepository configurationRepository = mock(ConfigurationRepository.class); List pullRequestBuildStatusDecorators = new ArrayList<>(); @@ -276,28 +417,48 @@ public void testFinishedAnalysisWithNoQualityGate() { doReturn(Optional.of("revision")).when(analysis).getRevision(); doReturn(Optional.of(analysis)).when(projectAnalysis).getAnalysis(); + DbClient dbClient = mock(DbClient.class); + ProjectAlmSettingDto projectAlmSettingDto = mock(ProjectAlmSettingDto.class); + when(projectAlmSettingDto.getAlmSlug()).thenReturn("dummy/repo"); + when(projectAlmSettingDto.getAlmSettingUuid()).thenReturn("almUuid"); + AlmSettingDto almSettingDto = mock(AlmSettingDto.class); + when(almSettingDto.getUrl()).thenReturn("http://host.name"); + when(almSettingDto.getAppId()).thenReturn("app id"); + when(almSettingDto.getPrivateKey()).thenReturn("private key"); + when(almSettingDto.getAlm()).thenReturn(ALM.GITHUB); + when(dbClient.openSession(anyBoolean())).thenReturn(mock(DbSession.class)); + AlmSettingDao almSettingDao = mock(AlmSettingDao.class); + when(almSettingDao.selectByUuid(any(), any())).thenReturn(Optional.of(almSettingDto)); + when(dbClient.almSettingDao()).thenReturn(almSettingDao); + ProjectAlmSettingDao projectAlmSettingDao = mock(ProjectAlmSettingDao.class); + when(projectAlmSettingDao.selectByProject(any(), anyString())).thenReturn(Optional.of(projectAlmSettingDto)); + when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao); + + ScannerContext scannerContext = mock(ScannerContext.class); + doReturn(scannerContext).when(projectAnalysis).getScannerContext(); + PullRequestBuildStatusDecorator decorator1 = mock(PullRequestBuildStatusDecorator.class); doReturn("decorator-name-1").when(decorator1).name(); pullRequestBuildStatusDecorators.add(decorator1); PullRequestBuildStatusDecorator decorator2 = mock(PullRequestBuildStatusDecorator.class); doReturn("decorator-name-2").when(decorator2).name(); + doReturn(ALM.GITHUB).when(decorator2).alm(); pullRequestBuildStatusDecorators.add(decorator2); Configuration configuration = mock(Configuration.class); - doReturn(Optional.of("decorator-name-2")).when(configuration).get(eq("sonar.pullrequest.provider")); doReturn(configuration).when(configurationRepository).getConfiguration(); PullRequestPostAnalysisTask testCase = new PullRequestPostAnalysisTask(server, configurationRepository, pullRequestBuildStatusDecorators, postAnalysisIssueVisitor, metricRepository, measureRepository, - treeRootHolder); - testCase.finished(projectAnalysis); + treeRootHolder, dbClient); + testCase.finished(context); verify(configurationRepository).getConfiguration(); verify(projectAnalysis).getAnalysis(); verify(projectAnalysis).getQualityGate(); - verify(decorator2, never()).decorateQualityGateStatus(any()); + verify(decorator2, never()).decorateQualityGateStatus(any(), any(), any()); } @Test @@ -308,7 +469,11 @@ public void testFinishedAnalysisDecorationRequest() { doReturn(Optional.of("pull-request")).when(branch).getName(); doReturn(Optional.of(branch)).when(projectAnalysis).getBranch(); + PostProjectAnalysisTask.Context context = mock(PostProjectAnalysisTask.Context.class); + doReturn(projectAnalysis).when(context).getProjectAnalysis(); + Project project = mock(Project.class); + doReturn("uuid").when(project).getUuid(); doReturn(project).when(projectAnalysis).getProject(); Server server = mock(Server.class); @@ -327,37 +492,75 @@ public void testFinishedAnalysisDecorationRequest() { QualityGate qualityGate = mock(QualityGate.class); doReturn(qualityGate).when(projectAnalysis).getQualityGate(); + org.sonar.api.ce.posttask.CeTask ceTask = mock(org.sonar.api.ce.posttask.CeTask.class); + doReturn(ceTask).when(projectAnalysis).getCeTask(); + when(ceTask.getId()).thenReturn(""); + PullRequestBuildStatusDecorator decorator1 = mock(PullRequestBuildStatusDecorator.class); doReturn("decorator-name-1").when(decorator1).name(); + doReturn(ALM.BITBUCKET).when(decorator1).alm(); pullRequestBuildStatusDecorators.add(decorator1); PullRequestBuildStatusDecorator decorator2 = mock(PullRequestBuildStatusDecorator.class); doReturn("decorator-name-2").when(decorator2).name(); + doReturn(ALM.GITHUB).when(decorator2).alm(); pullRequestBuildStatusDecorators.add(decorator2); Configuration configuration = mock(Configuration.class); - doReturn(Optional.of("decorator-name-2")).when(configuration).get(eq("sonar.pullrequest.provider")); doReturn(configuration).when(configurationRepository).getConfiguration(); + DbClient dbClient = mock(DbClient.class); + + ProjectAlmSettingDto projectAlmSettingDto = mock(ProjectAlmSettingDto.class); + when(projectAlmSettingDto.getAlmSlug()).thenReturn("dummy/repo"); + when(projectAlmSettingDto.getAlmSettingUuid()).thenReturn("almUuid"); + AlmSettingDto almSettingDto = mock(AlmSettingDto.class); + when(almSettingDto.getUrl()).thenReturn("http://host.name"); + when(almSettingDto.getAppId()).thenReturn("app id"); + when(almSettingDto.getPrivateKey()).thenReturn("private key"); + when(almSettingDto.getAlm()).thenReturn(ALM.GITHUB); + when(dbClient.openSession(anyBoolean())).thenReturn(mock(DbSession.class)); + AlmSettingDao almSettingDao = mock(AlmSettingDao.class); + when(almSettingDao.selectByUuid(any(), any())).thenReturn(Optional.of(almSettingDto)); + when(dbClient.almSettingDao()).thenReturn(almSettingDao); + ProjectAlmSettingDao projectAlmSettingDao = mock(ProjectAlmSettingDao.class); + when(projectAlmSettingDao.selectByProject(any(), anyString())).thenReturn(Optional.of(projectAlmSettingDto)); + when(dbClient.projectAlmSettingDao()).thenReturn(projectAlmSettingDao); + + //ScannerContext scannerContext = mock(ScannerContext.class); + ScannerContext scannerContext = new ScannerContext() { + private final Map props = new HashMap(); + @Override + public Map getProperties() { return this.props; } + }; + doReturn(scannerContext).when(projectAnalysis).getScannerContext(); + PullRequestPostAnalysisTask testCase = new PullRequestPostAnalysisTask(server, configurationRepository, pullRequestBuildStatusDecorators, postAnalysisIssueVisitor, metricRepository, measureRepository, - treeRootHolder); - testCase.finished(projectAnalysis); + treeRootHolder, dbClient); + testCase.finished(context); ArgumentCaptor analysisDetailsArgumentCaptor = ArgumentCaptor.forClass(AnalysisDetails.class); + ArgumentCaptor almSettingDtoArgumentCaptor = ArgumentCaptor.forClass(AlmSettingDto.class); + ArgumentCaptor projectAlmSettingDtoArgumentCaptor = + ArgumentCaptor.forClass(ProjectAlmSettingDto.class); verify(configurationRepository).getConfiguration(); verify(projectAnalysis).getAnalysis(); verify(projectAnalysis).getQualityGate(); - verify(decorator2).decorateQualityGateStatus(analysisDetailsArgumentCaptor.capture()); + verify(decorator2).decorateQualityGateStatus(analysisDetailsArgumentCaptor.capture(), + almSettingDtoArgumentCaptor.capture(), + projectAlmSettingDtoArgumentCaptor.capture()); + assertThat(almSettingDtoArgumentCaptor.getValue()).isSameAs(almSettingDto); + assertThat(projectAlmSettingDtoArgumentCaptor.getValue()).isSameAs(projectAlmSettingDto); AnalysisDetails analysisDetails = new AnalysisDetails(new AnalysisDetails.BranchDetails("pull-request", "revision"), postAnalysisIssueVisitor, qualityGate, new AnalysisDetails.MeasuresHolder(metricRepository, measureRepository, treeRootHolder), analysis, project, - configuration, null); + configuration, null, scannerContext); assertThat(analysisDetailsArgumentCaptor.getValue()).usingRecursiveComparison().isEqualTo(analysisDetails); } @@ -365,7 +568,8 @@ public void testFinishedAnalysisDecorationRequest() { public void testCorrectDescriptionReturnedForTask() { assertThat(new PullRequestPostAnalysisTask(mock(Server.class), mock(ConfigurationRepository.class), new ArrayList<>(), mock(PostAnalysisIssueVisitor.class), mock(MetricRepository.class), - mock(MeasureRepository.class), mock(TreeRootHolder.class)) + mock(MeasureRepository.class), mock(TreeRootHolder.class), + mock(DbClient.class)) .getDescription()).isEqualTo("Pull Request Decoration"); } } diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/AzureDevOpsServerPullRequestDecoratorTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/AzureDevOpsServerPullRequestDecoratorTest.java new file mode 100644 index 000000000..685ed1dbf --- /dev/null +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/azuredevops/AzureDevOpsServerPullRequestDecoratorTest.java @@ -0,0 +1,303 @@ +package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.azuredevops; + +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.AnalysisDetails; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.PostAnalysisIssueVisitor; +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.ce.posttask.QualityGate; +import org.sonar.api.issue.Issue; +import org.sonar.api.platform.Server; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.rules.RuleType; +import org.sonar.ce.task.projectanalysis.component.Component; +import org.sonar.ce.task.projectanalysis.scm.ScmInfoRepository; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.alm.setting.ProjectAlmSettingDto; +import org.sonar.db.protobuf.DbIssues; + +import java.util.Base64; +import java.util.Collections; +import java.util.Optional; + + +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class AzureDevOpsServerPullRequestDecoratorTest { + @Rule + public final WireMockRule wireMockRule = new WireMockRule(wireMockConfig()); + + @Test + public void testName() { + assertEquals("Azure", new AzureDevOpsServerPullRequestDecorator( mock(Server.class), mock(ScmInfoRepository.class) ).name()); + } + + @Test + public void decorateQualityGateStatus() { + String azureProject = "azureProject"; + String sonarProject = "sonarProject"; + String pullRequestId = "8513"; + String baseBranchName = "master"; + String branchName = "feature/some-feature"; + String azureRepository = "myRepository"; + String sonarRootUrl = "http://sonar:9000/sonar"; + String filePath = "path/to/file"; + String issueMessage = "issueMessage"; + String issueKeyVal = "issueKeyVal"; + String ruleKeyVal = "ruleKeyVal"; + int lineNumber = 5; + String token = "token"; + String authorizationHeader = "Basic " + Base64.getEncoder().encodeToString((":" + token).getBytes()); + + ProjectAlmSettingDto projectAlmSettingDto = mock(ProjectAlmSettingDto.class); + AlmSettingDto almSettingDto = mock(AlmSettingDto.class); + when(almSettingDto.getPersonalAccessToken()).thenReturn(token); + + AnalysisDetails analysisDetails = mock(AnalysisDetails.class); + PostAnalysisIssueVisitor issueVisitor = mock(PostAnalysisIssueVisitor.class); + PostAnalysisIssueVisitor.ComponentIssue componentIssue = mock(PostAnalysisIssueVisitor.ComponentIssue.class); + DefaultIssue defaultIssue = mock(DefaultIssue.class); + Component component = mock(Component.class); + + when(analysisDetails.getScannerProperty(eq(AzureDevOpsServerPullRequestDecorator.PULLREQUEST_AZUREDEVOPS_INSTANCE_URL))) + .thenReturn(Optional.of(wireMockRule.baseUrl())); + when(analysisDetails.getScannerProperty(eq(AzureDevOpsServerPullRequestDecorator.PULLREQUEST_AZUREDEVOPS_REPOSITORY_NAME))) + .thenReturn(Optional.of(azureRepository)); + when(analysisDetails.getScannerProperty(eq(AzureDevOpsServerPullRequestDecorator.PULLREQUEST_AZUREDEVOPS_PULLREQUEST_ID))) + .thenReturn(Optional.of(pullRequestId)); + when(analysisDetails.getScannerProperty(eq(AzureDevOpsServerPullRequestDecorator.PULLREQUEST_AZUREDEVOPS_PROJECT_ID))) + .thenReturn(Optional.of(azureProject)); + when(analysisDetails.getScannerProperty(eq(AzureDevOpsServerPullRequestDecorator.PULLREQUEST_AZUREDEVOPS_BASE_BRANCH))) + .thenReturn(Optional.of(baseBranchName)); + when(analysisDetails.getScannerProperty(eq(AzureDevOpsServerPullRequestDecorator.PULLREQUEST_AZUREDEVOPS_BRANCH))) + .thenReturn(Optional.of(branchName)); + when(analysisDetails.getAnalysisProjectKey()).thenReturn(sonarProject); + when(analysisDetails.getQualityGateStatus()).thenReturn(QualityGate.Status.OK); + when(analysisDetails.getBranchName()).thenReturn(pullRequestId); + when(analysisDetails.getPostAnalysisIssueVisitor()).thenReturn(issueVisitor); + when(analysisDetails.getSCMPathForIssue(componentIssue)).thenReturn(Optional.of(filePath)); + when(issueVisitor.getIssues()).thenReturn(Collections.singletonList(componentIssue)); + + DbIssues.Locations locate = DbIssues.Locations.newBuilder().build(); + RuleType rule = RuleType.CODE_SMELL; + RuleKey ruleKey = mock(RuleKey.class); + when(componentIssue.getIssue()).thenReturn(defaultIssue); + when(componentIssue.getComponent()).thenReturn(component); + when(defaultIssue.getStatus()).thenReturn(Issue.STATUS_OPEN); + when(defaultIssue.getLine()).thenReturn(lineNumber); + when(defaultIssue.getLocations()).thenReturn(locate); + when(defaultIssue.type()).thenReturn(rule); + when(defaultIssue.getMessage()).thenReturn(issueMessage); + when(defaultIssue.getRuleKey()).thenReturn(ruleKey); + when(defaultIssue.key()).thenReturn(issueKeyVal); + when(ruleKey.toString()).thenReturn(ruleKeyVal); + + ScmInfoRepository scmInfoRepository = mock(ScmInfoRepository.class); + wireMockRule.stubFor(get(urlEqualTo("/_apis/git/repositories/"+ azureRepository +"/pullRequests/"+ pullRequestId +"/threads?api-version=5.0-preview.1")) + .withHeader("Accept", equalTo("application/json")) + .withHeader("Content-Type", equalTo("application/json; charset=UTF-8")) + .withHeader("Authorization", equalTo(authorizationHeader)) + .willReturn(aResponse() + .withStatus(200) + .withBody( + "{\n" + + " \"value\": [\n" + + " {\n" + + " \"pullRequestThreadContext\": {\n" + + " \"iterationContext\": {\n" + + " \"firstComparingIteration\": 1,\n" + + " \"secondComparingIteration\": 1\n" + + " },\n" + + " \"changeTrackingId\": 4\n" + + " },\n" + + " \"id\": 80450,\n" + + " \"publishedDate\": \"2020-03-10T17:40:09.603Z\",\n" + + " \"lastUpdatedDate\": \"2020-03-10T18:05:06.99Z\",\n" + + " \"comments\": [\n" + + " {\n" + + " \"id\": 1,\n" + + " \"parentCommentId\": 0,\n" + + " \"author\": {\n" + + " \"displayName\": \"More text\",\n" + + " \"url\": \"https://dev.azure.com/fabrikam/_apis/Identities/c27db56f-07a0-43ac-9725-d6666e8b66b5\",\n" + + " \"_links\": {\n" + + " \"avatar\": {\n" + + " \"href\": \"https://dev.azure.com/fabrikam/_apis/GraphProfile/MemberAvatars/win.Uy0xLTUtMjEtMzkwNzU4MjE0NC0yNDM3MzcyODg4LTE5Njg5NDAzMjgtMjIxNQ\"\n" + + " }\n" + + " },\n" + + " \"id\": \"c27db56f-07a0-43ac-9725-d6666e8b66b5\",\n" + + " \"uniqueName\": \"user@mail.ru\",\n" + + " \"imageUrl\": \"https://dev.azure.com/fabrikam/_api/_common/identityImage?id=c27db56f-07a0-43ac-9725-d6666e8b66b5\",\n" + + " \"descriptor\": \"win.Uy0xLTUtMjEtMzkwNzU4MjE0NC0yNDM3MzcyODg4LTE5Njg5NDAzMjgtMjIxNQ\"\n" + + " },\n" + + " \"publishedDate\": \"2020-03-10T17:40:09.603Z\",\n" + + " \"lastUpdatedDate\": \"2020-03-10T18:05:06.99Z\",\n" + + " \"lastContentUpdatedDate\": \"2020-03-10T18:05:06.99Z\",\n" + + " \"isDeleted\": false,\n" + + " \"commentType\": \"text\",\n" + + " \"usersLiked\": [],\n" + + " \"_links\": {\n" + + " \"self\": {\n" + + " \"href\": \"https://dev.azure.com/fabrikam/_apis/git/repositories/28afee9d-4e53-46b8-8deb-99ea20202b2b/pullRequests/8513/threads/80450/comments/1\"\n" + + " }\n" + + " }\n" + + " }\n" + + " ],\n" + + " \"status\": \"active\",\n" + + " \"threadContext\": {\n" + + " \"filePath\": \"/azureProject/myReposytory/Helpers/file.cs\",\n" + + " \"rightFileStart\": {\n" + + " \"line\": 18,\n" + + " \"offset\": 11\n" + + " },\n" + + " \"rightFileEnd\": {\n" + + " \"line\": 18,\n" + + " \"offset\": 15\n" + + " }\n" + + " },\n" + + " \"properties\": {},\n" + + " \"identities\": null,\n" + + " \"isDeleted\": false,\n" + + " \"_links\": {\n" + + " \"self\": {\n" + + " \"href\": \"https://dev.azure.com/fabrikam/_apis/git/repositories/28afee9d-4e53-46b8-8deb-99ea20202b2b/pullRequests/8513/threads/80450\"\n" + + " }\n" + + " }\n" + + " },\n" + + " {\n" + + " \"pullRequestThreadContext\": {\n" + + " \"iterationContext\": {\n" + + " \"firstComparingIteration\": 1,\n" + + " \"secondComparingIteration\": 1\n" + + " },\n" + + " \"changeTrackingId\": 13\n" + + " },\n" + + " \"id\": 80452,\n" + + " \"publishedDate\": \"2020-03-10T19:06:11.37Z\",\n" + + " \"lastUpdatedDate\": \"2020-03-10T19:06:11.37Z\",\n" + + " \"comments\": [\n" + + " {\n" + + " \"id\": 1,\n" + + " \"parentCommentId\": 0,\n" + + " \"author\": {\n" + + " \"displayName\": \"text\",\n" + + " \"url\": \"https://dev.azure.com/fabrikam/_apis/Identities/c27db56f-07a0-43ac-9725-d6666e8b66b5\",\n" + + " \"_links\": {\n" + + " \"avatar\": {\n" + + " \"href\": \"https://dev.azure.com/fabrikam/_apis/GraphProfile/MemberAvatars/win.Uy0xLTUtMjEtMzkwNzU4MjE0NC0yNDM3MzcyODg4LTE5Njg5NDAzMjgtMjIxNQ\"\n" + + " }\n" + + " },\n" + + " \"id\": \"c27db56f-07a0-43ac-9725-d6666e8b66b5\",\n" + + " \"uniqueName\": \"user@mail.ru\",\n" + + " \"imageUrl\": \"https://dev.azure.com/fabrikam/_api/_common/identityImage?id=c27db56f-07a0-43ac-9725-d6666e8b66b5\",\n" + + " \"descriptor\": \"win.Uy0xLTUtMjEtMzkwNzU4MjE0NC0yNDM3MzcyODg4LTE5Njg5NDAzMjgtMjIxNQ\"\n" + + " },\n" + + " \"content\": \"Comment\",\n" + + " \"publishedDate\": \"2020-03-10T19:06:11.37Z\",\n" + + " \"lastUpdatedDate\": \"2020-03-10T19:06:11.37Z\",\n" + + " \"lastContentUpdatedDate\": \"2020-03-10T19:06:11.37Z\",\n" + + " \"commentType\": \"text\",\n" + + " \"usersLiked\": [],\n" + + " \"_links\": {\n" + + " \"self\": {\n" + + " \"href\": \"https://dev.azure.com/fabrikam/_apis/git/repositories/28afee9d-4e53-46b8-8deb-99ea20202b2b/pullRequests/8513/threads/80452/comments/1\"\n" + + " }\n" + + " }\n" + + " }\n" + + " ],\n" + + " \"status\": \"active\",\n" + + " \"threadContext\": {\n" + + " \"filePath\": \"/azureProject/myReposytory/Helpers/file2.cs\",\n" + + " \"rightFileStart\": {\n" + + " \"line\": 30,\n" + + " \"offset\": 57\n" + + " },\n" + + " \"rightFileEnd\": {\n" + + " \"line\": 30,\n" + + " \"offset\": 65\n" + + " }\n" + + " },\n" + + " \"properties\": {},\n" + + " \"identities\": null,\n" + + " \"isDeleted\": false,\n" + + " \"_links\": {\n" + + " \"self\": {\n" + + " \"href\": \"https://dev.azure.com/fabrikam/_apis/git/repositories/28afee9d-4e53-46b8-8deb-99ea20202b2b/pullRequests/8513/threads/80452\"\n" + + " }\n" + + " }\n" + + " }\n" + + " ],\n" + + " \"count\": 2\n" + + "}"))); + + wireMockRule.stubFor(post(urlEqualTo("/_apis/git/repositories/"+ azureRepository +"/pullRequests/"+ pullRequestId +"/threads?api-version=5.0-preview.1")) + .withHeader("Accept", equalTo("application/json")) + .withHeader("Content-Type", equalTo("application/json; charset=UTF-8")) + .withHeader("Authorization", equalTo(authorizationHeader)) + .withRequestBody(equalTo("{" + + "\"status\":\"ACTIVE\"," + + "\"comments\":[{" + + "\"content\":\"CODE_SMELL: issueMessage ([rule](" + sonarRootUrl + "/coding_rules?open=" + ruleKeyVal + "&rule_key=" + ruleKeyVal + "))\\n\\n[See in SonarQube](" + sonarRootUrl + "/project/issues?id=" + sonarProject + "&issues=" + issueKeyVal + "&open=" + issueKeyVal + "&pullRequest="+ pullRequestId +")\"," + + "\"commentType\":\"TEXT\"," + + "\"parentCommentId\":0," + + "\"id\":0," + + "\"threadId\":0," + + "\"author\":null," + + "\"publishedDate\":null," + + "\"lastUpdatedDate\":null," + + "\"lastContentUpdatedDate\":null," + + "\"usersLiked\":null," + + "\"isDeleted\":null," + + "\"_links\":null}]," + + "\"threadContext\":{" + + "\"filePath\":\"/" + filePath + "\"," + + "\"leftFileStart\":null," + + "\"leftFileEnd\":null," + + "\"rightFileStart\":{\"line\":0," + + "\"offset\":1}," + + "\"rightFileEnd\":{\"line\":0," + + "\"offset\":1}}," + + "\"id\":0," + + "\"publishedDate\":null," + + "\"lastUpdatedDate\":null," + + "\"identities\":null," + + "\"isDeleted\":null," + + "\"_links\":null" + + "}") + ) + .willReturn(ok())); + + + wireMockRule.stubFor(post(urlEqualTo("/_apis/git/repositories/"+ azureRepository +"/pullRequests/"+ pullRequestId +"/statuses?api-version=5.0-preview.1")) + .withHeader("Accept", equalTo("application/json")) + .withHeader("Content-Type", equalTo("application/json; charset=UTF-8")) + .withHeader("Authorization", equalTo(authorizationHeader)) + .withRequestBody(equalTo("{" + + "\"state\":\"SUCCEEDED\"," + + "\"description\":\"SonarQube Gate\"," + + "\"context\":{\"genre\":\"SonarQube\",\"name\":\"QualityGate\"}," + + "\"targetUrl\":\"" + sonarRootUrl + "/dashboard?id=" + sonarProject + "&pullRequest=" + pullRequestId + "\"" + + "}") + ) + .willReturn(ok())); + + Server server = mock(Server.class); + when(server.getPublicRootUrl()).thenReturn(sonarRootUrl); + AzureDevOpsServerPullRequestDecorator pullRequestDecorator = + new AzureDevOpsServerPullRequestDecorator(server, scmInfoRepository); + + pullRequestDecorator.decorateQualityGateStatus(analysisDetails, almSettingDto, projectAlmSettingDto); + } + +} diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/server/BitbucketServerPullRequestDecoratorTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/server/BitbucketServerPullRequestDecoratorTest.java index ebeca67ab..a388dcb7e 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/server/BitbucketServerPullRequestDecoratorTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/server/BitbucketServerPullRequestDecoratorTest.java @@ -1,8 +1,25 @@ +/* + * Copyright (C) 2019 Oliver Jedinger + * + * 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.ce.pullrequest.bitbucket.server; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.SummaryComment; -import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.response.activity.ActivityPage; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.response.diff.DiffPage; import com.github.tomakehurst.wiremock.junit.WireMockRule; import org.apache.commons.io.FileUtils; @@ -17,12 +34,16 @@ import java.util.HashMap; import java.util.Map; -import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.IsNull.notNullValue; -import static org.hamcrest.core.IsNull.nullValue; import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNull.notNullValue; public class BitbucketServerPullRequestDecoratorTest { @@ -37,154 +58,37 @@ public class BitbucketServerPullRequestDecoratorTest { /** * configure these settings if you want to trigger your server instead of the test * APITOKEN: use a real api token - * ACTIVITYURL: use for your bitbucket url (http://localhost:7990/rest/api/1.0/users/repo.owner/repos/testrepo/pull-requests/1/activities) */ private static final String APITOKEN = "APITOKEN"; - private static final String ACTIVITYURL = "http://localhost:8089/activities"; - private static final String DIFFURL = "http://localhost:8089/diff"; private static final String COMMENTURL = "http://localhost:8089/comments"; @Before public void setUp() { - bitbucketServerPullRequestDecorator = new BitbucketServerPullRequestDecorator(null); + bitbucketServerPullRequestDecorator = new BitbucketServerPullRequestDecorator(); headers = new HashMap<>(); headers.put("Authorization", String.format("Bearer %s", APITOKEN)); headers.put("Accept", "application/json"); } - @Test - public void getPageActivityClass() throws Exception { - stubFor( - get(urlEqualTo("/activities")) - .withHeader("Accept" , equalTo("application/json")) - .willReturn(aResponse() - .withStatus(200) - .withHeader("Content-Type", "application/json") - .withBody("") - ) - ); - assertThat(bitbucketServerPullRequestDecorator.getPage(ACTIVITYURL, headers, ActivityPage.class), nullValue()); - - stubFor( - get(urlEqualTo("/activities")) - .withHeader("Accept" , equalTo("application/json")) - .willReturn( - aResponse() - .withStatus(200) - .withHeader("Content-Type", "application/json") - .withBody(FileUtils.readFileToByteArray(new File("src/test/resources/bitbucket/activity.json"))) - ) - ); - ActivityPage activityPage = bitbucketServerPullRequestDecorator.getPage(ACTIVITYURL, headers, ActivityPage.class); - assertThat(activityPage, notNullValue()); - assertThat(activityPage.getSize(), is(3)); - } - - @Test(expected = IllegalStateException.class) - public void getPageActivityClassError() { - - stubFor( - get(urlEqualTo("/activities")) - .withHeader("Accept" , equalTo("application/json")) - .willReturn( - aResponse() - .withStatus(400) - .withHeader("Content-Type", "application/json") - .withBody("{}") - ) - ); - bitbucketServerPullRequestDecorator.getPage(ACTIVITYURL, headers, ActivityPage.class); - } - @Test public void getPageDiffClass() throws Exception { - stubFor( - get(urlEqualTo("/diff")) - .withHeader("Accept" , equalTo("application/json")) - .willReturn( - aResponse() - .withStatus(200) - .withHeader("Content-Type", "application/json") - .withBody(FileUtils.readFileToByteArray(new File("src/test/resources/bitbucket/diff.json"))) - ) - ); + stubFor(get(urlEqualTo("/diff")).withHeader("Accept", equalTo("application/json")).willReturn( + aResponse().withStatus(200).withHeader("Content-Type", "application/json") + .withBody(FileUtils.readFileToByteArray(new File("src/test/resources/bitbucket/diff.json"))))); DiffPage page = bitbucketServerPullRequestDecorator.getPage(DIFFURL, headers, DiffPage.class); assertThat(page, notNullValue()); assertThat(page.getDiffs().size(), is(1)); } - @Test - public void getCommentsToDelete() throws Exception { - ActivityPage activityPage = new ObjectMapper().readValue(FileUtils.readFileToByteArray(new File("src/test/resources/bitbucket/delete/activityPageCase1.json")), ActivityPage.class); - assertThat(bitbucketServerPullRequestDecorator.getCommentsToDelete("susi.sonar", activityPage).size() , is(0)); - - activityPage = new ObjectMapper().readValue(FileUtils.readFileToByteArray(new File("src/test/resources/bitbucket/delete/activityPageCase2.json")), ActivityPage.class); - assertThat(bitbucketServerPullRequestDecorator.getCommentsToDelete("susi.sonar", activityPage).size() , is(0)); - - activityPage = new ObjectMapper().readValue(FileUtils.readFileToByteArray(new File("src/test/resources/bitbucket/delete/activityPageCase3.json")), ActivityPage.class); - assertThat(bitbucketServerPullRequestDecorator.getCommentsToDelete("susi.sonar", activityPage).size() , is(0)); - - activityPage = new ObjectMapper().readValue(FileUtils.readFileToByteArray(new File("src/test/resources/bitbucket/delete/activityPageCase4.json")), ActivityPage.class); - assertThat(bitbucketServerPullRequestDecorator.getCommentsToDelete("susi.sonar", activityPage).size() , is(1)); - } - - @Test - public void deleteComments() throws Exception { - assertThat(bitbucketServerPullRequestDecorator.deleteComments(ACTIVITYURL, COMMENTURL, "susi.sonar", headers, false), is(false)); - - stubFor( - get(urlEqualTo("/activities")) - .withHeader("Accept" , equalTo("application/json")) - .willReturn( - aResponse() - .withStatus(200) - .withHeader("Content-Type", "application/json") - .withBody(FileUtils.readFileToByteArray(new File("src/test/resources/bitbucket/activity.json"))) - ) - ); - - stubFor( - delete(urlMatching("/comments/([0-9]*)\\?version=([0-9]*)")) - .withHeader("Accept" , equalTo("application/json")) - .willReturn( - aResponse() - .withStatus(404) - .withHeader("Content-Type", "application/json") - .withBody("{}") - ) - ); - - assertThat(bitbucketServerPullRequestDecorator.deleteComments(ACTIVITYURL, COMMENTURL, "susi.sonar", headers, true), is(false)); - - stubFor( - delete(urlMatching("/comments/([0-9]*)\\?version=([0-9]*)")) - .withHeader("Accept" , equalTo("application/json")) - .willReturn( - aResponse() - .withStatus(204) - .withHeader("Content-Type", "application/json") - .withBody("{}") - ) - ); - assertThat(bitbucketServerPullRequestDecorator.deleteComments(ACTIVITYURL, COMMENTURL, "susi.sonar", headers, true), is(true)); - } - @Test public void getIssueType() throws Exception{ - stubFor( - get(urlEqualTo("/diff")) - .withHeader("Accept" , equalTo("application/json")) - .willReturn( - aResponse() - .withStatus(200) - .withHeader("Content-Type", "application/json") - .withBody(FileUtils.readFileToByteArray(new File("src/test/resources/bitbucket/diff.json"))) - ) - ); + stubFor(get(urlEqualTo("/diff")).withHeader("Accept", equalTo("application/json")).willReturn( + aResponse().withStatus(200).withHeader("Content-Type", "application/json") + .withBody(FileUtils.readFileToByteArray(new File("src/test/resources/bitbucket/diff.json"))))); DiffPage diffPage = bitbucketServerPullRequestDecorator.getPage(DIFFURL, headers, DiffPage.class); // wrong file @@ -202,30 +106,13 @@ public void getIssueType() throws Exception{ @Test public void postComment() throws Exception{ StringEntity summaryComment = new StringEntity(new ObjectMapper().writeValueAsString(new SummaryComment("summaryComment")), ContentType.APPLICATION_JSON); - assertThat(bitbucketServerPullRequestDecorator.postComment(COMMENTURL, headers, summaryComment, false), is(false)); - - stubFor( - post(urlEqualTo("/comments")) - .withHeader("Accept" , equalTo("application/json")) - .willReturn( - aResponse() - .withStatus(400) - .withHeader("Content-Type", "application/json") - .withBody("{}") - ) - ); - assertThat(bitbucketServerPullRequestDecorator.postComment(COMMENTURL, headers, summaryComment, true), is(false)); - - stubFor( - post(urlEqualTo("/comments")) - .withHeader("Accept" , equalTo("application/json")) - .willReturn( - aResponse() - .withStatus(201) - .withHeader("Content-Type", "application/json") - .withBody("{}") - ) - ); - assertThat(bitbucketServerPullRequestDecorator.postComment(COMMENTURL, headers, summaryComment, true), is(true)); + + stubFor(post(urlEqualTo("/comments")).withHeader("Accept", equalTo("application/json")).willReturn( + aResponse().withStatus(400).withHeader("Content-Type", "application/json").withBody("{}"))); + assertThat(bitbucketServerPullRequestDecorator.postComment(COMMENTURL, headers, summaryComment), is(false)); + + stubFor(post(urlEqualTo("/comments")).withHeader("Accept", equalTo("application/json")).willReturn( + aResponse().withStatus(201).withHeader("Content-Type", "application/json").withBody("{}"))); + assertThat(bitbucketServerPullRequestDecorator.postComment(COMMENTURL, headers, summaryComment), is(true)); } } 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 20531ad1a..d370ddd57 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) 2019 Michael Clarke + * 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 @@ -21,6 +21,8 @@ import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.AnalysisDetails; import org.junit.Test; import org.mockito.ArgumentCaptor; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.alm.setting.ProjectAlmSettingDto; import java.io.IOException; import java.security.GeneralSecurityException; @@ -28,6 +30,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -47,8 +50,12 @@ public void testDecorateQualityGatePropagateException() throws IOException, Gene AnalysisDetails analysisDetails = mock(AnalysisDetails.class); GithubPullRequestDecorator testCase = new GithubPullRequestDecorator(checkRunProvider); - doThrow(dummyException).when(checkRunProvider).createCheckRun(any()); - assertThatThrownBy(() -> testCase.decorateQualityGateStatus(analysisDetails)) + ProjectAlmSettingDto projectAlmSettingDto = mock(ProjectAlmSettingDto.class); + AlmSettingDto almSettingDto = mock(AlmSettingDto.class); + + doThrow(dummyException).when(checkRunProvider).createCheckRun(any(), any(), any()); + assertThatThrownBy( + () -> testCase.decorateQualityGateStatus(analysisDetails, almSettingDto, projectAlmSettingDto)) .hasMessage("Could not decorate Pull Request on Github") .isExactlyInstanceOf(IllegalStateException.class).hasCause(dummyException); } @@ -59,10 +66,13 @@ public void testDecorateQualityGateReturnValue() throws IOException, GeneralSecu AnalysisDetails analysisDetails = mock(AnalysisDetails.class); GithubPullRequestDecorator testCase = new GithubPullRequestDecorator(checkRunProvider); + ProjectAlmSettingDto projectAlmSettingDto = mock(ProjectAlmSettingDto.class); + AlmSettingDto almSettingDto = mock(AlmSettingDto.class); + ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(AnalysisDetails.class); - testCase.decorateQualityGateStatus(analysisDetails); - verify(checkRunProvider).createCheckRun(argumentCaptor.capture()); + testCase.decorateQualityGateStatus(analysisDetails, almSettingDto, projectAlmSettingDto); + verify(checkRunProvider).createCheckRun(argumentCaptor.capture(), eq(almSettingDto), eq(projectAlmSettingDto)); assertEquals(analysisDetails, argumentCaptor.getValue()); } } \ No newline at end of file diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v4/GraphqlCheckRunProviderTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v4/GraphqlCheckRunProviderTest.java index 779dc956c..5c2196490 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v4/GraphqlCheckRunProviderTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/github/v4/GraphqlCheckRunProviderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Michael Clarke + * 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 @@ -34,15 +34,13 @@ import org.junit.Test; import org.mockito.ArgumentCaptor; import org.sonar.api.ce.posttask.QualityGate; -import org.sonar.api.config.Configuration; -import org.sonar.api.config.PropertyDefinition; -import org.sonar.api.config.PropertyDefinitions; import org.sonar.api.platform.Server; import org.sonar.api.rule.Severity; import org.sonar.ce.task.projectanalysis.component.Component; -import org.sonar.ce.task.projectanalysis.component.ConfigurationRepository; import org.sonar.ce.task.projectanalysis.component.ReportAttributes; import org.sonar.core.issue.DefaultIssue; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.alm.setting.ProjectAlmSettingDto; import java.io.IOException; import java.security.GeneralSecurityException; @@ -73,78 +71,6 @@ public class GraphqlCheckRunProviderTest { - @Test - public void createCheckRunThrowsExceptionOnMissingProperty() { - GraphqlProvider graphqlProvider = mock(GraphqlProvider.class); - Clock clock = mock(Clock.class); - GithubApplicationAuthenticationProvider githubApplicationAuthenticationProvider = - mock(GithubApplicationAuthenticationProvider.class); - Server server = mock(Server.class); - ConfigurationRepository configurationRepository = mock(ConfigurationRepository.class); - AnalysisDetails analysisDetails = mock(AnalysisDetails.class); - - Configuration configuration = mock(Configuration.class); - when(configurationRepository.getConfiguration()).thenReturn(configuration); - - PropertyDefinitions propertyDefinitions = new PropertyDefinitions(); - - GraphqlCheckRunProvider testCase = - new GraphqlCheckRunProvider(graphqlProvider, clock, githubApplicationAuthenticationProvider, server, - configurationRepository, propertyDefinitions); - assertThatThrownBy(() -> testCase.createCheckRun(analysisDetails)) - .isExactlyInstanceOf(IllegalStateException.class) - .hasMessage("sonar.pullrequest.github.endpoint must be specified in the project configuration"); - } - - @Test - public void createCheckRunTreatsEmptyStringAsNullDefaultPropertyValue() { - GraphqlProvider graphqlProvider = mock(GraphqlProvider.class); - Clock clock = mock(Clock.class); - GithubApplicationAuthenticationProvider githubApplicationAuthenticationProvider = - mock(GithubApplicationAuthenticationProvider.class); - Server server = mock(Server.class); - ConfigurationRepository configurationRepository = mock(ConfigurationRepository.class); - AnalysisDetails analysisDetails = mock(AnalysisDetails.class); - - Configuration configuration = mock(Configuration.class); - when(configurationRepository.getConfiguration()).thenReturn(configuration); - - PropertyDefinition propertyDefinition = - PropertyDefinition.builder("sonar.pullrequest.github.endpoint").defaultValue("").build(); - PropertyDefinitions propertyDefinitions = new PropertyDefinitions(propertyDefinition); - - GraphqlCheckRunProvider testCase = - new GraphqlCheckRunProvider(graphqlProvider, clock, githubApplicationAuthenticationProvider, server, - configurationRepository, propertyDefinitions); - assertThatThrownBy(() -> testCase.createCheckRun(analysisDetails)) - .isExactlyInstanceOf(IllegalStateException.class) - .hasMessage("sonar.pullrequest.github.endpoint must be specified in the project configuration"); - } - - @Test - public void createCheckRunOnMissingPropertyWithBlankDefault() { - GraphqlProvider graphqlProvider = mock(GraphqlProvider.class); - Clock clock = mock(Clock.class); - GithubApplicationAuthenticationProvider githubApplicationAuthenticationProvider = - mock(GithubApplicationAuthenticationProvider.class); - Server server = mock(Server.class); - ConfigurationRepository configurationRepository = mock(ConfigurationRepository.class); - AnalysisDetails analysisDetails = mock(AnalysisDetails.class); - - Configuration configuration = mock(Configuration.class); - when(configurationRepository.getConfiguration()).thenReturn(configuration); - - PropertyDefinition propertyDefinition = PropertyDefinition.builder("sonar.pullrequest.github.endpoint").build(); - PropertyDefinitions propertyDefinitions = new PropertyDefinitions(propertyDefinition); - - GraphqlCheckRunProvider testCase = - new GraphqlCheckRunProvider(graphqlProvider, clock, githubApplicationAuthenticationProvider, server, - configurationRepository, propertyDefinitions); - assertThatThrownBy(() -> testCase.createCheckRun(analysisDetails)) - .isExactlyInstanceOf(IllegalStateException.class) - .hasMessage("sonar.pullrequest.github.endpoint must be specified in the project configuration"); - } - @Test public void createCheckRunExceptionOnErrorResponse() throws IOException, GeneralSecurityException { GraphqlProvider graphqlProvider = mock(GraphqlProvider.class, RETURNS_DEEP_STUBS); @@ -153,7 +79,6 @@ public void createCheckRunExceptionOnErrorResponse() throws IOException, General mock(GithubApplicationAuthenticationProvider.class); Server server = mock(Server.class); when(server.getPublicRootUrl()).thenReturn("http://sonar.server/root"); - ConfigurationRepository configurationRepository = mock(ConfigurationRepository.class); PostAnalysisIssueVisitor postAnalysisIssueVisitor = mock(PostAnalysisIssueVisitor.class); when(postAnalysisIssueVisitor.getIssues()).thenReturn(new ArrayList<>()); @@ -168,12 +93,6 @@ public void createCheckRunExceptionOnErrorResponse() throws IOException, General when(analysisDetails.getAnalysisId()).thenReturn("analysis ID"); when(analysisDetails.getPostAnalysisIssueVisitor()).thenReturn(postAnalysisIssueVisitor); - Configuration configuration = mock(Configuration.class); - when(configuration.get(anyString())) - .then(i -> "sonar.pullrequest.github.endpoint".equals(i.getArguments()[0]) ? Optional.empty() : - Optional.of(i.getArguments()[0])); - when(configurationRepository.getConfiguration()).thenReturn(configuration); - RepositoryAuthenticationToken repositoryAuthenticationToken = mock(RepositoryAuthenticationToken.class); when(repositoryAuthenticationToken.getAuthenticationToken()).thenReturn("dummyAuthToken"); when(repositoryAuthenticationToken.getRepositoryId()).thenReturn("repository ID"); @@ -190,22 +109,22 @@ public void createCheckRunExceptionOnErrorResponse() throws IOException, General when(graphQLTemplate.mutate(any(), eq(CreateCheckRun.class))).thenReturn(graphQLResponseEntity); when(graphqlProvider.createGraphQLTemplate()).thenReturn(graphQLTemplate); - PropertyDefinition propertyDefinition = - PropertyDefinition.builder("sonar.pullrequest.github.endpoint").defaultValue("http://host.name") - .build(); - PropertyDefinitions propertyDefinitions = new PropertyDefinitions(propertyDefinition); + ProjectAlmSettingDto projectAlmSettingDto = mock(ProjectAlmSettingDto.class); + when(projectAlmSettingDto.getAlmRepo()).thenReturn("dummy/repo"); + AlmSettingDto almSettingDto = mock(AlmSettingDto.class); + when(almSettingDto.getUrl()).thenReturn("http://host.name"); + when(almSettingDto.getAppId()).thenReturn("app id"); + when(almSettingDto.getPrivateKey()).thenReturn("private key"); GraphqlCheckRunProvider testCase = - new GraphqlCheckRunProvider(graphqlProvider, clock, githubApplicationAuthenticationProvider, server, - configurationRepository, propertyDefinitions); - assertThatThrownBy(() -> testCase.createCheckRun(analysisDetails)).hasMessage( + new GraphqlCheckRunProvider(graphqlProvider, clock, githubApplicationAuthenticationProvider, server); + assertThatThrownBy(() -> testCase.createCheckRun(analysisDetails, almSettingDto, projectAlmSettingDto)) + .hasMessage( "An error was returned in the response from the Github API:" + System.lineSeparator() + "- Error{message='example message', locations=[]}").isExactlyInstanceOf(IllegalStateException.class); verify(githubApplicationAuthenticationProvider) - .getInstallationToken(eq("http://host.name"), eq("sonar.alm.github.app.id"), - eq("sonar.alm.github.app.privateKey.secured"), - eq("sonar.pullrequest.github.repository")); + .getInstallationToken(eq("http://host.name"), eq("app id"), eq("private key"), eq("dummy/repo")); } @@ -220,7 +139,6 @@ public void createCheckRunExceptionOnInvalidIssueSeverity() throws IOException, .thenReturn(mock(RepositoryAuthenticationToken.class)); Server server = mock(Server.class); when(server.getPublicRootUrl()).thenReturn("http://sonar.server/root"); - ConfigurationRepository configurationRepository = mock(ConfigurationRepository.class); ReportAttributes reportAttributes = mock(ReportAttributes.class); when(reportAttributes.getScmPath()).thenReturn(Optional.of("path")); @@ -249,22 +167,13 @@ public void createCheckRunExceptionOnInvalidIssueSeverity() throws IOException, when(analysisDetails.getAnalysisId()).thenReturn("analysis ID"); when(analysisDetails.getPostAnalysisIssueVisitor()).thenReturn(postAnalysisIssueVisitor); - Configuration configuration = mock(Configuration.class); - when(configuration.get(anyString())) - .then(i -> "sonar.pullrequest.github.endpoint".equals(i.getArguments()[0]) ? Optional.empty() : - Optional.of(i.getArguments()[0])); - when(configurationRepository.getConfiguration()).thenReturn(configuration); - - - PropertyDefinition propertyDefinition = - PropertyDefinition.builder("sonar.pullrequest.github.endpoint").defaultValue("http://host.name") - .build(); - PropertyDefinitions propertyDefinitions = new PropertyDefinitions(propertyDefinition); + ProjectAlmSettingDto projectAlmSettingDto = mock(ProjectAlmSettingDto.class); + AlmSettingDto almSettingDto = mock(AlmSettingDto.class); GraphqlCheckRunProvider testCase = - new GraphqlCheckRunProvider(graphqlProvider, clock, githubApplicationAuthenticationProvider, server, - configurationRepository, propertyDefinitions); - assertThatThrownBy(() -> testCase.createCheckRun(analysisDetails)).hasMessage("Unknown severity value: dummy") + new GraphqlCheckRunProvider(graphqlProvider, clock, githubApplicationAuthenticationProvider, server); + assertThatThrownBy(() -> testCase.createCheckRun(analysisDetails, almSettingDto, projectAlmSettingDto)) + .hasMessage("Unknown severity value: dummy") .isExactlyInstanceOf(IllegalArgumentException.class); } @@ -287,7 +196,6 @@ private void createCheckRunHappyPath(QualityGate.Status status) throws IOExcepti mock(GithubApplicationAuthenticationProvider.class); Server server = mock(Server.class); when(server.getPublicRootUrl()).thenReturn("http://sonar.server/root"); - ConfigurationRepository configurationRepository = mock(ConfigurationRepository.class); DefaultIssue issue1 = mock(DefaultIssue.class); when(issue1.getLine()).thenReturn(2); @@ -377,12 +285,6 @@ private void createCheckRunHappyPath(QualityGate.Status status) throws IOExcepti when(analysisDetails.getAnalysisId()).thenReturn("analysis ID"); when(analysisDetails.getPostAnalysisIssueVisitor()).thenReturn(postAnalysisIssueVisitor); - Configuration configuration = mock(Configuration.class); - when(configuration.get(anyString())).then(i -> "sonar.pullrequest.github.endpoint".equals(i.getArguments()[0]) ? - Optional.of("http://host.name") : - Optional.of(i.getArguments()[0])); - when(configurationRepository.getConfiguration()).thenReturn(configuration); - ArgumentCaptor authenticationProviderArgumentCaptor = ArgumentCaptor.forClass(String.class); RepositoryAuthenticationToken repositoryAuthenticationToken = mock(RepositoryAuthenticationToken.class); when(repositoryAuthenticationToken.getAuthenticationToken()).thenReturn("dummyAuthToken"); @@ -432,12 +334,16 @@ private void createCheckRunHappyPath(QualityGate.Status status) throws IOExcepti .thenReturn(graphQLResponseEntity); when(graphqlProvider.createGraphQLTemplate()).thenReturn(graphQLTemplate); - PropertyDefinitions propertyDefinitions = new PropertyDefinitions(); + ProjectAlmSettingDto projectAlmSettingDto = mock(ProjectAlmSettingDto.class); + when(projectAlmSettingDto.getAlmRepo()).thenReturn("dummy/repo"); + AlmSettingDto almSettingDto = mock(AlmSettingDto.class); + when(almSettingDto.getUrl()).thenReturn("http://host.name"); + when(almSettingDto.getAppId()).thenReturn("app id"); + when(almSettingDto.getPrivateKey()).thenReturn("private key"); GraphqlCheckRunProvider testCase = - new GraphqlCheckRunProvider(graphqlProvider, clock, githubApplicationAuthenticationProvider, server, - configurationRepository, propertyDefinitions); - testCase.createCheckRun(analysisDetails); + new GraphqlCheckRunProvider(graphqlProvider, clock, githubApplicationAuthenticationProvider, server); + testCase.createCheckRun(analysisDetails, almSettingDto, projectAlmSettingDto); assertEquals(1, requestBuilders.size()); @@ -457,12 +363,9 @@ private void createCheckRunHappyPath(QualityGate.Status status) throws IOExcepti assertEquals("createCheckRun", argumentsArgumentCaptor.getValue().getDotPath()); assertEquals(1, argumentsArgumentCaptor.getValue().getArguments().size()); assertEquals("input", argumentsArgumentCaptor.getValue().getArguments().get(0).getKey()); -// assertThat(argumentsArgumentCaptor.getValue().getArguments().get(0).getValue()).usingRecursiveComparison().isEqualTo(inputObjects.get(1)); - assertEquals( - Arrays.asList("http://host.name", "sonar.alm.github.app.id", "sonar.alm.github.app.privateKey.secured", - "sonar.pullrequest.github.repository"), - authenticationProviderArgumentCaptor.getAllValues()); + assertEquals(Arrays.asList("http://host.name", "app id", "private key", "dummy/repo"), + authenticationProviderArgumentCaptor.getAllValues()); List> expectedAnnotationObjects = new ArrayList<>(); int position = 0; @@ -508,7 +411,7 @@ private void createCheckRunHappyPath(QualityGate.Status status) throws IOExcepti assertThat(annotationArgumentCaptor.getValue()).isEqualTo(expectedAnnotationObjects); verify(inputObjectBuilders.get(position + 1)).put(eq("repositoryId"), eq("repository ID")); - verify(inputObjectBuilders.get(position + 1)).put(eq("name"), eq("sonar.alm.github.app.name Results")); + verify(inputObjectBuilders.get(position + 1)).put(eq("name"), eq("Sonarqube Results")); verify(inputObjectBuilders.get(position + 1)).put(eq("headSha"), eq("commit SHA")); verify(inputObjectBuilders.get(position + 1)).put(eq("status"), eq(RequestableCheckStatusState.COMPLETED)); verify(inputObjectBuilders.get(position + 1)).put(eq("conclusion"), eq(status == QualityGate.Status.OK ? @@ -529,12 +432,9 @@ public void checkCorrectDefaultValuesInjected() { GithubApplicationAuthenticationProvider githubApplicationAuthenticationProvider = mock(GithubApplicationAuthenticationProvider.class); Server server = mock(Server.class); - ConfigurationRepository configurationRepository = mock(ConfigurationRepository.class); - PropertyDefinitions propertyDefinitions = new PropertyDefinitions(); - assertThat(new GraphqlCheckRunProvider(clock, githubApplicationAuthenticationProvider, server, - configurationRepository, propertyDefinitions)).usingRecursiveComparison() + assertThat(new GraphqlCheckRunProvider(clock, githubApplicationAuthenticationProvider, server)) + .usingRecursiveComparison() .isEqualTo(new GraphqlCheckRunProvider(new DefaultGraphqlProvider(), clock, - githubApplicationAuthenticationProvider, server, - configurationRepository, propertyDefinitions)); + githubApplicationAuthenticationProvider, server)); } -} \ No newline at end of file +} diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabServerPullRequestDecoratorTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabServerPullRequestDecoratorTest.java index 204f9e8b8..bff354d02 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabServerPullRequestDecoratorTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/gitlab/GitlabServerPullRequestDecoratorTest.java @@ -1,29 +1,46 @@ +/* + * Copyright (C) 2019 Markus Heberling + * + * 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.ce.pullrequest.gitlab; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.Collections; -import java.util.Optional; - import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.AnalysisDetails; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.PostAnalysisIssueVisitor; -import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.PullRequestBuildStatusDecorator; import com.github.tomakehurst.wiremock.junit.WireMockRule; import org.junit.Rule; import org.junit.Test; import org.mockito.Mockito; import org.sonar.api.ce.posttask.QualityGate; -import org.sonar.api.config.Configuration; import org.sonar.api.issue.Issue; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.platform.Server; import org.sonar.ce.task.projectanalysis.component.Component; -import org.sonar.ce.task.projectanalysis.component.ConfigurationRepository; import org.sonar.ce.task.projectanalysis.scm.Changeset; import org.sonar.ce.task.projectanalysis.scm.ScmInfo; import org.sonar.ce.task.projectanalysis.scm.ScmInfoRepository; import org.sonar.core.issue.DefaultIssue; +import org.sonar.db.alm.setting.AlmSettingDto; +import org.sonar.db.alm.setting.ProjectAlmSettingDto; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Optional; import static com.github.tomakehurst.wiremock.client.WireMock.created; import static com.github.tomakehurst.wiremock.client.WireMock.delete; @@ -35,6 +52,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -56,22 +74,20 @@ public void decorateQualityGateStatus() { String filePath = "/path/to/file"; int lineNumber = 5; - ConfigurationRepository configurationRepository = mock(ConfigurationRepository.class); - Configuration configuration = mock(Configuration.class); - - when(configurationRepository.getConfiguration()).thenReturn(configuration); - when(configuration.get(GitlabServerPullRequestDecorator.PULLREQUEST_GITLAB_URL)).thenReturn(Optional.of(wireMockRule.baseUrl())); - when(configuration.get(GitlabServerPullRequestDecorator.PULLREQUEST_GITLAB_TOKEN)).thenReturn(Optional.of("token")); - when(configuration.get(GitlabServerPullRequestDecorator.PULLREQUEST_GITLAB_REPOSITORY_SLUG)).thenReturn(Optional.of(repositorySlug)); - when(configuration.get(PullRequestBuildStatusDecorator.PULL_REQUEST_COMMENT_SUMMARY_ENABLED)).thenReturn(Optional.of("true")); - when(configuration.get(PullRequestBuildStatusDecorator.PULL_REQUEST_DELETE_COMMENTS_ENABLED)).thenReturn(Optional.of("true")); - when(configuration.get(PullRequestBuildStatusDecorator.PULL_REQUEST_FILE_COMMENT_ENABLED)).thenReturn(Optional.of("true")); - QualityGate.Condition coverage = mock(QualityGate.Condition.class); when(coverage.getStatus()).thenReturn(QualityGate.EvaluationStatus.OK); when(coverage.getValue()).thenReturn("10"); + ProjectAlmSettingDto projectAlmSettingDto = mock(ProjectAlmSettingDto.class); + AlmSettingDto almSettingDto = mock(AlmSettingDto.class); + when(almSettingDto.getPersonalAccessToken()).thenReturn("token"); + AnalysisDetails analysisDetails = mock(AnalysisDetails.class); + when(analysisDetails.getScannerProperty(eq(GitlabServerPullRequestDecorator.PULLREQUEST_GITLAB_INSTANCE_URL))) + .thenReturn(Optional.of(wireMockRule.baseUrl()+"/api/v4")); + when(analysisDetails + .getScannerProperty(eq(GitlabServerPullRequestDecorator.PULLREQUEST_GITLAB_PROJECT_ID))) + .thenReturn(Optional.of(repositorySlug)); when(analysisDetails.getAnalysisProjectKey()).thenReturn(projectKey); when(analysisDetails.getBranchName()).thenReturn(branchName); when(analysisDetails.getCommitSha()).thenReturn(commitSHA); @@ -146,6 +162,7 @@ public void decorateQualityGateStatus() { urlEncode("position[base_sha]") + "=d6a420d043dfe85e7c240fd136fc6e197998b10a&" + urlEncode("position[start_sha]") + "=d6a420d043dfe85e7c240fd136fc6e197998b10a&" + urlEncode("position[head_sha]") + "=" + commitSHA + "&" + + urlEncode("position[old_path]") + "=" + urlEncode(filePath) + "&" + urlEncode("position[new_path]") + "=" + urlEncode(filePath) + "&" + urlEncode("position[new_line]") + "=" + lineNumber + "&" + urlEncode("position[position_type]") + "=text")) @@ -153,17 +170,18 @@ public void decorateQualityGateStatus() { Server server = mock(Server.class); when(server.getPublicRootUrl()).thenReturn(sonarRootUrl); - GitlabServerPullRequestDecorator pullRequestDecorator = new GitlabServerPullRequestDecorator(server, configurationRepository, scmInfoRepository); + GitlabServerPullRequestDecorator pullRequestDecorator = + new GitlabServerPullRequestDecorator(server, scmInfoRepository); - pullRequestDecorator.decorateQualityGateStatus(analysisDetails); + pullRequestDecorator.decorateQualityGateStatus(analysisDetails, almSettingDto, projectAlmSettingDto); } - private String urlEncode(String value) { + private static String urlEncode(String value) { try { return URLEncoder.encode(value, StandardCharsets.UTF_8.name()); } catch (UnsupportedEncodingException e) { throw new Error("No support for UTF-8!", e); } } -} \ No newline at end of file +} diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityBranchConfigurationLoaderTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityBranchConfigurationLoaderTest.java index f484f34e5..b237b29fa 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityBranchConfigurationLoaderTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityBranchConfigurationLoaderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Michael Clarke + * 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 @@ -23,8 +23,8 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.sonar.api.CoreProperties; import org.sonar.api.utils.MessageException; +import org.sonar.api.utils.System2; import org.sonar.scanner.scan.branch.BranchConfiguration; import org.sonar.scanner.scan.branch.BranchInfo; import org.sonar.scanner.scan.branch.BranchType; @@ -34,13 +34,11 @@ import java.util.HashMap; import java.util.Map; -import java.util.function.Supplier; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -49,7 +47,6 @@ */ public class CommunityBranchConfigurationLoaderTest { - private final Supplier> supplier = mock(Supplier.class); private final ExpectedException expectedException = ExpectedException.none(); @Rule @@ -59,7 +56,7 @@ public ExpectedException expectedException() { @Test public void testExceptionWhenNoExistingBranchAndBranchParamsPresent() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); ProjectBranches branchInfo = mock(ProjectBranches.class); when(branchInfo.isEmpty()).thenReturn(true); @@ -75,7 +72,7 @@ public void testExceptionWhenNoExistingBranchAndBranchParamsPresent() { @Test public void testDefaultConfigWhenNoExistingBranchAndBranchNameParamMaster() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); ProjectBranches branchInfo = mock(ProjectBranches.class); when(branchInfo.isEmpty()).thenReturn(true); @@ -88,7 +85,7 @@ public void testDefaultConfigWhenNoExistingBranchAndBranchNameParamMaster() { @Test public void testErrorWhenNoExistingBranchAndBranchTargetMasterButNoSourceBranch() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); ProjectBranches branchInfo = mock(ProjectBranches.class); when(branchInfo.isEmpty()).thenReturn(true); @@ -107,7 +104,7 @@ public void testErrorWhenNoExistingBranchAndBranchTargetMasterButNoSourceBranch( @Test public void testDefaultConfigWhenNoExistingBranchAndBranchParamsAllMaster() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); ProjectBranches branchInfo = mock(ProjectBranches.class); when(branchInfo.isEmpty()).thenReturn(true); @@ -121,7 +118,7 @@ public void testDefaultConfigWhenNoExistingBranchAndBranchParamsAllMaster() { @Test public void testExceptionWhenNoExistingBranchAndPullRequestAndBranchParametersPresent() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); ProjectBranches branchInfo = mock(ProjectBranches.class); when(branchInfo.isEmpty()).thenReturn(true); @@ -140,7 +137,7 @@ public void testExceptionWhenNoExistingBranchAndPullRequestAndBranchParametersPr @Test public void testDefaultBranchInfoWhenNoBranchParametersSpecifiedAndNoBranchesExist() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); ProjectBranches branchInfo = mock(ProjectBranches.class); when(branchInfo.isEmpty()).thenReturn(true); @@ -155,21 +152,21 @@ public void testDefaultBranchInfoWhenNoBranchParametersSpecifiedAndNoBranchesExi @Test public void testDefaultBranchInfoWhenNoParametersSpecified() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); assertEquals(DefaultBranchConfiguration.class, testCase.load(new HashMap<>(), mock(ProjectBranches.class), mock(ProjectPullRequests.class)).getClass()); } @Test public void testValidBranchInfoWhenAllBranchParametersSpecified() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); Map parameters = new HashMap<>(); parameters.put("sonar.branch.name", "feature/shortLivedFeatureBranch"); parameters.put("sonar.branch.target", "master"); BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); when(mockTargetBranchInfo.name()).thenReturn("masterBranchInfo"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); + when(mockTargetBranchInfo.type()).thenReturn(BranchType.BRANCH); ProjectBranches projectBranches = mock(ProjectBranches.class); when(projectBranches.get("master")).thenReturn(mockTargetBranchInfo); @@ -178,8 +175,8 @@ public void testValidBranchInfoWhenAllBranchParametersSpecified() { assertEquals("master", result.targetBranchName()); assertEquals("feature/shortLivedFeatureBranch", result.branchName()); - assertEquals("masterBranchInfo", result.longLivingSonarReferenceBranch()); - assertTrue(result.isShortOrPullRequest()); + assertEquals("masterBranchInfo", result.referenceBranchName()); + assertFalse(result.isPullRequest()); expectedException .expectMessage(IsEqual.equalTo("Only a branch of type PULL_REQUEST can have a Pull Request key")); @@ -190,13 +187,13 @@ public void testValidBranchInfoWhenAllBranchParametersSpecified() { @Test public void testValidBranchInfoWhenOnlySourceBranchSpecifiedAndMasterExists() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); Map parameters = new HashMap<>(); parameters.put("sonar.branch.name", "feature/shortLivedBranch"); BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); when(mockTargetBranchInfo.name()).thenReturn("defaultBranchInfo"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); + when(mockTargetBranchInfo.type()).thenReturn(BranchType.BRANCH); ProjectBranches projectBranches = mock(ProjectBranches.class); when(projectBranches.get("masterxxx")).thenReturn(mockTargetBranchInfo); @@ -206,20 +203,20 @@ public void testValidBranchInfoWhenOnlySourceBranchSpecifiedAndMasterExists() { assertEquals("masterxxx", result.targetBranchName()); assertEquals("feature/shortLivedBranch", result.branchName()); - assertEquals("defaultBranchInfo", result.longLivingSonarReferenceBranch()); - assertTrue(result.isShortOrPullRequest()); + assertEquals("defaultBranchInfo", result.referenceBranchName()); + assertFalse(result.isPullRequest()); } @Test public void testValidBranchInfoWhenOnlySourceBranchSpecifiedAndMasterExists2() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); Map parameters = new HashMap<>(); parameters.put("sonar.branch.name", "feature/shortLivedBranch"); parameters.put("sonar.branch.target", ""); BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); when(mockTargetBranchInfo.name()).thenReturn("defaultBranchInfo"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); + when(mockTargetBranchInfo.type()).thenReturn(BranchType.BRANCH); ProjectBranches projectBranches = mock(ProjectBranches.class); when(projectBranches.get("masterxxx")).thenReturn(mockTargetBranchInfo); @@ -229,19 +226,19 @@ public void testValidBranchInfoWhenOnlySourceBranchSpecifiedAndMasterExists2() { assertEquals("masterxxx", result.targetBranchName()); assertEquals("feature/shortLivedBranch", result.branchName()); - assertEquals("defaultBranchInfo", result.longLivingSonarReferenceBranch()); - assertTrue(result.isShortOrPullRequest()); + assertEquals("defaultBranchInfo", result.referenceBranchName()); + assertFalse(result.isPullRequest()); } @Test public void testExceptionWhenOnlySourceBranchSpecifiedAndNoMasterExists() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); Map parameters = new HashMap<>(); parameters.put("sonar.branch.name", "feature/shortLivedBranch"); BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); when(mockTargetBranchInfo.name()).thenReturn("defaultBranchInfo"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); + when(mockTargetBranchInfo.type()).thenReturn(BranchType.BRANCH); ProjectBranches projectBranches = mock(ProjectBranches.class); @@ -252,37 +249,9 @@ public void testExceptionWhenOnlySourceBranchSpecifiedAndNoMasterExists() { } - - @Test - public void testShortLivedBranchInvalidTarget() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "feature/shortLivedBranch"); - parameters.put("sonar.branch.target", "feature/otherShortLivedBranch"); - - BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); - when(mockTargetBranchInfo.name()).thenReturn("feature/otherShortLivedBranch"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.SHORT); - - ProjectBranches projectBranches = mock(ProjectBranches.class); - when(projectBranches.get("feature/otherShortLivedBranch")).thenReturn(mockTargetBranchInfo); - - expectedException.expect(MessageException.class); - expectedException.expectMessage(IsEqual.equalTo("Could not target requested branch")); - expectedException.expectCause(new CustomMatcher("Cause checker") { - @Override - public boolean matches(Object item) { - return item instanceof IllegalStateException && - ((IllegalStateException) item).getMessage().equals("Expected branch type of LONG but got SHORT"); - } - }); - - testCase.load(parameters, projectBranches, mock(ProjectPullRequests.class)); - } - @Test public void testUnknownTargetBranch() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); Map parameters = new HashMap<>(); parameters.put("sonar.branch.name", "feature/shortLivedBranch"); parameters.put("sonar.branch.target", "feature/otherShortLivedBranch"); @@ -302,73 +271,15 @@ public boolean matches(Object item) { testCase.load(parameters, projectBranches, mock(ProjectPullRequests.class)); } - @Test - public void testShortLivedBranchExistingSourceAllParametersCorrect() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "feature/shortLivedBranch"); - parameters.put("sonar.branch.target", "longLivedBranch"); - - BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); - when(mockTargetBranchInfo.name()).thenReturn("longLivedBranch"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); - - BranchInfo mockSourceBranchInfo = mock(BranchInfo.class); - when(mockSourceBranchInfo.name()).thenReturn("shortLivedBranch"); - when(mockSourceBranchInfo.type()).thenReturn(BranchType.SHORT); - - - ProjectBranches projectBranches = mock(ProjectBranches.class); - when(projectBranches.get("longLivedBranch")).thenReturn(mockTargetBranchInfo); - when(projectBranches.get("feature/shortLivedBranch")).thenReturn(mockSourceBranchInfo); - - BranchConfiguration result = testCase.load(parameters, projectBranches, mock(ProjectPullRequests.class)); - - assertEquals("longLivedBranch", result.targetBranchName()); - assertEquals("feature/shortLivedBranch", result.branchName()); - assertEquals("longLivedBranch", result.longLivingSonarReferenceBranch()); - assertTrue(result.isShortOrPullRequest()); - } - - @Test - public void testExistingShortLivedBranchOnlySourceParametersRetargetMaster() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "feature/shortLivedBranch"); - - BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); - when(mockTargetBranchInfo.name()).thenReturn("longLivedBranch"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); - - BranchInfo mockSourceBranchInfo = mock(BranchInfo.class); - when(mockSourceBranchInfo.name()).thenReturn("shortLivedBranch"); - when(mockSourceBranchInfo.branchTargetName()).thenReturn("otherLongLivedBranch"); - when(mockSourceBranchInfo.type()).thenReturn(BranchType.SHORT); - - - ProjectBranches projectBranches = mock(ProjectBranches.class); - when(projectBranches.get("master")).thenReturn(mockTargetBranchInfo); - when(projectBranches.get("feature/shortLivedBranch")).thenReturn(mockSourceBranchInfo); - when(projectBranches.defaultBranchName()).thenReturn("master"); - - BranchConfiguration result = testCase.load(parameters, projectBranches, mock(ProjectPullRequests.class)); - - assertEquals("master", result.targetBranchName()); - assertEquals("feature/shortLivedBranch", result.branchName()); - assertEquals("master", result.longLivingSonarReferenceBranch()); - assertTrue(result.isShortOrPullRequest()); - } - - @Test - public void testExistingLongLivedBranchOnlySourceParameters() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + public void testExistingBranchOnlySourceParameters() { + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); Map parameters = new HashMap<>(); parameters.put("sonar.branch.name", "longLivedBranch"); BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); when(mockTargetBranchInfo.name()).thenReturn("longLivedBranch"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); + when(mockTargetBranchInfo.type()).thenReturn(BranchType.BRANCH); ProjectBranches projectBranches = mock(ProjectBranches.class); @@ -378,13 +289,13 @@ public void testExistingLongLivedBranchOnlySourceParameters() { assertNull(result.targetBranchName()); assertEquals("longLivedBranch", result.branchName()); - assertEquals("longLivedBranch", result.longLivingSonarReferenceBranch()); - assertFalse(result.isShortOrPullRequest()); + assertEquals("longLivedBranch", result.referenceBranchName()); + assertFalse(result.isPullRequest()); } @Test public void testPullRequestAllParameters() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); Map parameters = new HashMap<>(); parameters.put("sonar.pullrequest.branch", "feature/sourceBranch"); parameters.put("sonar.pullrequest.base", "target"); @@ -392,7 +303,7 @@ public void testPullRequestAllParameters() { BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); when(mockTargetBranchInfo.name()).thenReturn("targetInfo"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); + when(mockTargetBranchInfo.type()).thenReturn(BranchType.BRANCH); ProjectBranches projectBranches = mock(ProjectBranches.class); @@ -402,22 +313,22 @@ public void testPullRequestAllParameters() { assertEquals("target", result.targetBranchName()); assertEquals("feature/sourceBranch", result.branchName()); - assertEquals("target", result.longLivingSonarReferenceBranch()); - assertTrue(result.isShortOrPullRequest()); + assertEquals("target", result.referenceBranchName()); + assertTrue(result.isPullRequest()); assertEquals("pr-key", result.pullRequestKey()); } @Test public void testPullRequestMandatoryParameters() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); Map parameters = new HashMap<>(); parameters.put("sonar.pullrequest.branch", "feature/sourceBranch"); parameters.put("sonar.pullrequest.key", "pr-key"); BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); when(mockTargetBranchInfo.name()).thenReturn("masterInfo"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); + when(mockTargetBranchInfo.type()).thenReturn(BranchType.BRANCH); ProjectBranches projectBranches = mock(ProjectBranches.class); @@ -428,13 +339,13 @@ public void testPullRequestMandatoryParameters() { assertEquals("master", result.targetBranchName()); assertEquals("feature/sourceBranch", result.branchName()); - assertEquals("master", result.longLivingSonarReferenceBranch()); - assertTrue(result.isShortOrPullRequest()); + assertEquals("master", result.referenceBranchName()); + assertTrue(result.isPullRequest()); } @Test public void testPullRequestMandatoryParameters2() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); Map parameters = new HashMap<>(); parameters.put("sonar.pullrequest.branch", "feature/sourceBranch"); parameters.put("sonar.pullrequest.key", "pr-key"); @@ -442,7 +353,7 @@ public void testPullRequestMandatoryParameters2() { BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); when(mockTargetBranchInfo.name()).thenReturn("masterInfo"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); + when(mockTargetBranchInfo.type()).thenReturn(BranchType.BRANCH); ProjectBranches projectBranches = mock(ProjectBranches.class); @@ -453,14 +364,14 @@ public void testPullRequestMandatoryParameters2() { assertEquals("master", result.targetBranchName()); assertEquals("feature/sourceBranch", result.branchName()); - assertEquals("master", result.longLivingSonarReferenceBranch()); - assertTrue(result.isShortOrPullRequest()); + assertEquals("master", result.referenceBranchName()); + assertTrue(result.isPullRequest()); } @Test public void testPullRequestNoSuchTarget() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); + CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(System2.INSTANCE); Map parameters = new HashMap<>(); parameters.put("sonar.pullrequest.branch", "feature/sourceBranch"); parameters.put("sonar.pullrequest.base", "missingTarget"); @@ -482,518 +393,4 @@ public boolean matches(Object item) { testCase.load(parameters, projectBranches, mock(ProjectPullRequests.class)); } - @Test - public void testComputeBranchType() { - BranchInfo branchInfo = mock(BranchInfo.class); - when(branchInfo.type()).thenReturn(BranchType.LONG); - - ProjectBranches projectBranches = mock(ProjectBranches.class); - when(projectBranches.defaultBranchName()).thenReturn("master"); - when(projectBranches.get(eq("master"))).thenReturn(branchInfo); - - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "release/1.2"); - parameters.put(CoreProperties.LONG_LIVED_BRANCHES_REGEX, "(master|release/.+)"); - - assertEquals(BranchType.LONG, new CommunityBranchConfigurationLoader() - .load(parameters, projectBranches, mock(ProjectPullRequests.class)).branchType()); - - parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "master-dummy"); - - assertEquals(BranchType.SHORT, new CommunityBranchConfigurationLoader() - .load(parameters, projectBranches, mock(ProjectPullRequests.class)).branchType()); - - } - - - @Test - public void testExceptionWhenNoExistingBranchAndBranchParamsPresentPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - ProjectBranches branchInfo = mock(ProjectBranches.class); - when(branchInfo.isEmpty()).thenReturn(true); - - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "dummy"); - - expectedException.expect(MessageException.class); - expectedException.expectMessage(IsEqual.equalTo( - "No branches currently exist in this project. Please scan the main branch without passing any branch parameters.")); - - testCase.load(parameters, supplier, branchInfo, mock(ProjectPullRequests.class)); - } - - @Test - public void testDefaultConfigWhenNoExistingBranchAndBranchNameParamMasterPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - ProjectBranches branchInfo = mock(ProjectBranches.class); - when(branchInfo.isEmpty()).thenReturn(true); - - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "master"); - - assertEquals(DefaultBranchConfiguration.class, - testCase.load(parameters, supplier, branchInfo, mock(ProjectPullRequests.class)).getClass()); - } - - @Test - public void testErrorWhenNoExistingBranchAndBranchTargetMasterButNoSourceBranchPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - ProjectBranches branchInfo = mock(ProjectBranches.class); - when(branchInfo.isEmpty()).thenReturn(true); - - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.source", null); - parameters.put("sonar.branch.target", "master"); - - - expectedException.expect(MessageException.class); - expectedException.expectMessage(IsEqual.equalTo( - "No branches currently exist in this project. Please scan the main branch without passing any branch parameters.")); - - testCase.load(parameters, supplier, branchInfo, mock(ProjectPullRequests.class)); - } - - - @Test - public void testDefaultConfigWhenNoExistingBranchAndBranchParamsAllMasterPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - ProjectBranches branchInfo = mock(ProjectBranches.class); - when(branchInfo.isEmpty()).thenReturn(true); - - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "master"); - parameters.put("sonar.branch.target", "master"); - - assertEquals(DefaultBranchConfiguration.class, - testCase.load(parameters, supplier, branchInfo, mock(ProjectPullRequests.class)).getClass()); - } - - @Test - public void testExceptionWhenNoExistingBranchAndPullRequestAndBranchParametersPresentPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - ProjectBranches branchInfo = mock(ProjectBranches.class); - when(branchInfo.isEmpty()).thenReturn(true); - - - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "dummy"); - parameters.put("sonar.pullrequest.branch", "dummy2"); - - - expectedException.expect(MessageException.class); - expectedException.expectMessage(IsEqual.equalTo( - "No branches currently exist in this project. Please scan the main branch without passing any branch parameters.")); - - testCase.load(parameters, supplier, branchInfo, mock(ProjectPullRequests.class)); - } - - @Test - public void testDefaultBranchInfoWhenNoBranchParametersSpecifiedAndNoBranchesExistPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - - ProjectBranches branchInfo = mock(ProjectBranches.class); - when(branchInfo.isEmpty()).thenReturn(true); - - Map parameters = new HashMap<>(); - parameters.put("dummy", "dummy"); - - - assertEquals(DefaultBranchConfiguration.class, - testCase.load(parameters, supplier, branchInfo, mock(ProjectPullRequests.class)).getClass()); - } - - @Test - public void testDefaultBranchInfoWhenNoParametersSpecifiedPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - assertEquals(DefaultBranchConfiguration.class, - testCase.load(new HashMap<>(), supplier, mock(ProjectBranches.class), - mock(ProjectPullRequests.class)).getClass()); - } - - @Test - public void testValidBranchInfoWhenAllBranchParametersSpecifiedPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "feature/shortLivedFeatureBranch"); - parameters.put("sonar.branch.target", "master"); - - BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); - when(mockTargetBranchInfo.name()).thenReturn("masterBranchInfo"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); - - ProjectBranches projectBranches = mock(ProjectBranches.class); - when(projectBranches.get("master")).thenReturn(mockTargetBranchInfo); - - when(supplier.get()).thenReturn(new HashMap<>()); - - BranchConfiguration result = - testCase.load(parameters, supplier, projectBranches, mock(ProjectPullRequests.class)); - - assertEquals("master", result.targetBranchName()); - assertEquals("feature/shortLivedFeatureBranch", result.branchName()); - assertEquals("masterBranchInfo", result.longLivingSonarReferenceBranch()); - assertTrue(result.isShortOrPullRequest()); - - expectedException - .expectMessage(IsEqual.equalTo("Only a branch of type PULL_REQUEST can have a Pull Request key")); - expectedException.expect(IllegalStateException.class); - - result.pullRequestKey(); - } - - @Test - public void testValidBranchInfoWhenOnlySourceBranchSpecifiedAndMasterExistsPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "feature/shortLivedBranch"); - - BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); - when(mockTargetBranchInfo.name()).thenReturn("defaultBranchInfo"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); - - ProjectBranches projectBranches = mock(ProjectBranches.class); - when(projectBranches.get("masterxxx")).thenReturn(mockTargetBranchInfo); - when(projectBranches.defaultBranchName()).thenReturn("masterxxx"); - - when(supplier.get()).thenReturn(new HashMap<>()); - - BranchConfiguration result = - testCase.load(parameters, supplier, projectBranches, mock(ProjectPullRequests.class)); - - assertEquals("masterxxx", result.targetBranchName()); - assertEquals("feature/shortLivedBranch", result.branchName()); - assertEquals("defaultBranchInfo", result.longLivingSonarReferenceBranch()); - assertTrue(result.isShortOrPullRequest()); - } - - @Test - public void testValidBranchInfoWhenOnlySourceBranchSpecifiedAndMasterExists2Pre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "feature/shortLivedBranch"); - parameters.put("sonar.branch.target", ""); - - BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); - when(mockTargetBranchInfo.name()).thenReturn("defaultBranchInfo"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); - - ProjectBranches projectBranches = mock(ProjectBranches.class); - when(projectBranches.get("masterxxx")).thenReturn(mockTargetBranchInfo); - when(projectBranches.defaultBranchName()).thenReturn("masterxxx"); - - when(supplier.get()).thenReturn(new HashMap<>()); - - BranchConfiguration result = - testCase.load(parameters, supplier, projectBranches, mock(ProjectPullRequests.class)); - - assertEquals("masterxxx", result.targetBranchName()); - assertEquals("feature/shortLivedBranch", result.branchName()); - assertEquals("defaultBranchInfo", result.longLivingSonarReferenceBranch()); - assertTrue(result.isShortOrPullRequest()); - } - - @Test - public void testExceptionWhenOnlySourceBranchSpecifiedAndNoMasterExistsPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "feature/shortLivedBranch"); - - BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); - when(mockTargetBranchInfo.name()).thenReturn("defaultBranchInfo"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); - - ProjectBranches projectBranches = mock(ProjectBranches.class); - - when(supplier.get()).thenReturn(new HashMap<>()); - - expectedException.expect(MessageException.class); - expectedException.expectMessage(IsEqual.equalTo("Could not target requested branch")); - - testCase.load(parameters, supplier, projectBranches, mock(ProjectPullRequests.class)); - - } - - - @Test - public void testShortLivedBranchInvalidTargetPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "feature/shortLivedBranch"); - parameters.put("sonar.branch.target", "feature/otherShortLivedBranch"); - - BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); - when(mockTargetBranchInfo.name()).thenReturn("feature/otherShortLivedBranch"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.SHORT); - - ProjectBranches projectBranches = mock(ProjectBranches.class); - when(projectBranches.get("feature/otherShortLivedBranch")).thenReturn(mockTargetBranchInfo); - - when(supplier.get()).thenReturn(new HashMap<>()); - - expectedException.expect(MessageException.class); - expectedException.expectMessage(IsEqual.equalTo("Could not target requested branch")); - expectedException.expectCause(new CustomMatcher("Cause checker") { - @Override - public boolean matches(Object item) { - return item instanceof IllegalStateException && - ((IllegalStateException) item).getMessage().equals("Expected branch type of LONG but got SHORT"); - } - }); - - testCase.load(parameters, supplier, projectBranches, mock(ProjectPullRequests.class)); - } - - @Test - public void testUnknownTargetBranchPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "feature/shortLivedBranch"); - parameters.put("sonar.branch.target", "feature/otherShortLivedBranch"); - - ProjectBranches projectBranches = mock(ProjectBranches.class); - - when(supplier.get()).thenReturn(new HashMap<>()); - - expectedException.expect(MessageException.class); - expectedException.expectMessage(IsEqual.equalTo("Could not target requested branch")); - expectedException.expectCause(new CustomMatcher("Cause checker") { - @Override - public boolean matches(Object item) { - return item instanceof IllegalStateException && ((IllegalStateException) item).getMessage() - .equals("Target branch 'feature/otherShortLivedBranch' does not exist"); - } - }); - - testCase.load(parameters, supplier, projectBranches, mock(ProjectPullRequests.class)); - } - - - @Test - public void testShortLivedBranchExistingSourceAllParametersCorrectPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "feature/shortLivedBranch"); - parameters.put("sonar.branch.target", "longLivedBranch"); - - BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); - when(mockTargetBranchInfo.name()).thenReturn("longLivedBranch"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); - - BranchInfo mockSourceBranchInfo = mock(BranchInfo.class); - when(mockSourceBranchInfo.name()).thenReturn("shortLivedBranch"); - when(mockSourceBranchInfo.type()).thenReturn(BranchType.SHORT); - - - ProjectBranches projectBranches = mock(ProjectBranches.class); - when(projectBranches.get("longLivedBranch")).thenReturn(mockTargetBranchInfo); - when(projectBranches.get("feature/shortLivedBranch")).thenReturn(mockSourceBranchInfo); - - when(supplier.get()).thenReturn(new HashMap<>()); - - BranchConfiguration result = - testCase.load(parameters, supplier, projectBranches, mock(ProjectPullRequests.class)); - - assertEquals("longLivedBranch", result.targetBranchName()); - assertEquals("feature/shortLivedBranch", result.branchName()); - assertEquals("longLivedBranch", result.longLivingSonarReferenceBranch()); - assertTrue(result.isShortOrPullRequest()); - } - - @Test - public void testExistingShortLivedBranchOnlySourceParametersRetargetMasterPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "feature/shortLivedBranch"); - - BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); - when(mockTargetBranchInfo.name()).thenReturn("longLivedBranch"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); - - BranchInfo mockSourceBranchInfo = mock(BranchInfo.class); - when(mockSourceBranchInfo.name()).thenReturn("shortLivedBranch"); - when(mockSourceBranchInfo.branchTargetName()).thenReturn("otherLongLivedBranch"); - when(mockSourceBranchInfo.type()).thenReturn(BranchType.SHORT); - - - ProjectBranches projectBranches = mock(ProjectBranches.class); - when(projectBranches.get("master")).thenReturn(mockTargetBranchInfo); - when(projectBranches.get("feature/shortLivedBranch")).thenReturn(mockSourceBranchInfo); - when(projectBranches.defaultBranchName()).thenReturn("master"); - - when(supplier.get()).thenReturn(new HashMap<>()); - - BranchConfiguration result = - testCase.load(parameters, supplier, projectBranches, mock(ProjectPullRequests.class)); - - assertEquals("master", result.targetBranchName()); - assertEquals("feature/shortLivedBranch", result.branchName()); - assertEquals("master", result.longLivingSonarReferenceBranch()); - assertTrue(result.isShortOrPullRequest()); - } - - @Test - public void testExistingLongLivedBranchOnlySourceParametersPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "longLivedBranch"); - - BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); - when(mockTargetBranchInfo.name()).thenReturn("longLivedBranch"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); - - - ProjectBranches projectBranches = mock(ProjectBranches.class); - when(projectBranches.get("longLivedBranch")).thenReturn(mockTargetBranchInfo); - - when(supplier.get()).thenReturn(new HashMap<>()); - - BranchConfiguration result = - testCase.load(parameters, supplier, projectBranches, mock(ProjectPullRequests.class)); - - assertNull(result.targetBranchName()); - assertEquals("longLivedBranch", result.branchName()); - assertEquals("longLivedBranch", result.longLivingSonarReferenceBranch()); - assertFalse(result.isShortOrPullRequest()); - } - - @Test - public void testPullRequestAllParametersPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.pullrequest.branch", "feature/sourceBranch"); - parameters.put("sonar.pullrequest.base", "target"); - parameters.put("sonar.pullrequest.key", "pr-key"); - - BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); - when(mockTargetBranchInfo.name()).thenReturn("targetInfo"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); - - - ProjectBranches projectBranches = mock(ProjectBranches.class); - when(projectBranches.get("target")).thenReturn(mockTargetBranchInfo); - - when(supplier.get()).thenReturn(new HashMap<>()); - - BranchConfiguration result = - testCase.load(parameters, supplier, projectBranches, mock(ProjectPullRequests.class)); - - assertEquals("target", result.targetBranchName()); - assertEquals("feature/sourceBranch", result.branchName()); - assertEquals("target", result.longLivingSonarReferenceBranch()); - assertTrue(result.isShortOrPullRequest()); - assertEquals("pr-key", result.pullRequestKey()); - } - - - @Test - public void testPullRequestMandatoryParametersPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.pullrequest.branch", "feature/sourceBranch"); - parameters.put("sonar.pullrequest.key", "pr-key"); - - BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); - when(mockTargetBranchInfo.name()).thenReturn("masterInfo"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); - - - ProjectBranches projectBranches = mock(ProjectBranches.class); - when(projectBranches.get("master")).thenReturn(mockTargetBranchInfo); - when(projectBranches.defaultBranchName()).thenReturn("master"); - - when(supplier.get()).thenReturn(new HashMap<>()); - - BranchConfiguration result = - testCase.load(parameters, supplier, projectBranches, mock(ProjectPullRequests.class)); - - assertEquals("master", result.targetBranchName()); - assertEquals("feature/sourceBranch", result.branchName()); - assertEquals("master", result.longLivingSonarReferenceBranch()); - assertTrue(result.isShortOrPullRequest()); - } - - @Test - public void testPullRequestMandatoryParameters2Pre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.pullrequest.branch", "feature/sourceBranch"); - parameters.put("sonar.pullrequest.key", "pr-key"); - parameters.put("sonar.pullrequest.base", ""); - - BranchInfo mockTargetBranchInfo = mock(BranchInfo.class); - when(mockTargetBranchInfo.name()).thenReturn("masterInfo"); - when(mockTargetBranchInfo.type()).thenReturn(BranchType.LONG); - - - ProjectBranches projectBranches = mock(ProjectBranches.class); - when(projectBranches.get("master")).thenReturn(mockTargetBranchInfo); - when(projectBranches.defaultBranchName()).thenReturn("master"); - - when(supplier.get()).thenReturn(new HashMap<>()); - - BranchConfiguration result = - testCase.load(parameters, supplier, projectBranches, mock(ProjectPullRequests.class)); - - assertEquals("master", result.targetBranchName()); - assertEquals("feature/sourceBranch", result.branchName()); - assertEquals("master", result.longLivingSonarReferenceBranch()); - assertTrue(result.isShortOrPullRequest()); - } - - - @Test - public void testPullRequestNoSuchTargetPre79() { - CommunityBranchConfigurationLoader testCase = new CommunityBranchConfigurationLoader(); - Map parameters = new HashMap<>(); - parameters.put("sonar.pullrequest.branch", "feature/sourceBranch"); - parameters.put("sonar.pullrequest.base", "missingTarget"); - parameters.put("sonar.pullrequest.key", "pr-key"); - - - ProjectBranches projectBranches = mock(ProjectBranches.class); - - when(supplier.get()).thenReturn(new HashMap<>()); - - expectedException.expect(MessageException.class); - expectedException.expectMessage(IsEqual.equalTo("Could not target requested branch")); - expectedException.expectCause(new CustomMatcher("Cause checker") { - @Override - public boolean matches(Object item) { - return item instanceof IllegalStateException && ((IllegalStateException) item).getMessage() - .equals("Target branch 'missingTarget' does not exist"); - } - }); - - testCase.load(parameters, supplier, projectBranches, mock(ProjectPullRequests.class)); - } - - @Test - public void testComputeBranchTypePre79() { - Map settings = new HashMap<>(); - settings.put(CoreProperties.LONG_LIVED_BRANCHES_REGEX, "(master|release/.+)"); - when(supplier.get()).thenReturn(settings); - - BranchInfo branchInfo = mock(BranchInfo.class); - when(branchInfo.type()).thenReturn(BranchType.LONG); - - ProjectBranches projectBranches = mock(ProjectBranches.class); - when(projectBranches.defaultBranchName()).thenReturn("master"); - when(projectBranches.get(eq("master"))).thenReturn(branchInfo); - - Map parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "release/1.2"); - - assertEquals(BranchType.LONG, new CommunityBranchConfigurationLoader() - .load(parameters, supplier, projectBranches, mock(ProjectPullRequests.class)).branchType()); - - parameters = new HashMap<>(); - parameters.put("sonar.branch.name", "master-dummy"); - - assertEquals(BranchType.SHORT, new CommunityBranchConfigurationLoader() - .load(parameters, supplier, projectBranches, mock(ProjectPullRequests.class)).branchType()); - - } - - } diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityBranchParamsValidatorTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityBranchParamsValidatorTest.java index 7911a57ea..ec00da402 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityBranchParamsValidatorTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityBranchParamsValidatorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Michael Clarke + * 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 @@ -19,76 +19,22 @@ package com.github.mc1arke.sonarqube.plugin.scanner; import org.junit.Test; -import org.sonar.scanner.bootstrap.GlobalConfiguration; import java.util.ArrayList; import java.util.List; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; /** * @author Michael Clarke */ public class CommunityBranchParamsValidatorTest { - @Test - public void testNoMessagesOnNoParams() { - List messages = new ArrayList<>(); - - GlobalConfiguration globalConfiguration = mock(GlobalConfiguration.class); - new CommunityBranchParamsValidator(globalConfiguration).validate(messages, null); - assertTrue(messages.isEmpty()); - } - - @Test - public void testNoMessagesOnlyLegacyBranch() { - List messages = new ArrayList<>(); - - GlobalConfiguration globalConfiguration = mock(GlobalConfiguration.class); - new CommunityBranchParamsValidator(globalConfiguration).validate(messages, "legacy"); - assertTrue(messages.isEmpty()); - } - - @Test - public void testMessagesBranchParamsAndLegacyBranch() { - List messages = new ArrayList<>(); - - GlobalConfiguration globalConfiguration = mock(GlobalConfiguration.class); - when(globalConfiguration.hasKey(any())).thenReturn(true); - new CommunityBranchParamsValidator(globalConfiguration).validate(messages, "legacy"); - assertEquals(1, messages.size()); - assertEquals( - "The legacy 'sonar.branch' parameter cannot be used at the same time as 'sonar.branch.name' or 'sonar.branch.target'", - messages.get(0)); - } - - @Test - public void testMessagesBranchParamsAndLegacyBranch2() { - List messages = new ArrayList<>(); - - GlobalConfiguration globalConfiguration = mock(GlobalConfiguration.class); - when(globalConfiguration.hasKey(eq("sonar.branch.target"))).thenReturn(true); - when(globalConfiguration.hasKey(eq("sonar.branch"))).thenReturn(true); - new CommunityBranchParamsValidator(globalConfiguration).validate(messages, "legacy"); - assertEquals(1, messages.size()); - assertEquals( - "The legacy 'sonar.branch' parameter cannot be used at the same time as 'sonar.branch.name' or 'sonar.branch.target'", - messages.get(0)); - } - @Test public void testNoMessagesOnValidate() { List messages = new ArrayList<>(); - GlobalConfiguration globalConfiguration = mock(GlobalConfiguration.class); - when(globalConfiguration.hasKey(eq("sonar.branch.target"))).thenReturn(true); - when(globalConfiguration.hasKey(eq("sonar.branch"))).thenReturn(true); - new CommunityBranchParamsValidator(globalConfiguration).validate(messages); + new CommunityBranchParamsValidator().validate(messages); assertEquals(0, messages.size()); } } diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityProjectBranchesLoaderTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityProjectBranchesLoaderTest.java index 3ccb20256..70a5a71cd 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityProjectBranchesLoaderTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/scanner/CommunityProjectBranchesLoaderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Michael Clarke + * 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 @@ -79,7 +79,7 @@ public void testAllBranchesFromNonEmptyServerResponse() { List infos = new ArrayList<>(); for (int i = 0; i < 10; i++) { - infos.add(new BranchInfo("key" + i, BranchType.LONG, i == 1, "target" + i)); + infos.add(new BranchInfo("key" + i, BranchType.BRANCH, i == 1, "target" + i)); } StringReader stringReader = new StringReader( diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/scanner/ScannerWsClientWrapperTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/scanner/ScannerWsClientWrapperTest.java deleted file mode 100644 index b34e522c8..000000000 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/scanner/ScannerWsClientWrapperTest.java +++ /dev/null @@ -1,117 +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.scanner; - -import org.hamcrest.BaseMatcher; -import org.hamcrest.Description; -import org.hamcrest.core.IsEqual; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.scanner.bootstrap.ScannerWsClient; -import org.sonarqube.ws.client.WsRequest; - -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; - -public class ScannerWsClientWrapperTest { - - private final ExpectedException expectedException = ExpectedException.none(); - - @Rule - public ExpectedException expectedException() { - return expectedException; - } - - @Test - public void testRuntimeExceptionPropagated() { - ScannerWsClient scannerWsClient = mock(ScannerWsClient.class); - doThrow(new IllegalStateException("Whoops")).when(scannerWsClient).call(any()); - - expectedException.expectMessage(IsEqual.equalTo("Whoops")); - expectedException.expect(IllegalStateException.class); - - new ScannerWsClientWrapper(scannerWsClient).call(mock(WsRequest.class)); - } - - @Test - public void testErrorPropagated() { - ScannerWsClient scannerWsClient = mock(ScannerWsClient.class); - doThrow(new ClassFormatError("Whoops")).when(scannerWsClient).call(any()); - - expectedException.expectMessage(IsEqual.equalTo("Whoops")); - expectedException.expect(ClassFormatError.class); - - new ScannerWsClientWrapper(scannerWsClient).call(mock(WsRequest.class)); - } - - @Test - public void testCheckedExceptionWrapped() { - ScannerWsClient scannerWsClient = mock(ScannerWsClient.class); - doAnswer(i -> { - throw new IOException("Whoops"); - }).when(scannerWsClient).call(any()); - - expectedException.expectMessage(IsEqual.equalTo("Could not execute ScannerWsClient")); - expectedException.expect(IllegalStateException.class); - expectedException.expectCause(new BaseMatcher() { - @Override - public boolean matches(Object item) { - return item instanceof InvocationTargetException && - ((InvocationTargetException) item).getCause() instanceof IOException && - "Whoops".equals(((InvocationTargetException) item).getCause().getMessage()); - } - - @Override - public void describeTo(Description description) { - description.appendText("Exception checked"); - } - }); - - new ScannerWsClientWrapper(scannerWsClient).call(mock(WsRequest.class)); - } - - - @Test - public void testNonInvocationExceptionWrapped() { - expectedException.expectMessage(IsEqual.equalTo("Could not execute ScannerWsClient")); - expectedException.expect(IllegalStateException.class); - expectedException.expectCause(new BaseMatcher() { - @Override - public boolean matches(Object item) { - return item instanceof NoSuchMethodException && - "java.lang.Object.call(org.sonarqube.ws.client.WsRequest)" - .equals(((NoSuchMethodException) item).getMessage()); - } - - @Override - public void describeTo(Description description) { - description.appendText("NoSuchMethodException"); - } - }); - - Object scannerWsClient = new Object(); - new ScannerWsClientWrapper(scannerWsClient).call(mock(WsRequest.class)); - } -} diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/server/CommunityBranchSupportDelegateTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/CommunityBranchSupportDelegateTest.java index 37ff88e8f..e5e59a848 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/server/CommunityBranchSupportDelegateTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/server/CommunityBranchSupportDelegateTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Michael Clarke + * 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 @@ -63,32 +63,12 @@ public ExpectedException expectedException() { } @Test - public void testCreateComponentKeyShortBranch() { - Map params = new HashMap<>(); - params.put("branch", "feature/dummy"); - params.put("branchType", "SHORT"); - - CommunityComponentKey componentKey = - new CommunityBranchSupportDelegate(new SequenceUuidFactory(), mock(DbClient.class), mock(Clock.class)) - .createComponentKey("xxx", params); - assertEquals("xxx:BRANCH:feature/dummy", componentKey.getDbKey()); - assertEquals("xxx", componentKey.getKey()); - assertFalse(componentKey.getPullRequestKey().isPresent()); - assertFalse(componentKey.isMainBranch()); - assertTrue(componentKey.getBranch().isPresent()); - assertEquals("feature/dummy", componentKey.getBranch().get().getName()); - assertFalse(componentKey.getDeprecatedBranchName().isPresent()); - assertTrue(componentKey.getMainBranchComponentKey().isMainBranch()); - assertEquals(BranchType.SHORT, componentKey.getBranch().get().getType()); - } - - @Test - public void testCreateComponentKeyLongBranch() { + public void testCreateComponentKeyBranchType() { Map params = new HashMap<>(); params.put("branch", "release-1.1"); - params.put("branchType", "LONG"); + params.put("branchType", "BRANCH"); - CommunityComponentKey componentKey = + BranchSupport.ComponentKey componentKey = new CommunityBranchSupportDelegate(new SequenceUuidFactory(), mock(DbClient.class), mock(Clock.class)) .createComponentKey("yyy", params); @@ -98,9 +78,8 @@ public void testCreateComponentKeyLongBranch() { assertFalse(componentKey.isMainBranch()); assertTrue(componentKey.getBranch().isPresent()); assertEquals("release-1.1", componentKey.getBranch().get().getName()); - assertFalse(componentKey.getDeprecatedBranchName().isPresent()); assertTrue(componentKey.getMainBranchComponentKey().isMainBranch()); - assertEquals(BranchType.LONG, componentKey.getBranch().get().getType()); + assertEquals(BranchType.BRANCH, componentKey.getBranch().get().getType()); } @Test @@ -117,7 +96,6 @@ public void testCreateComponentKeyPullRequest() { assertEquals("pullrequestkey", componentKey.getPullRequestKey().get()); assertFalse(componentKey.isMainBranch()); assertFalse(componentKey.getBranch().isPresent()); - assertFalse(componentKey.getDeprecatedBranchName().isPresent()); assertTrue(componentKey.getMainBranchComponentKey().isMainBranch()); CommunityComponentKey mainBranchComponentKey = componentKey.getMainBranchComponentKey(); assertSame(mainBranchComponentKey, mainBranchComponentKey.getMainBranchComponentKey()); @@ -148,18 +126,6 @@ public void testCreateComponentKeyInvalidBranchTypeParameter() { .createComponentKey("xxx", params); } - @Test - public void testCreateComponentKeyInvalidBranchTypeParameter2() { - Map params = new HashMap<>(); - params.put("branchType", "PULL_REQUEST"); - - expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage(IsEqual.equalTo("Unsupported branch type 'PULL_REQUEST'")); - - new CommunityBranchSupportDelegate(new SequenceUuidFactory(), mock(DbClient.class), mock(Clock.class)) - .createComponentKey("xxx", params); - } - @Test public void testCreateBranchComponentComponentKeyComponentDtoKeyMismatch() { DbSession dbSession = mock(DbSession.class); @@ -174,7 +140,7 @@ public void testCreateBranchComponentComponentKeyComponentDtoKeyMismatch() { BranchDto branchDto = mock(BranchDto.class); when(branchDto.getUuid()).thenReturn("componentUuid"); - when(branchDto.getBranchType()).thenReturn(BranchType.SHORT); + when(branchDto.getBranchType()).thenReturn(BranchType.BRANCH); Clock clock = mock(Clock.class); when(clock.millis()).thenReturn(12345678901234L); @@ -182,7 +148,7 @@ public void testCreateBranchComponentComponentKeyComponentDtoKeyMismatch() { BranchSupport.ComponentKey componentKey = mock(BranchSupport.ComponentKey.class); when(componentKey.getKey()).thenReturn("componentKey"); when(componentKey.getDbKey()).thenReturn("dbKey"); - when(componentKey.getBranch()).thenReturn(Optional.of(new BranchSupport.Branch("dummy", BranchType.LONG))); + when(componentKey.getBranch()).thenReturn(Optional.of(new BranchSupport.Branch("dummy", BranchType.BRANCH))); when(componentKey.getPullRequestKey()).thenReturn(Optional.empty()); ComponentDao componentDao = spy(mock(ComponentDao.class)); @@ -222,7 +188,7 @@ public void testCreateBranchComponent() { BranchSupport.ComponentKey componentKey = mock(BranchSupport.ComponentKey.class); when(componentKey.getKey()).thenReturn("componentKey"); when(componentKey.getDbKey()).thenReturn("dbKey"); - when(componentKey.getBranch()).thenReturn(Optional.of(new BranchSupport.Branch("dummy", BranchType.LONG))); + when(componentKey.getBranch()).thenReturn(Optional.of(new BranchSupport.Branch("dummy", BranchType.BRANCH))); when(componentKey.getPullRequestKey()).thenReturn(Optional.empty()); ComponentDao componentDao = spy(mock(ComponentDao.class)); @@ -273,7 +239,7 @@ public void testCreateBranchComponentUseExistingDto() { BranchDto branchDto = mock(BranchDto.class); when(branchDto.getUuid()).thenReturn("componentUuid"); when(branchDto.getKey()).thenReturn("dummy"); - when(branchDto.getBranchType()).thenReturn(BranchType.LONG); + when(branchDto.getBranchType()).thenReturn(BranchType.BRANCH); Clock clock = mock(Clock.class); when(clock.millis()).thenReturn(1234567890123L); @@ -281,7 +247,7 @@ public void testCreateBranchComponentUseExistingDto() { BranchSupport.ComponentKey componentKey = mock(BranchSupport.ComponentKey.class); when(componentKey.getKey()).thenReturn("componentKey"); when(componentKey.getDbKey()).thenReturn("dbKey"); - when(componentKey.getBranch()).thenReturn(Optional.of(new BranchSupport.Branch("dummy", BranchType.LONG))); + when(componentKey.getBranch()).thenReturn(Optional.of(new BranchSupport.Branch("dummy", BranchType.BRANCH))); when(componentKey.getPullRequestKey()).thenReturn(Optional.empty()); ComponentDao componentDao = spy(mock(ComponentDao.class)); @@ -313,7 +279,7 @@ public void testCreateBranchComponentUseExistingDto2() { BranchDto branchDto = mock(BranchDto.class); when(branchDto.getUuid()).thenReturn("componentUuid"); when(branchDto.getKey()).thenReturn("dummy"); - when(branchDto.getBranchType()).thenReturn(BranchType.SHORT); + when(branchDto.getBranchType()).thenReturn(BranchType.BRANCH); Clock clock = mock(Clock.class); when(clock.millis()).thenReturn(1234567890123L);