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..631c9f7137dc4 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, hasParameter } from './private/parameters'; import { countResources, findResources, hasResource, hasResourceProperties } from './private/resources'; import { Template as TemplateType } from './private/template'; @@ -108,6 +109,30 @@ 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. + * @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()`. 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();