Skip to content

Commit

Permalink
feat(assert): add countResourcesLike method (#6168)
Browse files Browse the repository at this point in the history
* feat(assert): add countResourcesLike method

Adds a method to get a count of Stack resources filtered by Type (i.e. `"AWS::ApiGateway::Method"`) as well as properties (i.e. `{ resourceId: "MyResource01234" }`)

* feat(assert): add countResourcesLike method

Adds documentation for `countResources` and `countResourcesLike` methods

* Update README.md

Co-authored-by: Elad Ben-Israel <benisrae@amazon.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Feb 10, 2020
1 parent b4e45fe commit 491e2d9
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 5 deletions.
20 changes: 20 additions & 0 deletions packages/@aws-cdk/assert/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,26 @@ expect(stack).to(haveResource('AWS::CertificateManager::Certificate', {

`ABSENT` is a magic value to assert that a particular key in an object is *not* set (or set to `undefined`).

### Check number of resources

If you want to assert that `n` number of resources of a particular type exist, with or without specific properties:

```ts
countResources(type, count)
countResourcesLike(type, count, props)
```

Example:

```ts
expect(stack).to(countResources('AWS::ApiGateway::Method', 3));
expect(stack).to(countResourcesLike('AWS::ApiGateway::Method', 1, {
HttpMethod: 'GET',
ResourceId: {
"Ref": "MyResource01234"
}
}));
```

### Check existence of an output
`haveOutput` assertion can be used to check that a stack contains specific output.
Expand Down
28 changes: 24 additions & 4 deletions packages/@aws-cdk/assert/lib/assertions/count-resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,48 @@ export function countResources(resourceType: string, count = 1): Assertion<Stack
return new CountResourcesAssertion(resourceType, count);
}

/**
* An assertion to check whether a resource of a given type and with the given properties exists, considering properties
*/
export function countResourcesLike(resourceType: string, count = 1, props: any): Assertion<StackInspector> {
return new CountResourcesAssertion(resourceType, count, props);
}

class CountResourcesAssertion extends Assertion<StackInspector> {
private inspected: number = 0;
private readonly props: any;

constructor(private readonly resourceType: string,
private readonly count: number) {
private readonly count: number,
props: any = null) {
super();
this.props = props;
}

public assertUsing(inspector: StackInspector): boolean {
let counted = 0;
for (const logicalId of Object.keys(inspector.value.Resources || {})) {
const resource = inspector.value.Resources[logicalId];
if (resource.Type === this.resourceType) {
counted++;
this.inspected += 1;
if (this.props) {
const propEntries = Object.entries(this.props);
propEntries.forEach(([key, val]) => {
if (resource.Properties && resource.Properties[key] && JSON.stringify(resource.Properties[key]) === JSON.stringify(val)) {
counted++;
this.inspected += 1;
}
});
} else {
counted++;
this.inspected += 1;
}
}
}

return counted === this.count;
}

public get description(): string {
return `stack only has ${this.inspected} resource of type ${this.resourceType} but we expected to find ${this.count}`;
return `stack only has ${this.inspected} resource of type ${this.resourceType}${this.props ? ' with specified properties' : ''} but we expected to find ${this.count}`;
}
}
71 changes: 70 additions & 1 deletion packages/@aws-cdk/assert/test/assertions.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as cdk from '@aws-cdk/core';
import * as cx from '@aws-cdk/cx-api';

import { countResources, exist, expect as cdkExpect, haveType, MatchStyle, matchTemplate } from '../lib/index';
import { countResources, countResourcesLike, exist, expect as cdkExpect, haveType, MatchStyle, matchTemplate } from '../lib/index';

passingExample('expect <synthStack> at <some path> to have <some type>', () => {
const resourceType = 'Test::Resource';
Expand Down Expand Up @@ -237,6 +237,75 @@ failingExample('expect <synthStack> to count resources - less than expected', ()
cdkExpect(synthStack).to(countResources(resourceType, 0));
});

// countResourcesLike

passingExample('expect <synthStack> to count resources like props - as expected', () => {
const synthStack = synthesizedStack(stack => {
new TestResource(stack, 'R1', { type: 'Bar', properties: { parentId: 123, name: "A" } });
new TestResource(stack, 'R2', { type: 'Bar', properties: { parentId: 123, name: "B" } });
new TestResource(stack, 'R3', { type: 'Foo', properties: { parentId: 123 } });
});

cdkExpect(synthStack).to(countResourcesLike('Bar', 2, { parentId: 123 }));
cdkExpect(synthStack).to(countResourcesLike('Foo', 1, { parentId: 123 }));
});

passingExample('expect <stack> to count resources like props - expected no resources', () => {
const resourceType = 'Test::Resource';
const stack = new cdk.Stack();
cdkExpect(stack).to(countResourcesLike(resourceType, 0, { parentId: 123 }));
});

passingExample('expect <stack> to count resources like props - expected no resources', () => {
const resourceType = 'Test::Resource';
const synthStack = synthesizedStack(stack => {
new TestResource(stack, 'R1', { type: resourceType, properties: { parentId: 123, name: "A" } });
new TestResource(stack, 'R2', { type: resourceType });
new TestResource(stack, 'R3', { type: 'Foo', properties: { parentId: 456} });
});
cdkExpect(synthStack).to(countResourcesLike(resourceType, 0, { parentId: 456 }));
});

failingExample('expect <synthStack> to count resources like props - more than expected', () => {
const resourceType = 'Test::Resource';
const synthStack = synthesizedStack(stack => {
new TestResource(stack, 'R1', { type: resourceType, properties: { parentId: 123 } });
new TestResource(stack, 'R2', { type: resourceType, properties: { parentId: 123 } });
});

cdkExpect(synthStack).to(countResourcesLike(resourceType, 1, { parentId: 123 }));
});

failingExample('expect <synthStack> to count resources like props - nested props out of order', () => {
const resourceType = 'Test::Resource';
const synthStack = synthesizedStack(stack => {
new TestResource(stack, 'R1', { type: resourceType, properties: { id: 987, parentInfo: { id: 123, name: "A" } } });
new TestResource(stack, 'R2', { type: resourceType, properties: { id: 456, parentInfo: { name: "A", id: 123 } } });
});

cdkExpect(synthStack).to(countResourcesLike(resourceType, 2, { parentInfo: { id: 123, name: "A" } }));
});

failingExample('expect <synthStack> to count resources like props - nested props incomplete', () => {
const resourceType = 'Test::Resource';
const synthStack = synthesizedStack(stack => {
new TestResource(stack, 'R1', { type: resourceType, properties: { id: 987, parentInfo: { id: 123, name: "A" } } });
new TestResource(stack, 'R2', { type: resourceType, properties: { id: 456, parentInfo: { name: "A", id: 123 } } });
});

cdkExpect(synthStack).to(countResourcesLike(resourceType, 2, { parentInfo: { id: 123 } }));
});

failingExample('expect <synthStack> to count resources like props - less than expected', () => {
const resourceType = 'Test::Resource';
const synthStack = synthesizedStack(stack => {
new TestResource(stack, 'R1', { type: resourceType, properties: { parentId: 123 } });
new TestResource(stack, 'R2', { type: resourceType, properties: { parentId: 123 } });
});

cdkExpect(synthStack).to(countResourcesLike(resourceType, 0, { parentId: 123 }));
});

function passingExample(title: string, cb: () => void) {
describe('passing', () => {
test(title, cb);
Expand Down

0 comments on commit 491e2d9

Please sign in to comment.