@aws-blocks/core 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/cdk/blocks-backend.d.ts +4 -1
- package/dist/cdk/blocks-backend.d.ts.map +1 -1
- package/dist/cdk/blocks-backend.js +56 -10
- package/dist/cdk/blocks-backend.test.js +71 -1
- package/dist/cdk/blocks-defaults.d.ts +11 -0
- package/dist/cdk/blocks-defaults.d.ts.map +1 -1
- package/dist/cdk/blocks-stack.test.js +23 -3
- package/dist/cdk/compute/compute.d.ts +80 -2
- package/dist/cdk/compute/compute.d.ts.map +1 -1
- package/dist/cdk/compute/compute.js +57 -3
- package/dist/cdk/config-registry.test.js +12 -0
- package/dist/cdk/dashboard-registry.d.ts +41 -0
- package/dist/cdk/dashboard-registry.d.ts.map +1 -0
- package/dist/cdk/dashboard-registry.js +61 -0
- package/dist/cdk/index.d.ts +67 -11
- package/dist/cdk/index.d.ts.map +1 -1
- package/dist/cdk/index.js +100 -18
- package/dist/cdk/internal.d.ts +3 -1
- package/dist/cdk/internal.d.ts.map +1 -1
- package/dist/cdk/internal.js +4 -1
- package/dist/cdk/tracer-registry.d.ts +31 -0
- package/dist/cdk/tracer-registry.d.ts.map +1 -0
- package/dist/cdk/tracer-registry.js +49 -0
- package/dist/cdk/vpc-requirements-registry.d.ts +33 -0
- package/dist/cdk/vpc-requirements-registry.d.ts.map +1 -0
- package/dist/cdk/vpc-requirements-registry.js +46 -0
- package/dist/cdk/vpc-types.d.ts +151 -0
- package/dist/cdk/vpc-types.d.ts.map +1 -0
- package/dist/cdk/vpc-types.js +3 -0
- package/dist/cdk/vpc.d.ts +59 -0
- package/dist/cdk/vpc.d.ts.map +1 -0
- package/dist/cdk/vpc.js +298 -0
- package/dist/cdk/vpc.test.d.ts +2 -0
- package/dist/cdk/vpc.test.d.ts.map +1 -0
- package/dist/cdk/vpc.test.js +285 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +5 -0
- package/dist/hosting.d.ts.map +1 -1
- package/dist/hosting.js +2 -0
- package/dist/hosting.test.js +39 -1
- package/dist/index.cdk.d.ts +2 -1
- package/dist/index.cdk.d.ts.map +1 -1
- package/dist/index.cdk.js +1 -1
- package/dist/lambda-handler.js +9 -2
- package/dist/lambda-handler.test.js +61 -1
- package/dist/raw-route.d.ts +15 -1
- package/dist/raw-route.d.ts.map +1 -1
- package/dist/raw-route.js +96 -12
- package/dist/raw-route.test.js +332 -1
- package/dist/scripts/dev-server.d.ts.map +1 -1
- package/dist/scripts/dev-server.js +11 -0
- package/dist/scripts/extract-ts-types.d.ts.map +1 -1
- package/dist/scripts/extract-ts-types.js +107 -23
- package/dist/scripts/extract-ts-types.test.js +225 -26
- package/dist/scripts/generate-spec.d.ts.map +1 -1
- package/dist/scripts/generate-spec.js +14 -5
- package/dist/scripts/generate-spec.test.js +93 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +8 -1
- package/src/cdk/blocks-backend.test.ts +144 -60
- package/src/cdk/blocks-backend.ts +298 -239
- package/src/cdk/blocks-defaults.ts +12 -0
- package/src/cdk/blocks-stack.test.ts +32 -13
- package/src/cdk/compute/compute.ts +105 -3
- package/src/cdk/config-registry.test.ts +14 -0
- package/src/cdk/dashboard-registry.ts +68 -0
- package/src/cdk/index.ts +426 -298
- package/src/cdk/internal.ts +6 -2
- package/src/cdk/tracer-registry.ts +54 -0
- package/src/cdk/vpc-requirements-registry.ts +63 -0
- package/src/cdk/vpc-types.ts +158 -0
- package/src/cdk/vpc.test.ts +348 -0
- package/src/cdk/vpc.ts +336 -0
- package/src/errors.ts +5 -0
- package/src/hosting.test.ts +59 -1
- package/src/hosting.ts +3 -0
- package/src/index.cdk.ts +7 -0
- package/src/lambda-handler.test.ts +79 -1
- package/src/lambda-handler.ts +11 -2
- package/src/raw-route.test.ts +427 -1
- package/src/raw-route.ts +125 -12
- package/src/scripts/dev-server.ts +12 -1
- package/src/scripts/extract-ts-types.test.ts +228 -26
- package/src/scripts/extract-ts-types.ts +104 -20
- package/src/scripts/generate-spec.test.ts +101 -0
- package/src/scripts/generate-spec.ts +15 -5
- package/src/version.ts +1 -1
|
@@ -15,6 +15,7 @@ import { join, dirname } from 'path';
|
|
|
15
15
|
import { tmpdir } from 'os';
|
|
16
16
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
17
17
|
import { generateSpec } from './generate-spec.js';
|
|
18
|
+
import { ApiNamespace } from '../api.js';
|
|
18
19
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
19
20
|
// `import.meta.url` lives in dist/scripts/, so `..` is dist/ and the built
|
|
20
21
|
// `api.js` is next to it. Re-using the real ApiNamespace ensures the marker
|
|
@@ -86,3 +87,95 @@ describe('generateSpec — @blocksSkipCodegen', () => {
|
|
|
86
87
|
}
|
|
87
88
|
});
|
|
88
89
|
});
|
|
90
|
+
describe('generateSpec — namespaces sharing a method name (regression: #445)', () => {
|
|
91
|
+
it('emits each namespace its OWN schema for a same-named method', async () => {
|
|
92
|
+
const dir = join(tmpdir(), `blocks-spec-test-${Date.now()}-collision`);
|
|
93
|
+
mkdirSync(dir, { recursive: true });
|
|
94
|
+
writeTsconfig(dir);
|
|
95
|
+
// Typed foundation for the TS type extractor. `create` has DIFFERENT params
|
|
96
|
+
// in each namespace; binding names (widgets/subscriptions) match the runtime
|
|
97
|
+
// module's export names below.
|
|
98
|
+
writeFileSync(join(dir, 'index.ts'), `
|
|
99
|
+
interface ApiNamespaceConstructor { new <T>(scope: any, name: string, handler: (ctx: any) => T): T; }
|
|
100
|
+
const ApiNamespace: ApiNamespaceConstructor = class {} as any;
|
|
101
|
+
|
|
102
|
+
export const widgets = new ApiNamespace(null, 'widgets', () => ({
|
|
103
|
+
async create(label: string): Promise<{ widgetId: string }> { return { widgetId: 'w' }; },
|
|
104
|
+
}));
|
|
105
|
+
export const subscriptions = new ApiNamespace(null, 'subscriptions', () => ({
|
|
106
|
+
async create(topicCount: number): Promise<{ subId: number }> { return { subId: 1 }; },
|
|
107
|
+
}));
|
|
108
|
+
`);
|
|
109
|
+
// Runtime module for namespace discovery (types come from the .ts above via
|
|
110
|
+
// the extractor). Use the real ApiNamespace so the marker symbol matches.
|
|
111
|
+
const widgets = new ApiNamespace(null, 'widgets', () => ({ async create(_label) { return { widgetId: 'w' }; } }));
|
|
112
|
+
const subscriptions = new ApiNamespace(null, 'subscriptions', () => ({ async create(_topicCount) { return { subId: 1 }; } }));
|
|
113
|
+
const loader = async () => ({ widgets, subscriptions });
|
|
114
|
+
try {
|
|
115
|
+
const doc = await generateSpec(join(dir, 'index.ts'), loader);
|
|
116
|
+
const byName = new Map(doc.methods.map((m) => [m.name, m]));
|
|
117
|
+
const w = byName.get('widgets.create');
|
|
118
|
+
const s = byName.get('subscriptions.create');
|
|
119
|
+
assert.ok(w && s, 'both namespace-qualified methods should be present');
|
|
120
|
+
// Each keeps its OWN param — no cross-assignment.
|
|
121
|
+
assert.strictEqual(w.params[0]?.name, 'label');
|
|
122
|
+
assert.strictEqual(w.params[0]?.schema?.type, 'string');
|
|
123
|
+
assert.strictEqual(s.params[0]?.name, 'topicCount');
|
|
124
|
+
assert.strictEqual(s.params[0]?.schema?.type, 'number');
|
|
125
|
+
}
|
|
126
|
+
finally {
|
|
127
|
+
rmSync(dir, { recursive: true, force: true });
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
describe('generateSpec — namespace returned by a factory, then destructured (regression: #444)', () => {
|
|
132
|
+
it('emits real schemas (not unknown) for a factory/destructured namespace', async () => {
|
|
133
|
+
const dir = join(tmpdir(), `blocks-spec-test-${Date.now()}-factory`);
|
|
134
|
+
mkdirSync(dir, { recursive: true });
|
|
135
|
+
writeTsconfig(dir);
|
|
136
|
+
// Typed foundation: one directly-exported namespace and one built inside a
|
|
137
|
+
// factory, returned as a property, destructured, and re-exported. Both use
|
|
138
|
+
// `create` with DIFFERENT params so a regression would cross-assign or emit
|
|
139
|
+
// unknown.
|
|
140
|
+
writeFileSync(join(dir, 'index.ts'), `
|
|
141
|
+
interface ApiNamespaceConstructor { new <T>(scope: any, name: string, handler: (ctx: any) => T): T; }
|
|
142
|
+
const ApiNamespace: ApiNamespaceConstructor = class {} as any;
|
|
143
|
+
class Scope { constructor(id: string) {} }
|
|
144
|
+
|
|
145
|
+
class Factory {
|
|
146
|
+
constructor(private readonly scope: Scope) {}
|
|
147
|
+
build() {
|
|
148
|
+
return {
|
|
149
|
+
subscriptions: new ApiNamespace(this.scope, 'subscriptions', () => ({
|
|
150
|
+
async create(topicCount: number): Promise<{ subId: number }> { return { subId: 1 }; },
|
|
151
|
+
})),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const scope = new Scope('app');
|
|
157
|
+
export const widgets = new ApiNamespace(scope, 'widgets', () => ({
|
|
158
|
+
async create(label: string): Promise<{ widgetId: string }> { return { widgetId: 'w' }; },
|
|
159
|
+
}));
|
|
160
|
+
const { subscriptions } = new Factory(scope).build();
|
|
161
|
+
export { subscriptions };
|
|
162
|
+
`);
|
|
163
|
+
const widgets = new ApiNamespace(null, 'widgets', () => ({ async create(_label) { return { widgetId: 'w' }; } }));
|
|
164
|
+
const subscriptions = new ApiNamespace(null, 'subscriptions', () => ({ async create(_topicCount) { return { subId: 1 }; } }));
|
|
165
|
+
const loader = async () => ({ widgets, subscriptions });
|
|
166
|
+
try {
|
|
167
|
+
const doc = await generateSpec(join(dir, 'index.ts'), loader);
|
|
168
|
+
const byName = new Map(doc.methods.map((m) => [m.name, m]));
|
|
169
|
+
const w = byName.get('widgets.create');
|
|
170
|
+
const s = byName.get('subscriptions.create');
|
|
171
|
+
assert.ok(w && s, 'both qualified methods present');
|
|
172
|
+
// The factory-returned namespace keeps its own param, not `unknown` and
|
|
173
|
+
// not the direct namespace's `string`.
|
|
174
|
+
assert.strictEqual(s.params[0]?.schema?.type, 'number');
|
|
175
|
+
assert.strictEqual(w.params[0]?.schema?.type, 'string');
|
|
176
|
+
}
|
|
177
|
+
finally {
|
|
178
|
+
rmSync(dir, { recursive: true, force: true });
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
});
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const CORE_VERSION = "0.
|
|
1
|
+
export declare const CORE_VERSION = "0.5.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Auto-generated by scripts/generate-version.mjs — do not edit manually
|
|
2
|
-
export const CORE_VERSION = '0.
|
|
2
|
+
export const CORE_VERSION = '0.5.0';
|
package/package.json
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aws-blocks/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"keywords": [
|
|
5
|
+
"aws-blocks",
|
|
6
|
+
"framework",
|
|
7
|
+
"typescript",
|
|
8
|
+
"backend",
|
|
9
|
+
"primitives"
|
|
10
|
+
],
|
|
4
11
|
"repository": {
|
|
5
12
|
"type": "git",
|
|
6
13
|
"url": "git+https://github.com/aws-devtools-labs/aws-blocks.git",
|
|
@@ -8,6 +8,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
8
8
|
import * as cdk from 'aws-cdk-lib';
|
|
9
9
|
import { Match, Template } from 'aws-cdk-lib/assertions';
|
|
10
10
|
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
|
|
11
|
+
import type { IWidget } from 'aws-cdk-lib/aws-cloudwatch';
|
|
11
12
|
import { PolicyStatement } from 'aws-cdk-lib/aws-iam';
|
|
12
13
|
import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs';
|
|
13
14
|
import type { Construct } from 'constructs';
|
|
@@ -18,6 +19,7 @@ import { BlocksPresets } from './blocks-defaults.js';
|
|
|
18
19
|
import { Compute } from './compute/compute.js';
|
|
19
20
|
import type { DefaultComputeFactory } from './compute/default-compute-factory.js';
|
|
20
21
|
import { Scope } from './index.js';
|
|
22
|
+
import { getVpcContext } from './vpc.js';
|
|
21
23
|
|
|
22
24
|
// A real app gets its default compute from @aws-blocks/bb-lambda-compute (via
|
|
23
25
|
// @aws-blocks/blocks), which core's own tests can't depend on. Use an
|
|
@@ -37,6 +39,11 @@ class StubLambdaCompute extends Compute {
|
|
|
37
39
|
this.logGroup = new cdk.aws_logs.LogGroup(this, 'HandlerLogGroup', {
|
|
38
40
|
removalPolicy: cdk.RemovalPolicy.DESTROY,
|
|
39
41
|
});
|
|
42
|
+
// Discover the shared VPC context from the owning stack/backend, mirroring
|
|
43
|
+
// LambdaCompute — so the core-side VPC placement plumbing (initializeVpc →
|
|
44
|
+
// getVpcContext) is exercised by these tests without depending on
|
|
45
|
+
// @aws-blocks/bb-lambda-compute.
|
|
46
|
+
const vpcContext = getVpcContext(this);
|
|
40
47
|
this.fn = new lambda.NodejsFunction(this, 'Handler', {
|
|
41
48
|
entry: this.backendHandlerPath,
|
|
42
49
|
runtime: cdk.aws_lambda.Runtime.NODEJS_22_X,
|
|
@@ -45,6 +52,13 @@ class StubLambdaCompute extends Compute {
|
|
|
45
52
|
logGroup: this.logGroup,
|
|
46
53
|
environment: { BLOCKS_STACK_NAME: this.backendStackName },
|
|
47
54
|
bundling: { minify: true, esbuildArgs: { '--conditions': 'aws-runtime' } },
|
|
55
|
+
...(vpcContext
|
|
56
|
+
? {
|
|
57
|
+
vpc: vpcContext.vpc,
|
|
58
|
+
vpcSubnets: vpcContext.computeSubnets,
|
|
59
|
+
securityGroups: [vpcContext.computeSecurityGroup],
|
|
60
|
+
}
|
|
61
|
+
: {}),
|
|
48
62
|
});
|
|
49
63
|
this.apiGateway = new apigateway.RestApi(this, 'API', { restApiName: 'Blocks API' });
|
|
50
64
|
this.apiGateway.root.addProxy({
|
|
@@ -57,6 +71,17 @@ class StubLambdaCompute extends Compute {
|
|
|
57
71
|
setEnv(key: string, value: string): void {
|
|
58
72
|
this.fn.addEnvironment(key, value);
|
|
59
73
|
}
|
|
74
|
+
|
|
75
|
+
protected applyTracing(): void {}
|
|
76
|
+
protected healthWidgets(_region: string): IWidget[][] {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
protected loggingWidgets(_region: string): IWidget[][] {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
protected tracingWidgets(_region: string): IWidget[][] {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
60
85
|
}
|
|
61
86
|
|
|
62
87
|
const stubComputeFactory: DefaultComputeFactory = (root) => new StubLambdaCompute(root as never, 'DefaultCompute');
|
|
@@ -77,7 +102,12 @@ const importMetaHandlerPath = join(__dirname, '__fixtures__', 'import-meta-handl
|
|
|
77
102
|
// Wraps BlocksBackend.create, injecting the stub default-compute factory the way
|
|
78
103
|
// @aws-blocks/blocks injects LambdaCompute — so tests don't repeat it 15 times.
|
|
79
104
|
const makeBackend = (scope: Construct, id: string, backendCDKPath: string) =>
|
|
80
|
-
BlocksBackend.create(scope, id, {
|
|
105
|
+
BlocksBackend.create(scope, id, {
|
|
106
|
+
backendHandlerPath: handlerPath,
|
|
107
|
+
backendCDKPath,
|
|
108
|
+
defaults: BlocksPresets.production,
|
|
109
|
+
defaultComputeFactory: stubComputeFactory,
|
|
110
|
+
});
|
|
81
111
|
|
|
82
112
|
describe('ESM cache-busting (multi-stage)', () => {
|
|
83
113
|
test('BlocksBackend.create() with same backendCDKPath but different IDs produces constructs in each', async () => {
|
|
@@ -230,25 +260,25 @@ describe('shared execution role', () => {
|
|
|
230
260
|
});
|
|
231
261
|
|
|
232
262
|
describe('CJS bundle: import.meta.url in the handler is shimmed (no Lambda-load crash)', () => {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
263
|
+
test('a handler that uses import.meta.url bundles successfully instead of throwing at load', async () => {
|
|
264
|
+
// The handler is bundled to CJS, where `import.meta` is empty. Left unshimmed,
|
|
265
|
+
// `fileURLToPath(import.meta.url)` compiles to `fileURLToPath(undefined)` and
|
|
266
|
+
// throws at Lambda load (esbuild only warns, so the broken bundle would deploy).
|
|
267
|
+
// blocksNodejsBundling shims import.meta.* to CommonJS equivalents, so bundling
|
|
268
|
+
// (which runs synchronously during construction) succeeds. The runtime behaviour
|
|
269
|
+
// of the emitted shim is verified directly in bundling.test.ts.
|
|
270
|
+
const app = new cdk.App();
|
|
271
|
+
const stack = new cdk.Stack(app, 'ImportMetaStack');
|
|
272
|
+
|
|
273
|
+
await assert.doesNotReject(() =>
|
|
274
|
+
BlocksBackend.create(stack, 'blocks', {
|
|
275
|
+
backendHandlerPath: importMetaHandlerPath,
|
|
276
|
+
backendCDKPath: sideEffectBackendPath,
|
|
277
|
+
defaults: BlocksPresets.production,
|
|
278
|
+
defaultComputeFactory: stubComputeFactory,
|
|
279
|
+
}),
|
|
280
|
+
);
|
|
281
|
+
});
|
|
252
282
|
});
|
|
253
283
|
|
|
254
284
|
describe('factory function support', () => {
|
|
@@ -348,44 +378,98 @@ describe('fullId is token-free (construct IDs / env-var keys)', () => {
|
|
|
348
378
|
});
|
|
349
379
|
|
|
350
380
|
describe('infrastructure defaults (backend-anchored)', () => {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
381
|
+
test('each backend exposes its own defaults', async () => {
|
|
382
|
+
const app = new cdk.App();
|
|
383
|
+
const stack = new cdk.Stack(app, 'TwoBackendsStack');
|
|
384
|
+
|
|
385
|
+
const a = await BlocksBackend.create(stack, 'A', {
|
|
386
|
+
backendHandlerPath: handlerPath,
|
|
387
|
+
backendCDKPath: sideEffectBackendPath,
|
|
388
|
+
defaults: BlocksPresets.production,
|
|
389
|
+
defaultComputeFactory: stubComputeFactory,
|
|
390
|
+
});
|
|
391
|
+
const b = await BlocksBackend.create(stack, 'B', {
|
|
392
|
+
backendHandlerPath: handlerPath,
|
|
393
|
+
backendCDKPath: sideEffectBackendPath,
|
|
394
|
+
defaults: BlocksPresets.sandbox,
|
|
395
|
+
defaultComputeFactory: stubComputeFactory,
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
// Two backends in one stack must NOT clobber each other — defaults are
|
|
399
|
+
// anchored on the backend, not the shared stack.
|
|
400
|
+
assert.strictEqual(a.defaults, BlocksPresets.production);
|
|
401
|
+
assert.strictEqual(b.defaults, BlocksPresets.sandbox);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test('a nested block resolves its owning backend defaults via the tree-walk', async () => {
|
|
405
|
+
const app = new cdk.App();
|
|
406
|
+
const stack = new cdk.Stack(app, 'ResolveDefaultsStack');
|
|
407
|
+
|
|
408
|
+
const backend = await BlocksBackend.create(stack, 'Blocks', {
|
|
409
|
+
backendHandlerPath: handlerPath,
|
|
410
|
+
backendCDKPath: sideEffectBackendPath,
|
|
411
|
+
defaults: BlocksPresets.sandbox,
|
|
412
|
+
defaultComputeFactory: stubComputeFactory,
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
// A Scope under the backend resolves scope.defaults by walking up to it.
|
|
416
|
+
const outer = new Scope('outer');
|
|
417
|
+
const inner = new Scope('inner', { parent: outer });
|
|
418
|
+
assert.strictEqual(inner.defaults, backend.defaults);
|
|
419
|
+
assert.strictEqual(inner.defaults, BlocksPresets.sandbox);
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
describe('VPC placement', () => {
|
|
424
|
+
test('places the handler Lambda in the VPC with a security group when vpc is provided', async () => {
|
|
425
|
+
const app = new cdk.App();
|
|
426
|
+
const parent = new cdk.Stack(app, 'VpcParent', {
|
|
427
|
+
env: { account: '123456789012', region: 'us-east-1' },
|
|
428
|
+
});
|
|
429
|
+
const vpc = new cdk.aws_ec2.Vpc(parent, 'AppVpc', { maxAzs: 2, natGateways: 1 });
|
|
430
|
+
|
|
431
|
+
await BlocksBackend.create(parent, 'Blocks', {
|
|
432
|
+
backendHandlerPath: handlerPath,
|
|
433
|
+
backendCDKPath: sideEffectBackendPath,
|
|
434
|
+
defaults: { ...BlocksPresets.production, vpc: { network: vpc } },
|
|
435
|
+
defaultComputeFactory: stubComputeFactory,
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
const template = Template.fromStack(parent);
|
|
439
|
+
// The handler Lambda is VPC-attached: it has a VpcConfig with subnets + SGs.
|
|
440
|
+
template.hasResourceProperties('AWS::Lambda::Function', {
|
|
441
|
+
VpcConfig: Match.objectLike({
|
|
442
|
+
SubnetIds: Match.anyValue(),
|
|
443
|
+
SecurityGroupIds: Match.anyValue(),
|
|
444
|
+
}),
|
|
445
|
+
});
|
|
446
|
+
// The VPC-access managed policy is attached to the execution role.
|
|
447
|
+
const policies = template.findResources('AWS::IAM::Role');
|
|
448
|
+
const hasVpcManagedPolicy = Object.values(policies).some((role: any) =>
|
|
449
|
+
JSON.stringify(role.Properties?.ManagedPolicyArns ?? []).includes('AWSLambdaVPCAccessExecutionRole'),
|
|
450
|
+
);
|
|
451
|
+
assert.ok(hasVpcManagedPolicy, 'execution role should have the VPC access managed policy');
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
test('no VpcConfig on the handler when vpc is omitted', async () => {
|
|
455
|
+
const app = new cdk.App();
|
|
456
|
+
const parent = new cdk.Stack(app, 'NoVpcParent');
|
|
457
|
+
|
|
458
|
+
await BlocksBackend.create(parent, 'Blocks', {
|
|
459
|
+
backendHandlerPath: handlerPath,
|
|
460
|
+
backendCDKPath: sideEffectBackendPath,
|
|
461
|
+
defaults: BlocksPresets.production,
|
|
462
|
+
defaultComputeFactory: stubComputeFactory,
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
const template = Template.fromStack(parent);
|
|
466
|
+
const fns = template.findResources('AWS::Lambda::Function');
|
|
467
|
+
for (const fn of Object.values(fns)) {
|
|
468
|
+
assert.strictEqual(
|
|
469
|
+
(fn as any).Properties?.VpcConfig,
|
|
470
|
+
undefined,
|
|
471
|
+
'handler must not have VpcConfig when no VPC is configured',
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
});
|
|
391
475
|
});
|