diff --git a/packages/@aws-cdk/assertions/README.md b/packages/@aws-cdk/assertions/README.md index d2763d0ba24ef..0c2d9d1ecdadf 100644 --- a/packages/@aws-cdk/assertions/README.md +++ b/packages/@aws-cdk/assertions/README.md @@ -129,9 +129,17 @@ assert.hasOutput('*', { }); ``` -`findOutputs()` will return a list of outputs that match the `logicalId` and `props`, +`findOutputs()` will return a set of outputs that match the `logicalId` and `props`, and you can use the `'*'` special case as well. +```ts +const result = assert.findOutputs('*', { + Value: 'Fred', +}); +expect(result.Foo).toEqual({ Value: 'Fred', Description: 'FooFred' }); +expect(result.Bar).toEqual({ Value: 'Fred', Description: 'BarFred' }); +``` + The APIs `hasMapping()` and `findMappings()` provide similar functionalities. ## Special Matchers diff --git a/packages/@aws-cdk/assertions/lib/private/mappings.ts b/packages/@aws-cdk/assertions/lib/private/mappings.ts index 266e322bb1139..e19afe541d204 100644 --- a/packages/@aws-cdk/assertions/lib/private/mappings.ts +++ b/packages/@aws-cdk/assertions/lib/private/mappings.ts @@ -1,12 +1,12 @@ import { StackInspector } from '../vendored/assert'; import { filterLogicalId, formatFailure, matchSection } from './section'; -export function findMappings(inspector: StackInspector, logicalId: string, props: any = {}): { [key: string]: any }[] { +export function findMappings(inspector: StackInspector, logicalId: string, props: any = {}): { [key: string]: { [key: string]: any } } { const section: { [key: string] : {} } = inspector.value.Mappings; const result = matchSection(filterLogicalId(section, logicalId), props); if (!result.match) { - return []; + return {}; } return result.matches; diff --git a/packages/@aws-cdk/assertions/lib/private/outputs.ts b/packages/@aws-cdk/assertions/lib/private/outputs.ts index 870e00555b254..320016d22a8eb 100644 --- a/packages/@aws-cdk/assertions/lib/private/outputs.ts +++ b/packages/@aws-cdk/assertions/lib/private/outputs.ts @@ -1,12 +1,12 @@ import { StackInspector } from '../vendored/assert'; import { filterLogicalId, formatFailure, matchSection } from './section'; -export function findOutputs(inspector: StackInspector, logicalId: string, props: any = {}): { [key: string]: any }[] { +export function findOutputs(inspector: StackInspector, logicalId: string, props: any = {}): { [key: string]: { [key: string]: any } } { const section: { [key: string] : {} } = inspector.value.Outputs; const result = matchSection(filterLogicalId(section, logicalId), props); if (!result.match) { - return []; + return {}; } return result.matches; diff --git a/packages/@aws-cdk/assertions/lib/private/resources.ts b/packages/@aws-cdk/assertions/lib/private/resources.ts index 31286a8a6f1a4..81b77346f61f5 100644 --- a/packages/@aws-cdk/assertions/lib/private/resources.ts +++ b/packages/@aws-cdk/assertions/lib/private/resources.ts @@ -6,12 +6,12 @@ type Resource = { Type: string; } -export function findResources(inspector: StackInspector, type: string, props: any = {}): { [key: string]: any }[] { +export function findResources(inspector: StackInspector, type: string, props: any = {}): { [key: string]: { [key: string]: any } } { const section: { [key: string] : Resource } = inspector.value.Resources; const result = matchSection(filterType(section, type), props); if (!result.match) { - return []; + return {}; } return result.matches; diff --git a/packages/@aws-cdk/assertions/lib/private/section.ts b/packages/@aws-cdk/assertions/lib/private/section.ts index 59ad55241e581..89242bc3d6005 100644 --- a/packages/@aws-cdk/assertions/lib/private/section.ts +++ b/packages/@aws-cdk/assertions/lib/private/section.ts @@ -1,22 +1,22 @@ import { Match } from '../match'; import { Matcher, MatchResult } from '../matcher'; -export type MatchSuccess = { match: true, matches: any[] }; +export type MatchSuccess = { match: true, matches: {[key: string]: any} }; export type MatchFailure = { match: false, closestResult?: MatchResult, analyzedCount: number }; export function matchSection(section: any, props: any): MatchSuccess | MatchFailure { const matcher = Matcher.isMatcher(props) ? props : Match.objectLike(props); let closestResult: MatchResult | undefined = undefined; - let matching: any[] = []; + let matching: {[key: string]: any} = {}; let count = 0; eachEntryInSection( section, - (entry) => { + (logicalId, entry) => { const result = matcher.test(entry); if (!result.hasFailed()) { - matching.push(entry); + matching[logicalId] = entry; } else { count++; if (closestResult === undefined || closestResult.failCount > result.failCount) { @@ -25,8 +25,7 @@ export function matchSection(section: any, props: any): MatchSuccess | MatchFail } }, ); - - if (matching.length > 0) { + if (Object.keys(matching).length > 0) { return { match: true, matches: matching }; } else { return { match: false, closestResult, analyzedCount: count }; @@ -35,11 +34,11 @@ export function matchSection(section: any, props: any): MatchSuccess | MatchFail function eachEntryInSection( section: any, - cb: (entry: {[key: string]: any}) => void): void { + cb: (logicalId: string, entry: {[key: string]: any}) => void): void { for (const logicalId of Object.keys(section ?? {})) { const resource: { [key: string]: any } = section[logicalId]; - cb(resource); + cb(logicalId, resource); } } diff --git a/packages/@aws-cdk/assertions/lib/template.ts b/packages/@aws-cdk/assertions/lib/template.ts index d642e74962080..e6f721d928576 100644 --- a/packages/@aws-cdk/assertions/lib/template.ts +++ b/packages/@aws-cdk/assertions/lib/template.ts @@ -101,7 +101,7 @@ export class Template { * When a literal is provided, performs a partial match via `Match.objectLike()`. * Use the `Match` APIs to configure a different behaviour. */ - public findResources(type: string, props: any = {}): { [key: string]: any }[] { + public findResources(type: string, props: any = {}): { [key: string]: { [key: string]: any } } { return findResources(this.inspector, type, props); } @@ -126,7 +126,7 @@ export class Template { * When a literal object is provided, performs a partial match via `Match.objectLike()`. * Use the `Match` APIs to configure a different behaviour. */ - public findOutputs(logicalId: string, props: any = {}): { [key: string]: any }[] { + public findOutputs(logicalId: string, props: any = {}): { [key: string]: { [key: string]: any } } { return findOutputs(this.inspector, logicalId, props); } @@ -151,7 +151,7 @@ export class Template { * When a literal object is provided, performs a partial match via `Match.objectLike()`. * Use the `Match` APIs to configure a different behaviour. */ - public findMappings(logicalId: string, props: any = {}): { [key: string]: any }[] { + public findMappings(logicalId: string, props: any = {}): { [key: string]: { [key: string]: any } } { return findMappings(this.inspector, logicalId, props); } diff --git a/packages/@aws-cdk/assertions/test/private/section.test.ts b/packages/@aws-cdk/assertions/test/private/section.test.ts index 20cc2d5d14e0b..28cdece823df7 100644 --- a/packages/@aws-cdk/assertions/test/private/section.test.ts +++ b/packages/@aws-cdk/assertions/test/private/section.test.ts @@ -18,7 +18,9 @@ describe('section', () => { // THEN expect(result.match).toEqual(true); const success = result as MatchSuccess; - expect(success.matches.length).toEqual(2); + expect(Object.keys(success.matches).length).toEqual(2); + expect(success.matches.Entry1).toEqual({ foo: 'bar' }); + expect(success.matches.Entry2).toEqual({ foo: 'bar', baz: 'qux' }); }); test('failure', () => { diff --git a/packages/@aws-cdk/assertions/test/template.test.ts b/packages/@aws-cdk/assertions/test/template.test.ts index ca5c5c5ea1e58..7c3221763446c 100644 --- a/packages/@aws-cdk/assertions/test/template.test.ts +++ b/packages/@aws-cdk/assertions/test/template.test.ts @@ -278,10 +278,12 @@ describe('Template', () => { }); const inspect = Template.fromStack(stack); - expect(inspect.findResources('Foo::Bar')).toEqual([{ - Type: 'Foo::Bar', - Properties: { baz: 'qux', fred: 'waldo' }, - }]); + expect(inspect.findResources('Foo::Bar')).toEqual({ + Foo: { + Type: 'Foo::Bar', + Properties: { baz: 'qux', fred: 'waldo' }, + }, + }); }); test('no matching resource type', () => { @@ -292,7 +294,7 @@ describe('Template', () => { }); const inspect = Template.fromStack(stack); - expect(inspect.findResources('Foo::Baz')).toEqual([]); + expect(inspect.findResources('Foo::Baz')).toEqual({}); }); test('matching resource props', () => { @@ -303,9 +305,9 @@ describe('Template', () => { }); const inspect = Template.fromStack(stack); - expect(inspect.findResources('Foo::Bar', { + expect(Object.keys(inspect.findResources('Foo::Bar', { Properties: { baz: 'qux' }, - }).length).toEqual(1); + })).length).toEqual(1); }); test('no matching resource props', () => { @@ -318,7 +320,7 @@ describe('Template', () => { const inspect = Template.fromStack(stack); expect(inspect.findResources('Foo::Bar', { Properties: { baz: 'waldo' }, - })).toEqual([]); + })).toEqual({}); }); test('multiple matching resources', () => { @@ -327,7 +329,10 @@ describe('Template', () => { new CfnResource(stack, 'Bar', { type: 'Foo::Bar' }); const inspect = Template.fromStack(stack); - expect(inspect.findResources('Foo::Bar').length).toEqual(2); + const result = inspect.findResources('Foo::Bar'); + expect(Object.keys(result).length).toEqual(2); + expect(result.Foo).toEqual({ Type: 'Foo::Bar' }); + expect(result.Bar).toEqual({ Type: 'Foo::Bar' }); }); }); @@ -433,10 +438,9 @@ describe('Template', () => { const inspect = Template.fromStack(stack); const result = inspect.findOutputs('*', { Value: 'Fred' }); - expect(result).toEqual([ - { Value: 'Fred', Description: 'FooFred' }, - { Value: 'Fred', Description: 'BarFred' }, - ]); + expect(Object.keys(result).length).toEqual(2); + expect(result.Foo).toEqual({ Value: 'Fred', Description: 'FooFred' }); + expect(result.Bar).toEqual({ Value: 'Fred', Description: 'BarFred' }); }); test('not matching', () => { @@ -447,7 +451,7 @@ describe('Template', () => { const inspect = Template.fromStack(stack); const result = inspect.findOutputs('*', { Value: 'Waldo' }); - expect(result.length).toEqual(0); + expect(Object.keys(result).length).toEqual(0); }); test('matching specific output', () => { @@ -461,9 +465,11 @@ describe('Template', () => { const inspect = Template.fromStack(stack); const result = inspect.findOutputs('Foo', { Value: 'Fred' }); - expect(result).toEqual([ - { Value: 'Fred' }, - ]); + expect(result).toEqual({ + Foo: { + Value: 'Fred', + }, + }); }); test('not matching specific output', () => { @@ -477,7 +483,7 @@ describe('Template', () => { const inspect = Template.fromStack(stack); const result = inspect.findOutputs('Foo', { Value: 'Waldo' }); - expect(result.length).toEqual(0); + expect(Object.keys(result).length).toEqual(0); }); }); @@ -592,13 +598,13 @@ describe('Template', () => { const inspect = Template.fromStack(stack); const result = inspect.findMappings('*', { Foo: { Bar: 'Lightning' } }); - expect(result).toEqual([ - { + expect(result).toEqual({ + Foo: { Foo: { Bar: 'Lightning', Fred: 'Waldo' }, Baz: { Bar: 'Qux' }, }, - { Foo: { Bar: 'Lightning' } }, - ]); + Fred: { Foo: { Bar: 'Lightning' } }, + }); }); test('not matching', () => { @@ -611,7 +617,7 @@ describe('Template', () => { const inspect = Template.fromStack(stack); const result = inspect.findMappings('*', { Foo: { Bar: 'Waldo' } }); - expect(result.length).toEqual(0); + expect(Object.keys(result).length).toEqual(0); }); test('matching with specific outputName', () => { @@ -630,15 +636,15 @@ describe('Template', () => { const inspect = Template.fromStack(stack); const result = inspect.findMappings('Foo', { Foo: { Bar: 'Lightning' } }); - expect(result).toEqual([ - { + expect(result).toEqual({ + Foo: { Foo: { Bar: 'Lightning', Fred: 'Waldo' }, Baz: { Bar: 'Qux' }, }, - ]); + }); }); - test('not matching', () => { + test('not matching specific output name', () => { const stack = new Stack(); new CfnMapping(stack, 'Foo', { mapping: { @@ -654,7 +660,7 @@ describe('Template', () => { const inspect = Template.fromStack(stack); const result = inspect.findMappings('Fred', { Baz: { Bar: 'Qux' } }); - expect(result.length).toEqual(0); + expect(Object.keys(result).length).toEqual(0); }); }); }); diff --git a/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts b/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts index a4e880bac742d..dc096594d0ecd 100644 --- a/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts +++ b/packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts @@ -267,9 +267,9 @@ describe('HttpApi', () => { Template.fromStack(stack).hasResourceProperties('AWS::ApiGatewayV2::VpcLink', { Name: 'Link-1', }); - expect(Template.fromStack(stack).findResources('AWS::ApiGatewayV2::VpcLink', { + expect(Object.keys(Template.fromStack(stack).findResources('AWS::ApiGatewayV2::VpcLink', { Name: 'Link-2', - }).length).toEqual(0); + })).length).toEqual(0); }); test('apiEndpoint is exported', () => { diff --git a/packages/@aws-cdk/aws-cloudwatch/test/dashboard.test.ts b/packages/@aws-cdk/aws-cloudwatch/test/dashboard.test.ts index 6e796aa6b833b..201f0158c11c3 100644 --- a/packages/@aws-cdk/aws-cloudwatch/test/dashboard.test.ts +++ b/packages/@aws-cdk/aws-cloudwatch/test/dashboard.test.ts @@ -27,8 +27,9 @@ describe('Dashboard', () => { // THEN const resources = Template.fromStack(stack).findResources('AWS::CloudWatch::Dashboard'); - expect(resources.length).toEqual(1); - hasWidgets(resources[0].Properties, [ + expect(Object.keys(resources).length).toEqual(1); + const key = Object.keys(resources)[0]; + hasWidgets(resources[key].Properties, [ { type: 'text', width: 10, height: 2, x: 0, y: 0, properties: { markdown: 'first' } }, { type: 'text', width: 1, height: 4, x: 0, y: 2, properties: { markdown: 'second' } }, { type: 'text', width: 4, height: 1, x: 0, y: 6, properties: { markdown: 'third' } }, @@ -63,8 +64,9 @@ describe('Dashboard', () => { // THEN const resources = Template.fromStack(stack).findResources('AWS::CloudWatch::Dashboard'); - expect(resources.length).toEqual(1); - hasWidgets(resources[0].Properties, [ + expect(Object.keys(resources).length).toEqual(1); + const key = Object.keys(resources)[0]; + hasWidgets(resources[key].Properties, [ { type: 'text', width: 10, height: 2, x: 0, y: 0, properties: { markdown: 'first' } }, { type: 'text', width: 1, height: 4, x: 10, y: 0, properties: { markdown: 'second' } }, { type: 'text', width: 4, height: 1, x: 11, y: 0, properties: { markdown: 'third' } },