diff --git a/docs/consumers-guide.md b/docs/consumers-guide.md index ddc53412..2fd6d2d2 100644 --- a/docs/consumers-guide.md +++ b/docs/consumers-guide.md @@ -62,3 +62,12 @@ getContext().get(ChecksInfo.class) Currently, the `ChecksInfo` object only includes a `name` specified by users, it is recommended that you look for this name and set it over your default checks name +## Integration Testing + +An implementation of `ChecksPublisher` that captures all published `ChecksDetails` is provided +in the `test` classifier, as `io.jenkins.plugins.checks.api.test.CapturingChecksPublisher`. + +Adding the factory for this publisher as a `TestExtension` will allow inspection of published checks after running a job +on a `JenkinsRule`. + +An example of this can be found in [PublishChecksStepITest](../src/test/java/io/jenkins/plugins/checks/steps/PublishChecksStepITest.java). diff --git a/pom.xml b/pom.xml index bce66b18..4fe8161e 100644 --- a/pom.xml +++ b/pom.xml @@ -126,6 +126,22 @@ + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + **/api/test/** + + + + + diff --git a/src/main/java/io/jenkins/plugins/checks/steps/PublishChecksStep.java b/src/main/java/io/jenkins/plugins/checks/steps/PublishChecksStep.java index 29b55b33..22724010 100644 --- a/src/main/java/io/jenkins/plugins/checks/steps/PublishChecksStep.java +++ b/src/main/java/io/jenkins/plugins/checks/steps/PublishChecksStep.java @@ -180,9 +180,16 @@ protected Void run() throws IOException, InterruptedException { } @VisibleForTesting - ChecksDetails extractChecksDetails() { + ChecksDetails extractChecksDetails() throws IOException, InterruptedException { + // If a checks name has been provided as part of the step, use that. + // If not, check to see if there is an active ChecksInfo context (e.g. from withChecks). + String checksName = StringUtils.defaultIfEmpty(step.getName(), + Optional.ofNullable(getContext().get(ChecksInfo.class)) + .map(ChecksInfo::getName) + .orElse(StringUtils.EMPTY) + ); return new ChecksDetails.ChecksDetailsBuilder() - .withName(step.getName()) + .withName(checksName) .withStatus(step.getStatus()) .withConclusion(step.getConclusion()) .withDetailsURL(step.getDetailsURL()) diff --git a/src/test/java/io/jenkins/plugins/checks/api/test/CapturingChecksPublisher.java b/src/test/java/io/jenkins/plugins/checks/api/test/CapturingChecksPublisher.java new file mode 100644 index 00000000..b29d900c --- /dev/null +++ b/src/test/java/io/jenkins/plugins/checks/api/test/CapturingChecksPublisher.java @@ -0,0 +1,75 @@ +package io.jenkins.plugins.checks.api.test; + +import hudson.ExtensionList; +import hudson.model.Job; +import hudson.model.Run; +import hudson.model.TaskListener; +import io.jenkins.plugins.checks.api.ChecksDetails; +import io.jenkins.plugins.checks.api.ChecksPublisher; +import io.jenkins.plugins.checks.api.ChecksPublisherFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +/** + * Implementation of {@link ChecksPublisher} for use in testing, that records each captured checks in a simple list. + * + * For example: + * + *
+ * public class ChecksPublishingTest extends IntegrationTestWithJenkinsPerTest {
+ *
+ *     @TestExtension
+ *     public static final CapturingChecksPublisher.Factory PUBLISHER_FACTORY = new CapturingChecksPublisher.Factory();
+ *
+ *     @After
+ *     public void clearPublishedChecks() {
+ *         PUBLISHER_FACTORY.getPublishedChecks().clear();
+ *     }
+ *
+ *     @Test
+ *     public void testChecksPublishing() {
+ *
+ *         // ...Run a test job...
+ *
+ *         List<ChecksDetails> publishedDetails = PUBLISHER_FACTORY.getPublishedChecks();
+ *
+ *         // ...Inspect published checks...
+ *     }
+ * }
+ * 
+ * + * An example of this can be found in {@link io.jenkins.plugins.checks.steps.PublishChecksStepITest} + */ +public class CapturingChecksPublisher extends ChecksPublisher { + + private final List publishedChecks = new ArrayList<>(); + + @Override + public void publish(final ChecksDetails details) { + publishedChecks.add(details); + } + + /** + * Implementation of {@link ChecksPublisherFactory} that returns a {@link CapturingChecksPublisher}. + */ + public static class Factory extends ChecksPublisherFactory { + + private final CapturingChecksPublisher publisher = new CapturingChecksPublisher(); + + @Override + protected Optional createPublisher(final Run run, final TaskListener listener) { + return Optional.of(publisher); + } + + @Override + protected Optional createPublisher(final Job job, final TaskListener listener) { + return Optional.of(publisher); + } + + public List getPublishedChecks() { + return ExtensionList.lookup(Factory.class).get(0).publisher.publishedChecks; + } + } +} diff --git a/src/test/java/io/jenkins/plugins/checks/steps/PublishChecksStepITest.java b/src/test/java/io/jenkins/plugins/checks/steps/PublishChecksStepITest.java index 6dc792df..f76ff38c 100644 --- a/src/test/java/io/jenkins/plugins/checks/steps/PublishChecksStepITest.java +++ b/src/test/java/io/jenkins/plugins/checks/steps/PublishChecksStepITest.java @@ -1,9 +1,15 @@ package io.jenkins.plugins.checks.steps; +import io.jenkins.plugins.checks.api.ChecksConclusion; +import io.jenkins.plugins.checks.api.ChecksDetails; +import io.jenkins.plugins.checks.api.ChecksOutput; +import io.jenkins.plugins.checks.api.ChecksStatus; +import io.jenkins.plugins.checks.api.test.CapturingChecksPublisher; import io.jenkins.plugins.util.IntegrationTestWithJenkinsPerTest; import org.jenkinsci.plugins.workflow.job.WorkflowJob; import org.junit.Test; import org.jvnet.hudson.test.JenkinsRule; +import org.jvnet.hudson.test.TestExtension; import java.io.IOException; @@ -13,6 +19,13 @@ * Tests the pipeline step to publish checks. */ public class PublishChecksStepITest extends IntegrationTestWithJenkinsPerTest { + + /** + * Provide a {@link CapturingChecksPublisher} to check published checks on each test. + */ + @TestExtension + public static final CapturingChecksPublisher.Factory PUBLISHER_FACTORY = new CapturingChecksPublisher.Factory(); + /** * Tests that the step "publishChecks" can be used in pipeline script. * @@ -26,7 +39,20 @@ public void shouldPublishChecksWhenUsingPipeline() throws IOException { + "text: 'Pipeline support for checks', status: 'IN_PROGRESS', conclusion: 'NONE'")); assertThat(JenkinsRule.getLog(buildSuccessfully(job))) - .contains("[Pipeline] publishChecks") - .contains("[Checks API] No suitable checks publisher found."); + .contains("[Pipeline] publishChecks"); + + assertThat(PUBLISHER_FACTORY.getPublishedChecks().size()).isEqualTo(1); + + ChecksDetails details = PUBLISHER_FACTORY.getPublishedChecks().get(0); + + assertThat(details.getName()).isPresent().get().isEqualTo("customized-check"); + assertThat(details.getOutput()).isPresent(); + assertThat(details.getStatus()).isEqualTo(ChecksStatus.IN_PROGRESS); + assertThat(details.getConclusion()).isEqualTo(ChecksConclusion.NONE); + + ChecksOutput output = details.getOutput().get(); + assertThat(output.getTitle()).isPresent().get().isEqualTo("Publish Checks Step"); + assertThat(output.getSummary()).isPresent().get().isEqualTo("customized check created in pipeline"); + assertThat(output.getText()).isPresent().get().isEqualTo("Pipeline support for checks"); } } diff --git a/src/test/java/io/jenkins/plugins/checks/steps/WithChecksStepITest.java b/src/test/java/io/jenkins/plugins/checks/steps/WithChecksStepITest.java index a6dc5bab..2cc2517b 100644 --- a/src/test/java/io/jenkins/plugins/checks/steps/WithChecksStepITest.java +++ b/src/test/java/io/jenkins/plugins/checks/steps/WithChecksStepITest.java @@ -1,9 +1,14 @@ package io.jenkins.plugins.checks.steps; import hudson.model.Run; +import io.jenkins.plugins.checks.api.ChecksConclusion; +import io.jenkins.plugins.checks.api.ChecksDetails; +import io.jenkins.plugins.checks.api.ChecksStatus; +import io.jenkins.plugins.checks.api.test.CapturingChecksPublisher; import io.jenkins.plugins.util.IntegrationTestWithJenkinsPerTest; import org.jenkinsci.plugins.workflow.job.WorkflowJob; import org.jenkinsci.plugins.workflow.steps.*; +import org.junit.After; import org.junit.Test; import org.jvnet.hudson.test.TestExtension; import org.kohsuke.stapler.DataBoundConstructor; @@ -20,6 +25,7 @@ * Tests the "withChecks" step. */ public class WithChecksStepITest extends IntegrationTestWithJenkinsPerTest { + /** * Tests that the step can inject the {@link ChecksInfo} into the closure. */ @@ -31,6 +37,66 @@ public void shouldInjectChecksInfoIntoClosure() { buildSuccessfully(job); } + /** + * Provide a {@link CapturingChecksPublisher} to check published checks on each test. + */ + @TestExtension + public static final CapturingChecksPublisher.Factory PUBLISHER_FACTORY = new CapturingChecksPublisher.Factory(); + + /** + * Clear captured checks on the {@link WithChecksStepITest#PUBLISHER_FACTORY} after each test. + */ + @After + public void clearPublisher() { + PUBLISHER_FACTORY.getPublishedChecks().clear(); + } + + /** + * Test that the publishChecks step picks up names from the withChecks context. + */ + @Test + public void publishChecksShouldTakeNameFromWithChecks() { + WorkflowJob job = createPipeline(); + job.setDefinition(asStage("withChecks('test injection') { publishChecks() }")); + + buildSuccessfully(job); + + assertThat(PUBLISHER_FACTORY.getPublishedChecks().size()).isEqualTo(2); + ChecksDetails autoChecks = PUBLISHER_FACTORY.getPublishedChecks().get(0); + ChecksDetails manualChecks = PUBLISHER_FACTORY.getPublishedChecks().get(1); + + assertThat(autoChecks.getName()).isPresent().get().isEqualTo("test injection"); + assertThat(autoChecks.getStatus()).isEqualTo(ChecksStatus.IN_PROGRESS); + assertThat(autoChecks.getConclusion()).isEqualTo(ChecksConclusion.NONE); + + assertThat(manualChecks.getName()).isPresent().get().isEqualTo("test injection"); + assertThat(manualChecks.getStatus()).isEqualTo(ChecksStatus.COMPLETED); + assertThat(manualChecks.getConclusion()).isEqualTo(ChecksConclusion.SUCCESS); + } + + /** + * Tests that withChecks step ignores names from the withChecks context if one has been explicitly set. + */ + @Test + public void publishChecksShouldTakeNameFromWithChecksUnlessOverridden() { + WorkflowJob job = createPipeline(); + job.setDefinition(asStage("withChecks('test injection') { publishChecks name: 'test override' }")); + + buildSuccessfully(job); + + assertThat(PUBLISHER_FACTORY.getPublishedChecks().size()).isEqualTo(2); + ChecksDetails autoChecks = PUBLISHER_FACTORY.getPublishedChecks().get(0); + ChecksDetails manualChecks = PUBLISHER_FACTORY.getPublishedChecks().get(1); + + assertThat(autoChecks.getName()).isPresent().get().isEqualTo("test injection"); + assertThat(autoChecks.getStatus()).isEqualTo(ChecksStatus.IN_PROGRESS); + assertThat(autoChecks.getConclusion()).isEqualTo(ChecksConclusion.NONE); + + assertThat(manualChecks.getName()).isPresent().get().isEqualTo("test override"); + assertThat(manualChecks.getStatus()).isEqualTo(ChecksStatus.COMPLETED); + assertThat(manualChecks.getConclusion()).isEqualTo(ChecksConclusion.SUCCESS); + } + /** * Assert that the injected {@link ChecksInfo} is as expected. */