From 8ce58250dbf08dce8f445cb2228ddae73858dd8e Mon Sep 17 00:00:00 2001 From: Michael Clarke Date: Sat, 16 Nov 2024 15:41:23 +0000 Subject: [PATCH] #893: Report build status to Bitbucket The Bitbucket decorators submit a report to Bitbucket containing the quality gate summary, but don't submit a report that influences the build status. A second call is being made to submit abuild status that is either successful is the Quality Gate has passed, or failed is the Quality Gate did not pass. --- .../almclient/bitbucket/BitbucketClient.java | 10 +- .../bitbucket/BitbucketCloudClient.java | 17 ++- .../bitbucket/BitbucketServerClient.java | 15 ++- .../bitbucket/model/BuildStatus.java | 63 +++++++++++ .../BitbucketPullRequestDecorator.java | 4 + .../BitbucketCloudClientUnitTest.java | 91 +++++++++------ .../BitbucketServerClientUnitTest.java | 107 ++++++++++-------- .../BitbucketPullRequestDecoratorTest.java | 5 + 8 files changed, 228 insertions(+), 84 deletions(-) create mode 100644 src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/bitbucket/model/BuildStatus.java diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/bitbucket/BitbucketClient.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/bitbucket/BitbucketClient.java index 336df3ba1..7875726aa 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/bitbucket/BitbucketClient.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/bitbucket/BitbucketClient.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2022 Marvin Wichmann, Michael Clarke + * Copyright (C) 2020-2024 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 @@ -19,6 +19,7 @@ package com.github.mc1arke.sonarqube.plugin.almclient.bitbucket; import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.AnnotationUploadLimit; +import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.BuildStatus; 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; @@ -108,4 +109,11 @@ CodeInsightsReport createCodeInsightsReport(List reportData, */ Repository retrieveRepository() throws IOException; + /** + * Submit the build status for the given commit. + * @param commitSha the Git commit hash + * @param buildStatus the build status containing the state, URL, and identifiers + * @throws IOException on any issue submitting the build status + */ + void submitBuildStatus(String commitSha, BuildStatus buildStatus) throws IOException; } diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/bitbucket/BitbucketCloudClient.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/bitbucket/BitbucketCloudClient.java index e9ffbbd14..e0d3d4f8d 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/bitbucket/BitbucketCloudClient.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/bitbucket/BitbucketCloudClient.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2023 Marvin Wichmann, Michael Clarke + * Copyright (C) 2020-2024 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,6 +23,7 @@ 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.BuildStatus; 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; @@ -198,6 +199,20 @@ public Repository retrieveRepository() throws IOException { } } + @Override + public void submitBuildStatus(String commitSha, BuildStatus buildStatus) throws IOException { + Request req = new Request.Builder() + .post(RequestBody.create(objectMapper.writeValueAsString(buildStatus), APPLICATION_JSON_MEDIA_TYPE)) + .url(format("https://api.bitbucket.org/2.0/repositories/%s/%s/commit/%s/statuses/build", bitbucketConfiguration.getProject(), bitbucketConfiguration.getRepository(), commitSha)) + .build(); + + LOGGER.info("Submitting build status to bitbucket cloud"); + + try (Response response = okHttpClient.newCall(req).execute()) { + validate(response); + } + } + void deleteExistingReport(String commit, String reportKey) throws IOException { Request req = new Request.Builder() .delete() diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/bitbucket/BitbucketServerClient.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/bitbucket/BitbucketServerClient.java index c40394281..5465f43e6 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/bitbucket/BitbucketServerClient.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/bitbucket/BitbucketServerClient.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020-2023 Mathias Åhsberg, Michael Clarke + * Copyright (C) 2020-2024 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 @@ -20,6 +20,7 @@ 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.BuildStatus; 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; @@ -179,6 +180,18 @@ public Repository retrieveRepository() throws IOException { } } + @Override + public void submitBuildStatus(String commitSha, BuildStatus buildStatus) throws IOException { + Request req = new Request.Builder() + .post(RequestBody.create(objectMapper.writeValueAsString(buildStatus), APPLICATION_JSON_MEDIA_TYPE)) + .url(format("%s/rest/api/1.0/projects/%s/repos/%s/commits/%s/builds", config.getUrl(), config.getProject(), config.getRepository(), commitSha)) + .build(); + + try (Response response = okHttpClient.newCall(req).execute()) { + validate(response); + } + } + public ServerProperties getServerProperties() throws IOException { Request req = new Request.Builder() .get() diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/bitbucket/model/BuildStatus.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/bitbucket/model/BuildStatus.java new file mode 100644 index 000000000..2530dd361 --- /dev/null +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/almclient/bitbucket/model/BuildStatus.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2024 Michael Clarke + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +package com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class BuildStatus { + + private final State state; + private final String key; + private final String name; + private final String url; + + public BuildStatus(@JsonProperty("state") State state, + @JsonProperty("key") String key, + @JsonProperty("name") String name, + @JsonProperty("url") String url) { + this.state = state; + this.key = key; + this.name = name; + this.url = url; + } + + public State getState() { + return state; + } + + public String getKey() { + return key; + } + + public String getName() { + return name; + } + + public String getUrl() { + return url; + } + + public enum State { + INPROGRESS, + SUCCESSFUL, + FAILED, + CANCELLED, + UNKNOWN + } +} diff --git a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/BitbucketPullRequestDecorator.java b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/BitbucketPullRequestDecorator.java index 228ce0a0d..9520523ca 100644 --- a/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/BitbucketPullRequestDecorator.java +++ b/src/main/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/BitbucketPullRequestDecorator.java @@ -45,6 +45,7 @@ import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.BitbucketClientFactory; import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.BitbucketException; import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.AnnotationUploadLimit; +import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.BuildStatus; 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; @@ -98,6 +99,9 @@ public DecorationResult decorateQualityGateStatus(AnalysisDetails analysisDetail client.uploadReport(analysisDetails.getCommitSha(), codeInsightsReport, reportKey); updateAnnotations(client, analysisDetails, reportKey); + + BuildStatus buildStatus = new BuildStatus(analysisDetails.getQualityGateStatus() == QualityGate.Status.OK ? BuildStatus.State.SUCCESSFUL : BuildStatus.State.FAILED, reportKey, "SonarQube", analysisSummary.getDashboardUrl()); + client.submitBuildStatus(analysisDetails.getCommitSha(),buildStatus); } catch (IOException e) { LOGGER.error("Could not decorate pull request for project {}", analysisDetails.getAnalysisProjectKey(), e); } diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/bitbucket/BitbucketCloudClientUnitTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/bitbucket/BitbucketCloudClientUnitTest.java index aade33685..f844c6d33 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/bitbucket/BitbucketCloudClientUnitTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/bitbucket/BitbucketCloudClientUnitTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021-2022 Michael Clarke + * Copyright (C) 2021-2024 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -21,6 +21,7 @@ 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.BuildStatus; 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; @@ -33,21 +34,21 @@ import okhttp3.Request; import okhttp3.Response; import okhttp3.ResponseBody; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; import java.io.IOException; import java.time.Instant; import java.util.ArrayList; import java.util.Set; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -55,27 +56,20 @@ 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; +class BitbucketCloudClientUnitTest { - @Mock - private OkHttpClient client; + private final ObjectMapper mapper = mock(); + private final OkHttpClient client = mock(); + private final BitbucketCloudClient underTest = new BitbucketCloudClient(mapper, client, new BitbucketConfiguration("project", "repository")); - @Before - public void before() { - BitbucketConfiguration bitbucketConfiguration = new BitbucketConfiguration("project", "repository"); - Call call = mock(Call.class); + @BeforeEach + void setup() { + Call call = mock(); when(client.newCall(any())).thenReturn(call); - underTest = new BitbucketCloudClient(mapper, client, bitbucketConfiguration); } @Test - public void testUploadReport() throws IOException { + void testUploadReport() throws IOException { // given CodeInsightsReport report = mock(CodeInsightsReport.class); Call call = mock(Call.class); @@ -99,7 +93,7 @@ public void testUploadReport() throws IOException { } @Test - public void testDeleteReport() throws IOException { + void testDeleteReport() throws IOException { // given Call call = mock(Call.class); Response response = mock(Response.class); @@ -119,7 +113,7 @@ public void testDeleteReport() throws IOException { } @Test - public void testUploadAnnotations() throws IOException { + void testUploadAnnotations() throws IOException { // given CodeInsightsAnnotation annotation = mock(CloudAnnotation.class); Set annotations = Sets.newHashSet(annotation); @@ -144,7 +138,7 @@ public void testUploadAnnotations() throws IOException { } @Test - public void testUploadLimit() { + void testUploadLimit() { // given // when AnnotationUploadLimit annotationUploadLimit = underTest.getAnnotationUploadLimit(); @@ -155,7 +149,7 @@ public void testUploadLimit() { } @Test - public void testUploadReportFailsWithMessage() throws IOException { + void testUploadReportFailsWithMessage() throws IOException { // given CodeInsightsReport report = mock(CodeInsightsReport.class); Call call = mock(Call.class); @@ -180,7 +174,7 @@ public void testUploadReportFailsWithMessage() throws IOException { } @Test - public void testUploadAnnotationsWithEmptyAnnotations() throws IOException { + void testUploadAnnotationsWithEmptyAnnotations() throws IOException { // given Set annotations = Sets.newHashSet(); @@ -192,14 +186,14 @@ public void testUploadAnnotationsWithEmptyAnnotations() throws IOException { } @Test - public void testCreateAnnotationForCloud() { + 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); + assertInstanceOf(CloudAnnotation.class, annotation); assertEquals("issueKey", ((CloudAnnotation) annotation).getExternalId()); assertEquals(12, annotation.getLine()); assertEquals("http://localhost:9000/dashboard", ((CloudAnnotation) annotation).getLink()); @@ -209,19 +203,19 @@ public void testCreateAnnotationForCloud() { } @Test - public void testCreateDataLinkForCloud() { + void testCreateDataLinkForCloud() { // given // when DataValue data = underTest.createLinkDataValue("https://localhost:9000/any/project"); // then - assertTrue(data instanceof DataValue.CloudLink); + assertInstanceOf(DataValue.CloudLink.class, data); assertEquals("https://localhost:9000/any/project", ((DataValue.CloudLink) data).getHref()); } @Test - public void testCloudAlwaysSupportsCodeInsights() { + void testCloudAlwaysSupportsCodeInsights() { // given // when @@ -232,14 +226,14 @@ public void testCloudAlwaysSupportsCodeInsights() { } @Test - public void testCreateCloudReport() { + void testCreateCloudReport() { // given // when CodeInsightsReport result = underTest.createCodeInsightsReport(new ArrayList<>(), "reportDescription", Instant.now(), "dashboardUrl", "logoUrl", ReportStatus.FAILED); // then - assertTrue(result instanceof CloudCreateReportRequest); + assertInstanceOf(CloudCreateReportRequest.class, result); assertEquals(0, result.getData().size()); assertEquals("reportDescription", result.getDetails()); assertEquals("dashboardUrl", result.getLink()); @@ -247,4 +241,31 @@ public void testCreateCloudReport() { assertEquals("FAILED", result.getResult()); } + + @Test + void shouldSubmitBuildStatusToServer() throws IOException { + // given + Call call = mock(); + Response response = mock(); + ArgumentCaptor captor = ArgumentCaptor.captor(); + + when(client.newCall(any())).thenReturn(call); + when(call.execute()).thenReturn(response); + when(response.isSuccessful()).thenReturn(true); + + when(mapper.writeValueAsString(any())).thenReturn("{payload}"); + + BuildStatus buildStatus = new BuildStatus(BuildStatus.State.INPROGRESS, "key", "name", "url"); + + // when + underTest.submitBuildStatus("commit", buildStatus); + + // then + verify(client).newCall(captor.capture()); + Request request = captor.getValue(); + assertThat(request.method()).isEqualTo("POST"); + assertThat(request.url()).hasToString("https://api.bitbucket.org/2.0/repositories/project/repository/commit/commit/statuses/build"); + + verify(mapper).writeValueAsString(buildStatus); + } } diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/bitbucket/BitbucketServerClientUnitTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/bitbucket/BitbucketServerClientUnitTest.java index 13c40c380..0c2fcd6e8 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/bitbucket/BitbucketServerClientUnitTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/almclient/bitbucket/BitbucketServerClientUnitTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021-2022 Michael Clarke + * Copyright (C) 2021-2024 Michael Clarke * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectReader; import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.AnnotationUploadLimit; +import com.github.mc1arke.sonarqube.plugin.almclient.bitbucket.model.BuildStatus; 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; @@ -37,49 +38,36 @@ import okhttp3.Response; import okhttp3.ResponseBody; import okio.Buffer; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; + +import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.Spy; -import org.mockito.junit.MockitoJUnitRunner; import java.io.IOException; import java.time.Instant; import java.util.ArrayList; import java.util.Set; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -@RunWith(MockitoJUnitRunner.class) -public class BitbucketServerClientUnitTest { - - private BitbucketServerClient underTest; - - @Spy - private ObjectMapper mapper; +class BitbucketServerClientUnitTest { - @Mock - private OkHttpClient client; - - @Before - public void before() { - BitbucketServerConfiguration - config = new BitbucketServerConfiguration("project", "repository", "https://my-server.org"); - underTest = new BitbucketServerClient(config, mapper, client); - } + private final ObjectMapper mapper = spy(); + private final OkHttpClient client = mock(); + private final BitbucketServerClient underTest = new BitbucketServerClient(new BitbucketServerConfiguration("project", "repository", "https://my-server.org"), mapper, client); @Test - public void testSupportsCodeInsightsIsFalse() throws IOException { + void testSupportsCodeInsightsIsFalse() throws IOException { // given ServerProperties serverProperties = new ServerProperties("5.0"); @@ -106,7 +94,7 @@ public void testSupportsCodeInsightsIsFalse() throws IOException { } @Test - public void testSupportsCodeInsightsIsTrueWhenVersionEqual() throws IOException { + void testSupportsCodeInsightsIsTrueWhenVersionEqual() throws IOException { // given ServerProperties serverProperties = new ServerProperties("5.15"); @@ -133,7 +121,7 @@ public void testSupportsCodeInsightsIsTrueWhenVersionEqual() throws IOException } @Test - public void testSupportsCodeInsightsIsTrueIfVersionIsHigher() throws IOException { + void testSupportsCodeInsightsIsTrueIfVersionIsHigher() throws IOException { // given ServerProperties serverProperties = new ServerProperties("6.0"); @@ -160,7 +148,7 @@ public void testSupportsCodeInsightsIsTrueIfVersionIsHigher() throws IOException } @Test - public void testSupportsCodeInsightsIsFalseWhenException() throws IOException { + void testSupportsCodeInsightsIsFalseWhenException() throws IOException { // given Call call = mock(Call.class); when(client.newCall(any())).thenReturn(call); @@ -174,7 +162,7 @@ public void testSupportsCodeInsightsIsFalseWhenException() throws IOException { } @Test - public void testGetServerProperties() throws IOException { + void testGetServerProperties() throws IOException { // given ServerProperties serverProperties = new ServerProperties("5.0"); @@ -206,7 +194,7 @@ public void testGetServerProperties() throws IOException { } @Test - public void testGetServerPropertiesError() throws IOException { + void testGetServerPropertiesError() throws IOException { // given Call call = mock(Call.class); Response response = mock(Response.class); @@ -221,12 +209,12 @@ public void testGetServerPropertiesError() throws IOException { when(reader.forType(ServerProperties.class)).thenReturn(reader); // when, then - assertThatThrownBy(() -> underTest.getServerProperties()) + assertThatThrownBy(underTest::getServerProperties) .isInstanceOf(IllegalStateException.class); } @Test - public void testUploadReport() throws IOException { + void testUploadReport() throws IOException { // given CodeInsightsReport report = mock(CodeInsightsReport.class); Call call = mock(Call.class); @@ -250,7 +238,7 @@ public void testUploadReport() throws IOException { } @Test - public void testUploadReportFails() throws IOException { + void testUploadReportFails() throws IOException { // given CodeInsightsReport report = mock(CodeInsightsReport.class); Call call = mock(Call.class); @@ -269,7 +257,7 @@ public void testUploadReportFails() throws IOException { } @Test - public void testUploadReportFailsWithMessage() throws IOException { + void testUploadReportFailsWithMessage() throws IOException { // given ErrorResponse.Error error = new ErrorResponse.Error("error!"); ErrorResponse errorResponse = new ErrorResponse(Sets.newHashSet(error)); @@ -303,7 +291,7 @@ public void testUploadReportFailsWithMessage() throws IOException { } @Test - public void testUploadAnnotations() throws IOException { + void testUploadAnnotations() throws IOException { // given Annotation annotation = mock(Annotation.class); when(annotation.getLine()).thenReturn(12); @@ -338,7 +326,7 @@ public void testUploadAnnotations() throws IOException { } @Test - public void testUploadAnnotationsWithEmptyAnnotations() throws IOException { + void testUploadAnnotationsWithEmptyAnnotations() throws IOException { // given Set annotations = Sets.newHashSet(); @@ -350,7 +338,7 @@ public void testUploadAnnotationsWithEmptyAnnotations() throws IOException { } @Test - public void testDeleteAnnotations() throws IOException { + void testDeleteAnnotations() throws IOException { // given Call call = mock(Call.class); Response response = mock(Response.class); @@ -371,13 +359,13 @@ public void testDeleteAnnotations() throws IOException { } @Test - public void testCreateAnnotationForServer() { + 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); + assertInstanceOf(Annotation.class, annotation); assertEquals("issueKey", ((Annotation) annotation).getExternalId()); assertEquals(12, annotation.getLine()); assertEquals("http://localhost:9000/dashboard", ((Annotation) annotation).getLink()); @@ -387,18 +375,18 @@ public void testCreateAnnotationForServer() { } @Test - public void testCreateDataLinkForServer() { + void testCreateDataLinkForServer() { // given // when DataValue data = underTest.createLinkDataValue("https://localhost:9000/any/project"); // then - assertTrue(data instanceof DataValue.Link); + assertInstanceOf(DataValue.Link.class, data); assertEquals("https://localhost:9000/any/project", ((DataValue.Link) data).getHref()); } @Test - public void testUploadLimit() { + void testUploadLimit() { // given // when AnnotationUploadLimit annotationUploadLimit = underTest.getAnnotationUploadLimit(); @@ -409,18 +397,45 @@ public void testUploadLimit() { } @Test - public void testCreateCloudReport() { + void testCreateCloudReport() { // given // when CodeInsightsReport result = underTest.createCodeInsightsReport(new ArrayList<>(), "reportDescription", Instant.now(), "dashboardUrl", "logoUrl", ReportStatus.FAILED); // then - assertTrue(result instanceof CreateReportRequest); + assertInstanceOf(CreateReportRequest.class, result); assertEquals(0, result.getData().size()); assertEquals("reportDescription", result.getDetails()); assertEquals("dashboardUrl", result.getLink()); assertEquals("logoUrl", ((CreateReportRequest) result).getLogoUrl()); assertEquals("FAIL", result.getResult()); } + + @Test + void shouldSubmitBuildStatusToServer() throws IOException { + // given + Call call = mock(); + Response response = mock(); + ArgumentCaptor captor = ArgumentCaptor.captor(); + + when(client.newCall(any())).thenReturn(call); + when(call.execute()).thenReturn(response); + when(response.isSuccessful()).thenReturn(true); + + when(mapper.writeValueAsString(any())).thenReturn("{payload}"); + + BuildStatus buildStatus = new BuildStatus(BuildStatus.State.INPROGRESS, "key", "name", "url"); + + // when + underTest.submitBuildStatus("commit", buildStatus); + + // then + verify(client).newCall(captor.capture()); + Request request = captor.getValue(); + assertThat(request.method()).isEqualTo("POST"); + assertThat(request.url()).hasToString("https://my-server.org/rest/api/1.0/projects/project/repos/repository/commits/commit/builds"); + + verify(mapper).writeValueAsString(buildStatus); + } } diff --git a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/BitbucketPullRequestDecoratorTest.java b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/BitbucketPullRequestDecoratorTest.java index a4f9b893a..3eb6fc45e 100644 --- a/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/BitbucketPullRequestDecoratorTest.java +++ b/src/test/java/com/github/mc1arke/sonarqube/plugin/ce/pullrequest/bitbucket/BitbucketPullRequestDecoratorTest.java @@ -51,6 +51,7 @@ 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.BuildStatus; 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; @@ -147,6 +148,10 @@ void testNullPercentagesReplacedWithZeroValues() throws IOException { verify(client).createCodeInsightsReport(reportDataArgumentCaptor.capture(), 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, REPORT_KEY); + ArgumentCaptor buildStatusArgumentCaptor = ArgumentCaptor.captor(); + verify(client).submitBuildStatus(eq(COMMIT), buildStatusArgumentCaptor.capture()); + assertThat(buildStatusArgumentCaptor.getValue()).usingRecursiveComparison().isEqualTo(new BuildStatus(BuildStatus.State.SUCCESSFUL, REPORT_KEY, "SonarQube", DASHBOARD_URL)); + assertThat(reportDataArgumentCaptor.getValue()) .usingRecursiveComparison() .isEqualTo(List.of(new ReportData("New Issues", new DataValue.Text("666 Issues")),