mirror of
https://github.com/mc1arke/sonarqube-community-branch-plugin.git
synced 2025-02-21 19:20:09 +02:00
Refactor Bitbucket operations to prevent leaking scope
The Bitbucket clients require different properties to be used from the relevant configuration DTOs depending on whether Bitbucket cloud or server are being used, with the management of the property retrieval being delegated to the relevant client implementation. However, this requires each client to reference DTO classes from Sonarqube core, where the clients should really only interact with their own models. As the work on retrieving the relevant details has already been performed in the `DefaultBitbucketClientFactory`, the logic for performing the retrieval has been removed from each client implementations, and the calculated values are passed into the constructor for each client instead. This does make each client instance constrained to a single repository, but given the way the clients are used within the decorators and validators, this isn't an issue. The client API has therefore been altered to remove the references to project and repository in any method signatures since the client now retrieves this internally from the client configuration. The clients have also been altered now to depend directly on the status from the Quality Gate, with a new enum being used by the client to indicate the report status, and the decorator performing the mapping between the Quality Gate and report status. Finally, to allow for the `DefaultBitbucketClientFactory` to have a single constructor rather than a test-specific constructor, the facility for creating an Http Client has been moved into an `HttpClientBuilderFactory` and this new class configured for injection in both the Compute Engine and server components.
This commit is contained in:
parent
8e8a31c448
commit
d7bb8b4894
@ -21,6 +21,7 @@ package com.github.mc1arke.sonarqube.plugin;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.DefaultLinkHeaderReader;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.azuredevops.DefaultAzureDevopsClientFactory;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.DefaultBitbucketClientFactory;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.HttpClientBuilderFactory;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.github.DefaultGithubClientFactory;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.github.v3.RestApplicationAuthenticationProvider;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.gitlab.DefaultGitlabClientFactory;
|
||||
@ -90,6 +91,7 @@ public class CommunityBranchPlugin implements Plugin, CoreExtension {
|
||||
DefaultGithubClientFactory.class,
|
||||
DefaultLinkHeaderReader.class,
|
||||
RestApplicationAuthenticationProvider.class,
|
||||
HttpClientBuilderFactory.class,
|
||||
DefaultBitbucketClientFactory.class,
|
||||
BitbucketValidator.class,
|
||||
GitlabValidator.class,
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2020-2021 Marvin Wichmann, Michael Clarke
|
||||
* Copyright (C) 2020-2022 Marvin Wichmann, Michael Clarke
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@ -23,10 +23,8 @@ import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.CodeInsight
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.CodeInsightsReport;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.DataValue;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.ReportData;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.ReportStatus;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.Repository;
|
||||
import org.sonar.api.ce.posttask.QualityGate;
|
||||
import org.sonar.db.alm.setting.AlmSettingDto;
|
||||
import org.sonar.db.alm.setting.ProjectAlmSettingDto;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
@ -55,21 +53,21 @@ public interface BitbucketClient {
|
||||
*/
|
||||
CodeInsightsReport createCodeInsightsReport(List<ReportData> reportData,
|
||||
String reportDescription, Instant creationDate, String dashboardUrl,
|
||||
String logoUrl, QualityGate.Status status);
|
||||
String logoUrl, ReportStatus reportStatus);
|
||||
|
||||
/**
|
||||
* 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;
|
||||
void deleteAnnotations(String commitSha) throws IOException;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
void uploadAnnotations(String commitSha, Set<CodeInsightsAnnotation> annotations) throws IOException;
|
||||
|
||||
/**
|
||||
* Creates a DataValue of type DataValue.Link or DataValue.CloudLink depending on the implementation
|
||||
@ -79,7 +77,7 @@ public interface BitbucketClient {
|
||||
/**
|
||||
* Uploads the code insights report for the given commit
|
||||
*/
|
||||
void uploadReport(String project, String repo, String commitSha, CodeInsightsReport codeInsightReport) throws IOException;
|
||||
void uploadReport(String commitSha, CodeInsightsReport codeInsightReport) throws IOException;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@ -104,32 +102,10 @@ public interface BitbucketClient {
|
||||
*/
|
||||
AnnotationUploadLimit getAnnotationUploadLimit();
|
||||
|
||||
/**
|
||||
* Extract the name of the project from the relevant configuration. The project is
|
||||
* the value that should be used in the calls that take a `project` parameter.
|
||||
*
|
||||
* @param almSettingDto the global `AlmSettingDto` containing the global configuration for this ALM
|
||||
* @param projectAlmSettingDto the `ProjectAlmSettingDto` assigned to the current project
|
||||
* @return the resolved project name.
|
||||
*/
|
||||
String resolveProject(AlmSettingDto almSettingDto, ProjectAlmSettingDto projectAlmSettingDto);
|
||||
|
||||
/**
|
||||
* Extract the name of the repository from the relevant configuration. The project is
|
||||
* the value that should be used in the calls that take a `repository` parameter.
|
||||
*
|
||||
* @param almSettingDto the global `AlmSettingDto` containing the global configuration for this ALM
|
||||
* @param projectAlmSettingDto the `ProjectAlmSettingDto` assigned to the current project
|
||||
* @return the resolved repository name.
|
||||
*/
|
||||
String resolveRepository(AlmSettingDto almSettingDto, ProjectAlmSettingDto projectAlmSettingDto);
|
||||
|
||||
/**
|
||||
* Retrieve the details of the repository from the target Bitbucket instance.
|
||||
* @param project the project as resolved from {@link #resolveProject(AlmSettingDto, ProjectAlmSettingDto)}
|
||||
* @param repo the repository as resolved from {@link #resolveRepository(AlmSettingDto, ProjectAlmSettingDto)}
|
||||
* @return the repository details retrieved from Bitbucket.
|
||||
*/
|
||||
Repository retrieveRepository(String project, String repo) throws IOException;
|
||||
Repository retrieveRepository() throws IOException;
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2020-2021 Marvin Wichmann, Michael Clarke
|
||||
* Copyright (C) 2020-2022 Marvin Wichmann, Michael Clarke
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@ -21,12 +21,13 @@ package com.github.mc1arke.sonarqube.plugin.almclient.bitbucket;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.AnnotationUploadLimit;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.BitbucketConfiguration;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.CodeInsightsAnnotation;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.CodeInsightsReport;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.DataValue;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.ReportData;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.ReportStatus;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.Repository;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.cloud.BitbucketCloudConfiguration;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.cloud.CloudAnnotation;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.cloud.CloudCreateReportRequest;
|
||||
import okhttp3.MediaType;
|
||||
@ -34,11 +35,8 @@ 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 org.sonar.db.alm.setting.AlmSettingDto;
|
||||
import org.sonar.db.alm.setting.ProjectAlmSettingDto;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@ -52,7 +50,6 @@ import java.util.stream.Collectors;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
|
||||
class BitbucketCloudClient implements BitbucketClient {
|
||||
|
||||
private static final Logger LOGGER = Loggers.get(BitbucketCloudClient.class);
|
||||
@ -64,27 +61,25 @@ class BitbucketCloudClient implements BitbucketClient {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
private final OkHttpClient okHttpClient;
|
||||
private final BitbucketConfiguration bitbucketConfiguration;
|
||||
|
||||
BitbucketCloudClient(BitbucketCloudConfiguration config, ObjectMapper objectMapper, OkHttpClient.Builder baseClientBuilder) {
|
||||
this(objectMapper, createAuthorisingClient(baseClientBuilder, negotiateBearerToken(config, objectMapper, baseClientBuilder.build())));
|
||||
}
|
||||
|
||||
BitbucketCloudClient(ObjectMapper objectMapper, OkHttpClient okHttpClient) {
|
||||
BitbucketCloudClient(ObjectMapper objectMapper, OkHttpClient okHttpClient, BitbucketConfiguration bitbucketConfiguration) {
|
||||
this.objectMapper = objectMapper;
|
||||
this.okHttpClient = okHttpClient;
|
||||
this.bitbucketConfiguration = bitbucketConfiguration;
|
||||
}
|
||||
|
||||
private static String negotiateBearerToken(BitbucketCloudConfiguration bitbucketCloudConfiguration, ObjectMapper objectMapper, OkHttpClient okHttpClient) {
|
||||
static String negotiateBearerToken(String clientId, String clientSecret, ObjectMapper objectMapper, OkHttpClient okHttpClient) {
|
||||
Request request = new Request.Builder()
|
||||
.header("Authorization", "Basic " + Base64.getEncoder().encodeToString((bitbucketCloudConfiguration.getClientId() + ":" + bitbucketCloudConfiguration.getSecret()).getBytes(
|
||||
StandardCharsets.UTF_8)))
|
||||
.header("Authorization", "Basic " + Base64.getEncoder().encodeToString((clientId + ":" + clientSecret).getBytes(StandardCharsets.UTF_8)))
|
||||
.url("https://bitbucket.org/site/oauth2/access_token")
|
||||
.post(RequestBody.create("grant_type=client_credentials", MediaType.parse("application/x-www-form-urlencoded")))
|
||||
.build();
|
||||
|
||||
try (Response response = okHttpClient.newCall(request).execute()) {
|
||||
AuthToken authToken = objectMapper.readValue(
|
||||
Optional.ofNullable(response.body()).orElseThrow(() -> new IllegalStateException("No response returned by Bitbucket Oauth")).string(), AuthToken.class);
|
||||
BitbucketCloudClient.AuthToken authToken = objectMapper.readValue(
|
||||
Optional.ofNullable(response.body()).orElseThrow(() -> new IllegalStateException("No response returned by Bitbucket Oauth")).string(), BitbucketCloudClient.AuthToken.class);
|
||||
return authToken.getAccessToken();
|
||||
} catch (IOException ex) {
|
||||
throw new IllegalStateException("Could not retrieve bearer token", ex);
|
||||
@ -106,7 +101,7 @@ class BitbucketCloudClient implements BitbucketClient {
|
||||
@Override
|
||||
public CodeInsightsReport createCodeInsightsReport(List<ReportData> reportData, String reportDescription,
|
||||
Instant creationDate, String dashboardUrl, String logoUrl,
|
||||
QualityGate.Status status) {
|
||||
ReportStatus status) {
|
||||
return new CloudCreateReportRequest(
|
||||
reportData,
|
||||
reportDescription,
|
||||
@ -116,16 +111,17 @@ class BitbucketCloudClient implements BitbucketClient {
|
||||
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"
|
||||
ReportStatus.FAILED == status ? "FAILED" : "PASSED"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteAnnotations(String project, String repo, String commitSha) {
|
||||
public void deleteAnnotations(String commitSha) {
|
||||
// not needed here.
|
||||
}
|
||||
|
||||
public void uploadAnnotations(String project, String repository, String commit, Set<CodeInsightsAnnotation> baseAnnotations) throws IOException {
|
||||
@Override
|
||||
public void uploadAnnotations(String commit, Set<CodeInsightsAnnotation> baseAnnotations) throws IOException {
|
||||
Set<CloudAnnotation> annotations = baseAnnotations.stream().map(CloudAnnotation.class::cast).collect(Collectors.toSet());
|
||||
|
||||
if (annotations.isEmpty()) {
|
||||
@ -134,7 +130,7 @@ class BitbucketCloudClient implements BitbucketClient {
|
||||
|
||||
Request req = new Request.Builder()
|
||||
.post(RequestBody.create(objectMapper.writeValueAsString(annotations), APPLICATION_JSON_MEDIA_TYPE))
|
||||
.url(format("https://api.bitbucket.org/2.0/repositories/%s/%s/commit/%s/reports/%s/annotations", project, repository, commit, REPORT_KEY))
|
||||
.url(format("https://api.bitbucket.org/2.0/repositories/%s/%s/commit/%s/reports/%s/annotations", bitbucketConfiguration.getProject(), bitbucketConfiguration.getRepository(), commit, REPORT_KEY))
|
||||
.build();
|
||||
|
||||
LOGGER.info("Creating annotations on bitbucket cloud");
|
||||
@ -151,10 +147,10 @@ class BitbucketCloudClient implements BitbucketClient {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uploadReport(String project, String repository, String commit, CodeInsightsReport codeInsightReport) throws IOException {
|
||||
deleteExistingReport(project, repository, commit);
|
||||
public void uploadReport(String commit, CodeInsightsReport codeInsightReport) throws IOException {
|
||||
deleteExistingReport(commit);
|
||||
|
||||
String targetUrl = format("https://api.bitbucket.org/2.0/repositories/%s/%s/commit/%s/reports/%s", project, repository, commit, REPORT_KEY);
|
||||
String targetUrl = format("https://api.bitbucket.org/2.0/repositories/%s/%s/commit/%s/reports/%s", bitbucketConfiguration.getProject(), bitbucketConfiguration.getRepository(), commit, REPORT_KEY);
|
||||
String body = objectMapper.writeValueAsString(codeInsightReport);
|
||||
Request req = new Request.Builder()
|
||||
.put(RequestBody.create(body, APPLICATION_JSON_MEDIA_TYPE))
|
||||
@ -180,20 +176,10 @@ class BitbucketCloudClient implements BitbucketClient {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resolveProject(AlmSettingDto almSettingDto, ProjectAlmSettingDto projectAlmSettingDto) {
|
||||
return almSettingDto.getAppId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resolveRepository(AlmSettingDto almSettingDto, ProjectAlmSettingDto projectAlmSettingDto) {
|
||||
return projectAlmSettingDto.getAlmRepo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Repository retrieveRepository(String project, String repo) throws IOException {
|
||||
public Repository retrieveRepository() throws IOException {
|
||||
Request req = new Request.Builder()
|
||||
.get()
|
||||
.url(format("https://api.bitbucket.org/2.0/repositories/%s/%s", project, repo))
|
||||
.url(format("https://api.bitbucket.org/2.0/repositories/%s/%s", bitbucketConfiguration.getProject(), bitbucketConfiguration.getRepository()))
|
||||
.build();
|
||||
try (Response response = okHttpClient.newCall(req).execute()) {
|
||||
validate(response);
|
||||
@ -205,10 +191,10 @@ class BitbucketCloudClient implements BitbucketClient {
|
||||
}
|
||||
}
|
||||
|
||||
void deleteExistingReport(String project, String repository, String commit) throws IOException {
|
||||
void deleteExistingReport(String commit) throws IOException {
|
||||
Request req = new Request.Builder()
|
||||
.delete()
|
||||
.url(format("https://api.bitbucket.org/2.0/repositories/%s/%s/commit/%s/reports/%s", project, repository, commit, REPORT_KEY))
|
||||
.url(format("https://api.bitbucket.org/2.0/repositories/%s/%s/commit/%s/reports/%s", bitbucketConfiguration.getProject(), bitbucketConfiguration.getRepository(), commit, REPORT_KEY))
|
||||
.build();
|
||||
|
||||
LOGGER.info("Deleting existing reports on bitbucket cloud");
|
||||
@ -218,17 +204,6 @@ class BitbucketCloudClient implements BitbucketClient {
|
||||
}
|
||||
}
|
||||
|
||||
private static OkHttpClient createAuthorisingClient(OkHttpClient.Builder baseClientBuilder, String bearerToken) {
|
||||
return baseClientBuilder.addInterceptor(chain -> {
|
||||
Request newRequest = chain.request().newBuilder()
|
||||
.addHeader("Authorization", format("Bearer %s", bearerToken))
|
||||
.addHeader("Accept", APPLICATION_JSON_MEDIA_TYPE.toString())
|
||||
.build();
|
||||
return chain.proceed(newRequest);
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
void validate(Response response) {
|
||||
if (!response.isSuccessful()) {
|
||||
String error = Optional.ofNullable(response.body()).map(b -> {
|
||||
@ -242,7 +217,7 @@ class BitbucketCloudClient implements BitbucketClient {
|
||||
}
|
||||
}
|
||||
|
||||
static class AuthToken {
|
||||
private static class AuthToken {
|
||||
|
||||
private final String accessToken;
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2020-2021 Mathias Åhsberg, Michael Clarke
|
||||
* Copyright (C) 2020-2022 Mathias Åhsberg, Michael Clarke
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@ -24,6 +24,7 @@ import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.CodeInsight
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.CodeInsightsReport;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.DataValue;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.ReportData;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.ReportStatus;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.Repository;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.server.Annotation;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.server.BitbucketServerConfiguration;
|
||||
@ -36,11 +37,8 @@ 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 org.sonar.db.alm.setting.AlmSettingDto;
|
||||
import org.sonar.db.alm.setting.ProjectAlmSettingDto;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
@ -63,10 +61,6 @@ class BitbucketServerClient implements BitbucketClient {
|
||||
private final ObjectMapper objectMapper;
|
||||
private final OkHttpClient okHttpClient;
|
||||
|
||||
BitbucketServerClient(BitbucketServerConfiguration config, ObjectMapper objectMapper, OkHttpClient.Builder baseClientBuilder) {
|
||||
this(config, objectMapper, createAuthorisingClient(baseClientBuilder, config));
|
||||
}
|
||||
|
||||
BitbucketServerClient(BitbucketServerConfiguration config, ObjectMapper objectMapper, OkHttpClient okHttpClient) {
|
||||
this.config = config;
|
||||
this.objectMapper = objectMapper;
|
||||
@ -85,7 +79,7 @@ class BitbucketServerClient implements BitbucketClient {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeInsightsReport createCodeInsightsReport(List<ReportData> reportData, String reportDescription, Instant creationDate, String dashboardUrl, String logoUrl, QualityGate.Status status) {
|
||||
public CodeInsightsReport createCodeInsightsReport(List<ReportData> reportData, String reportDescription, Instant creationDate, String dashboardUrl, String logoUrl, ReportStatus status) {
|
||||
return new CreateReportRequest(
|
||||
reportData,
|
||||
reportDescription,
|
||||
@ -94,14 +88,15 @@ class BitbucketServerClient implements BitbucketClient {
|
||||
creationDate,
|
||||
dashboardUrl,
|
||||
logoUrl,
|
||||
QualityGate.Status.ERROR.equals(status) ? "FAIL" : "PASS"
|
||||
ReportStatus.FAILED == status ? "FAIL" : "PASS"
|
||||
);
|
||||
}
|
||||
|
||||
public void deleteAnnotations(String project, String repository, String commit) throws IOException {
|
||||
@Override
|
||||
public void deleteAnnotations(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))
|
||||
.url(format("%s/rest/insights/1.0/projects/%s/repos/%s/commits/%s/reports/%s/annotations", config.getUrl(), config.getProject(), config.getRepository(), commit, REPORT_KEY))
|
||||
.build();
|
||||
try (Response response = okHttpClient.newCall(req).execute()) {
|
||||
validate(response);
|
||||
@ -109,7 +104,7 @@ class BitbucketServerClient implements BitbucketClient {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uploadAnnotations(String project, String repository, String commit, Set<CodeInsightsAnnotation> annotations) throws IOException {
|
||||
public void uploadAnnotations(String commit, Set<CodeInsightsAnnotation> annotations) throws IOException {
|
||||
if (annotations.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
@ -117,7 +112,7 @@ class BitbucketServerClient implements BitbucketClient {
|
||||
CreateAnnotationsRequest request = new CreateAnnotationsRequest(annotationSet);
|
||||
Request req = new Request.Builder()
|
||||
.post(RequestBody.create(objectMapper.writeValueAsString(request), APPLICATION_JSON_MEDIA_TYPE))
|
||||
.url(format("%s/rest/insights/1.0/projects/%s/repos/%s/commits/%s/reports/%s/annotations", config.getUrl(), project, repository, commit, REPORT_KEY))
|
||||
.url(format("%s/rest/insights/1.0/projects/%s/repos/%s/commits/%s/reports/%s/annotations", config.getUrl(), config.getProject(), config.getRepository(), commit, REPORT_KEY))
|
||||
.build();
|
||||
try (Response response = okHttpClient.newCall(req).execute()) {
|
||||
validate(response);
|
||||
@ -130,11 +125,11 @@ class BitbucketServerClient implements BitbucketClient {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uploadReport(String project, String repository, String commit, CodeInsightsReport codeInsightReport) throws IOException {
|
||||
public void uploadReport(String commit, CodeInsightsReport codeInsightReport) throws IOException {
|
||||
String body = objectMapper.writeValueAsString(codeInsightReport);
|
||||
Request req = new Request.Builder()
|
||||
.put(RequestBody.create(body, APPLICATION_JSON_MEDIA_TYPE))
|
||||
.url(format("%s/rest/insights/1.0/projects/%s/repos/%s/commits/%s/reports/%s", config.getUrl(), project, repository, commit, REPORT_KEY))
|
||||
.url(format("%s/rest/insights/1.0/projects/%s/repos/%s/commits/%s/reports/%s", config.getUrl(), config.getProject(), config.getRepository(), commit, REPORT_KEY))
|
||||
.build();
|
||||
|
||||
try (Response response = okHttpClient.newCall(req).execute()) {
|
||||
@ -166,20 +161,10 @@ class BitbucketServerClient implements BitbucketClient {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resolveProject(AlmSettingDto almSettingDto, ProjectAlmSettingDto projectAlmSettingDto) {
|
||||
return projectAlmSettingDto.getAlmRepo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resolveRepository(AlmSettingDto almSettingDto, ProjectAlmSettingDto projectAlmSettingDto) {
|
||||
return projectAlmSettingDto.getAlmSlug();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Repository retrieveRepository(String project, String repo) throws IOException {
|
||||
public Repository retrieveRepository() throws IOException {
|
||||
Request req = new Request.Builder()
|
||||
.get()
|
||||
.url(format("%s/rest/api/1.0/projects/%s/repos/%s", config.getUrl(), project, repo))
|
||||
.url(format("%s/rest/api/1.0/projects/%s/repos/%s", config.getUrl(), config.getProject(), config.getRepository()))
|
||||
.build();
|
||||
try (Response response = okHttpClient.newCall(req).execute()) {
|
||||
validate(response);
|
||||
@ -206,17 +191,6 @@ class BitbucketServerClient implements BitbucketClient {
|
||||
}
|
||||
}
|
||||
|
||||
private static OkHttpClient createAuthorisingClient(OkHttpClient.Builder clientBuilder, BitbucketServerConfiguration config) {
|
||||
return clientBuilder.addInterceptor(chain -> {
|
||||
Request newRequest = chain.request().newBuilder()
|
||||
.addHeader("Authorization", format("Bearer %s", config.getPersonalAccessToken()))
|
||||
.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;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2020-2021 Marvin Wichmann, Michael Clarke
|
||||
* Copyright (C) 2020-2022 Marvin Wichmann, Michael Clarke
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@ -22,9 +22,10 @@ import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.github.mc1arke.sonarqube.plugin.InvalidConfigurationException;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.cloud.BitbucketCloudConfiguration;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.BitbucketConfiguration;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.server.BitbucketServerConfiguration;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.logging.HttpLoggingInterceptor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.sonar.api.ce.ComputeEngineSide;
|
||||
@ -37,7 +38,8 @@ import org.sonar.db.alm.setting.AlmSettingDto;
|
||||
import org.sonar.db.alm.setting.ProjectAlmSettingDto;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
@ServerSide
|
||||
@ComputeEngineSide
|
||||
@ -45,15 +47,11 @@ public class DefaultBitbucketClientFactory implements BitbucketClientFactory {
|
||||
|
||||
private static final Logger LOGGER = Loggers.get(DefaultBitbucketClientFactory.class);
|
||||
|
||||
private final Supplier<OkHttpClient.Builder> okHttpClientBuilderSupplier;
|
||||
private final HttpClientBuilderFactory httpClientBuilderFactory;
|
||||
private final Settings settings;
|
||||
|
||||
public DefaultBitbucketClientFactory(Settings settings) {
|
||||
this(settings, OkHttpClient.Builder::new);
|
||||
}
|
||||
|
||||
DefaultBitbucketClientFactory(Settings settings, Supplier<OkHttpClient.Builder> okHttpClientBuilderSupplier) {
|
||||
this.okHttpClientBuilderSupplier = okHttpClientBuilderSupplier;
|
||||
public DefaultBitbucketClientFactory(Settings settings, HttpClientBuilderFactory httpClientBuilderFactory) {
|
||||
this.httpClientBuilderFactory = httpClientBuilderFactory;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
@ -61,6 +59,10 @@ public class DefaultBitbucketClientFactory implements BitbucketClientFactory {
|
||||
public BitbucketClient createClient(ProjectAlmSettingDto projectAlmSettingDto, AlmSettingDto almSettingDto) {
|
||||
String almRepo = Optional.ofNullable(StringUtils.trimToNull(projectAlmSettingDto.getAlmRepo()))
|
||||
.orElseThrow(() -> new InvalidConfigurationException(InvalidConfigurationException.Scope.PROJECT, "ALM Repo must be set in configuration"));
|
||||
|
||||
ObjectMapper objectMapper = createObjectMapper();
|
||||
OkHttpClient.Builder clientBuilder = createBaseClientBuilder(httpClientBuilderFactory);
|
||||
|
||||
if (almSettingDto.getAlm() == ALM.BITBUCKET_CLOUD) {
|
||||
String appId = Optional.ofNullable(StringUtils.trimToNull(almSettingDto.getAppId()))
|
||||
.orElseThrow(() -> new InvalidConfigurationException(InvalidConfigurationException.Scope.GLOBAL, "App ID must be set in configuration"));
|
||||
@ -68,7 +70,8 @@ public class DefaultBitbucketClientFactory implements BitbucketClientFactory {
|
||||
.orElseThrow(() -> new InvalidConfigurationException(InvalidConfigurationException.Scope.GLOBAL, "Client ID must be set in configuration"));
|
||||
String clientSecret = Optional.ofNullable(StringUtils.trimToNull(almSettingDto.getDecryptedClientSecret(settings.getEncryption())))
|
||||
.orElseThrow(() -> new InvalidConfigurationException(InvalidConfigurationException.Scope.GLOBAL, "Client Secret must be set in configuration"));
|
||||
return new BitbucketCloudClient(new BitbucketCloudConfiguration(appId, almRepo, clientId, clientSecret), createObjectMapper(), createBaseClientBuilder(okHttpClientBuilderSupplier));
|
||||
String bearerToken = BitbucketCloudClient.negotiateBearerToken(clientId, clientSecret, objectMapper, clientBuilder.build());
|
||||
return new BitbucketCloudClient(objectMapper, createAuthorisingClient(clientBuilder, bearerToken), new BitbucketConfiguration(appId, almRepo));
|
||||
} else {
|
||||
String almSlug = Optional.ofNullable(StringUtils.trimToNull(projectAlmSettingDto.getAlmSlug()))
|
||||
.orElseThrow(() -> new InvalidConfigurationException(InvalidConfigurationException.Scope.PROJECT, "ALM slug must be set in configuration"));
|
||||
@ -76,7 +79,7 @@ public class DefaultBitbucketClientFactory implements BitbucketClientFactory {
|
||||
.orElseThrow(() -> new InvalidConfigurationException(InvalidConfigurationException.Scope.GLOBAL, "URL must be set in configuration"));
|
||||
String personalAccessToken = Optional.ofNullable(StringUtils.trimToNull(almSettingDto.getDecryptedPersonalAccessToken(settings.getEncryption())))
|
||||
.orElseThrow(() -> new InvalidConfigurationException(InvalidConfigurationException.Scope.PROJECT, "Personal access token must be set in configuration"));
|
||||
return new BitbucketServerClient(new BitbucketServerConfiguration(almRepo, almSlug, url, personalAccessToken), createObjectMapper(), createBaseClientBuilder(okHttpClientBuilderSupplier));
|
||||
return new BitbucketServerClient(new BitbucketServerConfiguration(almRepo, almSlug, url), objectMapper, createAuthorisingClient(clientBuilder, personalAccessToken));
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,9 +89,19 @@ public class DefaultBitbucketClientFactory implements BitbucketClientFactory {
|
||||
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
|
||||
}
|
||||
|
||||
private static OkHttpClient.Builder createBaseClientBuilder(Supplier<OkHttpClient.Builder> builderSupplier) {
|
||||
private static OkHttpClient.Builder createBaseClientBuilder(HttpClientBuilderFactory httpClientBuilderFactory) {
|
||||
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(LOGGER::debug);
|
||||
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
|
||||
return builderSupplier.get().addInterceptor(httpLoggingInterceptor);
|
||||
return httpClientBuilderFactory.createClientBuilder().addInterceptor(httpLoggingInterceptor);
|
||||
}
|
||||
|
||||
private static OkHttpClient createAuthorisingClient(OkHttpClient.Builder clientBuilder, String bearerToken) {
|
||||
return clientBuilder.addInterceptor(chain -> {
|
||||
Request newRequest = chain.request().newBuilder()
|
||||
.addHeader("Authorization", format("Bearer %s", bearerToken))
|
||||
.addHeader("Accept", "application/json")
|
||||
.build();
|
||||
return chain.proceed(newRequest);
|
||||
}).build();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Michael Clarke
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
package com.github.mc1arke.sonarqube.plugin.almclient.bitbucket;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import org.sonar.api.ce.ComputeEngineSide;
|
||||
import org.sonar.api.server.ServerSide;
|
||||
|
||||
@ServerSide
|
||||
@ComputeEngineSide
|
||||
public class HttpClientBuilderFactory {
|
||||
|
||||
public OkHttpClient.Builder createClientBuilder() {
|
||||
return new OkHttpClient.Builder();
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Michael Clarke
|
||||
* Copyright (C) 2022 Michael Clarke
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@ -16,26 +16,8 @@
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
package com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.cloud;
|
||||
package com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model;
|
||||
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.BitbucketConfiguration;
|
||||
|
||||
public class BitbucketCloudConfiguration extends BitbucketConfiguration {
|
||||
|
||||
private final String clientId;
|
||||
private final String secret;
|
||||
|
||||
public BitbucketCloudConfiguration(String repository, String project, String clientId, String secret) {
|
||||
super(repository, project);
|
||||
this.clientId = clientId;
|
||||
this.secret = secret;
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public String getSecret() {
|
||||
return secret;
|
||||
}
|
||||
public enum ReportStatus {
|
||||
PASSED, FAILED
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Michael Clarke
|
||||
* Copyright (C) 2021-2022 Michael Clarke
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@ -23,19 +23,13 @@ import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.BitbucketCo
|
||||
public class BitbucketServerConfiguration extends BitbucketConfiguration {
|
||||
|
||||
private final String url;
|
||||
private final String personalAccessToken;
|
||||
|
||||
public BitbucketServerConfiguration(String almRepo, String almSlug, String url, String personalAccessToken) {
|
||||
public BitbucketServerConfiguration(String almRepo, String almSlug, String url) {
|
||||
super(almRepo, almSlug);
|
||||
this.url = url;
|
||||
this.personalAccessToken = personalAccessToken;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public String getPersonalAccessToken() {
|
||||
return personalAccessToken;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Michael Clarke
|
||||
* Copyright (C) 2019-2022 Michael Clarke
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@ -21,6 +21,7 @@ package com.github.mc1arke.sonarqube.plugin.ce;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.DefaultLinkHeaderReader;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.azuredevops.DefaultAzureDevopsClientFactory;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.DefaultBitbucketClientFactory;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.HttpClientBuilderFactory;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.github.DefaultGithubClientFactory;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.github.v3.RestApplicationAuthenticationProvider;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.gitlab.DefaultGitlabClientFactory;
|
||||
@ -45,7 +46,7 @@ public class CommunityReportAnalysisComponentProvider implements ReportAnalysisC
|
||||
return Arrays.asList(CommunityBranchLoaderDelegate.class, PullRequestPostAnalysisTask.class,
|
||||
PostAnalysisIssueVisitor.class, DefaultLinkHeaderReader.class,
|
||||
DefaultGithubClientFactory.class, RestApplicationAuthenticationProvider.class, GithubPullRequestDecorator.class,
|
||||
DefaultBitbucketClientFactory.class, BitbucketPullRequestDecorator.class,
|
||||
HttpClientBuilderFactory.class, DefaultBitbucketClientFactory.class, BitbucketPullRequestDecorator.class,
|
||||
DefaultGitlabClientFactory.class, GitlabMergeRequestDecorator.class,
|
||||
DefaultAzureDevopsClientFactory.class, AzureDevOpsPullRequestDecorator.class);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2020-2021 Michael Clarke
|
||||
* Copyright (C) 2020-2022 Michael Clarke
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@ -68,6 +68,9 @@ import java.util.stream.Collectors;
|
||||
public class AnalysisDetails {
|
||||
|
||||
private static final List<String> CLOSED_ISSUE_STATUS = Arrays.asList(Issue.STATUS_CLOSED, Issue.STATUS_RESOLVED);
|
||||
private static final List<String> OPEN_ISSUE_STATUSES =
|
||||
Issue.STATUSES.stream().filter(s -> !CLOSED_ISSUE_STATUS.contains(s))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
private static final List<BigDecimal> COVERAGE_LEVELS =
|
||||
Arrays.asList(BigDecimal.valueOf(100), BigDecimal.valueOf(90), BigDecimal.valueOf(60),
|
||||
@ -399,6 +402,15 @@ public class AnalysisDetails {
|
||||
return findMeasure(CoreMetrics.COVERAGE_KEY).map(Measure::getDoubleValue).map(BigDecimal::new);
|
||||
}
|
||||
|
||||
public List<PostAnalysisIssueVisitor.ComponentIssue> getScmReportableIssues() {
|
||||
return postAnalysisIssueVisitor.getIssues().stream()
|
||||
.filter(i -> getSCMPathForIssue(i).isPresent())
|
||||
.filter(i -> i.getComponent().getType() == Component.Type.FILE)
|
||||
.filter(i -> i.getIssue().resolution() == null)
|
||||
.filter(i -> OPEN_ISSUE_STATUSES.contains(i.getIssue().status()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public static class BranchDetails {
|
||||
|
||||
private final String branchName;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Michael Clarke
|
||||
* Copyright (C) 2019-2022 Michael Clarke
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@ -29,6 +29,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.annotation.CheckForNull;
|
||||
|
||||
@ -38,7 +39,9 @@ public class PostAnalysisIssueVisitor extends IssueVisitor {
|
||||
|
||||
@Override
|
||||
public void onIssue(Component component, DefaultIssue defaultIssue) {
|
||||
collectedIssues.add(new ComponentIssue(component, defaultIssue));
|
||||
collectedIssues.add(new ComponentIssue(component, Optional.ofNullable(defaultIssue)
|
||||
.map(LightIssue::new)
|
||||
.orElse(null)));
|
||||
}
|
||||
|
||||
public List<ComponentIssue> getIssues() {
|
||||
@ -50,11 +53,10 @@ public class PostAnalysisIssueVisitor extends IssueVisitor {
|
||||
private final Component component;
|
||||
private final LightIssue issue;
|
||||
|
||||
ComponentIssue(Component component, DefaultIssue issue) {
|
||||
ComponentIssue(Component component, LightIssue issue) {
|
||||
super();
|
||||
this.component = component;
|
||||
this.issue = (issue != null) ? new LightIssue(issue) : null;
|
||||
// the null test is to please PostAnalysisIssueVisitorTest.checkAllIssuesCollected()
|
||||
this.issue = issue;
|
||||
}
|
||||
|
||||
public Component getComponent() {
|
||||
@ -85,7 +87,7 @@ public class PostAnalysisIssueVisitor extends IssueVisitor {
|
||||
private final DbIssues.Locations locations;
|
||||
private final RuleKey ruleKey;
|
||||
|
||||
private LightIssue(DefaultIssue issue) {
|
||||
LightIssue(DefaultIssue issue) {
|
||||
this.effortInMinutes = issue.effortInMinutes();
|
||||
this.key = issue.key();
|
||||
this.line = issue.getLine();
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2020-2021 Mathias Åhsberg, Michael Clarke
|
||||
* Copyright (C) 2020-2022 Mathias Åhsberg, Michael Clarke
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@ -26,6 +26,7 @@ import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.CodeInsight
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.CodeInsightsReport;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.DataValue;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.ReportData;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.ReportStatus;
|
||||
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;
|
||||
@ -37,7 +38,6 @@ import org.sonar.api.rule.Severity;
|
||||
import org.sonar.api.rules.RuleType;
|
||||
import org.sonar.api.utils.log.Logger;
|
||||
import org.sonar.api.utils.log.Loggers;
|
||||
import org.sonar.ce.task.projectanalysis.component.Component;
|
||||
import org.sonar.db.alm.setting.ALM;
|
||||
import org.sonar.db.alm.setting.AlmSettingDto;
|
||||
import org.sonar.db.alm.setting.ProjectAlmSettingDto;
|
||||
@ -63,10 +63,6 @@ public class BitbucketPullRequestDecorator implements PullRequestBuildStatusDeco
|
||||
|
||||
private static final DecorationResult DEFAULT_DECORATION_RESULT = DecorationResult.builder().build();
|
||||
|
||||
private static final List<String> OPEN_ISSUE_STATUSES =
|
||||
Issue.STATUSES.stream().filter(s -> !Issue.STATUS_CLOSED.equals(s) && !Issue.STATUS_RESOLVED.equals(s))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
private final BitbucketClientFactory bitbucketClientFactory;
|
||||
|
||||
public BitbucketPullRequestDecorator(BitbucketClientFactory bitbucketClientFactory) {
|
||||
@ -82,22 +78,18 @@ public class BitbucketPullRequestDecorator implements PullRequestBuildStatusDeco
|
||||
return DEFAULT_DECORATION_RESULT;
|
||||
}
|
||||
|
||||
String project = client.resolveProject(almSettingDto, projectAlmSettingDto);
|
||||
String repo = client.resolveRepository(almSettingDto, projectAlmSettingDto);
|
||||
|
||||
CodeInsightsReport codeInsightsReport = client.createCodeInsightsReport(
|
||||
toReport(client, analysisDetails),
|
||||
reportDescription(analysisDetails),
|
||||
analysisDetails.getAnalysisDate().toInstant(),
|
||||
analysisDetails.getDashboardUrl(),
|
||||
format("%s/common/icon.png", analysisDetails.getBaseImageUrl()),
|
||||
analysisDetails.getQualityGateStatus()
|
||||
analysisDetails.getQualityGateStatus() == QualityGate.Status.OK ? ReportStatus.PASSED : ReportStatus.FAILED
|
||||
);
|
||||
|
||||
client.uploadReport(project, repo,
|
||||
analysisDetails.getCommitSha(), codeInsightsReport);
|
||||
client.uploadReport(analysisDetails.getCommitSha(), codeInsightsReport);
|
||||
|
||||
updateAnnotations(client, project, repo, analysisDetails);
|
||||
updateAnnotations(client, analysisDetails);
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Could not decorate pull request for project {}", analysisDetails.getAnalysisProjectKey(), e);
|
||||
}
|
||||
@ -124,17 +116,14 @@ public class BitbucketPullRequestDecorator implements PullRequestBuildStatusDeco
|
||||
return reportData;
|
||||
}
|
||||
|
||||
private void updateAnnotations(BitbucketClient client, String project, String repo, AnalysisDetails analysisDetails) throws IOException {
|
||||
private void updateAnnotations(BitbucketClient client, AnalysisDetails analysisDetails) throws IOException {
|
||||
final AtomicInteger chunkCounter = new AtomicInteger(0);
|
||||
|
||||
client.deleteAnnotations(project, repo, analysisDetails.getCommitSha());
|
||||
client.deleteAnnotations(analysisDetails.getCommitSha());
|
||||
|
||||
AnnotationUploadLimit uploadLimit = client.getAnnotationUploadLimit();
|
||||
|
||||
Map<Integer, 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()))
|
||||
Map<Integer, Set<CodeInsightsAnnotation>> annotationChunks = analysisDetails.getScmReportableIssues().stream()
|
||||
.filter(i -> !(i.getIssue().type() == RuleType.SECURITY_HOTSPOT && Issue.SECURITY_HOTSPOT_RESOLUTIONS
|
||||
.contains(i.getIssue().resolution())))
|
||||
.sorted(Comparator.comparing(a -> Severity.ALL.indexOf(a.getIssue().severity())))
|
||||
@ -158,7 +147,7 @@ public class BitbucketPullRequestDecorator implements PullRequestBuildStatusDeco
|
||||
break;
|
||||
}
|
||||
|
||||
client.uploadAnnotations(project, repo, analysisDetails.getCommitSha(), annotations);
|
||||
client.uploadAnnotations(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.");
|
||||
@ -174,7 +163,7 @@ public class BitbucketPullRequestDecorator implements PullRequestBuildStatusDeco
|
||||
return (chunkCounter * uploadLimit.getAnnotationBatchSize()) > uploadLimit.getTotalAllowedAnnotations();
|
||||
}
|
||||
|
||||
private String toBitbucketSeverity(String severity) {
|
||||
private static String toBitbucketSeverity(String severity) {
|
||||
if (severity == null) {
|
||||
return "LOW";
|
||||
}
|
||||
@ -189,7 +178,7 @@ public class BitbucketPullRequestDecorator implements PullRequestBuildStatusDeco
|
||||
}
|
||||
}
|
||||
|
||||
private String toBitbucketType(RuleType sonarqubeType) {
|
||||
private static String toBitbucketType(RuleType sonarqubeType) {
|
||||
switch (sonarqubeType) {
|
||||
case SECURITY_HOTSPOT:
|
||||
case VULNERABILITY:
|
||||
@ -203,24 +192,24 @@ public class BitbucketPullRequestDecorator implements PullRequestBuildStatusDeco
|
||||
}
|
||||
}
|
||||
|
||||
private ReportData securityReport(Long vulnerabilities, Long hotspots) {
|
||||
private static ReportData securityReport(Long vulnerabilities, Long hotspots) {
|
||||
String vulnerabilityDescription = vulnerabilities == 1 ? "Vulnerability" : "Vulnerabilities";
|
||||
String hotspotDescription = hotspots == 1 ? "Hotspot" : "Hotspots";
|
||||
String security = format("%d %s (and %d %s)", vulnerabilities, vulnerabilityDescription, hotspots, hotspotDescription);
|
||||
return new ReportData("Security", new DataValue.Text(security));
|
||||
}
|
||||
|
||||
private ReportData reliabilityReport(Long bugs) {
|
||||
private static ReportData reliabilityReport(Long bugs) {
|
||||
String description = bugs == 1 ? "Bug" : "Bugs";
|
||||
return new ReportData("Reliability", new DataValue.Text(format("%d %s", bugs, description)));
|
||||
}
|
||||
|
||||
private ReportData maintainabilityReport(Long codeSmells) {
|
||||
private static ReportData maintainabilityReport(Long codeSmells) {
|
||||
String description = codeSmells == 1 ? "Code Smell" : "Code Smells";
|
||||
return new ReportData("Maintainability", new DataValue.Text(format("%d %s", codeSmells, description)));
|
||||
}
|
||||
|
||||
private String reportDescription(AnalysisDetails details) {
|
||||
private static String reportDescription(AnalysisDetails details) {
|
||||
String header = details.getQualityGateStatus() == QualityGate.Status.OK ? "Quality Gate passed" : "Quality Gate failed";
|
||||
String body = details.findFailedConditions().stream()
|
||||
.map(AnalysisDetails::format)
|
||||
@ -229,7 +218,7 @@ public class BitbucketPullRequestDecorator implements PullRequestBuildStatusDeco
|
||||
return format("%s%n%s", header, body);
|
||||
}
|
||||
|
||||
private BigDecimal newCoverage(AnalysisDetails details) {
|
||||
private static BigDecimal newCoverage(AnalysisDetails details) {
|
||||
return details.findQualityGateCondition(CoreMetrics.NEW_COVERAGE_KEY)
|
||||
.filter(condition -> condition.getStatus() != QualityGate.EvaluationStatus.NO_VALUE)
|
||||
.map(QualityGate.Condition::getValue)
|
||||
@ -237,7 +226,7 @@ public class BitbucketPullRequestDecorator implements PullRequestBuildStatusDeco
|
||||
.orElse(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
private BigDecimal newDuplication(AnalysisDetails details) {
|
||||
private static BigDecimal newDuplication(AnalysisDetails details) {
|
||||
return details.findQualityGateCondition(CoreMetrics.NEW_DUPLICATED_LINES_DENSITY_KEY)
|
||||
.filter(condition -> condition.getStatus() != QualityGate.EvaluationStatus.NO_VALUE)
|
||||
.map(QualityGate.Condition::getValue)
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Michael Clarke
|
||||
* Copyright (C) 2021-2022 Michael Clarke
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@ -56,7 +56,7 @@ public class BitbucketValidator implements Validator {
|
||||
throw new InvalidConfigurationException(InvalidConfigurationException.Scope.PROJECT, "Could not create Bitbucket client - " + ex.getMessage(), ex);
|
||||
}
|
||||
try {
|
||||
bitbucketClient.retrieveRepository(bitbucketClient.resolveProject(almSettingDto, projectAlmSettingDto), bitbucketClient.resolveRepository(almSettingDto, projectAlmSettingDto));
|
||||
bitbucketClient.retrieveRepository();
|
||||
} catch (IOException | RuntimeException ex) {
|
||||
throw new InvalidConfigurationException(InvalidConfigurationException.Scope.PROJECT, "Could not retrieve repository details from Bitbucket - " + ex.getMessage(), ex);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2020-2021 Michael Clarke
|
||||
* Copyright (C) 2020-2022 Michael Clarke
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@ -118,7 +118,7 @@ public class CommunityBranchPluginTest {
|
||||
final ArgumentCaptor<Object> argumentCaptor = ArgumentCaptor.forClass(Object.class);
|
||||
verify(context, times(2)).addExtensions(argumentCaptor.capture(), argumentCaptor.capture());
|
||||
|
||||
assertEquals(22, argumentCaptor.getAllValues().size());
|
||||
assertEquals(23, argumentCaptor.getAllValues().size());
|
||||
|
||||
assertEquals(Arrays.asList(CommunityBranchFeatureExtension.class, CommunityBranchSupportDelegate.class),
|
||||
argumentCaptor.getAllValues().subList(0, 2));
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Michael Clarke
|
||||
* Copyright (C) 2021-2022 Michael Clarke
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@ -20,9 +20,11 @@ package com.github.mc1arke.sonarqube.plugin.almclient.bitbucket;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.AnnotationUploadLimit;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.BitbucketConfiguration;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.CodeInsightsAnnotation;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.CodeInsightsReport;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.DataValue;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.ReportStatus;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.cloud.CloudAnnotation;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.cloud.CloudCreateReportRequest;
|
||||
import com.google.common.collect.Sets;
|
||||
@ -37,7 +39,6 @@ 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;
|
||||
@ -67,9 +68,10 @@ public class BitbucketCloudClientUnitTest {
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
BitbucketConfiguration bitbucketConfiguration = new BitbucketConfiguration("repository", "project");
|
||||
Call call = mock(Call.class);
|
||||
when(client.newCall(any())).thenReturn(call);
|
||||
underTest = new BitbucketCloudClient(mapper, client);
|
||||
underTest = new BitbucketCloudClient(mapper, client, bitbucketConfiguration);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -87,7 +89,7 @@ public class BitbucketCloudClientUnitTest {
|
||||
when(mapper.writeValueAsString(report)).thenReturn("{payload}");
|
||||
|
||||
// when
|
||||
underTest.uploadReport("project", "repository", "commit", report);
|
||||
underTest.uploadReport("commit", report);
|
||||
|
||||
// then
|
||||
verify(client, times(2)).newCall(captor.capture());
|
||||
@ -107,7 +109,7 @@ public class BitbucketCloudClientUnitTest {
|
||||
when(call.execute()).thenReturn(response);
|
||||
|
||||
// when
|
||||
underTest.deleteExistingReport("project", "repository", "commit");
|
||||
underTest.deleteExistingReport("commit");
|
||||
|
||||
// then
|
||||
verify(client).newCall(captor.capture());
|
||||
@ -132,7 +134,7 @@ public class BitbucketCloudClientUnitTest {
|
||||
when(mapper.writeValueAsString(any())).thenReturn("{payload}");
|
||||
|
||||
// when
|
||||
underTest.uploadAnnotations("project", "repository", "commit", annotations);
|
||||
underTest.uploadAnnotations("commit", annotations);
|
||||
|
||||
// then
|
||||
verify(client).newCall(captor.capture());
|
||||
@ -170,7 +172,7 @@ public class BitbucketCloudClientUnitTest {
|
||||
when(mapper.writeValueAsString(report)).thenReturn("{payload}");
|
||||
|
||||
// when,then
|
||||
assertThatThrownBy(() -> underTest.uploadReport("project", "repository", "commit", report))
|
||||
assertThatThrownBy(() -> underTest.uploadReport("commit", report))
|
||||
.isInstanceOf(BitbucketCloudException.class)
|
||||
.hasMessage("HTTP Status Code: 400; Message:error!")
|
||||
.extracting(e -> ((BitbucketCloudException) e).isError(400))
|
||||
@ -183,7 +185,7 @@ public class BitbucketCloudClientUnitTest {
|
||||
Set<CodeInsightsAnnotation> annotations = Sets.newHashSet();
|
||||
|
||||
// when
|
||||
underTest.uploadAnnotations("project", "repository", "commit", annotations);
|
||||
underTest.uploadAnnotations("commit", annotations);
|
||||
|
||||
// then
|
||||
verify(client, never()).newCall(any());
|
||||
@ -199,10 +201,10 @@ public class BitbucketCloudClientUnitTest {
|
||||
// then
|
||||
assertTrue(annotation instanceof CloudAnnotation);
|
||||
assertEquals("issueKey", ((CloudAnnotation) annotation).getExternalId());
|
||||
assertEquals(12, ((CloudAnnotation) annotation).getLine());
|
||||
assertEquals(12, annotation.getLine());
|
||||
assertEquals("http://localhost:9000/dashboard", ((CloudAnnotation) annotation).getLink());
|
||||
assertEquals("/path/to/file", ((CloudAnnotation) annotation).getPath());
|
||||
assertEquals("MAJOR", ((CloudAnnotation) annotation).getSeverity());
|
||||
assertEquals("/path/to/file", annotation.getPath());
|
||||
assertEquals("MAJOR", annotation.getSeverity());
|
||||
assertEquals("BUG", ((CloudAnnotation) annotation).getAnnotationType());
|
||||
}
|
||||
|
||||
@ -234,15 +236,15 @@ public class BitbucketCloudClientUnitTest {
|
||||
// given
|
||||
|
||||
// when
|
||||
CodeInsightsReport result = underTest.createCodeInsightsReport(new ArrayList<>(), "reportDescription", Instant.now(), "dashboardUrl", "logoUrl", QualityGate.Status.ERROR);
|
||||
CodeInsightsReport result = underTest.createCodeInsightsReport(new ArrayList<>(), "reportDescription", Instant.now(), "dashboardUrl", "logoUrl", ReportStatus.FAILED);
|
||||
|
||||
// then
|
||||
assertTrue(result instanceof CloudCreateReportRequest);
|
||||
assertEquals(0, ((CloudCreateReportRequest) result).getData().size());
|
||||
assertEquals("reportDescription", ((CloudCreateReportRequest) result).getDetails());
|
||||
assertEquals("dashboardUrl", ((CloudCreateReportRequest) result).getLink());
|
||||
assertEquals(0, result.getData().size());
|
||||
assertEquals("reportDescription", result.getDetails());
|
||||
assertEquals("dashboardUrl", result.getLink());
|
||||
assertEquals("logoUrl", ((CloudCreateReportRequest) result).getLogoUrl());
|
||||
assertEquals("FAILED", ((CloudCreateReportRequest) result).getResult());
|
||||
assertEquals("FAILED", result.getResult());
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Michael Clarke
|
||||
* Copyright (C) 2021-2022 Michael Clarke
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@ -24,6 +24,7 @@ import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.AnnotationU
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.CodeInsightsAnnotation;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.CodeInsightsReport;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.DataValue;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.ReportStatus;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.server.Annotation;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.server.BitbucketServerConfiguration;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.server.CreateReportRequest;
|
||||
@ -43,7 +44,6 @@ import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.sonar.api.ce.posttask.QualityGate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
@ -74,7 +74,7 @@ public class BitbucketServerClientUnitTest {
|
||||
@Before
|
||||
public void before() {
|
||||
BitbucketServerConfiguration
|
||||
config = new BitbucketServerConfiguration("repo", "slug", "https://my-server.org", "token");
|
||||
config = new BitbucketServerConfiguration("repository", "slug", "https://my-server.org");
|
||||
underTest = new BitbucketServerClient(config, mapper, client);
|
||||
}
|
||||
|
||||
@ -240,13 +240,13 @@ public class BitbucketServerClientUnitTest {
|
||||
when(mapper.writeValueAsString(report)).thenReturn("{payload}");
|
||||
|
||||
// when
|
||||
underTest.uploadReport("project", "repository", "commit", report);
|
||||
underTest.uploadReport("commit", report);
|
||||
|
||||
// then
|
||||
verify(client).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());
|
||||
assertEquals("https://my-server.org/rest/insights/1.0/projects/slug/repos/repository/commits/commit/reports/com.github.mc1arke.sonarqube", request.url().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -264,7 +264,7 @@ public class BitbucketServerClientUnitTest {
|
||||
when(mapper.writeValueAsString(report)).thenReturn("{payload}");
|
||||
|
||||
// when,then
|
||||
assertThatThrownBy(() -> underTest.uploadReport("project", "repository", "commit", report))
|
||||
assertThatThrownBy(() -> underTest.uploadReport("commit", report))
|
||||
.isInstanceOf(BitbucketException.class);
|
||||
}
|
||||
|
||||
@ -295,7 +295,7 @@ public class BitbucketServerClientUnitTest {
|
||||
|
||||
|
||||
// when,then
|
||||
assertThatThrownBy(() -> underTest.uploadReport("project", "repository", "commit", report))
|
||||
assertThatThrownBy(() -> underTest.uploadReport("commit", report))
|
||||
.isInstanceOf(BitbucketException.class)
|
||||
.hasMessage("error!")
|
||||
.extracting(e -> ((BitbucketException) e).isError(400))
|
||||
@ -323,13 +323,13 @@ public class BitbucketServerClientUnitTest {
|
||||
when(response.isSuccessful()).thenReturn(true);
|
||||
|
||||
// when
|
||||
underTest.uploadAnnotations("project", "repository", "commit", annotations);
|
||||
underTest.uploadAnnotations("commit", annotations);
|
||||
|
||||
// then
|
||||
verify(client).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());
|
||||
assertEquals("https://my-server.org/rest/insights/1.0/projects/slug/repos/repository/commits/commit/reports/com.github.mc1arke.sonarqube/annotations", request.url().toString());
|
||||
|
||||
try (Buffer bodyContent = new Buffer()) {
|
||||
request.body().writeTo(bodyContent);
|
||||
@ -343,7 +343,7 @@ public class BitbucketServerClientUnitTest {
|
||||
Set<CodeInsightsAnnotation> annotations = Sets.newHashSet();
|
||||
|
||||
// when
|
||||
underTest.uploadAnnotations("project", "repository", "commit", annotations);
|
||||
underTest.uploadAnnotations("commit", annotations);
|
||||
|
||||
// then
|
||||
verify(client, never()).newCall(any());
|
||||
@ -361,13 +361,13 @@ public class BitbucketServerClientUnitTest {
|
||||
when(response.isSuccessful()).thenReturn(true);
|
||||
|
||||
// when
|
||||
underTest.deleteAnnotations("project", "repository", "commit");
|
||||
underTest.deleteAnnotations("commit");
|
||||
|
||||
// then
|
||||
verify(client).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());
|
||||
assertEquals("https://my-server.org/rest/insights/1.0/projects/slug/repos/repository/commits/commit/reports/com.github.mc1arke.sonarqube/annotations", request.url().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -413,7 +413,7 @@ public class BitbucketServerClientUnitTest {
|
||||
// given
|
||||
|
||||
// when
|
||||
CodeInsightsReport result = underTest.createCodeInsightsReport(new ArrayList<>(), "reportDescription", Instant.now(), "dashboardUrl", "logoUrl", QualityGate.Status.ERROR);
|
||||
CodeInsightsReport result = underTest.createCodeInsightsReport(new ArrayList<>(), "reportDescription", Instant.now(), "dashboardUrl", "logoUrl", ReportStatus.FAILED);
|
||||
|
||||
// then
|
||||
assertTrue(result instanceof CreateReportRequest);
|
||||
|
@ -1,8 +1,11 @@
|
||||
package com.github.mc1arke.sonarqube.plugin.almclient.bitbucket;
|
||||
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.ResponseBody;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mockito;
|
||||
import org.sonar.api.config.internal.Encryption;
|
||||
import org.sonar.api.config.internal.Settings;
|
||||
@ -15,6 +18,8 @@ import java.io.IOException;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class DefaultBitbucketClientFactoryUnitTest {
|
||||
@ -32,7 +37,7 @@ public class DefaultBitbucketClientFactoryUnitTest {
|
||||
when(builder.addInterceptor(any())).thenReturn(builder);
|
||||
|
||||
ResponseBody responseBody = mock(ResponseBody.class);
|
||||
when(responseBody.string()).thenReturn("{}");
|
||||
when(responseBody.string()).thenReturn("{\"access_token\": \"dummy\"}");
|
||||
when(builder.build().newCall(any()).execute().body()).thenReturn(responseBody);
|
||||
|
||||
Settings settings = mock(Settings.class);
|
||||
@ -40,10 +45,31 @@ public class DefaultBitbucketClientFactoryUnitTest {
|
||||
|
||||
// when
|
||||
when(settings.getEncryption()).thenReturn(encryption);
|
||||
BitbucketClient client = new DefaultBitbucketClientFactory(settings, () -> builder).createClient(projectAlmSettingDto, almSettingDto);
|
||||
HttpClientBuilderFactory httpClientBuilderFactory = mock(HttpClientBuilderFactory.class);
|
||||
when(httpClientBuilderFactory.createClientBuilder()).then(i -> builder);
|
||||
BitbucketClient client = new DefaultBitbucketClientFactory(settings, httpClientBuilderFactory).createClient(projectAlmSettingDto, almSettingDto);
|
||||
|
||||
// then
|
||||
assertTrue(client instanceof BitbucketCloudClient);
|
||||
|
||||
ArgumentCaptor<Interceptor> interceptorArgumentCaptor = ArgumentCaptor.forClass(Interceptor.class);
|
||||
verify(builder, times(2)).addInterceptor(interceptorArgumentCaptor.capture());
|
||||
|
||||
Interceptor.Chain chain = mock(Interceptor.Chain.class);
|
||||
Request request = mock(Request.class);
|
||||
when(chain.request()).thenReturn(request);
|
||||
Request.Builder requestBuilder = mock(Request.Builder.class);
|
||||
when(requestBuilder.addHeader(any(), any())).thenReturn(requestBuilder);
|
||||
when(request.newBuilder()).thenReturn(requestBuilder);
|
||||
|
||||
Request request2 = mock(Request.class);
|
||||
when(requestBuilder.build()).thenReturn(request2);
|
||||
|
||||
interceptorArgumentCaptor.getValue().intercept(chain);
|
||||
|
||||
verify(requestBuilder).addHeader("Authorization", "Bearer dummy");
|
||||
verify(requestBuilder).addHeader("Accept", "application/json");
|
||||
verify(chain).proceed(request2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -61,7 +87,9 @@ public class DefaultBitbucketClientFactoryUnitTest {
|
||||
|
||||
// when
|
||||
when(settings.getEncryption()).thenReturn(encryption);
|
||||
BitbucketClient client = new DefaultBitbucketClientFactory(settings, () -> mock(OkHttpClient.Builder.class, Mockito.RETURNS_DEEP_STUBS)).createClient(projectAlmSettingDto, almSettingDto);
|
||||
HttpClientBuilderFactory httpClientBuilderFactory = mock(HttpClientBuilderFactory.class);
|
||||
when(httpClientBuilderFactory.createClientBuilder()).then(i -> mock(OkHttpClient.Builder.class, Mockito.RETURNS_DEEP_STUBS));
|
||||
BitbucketClient client = new DefaultBitbucketClientFactory(settings, httpClientBuilderFactory).createClient(projectAlmSettingDto, almSettingDto);
|
||||
|
||||
// then
|
||||
assertTrue(client instanceof BitbucketServerClient);
|
||||
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Michael Clarke
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
package com.github.mc1arke.sonarqube.plugin.almclient.bitbucket;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class HttpClientBuilderFactoryTest {
|
||||
|
||||
@Test
|
||||
void verifyNotSameInstanceReturnedByFactory() {
|
||||
HttpClientBuilderFactory underTest = new HttpClientBuilderFactory();
|
||||
OkHttpClient.Builder builder1 = underTest.createClientBuilder();
|
||||
OkHttpClient.Builder builder2 = underTest.createClientBuilder();
|
||||
|
||||
assertThat(builder1).isNotSameAs(builder2);
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Michael Clarke
|
||||
* Copyright (C) 2019-2022 Michael Clarke
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@ -32,7 +32,7 @@ public class CommunityReportAnalysisComponentProviderTest {
|
||||
@Test
|
||||
public void testGetComponents() {
|
||||
List<Object> result = new CommunityReportAnalysisComponentProvider().getComponents();
|
||||
assertEquals(13, result.size());
|
||||
assertEquals(14, result.size());
|
||||
assertEquals(CommunityBranchLoaderDelegate.class, result.get(0));
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2020 Michael Clarke
|
||||
* Copyright (C) 2020-2022 Michael Clarke
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@ -40,6 +40,7 @@ import org.sonar.api.issue.Issue;
|
||||
import org.sonar.api.measures.CoreMetrics;
|
||||
import org.sonar.api.rules.RuleType;
|
||||
import org.sonar.ce.task.projectanalysis.component.Component;
|
||||
import org.sonar.ce.task.projectanalysis.component.ReportAttributes;
|
||||
import org.sonar.ce.task.projectanalysis.component.TreeRootHolder;
|
||||
import org.sonar.ce.task.projectanalysis.measure.Measure;
|
||||
import org.sonar.ce.task.projectanalysis.measure.MeasureRepository;
|
||||
@ -910,4 +911,52 @@ public class AnalysisDetailsTest {
|
||||
mock(Configuration.class),"", mock(ScannerContext.class));
|
||||
assertThat(analysisDetails.parseIssueIdFromUrl("http://subdomain.sonarqube.dummy/path/issue?id=projectId&other_parameter=123")).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldOnlyReturnNonClosedFileIssuesWithScmInfo() {
|
||||
PostAnalysisIssueVisitor.LightIssue lightIssue1 = mock(PostAnalysisIssueVisitor.LightIssue.class);
|
||||
when(lightIssue1.status()).thenReturn(Issue.STATUS_OPEN);
|
||||
Component component1 = mock(Component.class);
|
||||
when(component1.getType()).thenReturn(Component.Type.FILE);
|
||||
ReportAttributes reportAttributes1 = mock(ReportAttributes.class);
|
||||
when(reportAttributes1.getScmPath()).thenReturn(Optional.of("path"));
|
||||
when(component1.getReportAttributes()).thenReturn(reportAttributes1);
|
||||
PostAnalysisIssueVisitor.ComponentIssue componentIssue1 = new PostAnalysisIssueVisitor.ComponentIssue(component1, lightIssue1);
|
||||
|
||||
PostAnalysisIssueVisitor.LightIssue lightIssue2 = mock(PostAnalysisIssueVisitor.LightIssue.class);
|
||||
when(lightIssue2.status()).thenReturn(Issue.STATUS_OPEN);
|
||||
Component component2 = mock(Component.class);
|
||||
when(component2.getType()).thenReturn(Component.Type.FILE);
|
||||
ReportAttributes reportAttributes2 = mock(ReportAttributes.class);
|
||||
when(reportAttributes2.getScmPath()).thenReturn(Optional.empty());
|
||||
when(component2.getReportAttributes()).thenReturn(reportAttributes2);
|
||||
PostAnalysisIssueVisitor.ComponentIssue componentIssue2 = new PostAnalysisIssueVisitor.ComponentIssue(component2, lightIssue2);
|
||||
|
||||
PostAnalysisIssueVisitor.LightIssue lightIssue3 = mock(PostAnalysisIssueVisitor.LightIssue.class);
|
||||
when(lightIssue3.status()).thenReturn(Issue.STATUS_OPEN);
|
||||
Component component3 = mock(Component.class);
|
||||
when(component3.getType()).thenReturn(Component.Type.PROJECT);
|
||||
ReportAttributes reportAttributes3 = mock(ReportAttributes.class);
|
||||
when(reportAttributes3.getScmPath()).thenReturn(Optional.of("path"));
|
||||
when(component3.getReportAttributes()).thenReturn(reportAttributes3);
|
||||
PostAnalysisIssueVisitor.ComponentIssue componentIssue3 = new PostAnalysisIssueVisitor.ComponentIssue(component3, lightIssue3);
|
||||
|
||||
PostAnalysisIssueVisitor.LightIssue lightIssue4 = mock(PostAnalysisIssueVisitor.LightIssue.class);
|
||||
when(lightIssue4.status()).thenReturn(Issue.STATUS_CLOSED);
|
||||
Component component4 = mock(Component.class);
|
||||
when(component4.getType()).thenReturn(Component.Type.FILE);
|
||||
ReportAttributes reportAttributes4 = mock(ReportAttributes.class);
|
||||
when(reportAttributes4.getScmPath()).thenReturn(Optional.of("path"));
|
||||
when(component4.getReportAttributes()).thenReturn(reportAttributes4);
|
||||
PostAnalysisIssueVisitor.ComponentIssue componentIssue4 = new PostAnalysisIssueVisitor.ComponentIssue(component4, lightIssue4);
|
||||
|
||||
PostAnalysisIssueVisitor postAnalysisIssueVisitor = mock(PostAnalysisIssueVisitor.class);
|
||||
when(postAnalysisIssueVisitor.getIssues()).thenReturn(Arrays.asList(componentIssue1, componentIssue2, componentIssue3, componentIssue4));
|
||||
|
||||
AnalysisDetails underTest = new AnalysisDetails(mock(AnalysisDetails.BranchDetails.class), postAnalysisIssueVisitor,
|
||||
mock(QualityGate.class), mock(AnalysisDetails.MeasuresHolder.class), mock(Analysis.class), mock(Project.class),
|
||||
mock(Configuration.class),"", mock(ScannerContext.class));
|
||||
|
||||
assertThat(underTest.getScmReportableIssues()).containsOnly(componentIssue1);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Michael Clarke
|
||||
* Copyright (C) 2019-2022 Michael Clarke
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@ -57,7 +57,7 @@ public class PostAnalysisIssueVisitorTest {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
DefaultIssue issue = (i == 10 ? null : mock(DefaultIssue.class));
|
||||
Component component = (i == 5 ? null : mock(Component.class));
|
||||
expected.add(new PostAnalysisIssueVisitor.ComponentIssue(component, issue));
|
||||
expected.add(new PostAnalysisIssueVisitor.ComponentIssue(component, null == issue ? null : new PostAnalysisIssueVisitor.LightIssue(issue)));
|
||||
|
||||
testCase.onIssue(component, issue);
|
||||
}
|
||||
|
@ -3,12 +3,13 @@ package com.github.mc1arke.sonarqube.plugin.ce.pullrequest.bitbucket;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.BitbucketClient;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.BitbucketClientFactory;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.AnnotationUploadLimit;
|
||||
import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.ReportStatus;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.AnalysisDetails;
|
||||
import com.github.mc1arke.sonarqube.plugin.ce.pullrequest.PostAnalysisIssueVisitor;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
import org.sonar.api.ce.posttask.QualityGate;
|
||||
import org.sonar.api.issue.Issue;
|
||||
import org.sonar.api.measures.CoreMetrics;
|
||||
@ -27,18 +28,15 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class BitbucketPullRequestDecoratorTest {
|
||||
class BitbucketPullRequestDecoratorTest {
|
||||
|
||||
private static final String PROJECT = "project";
|
||||
private static final String REPO = "repo";
|
||||
private static final String COMMIT = "commit";
|
||||
|
||||
private static final String ISSUE_KEY = "issue-key";
|
||||
@ -57,15 +55,13 @@ public class BitbucketPullRequestDecoratorTest {
|
||||
private final AlmSettingDto almSettingDto = mock(AlmSettingDto.class);
|
||||
private final ProjectAlmSettingDto projectAlmSettingDto = mock(ProjectAlmSettingDto.class);
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
when(bitbucketClientFactory.createClient(any(), any())).thenReturn(client);
|
||||
when(client.resolveProject(any(), any())).thenReturn(PROJECT);
|
||||
when(client.resolveRepository(any(), any())).thenReturn(REPO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidAnalysis() throws IOException {
|
||||
void testValidAnalysis() throws IOException {
|
||||
when(client.supportsCodeInsights()).thenReturn(true);
|
||||
AnnotationUploadLimit uploadLimit = new AnnotationUploadLimit(1000, 1000);
|
||||
when(client.getAnnotationUploadLimit()).thenReturn(uploadLimit);
|
||||
@ -73,43 +69,19 @@ public class BitbucketPullRequestDecoratorTest {
|
||||
mockValidAnalysis();
|
||||
underTest.decorateQualityGateStatus(analysisDetails, almSettingDto, projectAlmSettingDto);
|
||||
|
||||
verify(client).createCodeInsightsAnnotation(eq(ISSUE_KEY), eq(ISSUE_LINE), eq(ISSUE_LINK), eq(ISSUE_MESSAGE), eq(ISSUE_PATH), eq("HIGH"), eq("BUG"));
|
||||
verify(client).createCodeInsightsAnnotation(ISSUE_KEY, ISSUE_LINE, ISSUE_LINK, ISSUE_MESSAGE, ISSUE_PATH, "HIGH", "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);
|
||||
verify(client).createCodeInsightsReport(any(), eq("Quality Gate passed" + System.lineSeparator()), any(), eq(DASHBOARD_URL), eq(String.format("%s/common/icon.png", IMAGE_URL)), eq(ReportStatus.PASSED));
|
||||
verify(client).deleteAnnotations(COMMIT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExceedsMaximumNumberOfAnnotations() {
|
||||
@ParameterizedTest(name = "{arguments}")
|
||||
@CsvSource({"100, 1000, 2",
|
||||
"1000, 1000, 1",
|
||||
"100, 1000, 10"})
|
||||
void testExceedsMaximumNumberOfAnnotations(int annotationBatchSize, int totalAllowedAnnotations, int counter) {
|
||||
// given
|
||||
AnnotationUploadLimit uploadLimit = new AnnotationUploadLimit(100, 1000);
|
||||
int counter = 2;
|
||||
|
||||
// when
|
||||
boolean result = BitbucketPullRequestDecorator.exceedsMaximumNumberOfAnnotations(counter, uploadLimit);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExceedsMaximumNumberOfAnnotationsEdgeCase() {
|
||||
// given
|
||||
AnnotationUploadLimit uploadLimit = new AnnotationUploadLimit(1000, 1000);
|
||||
int counter = 1;
|
||||
|
||||
// when
|
||||
boolean result = BitbucketPullRequestDecorator.exceedsMaximumNumberOfAnnotations(counter, uploadLimit);
|
||||
|
||||
// then
|
||||
assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExceedsMaximumNumberOfAnnotationsEdgeBatchCase() {
|
||||
// given
|
||||
AnnotationUploadLimit uploadLimit = new AnnotationUploadLimit(100, 1000);
|
||||
int counter = 10;
|
||||
AnnotationUploadLimit uploadLimit = new AnnotationUploadLimit(annotationBatchSize, totalAllowedAnnotations);
|
||||
|
||||
// when
|
||||
boolean result = BitbucketPullRequestDecorator.exceedsMaximumNumberOfAnnotations(counter, uploadLimit);
|
||||
@ -155,10 +127,7 @@ public class BitbucketPullRequestDecoratorTest {
|
||||
when(componentIssue.getIssue()).thenReturn(defaultIssue);
|
||||
when(componentIssue.getComponent()).thenReturn(component);
|
||||
|
||||
PostAnalysisIssueVisitor postAnalysisIssueVisitor = mock(PostAnalysisIssueVisitor.class);
|
||||
when(postAnalysisIssueVisitor.getIssues()).thenReturn(Collections.singletonList(componentIssue));
|
||||
|
||||
when(analysisDetails.getPostAnalysisIssueVisitor()).thenReturn(postAnalysisIssueVisitor);
|
||||
when(analysisDetails.getScmReportableIssues()).thenReturn(Collections.singletonList(componentIssue));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2021 Michael Clarke
|
||||
* Copyright (C) 2021-2022 Michael Clarke
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
@ -72,7 +72,7 @@ class BitbucketValidatorTest {
|
||||
void testInvalidConfigurationExceptionThrownIfRetrieveRepositoryFails() throws IOException {
|
||||
BitbucketValidator underTest = new BitbucketValidator(bitbucketClientFactory);
|
||||
BitbucketClient bitbucketClient = mock(BitbucketClient.class);
|
||||
when(bitbucketClient.retrieveRepository(any(), any())).thenThrow(new IOException("dummy"));
|
||||
when(bitbucketClient.retrieveRepository()).thenThrow(new IOException("dummy"));
|
||||
when(bitbucketClientFactory.createClient(any(), any())).thenReturn(bitbucketClient);
|
||||
assertThatThrownBy(() -> underTest.validate(projectAlmSettingDto, almSettingDto))
|
||||
.isInstanceOf(InvalidConfigurationException.class)
|
||||
|
Loading…
x
Reference in New Issue
Block a user