mirror of
https://github.com/mc1arke/sonarqube-community-branch-plugin.git
synced 2024-11-30 09:06:46 +02:00
Adds support for Bitbucket Cloud Code Insights
This commit provides support for the newly created bitbucket cloud code insights API endpoints. The implementation has been done under the consideration that in newer versions no dedicated ALM support for bitbucket cloud exists, thus this implementation is minimal invasive. One thing to note here: * For local testing the link on CloudCreateReportRequest has to be set manually to a non localhost URL since bitbuckets API doesn't support it.
This commit is contained in:
parent
54c7d4e4f4
commit
7a5ae26fbc
@ -19,7 +19,7 @@ SonarQube Version | Plugin Version
|
||||
|
||||
# Features
|
||||
The plugin is intended to support the [features and parameters specified in the SonarQube documentation](https://docs.sonarqube.org/latest/branches/overview/), with the following caveats
|
||||
* __Pull Requests:__ Analysis of Pull Requests is fully supported, but the decoration of pull requests is only currently available for Github, Gitlab and Bitbucket Server
|
||||
* __Pull Requests:__ Analysis of Pull Requests is fully supported, but the decoration of pull requests is only currently available for Github, Gitlab, Bitbucket Server and Bitbucket Cloud.
|
||||
|
||||
# Installation
|
||||
Either build the project or [download a compatible release version of the plugin JAR](https://github.com/mc1arke/sonarqube-community-branch-plugin/releases). Copy the plugin JAR file to the `extensions/plugins/` **and** the `lib/common/` directories of your SonarQube instance and restart SonarQube.
|
||||
|
@ -20,8 +20,7 @@ package com.github.mc1arke.sonarqube.plugin.ce;
|
||||
|
||||
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.BitbucketServerPullRequestDecorator;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.BitbucketClient;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.BitbucketPullRequestDecorator;
|
||||
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;
|
||||
@ -42,8 +41,7 @@ public class CommunityReportAnalysisComponentProvider implements ReportAnalysisC
|
||||
return Arrays.asList(CommunityBranchLoaderDelegate.class, PullRequestPostAnalysisTask.class,
|
||||
PostAnalysisIssueVisitor.class, GithubPullRequestDecorator.class,
|
||||
GraphqlCheckRunProvider.class, DefaultLinkHeaderReader.class, RestApplicationAuthenticationProvider.class,
|
||||
BitbucketServerPullRequestDecorator.class, BitbucketClient.class,
|
||||
GitlabServerPullRequestDecorator.class);
|
||||
BitbucketPullRequestDecorator.class, GitlabServerPullRequestDecorator.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,12 +22,14 @@ import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.AnalysisDetails;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.DecorationResult;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.PullRequestBuildStatusDecorator;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.BitbucketClient;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.BitbucketClientFactory;
|
||||
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.BitbucketConfiguration;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.CodeInsightsAnnotation;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.CodeInsightsReport;
|
||||
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 com.google.common.annotations.VisibleForTesting;
|
||||
import org.sonar.api.ce.posttask.QualityGate;
|
||||
import org.sonar.api.issue.Issue;
|
||||
import org.sonar.api.measures.CoreMetrics;
|
||||
@ -54,9 +56,9 @@ import java.util.stream.Collectors;
|
||||
import static java.lang.String.format;
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
|
||||
public class BitbucketServerPullRequestDecorator implements PullRequestBuildStatusDecorator {
|
||||
public class BitbucketPullRequestDecorator implements PullRequestBuildStatusDecorator {
|
||||
|
||||
private static final Logger LOGGER = Loggers.get(BitbucketServerPullRequestDecorator.class);
|
||||
private static final Logger LOGGER = Loggers.get(BitbucketPullRequestDecorator.class);
|
||||
|
||||
private static final int DEFAULT_MAX_ANNOTATIONS = 1000;
|
||||
|
||||
@ -66,28 +68,34 @@ public class BitbucketServerPullRequestDecorator implements PullRequestBuildStat
|
||||
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 DecorationResult decorateQualityGateStatus(AnalysisDetails analysisDetails, AlmSettingDto almSettingDto, ProjectAlmSettingDto projectAlmSettingDto) {
|
||||
String project = projectAlmSettingDto.getAlmRepo();
|
||||
String repo = projectAlmSettingDto.getAlmSlug();
|
||||
String url = almSettingDto.getUrl();
|
||||
String token = almSettingDto.getPersonalAccessToken();
|
||||
BitbucketConfiguration bitbucketConfiguration = new BitbucketConfiguration(url, token, repo, project);
|
||||
|
||||
BitbucketClient client = createClient(bitbucketConfiguration);
|
||||
try {
|
||||
if(!client.supportsCodeInsights(almSettingDto)) {
|
||||
LOGGER.warn("Your Bitbucket instances does not support the Code Insights API.");
|
||||
if (!client.supportsCodeInsights()) {
|
||||
LOGGER.warn("Your Bitbucket instance does not support the Code Insights API.");
|
||||
return DEFAULT_DECORATION_RESULT;
|
||||
}
|
||||
String project = projectAlmSettingDto.getAlmRepo();
|
||||
|
||||
String repo = projectAlmSettingDto.getAlmSlug();
|
||||
client.createReport(project, repo,
|
||||
analysisDetails.getCommitSha(),
|
||||
toReport(analysisDetails),
|
||||
almSettingDto
|
||||
CodeInsightsReport codeInsightsReport = client.createCodeInsightsReport(
|
||||
toReport(client, analysisDetails),
|
||||
reportDescription(analysisDetails),
|
||||
analysisDetails.getAnalysisDate().toInstant(),
|
||||
analysisDetails.getDashboardUrl(),
|
||||
format("%s/common/icon.png", analysisDetails.getBaseImageUrl()),
|
||||
analysisDetails.getQualityGateStatus()
|
||||
);
|
||||
updateAnnotations(project, repo, analysisDetails, almSettingDto);
|
||||
|
||||
client.uploadReport(project, repo,
|
||||
analysisDetails.getCommitSha(), codeInsightsReport);
|
||||
|
||||
updateAnnotations(client, project, repo, analysisDetails);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Could not decorate pull request for project {}", analysisDetails.getAnalysisProjectKey(), e);
|
||||
}
|
||||
@ -95,12 +103,17 @@ public class BitbucketServerPullRequestDecorator implements PullRequestBuildStat
|
||||
return DEFAULT_DECORATION_RESULT;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
BitbucketClient createClient(BitbucketConfiguration bitbucketConfiguration) {
|
||||
return BitbucketClientFactory.createClient(bitbucketConfiguration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ALM alm() {
|
||||
return ALM.BITBUCKET;
|
||||
}
|
||||
|
||||
private CreateReportRequest toReport(AnalysisDetails analysisDetails) {
|
||||
private List<ReportData> toReport(BitbucketClient client, AnalysisDetails analysisDetails) {
|
||||
Map<RuleType, Long> rules = analysisDetails.countRuleByType();
|
||||
|
||||
List<ReportData> reportData = new ArrayList<>();
|
||||
@ -109,31 +122,24 @@ public class BitbucketServerPullRequestDecorator implements PullRequestBuildStat
|
||||
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())));
|
||||
reportData.add(new ReportData("Analysis details", client.createLinkDataValue(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()));
|
||||
return reportData;
|
||||
}
|
||||
|
||||
private void updateAnnotations(String project, String repo, AnalysisDetails analysisDetails, AlmSettingDto almSettingDto) throws IOException {
|
||||
private void updateAnnotations(BitbucketClient client, String project, String repo, AnalysisDetails analysisDetails) throws IOException {
|
||||
final AtomicInteger chunkCounter = new AtomicInteger(0);
|
||||
|
||||
client.deleteAnnotations(project, repo, analysisDetails.getCommitSha(), almSettingDto);
|
||||
client.deleteAnnotations(project, repo, analysisDetails.getCommitSha());
|
||||
|
||||
Map<Object, Set<Annotation>> annotationChunks = analysisDetails.getPostAnalysisIssueVisitor().getIssues().stream()
|
||||
Map<Object, Set<CodeInsightsAnnotation>> 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(),
|
||||
return client.createCodeInsightsAnnotation(componentIssue.getIssue().key(),
|
||||
Optional.ofNullable(componentIssue.getIssue().getLine()).orElse(0),
|
||||
analysisDetails.getIssueUrl(componentIssue.getIssue().key()),
|
||||
componentIssue.getIssue().getMessage(),
|
||||
@ -142,9 +148,9 @@ public class BitbucketServerPullRequestDecorator implements PullRequestBuildStat
|
||||
toBitbucketType(componentIssue.getIssue().type()));
|
||||
}).collect(Collectors.groupingBy(s -> chunkCounter.getAndIncrement() / DEFAULT_MAX_ANNOTATIONS, toSet()));
|
||||
|
||||
for (Set<Annotation> annotations : annotationChunks.values()) {
|
||||
for (Set<CodeInsightsAnnotation> annotations : annotationChunks.values()) {
|
||||
try {
|
||||
client.createAnnotations(project, repo, analysisDetails.getCommitSha(), new CreateAnnotationsRequest(annotations), almSettingDto);
|
||||
client.uploadAnnotations(project, repo, analysisDetails.getCommitSha(), 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.");
|
||||
@ -156,10 +162,6 @@ public class BitbucketServerPullRequestDecorator implements PullRequestBuildStat
|
||||
}
|
||||
}
|
||||
|
||||
private String asInsightStatus(QualityGate.Status status) {
|
||||
return QualityGate.Status.ERROR.equals(status) ? "FAIL" : "PASS";
|
||||
}
|
||||
|
||||
private String toBitbucketSeverity(String severity) {
|
||||
if (severity == null) {
|
||||
return "LOW";
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Mathias Åhsberg
|
||||
* Copyright (C) 2020 Marvin Wichmann
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@ -18,137 +18,75 @@
|
||||
*/
|
||||
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 okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import org.sonar.api.ce.ComputeEngineSide;
|
||||
import org.sonar.api.utils.log.Logger;
|
||||
import org.sonar.api.utils.log.Loggers;
|
||||
import org.sonar.db.alm.setting.AlmSettingDto;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.CodeInsightsAnnotation;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.CodeInsightsReport;
|
||||
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 java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.lang.String.format;
|
||||
public interface BitbucketClient {
|
||||
|
||||
@ComputeEngineSide
|
||||
public class BitbucketClient {
|
||||
private static final Logger LOGGER = Loggers.get(BitbucketClient.class);
|
||||
private static final String REPORT_KEY = "com.github.mc1arke.sonarqube";
|
||||
private static final MediaType APPLICATION_JSON_MEDIA_TYPE = MediaType.get("application/json");
|
||||
/**
|
||||
* <p>
|
||||
* Creates an annotation for the given parameters based on the fact if the cloud
|
||||
* or the on prem bitbucket solution is used.
|
||||
* </p>
|
||||
*
|
||||
* @return The newly created {@link CodeInsightsAnnotation}
|
||||
*/
|
||||
CodeInsightsAnnotation createCodeInsightsAnnotation(String issueKey, int line, String issueUrl, String message, String path, String severity, String type);
|
||||
|
||||
private OkHttpClient client;
|
||||
private ObjectMapper objectMapper;
|
||||
/**
|
||||
* <p>
|
||||
* Creates a report for the given parameters based on the fact if the cloud
|
||||
* or the on prem bitbucket solution is used.
|
||||
* </p>
|
||||
*
|
||||
* @return The newly created {@link CodeInsightsReport}
|
||||
*/
|
||||
CodeInsightsReport createCodeInsightsReport(List<ReportData> reportData,
|
||||
String reportDescription, Instant creationDate, String dashboardUrl,
|
||||
String logoUrl, QualityGate.Status status);
|
||||
|
||||
/**
|
||||
* Deletes all code insights annotations for the given parameters.
|
||||
*
|
||||
* @throws IOException if the annotations cannot be deleted
|
||||
*/
|
||||
void deleteAnnotations(String project, String repo, String commitSha) throws IOException;
|
||||
|
||||
public ServerProperties getServerProperties(AlmSettingDto almSettingDto) throws IOException {
|
||||
Request req = new Request.Builder()
|
||||
.get()
|
||||
.url(format("%s/rest/api/1.0/application-properties", almSettingDto.getUrl()))
|
||||
.build();
|
||||
try (Response response = getClient(almSettingDto).newCall(req).execute()) {
|
||||
validate(response);
|
||||
/**
|
||||
* Uploads CodeInsights Annotations for the given commit.
|
||||
*
|
||||
* @throws IOException if the annotations cannot be uploaded
|
||||
*/
|
||||
void uploadAnnotations(String project, String repo, String commitSha, Set<CodeInsightsAnnotation> annotations) throws IOException;
|
||||
|
||||
return getObjectMapper().reader().forType(ServerProperties.class)
|
||||
.readValue(Optional.ofNullable(response.body())
|
||||
.orElseThrow(() -> new IllegalStateException("No response body from BitBucket"))
|
||||
.string());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Creates a DataValue of type DataValue.Link or DataValue.CloudLink depending on the implementation
|
||||
*/
|
||||
DataValue createLinkDataValue(String dashboardUrl);
|
||||
|
||||
public void createReport(String project, String repository, String commit, CreateReportRequest request, AlmSettingDto almSettingDto) throws IOException {
|
||||
String body = getObjectMapper().writeValueAsString(request);
|
||||
Request req = new Request.Builder()
|
||||
.put(RequestBody.create(APPLICATION_JSON_MEDIA_TYPE, body))
|
||||
.url(format("%s/rest/insights/1.0/projects/%s/repos/%s/commits/%s/reports/%s", almSettingDto.getUrl(), project, repository, commit, REPORT_KEY))
|
||||
.build();
|
||||
/**
|
||||
* Uploads the code insights report for the given commit
|
||||
*/
|
||||
void uploadReport(String project, String repo, String commitSha, CodeInsightsReport codeInsightReport) throws IOException;
|
||||
|
||||
try (Response response = getClient(almSettingDto).newCall(req).execute()) {
|
||||
validate(response);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* <p>
|
||||
* Determines if the used bitbucket endpoint supports the code insights feature.
|
||||
* <p>
|
||||
* For the cloud version we simply return true and for the server version a version
|
||||
* check is implemented that tests if the given server version is higher than 5.15
|
||||
* </p>
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
boolean supportsCodeInsights();
|
||||
|
||||
public void createAnnotations(String project, String repository, String commit, CreateAnnotationsRequest request, AlmSettingDto almSettingDto) throws IOException {
|
||||
if (request.getAnnotations().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Request req = new Request.Builder()
|
||||
.post(RequestBody.create(APPLICATION_JSON_MEDIA_TYPE, getObjectMapper().writeValueAsString(request)))
|
||||
.url(format("%s/rest/insights/1.0/projects/%s/repos/%s/commits/%s/reports/%s/annotations", almSettingDto.getUrl(), project, repository, commit, REPORT_KEY))
|
||||
.build();
|
||||
try (Response response = getClient(almSettingDto).newCall(req).execute()) {
|
||||
validate(response);
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteAnnotations(String project, String repository, String commit, AlmSettingDto almSettingDto) throws IOException {
|
||||
Request req = new Request.Builder()
|
||||
.delete()
|
||||
.url(format("%s/rest/insights/1.0/projects/%s/repos/%s/commits/%s/reports/%s/annotations", almSettingDto.getUrl(), project, repository, commit, REPORT_KEY))
|
||||
.build();
|
||||
try (Response response = getClient(almSettingDto).newCall(req).execute()) {
|
||||
validate(response);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean supportsCodeInsights(AlmSettingDto almSettingDto) {
|
||||
try {
|
||||
ServerProperties server = getServerProperties(almSettingDto);
|
||||
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 {
|
||||
LOGGER.debug(format("Validate response %s", response));
|
||||
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(AlmSettingDto almSettingDto) {
|
||||
client = Optional.ofNullable(client).orElseGet(() ->
|
||||
new OkHttpClient.Builder()
|
||||
.authenticator(((route, response) ->
|
||||
response.request()
|
||||
.newBuilder()
|
||||
.header("Authorization", format("Bearer %s", almSettingDto.getPersonalAccessToken()))
|
||||
.header("Accept", APPLICATION_JSON_MEDIA_TYPE.toString())
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Marvin Wichmann
|
||||
*
|
||||
* 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;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.BitbucketConfiguration;
|
||||
|
||||
public final class BitbucketClientFactory {
|
||||
private BitbucketClientFactory() {
|
||||
}
|
||||
|
||||
public static BitbucketClient createClient(BitbucketConfiguration bitbucketConfiguration) {
|
||||
if (bitbucketConfiguration.isCloud()) {
|
||||
return new BitbucketCloudClient(bitbucketConfiguration, createObjectMapper());
|
||||
} else {
|
||||
return new BitbucketServerClient(bitbucketConfiguration, createObjectMapper());
|
||||
}
|
||||
}
|
||||
|
||||
private static ObjectMapper createObjectMapper() {
|
||||
return new ObjectMapper()
|
||||
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
||||
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
||||
}
|
||||
}
|
@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Marvin Wichmann
|
||||
*
|
||||
* 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.databind.ObjectMapper;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.BitbucketConfiguration;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.CodeInsightsAnnotation;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.CodeInsightsReport;
|
||||
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 com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.cloud.CloudAnnotation;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.cloud.CloudCreateReportRequest;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import org.sonar.api.ce.posttask.QualityGate;
|
||||
import org.sonar.api.utils.log.Logger;
|
||||
import org.sonar.api.utils.log.Loggers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
|
||||
public class BitbucketCloudClient implements BitbucketClient {
|
||||
private static final Logger LOGGER = Loggers.get(BitbucketCloudClient.class);
|
||||
private static final String REPORT_KEY = "com.github.mc1arke.sonarqube";
|
||||
private static final MediaType APPLICATION_JSON_MEDIA_TYPE = MediaType.get("application/json");
|
||||
private static final String TITLE = "SonarQube";
|
||||
private static final String REPORTER = "SonarQube";
|
||||
private static final String LINK_TEXT = "Go to SonarQube";
|
||||
|
||||
private final BitbucketConfiguration config;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public BitbucketCloudClient(BitbucketConfiguration config, ObjectMapper objectMapper) {
|
||||
this.config = config;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeInsightsAnnotation createCodeInsightsAnnotation(String issueKey, int line, String issueUrl, String message,
|
||||
String path, String severity, String type) {
|
||||
return new CloudAnnotation(issueKey,
|
||||
line,
|
||||
issueUrl,
|
||||
message,
|
||||
path,
|
||||
severity,
|
||||
type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeInsightsReport createCodeInsightsReport(List<ReportData> reportData, String reportDescription,
|
||||
Instant creationDate, String dashboardUrl, String logoUrl,
|
||||
QualityGate.Status status) {
|
||||
return new CloudCreateReportRequest(
|
||||
reportData,
|
||||
reportDescription,
|
||||
TITLE,
|
||||
REPORTER,
|
||||
Date.from(creationDate),
|
||||
dashboardUrl, // you need to change this to a real https URL for local debugging since localhost will get declined by the API
|
||||
logoUrl,
|
||||
"COVERAGE",
|
||||
QualityGate.Status.ERROR.equals(status) ? "FAILED" : "PASSED"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAnnotations(String project, String repo, String commitSha) throws IOException {
|
||||
// not needed here.
|
||||
}
|
||||
|
||||
public void uploadAnnotations(String project, String repository, String commit, Set<CodeInsightsAnnotation> baseAnnotations) throws IOException {
|
||||
Set<CloudAnnotation> annotations = baseAnnotations.stream().map(annotation -> (CloudAnnotation) annotation).collect(Collectors.toSet());
|
||||
|
||||
if (annotations.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Request req = new Request.Builder()
|
||||
.post(RequestBody.create(APPLICATION_JSON_MEDIA_TYPE, objectMapper.writeValueAsString(annotations)))
|
||||
.url(format("%s/2.0/repositories/%s/%s/commit/%s/reports/%s/annotations", config.getUrl(), project, repository, commit, REPORT_KEY))
|
||||
.build();
|
||||
|
||||
LOGGER.info("Creating annotations on bitbucket cloud");
|
||||
LOGGER.debug("Create annotations: " + objectMapper.writeValueAsString(annotations));
|
||||
|
||||
try (Response response = getClient().newCall(req).execute()) {
|
||||
validate(response);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataValue createLinkDataValue(String dashboardUrl) {
|
||||
return new DataValue.CloudLink(LINK_TEXT, dashboardUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uploadReport(String project, String repository, String commit, CodeInsightsReport codeInsightReport) throws IOException {
|
||||
deleteExistingReport(project, repository, commit);
|
||||
|
||||
String body = objectMapper.writeValueAsString(codeInsightReport);
|
||||
Request req = new Request.Builder()
|
||||
.put(RequestBody.create(APPLICATION_JSON_MEDIA_TYPE, body))
|
||||
.url(format("%s/2.0/repositories/%s/%s/commit/%s/reports/%s", config.getUrl(), project, repository, commit, REPORT_KEY))
|
||||
.build();
|
||||
|
||||
LOGGER.info("Create report on bitbucket cloud");
|
||||
LOGGER.debug("Create report: " + body);
|
||||
|
||||
try (Response response = getClient().newCall(req).execute()) {
|
||||
validate(response);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCodeInsights() {
|
||||
return true;
|
||||
}
|
||||
|
||||
void deleteExistingReport(String project, String repository, String commit) throws IOException {
|
||||
Request req = new Request.Builder()
|
||||
.delete()
|
||||
.url(format("%s/2.0/repositories/%s/%s/commit/%s/reports/%s", config.getUrl(), project, repository, commit, REPORT_KEY))
|
||||
.build();
|
||||
|
||||
LOGGER.info("Deleting existing reports on bitbucket cloud");
|
||||
|
||||
try (Response response = getClient().newCall(req).execute()) {
|
||||
// we dont need to validate the output here since most of the time this call will just return a 404
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
OkHttpClient getClient() {
|
||||
return new OkHttpClient.Builder()
|
||||
.addInterceptor(chain -> {
|
||||
Request newRequest = chain.request().newBuilder()
|
||||
.addHeader("Authorization", format("Basic %s", config.getToken()))
|
||||
.addHeader("Accept", APPLICATION_JSON_MEDIA_TYPE.toString())
|
||||
.build();
|
||||
return chain.proceed(newRequest);
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
void validate(Response response) throws IOException {
|
||||
if (!response.isSuccessful()) {
|
||||
String error;
|
||||
if (response.body() != null) {
|
||||
error = response.body().string();
|
||||
} else {
|
||||
error = "Request failed but Bitbucket didn't respond with a proper error message";
|
||||
}
|
||||
|
||||
throw new BitbucketCloudException(response.code(), error);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Marvin Wichmann
|
||||
*
|
||||
* 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;
|
||||
|
||||
public class BitbucketCloudException extends RuntimeException {
|
||||
private final int code;
|
||||
private final String error;
|
||||
|
||||
BitbucketCloudException(int code, String error) {
|
||||
this.code = code;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public boolean isError(int code) {
|
||||
return this.code == code;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return "HTTP Status Code: " + code + "; Message:" + error;
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@
|
||||
*/
|
||||
package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client;
|
||||
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.ErrorResponse;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.server.ErrorResponse;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -0,0 +1,192 @@
|
||||
/*
|
||||
* 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.databind.ObjectMapper;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.BitbucketConfiguration;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.CodeInsightsAnnotation;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.CodeInsightsReport;
|
||||
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 com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.server.Annotation;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.server.CreateAnnotationsRequest;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.server.CreateReportRequest;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.server.ErrorResponse;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.server.ServerProperties;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import org.sonar.api.ce.posttask.QualityGate;
|
||||
import org.sonar.api.utils.log.Logger;
|
||||
import org.sonar.api.utils.log.Loggers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
public class BitbucketServerClient implements BitbucketClient {
|
||||
private static final Logger LOGGER = Loggers.get(BitbucketServerClient.class);
|
||||
private static final String REPORT_KEY = "com.github.mc1arke.sonarqube";
|
||||
private static final MediaType APPLICATION_JSON_MEDIA_TYPE = MediaType.get("application/json");
|
||||
private static final String TITLE = "SonarQube";
|
||||
private static final String REPORTER = "SonarQube";
|
||||
private static final String LINK_TEXT = "Go to SonarQube";
|
||||
|
||||
private final BitbucketConfiguration config;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public BitbucketServerClient(BitbucketConfiguration config, ObjectMapper objectMapper) {
|
||||
this.config = config;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeInsightsAnnotation createCodeInsightsAnnotation(String issueKey, int line, String issueUrl, String message, String path, String severity, String type) {
|
||||
return new Annotation(issueKey,
|
||||
line,
|
||||
issueUrl,
|
||||
message,
|
||||
path,
|
||||
severity,
|
||||
type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeInsightsReport createCodeInsightsReport(List<ReportData> reportData, String reportDescription, Instant creationDate, String dashboardUrl, String logoUrl, QualityGate.Status status) {
|
||||
return new CreateReportRequest(
|
||||
reportData,
|
||||
reportDescription,
|
||||
TITLE,
|
||||
REPORTER,
|
||||
creationDate,
|
||||
dashboardUrl,
|
||||
logoUrl,
|
||||
QualityGate.Status.ERROR.equals(status) ? "FAIL" : "PASS"
|
||||
);
|
||||
}
|
||||
|
||||
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", config.getUrl(), project, repository, commit, REPORT_KEY))
|
||||
.build();
|
||||
try (Response response = getClient().newCall(req).execute()) {
|
||||
validate(response);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uploadAnnotations(String project, String repository, String commit, Set<CodeInsightsAnnotation> annotations) throws IOException {
|
||||
Set<Annotation> annotationSet = annotations.stream().map(annotation -> (Annotation) annotation).collect(Collectors.toSet());
|
||||
CreateAnnotationsRequest request = new CreateAnnotationsRequest(annotationSet);
|
||||
if (request.getAnnotations().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Request req = new Request.Builder()
|
||||
.post(RequestBody.create(APPLICATION_JSON_MEDIA_TYPE, objectMapper.writeValueAsString(request)))
|
||||
.url(format("%s/rest/insights/1.0/projects/%s/repos/%s/commits/%s/reports/%s/annotations", config.getUrl(), project, repository, commit, REPORT_KEY))
|
||||
.build();
|
||||
try (Response response = getClient().newCall(req).execute()) {
|
||||
validate(response);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataValue createLinkDataValue(String dashboardUrl) {
|
||||
return new DataValue.Link(LINK_TEXT, dashboardUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uploadReport(String project, String repository, String commit, CodeInsightsReport codeInsightReport) throws IOException {
|
||||
String body = objectMapper.writeValueAsString(codeInsightReport);
|
||||
Request req = new Request.Builder()
|
||||
.put(RequestBody.create(APPLICATION_JSON_MEDIA_TYPE, body))
|
||||
.url(format("%s/rest/insights/1.0/projects/%s/repos/%s/commits/%s/reports/%s", config.getUrl(), project, repository, commit, REPORT_KEY))
|
||||
.build();
|
||||
|
||||
try (Response response = getClient().newCall(req).execute()) {
|
||||
validate(response);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
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;
|
||||
}
|
||||
|
||||
public ServerProperties getServerProperties() throws IOException {
|
||||
Request req = new Request.Builder()
|
||||
.get()
|
||||
.url(format("%s/rest/api/1.0/application-properties", config.getUrl()))
|
||||
.build();
|
||||
try (Response response = getClient().newCall(req).execute()) {
|
||||
validate(response);
|
||||
|
||||
return objectMapper.reader().forType(ServerProperties.class)
|
||||
.readValue(Optional.ofNullable(response.body())
|
||||
.orElseThrow(() -> new IllegalStateException("No response body from BitBucket"))
|
||||
.string());
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
OkHttpClient getClient() {
|
||||
return new OkHttpClient.Builder()
|
||||
.addInterceptor(chain -> {
|
||||
Request newRequest = chain.request().newBuilder()
|
||||
.addHeader("Authorization", format("Bearer %s", config.getToken()))
|
||||
.addHeader("Accept", APPLICATION_JSON_MEDIA_TYPE.toString())
|
||||
.build();
|
||||
return chain.proceed(newRequest);
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
void validate(Response response) throws IOException {
|
||||
if (!response.isSuccessful()) {
|
||||
ErrorResponse errors = null;
|
||||
if (response.body() != null) {
|
||||
errors = objectMapper.reader().forType(ErrorResponse.class)
|
||||
.readValue(response.body().string());
|
||||
}
|
||||
throw new BitbucketException(response.code(), errors);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Marvin Wichmann
|
||||
*
|
||||
* 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 java.util.Locale;
|
||||
|
||||
public class BitbucketConfiguration {
|
||||
private final String url;
|
||||
private final String token;
|
||||
private final String repository;
|
||||
private final String project;
|
||||
|
||||
public BitbucketConfiguration(String url, String token, String repository, String project) {
|
||||
this.url = url;
|
||||
this.token = token;
|
||||
this.repository = repository;
|
||||
this.project = project;
|
||||
}
|
||||
|
||||
public String getRepository() {
|
||||
return repository;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public String getProject() {
|
||||
return project;
|
||||
}
|
||||
|
||||
public boolean isCloud() {
|
||||
return url.toLowerCase(Locale.ENGLISH).startsWith("https://api.bitbucket.org");
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Marvin Wichmann
|
||||
*
|
||||
* 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.JsonProperty;
|
||||
|
||||
/**
|
||||
* Class for reusing models between the cloud and the server version
|
||||
*/
|
||||
public class CodeInsightsAnnotation {
|
||||
@JsonProperty("line")
|
||||
private final int line;
|
||||
@JsonProperty("message")
|
||||
private final String message;
|
||||
@JsonProperty("path")
|
||||
private final String path;
|
||||
@JsonProperty("severity")
|
||||
private final String severity;
|
||||
|
||||
public CodeInsightsAnnotation(int line, String message, String path, String severity) {
|
||||
this.line = line;
|
||||
this.message = message;
|
||||
this.path = path;
|
||||
this.severity = severity;
|
||||
}
|
||||
|
||||
public int getLine() {
|
||||
return line;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public String getSeverity() {
|
||||
return severity;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Marvin Wichmann
|
||||
*
|
||||
* 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.JsonProperty;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Interface for reusing models between the cloud and the server version
|
||||
*/
|
||||
public class CodeInsightsReport {
|
||||
@JsonProperty("data")
|
||||
private final List<ReportData> data;
|
||||
@JsonProperty("details")
|
||||
private final String details;
|
||||
@JsonProperty("title")
|
||||
private final String title;
|
||||
@JsonProperty("reporter")
|
||||
private final String reporter;
|
||||
@JsonProperty("link")
|
||||
private final String link;
|
||||
@JsonProperty("result")
|
||||
private final String result;
|
||||
|
||||
public CodeInsightsReport(List<ReportData> data, String details, String title, String reporter, String link, String result) {
|
||||
this.data = data;
|
||||
this.details = details;
|
||||
this.title = title;
|
||||
this.reporter = reporter;
|
||||
this.link = link;
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public List<ReportData> getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public String getDetails() {
|
||||
return details;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public String getReporter() {
|
||||
return reporter;
|
||||
}
|
||||
|
||||
public String getLink() {
|
||||
return link;
|
||||
}
|
||||
|
||||
public String getResult() {
|
||||
return result;
|
||||
}
|
||||
}
|
@ -46,6 +46,25 @@ public interface DataValue extends Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
class CloudLink implements DataValue {
|
||||
private final String text;
|
||||
private final String href;
|
||||
|
||||
@JsonCreator
|
||||
public CloudLink(@JsonProperty("text") String text, @JsonProperty("href") String href) {
|
||||
this.text = text;
|
||||
this.href = href;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public String getHref() {
|
||||
return href;
|
||||
}
|
||||
}
|
||||
|
||||
class Text implements DataValue {
|
||||
private final String value;
|
||||
|
||||
|
@ -34,6 +34,16 @@ public class ReportData {
|
||||
this.type = typeFrom(value);
|
||||
}
|
||||
|
||||
private static String typeFrom(DataValue value) {
|
||||
if (value instanceof DataValue.Link || value instanceof DataValue.CloudLink) {
|
||||
return "LINK";
|
||||
} else if (value instanceof DataValue.Percentage) {
|
||||
return "PERCENTAGE";
|
||||
} else {
|
||||
return "TEXT";
|
||||
}
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
@ -45,14 +55,4 @@ public class ReportData {
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
private static String typeFrom(DataValue value) {
|
||||
if (value instanceof DataValue.Link) {
|
||||
return "LINK";
|
||||
} else if (value instanceof DataValue.Percentage) {
|
||||
return "PERCENTAGE";
|
||||
} else {
|
||||
return "TEXT";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Marvin Wichmann
|
||||
*
|
||||
* 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.cloud;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.CodeInsightsAnnotation;
|
||||
|
||||
public class CloudAnnotation extends CodeInsightsAnnotation {
|
||||
@JsonProperty("external_id")
|
||||
private final String externalId;
|
||||
@JsonProperty("summary")
|
||||
private final String link;
|
||||
@JsonProperty("annotation_type")
|
||||
private final String annotationType;
|
||||
|
||||
@JsonCreator
|
||||
public CloudAnnotation(String externalId,
|
||||
int line,
|
||||
String link,
|
||||
String message,
|
||||
String path,
|
||||
String severity,
|
||||
String annotationType) {
|
||||
super(line, message, path, severity);
|
||||
this.externalId = externalId;
|
||||
this.link = link;
|
||||
this.annotationType = annotationType;
|
||||
}
|
||||
|
||||
public String getExternalId() {
|
||||
return externalId;
|
||||
}
|
||||
|
||||
public String getLink() {
|
||||
return link;
|
||||
}
|
||||
|
||||
public String getAnnotationType() {
|
||||
return annotationType;
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Marvin Wichmann
|
||||
*
|
||||
* 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.cloud;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.CodeInsightsReport;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.ReportData;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class CloudCreateReportRequest extends CodeInsightsReport {
|
||||
@JsonProperty("created_on")
|
||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssZ")
|
||||
private final Date createdDate;
|
||||
@JsonProperty("logo_url")
|
||||
private final String logoUrl;
|
||||
@JsonProperty("report_type")
|
||||
private final String reportType;
|
||||
@JsonProperty("remote_link_enabled")
|
||||
private final boolean remoteLinkEnabled;
|
||||
|
||||
@JsonCreator
|
||||
public CloudCreateReportRequest(
|
||||
List<ReportData> data,
|
||||
String details,
|
||||
String title,
|
||||
String reporter,
|
||||
Date createdDate,
|
||||
String link,
|
||||
String logoUrl,
|
||||
String reportType,
|
||||
String result) {
|
||||
super(data, details, title, reporter, link, result);
|
||||
this.createdDate = createdDate;
|
||||
this.logoUrl = logoUrl;
|
||||
this.reportType = reportType;
|
||||
this.remoteLinkEnabled = true;
|
||||
}
|
||||
|
||||
public Date getCreatedDate() {
|
||||
return createdDate;
|
||||
}
|
||||
|
||||
public String getLogoUrl() {
|
||||
return logoUrl;
|
||||
}
|
||||
|
||||
public String getReportType() {
|
||||
return reportType;
|
||||
}
|
||||
|
||||
public Boolean getRemoteLinkEnabled() {
|
||||
return remoteLinkEnabled;
|
||||
}
|
||||
}
|
@ -16,36 +16,30 @@
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model;
|
||||
package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.server;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.CodeInsightsAnnotation;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class Annotation implements Serializable {
|
||||
public class Annotation extends CodeInsightsAnnotation {
|
||||
private final String externalId;
|
||||
private final int line;
|
||||
|
||||
private final String link;
|
||||
private final String message;
|
||||
private final String path;
|
||||
private final String severity;
|
||||
|
||||
private final String type;
|
||||
|
||||
@JsonCreator
|
||||
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,
|
||||
String link,
|
||||
String message,
|
||||
String path,
|
||||
String severity,
|
||||
@JsonProperty("type") String type) {
|
||||
super(line, message, path, severity);
|
||||
this.externalId = externalId;
|
||||
this.line = line;
|
||||
this.link = link;
|
||||
this.message = message;
|
||||
this.path = path;
|
||||
this.severity = severity;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@ -53,26 +47,10 @@ public class Annotation implements Serializable {
|
||||
return externalId;
|
||||
}
|
||||
|
||||
public int getLine() {
|
||||
return line;
|
||||
}
|
||||
|
||||
public String getLink() {
|
||||
return link;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public String getSeverity() {
|
||||
return severity;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
@ -16,7 +16,7 @@
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model;
|
||||
package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.server;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
@ -16,23 +16,20 @@
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model;
|
||||
package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.server;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.CodeInsightsReport;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.ReportData;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
public class CreateReportRequest {
|
||||
private final List<ReportData> data;
|
||||
private final String details;
|
||||
private final String title;
|
||||
private final String reporter;
|
||||
public class CreateReportRequest extends CodeInsightsReport {
|
||||
|
||||
private final Instant createdDate;
|
||||
private final String link;
|
||||
private final String logoUrl;
|
||||
private final String result;
|
||||
|
||||
@JsonCreator
|
||||
public CreateReportRequest(
|
||||
@ -44,45 +41,16 @@ public class CreateReportRequest {
|
||||
@JsonProperty("link") String link,
|
||||
@JsonProperty("logoUrl") String logoUrl,
|
||||
@JsonProperty("result") String result) {
|
||||
this.data = data;
|
||||
this.details = details;
|
||||
this.title = title;
|
||||
this.reporter = reporter;
|
||||
super(data, details, title, reporter, link, result);
|
||||
this.createdDate = createdDate;
|
||||
this.link = link;
|
||||
this.logoUrl = logoUrl;
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
public List<ReportData> 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;
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model;
|
||||
package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.server;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
@ -27,7 +27,7 @@ import java.util.Set;
|
||||
public class ErrorResponse implements Serializable {
|
||||
private final Set<Error> errors;
|
||||
|
||||
ErrorResponse(@JsonProperty("errors") Set<Error> errors) {
|
||||
public ErrorResponse(@JsonProperty("errors") Set<Error> errors) {
|
||||
this.errors = errors;
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ public class ErrorResponse implements Serializable {
|
||||
|
||||
private final String message;
|
||||
|
||||
Error(@JsonProperty("message") String message) {
|
||||
public Error(@JsonProperty("message") String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
@ -16,7 +16,7 @@
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model;
|
||||
package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.server;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
@ -65,7 +65,7 @@ public class CommunityBranchPluginBootstrapTest {
|
||||
|
||||
@Test
|
||||
public void testDefineInvokedOnSuccessLoad() throws ClassNotFoundException {
|
||||
Plugin.Context context = spy(mock(Plugin.Context.class));
|
||||
Plugin.Context context = mock(Plugin.Context.class);
|
||||
Configuration configuration = mock(Configuration.class);
|
||||
when(context.getBootConfiguration()).thenReturn(configuration);
|
||||
when(configuration.get(any())).thenReturn(Optional.empty());
|
||||
@ -92,7 +92,7 @@ public class CommunityBranchPluginBootstrapTest {
|
||||
|
||||
@Test
|
||||
public void testDefineNotInvokedForNonScanner() throws ClassNotFoundException {
|
||||
Plugin.Context context = spy(mock(Plugin.Context.class));
|
||||
Plugin.Context context = mock(Plugin.Context.class);
|
||||
Configuration configuration = mock(Configuration.class);
|
||||
when(context.getBootConfiguration()).thenReturn(configuration);
|
||||
when(configuration.get(any())).thenReturn(Optional.empty());
|
||||
|
@ -62,7 +62,7 @@ public class CommunityBranchPluginTest {
|
||||
public void testScannerSideDefine() {
|
||||
final CommunityBranchPlugin testCase = new CommunityBranchPlugin();
|
||||
|
||||
final Plugin.Context context = spy(mock(Plugin.Context.class, Mockito.RETURNS_DEEP_STUBS));
|
||||
final Plugin.Context context = mock(Plugin.Context.class, Mockito.RETURNS_DEEP_STUBS);
|
||||
when(context.getRuntime().getSonarQubeSide()).thenReturn(SonarQubeSide.SCANNER);
|
||||
|
||||
testCase.define(context);
|
||||
@ -81,7 +81,7 @@ public class CommunityBranchPluginTest {
|
||||
public void testNonScannerSideDefine() {
|
||||
final CommunityBranchPlugin testCase = new CommunityBranchPlugin();
|
||||
|
||||
final Plugin.Context context = spy(mock(Plugin.Context.class, Mockito.RETURNS_DEEP_STUBS));
|
||||
final Plugin.Context context = mock(Plugin.Context.class, Mockito.RETURNS_DEEP_STUBS);
|
||||
when(context.getRuntime().getSonarQubeSide()).thenReturn(SonarQubeSide.SERVER);
|
||||
|
||||
testCase.define(context);
|
||||
@ -93,7 +93,7 @@ public class CommunityBranchPluginTest {
|
||||
public void testComputeEngineSideLoad() {
|
||||
final CommunityBranchPlugin testCase = new CommunityBranchPlugin();
|
||||
|
||||
final CoreExtension.Context context = spy(mock(CoreExtension.Context.class, Mockito.RETURNS_DEEP_STUBS));
|
||||
final CoreExtension.Context context = mock(CoreExtension.Context.class, Mockito.RETURNS_DEEP_STUBS);
|
||||
when(context.getRuntime().getSonarQubeSide()).thenReturn(SonarQubeSide.COMPUTE_ENGINE);
|
||||
|
||||
testCase.load(context);
|
||||
|
@ -32,7 +32,7 @@ public class CommunityReportAnalysisComponentProviderTest {
|
||||
@Test
|
||||
public void testGetComponents() {
|
||||
List<Object> result = new CommunityReportAnalysisComponentProvider().getComponents();
|
||||
assertEquals(10, result.size());
|
||||
assertEquals(9, result.size());
|
||||
assertEquals(CommunityBranchLoaderDelegate.class, result.get(0));
|
||||
}
|
||||
}
|
||||
|
@ -3,13 +3,10 @@ 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.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 com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.BitbucketConfiguration;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.sonar.api.ce.posttask.QualityGate;
|
||||
import org.sonar.api.issue.Issue;
|
||||
@ -24,14 +21,13 @@ import org.sonar.db.alm.setting.ProjectAlmSettingDto;
|
||||
|
||||
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.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@ -56,7 +52,12 @@ public class BitbucketPullRequestDecoratorTest {
|
||||
|
||||
private BitbucketClient client = mock(BitbucketClient.class);
|
||||
|
||||
private BitbucketServerPullRequestDecorator underTest = new BitbucketServerPullRequestDecorator(client);
|
||||
private BitbucketPullRequestDecorator underTest = new BitbucketPullRequestDecorator() {
|
||||
@Override
|
||||
BitbucketClient createClient(BitbucketConfiguration bitbucketConfiguration) {
|
||||
return client;
|
||||
}
|
||||
};
|
||||
|
||||
private final AlmSettingDto almSettingDto = mock(AlmSettingDto.class);
|
||||
private final ProjectAlmSettingDto projectAlmSettingDto = mock(ProjectAlmSettingDto.class);
|
||||
@ -69,23 +70,15 @@ public class BitbucketPullRequestDecoratorTest {
|
||||
|
||||
@Test
|
||||
public void testValidAnalysis() throws IOException {
|
||||
when(client.supportsCodeInsights(eq(almSettingDto))).thenReturn(true);
|
||||
when(client.supportsCodeInsights()).thenReturn(true);
|
||||
|
||||
mockValidAnalysis();
|
||||
final ArgumentCaptor<CreateReportRequest> reportCaptor = ArgumentCaptor.forClass(CreateReportRequest.class);
|
||||
final ArgumentCaptor<CreateAnnotationsRequest> annotationsCaptor = ArgumentCaptor.forClass(CreateAnnotationsRequest.class);
|
||||
|
||||
underTest.decorateQualityGateStatus(analysisDetails, almSettingDto, projectAlmSettingDto);
|
||||
|
||||
verify(client).createReport(eq(PROJECT), eq(REPO), eq(COMMIT), reportCaptor.capture(), eq(almSettingDto));
|
||||
verifyExpectedReport(reportCaptor.getValue());
|
||||
|
||||
verify(client).deleteAnnotations(PROJECT, REPO, COMMIT, almSettingDto);
|
||||
verify(client).createAnnotations(eq(PROJECT), eq(REPO), eq(COMMIT), annotationsCaptor.capture(), eq(almSettingDto));
|
||||
|
||||
CreateAnnotationsRequest actualAnnotations = annotationsCaptor.getValue();
|
||||
assertThat(actualAnnotations.getAnnotations()).size().isEqualTo(1);
|
||||
verifyExpectedAnnotation(actualAnnotations.getAnnotations().iterator().next());
|
||||
verify(client).createCodeInsightsAnnotation(eq(ISSUE_KEY), eq(ISSUE_LINE), eq(ISSUE_LINK), eq(ISSUE_MESSAGE), eq(ISSUE_PATH), eq("HIGH"), eq("BUG"));
|
||||
verify(client).createLinkDataValue(DASHBOARD_URL);
|
||||
verify(client).createCodeInsightsReport(any(), eq("Quality Gate passed" + System.lineSeparator()), any(), eq(DASHBOARD_URL), eq(String.format("%s/common/icon.png", IMAGE_URL)), eq(QualityGate.Status.OK));
|
||||
verify(client).deleteAnnotations(PROJECT, REPO, COMMIT);
|
||||
}
|
||||
|
||||
private void mockValidAnalysis() {
|
||||
@ -131,25 +124,4 @@ public class BitbucketPullRequestDecoratorTest {
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,35 @@
|
||||
package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client;
|
||||
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.BitbucketConfiguration;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class BitbucketClientFactoryUnitTest {
|
||||
|
||||
@Test
|
||||
public void testCreateClientIsCloudIfUrlMatches() {
|
||||
// given
|
||||
BitbucketConfiguration configuration = new BitbucketConfiguration("https://api.bitbucket.org", "token", "repository", "project");
|
||||
|
||||
// when
|
||||
BitbucketClient client = BitbucketClientFactory.createClient(configuration);
|
||||
|
||||
// then
|
||||
assertTrue(client instanceof BitbucketCloudClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateClientIsServerIfNotApiUrl() {
|
||||
// given
|
||||
BitbucketConfiguration configuration = new BitbucketConfiguration("https://api.server.org", "token", "repository", "project");
|
||||
|
||||
// when
|
||||
BitbucketClient client = BitbucketClientFactory.createClient(configuration);
|
||||
|
||||
// then
|
||||
assertTrue(client instanceof BitbucketServerClient);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,223 @@
|
||||
package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectReader;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.BitbucketConfiguration;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.CodeInsightsAnnotation;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.CodeInsightsReport;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.DataValue;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.cloud.CloudAnnotation;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.cloud.CloudCreateReportRequest;
|
||||
import com.google.common.collect.Sets;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.sonar.api.ce.posttask.QualityGate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.internal.verification.VerificationModeFactory.times;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class BitbucketCloudClientUnitTest {
|
||||
|
||||
private BitbucketCloudClient underTest;
|
||||
|
||||
@Mock
|
||||
private ObjectMapper mapper;
|
||||
|
||||
@Mock
|
||||
private OkHttpClient client;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
BitbucketConfiguration config = new BitbucketConfiguration("https://api.bitbucket.org", "token", "repository", "project");
|
||||
underTest = new BitbucketCloudClient(config, mapper) {
|
||||
@Override
|
||||
OkHttpClient getClient() {
|
||||
return client;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUploadReport() throws IOException {
|
||||
// given
|
||||
CodeInsightsReport report = mock(CodeInsightsReport.class);
|
||||
Call call = mock(Call.class);
|
||||
Response response = mock(Response.class);
|
||||
ArgumentCaptor<Request> captor = ArgumentCaptor.forClass(Request.class);
|
||||
|
||||
when(client.newCall(any())).thenReturn(call);
|
||||
when(call.execute()).thenReturn(response);
|
||||
when(response.isSuccessful()).thenReturn(true);
|
||||
|
||||
when(mapper.writeValueAsString(report)).thenReturn("{payload}");
|
||||
|
||||
// when
|
||||
underTest.uploadReport("project", "repository", "commit", report);
|
||||
|
||||
// then
|
||||
verify(client, times(2)).newCall(captor.capture());
|
||||
Request request = captor.getValue();
|
||||
assertEquals("PUT", request.method());
|
||||
assertEquals("https://api.bitbucket.org/2.0/repositories/project/repository/commit/commit/reports/com.github.mc1arke.sonarqube", request.url().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteReport() throws IOException {
|
||||
// given
|
||||
Call call = mock(Call.class);
|
||||
Response response = mock(Response.class);
|
||||
ArgumentCaptor<Request> captor = ArgumentCaptor.forClass(Request.class);
|
||||
|
||||
when(client.newCall(any())).thenReturn(call);
|
||||
when(call.execute()).thenReturn(response);
|
||||
|
||||
// when
|
||||
underTest.deleteExistingReport("project", "repository", "commit");
|
||||
|
||||
// then
|
||||
verify(client, times(1)).newCall(captor.capture());
|
||||
Request request = captor.getValue();
|
||||
assertEquals("DELETE", request.method());
|
||||
assertEquals("https://api.bitbucket.org/2.0/repositories/project/repository/commit/commit/reports/com.github.mc1arke.sonarqube", request.url().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUploadAnnotations() throws IOException {
|
||||
// given
|
||||
CodeInsightsAnnotation annotation = mock(CloudAnnotation.class);
|
||||
Set<CodeInsightsAnnotation> annotations = Sets.newHashSet(annotation);
|
||||
Call call = mock(Call.class);
|
||||
Response response = mock(Response.class);
|
||||
ArgumentCaptor<Request> captor = ArgumentCaptor.forClass(Request.class);
|
||||
|
||||
when(client.newCall(any())).thenReturn(call);
|
||||
when(call.execute()).thenReturn(response);
|
||||
when(response.isSuccessful()).thenReturn(true);
|
||||
|
||||
when(mapper.writeValueAsString(any())).thenReturn("{payload}");
|
||||
|
||||
// when
|
||||
underTest.uploadAnnotations("project", "repository", "commit", annotations);
|
||||
|
||||
// then
|
||||
verify(client, times(1)).newCall(captor.capture());
|
||||
Request request = captor.getValue();
|
||||
assertEquals("POST", request.method());
|
||||
assertEquals("https://api.bitbucket.org/2.0/repositories/project/repository/commit/commit/reports/com.github.mc1arke.sonarqube/annotations", request.url().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUploadReportFailsWithMessage() throws IOException {
|
||||
// given
|
||||
CodeInsightsReport report = mock(CodeInsightsReport.class);
|
||||
Call call = mock(Call.class);
|
||||
Response response = mock(Response.class);
|
||||
ResponseBody responseBody = mock(ResponseBody.class);
|
||||
|
||||
when(client.newCall(any())).thenReturn(call);
|
||||
when(call.execute()).thenReturn(response);
|
||||
when(response.isSuccessful()).thenReturn(false);
|
||||
when(response.body()).thenReturn(responseBody);
|
||||
when(responseBody.string()).thenReturn("error!");
|
||||
when(response.code()).thenReturn(400);
|
||||
|
||||
when(mapper.writeValueAsString(report)).thenReturn("{payload}");
|
||||
|
||||
// when,then
|
||||
assertThatThrownBy(() -> underTest.uploadReport("project", "repository", "commit", report))
|
||||
.isInstanceOf(BitbucketCloudException.class)
|
||||
.hasMessage("HTTP Status Code: 400; Message:error!")
|
||||
.extracting(e -> ((BitbucketCloudException) e).isError(400))
|
||||
.isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUploadAnnotationsWithEmptyAnnotations() throws IOException {
|
||||
// given
|
||||
Set<CodeInsightsAnnotation> annotations = Sets.newHashSet();
|
||||
|
||||
// when
|
||||
underTest.uploadAnnotations("project", "repository", "commit", annotations);
|
||||
|
||||
// then
|
||||
verify(client, times(0)).newCall(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateAnnotationForCloud() {
|
||||
// given
|
||||
|
||||
// when
|
||||
CodeInsightsAnnotation annotation = underTest.createCodeInsightsAnnotation("issueKey", 12, "http://localhost:9000/dashboard", "Failed", "/path/to/file", "MAJOR", "BUG");
|
||||
|
||||
// then
|
||||
assertTrue(annotation instanceof CloudAnnotation);
|
||||
assertEquals("issueKey", ((CloudAnnotation) annotation).getExternalId());
|
||||
assertEquals(12, ((CloudAnnotation) annotation).getLine());
|
||||
assertEquals("http://localhost:9000/dashboard", ((CloudAnnotation) annotation).getLink());
|
||||
assertEquals("/path/to/file", ((CloudAnnotation) annotation).getPath());
|
||||
assertEquals("MAJOR", ((CloudAnnotation) annotation).getSeverity());
|
||||
assertEquals("BUG", ((CloudAnnotation) annotation).getAnnotationType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDataLinkForCloud() {
|
||||
// given
|
||||
|
||||
// when
|
||||
DataValue data = underTest.createLinkDataValue("https://localhost:9000/any/project");
|
||||
|
||||
// then
|
||||
assertTrue(data instanceof DataValue.CloudLink);
|
||||
assertEquals("https://localhost:9000/any/project", ((DataValue.CloudLink) data).getHref());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCloudAlwaysSupportsCodeInsights() {
|
||||
// given
|
||||
|
||||
// when
|
||||
boolean result = underTest.supportsCodeInsights();
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateCloudReport() {
|
||||
// given
|
||||
|
||||
// when
|
||||
CodeInsightsReport result = underTest.createCodeInsightsReport(new ArrayList<>(), "reportDescription", Instant.now(), "dashboardUrl", "logoUrl", QualityGate.Status.ERROR);
|
||||
|
||||
// then
|
||||
assertTrue(result instanceof CloudCreateReportRequest);
|
||||
assertEquals(0, ((CloudCreateReportRequest) result).getData().size());
|
||||
assertEquals("reportDescription", ((CloudCreateReportRequest) result).getDetails());
|
||||
assertEquals("dashboardUrl", ((CloudCreateReportRequest) result).getLink());
|
||||
assertEquals("logoUrl", ((CloudCreateReportRequest) result).getLogoUrl());
|
||||
assertEquals("FAILED", ((CloudCreateReportRequest) result).getResult());
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,388 @@
|
||||
package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectReader;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.BitbucketConfiguration;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.CodeInsightsAnnotation;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.CodeInsightsReport;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.DataValue;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.server.Annotation;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.server.CreateReportRequest;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.server.ErrorResponse;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model.server.ServerProperties;
|
||||
import com.google.common.collect.Sets;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.sonar.api.ce.posttask.QualityGate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.internal.verification.VerificationModeFactory.times;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class BitbucketServerClientUnitTest {
|
||||
|
||||
private BitbucketServerClient underTest;
|
||||
|
||||
@Mock
|
||||
private ObjectMapper mapper;
|
||||
|
||||
@Mock
|
||||
private OkHttpClient client;
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
BitbucketConfiguration config = new BitbucketConfiguration("https://my-server.org", "token", "repository", "project");
|
||||
underTest = new BitbucketServerClient(config, mapper) {
|
||||
@Override
|
||||
OkHttpClient getClient() {
|
||||
return client;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSupportsCodeInsightsIsFalse() throws IOException {
|
||||
// given
|
||||
ServerProperties serverProperties = new ServerProperties("5.0");
|
||||
|
||||
Call call = mock(Call.class);
|
||||
Response response = mock(Response.class);
|
||||
ObjectReader reader = mock(ObjectReader.class);
|
||||
ResponseBody responseBody = mock(ResponseBody.class);
|
||||
|
||||
when(client.newCall(any())).thenReturn(call);
|
||||
when(call.execute()).thenReturn(response);
|
||||
when(response.isSuccessful()).thenReturn(true);
|
||||
when(response.body()).thenReturn(responseBody);
|
||||
when(responseBody.string()).thenReturn("test");
|
||||
|
||||
when(mapper.reader()).thenReturn(reader);
|
||||
when(reader.forType(ServerProperties.class)).thenReturn(reader);
|
||||
when(reader.readValue(any(String.class))).thenReturn(serverProperties);
|
||||
|
||||
// when
|
||||
boolean result = underTest.supportsCodeInsights();
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSupportsCodeInsightsIsTrueWhenVersionEqual() throws IOException {
|
||||
// given
|
||||
ServerProperties serverProperties = new ServerProperties("5.15");
|
||||
|
||||
Call call = mock(Call.class);
|
||||
Response response = mock(Response.class);
|
||||
ObjectReader reader = mock(ObjectReader.class);
|
||||
ResponseBody responseBody = mock(ResponseBody.class);
|
||||
|
||||
when(client.newCall(any())).thenReturn(call);
|
||||
when(call.execute()).thenReturn(response);
|
||||
when(response.isSuccessful()).thenReturn(true);
|
||||
when(response.body()).thenReturn(responseBody);
|
||||
when(responseBody.string()).thenReturn("test");
|
||||
|
||||
when(mapper.reader()).thenReturn(reader);
|
||||
when(reader.forType(ServerProperties.class)).thenReturn(reader);
|
||||
when(reader.readValue(any(String.class))).thenReturn(serverProperties);
|
||||
|
||||
// when
|
||||
boolean result = underTest.supportsCodeInsights();
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSupportsCodeInsightsIsTrueIfVersionIsHigher() throws IOException {
|
||||
// given
|
||||
ServerProperties serverProperties = new ServerProperties("6.0");
|
||||
|
||||
Call call = mock(Call.class);
|
||||
Response response = mock(Response.class);
|
||||
ObjectReader reader = mock(ObjectReader.class);
|
||||
ResponseBody responseBody = mock(ResponseBody.class);
|
||||
|
||||
when(client.newCall(any())).thenReturn(call);
|
||||
when(call.execute()).thenReturn(response);
|
||||
when(response.isSuccessful()).thenReturn(true);
|
||||
when(response.body()).thenReturn(responseBody);
|
||||
when(responseBody.string()).thenReturn("test");
|
||||
|
||||
when(mapper.reader()).thenReturn(reader);
|
||||
when(reader.forType(ServerProperties.class)).thenReturn(reader);
|
||||
when(reader.readValue(any(String.class))).thenReturn(serverProperties);
|
||||
|
||||
// when
|
||||
boolean result = underTest.supportsCodeInsights();
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSupportsCodeInsightsIsFalseWhenException() throws IOException {
|
||||
// given
|
||||
Call call = mock(Call.class);
|
||||
when(client.newCall(any())).thenReturn(call);
|
||||
when(call.execute()).thenThrow(new IOException());
|
||||
|
||||
// when
|
||||
boolean result = underTest.supportsCodeInsights();
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetServerProperties() throws IOException {
|
||||
// given
|
||||
ServerProperties serverProperties = new ServerProperties("5.0");
|
||||
|
||||
Call call = mock(Call.class);
|
||||
Response response = mock(Response.class);
|
||||
ObjectReader reader = mock(ObjectReader.class);
|
||||
ResponseBody responseBody = mock(ResponseBody.class);
|
||||
ArgumentCaptor<Request> captor = ArgumentCaptor.forClass(Request.class);
|
||||
|
||||
when(client.newCall(any())).thenReturn(call);
|
||||
when(call.execute()).thenReturn(response);
|
||||
when(response.isSuccessful()).thenReturn(true);
|
||||
when(response.body()).thenReturn(responseBody);
|
||||
when(responseBody.string()).thenReturn("{version: '5.0'}");
|
||||
|
||||
when(mapper.reader()).thenReturn(reader);
|
||||
when(reader.forType(ServerProperties.class)).thenReturn(reader);
|
||||
when(reader.readValue(any(String.class))).thenReturn(serverProperties);
|
||||
|
||||
// when
|
||||
ServerProperties result = underTest.getServerProperties();
|
||||
|
||||
// then
|
||||
verify(client, times(1)).newCall(captor.capture());
|
||||
Request request = captor.getValue();
|
||||
assertEquals("GET", request.method());
|
||||
assertEquals("https://my-server.org/rest/api/1.0/application-properties", request.url().toString());
|
||||
assertEquals("5.0", result.getVersion());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetServerPropertiesError() throws IOException {
|
||||
// given
|
||||
Call call = mock(Call.class);
|
||||
Response response = mock(Response.class);
|
||||
ObjectReader reader = mock(ObjectReader.class);
|
||||
|
||||
when(client.newCall(any())).thenReturn(call);
|
||||
when(call.execute()).thenReturn(response);
|
||||
when(response.isSuccessful()).thenReturn(true);
|
||||
when(response.body()).thenReturn(null);
|
||||
|
||||
when(mapper.reader()).thenReturn(reader);
|
||||
when(reader.forType(ServerProperties.class)).thenReturn(reader);
|
||||
|
||||
// when, then
|
||||
assertThatThrownBy(() -> underTest.getServerProperties())
|
||||
.isInstanceOf(IllegalStateException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUploadReport() throws IOException {
|
||||
// given
|
||||
CodeInsightsReport report = mock(CodeInsightsReport.class);
|
||||
Call call = mock(Call.class);
|
||||
Response response = mock(Response.class);
|
||||
ArgumentCaptor<Request> captor = ArgumentCaptor.forClass(Request.class);
|
||||
|
||||
when(client.newCall(any())).thenReturn(call);
|
||||
when(call.execute()).thenReturn(response);
|
||||
when(response.isSuccessful()).thenReturn(true);
|
||||
|
||||
when(mapper.writeValueAsString(report)).thenReturn("{payload}");
|
||||
|
||||
// when
|
||||
underTest.uploadReport("project", "repository", "commit", report);
|
||||
|
||||
// then
|
||||
verify(client, times(1)).newCall(captor.capture());
|
||||
Request request = captor.getValue();
|
||||
assertEquals("PUT", request.method());
|
||||
assertEquals("https://my-server.org/rest/insights/1.0/projects/project/repos/repository/commits/commit/reports/com.github.mc1arke.sonarqube", request.url().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUploadReportFails() throws IOException {
|
||||
// given
|
||||
CodeInsightsReport report = mock(CodeInsightsReport.class);
|
||||
Call call = mock(Call.class);
|
||||
Response response = mock(Response.class);
|
||||
|
||||
when(client.newCall(any())).thenReturn(call);
|
||||
when(call.execute()).thenReturn(response);
|
||||
when(response.isSuccessful()).thenReturn(false);
|
||||
when(response.body()).thenReturn(null);
|
||||
|
||||
when(mapper.writeValueAsString(report)).thenReturn("{payload}");
|
||||
|
||||
// when,then
|
||||
assertThatThrownBy(() -> underTest.uploadReport("project", "repository", "commit", report))
|
||||
.isInstanceOf(BitbucketException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUploadReportFailsWithMessage() throws IOException {
|
||||
// given
|
||||
ErrorResponse.Error error = new ErrorResponse.Error("error!");
|
||||
ErrorResponse errorResponse = new ErrorResponse(Sets.newHashSet(error));
|
||||
|
||||
CodeInsightsReport report = mock(CodeInsightsReport.class);
|
||||
Call call = mock(Call.class);
|
||||
Response response = mock(Response.class);
|
||||
ResponseBody responseBody = mock(ResponseBody.class);
|
||||
ObjectReader reader = mock(ObjectReader.class);
|
||||
|
||||
when(client.newCall(any())).thenReturn(call);
|
||||
when(call.execute()).thenReturn(response);
|
||||
when(response.isSuccessful()).thenReturn(false);
|
||||
when(response.body()).thenReturn(responseBody);
|
||||
when(responseBody.string()).thenReturn("error!");
|
||||
when(response.code()).thenReturn(400);
|
||||
|
||||
when(mapper.writeValueAsString(report)).thenReturn("{payload}");
|
||||
|
||||
when(mapper.reader()).thenReturn(reader);
|
||||
when(reader.forType(ErrorResponse.class)).thenReturn(reader);
|
||||
when(reader.readValue(any(String.class))).thenReturn(errorResponse);
|
||||
|
||||
|
||||
// when,then
|
||||
assertThatThrownBy(() -> underTest.uploadReport("project", "repository", "commit", report))
|
||||
.isInstanceOf(BitbucketException.class)
|
||||
.hasMessage("error!")
|
||||
.extracting(e -> ((BitbucketException) e).isError(400))
|
||||
.isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUploadAnnotations() throws IOException {
|
||||
// given
|
||||
CodeInsightsAnnotation annotation = mock(Annotation.class);
|
||||
Set<CodeInsightsAnnotation> annotations = Sets.newHashSet(annotation);
|
||||
Call call = mock(Call.class);
|
||||
Response response = mock(Response.class);
|
||||
ArgumentCaptor<Request> captor = ArgumentCaptor.forClass(Request.class);
|
||||
|
||||
when(client.newCall(any())).thenReturn(call);
|
||||
when(call.execute()).thenReturn(response);
|
||||
when(response.isSuccessful()).thenReturn(true);
|
||||
|
||||
when(mapper.writeValueAsString(any())).thenReturn("{payload}");
|
||||
|
||||
// when
|
||||
underTest.uploadAnnotations("project", "repository", "commit", annotations);
|
||||
|
||||
// then
|
||||
verify(client, times(1)).newCall(captor.capture());
|
||||
Request request = captor.getValue();
|
||||
assertEquals("POST", request.method());
|
||||
assertEquals("https://my-server.org/rest/insights/1.0/projects/project/repos/repository/commits/commit/reports/com.github.mc1arke.sonarqube/annotations", request.url().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUploadAnnotationsWithEmptyAnnotations() throws IOException {
|
||||
// given
|
||||
Set<CodeInsightsAnnotation> annotations = Sets.newHashSet();
|
||||
|
||||
// when
|
||||
underTest.uploadAnnotations("project", "repository", "commit", annotations);
|
||||
|
||||
// then
|
||||
verify(client, times(0)).newCall(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteAnnotations() throws IOException {
|
||||
// given
|
||||
Call call = mock(Call.class);
|
||||
Response response = mock(Response.class);
|
||||
ArgumentCaptor<Request> captor = ArgumentCaptor.forClass(Request.class);
|
||||
|
||||
when(client.newCall(any())).thenReturn(call);
|
||||
when(call.execute()).thenReturn(response);
|
||||
when(response.isSuccessful()).thenReturn(true);
|
||||
|
||||
// when
|
||||
underTest.deleteAnnotations("project", "repository", "commit");
|
||||
|
||||
// then
|
||||
verify(client, times(1)).newCall(captor.capture());
|
||||
Request request = captor.getValue();
|
||||
assertEquals("DELETE", request.method());
|
||||
assertEquals("https://my-server.org/rest/insights/1.0/projects/project/repos/repository/commits/commit/reports/com.github.mc1arke.sonarqube/annotations", request.url().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateAnnotationForServer() {
|
||||
// given
|
||||
// when
|
||||
CodeInsightsAnnotation annotation = underTest.createCodeInsightsAnnotation("issueKey", 12, "http://localhost:9000/dashboard", "Failed", "/path/to/file", "MAJOR", "BUG");
|
||||
|
||||
// then
|
||||
assertTrue(annotation instanceof Annotation);
|
||||
assertEquals("issueKey", ((Annotation) annotation).getExternalId());
|
||||
assertEquals(12, ((Annotation) annotation).getLine());
|
||||
assertEquals("http://localhost:9000/dashboard", ((Annotation) annotation).getLink());
|
||||
assertEquals("/path/to/file", ((Annotation) annotation).getPath());
|
||||
assertEquals("MAJOR", ((Annotation) annotation).getSeverity());
|
||||
assertEquals("BUG", ((Annotation) annotation).getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDataLinkForServer() {
|
||||
// given
|
||||
// when
|
||||
DataValue data = underTest.createLinkDataValue("https://localhost:9000/any/project");
|
||||
|
||||
// then
|
||||
assertTrue(data instanceof DataValue.Link);
|
||||
assertEquals("https://localhost:9000/any/project", ((DataValue.Link) data).getHref());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateCloudReport() {
|
||||
// given
|
||||
|
||||
// when
|
||||
CodeInsightsReport result = underTest.createCodeInsightsReport(new ArrayList<>(), "reportDescription", Instant.now(), "dashboardUrl", "logoUrl", QualityGate.Status.ERROR);
|
||||
|
||||
// then
|
||||
assertTrue(result instanceof CreateReportRequest);
|
||||
assertEquals(0, ((CreateReportRequest) result).getData().size());
|
||||
assertEquals("reportDescription", ((CreateReportRequest) result).getDetails());
|
||||
assertEquals("dashboardUrl", ((CreateReportRequest) result).getLink());
|
||||
assertEquals("logoUrl", ((CreateReportRequest) result).getLogoUrl());
|
||||
assertEquals("FAIL", ((CreateReportRequest) result).getResult());
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket.client.model;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class BitbucketConfigurationUnitTest {
|
||||
|
||||
@Test
|
||||
public void testIsCloudTrue() {
|
||||
// given
|
||||
BitbucketConfiguration configuration = new BitbucketConfiguration("https://api.bitbucket.org", "token", "repository", "project");
|
||||
|
||||
// when
|
||||
boolean result = configuration.isCloud();
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
assertEquals("token", configuration.getToken());
|
||||
assertEquals("repository", configuration.getRepository());
|
||||
assertEquals("https://api.bitbucket.org", configuration.getUrl());
|
||||
assertEquals("project", configuration.getProject());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsCloudTrueForOtherCasing() {
|
||||
// given
|
||||
BitbucketConfiguration configuration = new BitbucketConfiguration("https://API.BITBUCKET.org", "token", "repository", "project");
|
||||
|
||||
// when
|
||||
boolean result = configuration.isCloud();
|
||||
|
||||
// then
|
||||
assertTrue(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsCloudReturnsFalseForServerVersion() {
|
||||
// given
|
||||
BitbucketConfiguration configuration = new BitbucketConfiguration("https://API.server.org", "token", "repository", "project");
|
||||
|
||||
// when
|
||||
boolean result = configuration.isCloud();
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
}
|
@ -191,7 +191,7 @@ public class CommunityBranchSupportDelegateTest {
|
||||
when(componentKey.getBranch()).thenReturn(Optional.of(new BranchSupport.Branch("dummy", BranchType.BRANCH)));
|
||||
when(componentKey.getPullRequestKey()).thenReturn(Optional.empty());
|
||||
|
||||
ComponentDao componentDao = spy(mock(ComponentDao.class));
|
||||
ComponentDao componentDao = mock(ComponentDao.class);
|
||||
|
||||
DbClient dbClient = mock(DbClient.class);
|
||||
when(dbClient.componentDao()).thenReturn(componentDao);
|
||||
@ -273,7 +273,7 @@ public class CommunityBranchSupportDelegateTest {
|
||||
when(componentDto.getKey()).thenReturn("componentKey");
|
||||
when(componentDto.uuid()).thenReturn("componentUuid");
|
||||
|
||||
ComponentDto copyComponentDto = spy(ComponentDto.class);
|
||||
ComponentDto copyComponentDto = mock(ComponentDto.class);
|
||||
when(componentDto.copy()).thenReturn(copyComponentDto);
|
||||
|
||||
BranchDto branchDto = mock(BranchDto.class);
|
||||
@ -290,7 +290,7 @@ public class CommunityBranchSupportDelegateTest {
|
||||
when(componentKey.getBranch()).thenReturn(Optional.empty());
|
||||
when(componentKey.getPullRequestKey()).thenReturn(Optional.empty());
|
||||
|
||||
ComponentDao componentDao = spy(mock(ComponentDao.class));
|
||||
ComponentDao componentDao = mock(ComponentDao.class);
|
||||
|
||||
DbClient dbClient = mock(DbClient.class);
|
||||
when(dbClient.componentDao()).thenReturn(componentDao);
|
||||
|
@ -0,0 +1 @@
|
||||
mock-maker-inline
|
Loading…
Reference in New Issue
Block a user