-
Notifications
You must be signed in to change notification settings - Fork 4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(assertions): add stack tagging assertions
Adds a Tag class to the assertions library that permits assertions against tags on synthesized CDK stacks.
- Loading branch information
1 parent
e5959a0
commit 76744ae
Showing
5 changed files
with
201 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { Match } from './match'; | ||
import { Matcher } from './matcher'; | ||
import { Stack, Stage } from '../../core'; | ||
|
||
type ManifestTags = { [key: string]: string }; | ||
|
||
/** | ||
* Allows assertions on the tags associated with a synthesized CDK stack's | ||
* manifest. Stack tags are not part of the synthesized template, so can only be | ||
* checked from the manifest in this manner. | ||
*/ | ||
export class Tags { | ||
/** | ||
* Find tags associated with a synthesized CDK `Stack`. | ||
* | ||
* @param stack the CDK Stack to find tags on. | ||
*/ | ||
public static fromStack(stack: Stack): Tags { | ||
return new Tags(getManifestTags(stack)); | ||
} | ||
|
||
private readonly _tags: ManifestTags; | ||
|
||
private constructor(tags: ManifestTags) { | ||
this._tags = tags; | ||
} | ||
|
||
/** | ||
* Assert that the given Matcher or object matches the tags associated with | ||
* the synthesized CDK Stack's manifest. | ||
* | ||
* @param tags the expected set of tags. This should be a | ||
* string or Matcher object. | ||
*/ | ||
public hasValues(tags: any): void { | ||
const matcher = Matcher.isMatcher(tags) ? tags : Match.objectLike(tags); | ||
|
||
const result = matcher.test(this.all()); | ||
if (result.hasFailed()) { | ||
throw new Error( | ||
'Stack tags did not match as expected:\n' + result.renderMismatch(), | ||
); | ||
} | ||
} | ||
|
||
/** | ||
* Get the tags associated with the manifest. This will be an empty object if | ||
* no tags were supplied. | ||
* | ||
* @returns The tags associated with the stack's synthesized manifest. | ||
*/ | ||
public all(): ManifestTags { | ||
return this._tags; | ||
} | ||
} | ||
|
||
function getManifestTags(stack: Stack): ManifestTags { | ||
const root = stack.node.root; | ||
if (!Stage.isStage(root)) { | ||
throw new Error('unexpected: all stacks must be part of a Stage or an App'); | ||
} | ||
|
||
// synthesis is not forced: the stack will only be synthesized once regardless | ||
// of the number of times this is called. | ||
const assembly = root.synth(); | ||
|
||
const artifact = assembly.getStackArtifact(stack.artifactId); | ||
return artifact.tags; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import { IConstruct } from 'constructs'; | ||
import { | ||
Annotations, | ||
App, | ||
Aspects, | ||
CfnResource, | ||
IAspect, | ||
Stack, | ||
} from '../../core'; | ||
import { Tags as _Tags, Match, Tags } from '../lib'; | ||
|
||
describe('Tags', () => { | ||
let app: App; | ||
|
||
beforeEach(() => { | ||
app = new App(); | ||
}); | ||
|
||
describe('hasValues', () => { | ||
test('simple match', () => { | ||
const stack = new Stack(app, 'stack', { | ||
tags: { 'tag-one': 'tag-one-value' }, | ||
}); | ||
const tags = Tags.fromStack(stack); | ||
tags.hasValues({ | ||
'tag-one': 'tag-one-value', | ||
}); | ||
}); | ||
|
||
test('with matchers', () => { | ||
const stack = new Stack(app, 'stack', { | ||
tags: { 'tag-one': 'tag-one-value' }, | ||
}); | ||
const tags = Tags.fromStack(stack); | ||
tags.hasValues({ | ||
'tag-one': Match.anyValue(), | ||
}); | ||
}); | ||
|
||
test('no tags with absent matcher will fail', () => { | ||
const stack = new Stack(app, 'stack'); | ||
const tags = Tags.fromStack(stack); | ||
|
||
expect(() => tags.hasValues(Match.absent())).toThrow( | ||
/Received \[object Object], but key should be absent/, | ||
); | ||
}); | ||
|
||
test('no tags matches empty object successfully', () => { | ||
const stack = new Stack(app, 'stack'); | ||
const tags = Tags.fromStack(stack); | ||
|
||
tags.hasValues(Match.objectEquals({})); | ||
}); | ||
|
||
test('no match', () => { | ||
const stack = new Stack(app, 'stack', { | ||
tags: { 'tag-one': 'tag-one-value' }, | ||
}); | ||
const tags = Tags.fromStack(stack); | ||
|
||
expect(() => | ||
tags.hasValues({ | ||
'tag-one': 'mismatched value', | ||
}), | ||
).toThrow(/Expected mismatched value but received tag-one-value/); | ||
}); | ||
}); | ||
|
||
describe('all', () => { | ||
test('simple match', () => { | ||
const stack = new Stack(app, 'stack', { | ||
tags: { 'tag-one': 'tag-one-value' }, | ||
}); | ||
const tags = Tags.fromStack(stack); | ||
expect(tags.all()).toStrictEqual({ | ||
'tag-one': 'tag-one-value', | ||
}); | ||
}); | ||
|
||
test('no tags', () => { | ||
const stack = new Stack(app, 'stack'); | ||
const tags = Tags.fromStack(stack); | ||
|
||
expect(tags.all()).toStrictEqual({}); | ||
}); | ||
|
||
test('empty tags', () => { | ||
const stack = new Stack(app, 'stack', { tags: {} }); | ||
const tags = Tags.fromStack(stack); | ||
|
||
expect(tags.all()).toStrictEqual({}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters