/* * Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package com.amazonaws.codepipeline.jenkinsplugin; import static com.amazonaws.codepipeline.jenkinsplugin.TestUtils.assertContainsIgnoreCase; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.never; import static org.mockito.Mockito.when; import hudson.EnvVars; import hudson.model.BuildListener; import hudson.model.Result; import hudson.model.TaskListener; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.List; import java.util.UUID; import net.sf.json.JSONArray; import net.sf.json.JSONObject; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import com.amazonaws.codepipeline.jenkinsplugin.CodePipelineStateModel.CompressionType; import com.amazonaws.services.codepipeline.AWSCodePipeline; import com.amazonaws.services.codepipeline.model.ActionContext; import com.amazonaws.services.codepipeline.model.Artifact; import com.amazonaws.services.codepipeline.model.FailureType; import com.amazonaws.services.codepipeline.model.Job; import com.amazonaws.services.codepipeline.model.JobData; import com.amazonaws.services.codepipeline.model.PipelineContext; import com.amazonaws.services.codepipeline.model.PutJobFailureResultRequest; import com.amazonaws.services.codepipeline.model.PutJobSuccessResultRequest; import com.amazonaws.services.codepipeline.model.StageContext; import com.amazonaws.services.s3.model.AmazonS3Exception; public class AWSCodePipelinePublisherTest { private static final String REGION = "us-east-1"; private static final String ACCESS_KEY = "1234"; private static final String SECRET_KEY = "4321"; private static final String PROXY_HOST = ""; private static final int PROXY_PORT = 0; private static final String PLUGIN_VERSION = "aws-codepipeline:unknown"; private static final String BUILD_ID = "34"; @Mock private AWSClientFactory mockFactory; @Mock private AWSClients mockAWS; @Mock private AWSCodePipeline mockCodePipelineClient; @Mock private AbstractBuild mockBuild; @Mock private AbstractProject<?, ?> mockProject; @Mock private EnvVars vars; @Mock private Job mockJob; @Mock private JobData mockJobData; @Captor private ArgumentCaptor<PutJobSuccessResultRequest> putJobSuccessResultRequest; @Captor private ArgumentCaptor<PutJobFailureResultRequest> putJobFailureResultRequest; private String jobId; private CodePipelineStateModel model; private JSONArray outputLocations; private ByteArrayOutputStream outContent; private AWSCodePipelinePublisherMock publisher; @Before public void setUp() throws Throwable { MockitoAnnotations.initMocks(this); outContent = TestUtils.setOutputStream(); final JSONObject jsonObjectOne = new JSONObject(); jsonObjectOne.put("location", "output_1"); final JSONObject jsonObjectTwo = new JSONObject(); jsonObjectTwo.put("location", "output_2"); outputLocations = new JSONArray(); outputLocations.add(jsonObjectOne); outputLocations.add(jsonObjectTwo); publisher = new AWSCodePipelinePublisherMock(outputLocations, mockFactory); jobId = UUID.randomUUID().toString(); model = new CodePipelineStateModel(); model.setJob(mockJob); model.setAwsAccessKey(ACCESS_KEY); model.setAwsSecretKey(SECRET_KEY); model.setRegion(REGION); model.setProxyHost(PROXY_HOST); model.setProxyPort(PROXY_PORT); CodePipelineStateService.setModel(model); when(mockFactory.getAwsClient(anyString(), anyString(), anyString(), anyInt(), anyString(), anyString())).thenReturn(mockAWS); when(mockJob.getId()).thenReturn(jobId); when(mockJob.getData()).thenReturn(mockJobData); when(mockAWS.getCodePipelineClient()).thenReturn(mockCodePipelineClient); when(mockBuild.getId()).thenReturn(BUILD_ID); when(mockBuild.getResult()).thenReturn(Result.SUCCESS); when(mockBuild.getProject()).thenReturn(mockProject); when(mockBuild.getEnvironment(any(TaskListener.class))).thenReturn(vars); when(vars.get(any(String.class))).thenReturn("Project"); when(mockProject.getName()).thenReturn("Project"); } @Test public void putsJobSuccessWhenBuildSucceeds() { // given when(mockBuild.getResult()).thenReturn(Result.SUCCESS); final List<Artifact> outputBuildArtifacts = new ArrayList<>(); outputBuildArtifacts.add(new Artifact()); outputBuildArtifacts.add(new Artifact()); when(mockJobData.getOutputArtifacts()).thenReturn(outputBuildArtifacts); // when assertTrue(publisher.perform(mockBuild, null, null)); // then final InOrder inOrder = inOrder(mockFactory, mockAWS, mockCodePipelineClient); inOrder.verify(mockFactory).getAwsClient(ACCESS_KEY, SECRET_KEY, PROXY_HOST, PROXY_PORT, REGION, PLUGIN_VERSION); inOrder.verify(mockAWS).getCodePipelineClient(); inOrder.verify(mockCodePipelineClient).putJobSuccessResult(putJobSuccessResultRequest.capture()); final PutJobSuccessResultRequest request = putJobSuccessResultRequest.getValue(); assertEquals(jobId, request.getJobId()); assertEquals(BUILD_ID, request.getExecutionDetails().getExternalExecutionId()); assertEquals("Finished", request.getExecutionDetails().getSummary()); final String expected1 = "[AWS CodePipeline Plugin] Publishing artifacts\n"; final String expected2 = "[AWS CodePipeline Plugin] Build succeeded, calling PutJobSuccessResult\n"; assertContainsIgnoreCase(expected1, outContent.toString()); assertContainsIgnoreCase(expected2, outContent.toString()); } @Test public void putsJobFailedWhenBuildFails() { // given when(mockBuild.getResult()).thenReturn(Result.FAILURE); model.setActionTypeCategory("Build"); final List<Artifact> outputBuildArtifacts = new ArrayList<>(); outputBuildArtifacts.add(new Artifact()); outputBuildArtifacts.add(new Artifact()); when(mockJobData.getOutputArtifacts()).thenReturn(outputBuildArtifacts); // when assertFalse(publisher.perform(mockBuild, null, null)); // then final InOrder inOrder = inOrder(mockFactory, mockAWS, mockCodePipelineClient); inOrder.verify(mockFactory).getAwsClient(ACCESS_KEY, SECRET_KEY, PROXY_HOST, PROXY_PORT, REGION, PLUGIN_VERSION); inOrder.verify(mockAWS).getCodePipelineClient(); inOrder.verify(mockCodePipelineClient).putJobFailureResult(putJobFailureResultRequest.capture()); final PutJobFailureResultRequest request = putJobFailureResultRequest.getValue(); assertEquals(jobId, request.getJobId()); assertEquals(BUILD_ID, request.getFailureDetails().getExternalExecutionId()); assertEquals("Build failed", request.getFailureDetails().getMessage()); assertEquals(FailureType.JobFailed.toString(), request.getFailureDetails().getType()); final String expected1 = "[AWS CodePipeline Plugin] Publishing artifacts\n"; final String expected2 = "[AWS CodePipeline Plugin] Build failed, calling PutJobFailureResult\n"; assertContainsIgnoreCase(expected1, outContent.toString()); assertContainsIgnoreCase(expected2, outContent.toString()); } @Test public void skipsPutJobResultWhenSkipPutJobFailureFlagIsSetInModel() { // given when(mockBuild.getResult()).thenReturn(Result.FAILURE); model.setActionTypeCategory("Build"); model.setSkipPutJobResult(true); final List<Artifact> outputBuildArtifacts = new ArrayList<>(); outputBuildArtifacts.add(new Artifact()); outputBuildArtifacts.add(new Artifact()); when(mockJobData.getOutputArtifacts()).thenReturn(outputBuildArtifacts); // when assertFalse(publisher.perform(mockBuild, null, null)); // then final InOrder inOrder = inOrder(mockFactory, mockAWS, mockCodePipelineClient); inOrder.verify(mockFactory, never()).getAwsClient(ACCESS_KEY, SECRET_KEY, PROXY_HOST, PROXY_PORT, REGION, PLUGIN_VERSION); inOrder.verify(mockAWS, never()).getCodePipelineClient(); final String expected = String.format( "[AWS CodePipeline Plugin] Skipping PutJobFailureResult call for the job with ID %s", model.getJob().getId()); assertContainsIgnoreCase(expected, outContent.toString()); } @Test public void putsJobFailedWhenTheNumberOfOutputArtifactsDoNotMatch() { // given when(mockBuild.getResult()).thenReturn(Result.SUCCESS); model.setActionTypeCategory("Test"); final List<Artifact> outputArtifacts = new ArrayList<>(); outputArtifacts.add(new Artifact()); final PipelineContext pipelineContext = new PipelineContext() .withPipelineName("JenkinsPipeline") .withStage(new StageContext().withName("Build")) .withAction(new ActionContext().withName("JenkinsAction")); when(mockJobData.getOutputArtifacts()).thenReturn(outputArtifacts); when(mockJobData.getPipelineContext()).thenReturn(pipelineContext); // when assertFalse(publisher.perform(mockBuild, null, null)); // then final InOrder inOrder = inOrder(mockFactory, mockAWS, mockCodePipelineClient); inOrder.verify(mockFactory).getAwsClient(ACCESS_KEY, SECRET_KEY, PROXY_HOST, PROXY_PORT, REGION, PLUGIN_VERSION); inOrder.verify(mockAWS).getCodePipelineClient(); inOrder.verify(mockCodePipelineClient).putJobFailureResult(putJobFailureResultRequest.capture()); final PutJobFailureResultRequest request = putJobFailureResultRequest.getValue(); assertEquals(jobId, request.getJobId()); assertEquals(BUILD_ID, request.getFailureDetails().getExternalExecutionId()); assertEquals(FailureType.JobFailed.toString(), request.getFailureDetails().getType()); assertTrue(request.getFailureDetails().getMessage().startsWith("Failed to upload output artifact(s): " + "The number of output artifacts in the Jenkins")); final String expected1 = "[AWS CodePipeline Plugin] Publishing artifacts\n"; final String expected2 = "[AWS CodePipeline Plugin] Build failed, calling PutJobFailureResult\n"; final String expected3 = "[AWS CodePipeline Plugin] The number of output artifacts in the Jenkins " + "project and in the AWS CodePipeline pipeline action do not match. Please configure the output " + "locations of your Jenkins project to match the AWS CodePipeline pipeline action's output " + "artifacts. Number of output locations in Jenkins project: 2, number of output artifacts in AWS " + "CodePipeline pipeline action: 1 [Pipeline: JenkinsPipeline, stage: Build, action: JenkinsAction]."; assertContainsIgnoreCase(expected1, outContent.toString()); assertContainsIgnoreCase(expected2, outContent.toString()); assertContainsIgnoreCase(expected3, outContent.toString()); } @Test public void putsJobFailedWhenArtifactUploadFails() { // given when(mockBuild.getResult()).thenReturn(Result.SUCCESS); final List<Artifact> outputBuildArtifacts = new ArrayList<>(); outputBuildArtifacts.add(new Artifact()); outputBuildArtifacts.add(new Artifact()); when(mockJobData.getOutputArtifacts()).thenReturn(outputBuildArtifacts); final AWSCodePipelinePublisherMockS3Exception uploadFailurePublisher = new AWSCodePipelinePublisherMockS3Exception(outputLocations, mockFactory); // when assertFalse(uploadFailurePublisher.perform(mockBuild, null, null)); // then final InOrder inOrder = inOrder(mockFactory, mockAWS, mockCodePipelineClient); inOrder.verify(mockFactory).getAwsClient(ACCESS_KEY, SECRET_KEY, PROXY_HOST, PROXY_PORT, REGION, PLUGIN_VERSION); inOrder.verify(mockAWS).getCodePipelineClient(); inOrder.verify(mockCodePipelineClient).putJobFailureResult(putJobFailureResultRequest.capture()); final PutJobFailureResultRequest request = putJobFailureResultRequest.getValue(); assertEquals(jobId, request.getJobId()); assertEquals(BUILD_ID, request.getFailureDetails().getExternalExecutionId()); assertEquals("Failed to upload output artifact(s): S3 root cause", request.getFailureDetails().getMessage()); assertEquals(FailureType.JobFailed.toString(), request.getFailureDetails().getType()); final String expected1 = "[AWS CodePipeline Plugin] Publishing artifacts\n"; final String expected2 = "[AWS CodePipeline Plugin] Build failed, calling PutJobFailureResult\n"; assertContainsIgnoreCase(expected1, outContent.toString()); assertContainsIgnoreCase(expected2, outContent.toString()); } @Test public void cleanUpSuccess() { // given model.setCompressionType(CompressionType.Zip); // when publisher.cleanUp(model); // then assertNull(model.getJob()); assertEquals(CompressionType.None, model.getCompressionType()); assertNull(CodePipelineStateService.getModel()); } // -----Setup and Util Methods----- // public class AWSCodePipelinePublisherMock extends AWSCodePipelinePublisher { public AWSCodePipelinePublisherMock(final JSONArray outputLocations, final AWSClientFactory mockFactory) { super(outputLocations, mockFactory); } @Override public void callPublish(final AbstractBuild<?,?> action, final CodePipelineStateModel model, final BuildListener listener) { // Do nothing... } } public class AWSCodePipelinePublisherMockS3Exception extends AWSCodePipelinePublisher { public AWSCodePipelinePublisherMockS3Exception(final JSONArray outputLocations, final AWSClientFactory mockFactory) { super(outputLocations, mockFactory); } @Override public void callPublish(final AbstractBuild<?,?> action, final CodePipelineStateModel model, final BuildListener listener) { throw new AmazonS3Exception("S3 root cause"); } } }