@kensio/yulin 1.21.7 → 1.21.8

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.
Files changed (47) hide show
  1. package/README.md +22 -2
  2. package/dist/service/ses/command/authorize/sim-ses-authorizer.d.ts +9 -0
  3. package/dist/service/ses/command/authorize/sim-ses-authorizer.js +18 -2
  4. package/dist/service/ses/command/send/sim-ses-send-email.js +2 -1
  5. package/docs/README.md +7 -0
  6. package/docs/ai-skill/README.md +6 -1
  7. package/docs/sdk/README.md +15 -0
  8. package/docs/services/acm/README.md +24 -40
  9. package/docs/services/apigateway/README.md +52 -71
  10. package/docs/services/apigatewayv2/README.md +55 -74
  11. package/docs/services/athena/README.md +17 -26
  12. package/docs/services/backup/README.md +29 -39
  13. package/docs/services/bedrock/README.md +38 -52
  14. package/docs/services/cloudformation/README.md +43 -55
  15. package/docs/services/cloudfront/README.md +69 -95
  16. package/docs/services/cloudwatch/README.md +40 -54
  17. package/docs/services/cognito/README.md +30 -45
  18. package/docs/services/dynamodb/README.md +34 -51
  19. package/docs/services/ecr/README.md +36 -77
  20. package/docs/services/ecs/README.md +26 -46
  21. package/docs/services/elbv2/README.md +19 -29
  22. package/docs/services/eventbridge/README.md +14 -18
  23. package/docs/services/firehose/README.md +24 -32
  24. package/docs/services/glue/README.md +41 -78
  25. package/docs/services/iam/README.md +13 -15
  26. package/docs/services/kinesis/README.md +53 -93
  27. package/docs/services/kms/README.md +22 -32
  28. package/docs/services/lambda/README.md +53 -79
  29. package/docs/services/logs/README.md +41 -50
  30. package/docs/services/organizations/README.md +50 -85
  31. package/docs/services/personalize/README.md +28 -44
  32. package/docs/services/rekognition/README.md +26 -38
  33. package/docs/services/route53/README.md +17 -17
  34. package/docs/services/s3/README.md +47 -51
  35. package/docs/services/scheduler/README.md +41 -52
  36. package/docs/services/secretsmanager/README.md +27 -42
  37. package/docs/services/ses/README.md +24 -34
  38. package/docs/services/sns/README.md +18 -26
  39. package/docs/services/sqs/README.md +14 -14
  40. package/docs/services/ssm/README.md +13 -17
  41. package/docs/services/stepfunctions/README.md +18 -20
  42. package/docs/services/sts/README.md +32 -45
  43. package/docs/services/wafv2/README.md +12 -17
  44. package/docs/testing/README.md +228 -0
  45. package/docs/time/README.md +10 -0
  46. package/llms.txt +1 -0
  47. package/package.json +1 -1
