Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(assertions): matcher support for templateMatches() API #16789

Merged
merged 3 commits into from
Oct 5, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions packages/@aws-cdk/assertions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,9 @@ The APIs `hasMapping()` and `findMappings()` provide similar functionalities.

## Special Matchers
kaizencc marked this conversation as resolved.
Show resolved Hide resolved

The expectation provided to the `hasXXX()` and `findXXX()` methods, besides
carrying literal values, as seen in the above examples, also accept special
matchers.
The expectation provided to the `hasXxx()`, `findXxx()` and `templateMatches()`
APIs, besides carrying literal values, as seen in the above examples, also accept
special matchers.

They are available as part of the `Match` class.

Expand Down
3 changes: 2 additions & 1 deletion packages/@aws-cdk/assertions/lib/match.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Matcher, MatchResult } from './matcher';
import { getType } from './private/type';
import { ABSENT } from './vendored/assert';

const ABSENT = '{{ABSENT}}';

/**
* Partial and special matching during template assertions.
Expand Down
10 changes: 5 additions & 5 deletions packages/@aws-cdk/assertions/lib/private/mappings.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { StackInspector } from '../vendored/assert';
import { filterLogicalId, formatFailure, matchSection } from './section';
import { Template } from './template';

export function findMappings(inspector: StackInspector, logicalId: string, props: any = {}): { [key: string]: { [key: string]: any } } {
const section: { [key: string] : {} } = inspector.value.Mappings;
export function findMappings(template: Template, logicalId: string, props: any = {}): { [key: string]: { [key: string]: any } } {
const section: { [key: string] : {} } = template.Mappings;
const result = matchSection(filterLogicalId(section, logicalId), props);

if (!result.match) {
Expand All @@ -12,8 +12,8 @@ export function findMappings(inspector: StackInspector, logicalId: string, props
return result.matches;
}

export function hasMapping(inspector: StackInspector, logicalId: string, props: any): string | void {
const section: { [key: string]: {} } = inspector.value.Mappings;
export function hasMapping(template: Template, logicalId: string, props: any): string | void {
const section: { [key: string]: {} } = template.Mappings;
const result = matchSection(filterLogicalId(section, logicalId), props);

if (result.match) {
Expand Down
10 changes: 5 additions & 5 deletions packages/@aws-cdk/assertions/lib/private/outputs.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { StackInspector } from '../vendored/assert';
import { filterLogicalId, formatFailure, matchSection } from './section';
import { Template } from './template';

export function findOutputs(inspector: StackInspector, logicalId: string, props: any = {}): { [key: string]: { [key: string]: any } } {
const section: { [key: string] : {} } = inspector.value.Outputs;
export function findOutputs(template: Template, logicalId: string, props: any = {}): { [key: string]: { [key: string]: any } } {
const section = template.Outputs;
const result = matchSection(filterLogicalId(section, logicalId), props);

if (!result.match) {
Expand All @@ -12,8 +12,8 @@ export function findOutputs(inspector: StackInspector, logicalId: string, props:
return result.matches;
}

export function hasOutput(inspector: StackInspector, logicalId: string, props: any): string | void {
const section: { [key: string]: {} } = inspector.value.Outputs;
export function hasOutput(template: Template, logicalId: string, props: any): string | void {
const section: { [key: string]: {} } = template.Outputs;
const result = matchSection(filterLogicalId(section, logicalId), props);
if (result.match) {
return;
Expand Down
22 changes: 12 additions & 10 deletions packages/@aws-cdk/assertions/lib/private/resources.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import { StackInspector } from '../vendored/assert';
import { formatFailure, matchSection } from './section';
import { Resource, Template } from './template';

// Partial type for CloudFormation Resource
type Resource = {
Type: string;
}

export function findResources(inspector: StackInspector, type: string, props: any = {}): { [key: string]: { [key: string]: any } } {
const section: { [key: string] : Resource } = inspector.value.Resources;
export function findResources(template: Template, type: string, props: any = {}): { [key: string]: { [key: string]: any } } {
const section = template.Resources;
const result = matchSection(filterType(section, type), props);

if (!result.match) {
Expand All @@ -17,8 +12,8 @@ export function findResources(inspector: StackInspector, type: string, props: an
return result.matches;
}

export function hasResource(inspector: StackInspector, type: string, props: any): string | void {
const section: { [key: string]: Resource } = inspector.value.Resources;
export function hasResource(template: Template, type: string, props: any): string | void {
const section = template.Resources;
const result = matchSection(filterType(section, type), props);

if (result.match) {
Expand All @@ -35,6 +30,13 @@ export function hasResource(inspector: StackInspector, type: string, props: any)
].join('\n');
}

export function countResources(template: Template, type: string): number {
const section = template.Resources;
const types = filterType(section, type);

return Object.entries(types).length;
}

function filterType(section: { [key: string]: Resource }, type: string): { [key: string]: Resource } {
return Object.entries(section ?? {})
.filter(([_, v]) => v.Type === type)
Expand Down
16 changes: 16 additions & 0 deletions packages/@aws-cdk/assertions/lib/private/template.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Partial types for CloudFormation Template

export type Template = {
Resources: { [logicalId: string]: Resource },
Outputs: { [logicalId: string]: Output },
Mappings: { [logicalId: string]: Mapping }
}

export type Resource = {
Type: string;
[key: string]: any;
}

export type Output = { [key: string]: any };

export type Mapping = { [key: string]: any };
41 changes: 24 additions & 17 deletions packages/@aws-cdk/assertions/lib/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { Match } from './match';
import { Matcher } from './matcher';
import { findMappings, hasMapping } from './private/mappings';
import { findOutputs, hasOutput } from './private/outputs';
import { findResources, hasResource } from './private/resources';
import * as assert from './vendored/assert';
import { countResources, findResources, hasResource } from './private/resources';
import { Template as TemplateType } from './private/template';

/**
* Suite of assertions that can be run on a CDK stack.
Expand Down Expand Up @@ -39,12 +39,10 @@ export class Template {
return new Template(JSON.parse(template));
}

private readonly template: { [key: string]: any };
private readonly inspector: assert.StackInspector;
private readonly template: TemplateType;

private constructor(template: { [key: string]: any }) {
this.template = template;
this.inspector = new assert.StackInspector(template);
this.template = template as TemplateType;
}

/**
Expand All @@ -61,8 +59,10 @@ export class Template {
* @param count number of expected instances
*/
public resourceCountIs(type: string, count: number): void {
const assertion = assert.countResources(type, count);
assertion.assertOrThrow(this.inspector);
const counted = countResources(this.template, type);
if (counted !== count) {
throw new Error(`Expected ${count} resources of type ${type} but found ${counted}`);
}
}

