@kensio/yulin-aws-simulation 1.13.0 → 1.14.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.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://anthropic.com/claude-code/plugin.schema.json",
|
|
3
3
|
"name": "yulin-aws-simulation",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.14.0",
|
|
5
5
|
"description": "How to test AWS code well with the @kensio/yulin in-process simulator.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Kensio Software",
|
package/README.md
CHANGED
|
@@ -76,8 +76,10 @@ deployed in `beforeAll` is already isolated between files. Isolation inside the
|
|
|
76
76
|
randomised names.
|
|
77
77
|
|
|
78
78
|
**Run the handler as a real simulated Lambda.** Bind an in-process handler to a template function
|
|
79
|
-
and
|
|
80
|
-
role fails the test at the point AWS would
|
|
79
|
+
and invoke the function through simulated Lambda. Its SDK calls are then routed into the simulation
|
|
80
|
+
as the execution role, and a missing permission on that role fails the test at the point AWS would
|
|
81
|
+
have failed it. Calling the bound handler directly skips all of that, and so does reading
|
|
82
|
+
`process.env` at module scope.
|
|
81
83
|
|
|
82
84
|
## Related skills
|
|
83
85
|
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@ name: yulin-aws-simulation
|
|
|
3
3
|
description: Use the @kensio/yulin in-process AWS simulator well when testing AWS code, using it directly rather than building a harness around it, driving tests, local dev and production from one synthesized CDK template, intercepting SDK clients with SimSdk, controlling simulated time, matching service errors by name, and handling properties Yulin refuses to simulate. Use when writing or reviewing tests that touch AWS, when replacing aws-sdk-client-mock or hand-rolled AWS stubs, when a CDK stack needs testing, when test setup around Yulin is growing helper classes or wrapper functions, and when Yulin refuses a template property or an SDK command.
|
|
4
4
|
license: Apache-2.0
|
|
5
5
|
metadata:
|
|
6
|
-
version: "1.
|
|
6
|
+
version: "1.14.0"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Testing with Yulin
|
|
@@ -12,9 +12,11 @@ metadata:
|
|
|
12
12
|
network and no AWS account. Its own docs are the authority on the API. This skill covers how to use
|
|
13
13
|
it well, and that lives mostly outside the API.
|
|
14
14
|
|
|
15
|
-
Read the package docs for anything API-shaped.
|
|
16
|
-
[
|
|
17
|
-
|
|
15
|
+
Read the package docs for anything API-shaped.
|
|
16
|
+
[yulinsim.dev/llms.txt](https://yulinsim.dev/llms.txt) indexes every page as plain markdown, one
|
|
17
|
+
link per guide and one per simulated service. Drop the `llms.txt` from a link for the HTML page. The
|
|
18
|
+
same pages ship in the repository, as [the README](https://github.com/KensioSoftware/yulin#readme),
|
|
19
|
+
then `docs/sdk/` for interception and `docs/services/<name>/` for each simulated service.
|
|
18
20
|
|
|
19
21
|
This skill serves the `isolated-testing-style` skill, the general argument for simulation over
|
|
20
22
|
stubs.
|
|
@@ -84,8 +86,7 @@ The two options that make a wrapper unnecessary:
|
|
|
84
86
|
|
|
85
87
|
- **`transform`** is given the parsed template and answers with the one to deploy. It runs on the
|
|
86
88
|
deployment and again on every re-read. A wrapper cannot do that. Use it for what a simulation
|
|
87
|
-
genuinely cannot resolve, such as an ARN carrying a real account
|
|
88
|
-
from `HostedZone.fromLookup`.
|
|
89
|
+
genuinely cannot resolve, such as an ARN carrying a real account.
|
|
89
90
|
- **`watch`** re-applies the file when it changes, updating the stack in place. This is for dev
|
|
90
91
|
servers. A `cdk synth` becomes a stack update without restarting the process, and resources the
|
|
91
92
|
change left alone keep what they hold.
|
|
@@ -93,11 +94,23 @@ The two options that make a wrapper unnecessary:
|
|
|
93
94
|
```typescript
|
|
94
95
|
await simAws.cloudFormation().deployTemplateFile({
|
|
95
96
|
templatePath: "cdk.out/SiteStack.template.json",
|
|
96
|
-
transform:
|
|
97
|
+
transform: withRealAccountArnsResolved,
|
|
97
98
|
watch: { onUpdated: () => srv.reload() },
|
|
98
99
|
});
|
|
99
100
|
```
|
|
100
101
|
|
|
102
|
+
A hosted zone ID that came from `HostedZone.fromLookup` needs no transform of its own. An
|
|
103
|
+
`AWS::Route53::RecordSet` naming a hosted zone ID that no zone holds gets one registered under that
|
|
104
|
+
ID as the record is created, and its records resolve. The zone takes its name from the records that
|
|
105
|
+
name it. Register it yourself with `simAws.route53().registerHostedZone({ id, name })` where a test
|
|
106
|
+
depends on the name, such as one listing zones by name.
|
|
107
|
+
|
|
108
|
+
A failed `cdk synth` leaves the previous template in `cdk.out` where it was, and the tests carry on
|
|
109
|
+
running against it. An edit that leaves an unused variable behind fails `tsc` and exits non-zero,
|
|
110
|
+
with the error hidden behind a redirect. The test that runs next passes against the template from
|
|
111
|
+
before the change. Check that the synthesized JSON changed before concluding anything from a
|
|
112
|
+
construct change.
|
|
113
|
+
|
|
101
114
|
## Intercept real SDK clients, never hand-roll stubs
|
|
102
115
|
|
|
103
116
|
`SimSdk` replaces the `send` method of an AWS SDK client class or instance, so real clients answer
|
|
@@ -270,6 +283,11 @@ control also needs `lambda:InvokeFunction`. The tests passed, the release went o
|
|
|
270
283
|
Report a false pass with what production does and what the simulation did. Report a false refusal
|
|
271
284
|
with the property and the template that carries it.
|
|
272
285
|
|
|
286
|
+
A workaround kept while the issue is open wants a comment naming that issue, and a revisit when it
|
|
287
|
+
closes. One test file carried a comment saying its handler could not reach simulated AWS from inside
|
|
288
|
+
a simulated Lambda, citing KensioSoftware/yulin#638. That held when it was written and was fixed in
|
|
289
|
+
1.16.0. The comment went on shaping the design of the test for months after that.
|
|
290
|
+
|
|
273
291
|
## Deploy expensive context once per test file
|
|
274
292
|
|
|
275
293
|
Vitest gives each test file its own worker, so module-level state is already isolated between files.
|
|
@@ -322,3 +340,74 @@ The handler still runs in process. It can close over test state and be stepped t
|
|
|
322
340
|
debugger. The difference from calling it directly is that a missing `s3:PutObject` on the execution
|
|
323
341
|
role now fails the test, at the point AWS would have failed it. A binding can target a function by
|
|
324
342
|
`logicalId`, `functionName`, `arn`, `cdkPath`, or `imageRepository` for a container image function.
|
|
343
|
+
|
|
344
|
+
### Invoke through simulated Lambda
|
|
345
|
+
|
|
346
|
+
Binding a handler proves nothing about IAM on its own. The execution role, the function's
|
|
347
|
+
environment and its outbound HTTP are all applied by the invocation. The test has to go through
|
|
348
|
+
`simAws.lambda().invoke(new InvokeCommand({ FunctionName, Payload }))` to get any of them. A test
|
|
349
|
+
holding the same handler reference and calling it directly runs it in the test's own scope, as the
|
|
350
|
+
test's own caller, with none of the three.
|
|
351
|
+
|
|
352
|
+
The difference shows up under mutation. Removing `dynamodb:GetItem` from the role's policy in the
|
|
353
|
+
CDK stack and re-synthesizing failed every invoked case:
|
|
354
|
+
|
|
355
|
+
```
|
|
356
|
+
AccessDenied: User: arn:aws:iam::111111111111:role/UserFunctionServiceRole1B2C3D4E
|
|
357
|
+
is not authorized to perform: dynamodb:GetItem
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
The cases calling the handler directly passed. A suite that stays green through that mutation was
|
|
361
|
+
never covering the policy.
|
|
362
|
+
|
|
363
|
+
### Read the environment inside the handler
|
|
364
|
+
|
|
365
|
+
A bound handler gets the function's declared environment variables with nothing stubbed.
|
|
366
|
+
`SimProcessEnvironment` holds a run's variables in an `AsyncLocalStorage` store and resolves
|
|
367
|
+
`process.env` to that store for the length of the run. Concurrent runs each see their own. Its own
|
|
368
|
+
doc comment states the one limit:
|
|
369
|
+
|
|
370
|
+
<!-- prose-check:off -->
|
|
371
|
+
|
|
372
|
+
> The one thing this cannot reach is a read that already happened. A handler module doing
|
|
373
|
+
> `const TABLE = process.env.TABLE_NAME` at module scope is evaluated when the test file imports it,
|
|
374
|
+
> long before any run, so it captures the host value.
|
|
375
|
+
|
|
376
|
+
<!-- prose-check:on -->
|
|
377
|
+
|
|
378
|
+
So read the environment inside the handler body. Memoise there where a warm container should build
|
|
379
|
+
its clients once. The substituted `Date` a bound handler reads works the same way, and a time
|
|
380
|
+
captured at module scope is captured equally early.
|
|
381
|
+
|
|
382
|
+
Yulin ships `SimLambdaEnvironmentConflicts` to warn about this, and it warns only where the host
|
|
383
|
+
value and the declared value differ. A suite that stubs the right values stays quiet and never
|
|
384
|
+
learns. The rule stays invisible for as long as the stubs agree.
|
|
385
|
+
|
|
386
|
+
A `vi.stubEnv` around a bound handler is the sign that the handler reads too early. Moving the reads
|
|
387
|
+
into the handler body removed every `vi.stubEnv` from one repository.
|
|
388
|
+
|
|
389
|
+
### What a binding buys, and what the zip path buys
|
|
390
|
+
|
|
391
|
+
Deploying without `bindings` runs the bundle `cdk synth` produced. `deployTemplateFile` publishes
|
|
392
|
+
the cloud assembly's assets into the staging bucket in simulated S3, and the function's modules are
|
|
393
|
+
evaluated as CommonJS in a vm sandbox. That sandbox hands the code its own `process.env`, its own
|
|
394
|
+
`Date` and its own HTTP clients, and the module-scope problem above never arises there.
|
|
395
|
+
|
|
396
|
+
Both paths authorise through the execution role. The same policy mutation fails a zip-path test
|
|
397
|
+
exactly as it fails a bound one. The choice between them is about what else the test needs:
|
|
398
|
+
|
|
399
|
+
- **A binding** keeps a breakpoint working and lets the handler close over test state.
|
|
400
|
+
- **The zip path** exercises the artefact that deploys, its imports and its bundling included.
|
|
401
|
+
|
|
402
|
+
### Outbound HTTP is answered by the simulation
|
|
403
|
+
|
|
404
|
+
From 1.16.2, a simulated Lambda's `fetch` and its `node:http` and `node:https` are answered by the
|
|
405
|
+
simulation for every hostname simulated Route 53 resolves. Those requests go through the same
|
|
406
|
+
in-process entry point one arriving on localhost uses. A Cognito user pool domain, an HTTP API and a
|
|
407
|
+
load balancer are all answered without the test knowing which of them it asked. Everything else
|
|
408
|
+
reaches the network as it was addressed.
|
|
409
|
+
|
|
410
|
+
This is what makes an OAuth authorization code exchange testable. That exchange lives only at the
|
|
411
|
+
hosted `/oauth2/token` endpoint of the user pool domain and has no SDK operation behind it. A
|
|
412
|
+
handler that cannot reach the domain leaves the whole exchange uncovered. The same routing lets
|
|
413
|
+
`CognitoJwtVerifier` fetch a simulated pool's JWKS from inside a handler with no cache primed.
|