Skip to content

Commit

Permalink
feat(assertions): findXxx() APIs now includes the logical id as part …
Browse files Browse the repository at this point in the history
…of its result (#16454)

The current return type of `findResources()`, `findOutputs()`, and `findMappings()` omits the `logicalId`. This creates ambiguity because it is not trivial to figure out which resource/output/mapping is being matched. For example, 

```ts
const result = assert.findOutputs('*', {
  Value: 'Fred',
});
```

Could return a list like:

```ts
expect(result).toEqual([
  { Value: 'Fred', Description: 'FooFred' },
  { Value: 'Fred', Description: 'BarFred' },
]);
```

This does not provide information on which output(s) are being matched to the `Value: 'Fred'` property. Under the new return type proposed in this PR, the output would look like this:

```ts
expect(result).toEqual({
  Foo: { Value: 'Fred', Description: 'FooFred' },
  Bar: { Value: 'Fred', Description: 'BarFred' },
});
```

Returning the `logicalId` in this way for all `find*()` APIs will provide the full picture back to the user.

BREAKING CHANGE: the `findResources()` API previously returned a list of resources, but now returns a map of logical id to resource.
* **assertions:** the `findOutputs()` API previously returned a list of outputs, but now returns a map of logical id to output.
* **assertions:** the `findMappings()` API previously returned a list of mappings, but now returns a map of logical id to mapping.


----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
kaizencc authored Sep 16, 2021
1 parent ae4aa95 commit 532a72b
Show file tree
Hide file tree
Showing 10 changed files with 70 additions and 53 deletions.
10 changes: 9 additions & 1 deletion packages/@aws-cdk/assertions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions packages/@aws-cdk/assertions/lib/private/mappings.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
4 changes: 2 additions & 2 deletions packages/@aws-cdk/assertions/lib/private/outputs.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
4 changes: 2 additions & 2 deletions packages/@aws-cdk/assertions/lib/private/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
15 changes: 7 additions & 8 deletions packages/@aws-cdk/assertions/lib/private/section.ts
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -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 };
Expand All @@ -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);
}
}

Expand Down
6 changes: 3 additions & 3 deletions packages/@aws-cdk/assertions/lib/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand All @@ -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);
}

Expand All @@ -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);
}

Expand Down
4 changes: 3 additions & 1 deletion packages/@aws-cdk/assertions/test/private/section.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
62 changes: 34 additions & 28 deletions packages/@aws-cdk/assertions/test/template.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand All @@ -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', () => {
Expand All @@ -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', () => {
Expand All @@ -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', () => {
Expand All @@ -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' });
});
});

Expand Down Expand Up @@ -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', () => {
Expand All @@ -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', () => {
Expand All @@ -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', () => {
Expand All @@ -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);
});
});

Expand Down Expand Up @@ -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', () => {
Expand All @@ -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', () => {
Expand All @@ -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: {
Expand All @@ -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);
});
});
});
Expand Down
4 changes: 2 additions & 2 deletions packages/@aws-cdk/aws-apigatewayv2/test/http/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
10 changes: 6 additions & 4 deletions packages/@aws-cdk/aws-cloudwatch/test/dashboard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' } },
Expand Down Expand Up @@ -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' } },
Expand Down

0 comments on commit 532a72b

Please sign in to comment.