Skip to content

Commit 55ff1b2

Browse files
authored
feat(assertions): support for conditions (#18577)
Add conditions matcher to assertion package. Required by #18560. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 1393729 commit 55ff1b2

File tree

5 files changed

+207
-4
lines changed

5 files changed

+207
-4
lines changed

Diff for: packages/@aws-cdk/assertions/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ expect(result.Foo).toEqual({ Value: 'Fred', Description: 'FooFred' });
139139
expect(result.Bar).toEqual({ Value: 'Fred', Description: 'BarFred' });
140140
```
141141

142-
The APIs `hasMapping()` and `findMappings()` provide similar functionalities.
142+
The APIs `hasMapping()`, `findMappings()`, `hasCondition()`, and `hasCondtions()` provide similar functionalities.
143143

144144
## Special Matchers
145145

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { filterLogicalId, formatFailure, matchSection } from './section';
2+
import { Template } from './template';
3+
4+
export function findConditions(template: Template, logicalId: string, props: any = {}): { [key: string]: { [key: string]: any } } {
5+
const section: { [key: string] : {} } = template.Conditions;
6+
const result = matchSection(filterLogicalId(section, logicalId), props);
7+
8+
if (!result.match) {
9+
return {};
10+
}
11+
12+
return result.matches;
13+
}
14+
15+
export function hasCondition(template: Template, logicalId: string, props: any): string | void {
16+
const section: { [key: string] : {} } = template.Conditions;
17+
const result = matchSection(filterLogicalId(section, logicalId), props);
18+
if (result.match) {
19+
return;
20+
}
21+
22+
if (result.closestResult === undefined) {
23+
return 'No conditions found in the template';
24+
}
25+
26+
return [
27+
`Template has ${result.analyzedCount} conditions, but none match as expected.`,
28+
formatFailure(result.closestResult),
29+
].join('\n');
30+
}

Diff for: packages/@aws-cdk/assertions/lib/private/template.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ export type Template = {
44
Resources: { [logicalId: string]: Resource },
55
Outputs: { [logicalId: string]: Output },
66
Mappings: { [logicalId: string]: Mapping },
7-
Parameters: { [logicalId: string]: Parameter }
7+
Parameters: { [logicalId: string]: Parameter },
8+
Conditions: { [logicalId: string]: Condition },
89
}
910

1011
export type Resource = {
@@ -19,4 +20,6 @@ export type Mapping = { [key: string]: any };
1920
export type Parameter = {
2021
Type: string;
2122
[key: string]: any;
22-
}
23+
}
24+
25+
export type Condition = { [key: string]: any };

Diff for: packages/@aws-cdk/assertions/lib/template.ts

+26
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Stack, Stage } from '@aws-cdk/core';
33
import * as fs from 'fs-extra';
44
import { Match } from './match';
55
import { Matcher } from './matcher';
6+
import { findConditions, hasCondition } from './private/conditions';
67
import { findMappings, hasMapping } from './private/mappings';
78
import { findOutputs, hasOutput } from './private/outputs';
89
import { findParameters, hasParameter } from './private/parameters';
@@ -183,6 +184,31 @@ export class Template {
183184
return findMappings(this.template, logicalId, props);
184185
}
185186

187+
/**
188+
* Assert that a Condition with the given properties exists in the CloudFormation template.
189+
* By default, performs partial matching on the resource, via the `Match.objectLike()`.
190+
* To configure different behavour, use other matchers in the `Match` class.
191+
* @param logicalId the name of the mapping. Provide `'*'` to match all conditions in the template.
192+
* @param props the output as should be expected in the template.
193+
*/
194+
public hasCondition(logicalId: string, props: any): void {
195+
const matchError = hasCondition(this.template, logicalId, props);
196+
if (matchError) {
197+
throw new Error(matchError);
198+
}
199+
}
200+
201+
/**
202+
* Get the set of matching Conditions that match the given properties in the CloudFormation template.
203+
* @param logicalId the name of the condition. Provide `'*'` to match all conditions in the template.
204+
* @param props by default, matches all Conditions in the template.
205+
* When a literal object is provided, performs a partial match via `Match.objectLike()`.
206+
* Use the `Match` APIs to configure a different behaviour.
207+
*/
208+
public findConditions(logicalId: string, props: any = {}): { [key: string]: { [key: string]: any } } {
209+
return findConditions(this.template, logicalId, props);
210+
}
211+
186212
/**
187213
* Assert that the CloudFormation template matches the given value
188214
* @param expected the expected CloudFormation template as key-value pairs.

Diff for: packages/@aws-cdk/assertions/test/template.test.ts

+145-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { App, CfnMapping, CfnOutput, CfnParameter, CfnResource, NestedStack, Stack } from '@aws-cdk/core';
1+
import { App, CfnCondition, CfnMapping, CfnOutput, CfnParameter, CfnResource, Fn, NestedStack, Stack } from '@aws-cdk/core';
22
import { Construct } from 'constructs';
33
import { Capture, Match, Template } from '../lib';
44

@@ -940,6 +940,150 @@ describe('Template', () => {
940940
expect(Object.keys(result).length).toEqual(0);
941941
});
942942
});
943+
944+
describe('hasCondition', () => {
945+
test('matching', () => {
946+
const stack = new Stack();
947+
new CfnCondition(stack, 'Foo', {
948+
expression: Fn.conditionEquals('Bar', 'Baz'),
949+
});
950+
951+
const inspect = Template.fromStack(stack);
952+
expect(() => inspect.hasCondition('*', { 'Fn::Equals': ['Bar', 'Baz'] })).not.toThrow();
953+
});
954+
955+
test('not matching', (done) => {
956+
const stack = new Stack();
957+
new CfnCondition(stack, 'Foo', {
958+
expression: Fn.conditionEquals('Bar', 'Baz'),
959+
});
960+
961+
new CfnCondition(stack, 'Qux', {
962+
expression: Fn.conditionNot(Fn.conditionEquals('Quux', 'Quuz')),
963+
});
964+
965+
const inspect = Template.fromStack(stack);
966+
expectToThrow(
967+
() => inspect.hasCondition('*', {
968+
'Fn::Equals': ['Baz', 'Bar'],
969+
}),
970+
[
971+
/2 conditions/,
972+
/Missing key/,
973+
],
974+
done,
975+
);
976+
done();
977+
});
978+
979+
test('matching specific outputName', () => {
980+
const stack = new Stack();
981+
new CfnCondition(stack, 'Foo', {
982+
expression: Fn.conditionEquals('Bar', 'Baz'),
983+
});
984+
985+
const inspect = Template.fromStack(stack);
986+
expect(() => inspect.hasCondition('Foo', { 'Fn::Equals': ['Bar', 'Baz'] })).not.toThrow();
987+
});
988+
989+
test('not matching specific outputName', (done) => {
990+
const stack = new Stack();
991+
new CfnCondition(stack, 'Foo', {
992+
expression: Fn.conditionEquals('Baz', 'Bar'),
993+
});
994+
995+
const inspect = Template.fromStack(stack);
996+
expectToThrow(
997+
() => inspect.hasCondition('Foo', {
998+
'Fn::Equals': ['Bar', 'Baz'],
999+
}),
1000+
[
1001+
/1 conditions/,
1002+
/Expected Baz but received Bar/,
1003+
],
1004+
done,
1005+
);
1006+
done();
1007+
});
1008+
});
1009+
1010+
describe('findConditions', () => {
1011+
test('matching', () => {
1012+
const stack = new Stack();
1013+
new CfnCondition(stack, 'Foo', {
1014+
expression: Fn.conditionEquals('Bar', 'Baz'),
1015+
});
1016+
1017+
new CfnCondition(stack, 'Qux', {
1018+
expression: Fn.conditionNot(Fn.conditionEquals('Quux', 'Quuz')),
1019+
});
1020+
1021+
const inspect = Template.fromStack(stack);
1022+
const firstCondition = inspect.findConditions('Foo');
1023+
expect(firstCondition).toEqual({
1024+
Foo: {
1025+
'Fn::Equals': [
1026+
'Bar',
1027+
'Baz',
1028+
],
1029+
},
1030+
});
1031+
1032+
const secondCondition = inspect.findConditions('Qux');
1033+
expect(secondCondition).toEqual({
1034+
Qux: {
1035+
'Fn::Not': [
1036+
{
1037+
'Fn::Equals': [
1038+
'Quux',
1039+
'Quuz',
1040+
],
1041+
},
1042+
],
1043+
},
1044+
});
1045+
});
1046+
1047+
test('not matching', () => {
1048+
const stack = new Stack();
1049+
new CfnCondition(stack, 'Foo', {
1050+
expression: Fn.conditionEquals('Bar', 'Baz'),
1051+
});
1052+
1053+
const inspect = Template.fromStack(stack);
1054+
const result = inspect.findMappings('Bar');
1055+
expect(Object.keys(result).length).toEqual(0);
1056+
});
1057+
1058+
test('matching with specific outputName', () => {
1059+
const stack = new Stack();
1060+
new CfnCondition(stack, 'Foo', {
1061+
expression: Fn.conditionEquals('Bar', 'Baz'),
1062+
});
1063+
1064+
const inspect = Template.fromStack(stack);
1065+
const result = inspect.findConditions('Foo', { 'Fn::Equals': ['Bar', 'Baz'] });
1066+
expect(result).toEqual({
1067+
Foo: {
1068+
'Fn::Equals': [
1069+
'Bar',
1070+
'Baz',
1071+
],
1072+
},
1073+
});
1074+
});
1075+
1076+
test('not matching specific output name', () => {
1077+
const stack = new Stack();
1078+
new CfnCondition(stack, 'Foo', {
1079+
expression: Fn.conditionEquals('Bar', 'Baz'),
1080+
});
1081+
1082+
const inspect = Template.fromStack(stack);
1083+
const result = inspect.findConditions('Foo', { 'Fn::Equals': ['Bar', 'Qux'] });
1084+
expect(Object.keys(result).length).toEqual(0);
1085+
});
1086+
});
9431087
});
9441088

9451089
function expectToThrow(fn: () => void, msgs: (RegExp | string)[], done: jest.DoneCallback): void {

0 commit comments

Comments
 (0)