From c5dd026dc39e6021928cea830f5b9c88a9cc0dc7 Mon Sep 17 00:00:00 2001 From: Otavio Macedo Date: Mon, 17 Jan 2022 11:42:53 +0000 Subject: [PATCH 1/3] feat(assertions): support for parameters --- .../assertions/lib/private/parameters.ts | 30 +++++++++++++++++++ .../assertions/lib/private/template.ts | 10 +++++-- packages/@aws-cdk/assertions/lib/template.ts | 11 +++++++ 3 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 packages/@aws-cdk/assertions/lib/private/parameters.ts diff --git a/packages/@aws-cdk/assertions/lib/private/parameters.ts b/packages/@aws-cdk/assertions/lib/private/parameters.ts new file mode 100644 index 0000000000000..b708460caf399 --- /dev/null +++ b/packages/@aws-cdk/assertions/lib/private/parameters.ts @@ -0,0 +1,30 @@ +import { filterLogicalId, formatFailure, matchSection } from './section'; +import { Template } from './template'; + +export function findParameters(template: Template, logicalId: string, props: any = {}): { [key: string]: { [key: string]: any } } { + const section: { [key: string] : {} } = template.Parameters; + const result = matchSection(filterLogicalId(section, logicalId), props); + + if (!result.match) { + return {}; + } + + return result.matches; +} + +export function hasParameter(template: Template, logicalId: string, props: any): string | void { + const section: { [key: string] : {} } = template.Parameters; + const result = matchSection(filterLogicalId(section, logicalId), props); + if (result.match) { + return; + } + + if (result.closestResult === undefined) { + return 'No parameters found in the template'; + } + + return [ + `Template has ${result.analyzedCount} parameters, but none match as expected.`, + formatFailure(result.closestResult), + ].join('\n'); +} diff --git a/packages/@aws-cdk/assertions/lib/private/template.ts b/packages/@aws-cdk/assertions/lib/private/template.ts index 3b44368138435..72dbeb8b64661 100644 --- a/packages/@aws-cdk/assertions/lib/private/template.ts +++ b/packages/@aws-cdk/assertions/lib/private/template.ts @@ -3,7 +3,8 @@ export type Template = { Resources: { [logicalId: string]: Resource }, Outputs: { [logicalId: string]: Output }, - Mappings: { [logicalId: string]: Mapping } + Mappings: { [logicalId: string]: Mapping }, + Parameters: { [logicalId: string]: Parameter } } export type Resource = { @@ -13,4 +14,9 @@ export type Resource = { export type Output = { [key: string]: any }; -export type Mapping = { [key: string]: any }; \ No newline at end of file +export type Mapping = { [key: string]: any }; + +export type Parameter = { + Type: string; + [key: string]: any; +} \ No newline at end of file diff --git a/packages/@aws-cdk/assertions/lib/template.ts b/packages/@aws-cdk/assertions/lib/template.ts index dfc830cf8d822..affeaaf22e59a 100644 --- a/packages/@aws-cdk/assertions/lib/template.ts +++ b/packages/@aws-cdk/assertions/lib/template.ts @@ -5,6 +5,7 @@ import { Match } from './match'; import { Matcher } from './matcher'; import { findMappings, hasMapping } from './private/mappings'; import { findOutputs, hasOutput } from './private/outputs'; +import { findParameters } from './private/parameters'; import { countResources, findResources, hasResource, hasResourceProperties } from './private/resources'; import { Template as TemplateType } from './private/template'; @@ -108,6 +109,16 @@ export class Template { return findResources(this.template, type, props); } + /** + * Get the set of matching Parameters that match the given properties in the CloudFormation template. + * @param logicalId the name of the parameter. Provide `'*'` to match all outputs in the template. + * @param props by default, matches all Parameters in the template. + * When a literal object is provided, performs a partial match via `Match.objectLike()`. + * Use the `Match` APIs to configure a different behaviour. */ + public findParameters(logicalId: string, props: any = {}): { [key: string]: { [key: string]: any } } { + return findParameters(this.template, logicalId, props); + } + /** * Assert that an Output with the given properties exists in the CloudFormation template. * By default, performs partial matching on the resource, via the `Match.objectLike()`. From 17fe12529b843ae4325a1f073fa0c4969a627bab Mon Sep 17 00:00:00 2001 From: Otavio Macedo Date: Mon, 17 Jan 2022 11:45:13 +0000 Subject: [PATCH 2/3] Fixed doc string --- packages/@aws-cdk/assertions/lib/template.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@aws-cdk/assertions/lib/template.ts b/packages/@aws-cdk/assertions/lib/template.ts index affeaaf22e59a..e12dfc25ea506 100644 --- a/packages/@aws-cdk/assertions/lib/template.ts +++ b/packages/@aws-cdk/assertions/lib/template.ts @@ -111,7 +111,7 @@ export class Template { /** * Get the set of matching Parameters that match the given properties in the CloudFormation template. - * @param logicalId the name of the parameter. Provide `'*'` to match all outputs in the template. + * @param logicalId the name of the parameter. Provide `'*'` to match all parameters in the template. * @param props by default, matches all Parameters in the template. * When a literal object is provided, performs a partial match via `Match.objectLike()`. * Use the `Match` APIs to configure a different behaviour. */ From 604181f4b1d4a856a2356781ecff9807cabee16d Mon Sep 17 00:00:00 2001 From: Otavio Macedo Date: Mon, 17 Jan 2022 17:47:28 +0000 Subject: [PATCH 3/3] Added tests and `hasParameter` function --- packages/@aws-cdk/assertions/lib/template.ts | 16 +- .../@aws-cdk/assertions/test/template.test.ts | 152 +++++++++++++++++- 2 files changed, 166 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/assertions/lib/template.ts b/packages/@aws-cdk/assertions/lib/template.ts index e12dfc25ea506..631c9f7137dc4 100644 --- a/packages/@aws-cdk/assertions/lib/template.ts +++ b/packages/@aws-cdk/assertions/lib/template.ts @@ -5,7 +5,7 @@ import { Match } from './match'; import { Matcher } from './matcher'; import { findMappings, hasMapping } from './private/mappings'; import { findOutputs, hasOutput } from './private/outputs'; -import { findParameters } from './private/parameters'; +import { findParameters, hasParameter } from './private/parameters'; import { countResources, findResources, hasResource, hasResourceProperties } from './private/resources'; import { Template as TemplateType } from './private/template'; @@ -109,6 +109,20 @@ export class Template { return findResources(this.template, type, props); } + /** + * Assert that a Parameter with the given properties exists in the CloudFormation template. + * By default, performs partial matching on the parameter, via the `Match.objectLike()`. + * To configure different behavior, use other matchers in the `Match` class. + * @param logicalId the name of the parameter. Provide `'*'` to match all parameters in the template. + * @param props the parameter as should be expected in the template. + */ + public hasParameter(logicalId: string, props: any): void { + const matchError = hasParameter(this.template, logicalId, props); + if (matchError) { + throw new Error(matchError); + } + } + /** * Get the set of matching Parameters that match the given properties in the CloudFormation template. * @param logicalId the name of the parameter. Provide `'*'` to match all parameters in the template. diff --git a/packages/@aws-cdk/assertions/test/template.test.ts b/packages/@aws-cdk/assertions/test/template.test.ts index f5068cede6265..dd8377892f405 100644 --- a/packages/@aws-cdk/assertions/test/template.test.ts +++ b/packages/@aws-cdk/assertions/test/template.test.ts @@ -1,4 +1,4 @@ -import { App, CfnMapping, CfnOutput, CfnResource, NestedStack, Stack } from '@aws-cdk/core'; +import { App, CfnMapping, CfnOutput, CfnParameter, CfnResource, NestedStack, Stack } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { Capture, Match, Template } from '../lib'; @@ -708,6 +708,156 @@ describe('Template', () => { }); }); + describe('findParameters', () => { + test('matching', () => { + const stack = new Stack(); + new CfnParameter(stack, 'p1', { + type: 'String', + description: 'string parameter', + }); + new CfnParameter(stack, 'p2', { + type: 'Number', + description: 'number parameter', + }); + + const inspect = Template.fromStack(stack); + const result = inspect.findParameters('*', { Type: 'String' }); + expect(result).toEqual({ + p1: { + Description: 'string parameter', + Type: 'String', + }, + }); + }); + + test('not matching', () => { + const stack = new Stack(); + new CfnParameter(stack, 'p1', { + type: 'String', + description: 'string parameter', + }); + + const inspect = Template.fromStack(stack); + const result = inspect.findParameters('*', { Type: 'Number' }); + expect(Object.keys(result).length).toEqual(0); + }); + + test('matching with specific parameter name', () => { + const stack = new Stack(); + new CfnParameter(stack, 'p1', { + type: 'String', + description: 'string parameter', + }); + new CfnParameter(stack, 'p2', { + type: 'Number', + description: 'number parameter', + }); + + const inspect = Template.fromStack(stack); + const result = inspect.findParameters('p1', { Type: 'String' }); + expect(result).toEqual({ + p1: { + Description: 'string parameter', + Type: 'String', + }, + }); + }); + + test('not matching specific parameter name', () => { + const stack = new Stack(); + new CfnParameter(stack, 'p1', { + type: 'String', + description: 'string parameter', + }); + new CfnParameter(stack, 'p2', { + type: 'Number', + description: 'number parameter', + }); + + const inspect = Template.fromStack(stack); + const result = inspect.findParameters('p3', { Type: 'String' }); + expect(Object.keys(result).length).toEqual(0); + }); + }); + + describe('hasParameter', () => { + test('matching', () => { + const stack = new Stack(); + new CfnParameter(stack, 'p1', { + type: 'String', + description: 'string parameter', + }); + new CfnParameter(stack, 'p2', { + type: 'Number', + description: 'number parameter', + }); + + const inspect = Template.fromStack(stack); + expect(() => inspect.findParameters('p3', { Type: 'String' })).not.toThrow(); + }); + + test('not matching', (done) => { + const stack = new Stack(); + new CfnParameter(stack, 'p1', { + type: 'String', + description: 'string parameter', + }); + new CfnParameter(stack, 'p2', { + type: 'Number', + description: 'number parameter', + }); + + const inspect = Template.fromStack(stack); + expectToThrow( + () => inspect.hasParameter('*', { Type: 'CommaDelimitedList' }), + [ + /2 parameters/, + /Expected CommaDelimitedList but received String/, + ], + done, + ); + done(); + }); + + test('matching specific parameter name', () => { + const stack = new Stack(); + new CfnParameter(stack, 'p1', { + type: 'String', + description: 'string parameter', + }); + new CfnParameter(stack, 'p2', { + type: 'Number', + description: 'number parameter', + }); + + const inspect = Template.fromStack(stack); + expect(() => inspect.findParameters('p1', { Type: 'String' })).not.toThrow(); + }); + + test('not matching specific parameter name', (done) => { + const stack = new Stack(); + new CfnParameter(stack, 'p1', { + type: 'String', + description: 'string parameter', + }); + new CfnParameter(stack, 'p2', { + type: 'Number', + description: 'number parameter', + }); + + const inspect = Template.fromStack(stack); + expectToThrow( + () => inspect.hasParameter('p2', { Type: 'CommaDelimitedList' }), + [ + /1 parameter/, + /Expected CommaDelimitedList but received Number/, + ], + done, + ); + done(); + }); + }); + describe('findMappings', () => { test('matching', () => { const stack = new Stack();