Skip to content

Commit

Permalink
feat(assertions): stringLikeRegexp() matcher (#18491)
Browse files Browse the repository at this point in the history
The logic was partly copied from the function with the same name from `assert-internal`, but the implementation was changed to use [minimatch](https://github.com/isaacs/minimatch). This way, we can have a well-known and stable contract for the matcher.

Closes #18051.

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
otaviomacedo authored Jan 20, 2022
1 parent 006168f commit b49b002
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 1 deletion.
2 changes: 1 addition & 1 deletion packages/@aws-cdk/assertions/NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,4 @@ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

----------------
----------------
29 changes: 29 additions & 0 deletions packages/@aws-cdk/assertions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,35 @@ target array. Out of order will be recorded as a match failure.
Alternatively, the `Match.arrayEquals()` API can be used to assert that the target is
exactly equal to the pattern array.

### String Matchers

The `Match.stringLikeRegexp()` API can be used to assert that the target matches the
provided regular expression.

```ts
// Given a template -
// {
// "Resources": {
// "MyBar": {
// "Type": "Foo::Bar",
// "Properties": {
// "Template": "const includeHeaders = true;"
// }
// }
// }
// }

// The following will NOT throw an assertion error
template.hasResourceProperties('Foo::Bar', {
Template: Match.stringLikeRegexp('includeHeaders = (true|false)'),
});

// The following will throw an assertion error
template.hasResourceProperties('Foo::Bar', {
Template: Match.stringLikeRegexp('includeHeaders = null'),
});
```

### Not Matcher

The not matcher inverts the search pattern and matches all patterns in the path that does
Expand Down
41 changes: 41 additions & 0 deletions packages/@aws-cdk/assertions/lib/match.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ export abstract class Match {
public static anyValue(): Matcher {
return new AnyMatch('anyValue');
}

/**
* Matches targets according to a regular expression
*/
public static stringLikeRegexp(pattern: string): Matcher {
return new StringLikeRegexpMatch('stringLikeRegexp', pattern);
}
}

/**
Expand Down Expand Up @@ -390,3 +397,37 @@ class AnyMatch extends Matcher {
return result;
}
}

class StringLikeRegexpMatch extends Matcher {
constructor(
public readonly name: string,
private readonly pattern: string) {

super();
}

test(actual: any): MatchResult {
const result = new MatchResult(actual);

const regex = new RegExp(this.pattern, 'gm');

if (typeof actual !== 'string') {
result.recordFailure({
matcher: this,
path: [],
message: `Expected a string, but got '${typeof actual}'`,
});
}

if (!regex.test(actual)) {
result.recordFailure({
matcher: this,
path: [],
message: `String '${actual}' did not match pattern '${this.pattern}'`,
});
}

return result;
}

}
1 change: 1 addition & 0 deletions packages/@aws-cdk/assertions/lib/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ function toTemplate(stack: Stack): any {
if (!Stage.isStage(root)) {
throw new Error('unexpected: all stacks must be part of a Stage or an App');
}

const assembly = root.synth();
if (stack.nestedStackParent) {
// if this is a nested stack (it has a parent), then just read the template as a string
Expand Down
16 changes: 16 additions & 0 deletions packages/@aws-cdk/assertions/test/match.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,22 @@ describe('Matchers', () => {
expectPass(matcher, {});
});
});

describe('stringLikeRegexp', () => {
let matcher: Matcher;

test('simple', () => {
matcher = Match.stringLikeRegexp('.*includeHeaders = true.*');
expectFailure(matcher, 'const includeHeaders = false;', [/did not match pattern/]);
expectPass(matcher, 'const includeHeaders = true;');
});

test('nested in object', () => {
matcher = Match.objectLike({ foo: Match.stringLikeRegexp('.*includeHeaders = true.*') });
expectFailure(matcher, { foo: 'const includeHeaders = false;' }, [/did not match pattern/]);
expectPass(matcher, { foo: 'const includeHeaders = true;' });
});
});
});

function expectPass(matcher: Matcher, target: any): void {
Expand Down

0 comments on commit b49b002

Please sign in to comment.