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 80f375312..8595f99b5 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchPlugin.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchPlugin.java @@ -21,7 +21,7 @@ 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.bitbucket.BitbucketServerPullRequestDecorator; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.github.v4.GraphqlCheckRunProvider; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.gitlab.GitlabServerPullRequestDecorator; import com.github.mc1arke.sonarqube.plugin.scanner.CommunityBranchConfigurationLoader; @@ -142,10 +142,6 @@ public void load(CoreExtension.Context context) { .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") 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 95a98e654..3210268ce 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,7 +20,8 @@ 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.bitbucket.server.BitbucketServerPullRequestDecorator; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.BitbucketServerPullRequestDecorator; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.BitbucketClient; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.github.GithubPullRequestDecorator; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.github.v3.DefaultLinkHeaderReader; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.github.v3.RestApplicationAuthenticationProvider; @@ -41,7 +42,8 @@ public List getComponents() { return Arrays.asList(CommunityBranchLoaderDelegate.class, PullRequestPostAnalysisTask.class, PostAnalysisIssueVisitor.class, GithubPullRequestDecorator.class, GraphqlCheckRunProvider.class, DefaultLinkHeaderReader.class, RestApplicationAuthenticationProvider.class, - BitbucketServerPullRequestDecorator.class, GitlabServerPullRequestDecorator.class); + BitbucketServerPullRequestDecorator.class, BitbucketClient.class, + GitlabServerPullRequestDecorator.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 3f55b9b02..7f2b1c693 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 @@ -105,6 +105,14 @@ public String getCommitSha() { return branchDetails.getCommitId(); } + public String getDashboardUrl() { + return publicRootURL + "/dashboard?id=" + URLEncoder.encode(project.getKey()) + "&pullRequest=" + branchDetails.getBranchName(); + } + + public String getIssueUrl(String issueKey) { + return publicRootURL + "/project/issues?id=" + URLEncoder.encode(project.getKey()) + "&pullRequest=" + branchDetails.getBranchName() + "&issues=" + issueKey + "&open=" + issueKey; + } + public QualityGate.Status getQualityGateStatus() { return qualityGate.getStatus(); } @@ -183,7 +191,7 @@ public String createAnalysisSummary(FormatterFactory formatterFactory) { .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"))); + new Link(getDashboardUrl(), new Text("View in SonarQube"))); return formatterFactory.documentFormatter().format(document, formatterFactory); } @@ -205,7 +213,7 @@ public String createAnalysisIssueSummary(PostAnalysisIssueVisitor.ComponentIssue 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")) + new Link(getIssueUrl(issue.key()), new Text("View in SonarQube")) ); return formatterFactory.documentFormatter().format(document, formatterFactory); } @@ -273,13 +281,12 @@ public String getAnalysisProjectKey() { return project.getKey(); } - - private List findFailedConditions() { + public List findFailedConditions() { return qualityGate.getConditions().stream().filter(c -> c.getStatus() == QualityGate.EvaluationStatus.ERROR) .collect(Collectors.toList()); } - private Optional findMeasure(String metricKey) { + public Optional findMeasure(String metricKey) { return measuresHolder.getMeasureRepository().getRawMeasure(measuresHolder.getTreeRootHolder().getRoot(), measuresHolder.getMetricRepository() .getByKey(metricKey)) @@ -290,7 +297,7 @@ public Optional findQualityGateCondition(String metricKey return qualityGate.getConditions().stream().filter(c -> metricKey.equals(c.getMetricKey())).findFirst(); } - private Map countRuleByType() { + public Map countRuleByType() { return Arrays.stream(RuleType.values()).collect(Collectors.toMap(k -> k, k -> postAnalysisIssueVisitor.getIssues() .stream() @@ -305,7 +312,7 @@ private static String pluralOf(long value, String singleLabel, String multiLabel } - private static String format(QualityGate.Condition condition) { + public static String format(QualityGate.Condition condition) { Metric metric = CoreMetrics.getMetric(condition.getMetricKey()); if (metric.getType() == Metric.ValueType.RATING) { return String diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/BitbucketServerPullRequestDecorator.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/BitbucketServerPullRequestDecorator.java new file mode 100644 index 000000000..24834b1bb --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/BitbucketServerPullRequestDecorator.java @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2020 Mathias Åhsberg + * + * 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; + +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.AnalysisDetails; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.PullRequestBuildStatusDecorator; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.UnifyConfiguration; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.BitbucketClient; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.BitbucketException; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.Annotation; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.CreateAnnotationsRequest; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.CreateReportRequest; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.DataValue; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.ReportData; +import org.sonar.api.ce.posttask.QualityGate; +import org.sonar.api.issue.Issue; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.rule.Severity; +import org.sonar.api.rules.RuleType; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.ce.task.projectanalysis.component.Component; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +import static java.lang.String.format; +import static java.util.stream.Collectors.toSet; + +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 = "sonar.pullrequest.bitbucket.projectKey"; + + public static final String PULL_REQUEST_BITBUCKET_REPOSITORY_SLUG = "sonar.pullrequest.bitbucket.repositorySlug"; + + public static final String PULL_REQUEST_BITBUCKET_USER_SLUG = "sonar.pullrequest.bitbucket.userSlug"; + + private static final Logger LOGGER = Loggers.get(BitbucketServerPullRequestDecorator.class); + + private static final int DEFAULT_MAX_ANNOTATIONS = 1000; + + 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 final BitbucketClient client; + + public BitbucketServerPullRequestDecorator(BitbucketClient client) { + this.client = client; + } + + @Override + public String name() { + return "BitbucketServer"; + } + + @Override + public void decorateQualityGateStatus(AnalysisDetails analysisDetails, UnifyConfiguration configuration) { + try { + if(!client.supportsCodeInsights()) { + LOGGER.warn("Your Bitbucket instances does not support the Code Insights API."); + return; + } + String project = configuration.getRequiredProperty(PULL_REQUEST_BITBUCKET_PROJECT_KEY); + + String repo = configuration.getRequiredProperty(PULL_REQUEST_BITBUCKET_REPOSITORY_SLUG); + client.createReport(project, repo, + analysisDetails.getCommitSha(), + toReport(analysisDetails) + ); + updateAnnotations(project, repo, analysisDetails); + } catch (IOException e) { + LOGGER.error("Could not decorate pull request for project {}", analysisDetails.getAnalysisProjectKey(), e); + } + } + + private CreateReportRequest toReport(AnalysisDetails analysisDetails) { + Map rules = analysisDetails.countRuleByType(); + + List reportData = new ArrayList<>(); + reportData.add(reliabilityReport(rules.get(RuleType.BUG))); + reportData.add(new ReportData("Code coverage", new DataValue.Percentage(newCoverage(analysisDetails)))); + reportData.add(securityReport(rules.get(RuleType.VULNERABILITY), rules.get(RuleType.SECURITY_HOTSPOT))); + reportData.add(new ReportData("Duplication", new DataValue.Percentage(newDuplication(analysisDetails)))); + reportData.add(maintainabilityReport(rules.get(RuleType.CODE_SMELL))); + reportData.add(new ReportData("Analysis details", new DataValue.Link("Go to SonarQube", analysisDetails.getDashboardUrl()))); + + return new CreateReportRequest(reportData, + reportDescription(analysisDetails), + "SonarQube", + "SonarQube", + analysisDetails.getAnalysisDate().toInstant(), + analysisDetails.getDashboardUrl(), + format("%s/common/icon.png", analysisDetails.getBaseImageUrl()), + asInsightStatus(analysisDetails.getQualityGateStatus())); + } + + private void updateAnnotations(String project, String repo, AnalysisDetails analysisDetails) throws IOException { + final AtomicInteger chunkCounter = new AtomicInteger(0); + + client.deleteAnnotations(project, repo, analysisDetails.getCommitSha()); + + Map> annotationChunks = analysisDetails.getPostAnalysisIssueVisitor().getIssues().stream() + .filter(i -> i.getComponent().getReportAttributes().getScmPath().isPresent()) + .filter(i -> i.getComponent().getType() == Component.Type.FILE) + .filter(i -> OPEN_ISSUE_STATUSES.contains(i.getIssue().status())) + .sorted(Comparator.comparing(a -> Severity.ALL.indexOf(a.getIssue().severity()))) + .map(componentIssue -> { + String path = componentIssue.getComponent().getReportAttributes().getScmPath().get(); + return new Annotation(componentIssue.getIssue().key(), + Optional.ofNullable(componentIssue.getIssue().getLine()).orElse(0), + analysisDetails.getIssueUrl(componentIssue.getIssue().key()), + componentIssue.getIssue().getMessage(), + path, + toBitbucketSeverity(componentIssue.getIssue().severity()), + toBitbucketType(componentIssue.getIssue().type())); + }).collect(Collectors.groupingBy(s -> chunkCounter.getAndIncrement() / DEFAULT_MAX_ANNOTATIONS, toSet())); + + for (Set annotations : annotationChunks.values()) { + try { + client.createAnnotations(project, repo, analysisDetails.getCommitSha(), new CreateAnnotationsRequest(annotations)); + } catch (BitbucketException e) { + if (e.isError(BitbucketException.PAYLOAD_TOO_LARGE)) { + LOGGER.warn("The annotations will be truncated since the maximum number of annotations for this report has been reached."); + } else { + throw e; + } + + } + } + } + + private String asInsightStatus(QualityGate.Status status) { + return QualityGate.Status.ERROR.equals(status) ? "FAIL" : "PASS"; + } + + private String toBitbucketSeverity(String severity) { + if (severity == null) { + return "LOW"; + } + switch (severity) { + case Severity.BLOCKER: + case Severity.CRITICAL: + return "HIGH"; + case Severity.MAJOR: + return "MEDIUM"; + default: + return "LOW"; + } + } + + private String toBitbucketType(RuleType sonarqubeType) { + switch (sonarqubeType) { + case SECURITY_HOTSPOT: + case VULNERABILITY: + return "VULNERABILITY"; + case CODE_SMELL: + return "CODE_SMELL"; + case BUG: + return "BUG"; + default: + throw new IllegalStateException(format("%s is not a valid ruleType.", sonarqubeType)); + } + } + + private ReportData securityReport(Long vulnerabilities, Long hotspots) { + String vulnerabilityDescription = vulnerabilities == 1 ? "Vulnerability" : "Vulnerabilities"; + String hotspotDescription = hotspots == 1 ? "Hotspot" : "Hotspots"; + String security = format("%d %s (and %d %s)", vulnerabilities, vulnerabilityDescription, hotspots, hotspotDescription); + return new ReportData("Security", new DataValue.Text(security)); + } + + private ReportData reliabilityReport(Long bugs) { + String description = bugs == 1 ? "Bug" : "Bugs"; + return new ReportData("Reliability", new DataValue.Text(format("%d %s", bugs, description))); + } + + private ReportData maintainabilityReport(Long codeSmells) { + String description = codeSmells == 1 ? "Code Smell" : "Code Smells"; + return new ReportData("Maintainability", new DataValue.Text(format("%d %s", codeSmells, description))); + } + + private String reportDescription(AnalysisDetails details) { + String header = details.getQualityGateStatus() == QualityGate.Status.OK ? "Quality Gate passed" : "Quality Gate failed"; + String body = details.findFailedConditions().stream() + .map(AnalysisDetails::format) + .map(s -> format("- %s", s)) + .collect(Collectors.joining(System.lineSeparator())); + return format("%s%n%s", header, body); + } + + private BigDecimal newCoverage(AnalysisDetails details) { + return details.findQualityGateCondition(CoreMetrics.NEW_COVERAGE_KEY) + .filter(condition -> condition.getStatus() != QualityGate.EvaluationStatus.NO_VALUE) + .map(QualityGate.Condition::getValue) + .map(BigDecimal::new) + .orElse(BigDecimal.ZERO); + } + + private BigDecimal newDuplication(AnalysisDetails details) { + return details.findQualityGateCondition(CoreMetrics.NEW_DUPLICATED_LINES_DENSITY_KEY) + .filter(condition -> condition.getStatus() != QualityGate.EvaluationStatus.NO_VALUE) + .map(QualityGate.Condition::getValue) + .map(BigDecimal::new) + .orElse(BigDecimal.ZERO); + } +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/FileComment.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/FileComment.java deleted file mode 100644 index b3988e747..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/FileComment.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; - -import java.io.Serializable; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -public class FileComment implements Serializable { - private final String text; - - private final Anchor anchor; - - @JsonCreator - public FileComment(@JsonProperty("text") String text, @JsonProperty("anchor") Anchor anchor) { - this.text = text; - this.anchor = anchor; - } - - public String getText() { - return text; - } - - public Anchor getAnchor() { - return anchor; - } -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/BitbucketClient.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/BitbucketClient.java new file mode 100644 index 000000000..209768a38 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/BitbucketClient.java @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2020 Mathias Åhsberg + * + * 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.client; + +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.CreateAnnotationsRequest; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.CreateReportRequest; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.ErrorResponse; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.ServerProperties; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.BitbucketServerPullRequestDecorator; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import org.sonar.api.ce.ComputeEngineSide; +import org.sonar.api.config.Configuration; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; + +import java.io.IOException; +import java.util.Optional; + +import static java.lang.String.format; + +@ComputeEngineSide +public class BitbucketClient { + private static final Logger LOGGER = Loggers.get(BitbucketClient.class); + private static final String REPORT_KEY = "com.github.mc1arke.sonarqube"; + private final Configuration configuration; + + private OkHttpClient client; + private ObjectMapper objectMapper; + + public BitbucketClient(Configuration configuration) { + this.configuration = configuration; + } + + public boolean isConfigured() { + return configuration.hasKey(BitbucketServerPullRequestDecorator.PULL_REQUEST_BITBUCKET_URL) && + configuration.hasKey(BitbucketServerPullRequestDecorator.PULL_REQUEST_BITBUCKET_TOKEN); + } + + public ServerProperties getServerProperties() throws IOException { + Request req = new Request.Builder() + .get() + .url(format("%s/rest/api/1.0/application-properties", baseUrl())) + .build(); + try (Response response = getClient().newCall(req).execute()) { + validate(response); + + return getObjectMapper().reader().forType(ServerProperties.class) + .readValue(response.body().string()); + } + } + + public void createReport(String project, String repository, String commit, CreateReportRequest request) throws IOException { + String body = getObjectMapper().writeValueAsString(request); + Request req = new Request.Builder() + .put(RequestBody.create(MediaType.parse("application/json"), body)) + .url(format("%s/rest/insights/1.0/projects/%s/repos/%s/commits/%s/reports/%s", baseUrl(), project, repository, commit, REPORT_KEY)) + .build(); + + try (Response response = getClient().newCall(req).execute()) { + validate(response); + } + } + + public void createAnnotations(String project, String repository, String commit, CreateAnnotationsRequest request) throws IOException { + if (request.getAnnotations().isEmpty()) { + return; + } + Request req = new Request.Builder() + .post(RequestBody.create(MediaType.parse("application/json"), getObjectMapper().writeValueAsString(request))) + .url(format("%s/rest/insights/1.0/projects/%s/repos/%s/commits/%s/reports/%s/annotations", baseUrl(), project, repository, commit, REPORT_KEY)) + .build(); + try (Response response = getClient().newCall(req).execute()) { + validate(response); + } + } + + public void deleteAnnotations(String project, String repository, String commit) throws IOException { + Request req = new Request.Builder() + .delete() + .url(format("%s/rest/insights/1.0/projects/%s/repos/%s/commits/%s/reports/%s/annotations", baseUrl(), project, repository, commit, REPORT_KEY)) + .build(); + try (Response response = getClient().newCall(req).execute()) { + validate(response); + } + } + + public boolean supportsCodeInsights() { + try { + ServerProperties server = getServerProperties(); + LOGGER.debug(format("Your Bitbucket Server installation is version %s", server.getVersion())); + if (server.hasCodeInsightsApi()) { + return true; + } else { + LOGGER.info("Bitbucket Server version is to old. %s is the minimum version that supports Code Insights", + ServerProperties.CODE_INSIGHT_VERSION); + } + } catch (IOException e) { + LOGGER.error("Could not determine Bitbucket Server version", e); + return false; + } + return false; + } + + private void validate(Response response) throws IOException { + if (!response.isSuccessful()) { + ErrorResponse errors = null; + if (response.body() != null) { + errors = getObjectMapper().reader().forType(ErrorResponse.class) + .readValue(response.body().string()); + } + throw new BitbucketException(response.code(), errors); + } + } + + private OkHttpClient getClient() { + client = Optional.ofNullable(client).orElseGet(() -> + new OkHttpClient.Builder() + .authenticator(((route, response) -> + response.request() + .newBuilder() + .header("Authorization", format("Bearer %s", getToken())) + .header("Accept", "application/json") + .build() + )) + .build() + ); + return client; + } + + private ObjectMapper getObjectMapper() { + objectMapper = Optional.ofNullable(objectMapper).orElseGet(() -> new ObjectMapper() + .setSerializationInclusion(Include.NON_NULL) + .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + ); + return objectMapper; + } + + private String baseUrl() { + return configuration.get(BitbucketServerPullRequestDecorator.PULL_REQUEST_BITBUCKET_URL) + .orElseThrow(() -> + new IllegalArgumentException(format("Missing required property %s", BitbucketServerPullRequestDecorator.PULL_REQUEST_BITBUCKET_URL)) + ); + } + + private String getToken() { + return configuration.get(BitbucketServerPullRequestDecorator.PULL_REQUEST_BITBUCKET_TOKEN) + .orElseThrow(() -> new IllegalArgumentException("Personal Access Token for Bitbucket Server is missing")); + } +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/BitbucketException.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/BitbucketException.java new file mode 100644 index 000000000..a1d40027d --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/BitbucketException.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2020 Mathias Åhsberg + * + * 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.client; + +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.ErrorResponse; + +import java.util.Optional; +import java.util.stream.Collectors; + +public class BitbucketException extends RuntimeException { + public static final int PAYLOAD_TOO_LARGE = 413; + + private final int code; + private final ErrorResponse errors; + + BitbucketException(int code, ErrorResponse errors) { + this.code = code; + this.errors = errors; + } + + public boolean isError(int code) { + return this.code == code; + } + + @Override + public String getMessage() { + return Optional.ofNullable(errors) + .map(ErrorResponse::getErrors) + .map(e -> e.stream() + .map(ErrorResponse.Error::getMessage) + .collect(Collectors.joining(System.lineSeparator()))) + .orElse(String.format("Bitbucket responded with an error status (%d)", code)); + } +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/Anchor.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/model/Annotation.java similarity index 51% rename from src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/Anchor.java rename to src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/model/Annotation.java index eaed06360..255fe16ca 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/Anchor.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/model/Annotation.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Oliver Jedinger + * Copyright (C) 2020 Mathias Åhsberg * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -16,43 +16,64 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ -package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket; +package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import java.io.Serializable; -public class Anchor implements Serializable { +public class Annotation implements Serializable { + private final String externalId; private final int line; - - private final String lineType; - + private final String link; + private final String message; private final String path; - - private final String fileType; + private final String severity; + private final String type; @JsonCreator - public Anchor(@JsonProperty("line") int line, @JsonProperty("lineType") String lineType, @JsonProperty("path") String path, @JsonProperty("fileType") String fileType) { + public Annotation(@JsonProperty("externalId") String externalId, + @JsonProperty("line") int line, + @JsonProperty("link") String link, + @JsonProperty("message") String message, + @JsonProperty("path") String path, + @JsonProperty("severity") String severity, + @JsonProperty("type") String type) { + this.externalId = externalId; this.line = line; - this.lineType = lineType; + this.link = link; + this.message = message; this.path = path; - this.fileType = fileType; + this.severity = severity; + this.type = type; + } + + public String getExternalId() { + return externalId; } public int getLine() { return line; } - public String getLineType() { - return lineType; + public String getLink() { + return link; + } + + public String getMessage() { + return message; } public String getPath() { return path; } - public String getFileType() { - return fileType; + public String getSeverity() { + return severity; + } + + public String getType() { + return type; } } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/SummaryComment.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/model/CreateAnnotationsRequest.java similarity index 65% rename from src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/SummaryComment.java rename to src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/model/CreateAnnotationsRequest.java index 70c0b9435..d115bea5d 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/SummaryComment.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/model/CreateAnnotationsRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Oliver Jedinger + * Copyright (C) 2020 Mathias Åhsberg * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -16,23 +16,20 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ -package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket; +package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model; import java.io.Serializable; +import java.util.Collections; +import java.util.Set; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; +public class CreateAnnotationsRequest implements Serializable { + private final Set annotations; -public class SummaryComment implements Serializable { - private final String text; - - @JsonCreator - public SummaryComment(@JsonProperty("text") final String text) { - super(); - this.text = text; + public CreateAnnotationsRequest(Set annotations) { + this.annotations = annotations == null ? Collections.emptySet() : annotations; } - public String getText() { - return text; + public Set getAnnotations() { + return annotations; } } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/model/CreateReportRequest.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/model/CreateReportRequest.java new file mode 100644 index 000000000..c39e0259c --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/model/CreateReportRequest.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2020 Mathias Åhsberg + * + * 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.client.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.time.Instant; +import java.util.List; + +public class CreateReportRequest { + private final List data; + private final String details; + private final String title; + private final String reporter; + private final Instant createdDate; + private final String link; + private final String logoUrl; + private final String result; + + @JsonCreator + public CreateReportRequest( + @JsonProperty("data") List data, + @JsonProperty("details") String details, + @JsonProperty("title") String title, + @JsonProperty("reporter") String reporter, + @JsonProperty("createdDate") Instant createdDate, + @JsonProperty("link") String link, + @JsonProperty("logoUrl") String logoUrl, + @JsonProperty("result") String result) { + this.data = data; + this.details = details; + this.title = title; + this.reporter = reporter; + this.createdDate = createdDate; + this.link = link; + this.logoUrl = logoUrl; + this.result = result; + } + + public List getData() { + return data; + } + + public String getDetails() { + return details; + } + + public String getTitle() { + return title; + } + + public String getReporter() { + return reporter; + } + + public Instant getCreatedDate() { + return createdDate; + } + + public String getLink() { + return link; + } + + public String getLogoUrl() { + return logoUrl; + } + + public String getResult() { + return result; + } +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/model/DataValue.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/model/DataValue.java new file mode 100644 index 000000000..e7ebd1edc --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/model/DataValue.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2020 Mathias Åhsberg + * + * 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.client.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonValue; + +import java.io.Serializable; +import java.math.BigDecimal; + +public abstract class DataValue implements Serializable { + + public static class Link extends DataValue { + private final String linktext; + private final String href; + + @JsonCreator + public Link(@JsonProperty("linktext") String linktext, @JsonProperty("href") String href) { + this.linktext = linktext; + this.href = href; + } + + public String getLinktext() { + return linktext; + } + + public String getHref() { + return href; + } + } + + public static class Text extends DataValue { + private final String value; + + @JsonCreator + public Text(@JsonProperty("value") String value) { + this.value = value; + } + + @JsonValue + public String getValue() { + return value; + } + } + + public static class Percentage extends DataValue { + + private final BigDecimal value; + + @JsonCreator + public Percentage(@JsonProperty("value") BigDecimal value) { + this.value = value; + } + + @JsonValue + public BigDecimal getValue() { + return value; + } + } +} 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/client/model/ErrorResponse.java similarity index 59% rename from src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/response/activity/User.java rename to src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/model/ErrorResponse.java index e0ef00055..bbe230b45 100644 --- 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/client/model/ErrorResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Oliver Jedinger + * Copyright (C) 2020 Mathias Åhsberg * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -16,29 +16,31 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ -package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.response.activity; +package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model; -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; +import java.util.Collections; +import java.util.Set; - private final String slug; +public class ErrorResponse { + private final Set errors; - @JsonCreator - public User(@JsonProperty("name") final String name, @JsonProperty("slug") final String slug) { - this.name = name; - this.slug = slug; + ErrorResponse(@JsonProperty("errors") Set errors) { + this.errors = errors; } - - public String getName() { - return name; + public Set getErrors() { + return Collections.unmodifiableSet(errors); } + public static class Error { + private String message; + Error(@JsonProperty("message") String message) { + this.message = message; + } - public String getSlug() { - return slug; + public String getMessage() { + return this.message; + } } } + diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/response/diff/Segment.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/model/ReportData.java similarity index 58% rename from src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/response/diff/Segment.java rename to src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/model/ReportData.java index 829b81bd3..3039f029f 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/response/diff/Segment.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/model/ReportData.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Oliver Jedinger + * Copyright (C) 2020 Mathias Åhsberg * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -16,37 +16,43 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ -package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.response.diff; - -import java.io.Serializable; -import java.util.List; +package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -public class Segment implements Serializable { +public class ReportData { + private final String title; + private final DataValue value; + @JsonProperty("type") private final String type; - private final List lines; + @JsonCreator + public ReportData(@JsonProperty("title") String title, @JsonProperty("value") DataValue value) { + this.title = title; + this.value = value; + this.type = typeFrom(value); + } - private final boolean truncated; + public String getTitle() { + return title; + } - @JsonCreator - public Segment(@JsonProperty("type") final String type, @JsonProperty("lines") final List lines, @JsonProperty("truncated") final boolean truncated) { - this.type = type; - this.lines = lines; - this.truncated = truncated; + public DataValue getValue() { + return value; } public String getType() { return type; } - public List getLines() { - return lines; - } - - public boolean isTruncated() { - return truncated; + private String typeFrom(DataValue value) { + if (value instanceof DataValue.Link) { + return "LINK"; + } else if (value instanceof DataValue.Percentage) { + return "PERCENTAGE"; + } else { + return "TEXT"; + } } } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/model/ServerProperties.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/model/ServerProperties.java new file mode 100644 index 000000000..1d5ef8b48 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/client/model/ServerProperties.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2020 Mathias Åhsberg + * + * 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.client.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.io.Serializable; + +public class ServerProperties implements Serializable { + + public static final String CODE_INSIGHT_VERSION = "5.15"; + + private final String version; + + @JsonCreator + public ServerProperties(@JsonProperty("version") String version) { + this.version = version; + } + + public String getVersion() { + return version; + } + + public boolean hasCodeInsightsApi() { + return compareTo(CODE_INSIGHT_VERSION) >= 0; + } + + private int compareTo(String other) { + String[] current = semver(version); + String[] minimum = semver(other); + + int length = Math.max(current.length, minimum.length); + for (int i = 0; i < length; i++) { + int thisPart = i < current.length ? + Integer.parseInt(current[i]) : 0; + int thatPart = i < minimum.length ? + Integer.parseInt(minimum[i]) : 0; + if (thisPart < thatPart) + return -1; + if (thisPart > thatPart) + return 1; + } + return 0; + } + + private String[] semver(String v) { + return v.split("\\."); + } +} 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/diff/Diff.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/response/diff/Diff.java deleted file mode 100644 index c7299c948..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/response/diff/Diff.java +++ /dev/null @@ -1,66 +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.diff; - -import java.io.Serializable; -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -public class Diff implements Serializable { - private final String fromHash; - - private final String toHash; - - private final List hunks; - - private final File source; - - private final File destination; - - @JsonCreator - public Diff(@JsonProperty("fromHash") String fromHash, @JsonProperty("toHash") String toHash, @JsonProperty("hunks") List hunks, @JsonProperty("source") File source, @JsonProperty("destination") File destination) { - this.fromHash = fromHash; - this.toHash = toHash; - this.hunks = hunks; - this.source = source; - this.destination = destination; - } - - public String getFromHash() { - return fromHash; - } - - public String getToHash() { - return toHash; - } - - public List getHunks() { - return hunks; - } - - public File getSource() { - return source; - } - - public File getDestination() { - return destination; - } -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/response/diff/DiffLine.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/response/diff/DiffLine.java deleted file mode 100644 index 4e0fd6ff6..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/response/diff/DiffLine.java +++ /dev/null @@ -1,66 +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.diff; - -import java.io.Serializable; -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -public class DiffLine implements Serializable { - private final int source; - - private final int destination; - - private final String line; - - private final boolean truncated; - - private final List commentIds; - - @JsonCreator - public DiffLine(@JsonProperty("source") int source, @JsonProperty("destination") int destination, @JsonProperty("line") String line, @JsonProperty("truncated") boolean truncated, @JsonProperty("commentIds") List commentIds) { - this.source = source; - this.destination = destination; - this.line = line; - this.truncated = truncated; - this.commentIds = commentIds; - } - - public int getSource() { - return source; - } - - public int getDestination() { - return destination; - } - - public String getLine() { - return line; - } - - public boolean isTruncated() { - return truncated; - } - - public List getCommentIds() { - return commentIds; - } -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/response/diff/DiffPage.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/response/diff/DiffPage.java deleted file mode 100644 index 55fba904c..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/response/diff/DiffPage.java +++ /dev/null @@ -1,59 +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.diff; - -import java.io.Serializable; -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -public class DiffPage implements Serializable { - private final String fromHash; - - private final String toHash; - - private final boolean truncated; - - private final List diffs; - - @JsonCreator - public DiffPage(@JsonProperty("fromHash") final String fromHash, @JsonProperty("toHash") final String toHash, @JsonProperty("truncated") final boolean truncated, @JsonProperty("diffs") final List diffs) { - this.fromHash = fromHash; - this.toHash = toHash; - this.truncated = truncated; - this.diffs = diffs; - } - - public String getFromHash() { - return fromHash; - } - - public String getToHash() { - return toHash; - } - - public boolean isTruncated() { - return truncated; - } - - public List getDiffs() { - return diffs; - } -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/response/diff/File.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/response/diff/File.java deleted file mode 100644 index 71487d2a7..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/response/diff/File.java +++ /dev/null @@ -1,66 +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.diff; - -import java.io.Serializable; -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -public class File implements Serializable { - private final String parent; - - private final String name; - - private final String extension; - - private final String toString; - - private final List components; - - @JsonCreator - public File(@JsonProperty("parent") final String parent, @JsonProperty("name") final String name, @JsonProperty("extension") final String extension, @JsonProperty("toString") final String toString, @JsonProperty("components") final List components) { - this.parent = parent; - this.name = name; - this.extension = extension; - this.toString = toString; - this.components = components; - } - - public String getParent() { - return parent; - } - - public String getName() { - return name; - } - - public String getExtension() { - return extension; - } - - public String getToString() { - return toString; - } - - public List getComponents() { - return components; - } -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/response/diff/Hunk.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/response/diff/Hunk.java deleted file mode 100644 index 9f64faa4c..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/response/diff/Hunk.java +++ /dev/null @@ -1,73 +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.diff; - -import java.io.Serializable; -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -public class Hunk implements Serializable { - private final String context; - - private final int sourceLine; - - private final int sourceSpan; - - private final int destinationLine; - - private final int destinationSpan; - - private final List segments; - - @JsonCreator - public Hunk(@JsonProperty("context") final String context, @JsonProperty("sourceLine") final int sourceLine, @JsonProperty("sourceSpan") final int sourceSpan, @JsonProperty("destinationLine") final int destinationLine, @JsonProperty("destinationSpan") final int destinationSpan, @JsonProperty("segments") final List segments) { - this.context = context; - this.sourceLine = sourceLine; - this.sourceSpan = sourceSpan; - this.destinationLine = destinationLine; - this.destinationSpan = destinationSpan; - this.segments = segments; - } - - public String getContext() { - return context; - } - - public int getSourceLine() { - return sourceLine; - } - - public int getSourceSpan() { - return sourceSpan; - } - - public int getDestinationLine() { - return destinationLine; - } - - public int getDestinationSpan() { - return destinationSpan; - } - - public List getSegments() { - return segments; - } -} 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 deleted file mode 100644 index 48cc29cb4..000000000 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/server/BitbucketServerPullRequestDecorator.java +++ /dev/null @@ -1,319 +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.server; - -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.UnifyConfiguration; -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; -import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.response.diff.Hunk; -import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.response.diff.Segment; -import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.markup.MarkdownFormatterFactory; -import org.apache.commons.io.IOUtils; -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; -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.utils.log.Logger; -import org.sonar.api.utils.log.Loggers; -import org.sonar.core.issue.DefaultIssue; - -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; -import java.util.Optional; -import java.util.stream.Collectors; - -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 = "sonar.pullrequest.bitbucket.projectKey"; - - public static final String PULL_REQUEST_BITBUCKET_USER_SLUG = "sonar.pullrequest.bitbucket.userSlug"; - - public static final String PULL_REQUEST_BITBUCKET_REPOSITORY_SLUG = "sonar.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; - - @Override - public void decorateQualityGateStatus(AnalysisDetails analysisDetails, UnifyConfiguration configuration) { - LOGGER.info("starting to analyze with " + analysisDetails.toString()); - - try { - final String hostURL = configuration.getRequiredServerProperty(PULL_REQUEST_BITBUCKET_URL); - final String apiToken = configuration.getRequiredServerProperty(PULL_REQUEST_BITBUCKET_TOKEN); - final String repositorySlug = configuration.getRequiredProperty(PULL_REQUEST_BITBUCKET_REPOSITORY_SLUG); - final String pullRequestId = analysisDetails.getBranchName(); - final String userSlug = configuration.getProperty(PULL_REQUEST_BITBUCKET_USER_SLUG).orElse(StringUtils.EMPTY); - final String projectKey = configuration.getProperty(PULL_REQUEST_BITBUCKET_PROJECT_KEY).orElse(StringUtils.EMPTY); - final String commentUserSlug = configuration.getServerProperty(PULL_REQUEST_BITBUCKET_COMMENT_USER_SLUG).orElse(StringUtils.EMPTY); - - final boolean summaryCommentEnabled = Boolean.parseBoolean(configuration.getRequiredServerProperty(PULL_REQUEST_COMMENT_SUMMARY_ENABLED)); - final boolean fileCommentEnabled = Boolean.parseBoolean(configuration.getRequiredServerProperty(PULL_REQUEST_FILE_COMMENT_ENABLED)); - final boolean deleteCommentsEnabled = Boolean.parseBoolean(configuration.getRequiredServerProperty(PULL_REQUEST_DELETE_COMMENTS_ENABLED)); - - 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); - - 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()); - for (PostAnalysisIssueVisitor.ComponentIssue componentIssue : componentIssues) { - final DefaultIssue issue = componentIssue.getIssue(); - String analysisIssueSummary = analysisDetails.createAnalysisIssueSummary(componentIssue, new MarkdownFormatterFactory()); - String issuePath = analysisDetails.getSCMPathForIssue(componentIssue).orElse(StringUtils.EMPTY); - int issueLine = issue.getLine() != null ? issue.getLine() : 0; - String issueType = getIssueType(diffPage, issuePath, issueLine); - String fileType = "TO"; - if (issueType.equals("CONTEXT")) { - fileType = "FROM"; - } - StringEntity fileCommentEntity = new StringEntity( - new ObjectMapper().writeValueAsString(new FileComment(analysisIssueSummary, new Anchor(issueLine, issueType, issuePath, fileType))), ContentType.APPLICATION_JSON - ); - postComment(commentUrl, headers, fileCommentEntity, fileCommentEnabled); - } - } catch (IOException ex) { - throw new IllegalStateException("Could not decorate Pull Request on Bitbucket Server", ex); - } - - } - - protected String getIssueType(DiffPage diffPage, String issuePath, int issueLine) { - String issueType = "CONTEXT"; - List diffs = diffPage.getDiffs().stream() - .filter(diff -> diff.getDestination() != null) - .filter(diff -> issuePath.equals(diff.getDestination().getToString())) - .collect(Collectors.toList()); - - if (!diffs.isEmpty()) { - for (Diff diff : diffs) { - List hunks = diff.getHunks(); - if (!hunks.isEmpty()) { - issueType = getExtractIssueType(issueLine, issueType, hunks); - } - } - } - return issueType; - } - - private String getExtractIssueType(int issueLine, String issueType, List hunks) { - for (Hunk hunk : hunks) { - List segments = hunk.getSegments(); - for (Segment segment : segments) { - Optional optionalLine = segment.getLines().stream().filter(diffLine -> diffLine.getDestination() == issueLine).findFirst(); - if (optionalLine.isPresent()) { - issueType = segment.getType(); - break; - } - } - } - 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()) { - LOGGER.debug(String.format("Getting page %s", type)); - HttpGet httpGet = new HttpGet(diffUrl); - for (Map.Entry entry : headers.entrySet()) { - httpGet.addHeader(entry.getKey(), entry.getValue()); - } - HttpResponse httpResponse = closeableHttpClient.execute(httpGet); - if (null == httpResponse) { - LOGGER.error(String.format("HttpResponse for getting page %s was null", type)); - } else if (httpResponse.getStatusLine().getStatusCode() != 200) { - HttpEntity entity = httpResponse.getEntity(); - LOGGER.error("Error response from Bitbucket: " + IOUtils.toString(entity.getContent(), StandardCharsets.UTF_8.name())); - throw new IllegalStateException(String.format("Error response returned from Bitbucket Server. Expected HTTP Status 200 but got %s", httpResponse.getStatusLine().getStatusCode()) ); - } else { - HttpEntity entity = httpResponse.getEntity(); - page = 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.name()), type); - LOGGER.debug(new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(page)); - } - } catch (IOException ex) { - LOGGER.error(String.format("Could not get %s from Bitbucket Server", type.getName()), ex); - } - return type.cast(page); - } - - protected boolean postComment(String commentUrl, Map headers, StringEntity requestEntity, boolean sendRequest) throws IOException { - boolean commentPosted = false; - HttpPost httpPost = new HttpPost(commentUrl); - for (Map.Entry entry : headers.entrySet()) { - httpPost.addHeader(entry.getKey(), entry.getValue()); - } - 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; - } - } - } - return commentPosted; - } - - @Override - public String name() { - return "BitbucketServer"; - } -} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/ScannerConfigurationLoaderSensor.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/ScannerConfigurationLoaderSensor.java index 0896fae45..96f241073 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/ScannerConfigurationLoaderSensor.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/scanner/ScannerConfigurationLoaderSensor.java @@ -19,7 +19,7 @@ package com.github.mc1arke.sonarqube.plugin.scanner; import com.github.mc1arke.sonarqube.plugin.CommunityBranchPlugin; -import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.server.BitbucketServerPullRequestDecorator; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.BitbucketServerPullRequestDecorator; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.github.v4.GraphqlCheckRunProvider; import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.gitlab.GitlabServerPullRequestDecorator; import com.google.common.collect.Sets; diff --git a/src/main/resources/static/common/icon.png b/src/main/resources/static/common/icon.png new file mode 100644 index 000000000..bc84c1660 Binary files /dev/null and b/src/main/resources/static/common/icon.png differ 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 3af011a47..b77039317 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchPluginTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/CommunityBranchPluginTest.java @@ -119,7 +119,7 @@ public void testServerSideLoad() { final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Object.class); verify(context, times(2)).addExtensions(argumentCaptor.capture(), argumentCaptor.capture()); - assertEquals(23, argumentCaptor.getAllValues().size()); + assertEquals(22, argumentCaptor.getAllValues().size()); assertEquals(Arrays.asList(CommunityBranchFeatureExtension.class, CommunityBranchSupportDelegate.class), argumentCaptor.getAllValues().subList(0, 2)); 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 5737399a3..16a2b7622 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(9, result.size()); + assertEquals(10, result.size()); assertEquals(CommunityBranchLoaderDelegate.class, result.get(0)); } } diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/BitbucketPullRequestDecoratorTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/BitbucketPullRequestDecoratorTest.java new file mode 100644 index 000000000..fce8a3990 --- /dev/null +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/BitbucketPullRequestDecoratorTest.java @@ -0,0 +1,153 @@ +package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket; + +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.UnifyConfiguration; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.BitbucketClient; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.Annotation; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.CreateAnnotationsRequest; +import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.CreateReportRequest; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.junit.MockitoJUnitRunner; +import org.sonar.api.ce.posttask.QualityGate; +import org.sonar.api.issue.Issue; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.rule.Severity; +import org.sonar.api.rules.RuleType; +import org.sonar.ce.task.projectanalysis.component.Component; +import org.sonar.ce.task.projectanalysis.component.ReportAttributes; +import org.sonar.core.issue.DefaultIssue; + +import java.io.IOException; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class BitbucketPullRequestDecoratorTest { + + private static final String PROJECT = "project"; + private static final String REPO = "repo"; + private static final String COMMIT = "commit"; + + private static final String ISSUE_KEY = "issue-key"; + private static final int ISSUE_LINE = 1; + private static final String ISSUE_LINK = "https://issue-link"; + private static final String ISSUE_MESSAGE = "issue message"; + private static final String ISSUE_PATH = "/issue/path"; + private static final String DASHBOARD_URL = "https://dashboard-url"; + private static final String IMAGE_URL = "https://image-url"; + + private AnalysisDetails analysisDetails = mock(AnalysisDetails.class); + private UnifyConfiguration unifyConfiguration = mock(UnifyConfiguration.class); + + private BitbucketClient client = mock(BitbucketClient.class); + + private BitbucketServerPullRequestDecorator underTest = new BitbucketServerPullRequestDecorator(client); + + @Before + public void setUp() { + when(unifyConfiguration.getRequiredProperty(BitbucketServerPullRequestDecorator.PULL_REQUEST_BITBUCKET_PROJECT_KEY)).thenReturn(PROJECT); + when(unifyConfiguration.getRequiredProperty(BitbucketServerPullRequestDecorator.PULL_REQUEST_BITBUCKET_REPOSITORY_SLUG)).thenReturn(REPO); + } + + @Test + public void testValidAnalysis() throws IOException { + when(client.supportsCodeInsights()).thenReturn(true); + + mockValidAnalysis(); + final ArgumentCaptor reportCaptor = ArgumentCaptor.forClass(CreateReportRequest.class); + final ArgumentCaptor annotationsCaptor = ArgumentCaptor.forClass(CreateAnnotationsRequest.class); + + underTest.decorateQualityGateStatus(analysisDetails, unifyConfiguration); + + verify(client).createReport(eq(PROJECT), eq(REPO), eq(COMMIT), reportCaptor.capture()); + verifyExpectedReport(reportCaptor.getValue()); + + verify(client).deleteAnnotations(PROJECT, REPO, COMMIT); + verify(client).createAnnotations(eq(PROJECT), eq(REPO), eq(COMMIT), annotationsCaptor.capture()); + + CreateAnnotationsRequest actualAnnotations = annotationsCaptor.getValue(); + assertThat(actualAnnotations.getAnnotations()).size().isEqualTo(1); + verifyExpectedAnnotation(actualAnnotations.getAnnotations().iterator().next()); + } + + private void mockValidAnalysis() { + when(analysisDetails.getCommitSha()).thenReturn(COMMIT); + when(analysisDetails.getQualityGateStatus()).thenReturn(QualityGate.Status.OK); + + Map ruleCount = new HashMap<>(); + ruleCount.put(RuleType.CODE_SMELL, 1L); + ruleCount.put(RuleType.VULNERABILITY, 2L); + ruleCount.put(RuleType.SECURITY_HOTSPOT, 3L); + ruleCount.put(RuleType.BUG, 4L); + + when(analysisDetails.countRuleByType()).thenReturn(ruleCount); + when(analysisDetails.findQualityGateCondition(CoreMetrics.NEW_COVERAGE_KEY)).thenReturn(Optional.empty()); + when(analysisDetails.findQualityGateCondition(CoreMetrics.NEW_DUPLICATED_LINES_DENSITY_KEY)).thenReturn(Optional.empty()); + when(analysisDetails.getAnalysisDate()).thenReturn(Date.from(Instant.now())); + when(analysisDetails.getDashboardUrl()).thenReturn(DASHBOARD_URL); + + ReportAttributes reportAttributes = mock(ReportAttributes.class); + when(reportAttributes.getScmPath()).thenReturn(Optional.of(ISSUE_PATH)); + + Component component = mock(Component.class); + when(component.getType()).thenReturn(Component.Type.FILE); + when(component.getReportAttributes()).thenReturn(reportAttributes); + + DefaultIssue defaultIssue = mock(DefaultIssue.class); + when(defaultIssue.status()).thenReturn(Issue.STATUS_OPEN); + when(defaultIssue.severity()).thenReturn(Severity.CRITICAL); + when(defaultIssue.getLine()).thenReturn(ISSUE_LINE); + when(defaultIssue.key()).thenReturn(ISSUE_KEY); + when(defaultIssue.type()).thenReturn(RuleType.BUG); + when(defaultIssue.getMessage()).thenReturn(ISSUE_MESSAGE); + when(analysisDetails.getIssueUrl(ISSUE_KEY)).thenReturn(ISSUE_LINK); + when(analysisDetails.getBaseImageUrl()).thenReturn(IMAGE_URL); + + PostAnalysisIssueVisitor.ComponentIssue componentIssue = mock(PostAnalysisIssueVisitor.ComponentIssue.class); + when(componentIssue.getIssue()).thenReturn(defaultIssue); + when(componentIssue.getComponent()).thenReturn(component); + + PostAnalysisIssueVisitor postAnalysisIssueVisitor = mock(PostAnalysisIssueVisitor.class); + when(postAnalysisIssueVisitor.getIssues()).thenReturn(Collections.singletonList(componentIssue)); + + when(analysisDetails.getPostAnalysisIssueVisitor()).thenReturn(postAnalysisIssueVisitor); + } + + private void verifyExpectedReport(CreateReportRequest actual) { + assertThat(actual.getTitle()).isEqualTo("SonarQube"); + assertThat(actual.getResult()).isEqualTo("PASS"); + assertThat(actual.getReporter()).isEqualTo("SonarQube"); + assertThat(actual.getCreatedDate()).isBetween(Instant.now().minus(1, ChronoUnit.MINUTES), Instant.now()); + assertThat(actual.getDetails()).isEqualTo("Quality Gate passed" + System.lineSeparator()); + assertThat(actual.getLink()).isEqualTo(DASHBOARD_URL); + assertThat(actual.getLogoUrl()).isEqualTo(String.format("%s/common/icon.png", IMAGE_URL)); + + assertThat(actual.getData()).size().isEqualTo(6); + } + + private void verifyExpectedAnnotation(Annotation actual) { + assertThat(actual.getExternalId()).isEqualTo(ISSUE_KEY); + assertThat(actual.getLine()).isEqualTo(ISSUE_LINE); + assertThat(actual.getLink()).isEqualTo(ISSUE_LINK); + assertThat(actual.getMessage()).isEqualTo(ISSUE_MESSAGE); + assertThat(actual.getPath()).isEqualTo(ISSUE_PATH); + assertThat(actual.getSeverity()).isEqualTo("HIGH"); + assertThat(actual.getType()).isEqualTo("BUG"); + } +} 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 deleted file mode 100644 index 52156ec4f..000000000 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/server/BitbucketServerPullRequestDecoratorTest.java +++ /dev/null @@ -1,231 +0,0 @@ -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; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.StringEntity; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.mockito.InjectMocks; - -import java.io.File; -import java.util.HashMap; -import java.util.Map; - -import static com.github.tomakehurst.wiremock.client.WireMock.*; -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; - -public class BitbucketServerPullRequestDecoratorTest { - - @Rule - public final WireMockRule wireMockRule = new WireMockRule(wireMockConfig().port(8089)); - - @InjectMocks - private BitbucketServerPullRequestDecorator bitbucketServerPullRequestDecorator; - - private Map headers; - - /** - * 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(); - - 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"))) - ) - ); - 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"))) - ) - ); - DiffPage diffPage = bitbucketServerPullRequestDecorator.getPage(DIFFURL, headers, DiffPage.class); - - // wrong file - String issueType = bitbucketServerPullRequestDecorator.getIssueType(diffPage, "src/DoesNotExist.java", 15); - assertThat(issueType, is("CONTEXT")); - - // line not within diff - issueType = bitbucketServerPullRequestDecorator.getIssueType(diffPage, "src/com/sonar/sample/classes/ClassWithInvalidMethodName.java", 0); - assertThat(issueType, is("CONTEXT")); - - issueType = bitbucketServerPullRequestDecorator.getIssueType(diffPage, "src/com/sonar/sample/classes/ClassWithInvalidMethodName.java", 15); - assertThat(issueType, is("ADDED")); - } - - @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)); - } -}