diff --git a/packages/@aws-cdk/pipelines/test/codepipeline/codepipeline-sources.test.ts b/packages/@aws-cdk/pipelines/test/codepipeline/codepipeline-sources.test.ts index 84aef10bb171c..46fb468c37623 100644 --- a/packages/@aws-cdk/pipelines/test/codepipeline/codepipeline-sources.test.ts +++ b/packages/@aws-cdk/pipelines/test/codepipeline/codepipeline-sources.test.ts @@ -1,5 +1,4 @@ -import { anything, arrayWith, Capture, objectLike } from '@aws-cdk/assert-internal'; -import '@aws-cdk/assert-internal/jest'; +import { Capture, Match, Template } from '@aws-cdk/assertions'; import * as ccommit from '@aws-cdk/aws-codecommit'; import { CodeCommitTrigger, GitHubTrigger } from '@aws-cdk/aws-codepipeline-actions'; import { AnyPrincipal, Role } from '@aws-cdk/aws-iam'; @@ -28,18 +27,18 @@ test('CodeCommit source handles tokenized names correctly', () => { input: cdkp.CodePipelineSource.codeCommit(repo, 'main'), }); - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: arrayWith({ + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: Match.arrayWith([{ Name: 'Source', Actions: [ - objectLike({ - Configuration: objectLike({ - RepositoryName: { 'Fn::GetAtt': [anything(), 'Name'] }, + Match.objectLike({ + Configuration: Match.objectLike({ + RepositoryName: { 'Fn::GetAtt': [Match.anyValue(), 'Name'] }, }), - Name: { 'Fn::GetAtt': [anything(), 'Name'] }, + Name: { 'Fn::GetAtt': [Match.anyValue(), 'Name'] }, }), ], - }), + }]), }); }); @@ -58,20 +57,20 @@ test('CodeCommit source honors all valid properties', () => { }), }); - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: arrayWith({ + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: Match.arrayWith([{ Name: 'Source', Actions: [ - objectLike({ - Configuration: objectLike({ + Match.objectLike({ + Configuration: Match.objectLike({ BranchName: 'main', PollForSourceChanges: true, OutputArtifactFormat: 'CODEBUILD_CLONE_REF', }), - RoleArn: { 'Fn::GetAtt': [anything(), 'Arn'] }, + RoleArn: { 'Fn::GetAtt': [Match.anyValue(), 'Arn'] }, }), ], - }), + }]), }); }); @@ -81,19 +80,19 @@ test('S3 source handles tokenized names correctly', () => { input: cdkp.CodePipelineSource.s3(buckit, 'thefile.zip'), }); - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: arrayWith({ + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: Match.arrayWith([{ Name: 'Source', Actions: [ - objectLike({ - Configuration: objectLike({ - S3Bucket: { Ref: anything() }, + Match.objectLike({ + Configuration: Match.objectLike({ + S3Bucket: { Ref: Match.anyValue() }, S3ObjectKey: 'thefile.zip', }), - Name: { Ref: anything() }, + Name: { Ref: Match.anyValue() }, }), ], - }), + }]), }); }); @@ -105,12 +104,12 @@ test('GitHub source honors all valid properties', () => { }), }); - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: arrayWith({ + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: Match.arrayWith([{ Name: 'Source', Actions: [ - objectLike({ - Configuration: objectLike({ + Match.objectLike({ + Configuration: Match.objectLike({ Owner: 'owner', Repo: 'repo', Branch: 'main', @@ -120,7 +119,7 @@ test('GitHub source honors all valid properties', () => { Name: 'owner_repo', }), ], - }), + }]), }); }); @@ -145,17 +144,17 @@ test('Dashes in repo names are removed from artifact names', () => { input: cdkp.CodePipelineSource.gitHub('owner/my-repo', 'main'), }); - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: arrayWith({ + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: Match.arrayWith([{ Name: 'Source', Actions: [ - objectLike({ + Match.objectLike({ OutputArtifacts: [ { Name: 'owner_my_repo_Source' }, ], }), ], - }), + }]), }); }); @@ -164,19 +163,19 @@ test('artifact names are never longer than 128 characters', () => { input: cdkp.CodePipelineSource.gitHub('owner/' + 'my-repo'.repeat(100), 'main'), }); - const artifactId = Capture.aString(); - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: arrayWith({ + const artifactId = new Capture(); + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: Match.arrayWith([{ Name: 'Source', Actions: [ - objectLike({ + Match.objectLike({ OutputArtifacts: [ - { Name: artifactId.capture() }, + { Name: artifactId }, ], }), ], - }), + }]), }); - expect(artifactId.capturedValue.length).toBeLessThanOrEqual(128); + expect(artifactId.asString().length).toBeLessThanOrEqual(128); }); \ No newline at end of file diff --git a/packages/@aws-cdk/pipelines/test/compliance/basic-behavior.test.ts b/packages/@aws-cdk/pipelines/test/compliance/basic-behavior.test.ts index 1248831737bdf..937e9bff50634 100644 --- a/packages/@aws-cdk/pipelines/test/compliance/basic-behavior.test.ts +++ b/packages/@aws-cdk/pipelines/test/compliance/basic-behavior.test.ts @@ -1,7 +1,7 @@ /* eslint-disable import/no-extraneous-dependencies */ import * as fs from 'fs'; import * as path from 'path'; -import { arrayWith, Capture, objectLike, stringLike } from '@aws-cdk/assert-internal'; +import { Capture, Match, Template } from '@aws-cdk/assertions'; import '@aws-cdk/assert-internal/jest'; import { Stack, Stage, StageProps, Tags } from '@aws-cdk/core'; import { Construct } from 'constructs'; @@ -25,7 +25,21 @@ behavior('stack templates in nested assemblies are correctly addressed', (suite) const pipeline = new LegacyTestGitHubNpmPipeline(pipelineStack, 'Cdk'); pipeline.addApplicationStage(new OneStackApp(app, 'App')); - THEN_codePipelineExpectation(); + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: Match.arrayWith([{ + Name: 'App', + Actions: Match.arrayWith([ + Match.objectLike({ + Name: 'Stack.Prepare', + InputArtifacts: [Match.objectLike({})], + Configuration: Match.objectLike({ + StackName: 'App-Stack', + TemplatePath: 'Artifact_Build_Synth::assembly-App/AppStackA53728DD.template.json', + }), + }), + ]), + }]), + }); }); suite.modern(() => { @@ -33,26 +47,22 @@ behavior('stack templates in nested assemblies are correctly addressed', (suite) const pipeline = new ModernTestGitHubNpmPipeline(pipelineStack, 'Cdk'); pipeline.addStage(new OneStackApp(app, 'App')); - THEN_codePipelineExpectation(); - }); - - 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({ - Name: stringLike('*Prepare'), - InputArtifacts: [objectLike({})], - Configuration: objectLike({ + Actions: Match.arrayWith([ + Match.objectLike({ + Name: 'Prepare', + InputArtifacts: [Match.objectLike({})], + Configuration: Match.objectLike({ StackName: 'App-Stack', - TemplatePath: stringLike('*::assembly-App/*.template.json'), + TemplatePath: 'Synth_Output::assembly-App/AppStackA53728DD.template.json', }), }), - ), - }), + ]), + }]), }); - } + }); }); behavior('obvious error is thrown when stage contains no stacks', (suite) => { @@ -82,7 +92,28 @@ behavior('overridden stack names are respected', (suite) => { pipeline.addApplicationStage(new OneStackAppWithCustomName(app, 'App1')); pipeline.addApplicationStage(new OneStackAppWithCustomName(app, 'App2')); - THEN_codePipelineExpectation(); + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: Match.arrayWith([ + { + Name: 'App1', + Actions: Match.arrayWith([Match.objectLike({ + Name: 'MyFancyStack.Prepare', + Configuration: Match.objectLike({ + StackName: 'MyFancyStack', + }), + })]), + }, + { + Name: 'App2', + Actions: Match.arrayWith([Match.objectLike({ + Name: 'MyFancyStack.Prepare', + Configuration: Match.objectLike({ + StackName: 'MyFancyStack', + }), + })]), + }, + ]), + }); }); suite.modern(() => { @@ -90,33 +121,29 @@ behavior('overridden stack names are respected', (suite) => { pipeline.addStage(new OneStackAppWithCustomName(app, 'App1')); pipeline.addStage(new OneStackAppWithCustomName(app, 'App2')); - THEN_codePipelineExpectation(); - }); - - function THEN_codePipelineExpectation() { - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: arrayWith( + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: Match.arrayWith([ { Name: 'App1', - Actions: arrayWith(objectLike({ - Name: stringLike('*Prepare'), - Configuration: objectLike({ + Actions: Match.arrayWith([Match.objectLike({ + Name: 'Prepare', + Configuration: Match.objectLike({ StackName: 'MyFancyStack', }), - })), + })]), }, { Name: 'App2', - Actions: arrayWith(objectLike({ - Name: stringLike('*Prepare'), - Configuration: objectLike({ + Actions: Match.arrayWith([Match.objectLike({ + Name: 'Prepare', + Configuration: Match.objectLike({ StackName: 'MyFancyStack', }), - })), + })]), }, - ), + ]), }); - } + }); }); behavior('changing CLI version leads to a different pipeline structure (restarting it)', (suite) => { @@ -154,17 +181,17 @@ behavior('changing CLI version leads to a different pipeline structure (restarti function THEN_codePipelineExpectation(stack2: Stack, stack3: Stack) { // THEN - const structure2 = Capture.anyType(); - const structure3 = Capture.anyType(); + const structure2 = new Capture(); + const structure3 = new Capture(); - expect(stack2).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: structure2.capture(), + Template.fromStack(stack2).hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: structure2, }); - expect(stack3).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: structure3.capture(), + Template.fromStack(stack3).hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: structure3, }); - expect(JSON.stringify(structure2.capturedValue)).not.toEqual(JSON.stringify(structure3.capturedValue)); + expect(JSON.stringify(structure2.asArray())).not.toEqual(JSON.stringify(structure3.asArray())); } }); @@ -176,7 +203,25 @@ behavior('tags get reflected in pipeline', (suite) => { Tags.of(stage).add('CostCenter', 'F00B4R'); pipeline.addApplicationStage(stage); - THEN_codePipelineExpectation(); + const templateConfig = new Capture(); + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: Match.arrayWith([{ + Name: 'App', + Actions: Match.arrayWith([ + Match.objectLike({ + Name: 'Stack.Prepare', + InputArtifacts: [Match.objectLike({})], + Configuration: Match.objectLike({ + StackName: 'App-Stack', + TemplateConfiguration: templateConfig, + }), + }), + ]), + }]), + }); + + const captured = templateConfig.asString(); + assertTemplateConfig(captured); }); suite.modern(() => { @@ -185,29 +230,33 @@ behavior('tags get reflected in pipeline', (suite) => { const pipeline = new ModernTestGitHubNpmPipeline(pipelineStack, 'Cdk'); Tags.of(stage).add('CostCenter', 'F00B4R'); pipeline.addStage(stage); - THEN_codePipelineExpectation(); - }); - function THEN_codePipelineExpectation() { - // THEN - const templateConfig = Capture.aString(); - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: arrayWith({ + const templateConfig = new Capture(); + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: Match.arrayWith([{ Name: 'App', - Actions: arrayWith( - objectLike({ - Name: stringLike('*Prepare'), - InputArtifacts: [objectLike({})], - Configuration: objectLike({ + Actions: Match.arrayWith([ + Match.objectLike({ + Name: 'Prepare', + InputArtifacts: [Match.objectLike({})], + Configuration: Match.objectLike({ StackName: 'App-Stack', - TemplateConfiguration: templateConfig.capture(stringLike('*::assembly-App/*.template.*json')), + TemplateConfiguration: templateConfig, }), }), - ), - }), + ]), + }]), }); - const [, relConfigFile] = templateConfig.capturedValue.split('::'); + const captured = templateConfig.asString(); + assertTemplateConfig(captured); + }); + + function assertTemplateConfig(config: string) { + // THEN + + expect(config).toMatch(/::assembly-App\/.*.template\..*json/); + const [, relConfigFile] = config.split('::'); const absConfigFile = path.join(app.outdir, relConfigFile); const configFile = JSON.parse(fs.readFileSync(absConfigFile, { encoding: 'utf-8' })); expect(configFile).toEqual(expect.objectContaining({ diff --git a/packages/@aws-cdk/pipelines/test/compliance/environments.test.ts b/packages/@aws-cdk/pipelines/test/compliance/environments.test.ts index d30e5a423fcb3..b86758d332556 100644 --- a/packages/@aws-cdk/pipelines/test/compliance/environments.test.ts +++ b/packages/@aws-cdk/pipelines/test/compliance/environments.test.ts @@ -1,6 +1,4 @@ -/* eslint-disable import/no-extraneous-dependencies */ -import { arrayWith, objectLike, stringLike } from '@aws-cdk/assert-internal'; -import '@aws-cdk/assert-internal/jest'; +import { Capture, Match, Template } from '@aws-cdk/assertions'; import { Stack } from '@aws-cdk/core'; import { behavior, LegacyTestGitHubNpmPipeline, OneStackApp, PIPELINE_ENV, TestApp, ModernTestGitHubNpmPipeline } from '../testhelpers'; @@ -21,7 +19,7 @@ behavior('action has right settings for same-env deployment', (suite) => { const pipeline = new LegacyTestGitHubNpmPipeline(pipelineStack, 'Cdk'); pipeline.addApplicationStage(new OneStackApp(app, 'Same')); - THEN_codePipelineExpection(agnosticRole); + THEN_codePipelineExpection('Stack.Prepare', 'Stack.Deploy', agnosticRole); }); suite.additional('legacy: even if env is specified but the same as the pipeline', () => { @@ -30,14 +28,14 @@ behavior('action has right settings for same-env deployment', (suite) => { env: PIPELINE_ENV, })); - THEN_codePipelineExpection(pipelineEnvRole); + THEN_codePipelineExpection('Stack.Prepare', 'Stack.Deploy', pipelineEnvRole); }); suite.modern(() => { const pipeline = new ModernTestGitHubNpmPipeline(pipelineStack, 'Cdk'); pipeline.addStage(new OneStackApp(app, 'Same')); - THEN_codePipelineExpection(agnosticRole); + THEN_codePipelineExpection('Prepare', 'Deploy', agnosticRole); }); suite.additional('modern: even if env is specified but the same as the pipeline', () => { @@ -46,43 +44,43 @@ behavior('action has right settings for same-env deployment', (suite) => { env: PIPELINE_ENV, })); - THEN_codePipelineExpection(pipelineEnvRole); + THEN_codePipelineExpection('Prepare', 'Deploy', pipelineEnvRole); }); - function THEN_codePipelineExpection(roleArn: (x: string) => any) { + function THEN_codePipelineExpection(prepareStageName: string, deployStageName: string, roleArn: (x: string) => any) { // THEN: pipeline structure is correct - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: arrayWith({ + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: Match.arrayWith([{ Name: 'Same', Actions: [ - objectLike({ - Name: stringLike('*Prepare'), + Match.objectLike({ + Name: prepareStageName, RoleArn: roleArn('deploy-role'), - Configuration: objectLike({ + Configuration: Match.objectLike({ StackName: 'Same-Stack', RoleArn: roleArn('cfn-exec-role'), }), }), - objectLike({ - Name: stringLike('*Deploy'), + Match.objectLike({ + Name: deployStageName, RoleArn: roleArn('deploy-role'), - Configuration: objectLike({ + Configuration: Match.objectLike({ StackName: 'Same-Stack', }), }), ], - }), + }]), }); // THEN: artifact bucket can be read by deploy role - expect(pipelineStack).toHaveResourceLike('AWS::S3::BucketPolicy', { + Template.fromStack(pipelineStack).hasResourceProperties('AWS::S3::BucketPolicy', { PolicyDocument: { - Statement: arrayWith(objectLike({ + Statement: Match.arrayWith([Match.objectLike({ Action: ['s3:GetObject*', 's3:GetBucket*', 's3:List*'], Principal: { AWS: roleArn('deploy-role'), }, - })), + })]), }, }); } @@ -94,7 +92,7 @@ behavior('action has right settings for cross-account deployment', (suite) => { const pipeline = new LegacyTestGitHubNpmPipeline(pipelineStack, 'Cdk'); pipeline.addApplicationStage(new OneStackApp(app, 'CrossAccount', { env: { account: 'you' } })); - THEN_codePipelineExpectation(); + THEN_codePipelineExpectation('Stack.Prepare', 'Stack.Deploy'); }); suite.modern(() => { @@ -104,17 +102,17 @@ behavior('action has right settings for cross-account deployment', (suite) => { }); pipeline.addStage(new OneStackApp(app, 'CrossAccount', { env: { account: 'you' } })); - THEN_codePipelineExpectation(); + THEN_codePipelineExpectation('Prepare', 'Deploy'); }); - function THEN_codePipelineExpectation() { + function THEN_codePipelineExpectation(prepareStageName: string, deployStageName: string) { // THEN: Pipelien structure is correct - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: arrayWith({ + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: Match.arrayWith([{ Name: 'CrossAccount', Actions: [ - objectLike({ - Name: stringLike('*Prepare'), + Match.objectLike({ + Name: prepareStageName, RoleArn: { 'Fn::Join': ['', [ 'arn:', @@ -123,7 +121,7 @@ behavior('action has right settings for cross-account deployment', (suite) => { { Ref: 'AWS::Region' }, ]], }, - Configuration: objectLike({ + Configuration: Match.objectLike({ StackName: 'CrossAccount-Stack', RoleArn: { 'Fn::Join': ['', [ @@ -135,8 +133,8 @@ behavior('action has right settings for cross-account deployment', (suite) => { }, }), }), - objectLike({ - Name: stringLike('*Deploy'), + Match.objectLike({ + Name: deployStageName, RoleArn: { 'Fn::Join': ['', [ 'arn:', @@ -145,32 +143,34 @@ behavior('action has right settings for cross-account deployment', (suite) => { { Ref: 'AWS::Region' }, ]], }, - Configuration: objectLike({ + Configuration: Match.objectLike({ StackName: 'CrossAccount-Stack', }), }), ], - }), + }]), }); // THEN: Artifact bucket can be read by deploy role - expect(pipelineStack).toHaveResourceLike('AWS::S3::BucketPolicy', { + const roleNamePrefix = new Capture(); + Template.fromStack(pipelineStack).hasResourceProperties('AWS::S3::BucketPolicy', { PolicyDocument: { - Statement: arrayWith(objectLike({ + Statement: Match.arrayWith([Match.objectLike({ Action: ['s3:GetObject*', 's3:GetBucket*', 's3:List*'], Principal: { AWS: { 'Fn::Join': ['', [ 'arn:', { Ref: 'AWS::Partition' }, - stringLike('*-deploy-role-*'), + roleNamePrefix, { Ref: 'AWS::Region' }, ]], }, }, - })), + })]), }, }); + expect(roleNamePrefix.asString()).toMatch(/-deploy-role-/); } }); @@ -180,7 +180,7 @@ behavior('action has right settings for cross-region deployment', (suite) => { const pipeline = new LegacyTestGitHubNpmPipeline(pipelineStack, 'Cdk'); pipeline.addApplicationStage(new OneStackApp(app, 'CrossRegion', { env: { region: 'elsewhere' } })); - THEN_codePipelineExpectation(); + THEN_codePipelineExpectation('Stack.Prepare', 'Stack.Deploy'); }); suite.modern(() => { @@ -189,17 +189,17 @@ behavior('action has right settings for cross-region deployment', (suite) => { }); pipeline.addStage(new OneStackApp(app, 'CrossRegion', { env: { region: 'elsewhere' } })); - THEN_codePipelineExpectation(); + THEN_codePipelineExpectation('Prepare', 'Deploy'); }); - function THEN_codePipelineExpectation() { + function THEN_codePipelineExpectation(prepareStageName: string, deployStageName: string) { // THEN - expect(pipelineStack).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: arrayWith({ + Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: Match.arrayWith([{ Name: 'CrossRegion', Actions: [ - objectLike({ - Name: stringLike('*Prepare'), + Match.objectLike({ + Name: prepareStageName, RoleArn: { 'Fn::Join': ['', [ 'arn:', @@ -212,7 +212,7 @@ behavior('action has right settings for cross-region deployment', (suite) => { ]], }, Region: 'elsewhere', - Configuration: objectLike({ + Configuration: Match.objectLike({ StackName: 'CrossRegion-Stack', RoleArn: { 'Fn::Join': ['', [ @@ -227,8 +227,8 @@ behavior('action has right settings for cross-region deployment', (suite) => { }, }), }), - objectLike({ - Name: stringLike('*Deploy'), + Match.objectLike({ + Name: deployStageName, RoleArn: { 'Fn::Join': ['', [ 'arn:', @@ -241,12 +241,12 @@ behavior('action has right settings for cross-region deployment', (suite) => { ]], }, Region: 'elsewhere', - Configuration: objectLike({ + Configuration: Match.objectLike({ StackName: 'CrossRegion-Stack', }), }), ], - }), + }]), }); } }); @@ -262,7 +262,7 @@ behavior('action has right settings for cross-account/cross-region deployment', }, })); - THEN_codePipelineExpectations(); + THEN_codePipelineExpectations('Stack.Prepare', 'Stack.Deploy'); }); suite.modern(() => { @@ -277,17 +277,18 @@ behavior('action has right settings for cross-account/cross-region deployment', }, })); - THEN_codePipelineExpectations(); + THEN_codePipelineExpectations('Prepare', 'Deploy'); }); - function THEN_codePipelineExpectations() { + function THEN_codePipelineExpectations(prepareStageName: string, deployStageName: string) { // THEN: pipeline structure must be correct - expect(app.stackArtifact(pipelineStack)).toHaveResourceLike('AWS::CodePipeline::Pipeline', { - Stages: arrayWith({ + expect(app.stackArtifact(pipelineStack)).toBeDefined(); + Template.fromStack(app.stackArtifact(pipelineStack)!).hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: Match.arrayWith([{ Name: 'CrossBoth', Actions: [ - objectLike({ - Name: stringLike('*Prepare'), + Match.objectLike({ + Name: prepareStageName, RoleArn: { 'Fn::Join': ['', [ 'arn:', @@ -296,7 +297,7 @@ behavior('action has right settings for cross-account/cross-region deployment', ]], }, Region: 'elsewhere', - Configuration: objectLike({ + Configuration: Match.objectLike({ StackName: 'CrossBoth-Stack', RoleArn: { 'Fn::Join': ['', [ @@ -307,8 +308,8 @@ behavior('action has right settings for cross-account/cross-region deployment', }, }), }), - objectLike({ - Name: stringLike('*Deploy'), + Match.objectLike({ + Name: deployStageName, RoleArn: { 'Fn::Join': ['', [ 'arn:', @@ -317,48 +318,52 @@ behavior('action has right settings for cross-account/cross-region deployment', ]], }, Region: 'elsewhere', - Configuration: objectLike({ + Configuration: Match.objectLike({ StackName: 'CrossBoth-Stack', }), }), ], - }), + }]), }); // THEN: artifact bucket can be read by deploy role const supportStack = 'PipelineStack-support-elsewhere'; - expect(app.stackArtifact(supportStack)).toHaveResourceLike('AWS::S3::BucketPolicy', { + expect(app.stackArtifact(supportStack)).toBeDefined(); + const policyRoleNamePrefix = new Capture(); + Template.fromStack(app.stackArtifact(supportStack)!).hasResourceProperties('AWS::S3::BucketPolicy', { PolicyDocument: { - Statement: arrayWith(objectLike({ - Action: arrayWith('s3:GetObject*', 's3:GetBucket*', 's3:List*'), + Statement: Match.arrayWith([Match.objectLike({ + Action: Match.arrayWith(['s3:GetObject*', 's3:GetBucket*', 's3:List*']), Principal: { AWS: { 'Fn::Join': ['', [ 'arn:', { Ref: 'AWS::Partition' }, - stringLike('*-deploy-role-*'), + policyRoleNamePrefix, ]], }, }, - })), + })]), }, }); + expect(policyRoleNamePrefix.asString()).toMatch(/-deploy-role-/); // And the key to go along with it - expect(app.stackArtifact(supportStack)).toHaveResourceLike('AWS::KMS::Key', { + expect(app.stackArtifact(supportStack)).toBeDefined(); + Template.fromStack(app.stackArtifact(supportStack)!).hasResourceProperties('AWS::KMS::Key', { KeyPolicy: { - Statement: arrayWith(objectLike({ - Action: arrayWith('kms:Decrypt', 'kms:DescribeKey'), + Statement: Match.arrayWith([Match.objectLike({ + Action: Match.arrayWith(['kms:Decrypt', 'kms:DescribeKey']), Principal: { AWS: { 'Fn::Join': ['', [ 'arn:', { Ref: 'AWS::Partition' }, - stringLike('*-deploy-role-*'), + ':iam::123pipeline:root', ]], }, }, - })), + })]), }, }); }