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): Add the hasNoXXX methods. #19330

Merged
merged 12 commits into from
Mar 15, 2022
6 changes: 3 additions & 3 deletions packages/@aws-cdk/assertions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -550,9 +550,9 @@ Annotations.fromStack(stack).hasError(

Here are the available APIs for `Annotations`:

- `hasError()` and `findError()`
- `hasWarning()` and `findWarning()`
- `hasInfo()` and `findInfo()`
- `hasError()`, `hasNoError()`, and `findError()`
- `hasWarning()`, `hasNoWarning()`, and `findWarning()`
- `hasInfo()`, `hasNoInfo()`, and `findInfo()`

The corresponding `findXxx()` API is complementary to the `hasXxx()` API, except instead
of asserting its presence, it returns the set of matching messages.
Expand Down
41 changes: 40 additions & 1 deletion packages/@aws-cdk/assertions/lib/annotations.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Stack, Stage } from '@aws-cdk/core';
import { SynthesisMessage } from '@aws-cdk/cx-api';
import { Messages } from './private/message';
import { findMessage, hasMessage } from './private/messages';
import { findMessage, hasMessage, hasNoMessage } from './private/messages';

/**
* Suite of assertions that can be run on a CDK Stack.
Expand Down Expand Up @@ -35,6 +35,19 @@ export class Annotations {
}
}

/**
* Assert that an error with the given message does not exist in the synthesized CDK `Stack`.
*
* @param constructPath the construct path to the error. Provide `'*'` to match all errors in the template.
* @param message the error message as should be expected. This should be a string or Matcher object.
*/
public hasNoError(constructPath: string, message: any): void {
const matchError = hasNoMessage(this._messages, constructPath, constructMessage('error', message));
if (matchError) {
throw new Error(matchError);
}
}

/**
* Get the set of matching errors of a given construct path and message.
*
Expand All @@ -58,6 +71,19 @@ export class Annotations {
}
}

/**
* Assert that an warning with the given message does not exist in the synthesized CDK `Stack`.
*
* @param constructPath the construct path to the warning. Provide `'*'` to match all warnings in the template.
* @param message the warning message as should be expected. This should be a string or Matcher object.
*/
public hasNoWarning(constructPath: string, message: any): void {
const matchError = hasNoMessage(this._messages, constructPath, constructMessage('warning', message));
if (matchError) {
throw new Error(matchError);
}
}

/**
* Get the set of matching warning of a given construct path and message.
*
Expand All @@ -81,6 +107,19 @@ export class Annotations {
}
}

/**
* Assert that an info with the given message does not exist in the synthesized CDK `Stack`.
*
* @param constructPath the construct path to the info. Provide `'*'` to match all info in the template.
* @param message the info message as should be expected. This should be a string or Matcher object.
*/
public hasNoInfo(constructPath: string, message: any): void {
const matchError = hasNoMessage(this._messages, constructPath, constructMessage('info', message));
if (matchError) {
throw new Error(matchError);
}
}

/**
* Get the set of matching infos of a given construct path and message.
*
Expand Down
16 changes: 15 additions & 1 deletion packages/@aws-cdk/assertions/lib/private/messages.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { SynthesisMessage } from '@aws-cdk/cx-api';
import { Messages } from './message';
import { formatFailure, matchSection } from './section';
import { formatAllMatches, formatFailure, matchSection } from './section';

export function findMessage(messages: Messages, constructPath: string, props: any = {}): { [key: string]: { [key: string]: any } } {
const section: { [key: string]: SynthesisMessage } = messages;
Expand Down Expand Up @@ -32,6 +32,20 @@ export function hasMessage(messages: Messages, constructPath: string, props: any
].join('\n');
}

export function hasNoMessage(messages: Messages, constructPath: string, props: any): string | void {
const section: { [key: string]: SynthesisMessage } = messages;
const result = matchSection(filterPath(section, constructPath), props);

if (!result.match) {
return;
}

return [
`Expected no matches, but stack has ${Object.keys(result.matches).length} messages as follows:`,
formatAllMatches(result.matches),
].join('\n');
}

// We redact the stack trace by default because it is unnecessarily long and unintelligible.
// If there is a use case for rendering the trace, we can add it later.
function handleTrace(match: any, redact: boolean = true): void {
Expand Down
6 changes: 6 additions & 0 deletions packages/@aws-cdk/assertions/lib/private/section.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ function eachEntryInSection(
}
}

export function formatAllMatches(matches: {[key: string]: any}): string {
return [
leftPad(JSON.stringify(matches, undefined, 2)),
].join('\n');
}

export function formatFailure(closestResult: MatchResult): string {
return [
'The closest result is:',
Expand Down
33 changes: 33 additions & 0 deletions packages/@aws-cdk/assertions/test/annotations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@ describe('Messages', () => {
});
});

describe('hasNoError', () => {
test('match', () => {
annotations.hasNoError('/Default/Fred', Match.anyValue());
});

test('no match', () => {
expect(() => annotations.hasNoError('/Default/Foo', 'this is an error'))
.toThrowError(/Expected no matches, but stack has 1 messages as follows:/);
});
});

describe('findError', () => {
test('match', () => {
const result = annotations.findError('*', Match.anyValue());
Expand All @@ -72,6 +83,17 @@ describe('Messages', () => {
});
});

describe('hasNoWarning', () => {
test('match', () => {
annotations.hasNoWarning('/Default/Foo', Match.anyValue());
});

test('no match', () => {
expect(() => annotations.hasNoWarning('/Default/Fred', 'this is a warning'))
.toThrowError(/Expected no matches, but stack has 1 messages as follows:/);
});
});

describe('findWarning', () => {
test('match', () => {
const result = annotations.findWarning('*', Match.anyValue());
Expand All @@ -94,6 +116,17 @@ describe('Messages', () => {
});
});

describe('hasNoInfo', () => {
test('match', () => {
annotations.hasNoInfo('/Default/Qux', 'this info is incorrect');
});

test('no match', () => {
expect(() => annotations.hasNoInfo('/Default/Qux', 'this is an info'))
.toThrowError(/Expected no matches, but stack has 1 messages as follows:/);
});
});

describe('findInfo', () => {
test('match', () => {
const result = annotations.findInfo('/Default/Qux', 'this is an info');
Expand Down