package/README.md CHANGED
@@ -66,6 +66,7 @@ npm i -D @kensio/yulin
66
66
  - [Non-AWS dependencies](https://yulinsim.dev/non-aws-dependencies/ "Dependencies Yulin does not simulate docs")
67
67
  - [Serving on localhost](https://yulinsim.dev/serve/ "Serving simulated AWS on localhost docs")
68
68
  - [Simulated time](https://yulinsim.dev/time/ "Simulated time docs")
69
+ - [Test suite setup](https://yulinsim.dev/testing/ "Sharing one Yulin environment across a test suite")
69
70
  - [Terraform](https://yulinsim.dev/terraform/ "Deploying Terraform into simulated AWS docs")
70
71
 
71
72
  Every page listed above also ships inside the package as markdown, documenting the version
@@ -74,6 +75,24 @@ reach can read them from there.
74
75
 
75
76
  ## Usage
76
77
 
78
+ ### Set up Yulin once for the test suite
79
+
80
+ An application test suite should normally create one Yulin environment in its Vitest setup. Install
81
+ SDK interception and deploy the application's simulated infrastructure there, then let every test
82
+ use that state. Treat the environment like an AWS account or a LocalStack container that belongs to
83
+ the suite.
84
+
85
+ Keep tests independent by giving their records and resources unique names. A fresh `SimAws` or
86
+ `SimSdk` for every test or test file is supported, but it is intended for cases that specifically
87
+ need an empty simulated account.
88
+
89
+ The simulated clock is shared too. Most tests should leave it alone and use the suite environment.
90
+ Put tests that call `freeze()`, `setTo(...)`, `advanceBy(...)`, or `resume()` in a separate group with
91
+ a fresh Yulin environment for each test.
92
+
93
+ The [test suite setup guide](https://yulinsim.dev/testing/ "Sharing one Yulin environment across a test suite")
94
+ shows the Vitest configuration, shared deployment, and SDK interception.
95
+
77
96
  ### Intercept AWS SDK clients
78
97
 
79
98
  If your code uses the AWS SDK, you can intercept its clients and route their Commands to simulated
@@ -166,8 +185,9 @@ const accountId = simAwsAccountId("111111111111");
166
185
  const someOtherAccountId = makeSimAwsAccountId();
167
186
  ```
168
187
 
169
- Each instance of `SimAws` is cheap and encapsulated. Make one wherever you need it, in every test
170
- case or in shared test setup, whichever suits.
188
+ Each instance of `SimAws` is encapsulated. Application tests should usually share one instance from
189
+ suite setup. Create another instance when a test needs a separate simulated account with no shared
190
+ state.
171
191
 
172
192
  If you prefer, you can also instantiate simulated services individually:
173
193
 
@@ -30,6 +30,15 @@ export declare class SimSesAuthorizer {
30
30
  * ARN the identity is about to have.
31
31
  */
32
32
  authorizeIdentity(action: string, emailIdentity: string, caller?: SimAwsCaller): SimAwsResolvedCaller;
33
+ /**
34
+ * Ensure the caller may send from one email identity and address.
35
+ *
36
+ * SES supplies the bare sender address as `ses:FromAddress`. A policy can
37
+ * use this key to grant one address under a verified domain identity.
38
+ *
39
+ * https://docs.aws.amazon.com/ses/latest/dg/sending-authorization-policy-examples.html#sending-authorization-policy-example-from
40
+ */
41
+ authorizeSendEmail(emailIdentity: string, fromAddress: string, caller?: SimAwsCaller): SimAwsResolvedCaller;
33
42
  /**
34
43
  * Ensure the caller may perform an action on one email template.
35
44
  *
@@ -40,6 +40,17 @@ export class SimSesAuthorizer {
40
40
  authorizeIdentity(action, emailIdentity, caller) {
41
41
  return this.authorizeResource(action, simSesIdentityArn(this.#accountRegionScope, emailIdentity), caller);
42
42
  }
43
+ /**
44
+ * Ensure the caller may send from one email identity and address.
45
+ *
46
+ * SES supplies the bare sender address as `ses:FromAddress`. A policy can
47
+ * use this key to grant one address under a verified domain identity.
48
+ *
49
+ * https://docs.aws.amazon.com/ses/latest/dg/sending-authorization-policy-examples.html#sending-authorization-policy-example-from
50
+ */
51
+ authorizeSendEmail(emailIdentity, fromAddress, caller) {
52
+ return this.authorizeResource("ses:SendEmail", simSesIdentityArn(this.#accountRegionScope, emailIdentity), caller, { "ses:FromAddress": fromAddress });
53
+ }
43
54
  /**
44
55
  * Ensure the caller may perform an action on one email template.
45
56
  *
@@ -73,8 +84,13 @@ export class SimSesAuthorizer {
73
84
  authorizeNoResource(action, caller) {
74
85
  return this.authorizeResource(action, noResource, caller);
75
86
  }
76
- authorizeResource(action, resource, caller) {
77
- const decision = this.#iam.authorize({ action, resource, caller });
87
+ authorizeResource(action, resource, caller, conditionContext) {
88
+ const decision = this.#iam.authorize({
89
+ action,
90
+ resource,
91
+ caller,
92
+ conditionContext,
93
+ });
78
94
  if (decision.isDenied) {
79
95
  throw new SimIamAccessDenied({
80
96
  principal: decision.caller.principal,
@@ -42,8 +42,9 @@ export class SimSesSendEmail {
42
42
  handle(command, options) {
43
43
  const input = command.input;
44
44
  const fromEmailAddress = requiredSimSesFromAddress(input.FromEmailAddress);
45
+ const bareFromAddress = simSesBareAddress(fromEmailAddress);
45
46
  refuseUnsimulatedSendInput(input);
46
- this.#authorizer.authorizeIdentity("ses:SendEmail", this.#identities.covering(simSesBareAddress(fromEmailAddress)), options?.caller);
47
+ this.#authorizer.authorizeSendEmail(this.#identities.covering(bareFromAddress), bareFromAddress, options?.caller);
47
48
  const destination = readDestination(input.Destination);
48
49
  const recipients = allRecipients(destination);
49
50
  if (recipients.length === 0) {
package/docs/README.md CHANGED
@@ -15,6 +15,12 @@ npm install --save-dev @kensio/yulin
15
15
 
16
16
  ## Choose how to use Yulin
17
17
 
18
+ Start with [test suite setup](https://yulinsim.dev/testing/) when adding Yulin to an application's
19
+ tests. Create one simulated environment, deploy the application's infrastructure, and install SDK
20
+ interception for the whole suite. Tests then use that shared environment as they would use an AWS
21
+ account or a container-based simulator. Put the smaller set of tests that control simulated time in
22
+ an isolated group because the clock belongs to the environment too.
23
+
18
24
  Start with [AWS SDK interception](https://yulinsim.dev/sdk/) when the code under test already uses an
19
25
  AWS SDK client. Yulin intercepts the client's `send` calls and returns responses from a simulated
20
26
  service. The application code continues to use the AWS SDK normally.
@@ -81,4 +87,5 @@ credential, or run other work that depends on time passing.
81
87
  - [Non-AWS dependencies](https://yulinsim.dev/non-aws-dependencies/ "Dependencies Yulin does not simulate usage docs")
82
88
  - [Serving on localhost](https://yulinsim.dev/serve/ "Serving simulated AWS on localhost usage docs")
83
89
  - [Simulated time](https://yulinsim.dev/time/ "Simulated time usage docs")
90
+ - [Test suite setup](https://yulinsim.dev/testing/ "Sharing one Yulin environment across a test suite")
84
91
  - [Terraform](https://yulinsim.dev/terraform/ "Deploying Terraform into simulated AWS usage docs")
@@ -37,10 +37,15 @@ The skill tells an agent how to:
37
37
  - control simulated time
38
38
  - inspect simulated state in assertions
39
39
  - match simulated service errors by `name`
40
- - share an expensive deployment across tests in one file
40
+ - share one simulated deployment and SDK interception across a whole test suite
41
+ - give tests that control simulated time their own isolated Yulin environment
41
42
  - invoke code through a simulated Lambda function with its configured role and environment
42
43
  - treat unsupported behaviour as a gap to report, not behaviour to guess
43
44
 
45
+ The [test suite setup guide](https://yulinsim.dev/testing/) states the default explicitly. An agent
46
+ should set Yulin up once for the suite and let tests share it as they would share an AWS account or a
47
+ container-based simulator. Tests that move the shared simulated clock are the main exception.
48
+
44
49
  ## Give the agent access to the API docs
45
50
 
46
51
  The skill covers testing choices. The service guides document Yulin's APIs and supported AWS
@@ -3,6 +3,18 @@
3
3
  `SimSdk` routes AWS SDK for JavaScript v3 commands to Yulin. Use it to test code that already sends
4
4
  commands through AWS SDK clients.
5
5
 
6
+ ## Install interception in suite setup
7
+
8
+ Application tests should normally create one `SimSdk` in Vitest suite setup and keep its class
9
+ interceptions installed for the whole suite. Every SDK client then reaches the same simulated state,
10
+ including clients created in different test files.
11
+
12
+ The [test suite setup guide](https://yulinsim.dev/testing/) shows how to share one in-process
13
+ environment across Vitest files. It also covers the worker and isolation settings this requires.
14
+ A separate `SimSdk` for each test or file remains supported when a case needs an empty environment.
15
+ Tests that control the simulation's clock should use a separate `SimSdk` so their time changes cannot
16
+ affect the shared suite.
17
+
6
18
  ## Intercept a client
7
19
 
8
20
  Create a `SimSdk`, intercept a client class, and run the code under test:
@@ -147,6 +159,9 @@ Restore an interception before later code needs the client's original `send` met
147
159
  - Call `simSdk.restoreAll()` to restore every client intercepted by that `SimSdk`.
148
160
  - Declare `SimSdk` or an interception handle with `using` to restore it when the scope ends.
149
161
 
162
+ A suite-wide interception stays installed until the test worker exits. Do not restore it in a
163
+ per-file `afterAll`, since later files use the same interception and simulated state.
164
+
150
165
  ## Limit the intercepted commands
151
166
 
152
167
  An interception handles every command by default. Pass an allow list when a test should accept only
@@ -1,8 +1,8 @@
1
1
  # Simulated ACM
2
2
 
3
- Yulin includes a simulated AWS Certificate Manager (ACM) for tests and local development. In this
4
- guide, you'll request certificates, configure DNS validation, filter certificate lists, and use ACM
5
- with simulated CloudFormation.
3
+ Yulin simulates AWS Certificate Manager (ACM) in memory. You can request and inspect certificates,
4
+ exercise DNS validation against simulated Route53, and deploy certificate resources through
5
+ simulated CloudFormation.
6
6
 
7
7
  ## Prerequisites
8
8
 
@@ -12,9 +12,8 @@ with simulated CloudFormation.
12
12
 
13
13
  ## Request a certificate
14
14
 
15
- 1. Create a `SimAws` instance and get a simulated ACM client.
16
- 2. Call `requestCertificate` with a `RequestCertificateCommand`.
17
- 3. Call `listCertificates` with a `ListCertificatesCommand` to confirm the certificate exists.
15
+ Create a `SimAws` instance, request a certificate, then list the certificates in that account and
16
+ region:
18
17
 
19
18
  ```typescript sim-acm-request-certificate
20
19
  /**
@@ -145,9 +144,8 @@ for (const validation of domainValidationOptions) {
145
144
  }
146
145
  ```
147
146
 
148
- For DNS validation, simulated ACM returns CNAME validation records for the primary domain and each
149
- subject alternative name. The records are deterministic, which makes them suitable for assertions in
150
- tests.
147
+ For DNS validation, ACM returns a deterministic CNAME record for the primary domain and each subject
148
+ alternative name. Tests can assert on these records directly.
151
149
 
152
150
  For EMAIL validation, the validation method is recorded but no DNS resource record is returned.
153
151
 
@@ -187,11 +185,8 @@ console.log(validation?.ResourceRecord);
187
185
 
188
186
  ## Wait for certificate issuance
189
187
 
190
- Requested certificates start in `PENDING_VALIDATION` status. Simulated ACM schedules background work
191
- to move them to `ISSUED`.
192
-
193
- If your test needs the issued state, wait for background tasks to complete before describing the
194
- certificate.
188
+ Requested certificates start as `PENDING_VALIDATION`. Wait for background tasks before asserting
189
+ that a certificate is `ISSUED`.
195
190
 
196
191
  > **Note:** Where a simulated Route53 hosted zone covers the certificate domain, issuance waits for
197
192
  > DNS validation first.
@@ -233,19 +228,14 @@ console.log(describeOutput.Certificate?.IssuedAt);
233
228
 
234
229
  ## Validate a certificate against simulated Route53
235
230
 
236
- Real ACM issues a DNS-validated certificate only once the CNAME it requests is resolvable. Simulated
237
- ACM does the same, but only where the simulation can answer for the domain.
238
-
239
- By default, the rules are:
231
+ ACM requires the validation CNAME when simulated Route53 is authoritative for the domain. The
232
+ default rules are:
240
233
 
241
234
  - If a simulated Route53 hosted zone covers the certificate domain, the certificate waits for its validation record.
242
235
  - If no hosted zone covers the domain, the certificate is issued as soon as background tasks drain.
243
236
 
244
- Templates commonly reference hosted zones managed by another team or another tool. Those
245
- certificates keep working here because the simulation holds no zone for their domain.
246
-
247
- [Two methods override that default](#override-when-validation-is-required) where it doesn't suit
248
- your test.
237
+ This lets templates refer to externally managed hosted zones without leaving certificates pending.
238
+ [Two methods override the default](#override-when-validation-is-required).
249
239
 
250
240
  ```typescript sim-acm-dns-validation
251
241
  /**
@@ -322,11 +312,9 @@ const issuedOutput = await simAws.acm().describeCertificate(
322
312
  console.log(issuedOutput.Certificate?.Status); // ISSUED
323
313
  ```
324
314
 
325
- Each domain on a certificate is validated separately. A certificate with subject alternative names
326
- is issued only once every domain that needs DNS validation has its record. Domains that are not
327
- covered by any hosted zone don't need anything published for them. Until all domains are validated,
328
- `DescribeCertificateCommand` reports `SUCCESS` for validated domains and `PENDING_VALIDATION` for
329
- the rest.
315
+ Each domain is validated separately. A certificate is issued when every domain covered by a
316
+ simulated hosted zone has its validation record. `DescribeCertificateCommand` reports `SUCCESS` for
317
+ validated domains and `PENDING_VALIDATION` for the rest.
330
318
 
331
319
  Hosted zones are looked up across every simulated account, matching real ACM validating against
332
320
  public DNS. A certificate in one account can be validated by a hosted zone in another.
@@ -554,18 +542,16 @@ await scopedAcm.requestCertificate(
554
542
  );
555
543
  ```
556
544
 
557
- Each `SimAws` instance has its own isolated state. Create a fresh instance per test or share one across related local setup.
545
+ Each `SimAws` instance has its own isolated state. Application tests should normally use the instance
546
+ from their shared [test suite setup](https://yulinsim.dev/testing/). A fresh instance remains useful
547
+ when a test specifically needs an empty simulated account.
558
548
 
559
549
  ## Register a certificate with a chosen ARN
560
550
 
561
- `RequestCertificateCommand` allocates its own certificate ARN, as real ACM does, and takes none from
562
- you. When something else already decided the ARN, register the certificate as part of your test setup
563
- instead.
564
-
565
- The usual reason is a CDK app that creates its certificate in one stack and uses it in another. The
566
- ARN crosses between the two as a plain string, and the stack using it carries that ARN into its
567
- synthesized template. Simulated CloudFront checks the certificate before it creates a Distribution,
568
- so registering the certificate first lets the template deploy as it is, with no rewriting.
551
+ `RequestCertificateCommand` allocates the ARN. If a fixture or another stack already provides the
552
+ ARN, register that certificate during test setup. This is useful when one CDK stack creates a
553
+ certificate and another uses its ARN. Simulated CloudFront can then resolve the certificate without
554
+ changing the synthesized template.
569
555
 
570
556
  ```typescript sim-acm-register-certificate
571
557
  /**
@@ -791,9 +777,7 @@ A `HostedZoneId` that names a hosted zone the simulator doesn't hold is skipped
791
777
 
792
778
  If a hosted zone covers the domain but the validation record never appears, the stack fails rather than hanging. Real CloudFormation sits in `CREATE_IN_PROGRESS` for hours before timing out, which is of little use in a test. The resource fails immediately and names the record it waited for.
793
779
 
794
- ## Available functionality
795
-
796
- Simulated ACM supports:
780
+ ## Supported operations
797
781
 
798
782
  - `RequestCertificateCommand`, `DescribeCertificateCommand`, and `ListCertificatesCommand`
799
783
  - DNS validation against records in simulated Route53
@@ -1,16 +1,11 @@
1
1
  # Simulated API Gateway REST APIs
2
2
 
3
- Yulin includes a simulated API Gateway v1 service, reachable as `simAws.apiGateway()`. It covers the
4
- REST API resource tree, the methods declared on it, a Lambda proxy integration behind each method,
5
- and the deployments and stages that publish them. REST-API-specific types are imported from the
6
- `@kensio/yulin/apigateway` subpath.
3
+ Yulin simulates API Gateway REST APIs (the v1 API) with Lambda proxy integrations. Use
4
+ `simAws.apiGateway()` directly or intercept an `APIGatewayClient`. REST API types are available from
5
+ `@kensio/yulin/apigateway`.
7
6
 
8
- This is the v1 service. HTTP APIs are v2, on a separate SDK client, and they are documented under
9
- [API Gateway HTTP APIs](https://yulinsim.dev/services/apigatewayv2/). The two hold separate state. A REST API created here
10
- stays out of `simAws.apiGatewayV2()`.
11
-
12
- A handler behind a REST API can be tested against a real HTTP request, with no hand-built event to
13
- keep in step.
7
+ HTTP APIs use the separate `simAws.apiGatewayV2()` service. See
8
+ [API Gateway HTTP APIs](https://yulinsim.dev/services/apigatewayv2/).
14
9
 
15
10
  ## Creating a REST API
16
11
 
@@ -48,13 +43,13 @@ console.log(typeof created.rootResourceId);
48
43
  // "string"
49
44
  ```
50
45
 
51
- A REST API name identifies nothing. Two APIs in one account and region may share a name, and the id
52
- is what tells them apart. Hold the id the create returns.
46
+ A REST API name is a label. Two APIs in the same account and region may share a name. Use the ID
47
+ returned by `CreateRestApiCommand` to address the API.
53
48
 
54
49
  ## Building the path tree
55
50
 
56
- A REST API path is a chain of resources, each holding one segment. `CreateResourceCommand` adds a
57
- segment under a parent and reports the full path its place in the tree gives it.
51
+ A REST API stores its path as a tree of resources. Each resource holds one segment.
52
+ `CreateResourceCommand` adds a segment below a parent and returns its full path.
58
53
 
59
54
  ```typescript sim-apigateway-resource-tree
60
55
  /**
@@ -103,18 +98,17 @@ console.log(listed.items.map((resource) => resource.path));
103
98
  // [ "/", "/orders", "/orders/{orderId}" ]
104
99
  ```
105
100
 
106
- A segment is a literal such as `orders`, a path parameter such as `{orderId}`, or a greedy path
107
- parameter such as `{proxy+}`. A greedy segment matches the rest of the request path. A resource
108
- holding one therefore takes no children, and adding under it is refused.
101
+ A segment may be literal (`orders`), a path parameter (`{orderId}`), or a greedy path parameter
102
+ (`{proxy+}`). A greedy segment matches the rest of the path and cannot have children.
109
103
 
110
104
  Deleting a resource deletes everything under it, the way real API Gateway does. The root resource
111
105
  stays, because every REST API has one.
112
106
 
113
107
  ## Methods and their integrations
114
108
 
115
- A method is declared on a resource with `PutMethodCommand`, and what it does with a request goes
116
- behind it with `PutIntegrationCommand`. Both address the same resource id and HTTP method, since a
117
- REST API method has no id of its own.
109
+ `PutMethodCommand` declares a method on a resource. `PutIntegrationCommand` connects that method to
110
+ a Lambda function. Both commands use the resource ID and HTTP method because a method has no ID of
111
+ its own.
118
112
 
119
113
  ```typescript sim-apigateway-method-integration
120
114
  /**
@@ -184,18 +178,15 @@ console.log(method.methodIntegration?.uri);
184
178
  // echoed back as one line, the way it was configured
185
179
  ```
186
180
 
187
- The integration URI is written either as the bare function ARN, which CDK emits, or wrapped in the
188
- API Gateway invoke path above, which CloudFormation templates and OpenAPI documents emit. Both reach
189
- the same function, and the string is echoed back as it was configured, the way real API Gateway does.
190
- A version or alias qualifier on the end of the ARN is kept. An integration built on an alias
191
- therefore follows that alias.
181
+ The integration URI may be a function ARN or the API Gateway invocation URI shown above. CDK emits
182
+ the first form. CloudFormation templates and OpenAPI documents commonly emit the second. A version
183
+ or alias qualifier is preserved. An integration that names an alias follows that alias.
192
184
 
193
185
  Deleting a method deletes its integration, because a REST API integration is part of the method.
194
186
 
195
187
  ## Deployments and stages
196
188
 
197
- A REST API has an invocation URL once a stage exists, and every stage is the first path segment of
198
- that URL. An HTTP API can serve a `$default` stage at the root, and a REST API always carries the
189
+ A REST API gets an invocation URL when it has a stage. The stage name is always the first path
199
190
  segment.
200
191
 
201
192
  ```typescript sim-apigateway-deploy-stage
@@ -249,15 +240,13 @@ console.log(restApi?.invokeUrl("prod"));
249
240
  `invokeUrl` is a simulator accessor. Real API Gateway reports no endpoint for a REST API and leaves
250
241
  callers to build the URL themselves. CDK's `RestApi.urlForPath` builds the same one.
251
242
 
252
- Real API Gateway freezes the resources and methods into a deployment, and an edit made afterwards
253
- reaches no client until another deployment is created. Here a stage serves the API's current
254
- resources. A test that edits a method sees the change straight away, with no redeployment in
255
- between. That is the one place this departs from AWS.
243
+ Stages serve the API's current resources and methods. Changes take effect immediately. AWS freezes
244
+ the configuration in a deployment and requires another deployment before changes become visible.
256
245
 
257
246
  ## Serving a request
258
247
 
259
- A request to the stage's invoke URL walks the resource tree to a method and invokes that method's
260
- integration, and the handler's response becomes the HTTP response.
248
+ A request to a stage selects a resource and method, then invokes its integration. The Lambda result
249
+ becomes the HTTP response.
261
250
 
262
251
  ```typescript sim-apigateway-serve
263
252
  /**
@@ -300,14 +289,13 @@ console.log(await response.text());
300
289
  await srv.close();
301
290
  ```
302
291
 
303
- `simRestApiLambdaProxyFactory` builds the function, the resources, the method, the integration, the
304
- invoke permission and the deployment in one call. A test about serving wants all of them and is
305
- about none of them. A test about the commands themselves sends them one at a time.
292
+ `simRestApiLambdaProxyFactory` creates a ready-to-call REST API in one operation. It creates the
293
+ function, resources, method, integration, invoke permission and deployment.
306
294
 
307
295
  ### The event a handler receives
308
296
 
309
- A REST API sends payload format 1.0. It carries both a single-value and a multi-value map for the
310
- headers and the query string, and it sends `null` for an empty map where format 2.0 omits the field:
297
+ A REST API sends a payload format 1.0 event. Headers and query parameters appear in both single-value
298
+ and multi-value maps. Empty values use `null`:
311
299
 
312
300
  | Field | Empty case |
313
301
  | ---------------------------------------------------------- | ---------- |
@@ -319,18 +307,15 @@ headers and the query string, and it sends `null` for an empty map where format
319
307
  client asked for, stage segment and all. A handler behind a `{proxy+}` reads `resource` to tell which
320
308
  template caught its request.
321
309
 
322
- Each format's handler reads the other format's event wrongly. One function behind both an HTTP API
323
- and a REST API therefore has to pick a side.
310
+ REST API and HTTP API events have different shapes. A handler shared by both must handle both
311
+ formats explicitly.
324
312
 
325
313
  ### The response a handler returns
326
314
 
327
- A REST API proxy integration takes one shape. A result carrying a numeric `statusCode` becomes the
328
- response, and `multiValueHeaders` sends a header more than once. Anything else is a 502 with
329
- `Internal server error`. That is what real API Gateway answers when it cannot read the integration
330
- response. Payload format 2.0 is the lenient one, wrapping an unrecognised value in a 200, and a
331
- handler relying on that behaves differently here for the same reason it does on AWS.
315
+ A REST API proxy result must contain a numeric `statusCode`. Use `multiValueHeaders` to send the
316
+ same header more than once. An invalid result produces a 502 response with `Internal server error`.
332
317
 
333
- ### Answers when the request matches nothing
318
+ ### Responses before an integration runs
334
319
 
335
320
  | Case | Answer |
336
321
  | --------------------------------------------- | ---------------------------------- |
@@ -340,8 +325,8 @@ handler relying on that behaves differently here for the same reason it does on
340
325
  | No integration, no function, or no permission | 502 `Internal server error` |
341
326
  | The handler threw | 502 `Internal server error` |
342
327
 
343
- `Missing Authentication Token` is the wording real API Gateway is well known for. It answers a path
344
- that matched nothing just as much as one that needed credentials.
328
+ API Gateway uses `Missing Authentication Token` for an unmatched path or method, even when the
329
+ request did not require authentication.
345
330
 
346
331
  ### The invoke permission
347
332
 
@@ -467,9 +452,8 @@ is named `ANY`, whatever method the client sent.
467
452
 
468
453
  Real `CreateStage` carries no method settings. AWS sets them with `UpdateStage` patch operations,
469
454
  which are outside this simulation, or from an `AWS::ApiGateway::Stage`. The `methodSettings` input
470
- above is this simulator's own, so that a test can throttle a stage without a template, and the SDK's
471
- `CreateStageCommand` declares no such member. `GetStage` reports the settings the way AWS reports
472
- them.
455
+ above is specific to Yulin and lets a test configure throttling without a template. The SDK's
456
+ `CreateStageCommand` has no such member. `GetStage` reports the settings in the AWS response shape.
473
457
 
474
458
  ## Authorizing a method
475
459
 
@@ -482,8 +466,8 @@ function answers an IAM policy document, evaluated for `execute-api:Invoke` agai
482
466
  request being made. Whatever `context` it returns reaches the handler.
483
467
 
484
468
  A `REQUEST` authorizer sends the whole request to its function (see
485
- [A REQUEST authorizer](#a-request-authorizer)), so it can identify a caller by several headers
486
- together or by the query string. It answers the same policy document.
469
+ [A REQUEST authorizer](#a-request-authorizer)). It can identify a caller from several headers or
470
+ query parameters. It answers the same policy document.
487
471
 
488
472
  ```typescript sim-apigateway-token-authorizer
489
473
  /**
@@ -674,9 +658,8 @@ left out.
674
658
  | `pathParameters`, `stageVariables` | What the resource path captured, and the stage's variables |
675
659
  | `requestContext` | The same block a handler gets, without the `authorizer` member |
676
660
 
677
- The maps are empty objects where the request supplied nothing. An integration event sends `null`
678
- there, and AWS's own example of the authorizer event sends `{}`, so a function reading
679
- `event.queryStringParameters.plan` finds nothing rather than throwing.
661
+ The maps are empty objects when the request supplies no values. Integration events use `null` for
662
+ the same fields. AWS's authorizer event examples use `{}`.
680
663
 
681
664
  ### Answering with a policy
682
665
 
@@ -710,8 +693,8 @@ authorizer's own logs, and IAM never sees it.
710
693
  ### The context the handler receives
711
694
 
712
695
  `context` reaches the handler under `requestContext.authorizer`, flattened alongside `principalId`.
713
- Payload format 2.0 keeps the context in a block of its own, so a handler moved between a REST API and
714
- an HTTP API reads a different shape.
696
+ Payload format 2.0 keeps the context in its own block. A handler shared with an HTTP API must handle
697
+ both shapes.
715
698
 
716
699
  An open method has no caller to describe, and leaves `requestContext.authorizer` out of the event
717
700
  altogether.
@@ -976,9 +959,8 @@ An admitted caller reaches the handler under `requestContext.identity`.
976
959
  | `user` | The caller's ARN |
977
960
  | `userArn` | The caller's ARN |
978
961
 
979
- Real API Gateway puts the unique id of the principal in `caller` and `user`, such as `AIDA...` for a
980
- User. A request carries no such id into the simulation, so the ARN identifying the caller goes in
981
- every field that can be filled from it.
962
+ Real API Gateway puts the principal's unique ID in `caller` and `user`, such as `AIDA...` for a user.
963
+ The simulation receives only the caller ARN and uses it for each identity field it can populate.
982
964
 
983
965
  A method of any other authorization type leaves those four `null`, and so does an `AWS_IAM` method
984
966
  called by a principal with no ARN behind it. `accessKey`, `apiKey`, `apiKeyId`, `principalOrgId` and
@@ -987,8 +969,8 @@ request itself and are filled for every method.
987
969
 
988
970
  ## Authorizing a method with a user pool
989
971
 
990
- A `COGNITO_USER_POOLS` authorizer verifies the token itself against the keys the user pools it names
991
- publish. Nothing is invoked, so there is no function to write and no policy to answer.
972
+ A `COGNITO_USER_POOLS` authorizer verifies the token against the keys published by its user pools.
973
+ It uses no Lambda function or returned policy.
992
974
  `CreateAuthorizerCommand` takes the pools as `providerARNs`, and `PutMethodCommand` binds the
993
975
  authorizer to a method with `authorizationType: "COGNITO_USER_POOLS"`.
994
976
 
@@ -1118,8 +1100,8 @@ two groups arrive as `[Admins Readers]`.
1118
1100
  }
1119
1101
  ```
1120
1102
 
1121
- An HTTP API puts the same claims under `requestContext.authorizer.jwt.claims`, with the scopes
1122
- beside them, so a handler moved between the two reads a different shape.
1103
+ An HTTP API puts the same claims under `requestContext.authorizer.jwt.claims`, with scopes beside
1104
+ them. A handler shared by both API types must handle both shapes.
1123
1105
 
1124
1106
  ### Scopes
1125
1107
 
@@ -1141,9 +1123,8 @@ token, since an id token carries no `scope` claim at all.
1141
1123
  | A token that has expired, or has no `exp` at all | 401 `Unauthorized` |
1142
1124
  | A verified token claiming none of the method's scopes | 403 `User is not authorized to access this resource` |
1143
1125
 
1144
- Every refusal up to and including the claim checks is the same 401, so a client learns that its
1145
- token was not accepted and nothing about which check it failed. An unmet scope is the one 403: the
1146
- token was accepted, and it does not allow this method.
1126
+ Token parsing and claim failures return the same 401 response. An unmet scope returns 403 because
1127
+ the token was valid but did not allow the method.
1147
1128
 
1148
1129
  The token is taken with or without the `Bearer` scheme in front of it. The identity source is one
1149
1130
  header, as it is for a `TOKEN` authorizer.
@@ -1503,7 +1484,7 @@ AWS sorts what an import finds into three categories, and the third is valid Ope
1503
1484
  leaves unsupported without a request validator. AWS ignores it silently, and so does this:
1504
1485
  `requestBody`, the content schemas under `responses`, `components.schemas`, and an operation's
1505
1486
  `parameters`, `summary`, `description` and `tags`. Request validation is refused at the root of the
1506
- document, so a request whose body contradicts a declared schema still reaches the handler.
1487
+ document. A request body is not checked against the declared schema.
1507
1488
  `operationId` is ignored too, since it only supplies the `OperationName` a method carries for
1508
1489
  documentation.
1509
1490
 
@@ -1902,8 +1883,8 @@ and `Stage`, including the template CDK synthesizes from a `RestApi` or a `Lambd
1902
1883
  that is the form the platform's `Headers` hands over. Real API Gateway reports each separately.
1903
1884
  - A Lambda authorizer's `context` reaches the handler as the authorizer returned it. AWS accepts a
1904
1885
  string, a number or a boolean for each value, and how it renders them is not published.
1905
- - A Cognito authorizer reads a `providerARN` for the pool id it names, so a pool in another account
1906
- is verified against whenever this simulation holds it. `identityValidationExpression`, which real
1886
+ - A Cognito authorizer resolves a `providerARN` by pool ID across the simulated accounts.
1887
+ `identityValidationExpression`, which real
1907
1888
  API Gateway matches a token against before verifying it, is outside this.
1908
1889
  - Binary media types negotiated by `Accept`, CORS preflight and gateway responses are outside this.
1909
1890
  A response body is still base64 decoded when the handler says `isBase64Encoded`.