/**
Expand All @@ -88,7 +88,7 @@ export class Template {
* @param props the entire defintion of the resource as should be expected in the template.
*/
public hasResource(type: string, props: any): void {
const matchError = hasResource(this.inspector, type, props);
const matchError = hasResource(this.template, type, props);
if (matchError) {
throw new Error(matchError);
}
Expand All @@ -102,7 +102,7 @@ export class Template {
* Use the `Match` APIs to configure a different behaviour.
*/
public findResources(type: string, props: any = {}): { [key: string]: { [key: string]: any } } {
return findResources(this.inspector, type, props);
return findResources(this.template, type, props);
}

/**
Expand All @@ -113,7 +113,7 @@ export class Template {
* @param props the output as should be expected in the template.
*/
public hasOutput(logicalId: string, props: any): void {
const matchError = hasOutput(this.inspector, logicalId, props);
const matchError = hasOutput(this.template, logicalId, props);
if (matchError) {
throw new Error(matchError);
}
Expand All @@ -127,7 +127,7 @@ export class Template {
* Use the `Match` APIs to configure a different behaviour.
*/
public findOutputs(logicalId: string, props: any = {}): { [key: string]: { [key: string]: any } } {
return findOutputs(this.inspector, logicalId, props);
return findOutputs(this.template, logicalId, props);
}

/**
Expand All @@ -138,7 +138,7 @@ export class Template {
* @param props the output as should be expected in the template.
*/
public hasMapping(logicalId: string, props: any): void {
const matchError = hasMapping(this.inspector, logicalId, props);
const matchError = hasMapping(this.template, logicalId, props);
if (matchError) {
throw new Error(matchError);
}
Expand All @@ -152,16 +152,23 @@ export class Template {
* Use the `Match` APIs to configure a different behaviour.
*/
public findMappings(logicalId: string, props: any = {}): { [key: string]: { [key: string]: any } } {
return findMappings(this.inspector, logicalId, props);
return findMappings(this.template, logicalId, props);
}

/**
* Assert that the CloudFormation template matches the given value
* @param expected the expected CloudFormation template as key-value pairs.
*/
public templateMatches(expected: {[key: string]: any}): void {
const assertion = assert.matchTemplate(expected);
assertion.assertOrThrow(this.inspector);
public templateMatches(expected: any): void {
const matcher = Matcher.isMatcher(expected) ? expected : Match.objectLike(expected);
nija-at marked this conversation as resolved.
Show resolved Hide resolved
const result = matcher.test(this.template);

if (result.hasFailed()) {
throw new Error([
'Template did not match as expected. The following mismatches were found:',
...result.toHumanStrings().map(s => `\t${s}`),
].join('\n'));
}
}
}

Expand Down
26 changes: 0 additions & 26 deletions packages/@aws-cdk/assertions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,6 @@
},
"projectReferences": true
},
"cdk-build": {
"pre": [
"./vendor-in.sh"
]
},
"author": {
"name": "Amazon Web Services",
"url": "https://aws.amazon.com",
Expand All @@ -61,7 +56,6 @@
"license": "Apache-2.0",
"devDependencies": {
"@aws-cdk/cdk-build-tools": "0.0.0",
"@aws-cdk/cfnspec": "0.0.0",
"@aws-cdk/pkglint": "0.0.0",
"@types/jest": "^26.0.24",
"constructs": "^3.3.69",
Expand Down Expand Up @@ -106,26 +100,6 @@
"engines": {
"node": ">= 10.13.0 <13 || >=13.7.0"
},
"nozem": {
"ostools": [
"dirname",
"cd",
"bash",
"rm",
"xargs",
"sed",
"mkdir",
"rsync",
"cat",
"find"
],
"additionalDirs": [
"../cfnspec/lib",
"ARTIFACTS:../cfnspec/spec",
"../cloudformation-diff",
"../assert-internal"
]
},
"stability": "experimental",
"maturity": "experimental",
"publishConfig": {
Expand Down
10 changes: 5 additions & 5 deletions packages/@aws-cdk/assertions/test/template.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,10 @@ describe('Template', () => {
const inspect = Template.fromStack(stack);
inspect.resourceCountIs('Foo::Bar', 1);

expect(() => inspect.resourceCountIs('Foo::Bar', 0)).toThrow(/has 1 resource of type Foo::Bar/);
expect(() => inspect.resourceCountIs('Foo::Bar', 2)).toThrow(/has 1 resource of type Foo::Bar/);
expect(() => inspect.resourceCountIs('Foo::Bar', 0)).toThrow('Expected 0 resources of type Foo::Bar but found 1');
expect(() => inspect.resourceCountIs('Foo::Bar', 2)).toThrow('Expected 2 resources of type Foo::Bar but found 1');

expect(() => inspect.resourceCountIs('Foo::Baz', 1)).toThrow(/has 0 resource of type Foo::Baz/);
expect(() => inspect.resourceCountIs('Foo::Baz', 1)).toThrow('Expected 1 resources of type Foo::Baz but found 0');
});

test('no resource', () => {
Expand All @@ -94,7 +94,7 @@ describe('Template', () => {
const inspect = Template.fromStack(stack);
inspect.resourceCountIs('Foo::Bar', 0);

expect(() => inspect.resourceCountIs('Foo::Bar', 1)).toThrow(/has 0 resource of type Foo::Bar/);
expect(() => inspect.resourceCountIs('Foo::Bar', 1)).toThrow('Expected 1 resources of type Foo::Bar but found 0');
});
});

Expand Down Expand Up @@ -132,7 +132,7 @@ describe('Template', () => {
Properties: { baz: 'waldo' },
},
},
})).toThrowError();
})).toThrowError(/Expected waldo but received qux at \/Resources\/Foo\/Properties\/baz/);
});
});

Expand Down
57 changes: 0 additions & 57 deletions packages/@aws-cdk/assertions/vendor-in.sh

This file was deleted.