-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdata-plane-stack.test.ts
More file actions
87 lines (78 loc) · 3.24 KB
/
Copy pathdata-plane-stack.test.ts
File metadata and controls
87 lines (78 loc) · 3.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import { App } from 'aws-cdk-lib';
import { Capture, Match, Template } from 'aws-cdk-lib/assertions';
import { DataPlaneStack } from '../lib/data-plane-stack';
import { EnvName } from '../lib/env';
function synth(envName: EnvName): Template {
const app = new App();
const stack = new DataPlaneStack(app, `Test-${envName}`, { envName });
return Template.fromStack(stack);
}
describe('DataPlaneStack', () => {
it('creates exactly two DynamoDB tables', () => {
synth('dev').resourceCountIs('AWS::DynamoDB::Table', 2);
});
it('keys the tenant table on userId/sk, matching entities.scoped_key', () => {
const template = synth('dev');
template.hasResourceProperties('AWS::DynamoDB::Table', {
KeySchema: Match.arrayWith([
{ AttributeName: 'userId', KeyType: 'HASH' },
{ AttributeName: 'sk', KeyType: 'RANGE' },
]),
BillingMode: 'PAY_PER_REQUEST',
});
});
it('gives the due-work table a date-bucketed GSI for the cross-tenant due-before scan', () => {
const template = synth('dev');
template.hasResourceProperties('AWS::DynamoDB::Table', {
GlobalSecondaryIndexes: Match.arrayWith([
Match.objectLike({
IndexName: 'DueIndex',
KeySchema: [
{ AttributeName: 'duePartition', KeyType: 'HASH' },
{ AttributeName: 'nextDueAt', KeyType: 'RANGE' },
],
}),
]),
});
});
it('enables point-in-time recovery and server-side encryption on every table', () => {
const template = synth('dev');
const tables = template.findResources('AWS::DynamoDB::Table');
const found = Object.values(tables);
expect(found).toHaveLength(2);
for (const resource of found) {
expect(resource.Properties.PointInTimeRecoverySpecification).toEqual({
PointInTimeRecoveryEnabled: true,
});
expect(resource.Properties.SSESpecification).toMatchObject({ SSEEnabled: true });
}
});
it('retains tables in prod but destroys them in dev/stage', () => {
for (const envName of ['dev', 'stage'] as const) {
const tables = Object.values(synth(envName).findResources('AWS::DynamoDB::Table'));
for (const resource of tables) {
expect(resource.DeletionPolicy).toBe('Delete');
}
}
const prodTables = Object.values(synth('prod').findResources('AWS::DynamoDB::Table'));
for (const resource of prodTables) {
expect(resource.DeletionPolicy).toBe('Retain');
}
});
it('tags every resource with the environment name, never a literal account/domain', () => {
const template = synth('stage');
const json = JSON.stringify(template.toJSON());
// Roadmap item A8's exact failure mode: a hardcoded 12-digit AWS account ID baked into the
// CDK stack. Nothing in this synthesized template may contain one.
expect(json).not.toMatch(/\b\d{12}\b/);
template.hasResourceProperties('AWS::DynamoDB::Table', {
Tags: Match.arrayWith([{ Key: 'openjobradar:env', Value: 'stage' }]),
});
});
it('names outputs for both table names so a future compute stack can import them', () => {
const template = synth('dev');
const capture = new Capture();
template.hasOutput('TenantTableName', { Value: capture });
template.hasOutput('DueWorkTableName', Match.anyValue());
});
});