diff --git a/packages/aws-cdk/test/workflows.test.ts b/packages/aws-cdk/test/workflows.test.ts index 95c686045fa32..ac9c6a785f6a8 100644 --- a/packages/aws-cdk/test/workflows.test.ts +++ b/packages/aws-cdk/test/workflows.test.ts @@ -3,7 +3,7 @@ import { instanceMockFrom, MockCloudExecutable, TestStackArtifact } from './util import { MockSdkProvider } from './util/mock-sdk'; import { Bootstrapper } from '../lib/api/bootstrap'; import { Deployments } from '../lib/api/deployments'; -import { CdkToolkit, Tag } from '../lib/cdk-toolkit'; +import { CdkToolkit } from '../lib/cdk-toolkit'; import { listWorkflow } from '../lib/workflows'; let cloudExecutable: MockCloudExecutable; @@ -24,16 +24,13 @@ beforeEach(() => { }); describe('list', () => { - test('list stacks with no dependencies', async () => { + test('stacks with no dependencies', async () => { // GIVEN const toolkit = new CdkToolkit({ cloudExecutable, configuration: cloudExecutable.configuration, sdkProvider: cloudExecutable.sdkProvider, - deployments: new FakeCloudFormation({ - 'Test-Stack-A': { Foo: 'Bar' }, - 'Test-Stack-B': { Baz: 'Zinga!' }, - }), + deployments: new Deployments({ sdkProvider: new MockSdkProvider() }), }); // WHEN @@ -62,7 +59,7 @@ describe('list', () => { }]); }); - test('list stacks with dependent stacks', async () => { + test('stacks with dependent stacks', async () => { // GIVEN const toolkit = new CdkToolkit({ cloudExecutable: new MockCloudExecutable({ @@ -76,9 +73,6 @@ describe('list', () => { '/Test-Stack-B': [ { type: cxschema.ArtifactMetadataEntryType.STACK_TAGS, - data: [ - { key: 'Baz', value: 'Zinga!' }, - ], }, ], }, @@ -88,10 +82,7 @@ describe('list', () => { }), configuration: cloudExecutable.configuration, sdkProvider: cloudExecutable.sdkProvider, - deployments: new FakeCloudFormation({ - 'Test-Stack-A': { Foo: 'Bar' }, - 'Test-Stack-B': { Baz: 'Zinga!' }, - }), + deployments: new Deployments({ sdkProvider: new MockSdkProvider() }), }); // WHEN @@ -122,6 +113,162 @@ describe('list', () => { }], }]); }); + + test('stacks with nested dependencies', async () => { + // GIVEN + const toolkit = new CdkToolkit({ + cloudExecutable: new MockCloudExecutable({ + stacks: [ + MockStack.MOCK_STACK_A, + { + stackName: 'Test-Stack-B', + template: { Resources: { TemplateName: 'Test-Stack-B' } }, + env: 'aws://123456789012/bermuda-triangle-1', + metadata: { + '/Test-Stack-B': [ + { + type: cxschema.ArtifactMetadataEntryType.STACK_TAGS, + }, + ], + }, + depends: ['Test-Stack-A'], + }, + { + stackName: 'Test-Stack-C', + template: { Resources: { TemplateName: 'Test-Stack-B' } }, + env: 'aws://123456789012/bermuda-triangle-1', + metadata: { + '/Test-Stack-B': [ + { + type: cxschema.ArtifactMetadataEntryType.STACK_TAGS, + }, + ], + }, + depends: ['Test-Stack-B'], + }, + ], + }), + configuration: cloudExecutable.configuration, + sdkProvider: cloudExecutable.sdkProvider, + deployments: new Deployments({ sdkProvider: new MockSdkProvider() }), + }); + + // WHEN + const workflow = await listWorkflow( toolkit, { selectors: ['Test-Stack-A', 'Test-Stack-B', 'Test-Stack-C'] }); + + // THEN + expect(JSON.parse(workflow)).toEqual([{ + id: 'Test-Stack-A', + name: 'Test-Stack-A', + environment: { + account: '123456789012', + region: 'bermuda-triangle-1', + name: 'aws://123456789012/bermuda-triangle-1', + }, + dependencies: [], + }, + { + id: 'Test-Stack-B', + name: 'Test-Stack-B', + environment: { + account: '123456789012', + region: 'bermuda-triangle-1', + name: 'aws://123456789012/bermuda-triangle-1', + }, + dependencies: [{ + id: 'Test-Stack-A', + dependencies: [], + }], + }, + { + id: 'Test-Stack-C', + name: 'Test-Stack-C', + environment: { + account: '123456789012', + region: 'bermuda-triangle-1', + name: 'aws://123456789012/bermuda-triangle-1', + }, + dependencies: [{ + id: 'Test-Stack-B', + dependencies: [{ + id: 'Test-Stack-A', + dependencies: [], + }], + }], + }]); + }); + + // In the context of stacks with cross-stack or cross-region references, + // the dependency mechanism is responsible for appropriately applying dependencies at the correct hierarchy level, + // typically at the top-level stacks. + // This involves handling the establishment of cross-references between stacks or nested stacks + // and generating assets for nested stack templates as necessary. + test('stacks with cross stack referencing', async () => { + // GIVEN + const toolkit = new CdkToolkit({ + cloudExecutable: new MockCloudExecutable({ + stacks: [ + { + stackName: 'Test-Stack-A', + template: { + Resources: { + MyBucket1Reference: { + Type: 'AWS::CloudFormation::Stack', + Properties: { + TemplateURL: 'XXXXXXXXXXXXXXXXXXXXXXXXX', + Parameters: { + BucketName: { 'Fn::GetAtt': ['MyBucket1', 'Arn'] }, + }, + }, + }, + }, + }, + env: 'aws://123456789012/bermuda-triangle-1', + metadata: { + '/Test-Stack-A': [ + { + type: cxschema.ArtifactMetadataEntryType.STACK_TAGS, + }, + ], + }, + depends: ['Test-Stack-C'], + }, + MockStack.MOCK_STACK_C, + ], + }), + configuration: cloudExecutable.configuration, + sdkProvider: cloudExecutable.sdkProvider, + deployments: new Deployments({ sdkProvider: new MockSdkProvider() }), + }); + + // WHEN + const workflow = await listWorkflow( toolkit, { selectors: ['Test-Stack-A', 'Test-Stack-C'] }); + + // THEN + expect(JSON.parse(workflow)).toEqual([{ + id: 'Test-Stack-C', + name: 'Test-Stack-C', + environment: { + account: '123456789012', + region: 'bermuda-triangle-1', + name: 'aws://123456789012/bermuda-triangle-1', + }, + dependencies: [], + }, + { + id: 'Test-Stack-A', + name: 'Test-Stack-A', + environment: { + account: '123456789012', + region: 'bermuda-triangle-1', + name: 'aws://123456789012/bermuda-triangle-1', + }, + dependencies: [{ + id: 'Test-Stack-C', + dependencies: [], + }], + }]); + }); }); class MockStack { @@ -133,9 +280,6 @@ class MockStack { '/Test-Stack-A': [ { type: cxschema.ArtifactMetadataEntryType.STACK_TAGS, - data: [ - { key: 'Foo', value: 'Bar' }, - ], }, ], }, @@ -149,27 +293,30 @@ class MockStack { '/Test-Stack-B': [ { type: cxschema.ArtifactMetadataEntryType.STACK_TAGS, - data: [ - { key: 'Baz', value: 'Zinga!' }, - ], }, ], }, }; -} - -class FakeCloudFormation extends Deployments { - private readonly expectedTags: { [stackName: string]: Tag[] } = {}; - - constructor( - expectedTags: { [stackName: string]: { [key: string]: string } } = {}, - ) { - super({ sdkProvider: new MockSdkProvider() }); - - for (const [stackName, tags] of Object.entries(expectedTags)) { - this.expectedTags[stackName] = - Object.entries(tags).map(([Key, Value]) => ({ Key, Value })) - .sort((l, r) => l.Key.localeCompare(r.Key)); - } + public static readonly MOCK_STACK_C: TestStackArtifact = { + stackName: 'Test-Stack-C', + template: { + Resources: { + MyBucket1: { + Type: 'AWS::S3::Bucket', + Properties: { + AccessControl: 'PublicRead', + }, + DeletionPolicy: 'Retain', + }, + }, + }, + env: 'aws://123456789012/bermuda-triangle-1', + metadata: { + '/Test-Stack-C': [ + { + type: cxschema.ArtifactMetadataEntryType.STACK_TAGS, + }, + ], + }, } }