diff --git a/packages/@aws-cdk/pipelines/test/compliance/assets.test.ts b/packages/@aws-cdk/pipelines/test/compliance/assets.test.ts index c1b72cf7ab316..86829bd2eff51 100644 --- a/packages/@aws-cdk/pipelines/test/compliance/assets.test.ts +++ b/packages/@aws-cdk/pipelines/test/compliance/assets.test.ts @@ -1,11 +1,11 @@ import * as fs from 'fs'; import * as path from 'path'; -import { arrayWith, Capture, deepObjectLike, encodedJson, notMatching, objectLike, ResourcePart, stringLike, SynthUtils } from '@aws-cdk/assert-internal'; +import { Capture, Match, Template } from '@aws-cdk/assertions'; import '@aws-cdk/assert-internal/jest'; import * as cb from '@aws-cdk/aws-codebuild'; import * as ec2 from '@aws-cdk/aws-ec2'; -import { Stack } from '@aws-cdk/core'; -import { behavior, PIPELINE_ENV, TestApp, LegacyTestGitHubNpmPipeline, ModernTestGitHubNpmPipeline, FileAssetApp, MegaAssetsApp, TwoFileAssetsApp, DockerAssetApp, PlainStackApp } from '../testhelpers'; +import { Stack, Stage } from '@aws-cdk/core'; +import { behavior, PIPELINE_ENV, TestApp, LegacyTestGitHubNpmPipeline, ModernTestGitHubNpmPipeline, FileAssetApp, MegaAssetsApp, TwoFileAssetsApp, DockerAssetApp, PlainStackApp, stringLike } from '../testhelpers'; const FILE_ASSET_SOURCE_HASH = '8289faf53c7da377bb2b90615999171adef5e1d8f6b88810e5fef75e6ca09ba5'; const FILE_ASSET_SOURCE_HASH2 = 'ac76997971c3f6ddf37120660003f1ced72b4fc58c498dfd99c78fa77e721e0e'; @@ -42,10 +42,10 @@ describe('basic pipeline', () => { function THEN_codePipelineExpectation() { // THEN - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: notMatching(arrayWith(objectLike({ + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: Match.not(Match.arrayWith([Match.objectLike({ Name: 'Assets', - }))), + })])), }); } }); @@ -67,13 +67,13 @@ describe('basic pipeline', () => { }); function THEN_codePipelineExpectation() { - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { Stages: [ - objectLike({ Name: 'Source' }), - objectLike({ Name: 'Build' }), - objectLike({ Name: 'UpdatePipeline' }), - objectLike({ Name: 'Assets' }), - objectLike({ Name: 'App' }), + Match.objectLike({ Name: 'Source' }), + Match.objectLike({ Name: 'Build' }), + Match.objectLike({ Name: 'UpdatePipeline' }), + Match.objectLike({ Name: 'Assets' }), + Match.objectLike({ Name: 'App' }), ], }); } @@ -96,13 +96,13 @@ describe('basic pipeline', () => { }); function THEN_codePipelineExpectation() { - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { Stages: [ - objectLike({ Name: 'Source' }), - objectLike({ Name: 'Build' }), - objectLike({ Name: 'UpdatePipeline' }), - objectLike({ Name: 'Assets' }), - objectLike({ Name: 'App' }), + Match.objectLike({ Name: 'Source' }), + Match.objectLike({ Name: 'Build' }), + Match.objectLike({ Name: 'UpdatePipeline' }), + Match.objectLike({ Name: 'Assets' }), + Match.objectLike({ Name: 'App' }), ], }); } @@ -126,14 +126,14 @@ describe('basic pipeline', () => { }); function THEN_codePipelineExpectation() { - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { Stages: [ - objectLike({ Name: 'Source' }), - objectLike({ Name: 'Build' }), - objectLike({ Name: 'UpdatePipeline' }), - objectLike({ Name: stringLike('Assets*') }), - objectLike({ Name: stringLike('Assets*2') }), - objectLike({ Name: 'App' }), + Match.objectLike({ Name: 'Source' }), + Match.objectLike({ Name: 'Build' }), + Match.objectLike({ Name: 'UpdatePipeline' }), + Match.objectLike({ Name: stringLike('Assets*') }), + Match.objectLike({ Name: stringLike('Assets*2') }), + Match.objectLike({ Name: 'App' }), ], }); } @@ -155,15 +155,15 @@ describe('basic pipeline', () => { }); function THEN_codePipelineExpectation() { - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { Stages: [ - objectLike({ Name: 'Source' }), - objectLike({ Name: 'Build' }), - objectLike({ Name: 'UpdatePipeline' }), - objectLike({ Name: stringLike('Assets*') }), // 'Assets' vs 'Assets.1' - objectLike({ Name: stringLike('Assets*2') }), - objectLike({ Name: stringLike('Assets*3') }), - objectLike({ Name: 'App' }), + Match.objectLike({ Name: 'Source' }), + Match.objectLike({ Name: 'Build' }), + Match.objectLike({ Name: 'UpdatePipeline' }), + Match.objectLike({ Name: stringLike('Assets*') }), // 'Assets' vs 'Assets.1' + Match.objectLike({ Name: stringLike('Assets*2') }), + Match.objectLike({ Name: stringLike('Assets*3') }), + Match.objectLike({ Name: 'App' }), ], }); } @@ -186,15 +186,15 @@ describe('basic pipeline', () => { }); function THEN_codePipelineExpectation() { - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { Environment: { Image: 'aws/codebuild/standard:5.0', }, Source: { - BuildSpec: encodedJson(deepObjectLike({ + BuildSpec: Match.serializedJson(Match.objectLike({ phases: { build: { - commands: arrayWith(`cdk-assets --path "assembly-FileAssetApp/FileAssetAppStackEADD68C5.assets.json" --verbose publish "${FILE_ASSET_SOURCE_HASH}:current_account-current_region"`), + commands: Match.arrayWith([`cdk-assets --path "assembly-FileAssetApp/FileAssetAppStackEADD68C5.assets.json" --verbose publish "${FILE_ASSET_SOURCE_HASH}:current_account-current_region"`]), }, }, })), @@ -220,14 +220,14 @@ describe('basic pipeline', () => { }); function THEN_codePipelineExpectation() { - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: arrayWith({ + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: Match.arrayWith([{ Name: 'Assets', Actions: [ - objectLike({ RunOrder: 1 }), - objectLike({ RunOrder: 1 }), + Match.objectLike({ RunOrder: 1 }), + Match.objectLike({ RunOrder: 1 }), ], - }), + }]), }); } }); @@ -242,16 +242,16 @@ describe('basic pipeline', () => { pipeline.addStage('SomeStage').addStackArtifactDeployment(asm.getStackByName('FileAssetApp-Stack')); // THEN - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: arrayWith({ + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: Match.arrayWith([{ Name: 'Assets', Actions: [ - objectLike({ + Match.objectLike({ Name: 'FileAsset1', RunOrder: 1, }), ], - }), + }]), }); }); @@ -277,17 +277,17 @@ describe('basic pipeline', () => { }); function THEN_codePipelineExpectation() { - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { Source: { - BuildSpec: encodedJson(deepObjectLike({ + BuildSpec: Match.serializedJson(Match.objectLike({ phases: { build: { - commands: arrayWith(stringLike('cdk-assets *')), + commands: Match.arrayWith([stringLike('cdk-assets *')]), }, }, })), }, - Environment: objectLike({ + Environment: Match.objectLike({ PrivilegedMode: false, Image: 'aws/codebuild/standard:5.0', }), @@ -311,17 +311,17 @@ describe('basic pipeline', () => { }); function THEN_codePipelineExpectation() { - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { Source: { - BuildSpec: encodedJson(deepObjectLike({ + BuildSpec: Match.serializedJson(Match.objectLike({ phases: { build: { - commands: arrayWith(stringLike('cdk-assets *')), + commands: Match.arrayWith([stringLike('cdk-assets *')]), }, }, })), }, - Environment: objectLike({ + Environment: Match.objectLike({ Image: 'aws/codebuild/standard:5.0', PrivilegedMode: true, }), @@ -349,12 +349,12 @@ describe('basic pipeline', () => { }); function THEN_codePipelineExpectation() { - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { Environment: { Image: 'aws/codebuild/standard:5.0', }, Source: { - BuildSpec: encodedJson(deepObjectLike({ + BuildSpec: Match.serializedJson(Match.objectLike({ phases: { install: { commands: ['npm install -g cdk-assets@1.2.3'], @@ -386,7 +386,7 @@ describe('basic pipeline', () => { }); function THEN_codePipelineExpectation() { - expect(pipelineStack).toHaveResourceLike('AWS::IAM::Role', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::IAM::Role', { AssumeRolePolicyDocument: { Statement: [{ Action: 'sts:AssumeRole', @@ -402,7 +402,7 @@ describe('basic pipeline', () => { }], }, }); - expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', + Template.fromStack(pipelineStack).hasResourceProperties('AWS::IAM::Policy', expectedAssetRolePolicy(FILE_PUBLISHING_ROLE, 'CdkAssetsFileRole6BE17A07')); } }); @@ -439,7 +439,7 @@ describe('basic pipeline', () => { }); function THEN_codePipelineExpectation() { - expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', + Template.fromStack(pipelineStack).hasResourceProperties('AWS::IAM::Policy', expectedAssetRolePolicy([FILE_PUBLISHING_ROLE, 'arn:${AWS::Partition}:iam::0123456789012:role/cdk-hnb659fds-file-publishing-role-0123456789012-eu-west-1'], 'CdkAssetsFileRole6BE17A07')); } @@ -468,7 +468,7 @@ describe('basic pipeline', () => { }); function THEN_codePipelineExpectation() { - expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', + Template.fromStack(pipelineStack).hasResourceProperties('AWS::IAM::Policy', expectedAssetRolePolicy(FILE_PUBLISHING_ROLE, 'CdkAssetsFileRole6BE17A07')); } }); @@ -492,7 +492,7 @@ describe('basic pipeline', () => { }); function THEN_codePipelineExpectation() { - expect(pipelineStack).toHaveResourceLike('AWS::IAM::Role', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::IAM::Role', { AssumeRolePolicyDocument: { Statement: [{ Action: 'sts:AssumeRole', @@ -508,7 +508,7 @@ describe('basic pipeline', () => { }], }, }); - expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', + Template.fromStack(pipelineStack).hasResourceProperties('AWS::IAM::Policy', expectedAssetRolePolicy(IMAGE_PUBLISHING_ROLE, 'CdkAssetsDockerRole484B6DD3')); } }); @@ -534,9 +534,9 @@ describe('basic pipeline', () => { }); function THEN_codePipelineExpectation() { - expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', + Template.fromStack(pipelineStack).hasResourceProperties('AWS::IAM::Policy', expectedAssetRolePolicy(FILE_PUBLISHING_ROLE, 'CdkAssetsFileRole6BE17A07')); - expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', + Template.fromStack(pipelineStack).hasResourceProperties('AWS::IAM::Policy', expectedAssetRolePolicy(IMAGE_PUBLISHING_ROLE, 'CdkAssetsDockerRole484B6DD3')); } }); @@ -576,12 +576,12 @@ behavior('can supply pre-install scripts to asset upload', (suite) => { }); function THEN_codePipelineExpectation() { - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { Environment: { Image: 'aws/codebuild/standard:5.0', }, Source: { - BuildSpec: encodedJson(deepObjectLike({ + BuildSpec: Match.serializedJson(Match.objectLike({ phases: { install: { commands: ['npm config set registry https://registry.com', 'npm install -g cdk-assets'], @@ -620,8 +620,8 @@ describe('pipeline with VPC', () => { function THEN_codePipelineExpectation() { // THEN - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { - VpcConfig: objectLike({ + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { + VpcConfig: Match.objectLike({ SecurityGroupIds: [ { 'Fn::GetAtt': ['CdkAssetsDockerAsset1SecurityGroup078F5C66', 'GroupId'] }, ], @@ -655,16 +655,16 @@ describe('pipeline with VPC', () => { function THEN_codePipelineExpectation() { // Assets Project - expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::IAM::Policy', { Roles: [ { Ref: 'CdkAssetsDockerRole484B6DD3' }, ], PolicyDocument: { - Statement: arrayWith({ - Action: arrayWith('ec2:DescribeSecurityGroups'), + Statement: Match.arrayWith([{ + Action: Match.arrayWith(['ec2:DescribeSecurityGroups']), Effect: 'Allow', Resource: '*', - }), + }]), }, }); } @@ -690,10 +690,10 @@ describe('pipeline with VPC', () => { function THEN_codePipelineExpectation() { // Assets Project - expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ - { + Match.objectLike({ Resource: '*', Action: [ 'ec2:CreateNetworkInterface', @@ -704,19 +704,19 @@ describe('pipeline with VPC', () => { 'ec2:DescribeDhcpOptions', 'ec2:DescribeVpcs', ], - }, + }), ], }, Roles: [{ Ref: 'CdkAssetsDockerRole484B6DD3' }], }); - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(pipelineStack).hasResource('AWS::CodeBuild::Project', { Properties: { ServiceRole: { 'Fn::GetAtt': ['CdkAssetsDockerRole484B6DD3', 'Arn'] }, }, DependsOn: [ 'CdkAssetsDockerAsset1PolicyDocument8DA96A22', ], - }, ResourcePart.CompleteDefinition); + }); } }); }); @@ -743,27 +743,27 @@ describe('pipeline with single asset publisher', () => { function THEN_codePipelineExpectation() { // THEN - const buildSpecName = Capture.aString(); - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: arrayWith({ + const buildSpecName = new Capture(); + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: Match.arrayWith([{ Name: 'Assets', Actions: [ // Only one file asset action - objectLike({ RunOrder: 1, Name: 'FileAsset' }), + Match.objectLike({ RunOrder: 1, Name: 'FileAsset' }), ], - }), + }]), }); - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { Environment: { Image: 'aws/codebuild/standard:5.0', }, Source: { - BuildSpec: buildSpecName.capture(stringLike('buildspec-*.yaml')), + BuildSpec: buildSpecName, }, }); - const assembly = SynthUtils.synthesize(pipelineStack, { skipValidation: true }).assembly; + const assembly = synthesize(pipelineStack); - const actualFileName = buildSpecName.capturedValue; + const actualFileName = buildSpecName.asString(); const buildSpec = JSON.parse(fs.readFileSync(path.join(assembly.directory, actualFileName), { encoding: 'utf-8' })); expect(buildSpec.phases.build.commands).toContain(`cdk-assets --path "assembly-FileAssetApp/FileAssetAppStackEADD68C5.assets.json" --verbose publish "${FILE_ASSET_SOURCE_HASH}:current_account-current_region"`); @@ -804,20 +804,20 @@ describe('pipeline with single asset publisher', () => { function THEN_codePipelineExpectation(pipelineStack2: Stack) { // THEN - const buildSpecName1 = Capture.aString(); - const buildSpecName2 = Capture.aString(); - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + const buildSpecName1 = new Capture(); + const buildSpecName2 = new Capture(); + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { Source: { - BuildSpec: buildSpecName1.capture(stringLike('buildspec-*.yaml')), + BuildSpec: buildSpecName1, }, }); - expect(pipelineStack2).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(pipelineStack2).hasResourceProperties('AWS::CodeBuild::Project', { Source: { - BuildSpec: buildSpecName2.capture(stringLike('buildspec-*.yaml')), + BuildSpec: buildSpecName2, }, }); - expect(buildSpecName1.capturedValue).not.toEqual(buildSpecName2.capturedValue); + expect(buildSpecName1.asString()).not.toEqual(buildSpecName2.asString()); } }); }); @@ -870,27 +870,27 @@ describe('pipeline with custom asset publisher BuildSpec', () => { function THEN_codePipelineExpectation() { - const buildSpecName = Capture.aString(); + const buildSpecName = new Capture(); - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: arrayWith({ + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: Match.arrayWith([{ Name: 'Assets', Actions: [ // Only one file asset action - objectLike({ RunOrder: 1, Name: 'FileAsset' }), + Match.objectLike({ RunOrder: 1, Name: 'FileAsset' }), ], - }), + }]), }); - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { Environment: { Image: 'aws/codebuild/standard:5.0', }, Source: { - BuildSpec: buildSpecName.capture(stringLike('buildspec-*.yaml')), + BuildSpec: buildSpecName, }, }); - const assembly = SynthUtils.synthesize(pipelineStack, { skipValidation: true }).assembly; - const buildSpec = JSON.parse(fs.readFileSync(path.join(assembly.directory, buildSpecName.capturedValue)).toString()); + const assembly = synthesize(pipelineStack); + const buildSpec = JSON.parse(fs.readFileSync(path.join(assembly.directory, buildSpecName.asString())).toString()); expect(buildSpec.phases.build.commands).toContain(`cdk-assets --path "assembly-FileAssetApp/FileAssetAppStackEADD68C5.assets.json" --verbose publish "${FILE_ASSET_SOURCE_HASH}:current_account-current_region"`); expect(buildSpec.phases.build.commands).toContain(`cdk-assets --path "assembly-FileAssetApp/FileAssetAppStackEADD68C5.assets.json" --verbose publish "${FILE_ASSET_SOURCE_HASH2}:current_account-current_region"`); expect(buildSpec.phases.pre_install.commands).toContain('preinstall'); @@ -978,9 +978,9 @@ behavior('necessary secrets manager permissions get added to asset roles', suite }); function THEN_codePipelineExpectation() { - expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { - Statement: arrayWith({ + Statement: Match.arrayWith([{ Action: 'secretsmanager:GetSecretValue', Effect: 'Allow', Resource: { @@ -993,7 +993,7 @@ behavior('necessary secrets manager permissions get added to asset roles', suite ], ], }, - }), + }]), }, Roles: [ { Ref: 'PipelineAssetsFileRole59943A77' }, @@ -1021,10 +1021,10 @@ behavior('adding environment variable to assets job adds SecretsManager permissi }); pipeline.addStage(new FileAssetApp(pipelineStack, 'MyApp')); - expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { - Statement: arrayWith( - objectLike({ + Statement: Match.arrayWith([ + Match.objectLike({ Action: 'secretsmanager:GetSecretValue', Effect: 'Allow', Resource: { @@ -1035,8 +1035,17 @@ behavior('adding environment variable to assets job adds SecretsManager permissi ]], }, }), - ), + ]), }, }); }); -}); \ No newline at end of file +}); + +function synthesize(stack: Stack) { + const root = stack.node.root; + if (!Stage.isStage(root)) { + throw new Error('unexpected: all stacks must be part of a Stage'); + } + + return root.synth({ skipValidation: true }); +} \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/compliance/docker-credentials.test.ts b/packages/@aws-cdk/pipelines/test/compliance/docker-credentials.test.ts index 5ada88b49b937..ac70ef72c93fa 100644 --- a/packages/@aws-cdk/pipelines/test/compliance/docker-credentials.test.ts +++ b/packages/@aws-cdk/pipelines/test/compliance/docker-credentials.test.ts @@ -1,4 +1,4 @@ -import { arrayWith, deepObjectLike, encodedJson, stringLike } from '@aws-cdk/assert-internal'; +import { Match, Template } from '@aws-cdk/assertions'; import '@aws-cdk/assert-internal/jest'; import * as cb from '@aws-cdk/aws-codebuild'; import * as secretsmanager from '@aws-cdk/aws-secretsmanager'; @@ -6,7 +6,7 @@ import { Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import * as cdkp from '../../lib'; import { CodeBuildStep } from '../../lib'; -import { behavior, PIPELINE_ENV, TestApp, LegacyTestGitHubNpmPipeline, ModernTestGitHubNpmPipeline, DockerAssetApp } from '../testhelpers'; +import { behavior, PIPELINE_ENV, TestApp, LegacyTestGitHubNpmPipeline, ModernTestGitHubNpmPipeline, DockerAssetApp, stringLike } from '../testhelpers'; const secretSynthArn = 'arn:aws:secretsmanager:eu-west-1:0123456789012:secret:synth-012345'; const secretUpdateArn = 'arn:aws:secretsmanager:eu-west-1:0123456789012:secret:update-012345'; @@ -51,32 +51,32 @@ behavior('synth action receives install commands and access to relevant credenti domainCredentials: { 'synth.example.com': { secretsManagerSecretId: secretSynthArn } }, }); - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { Environment: { Image: 'aws/codebuild/standard:5.0' }, Source: { - BuildSpec: encodedJson(deepObjectLike({ + BuildSpec: Match.serializedJson(Match.objectLike({ phases: { pre_build: { - commands: arrayWith( + commands: Match.arrayWith([ 'mkdir $HOME/.cdk', `echo '${expectedCredsConfig}' > $HOME/.cdk/cdk-docker-creds.json`, - ), + ]), }, // Prove we're looking at the Synth project build: { - commands: arrayWith(stringLike('*cdk*synth*')), + commands: Match.arrayWith([stringLike('*cdk*synth*')]), }, }, })), }, }); - expect(pipelineStack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { - Statement: arrayWith({ + Statement: Match.arrayWith([{ Action: ['secretsmanager:GetSecretValue', 'secretsmanager:DescribeSecret'], Effect: 'Allow', Resource: secretSynthArn, - }), + }]), Version: '2012-10-17', }, Roles: [{ Ref: stringLike('Cdk*BuildProjectRole*') }], @@ -121,20 +121,20 @@ behavior('synth action receives Windows install commands if a Windows image is d domainCredentials: { 'synth.example.com': { secretsManagerSecretId: secretSynthArn } }, }); - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { Environment: { Image: 'aws/codebuild/windows-base:2.0' }, Source: { - BuildSpec: encodedJson(deepObjectLike({ + BuildSpec: Match.serializedJson(Match.objectLike({ phases: { pre_build: { - commands: arrayWith( + commands: Match.arrayWith([ 'mkdir %USERPROFILE%\\.cdk', `echo '${expectedCredsConfig}' > %USERPROFILE%\\.cdk\\cdk-docker-creds.json`, - ), + ]), }, // Prove we're looking at the Synth project build: { - commands: arrayWith(stringLike('*cdk*synth*')), + commands: Match.arrayWith([stringLike('*cdk*synth*')]), }, }, })), @@ -164,34 +164,34 @@ behavior('self-update receives install commands and access to relevant credentia domainCredentials: { 'selfupdate.example.com': { secretsManagerSecretId: secretUpdateArn } }, }); - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { Environment: { Image: 'aws/codebuild/standard:5.0' }, Source: { - BuildSpec: encodedJson(deepObjectLike({ + BuildSpec: Match.serializedJson(Match.objectLike({ phases: { [expectedPhase]: { - commands: arrayWith( + commands: Match.arrayWith([ 'mkdir $HOME/.cdk', `echo '${expectedCredsConfig}' > $HOME/.cdk/cdk-docker-creds.json`, - ), + ]), }, // Prove we're looking at the SelfMutate project build: { - commands: arrayWith( + commands: Match.arrayWith([ stringLike('cdk * deploy PipelineStack*'), - ), + ]), }, }, })), }, }); - expect(pipelineStack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { - Statement: arrayWith({ + Statement: Match.arrayWith([{ Action: ['secretsmanager:GetSecretValue', 'secretsmanager:DescribeSecret'], Effect: 'Allow', Resource: secretUpdateArn, - }), + }]), Version: '2012-10-17', }, Roles: [{ Ref: stringLike('*SelfMutat*Role*') }], @@ -220,32 +220,32 @@ behavior('asset publishing receives install commands and access to relevant cred domainCredentials: { 'publish.example.com': { secretsManagerSecretId: secretPublishArn } }, }); - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { Environment: { Image: 'aws/codebuild/standard:5.0' }, Source: { - BuildSpec: encodedJson(deepObjectLike({ + BuildSpec: Match.serializedJson(Match.objectLike({ phases: { [expectedPhase]: { - commands: arrayWith( + commands: Match.arrayWith([ 'mkdir $HOME/.cdk', `echo '${expectedCredsConfig}' > $HOME/.cdk/cdk-docker-creds.json`, - ), + ]), }, // Prove we're looking at the Publishing project build: { - commands: arrayWith(stringLike('cdk-assets*')), + commands: Match.arrayWith([stringLike('cdk-assets*')]), }, }, })), }, }); - expect(pipelineStack).toHaveResource('AWS::IAM::Policy', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { - Statement: arrayWith({ + Statement: Match.arrayWith([{ Action: ['secretsmanager:GetSecretValue', 'secretsmanager:DescribeSecret'], Effect: 'Allow', Resource: secretPublishArn, - }), + }]), Version: '2012-10-17', }, Roles: [{ Ref: 'CdkAssetsDockerRole484B6DD3' }], diff --git a/packages/@aws-cdk/pipelines/test/compliance/security-check.test.ts b/packages/@aws-cdk/pipelines/test/compliance/security-check.test.ts index 7367930e6618a..88f5e2fd0c7b8 100644 --- a/packages/@aws-cdk/pipelines/test/compliance/security-check.test.ts +++ b/packages/@aws-cdk/pipelines/test/compliance/security-check.test.ts @@ -1,9 +1,9 @@ -import { anything, arrayWith, encodedJson, objectLike, stringLike } from '@aws-cdk/assert-internal'; +import { Match, Template } from '@aws-cdk/assertions'; import '@aws-cdk/assert-internal/jest'; import { Topic } from '@aws-cdk/aws-sns'; import { Stack } from '@aws-cdk/core'; import * as cdkp from '../../lib'; -import { LegacyTestGitHubNpmPipeline, ModernTestGitHubNpmPipeline, OneStackApp, PIPELINE_ENV, TestApp } from '../testhelpers'; +import { LegacyTestGitHubNpmPipeline, ModernTestGitHubNpmPipeline, OneStackApp, PIPELINE_ENV, TestApp, stringLike } from '../testhelpers'; import { behavior } from '../testhelpers/compliance'; let app: TestApp; @@ -41,8 +41,8 @@ behavior('security check option generates lambda/codebuild at pipeline scope', ( }); function THEN_codePipelineExpectation() { - expect(pipelineStack).toCountResources('AWS::Lambda::Function', 1); - expect(pipelineStack).toHaveResourceLike('AWS::Lambda::Function', { + Template.fromStack(pipelineStack).resourceCountIs('AWS::Lambda::Function', 1); + Template.fromStack(pipelineStack).hasResourceProperties('AWS::Lambda::Function', { Role: { 'Fn::GetAtt': [ stringLike('CdkPipeline*SecurityCheckCDKPipelinesAutoApproveServiceRole*'), @@ -51,7 +51,7 @@ behavior('security check option generates lambda/codebuild at pipeline scope', ( }, }); // 1 for github build, 1 for synth stage, and 1 for the application security check - expect(pipelineStack).toCountResources('AWS::CodeBuild::Project', 3); + Template.fromStack(pipelineStack).resourceCountIs('AWS::CodeBuild::Project', 3); } }); @@ -78,24 +78,24 @@ behavior('security check option passes correct environment variables to check pr }); function THEN_codePipelineExpectation() { - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: arrayWith( + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: Match.arrayWith([ { Name: 'App', - Actions: arrayWith( - objectLike({ + Actions: Match.arrayWith([ + Match.objectLike({ Name: stringLike('*Check'), - Configuration: objectLike({ - EnvironmentVariables: encodedJson([ + Configuration: Match.objectLike({ + EnvironmentVariables: Match.serializedJson([ { name: 'STAGE_PATH', type: 'PLAINTEXT', value: 'PipelineSecurityStack/App' }, { name: 'STAGE_NAME', type: 'PLAINTEXT', value: 'App' }, - { name: 'ACTION_NAME', type: 'PLAINTEXT', value: anything() }, + { name: 'ACTION_NAME', type: 'PLAINTEXT', value: Match.anyValue() }, ]), }), }), - ), + ]), }, - ), + ]), }); } }); @@ -124,7 +124,7 @@ behavior('pipeline created with auto approve tags and lambda/codebuild w/ valid function THEN_codePipelineExpectation() { // CodePipeline must be tagged as SECURITY_CHECK=ALLOW_APPROVE - expect(pipelineStack).toHaveResource('AWS::CodePipeline::Pipeline', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { Tags: [ { Key: 'SECURITY_CHECK', @@ -133,7 +133,7 @@ behavior('pipeline created with auto approve tags and lambda/codebuild w/ valid ], }); // Lambda Function only has access to pipelines tagged SECURITY_CHECK=ALLOW_APPROVE - expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { Statement: [ { @@ -148,9 +148,9 @@ behavior('pipeline created with auto approve tags and lambda/codebuild w/ valid }, }); // CodeBuild must have access to the stacks and invoking the lambda function - expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { - Statement: arrayWith( + Statement: Match.arrayWith([ { Action: 'sts:AssumeRole', Condition: { @@ -173,7 +173,7 @@ behavior('pipeline created with auto approve tags and lambda/codebuild w/ valid ], }, }, - ), + ]), }, }); } @@ -193,32 +193,32 @@ behavior('confirmBroadeningPermissions option at addApplicationStage runs securi suite.doesNotApply.modern(); function THEN_codePipelineExpectation() { - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { Stages: [ { - Actions: [{ Name: 'GitHub', RunOrder: 1 }], + Actions: [Match.objectLike({ Name: 'GitHub', RunOrder: 1 })], Name: 'Source', }, { - Actions: [{ Name: 'Synth', RunOrder: 1 }], + Actions: [Match.objectLike({ Name: 'Synth', RunOrder: 1 })], Name: 'Build', }, { - Actions: [{ Name: 'SelfMutate', RunOrder: 1 }], + Actions: [Match.objectLike({ Name: 'SelfMutate', RunOrder: 1 })], Name: 'UpdatePipeline', }, { Actions: [ - { Name: 'StageSecurityCheckStackSecurityCheck', RunOrder: 1 }, - { Name: 'StageSecurityCheckStackManualApproval', RunOrder: 2 }, - { Name: 'AnotherStackSecurityCheck', RunOrder: 5 }, - { Name: 'AnotherStackManualApproval', RunOrder: 6 }, - { Name: 'Stack.Prepare', RunOrder: 3 }, - { Name: 'Stack.Deploy', RunOrder: 4 }, - { Name: 'AnotherStack-Stack.Prepare', RunOrder: 7 }, - { Name: 'AnotherStack-Stack.Deploy', RunOrder: 8 }, - { Name: 'SkipCheckStack-Stack.Prepare', RunOrder: 9 }, - { Name: 'SkipCheckStack-Stack.Deploy', RunOrder: 10 }, + Match.objectLike({ Name: 'StageSecurityCheckStackSecurityCheck', RunOrder: 1 }), + Match.objectLike({ Name: 'StageSecurityCheckStackManualApproval', RunOrder: 2 }), + Match.objectLike({ Name: 'AnotherStackSecurityCheck', RunOrder: 5 }), + Match.objectLike({ Name: 'AnotherStackManualApproval', RunOrder: 6 }), + Match.objectLike({ Name: 'Stack.Prepare', RunOrder: 3 }), + Match.objectLike({ Name: 'Stack.Deploy', RunOrder: 4 }), + Match.objectLike({ Name: 'AnotherStack-Stack.Prepare', RunOrder: 7 }), + Match.objectLike({ Name: 'AnotherStack-Stack.Deploy', RunOrder: 8 }), + Match.objectLike({ Name: 'SkipCheckStack-Stack.Prepare', RunOrder: 9 }), + Match.objectLike({ Name: 'SkipCheckStack-Stack.Deploy', RunOrder: 10 }), ], Name: 'StageSecurityCheckStack', }, @@ -240,28 +240,28 @@ behavior('confirmBroadeningPermissions option at addApplication runs security ch suite.doesNotApply.modern(); function THEN_codePipelineExpectation() { - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { Stages: [ { - Actions: [{ Name: 'GitHub', RunOrder: 1 }], + Actions: [Match.objectLike({ Name: 'GitHub', RunOrder: 1 })], Name: 'Source', }, { - Actions: [{ Name: 'Synth', RunOrder: 1 }], + Actions: [Match.objectLike({ Name: 'Synth', RunOrder: 1 })], Name: 'Build', }, { - Actions: [{ Name: 'SelfMutate', RunOrder: 1 }], + Actions: [Match.objectLike({ Name: 'SelfMutate', RunOrder: 1 })], Name: 'UpdatePipeline', }, { Actions: [ - { Name: 'EnableCheckStackSecurityCheck', RunOrder: 3 }, - { Name: 'EnableCheckStackManualApproval', RunOrder: 4 }, - { Name: 'Stack.Prepare', RunOrder: 1 }, - { Name: 'Stack.Deploy', RunOrder: 2 }, - { Name: 'EnableCheckStack-Stack.Prepare', RunOrder: 5 }, - { Name: 'EnableCheckStack-Stack.Deploy', RunOrder: 6 }, + Match.objectLike({ Name: 'EnableCheckStackSecurityCheck', RunOrder: 3 }), + Match.objectLike({ Name: 'EnableCheckStackManualApproval', RunOrder: 4 }), + Match.objectLike({ Name: 'Stack.Prepare', RunOrder: 1 }), + Match.objectLike({ Name: 'Stack.Deploy', RunOrder: 2 }), + Match.objectLike({ Name: 'EnableCheckStack-Stack.Prepare', RunOrder: 5 }), + Match.objectLike({ Name: 'EnableCheckStack-Stack.Deploy', RunOrder: 6 }), ], Name: 'NoSecurityCheckStack', }, @@ -299,13 +299,13 @@ behavior('confirmBroadeningPermissions and notification topic options generates }); function THEN_codePipelineExpectation() { - expect(pipelineStack).toCountResources('AWS::SNS::Topic', 1); - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: arrayWith( + Template.fromStack(pipelineStack).resourceCountIs('AWS::SNS::Topic', 1); + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: Match.arrayWith([ { Name: 'MyStack', Actions: [ - objectLike({ + Match.objectLike({ Configuration: { ProjectName: { Ref: stringLike('*SecurityCheck*') }, EnvironmentVariables: { @@ -320,7 +320,7 @@ behavior('confirmBroadeningPermissions and notification topic options generates Namespace: stringLike('*'), RunOrder: 1, }), - objectLike({ + Match.objectLike({ Configuration: { CustomData: stringLike('#{*.MESSAGE}'), ExternalEntityLink: stringLike('#{*.LINK}'), @@ -328,11 +328,11 @@ behavior('confirmBroadeningPermissions and notification topic options generates Name: stringLike('*Approv*'), RunOrder: 2, }), - objectLike({ Name: 'Stack.Prepare', RunOrder: 3 }), - objectLike({ Name: 'Stack.Deploy', RunOrder: 4 }), + Match.objectLike({ Name: 'Stack.Prepare', RunOrder: 3 }), + Match.objectLike({ Name: 'Stack.Deploy', RunOrder: 4 }), ], }, - ), + ]), }); } }); @@ -365,10 +365,10 @@ behavior('Stages declared outside the pipeline create their own ApplicationSecur suite.doesNotApply.modern(); function THEN_codePipelineExpectation() { - expect(pipelineStack).toCountResources('AWS::Lambda::Function', 1); + Template.fromStack(pipelineStack).resourceCountIs('AWS::Lambda::Function', 1); // 1 for github build, 1 for synth stage, and 1 for the application security check - expect(pipelineStack).toCountResources('AWS::CodeBuild::Project', 3); - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { + Template.fromStack(pipelineStack).resourceCountIs('AWS::CodeBuild::Project', 3); + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { Tags: [ { Key: 'SECURITY_CHECK', @@ -376,28 +376,28 @@ behavior('Stages declared outside the pipeline create their own ApplicationSecur }, ], Stages: [ - { Name: 'Source' }, - { Name: 'Build' }, - { Name: 'UpdatePipeline' }, + Match.objectLike({ Name: 'Source' }), + Match.objectLike({ Name: 'Build' }), + Match.objectLike({ Name: 'UpdatePipeline' }), { Actions: [ - { + Match.objectLike({ Configuration: { ProjectName: { Ref: 'UnattachedStageStageApplicationSecurityCheckCDKSecurityCheckADCE795B' }, }, Name: 'UnattachedStageSecurityCheck', RunOrder: 1, - }, - { + }), + Match.objectLike({ Configuration: { CustomData: '#{UnattachedStageSecurityCheck.MESSAGE}', ExternalEntityLink: '#{UnattachedStageSecurityCheck.LINK}', }, Name: 'UnattachedStageManualApproval', RunOrder: 2, - }, - { Name: 'Stack.Prepare', RunOrder: 3 }, - { Name: 'Stack.Deploy', RunOrder: 4 }, + }), + Match.objectLike({ Name: 'Stack.Prepare', RunOrder: 3 }), + Match.objectLike({ Name: 'Stack.Deploy', RunOrder: 4 }), ], Name: 'UnattachedStage', }, diff --git a/packages/@aws-cdk/pipelines/test/compliance/self-mutation.test.ts b/packages/@aws-cdk/pipelines/test/compliance/self-mutation.test.ts index 8196c84a0920b..74cf026755d68 100644 --- a/packages/@aws-cdk/pipelines/test/compliance/self-mutation.test.ts +++ b/packages/@aws-cdk/pipelines/test/compliance/self-mutation.test.ts @@ -1,10 +1,10 @@ /* eslint-disable import/no-extraneous-dependencies */ -import { anything, arrayWith, deepObjectLike, encodedJson, notMatching, objectLike } from '@aws-cdk/assert-internal'; +import { Match, Template } from '@aws-cdk/assertions'; import '@aws-cdk/assert-internal/jest'; import * as cb from '@aws-cdk/aws-codebuild'; import * as cp from '@aws-cdk/aws-codepipeline'; import { Stack, Stage } from '@aws-cdk/core'; -import { behavior, LegacyTestGitHubNpmPipeline, PIPELINE_ENV, stackTemplate, TestApp, ModernTestGitHubNpmPipeline } from '../testhelpers'; +import { behavior, LegacyTestGitHubNpmPipeline, PIPELINE_ENV, TestApp, ModernTestGitHubNpmPipeline } from '../testhelpers'; let app: TestApp; let pipelineStack: Stack; @@ -31,32 +31,32 @@ behavior('CodePipeline has self-mutation stage', (suite) => { function THEN_codePipelineExpectation() { // THEN - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: arrayWith({ + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: Match.arrayWith([{ Name: 'UpdatePipeline', Actions: [ - objectLike({ + Match.objectLike({ Name: 'SelfMutate', - Configuration: objectLike({ - ProjectName: { Ref: anything() }, + Configuration: Match.objectLike({ + ProjectName: { Ref: Match.anyValue() }, }), }), ], - }), + }]), }); - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { Environment: { Image: 'aws/codebuild/standard:5.0', }, Source: { - BuildSpec: encodedJson(deepObjectLike({ + BuildSpec: Match.serializedJson(Match.objectLike({ phases: { install: { commands: ['npm install -g aws-cdk'], }, build: { - commands: arrayWith('cdk -a . deploy PipelineStack --require-approval=never --verbose'), + commands: Match.arrayWith(['cdk -a . deploy PipelineStack --require-approval=never --verbose']), }, }, })), @@ -84,15 +84,15 @@ behavior('selfmutation stage correctly identifies nested assembly of pipeline st }); function THEN_codePipelineExpectation(nestedPipelineStack: Stack) { - expect(stackTemplate(nestedPipelineStack)).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(nestedPipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { Environment: { Image: 'aws/codebuild/standard:5.0', }, Source: { - BuildSpec: encodedJson(deepObjectLike({ + BuildSpec: Match.serializedJson(Match.objectLike({ phases: { build: { - commands: arrayWith('cdk -a assembly-PipelineStage deploy PipelineStage/PipelineStack --require-approval=never --verbose'), + commands: Match.arrayWith(['cdk -a assembly-PipelineStage deploy PipelineStage/PipelineStack --require-approval=never --verbose']), }, }, })), @@ -123,11 +123,11 @@ behavior('selfmutation feature can be turned off', (suite) => { }); function THEN_codePipelineExpectation() { - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: notMatching(arrayWith({ + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: Match.not(Match.arrayWith([{ Name: 'UpdatePipeline', - Actions: anything(), - })), + Actions: Match.anyValue(), + }])), }); } }); @@ -154,10 +154,10 @@ behavior('can control fix/CLI version used in pipeline selfupdate', (suite) => { function THEN_codePipelineExpectation() { // THEN - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { Name: 'vpipe-selfupdate', Source: { - BuildSpec: encodedJson(deepObjectLike({ + BuildSpec: Match.serializedJson(Match.objectLike({ phases: { install: { commands: ['npm install -g aws-cdk@1.2.3'], @@ -177,7 +177,7 @@ behavior('Pipeline stack itself can use assets (has implications for selfupdate) }); // THEN - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { Environment: { PrivilegedMode: true, }, @@ -191,7 +191,7 @@ behavior('Pipeline stack itself can use assets (has implications for selfupdate) }); // THEN - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { Environment: { PrivilegedMode: true, }, @@ -212,9 +212,9 @@ behavior('self-update project role uses tagged bootstrap-role permissions', (sui }); function THEN_codePipelineExpectations() { - expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { - Statement: arrayWith( + Statement: Match.arrayWith([ { Action: 'sts:AssumeRole', Effect: 'Allow', @@ -235,7 +235,7 @@ behavior('self-update project role uses tagged bootstrap-role permissions', (sui Effect: 'Allow', Resource: '*', }, - ), + ]), }, }); } @@ -280,19 +280,19 @@ behavior('self-mutation stage can be customized with BuildSpec', (suite) => { }); function THEN_codePipelineExpectation() { - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { Environment: { Image: 'aws/codebuild/standard:5.0', PrivilegedMode: false, }, Source: { - BuildSpec: encodedJson(deepObjectLike({ + BuildSpec: Match.serializedJson(Match.objectLike({ phases: { install: { commands: ['npm config set registry example.com', 'npm install -g aws-cdk'], }, build: { - commands: arrayWith('cdk -a . deploy PipelineStack --require-approval=never --verbose'), + commands: Match.arrayWith(['cdk -a . deploy PipelineStack --require-approval=never --verbose']), }, }, cache: { diff --git a/packages/@aws-cdk/pipelines/test/compliance/synths.test.ts b/packages/@aws-cdk/pipelines/test/compliance/synths.test.ts index 58bae441ee156..0726df792bfa5 100644 --- a/packages/@aws-cdk/pipelines/test/compliance/synths.test.ts +++ b/packages/@aws-cdk/pipelines/test/compliance/synths.test.ts @@ -1,4 +1,4 @@ -import { arrayWith, deepObjectLike, encodedJson, objectLike, Capture, anything } from '@aws-cdk/assert-internal'; +import { Capture, Match, Template } from '@aws-cdk/assertions'; import '@aws-cdk/assert-internal/jest'; import * as cbuild from '@aws-cdk/aws-codebuild'; import * as codepipeline from '@aws-cdk/aws-codepipeline'; @@ -64,12 +64,12 @@ behavior('synth takes arrays of commands', (suite) => { function THEN_codePipelineExpectation(installPhase: string) { // THEN - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { Environment: { Image: 'aws/codebuild/standard:5.0', }, Source: { - BuildSpec: encodedJson(deepObjectLike({ + BuildSpec: Match.serializedJson(Match.objectLike({ phases: { [installPhase]: { commands: [ @@ -112,12 +112,12 @@ behavior('synth sets artifact base-directory to cdk.out', (suite) => { function THEN_codePipelineExpectation() { // THEN - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { Environment: { Image: 'aws/codebuild/standard:5.0', }, Source: { - BuildSpec: encodedJson(deepObjectLike({ + BuildSpec: Match.serializedJson(Match.objectLike({ artifacts: { 'base-directory': 'cdk.out', }, @@ -154,15 +154,15 @@ behavior('synth supports setting subdirectory', (suite) => { function THEN_codePipelineExpectation(installPhase: string) { // THEN - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { Environment: { Image: 'aws/codebuild/standard:5.0', }, Source: { - BuildSpec: encodedJson(deepObjectLike({ + BuildSpec: Match.serializedJson(Match.objectLike({ phases: { [installPhase]: { - commands: arrayWith('cd subdir'), + commands: Match.arrayWith(['cd subdir']), }, }, artifacts: { @@ -201,7 +201,7 @@ behavior('npm synth sets, or allows setting, UNSAFE_PERM=true', (suite) => { function THEN_codePipelineExpectation() { // THEN - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { Environment: { EnvironmentVariables: [ { @@ -225,12 +225,12 @@ behavior('synth assumes a JavaScript project by default (no build, yes synth)', }); // THEN - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { Environment: { Image: 'aws/codebuild/standard:5.0', }, Source: { - BuildSpec: encodedJson(deepObjectLike({ + BuildSpec: Match.serializedJson(Match.objectLike({ phases: { pre_build: { commands: ['npm ci'], @@ -278,24 +278,24 @@ behavior('Magic CodePipeline variables passed to synth envvars must be rendered function THEN_codePipelineExpectation() { // THEN - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: arrayWith({ + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: Match.arrayWith([{ Name: 'Build', Actions: [ - objectLike({ + Match.objectLike({ Name: 'Synth', - Configuration: objectLike({ - EnvironmentVariables: encodedJson(arrayWith( + Configuration: Match.objectLike({ + EnvironmentVariables: Match.serializedJson(Match.arrayWith([ { name: 'VERSION', type: 'PLAINTEXT', value: '#{codepipeline.PipelineExecutionId}', }, - )), + ])), }), }), ], - }), + }]), }); } }); @@ -354,24 +354,24 @@ behavior('CodeBuild: environment variables specified in multiple places are corr function THEN_codePipelineExpectation(installPhase: string) { // THEN - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { - Environment: objectLike({ + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { + Environment: Match.objectLike({ PrivilegedMode: true, - EnvironmentVariables: arrayWith( + EnvironmentVariables: Match.arrayWith([ { - Name: 'SOME_ENV_VAR', + Name: 'INNER_VAR', Type: 'PLAINTEXT', - Value: 'SomeValue', + Value: 'InnerValue', }, { - Name: 'INNER_VAR', + Name: 'SOME_ENV_VAR', Type: 'PLAINTEXT', - Value: 'InnerValue', + Value: 'SomeValue', }, - ), + ]), }), Source: { - BuildSpec: encodedJson(deepObjectLike({ + BuildSpec: Match.serializedJson(Match.objectLike({ phases: { [installPhase]: { commands: ['install1', 'install2'], @@ -413,12 +413,12 @@ behavior('install command can be overridden/specified', (suite) => { function THEN_codePipelineExpectation(installPhase: string) { // THEN - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { Environment: { Image: 'aws/codebuild/standard:5.0', }, Source: { - BuildSpec: encodedJson(deepObjectLike({ + BuildSpec: Match.serializedJson(Match.objectLike({ phases: { [installPhase]: { commands: ['/bin/true'], @@ -445,12 +445,12 @@ behavior('synth can have its test commands set', (suite) => { }); // THEN - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { Environment: { Image: 'aws/codebuild/standard:5.0', }, Source: { - BuildSpec: encodedJson(objectLike({ + BuildSpec: Match.serializedJson(Match.objectLike({ phases: { pre_build: { commands: ['/bin/true'], @@ -506,12 +506,12 @@ behavior('Synth can output additional artifacts', (suite) => { function THEN_codePipelineExpectation(asmArtifact: string, testArtifact: string) { // THEN - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { Environment: { Image: 'aws/codebuild/standard:5.0', }, Source: { - BuildSpec: encodedJson(deepObjectLike({ + BuildSpec: Match.serializedJson(Match.objectLike({ artifacts: { 'secondary-artifacts': { [asmArtifact]: { @@ -585,7 +585,7 @@ behavior('Synth can be made to run in a VPC', (suite) => { function THEN_codePipelineExpectation() { // THEN - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { VpcConfig: { SecurityGroupIds: [ { 'Fn::GetAtt': ['CdkPipelineBuildSynthCdkBuildProjectSecurityGroupEA44D7C2', 'GroupId'] }, @@ -599,16 +599,16 @@ behavior('Synth can be made to run in a VPC', (suite) => { }, }); - expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::IAM::Policy', { Roles: [ { Ref: 'CdkPipelineBuildSynthCdkBuildProjectRole5E173C62' }, ], PolicyDocument: { - Statement: arrayWith({ - Action: arrayWith('ec2:DescribeSecurityGroups'), + Statement: Match.arrayWith([{ + Action: Match.arrayWith(['ec2:DescribeSecurityGroups']), Effect: 'Allow', Resource: '*', - }), + }]), }, }); } @@ -721,28 +721,28 @@ behavior('Pipeline action contains a hash that changes as the buildspec changes' } function captureProjectConfigHash(_pipelineStack: Stack) { - const theHash = Capture.aString(); - expect(_pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: arrayWith({ + const theHash = new Capture(); + Template.fromStack(_pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: Match.arrayWith([{ Name: 'Build', Actions: [ - objectLike({ + Match.objectLike({ Name: 'Synth', - Configuration: objectLike({ - EnvironmentVariables: encodedJson([ + Configuration: Match.objectLike({ + EnvironmentVariables: Match.serializedJson([ { name: '_PROJECT_CONFIG_HASH', type: 'PLAINTEXT', - value: theHash.capture(), + value: theHash, }, ]), }), }), ], - }), + }]), }); - return theHash.capturedValue; + return theHash.asString(); } }); @@ -784,12 +784,12 @@ behavior('Synth CodeBuild project role can be granted permissions', (suite) => { function THEN_codePipelineExpectation() { // THEN - expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { - Statement: arrayWith(deepObjectLike({ + Statement: Match.arrayWith([Match.objectLike({ Action: ['s3:GetObject*', 's3:GetBucket*', 's3:List*'], Resource: ['arn:aws:s3:::ThisParticularBucket', 'arn:aws:s3:::ThisParticularBucket/*'], - })), + })]), }, }); } @@ -878,15 +878,15 @@ behavior('CodeBuild: Can specify additional policy statements', (suite) => { }); function THEN_codePipelineExpectation() { - expect(pipelineStack).toHaveResourceLike('AWS::IAM::Policy', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::IAM::Policy', { PolicyDocument: { - Statement: arrayWith(deepObjectLike({ + Statement: Match.arrayWith([Match.objectLike({ Action: [ 'codeartifact:*', 'sts:GetServiceBearerToken', ], Resource: 'arn:my:arn', - })), + })]), }, }); } @@ -913,38 +913,38 @@ behavior('Multiple input sources in side-by-side directories', (suite) => { }), }); - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: arrayWith( + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: Match.arrayWith([ { Name: 'Source', Actions: [ - objectLike({ Configuration: objectLike({ Repo: 'bar' }) }), - objectLike({ Configuration: objectLike({ Repo: 'build' }) }), - objectLike({ Configuration: objectLike({ Repo: 'test' }) }), + Match.objectLike({ Configuration: Match.objectLike({ Repo: 'bar' }) }), + Match.objectLike({ Configuration: Match.objectLike({ Repo: 'build' }) }), + Match.objectLike({ Configuration: Match.objectLike({ Repo: 'test' }) }), ], }, { Name: 'Build', Actions: [ - objectLike({ Name: 'Prebuild', RunOrder: 1 }), - objectLike({ + Match.objectLike({ Name: 'Prebuild', RunOrder: 1 }), + Match.objectLike({ Name: 'Synth', RunOrder: 2, InputArtifacts: [ // 3 input artifacts - anything(), - anything(), - anything(), + Match.anyValue(), + Match.anyValue(), + Match.anyValue(), ], }), ], }, - ), + ]), }); - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { Source: { - BuildSpec: encodedJson(deepObjectLike({ + BuildSpec: Match.serializedJson(Match.objectLike({ phases: { install: { commands: [ @@ -975,12 +975,12 @@ behavior('Can easily switch on privileged mode for synth', (suite) => { commands: ['LookAtMe'], }); - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { - Environment: objectLike({ + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { + Environment: Match.objectLike({ PrivilegedMode: true, }), Source: { - BuildSpec: encodedJson(deepObjectLike({ + BuildSpec: Match.serializedJson(Match.objectLike({ phases: { build: { commands: [ @@ -1079,19 +1079,19 @@ behavior('can provide custom BuildSpec that is merged with generated one', (suit function THEN_codePipelineExpectation() { // THEN - expect(pipelineStack).toHaveResourceLike('AWS::CodeBuild::Project', { - Environment: objectLike({ + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodeBuild::Project', { + Environment: Match.objectLike({ PrivilegedMode: true, - EnvironmentVariables: arrayWith( + EnvironmentVariables: Match.arrayWith([ { Name: 'INNER_VAR', Type: 'PLAINTEXT', Value: 'InnerValue', }, - ), + ]), }), Source: { - BuildSpec: encodedJson(deepObjectLike({ + BuildSpec: Match.serializedJson(Match.objectLike({ env: { variables: { FOO: 'bar', @@ -1099,7 +1099,7 @@ behavior('can provide custom BuildSpec that is merged with generated one', (suit }, phases: { pre_build: { - commands: arrayWith('installCustom'), + commands: Match.arrayWith(['installCustom']), }, build: { commands: ['synth'],