@kensio/yulin 1.20.15 → 1.20.16
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 +13 -0
- package/dist/sdk/module/sim-sdk-module-client-interceptor.d.ts +5 -0
- package/dist/sdk/module/sim-sdk-module-client-interceptor.js +14 -0
- package/dist/sdk/module/sim-sdk-static-client-factory.d.ts +26 -0
- package/dist/sdk/module/sim-sdk-static-client-factory.js +59 -0
- package/dist/sdk/send-patch.d.ts +8 -0
- package/dist/sdk/send-patch.js +11 -0
- package/dist/service/aws/caller/sim-aws-caller-resolver.d.ts +28 -6
- package/dist/service/aws/caller/sim-aws-caller-resolver.js +18 -11
- package/dist/service/aws/caller/sim-aws-caller.d.ts +9 -0
- package/dist/service/aws/caller/sim-aws-resolved-caller.d.ts +19 -0
- package/dist/service/aws/caller/sim-aws-resolved-caller.js +26 -0
- package/dist/service/aws/factory/sim-aws-account-region-service-builder.d.ts +6 -0
- package/dist/service/aws/factory/sim-aws-account-region-service-builder.js +3 -0
- package/dist/service/aws/factory/sim-aws-account-service-cache.d.ts +6 -0
- package/dist/service/aws/factory/sim-aws-account-service-cache.js +5 -0
- package/dist/service/aws/factory/sim-aws-service-factory.d.ts +5 -0
- package/dist/service/aws/factory/sim-aws-service-factory.js +6 -0
- package/dist/service/aws/sim-aws-properties.d.ts +15 -0
- package/dist/service/aws/sim-aws.js +2 -1
- package/dist/service/iam/authorize/context/sim-iam-auth-z-caller-context-builder.d.ts +30 -7
- package/dist/service/iam/authorize/context/sim-iam-auth-z-caller-context-builder.js +20 -11
- package/dist/service/iam/authorize/context/sim-iam-auth-z-context-builder.d.ts +2 -1
- package/dist/service/iam/authorize/context/sim-iam-auth-z-context-builder.js +5 -1
- package/dist/service/iam/authorize/match/sim-iam-policy-principal-matcher.d.ts +10 -0
- package/dist/service/iam/authorize/match/sim-iam-policy-principal-matcher.js +15 -3
- package/dist/service/iam/authorize/sim-iam-account-auth-z.d.ts +11 -4
- package/dist/service/iam/authorize/sim-iam-account-auth-z.js +5 -3
- package/dist/service/iam/authorize/sim-iam-authorizer.d.ts +2 -1
- package/dist/service/iam/authorize/sim-iam-authorizer.js +1 -0
- package/dist/service/iam/role/sim-iam-role-with-policy.factory.d.ts +9 -0
- package/dist/service/iam/role/sim-iam-role-with-policy.factory.js +4 -2
- package/dist/service/iam/sim-iam-account-parts.d.ts +8 -0
- package/dist/service/iam/sim-iam-account-parts.js +1 -0
- package/dist/service/sts/auth-z/assume-role-auth-z-coordinator.d.ts +9 -2
- package/dist/service/sts/auth-z/assume-role-auth-z-coordinator.js +2 -2
- package/dist/service/sts/auth-z/assume-role-source-account-auth-z.d.ts +10 -4
- package/dist/service/sts/auth-z/assume-role-source-account-auth-z.js +8 -7
- package/dist/service/sts/auth-z/assume-role-target-auth-z.d.ts +2 -2
- package/dist/service/sts/auth-z/assume-role-trust-policy-authorizer.d.ts +6 -2
- package/dist/service/sts/auth-z/assume-role-trust-policy-authorizer.js +14 -11
- package/dist/service/sts/command/assume-role/assume-role.handler.d.ts +5 -1
- package/dist/service/sts/command/assume-role/assume-role.handler.js +5 -2
- package/dist/service/sts/command/get-caller-identity/get-caller-identity.handler.d.ts +5 -1
- package/dist/service/sts/command/get-caller-identity/get-caller-identity.handler.js +4 -1
- package/dist/service/sts/service-role/sim-service-role.js +5 -1
- package/dist/service/sts/sim-sts.d.ts +9 -1
- package/dist/service/sts/sim-sts.js +4 -0
- package/docs/README.md +54 -0
- package/docs/ai-skill/README.md +69 -0
- package/docs/cli/README.md +284 -0
- package/docs/factories/README.md +140 -0
- package/docs/lint/README.md +176 -0
- package/docs/non-aws-dependencies/README.md +261 -0
- package/docs/sdk/README.md +272 -0
- package/docs/serve/README.md +1126 -0
- package/docs/services/acm/README.md +823 -0
- package/docs/services/apigateway/README.md +1919 -0
- package/docs/services/apigatewayv2/README.md +2678 -0
- package/docs/services/athena/README.md +1044 -0
- package/docs/services/bedrock/README.md +350 -0
- package/docs/services/cloudformation/README.md +3440 -0
- package/docs/services/cloudfront/README.md +2874 -0
- package/docs/services/cloudwatch/README.md +420 -0
- package/docs/services/cognito/README.md +4923 -0
- package/docs/services/dynamodb/README.md +3784 -0
- package/docs/services/ecr/README.md +302 -0
- package/docs/services/ecs/README.md +2010 -0
- package/docs/services/elbv2/README.md +1897 -0
- package/docs/services/eventbridge/README.md +1110 -0
- package/docs/services/firehose/README.md +805 -0
- package/docs/services/glue/README.md +501 -0
- package/docs/services/iam/README.md +1283 -0
- package/docs/services/kinesis/README.md +454 -0
- package/docs/services/kms/README.md +659 -0
- package/docs/services/lambda/README.md +3645 -0
- package/docs/services/logs/README.md +868 -0
- package/docs/services/organizations/README.md +574 -0
- package/docs/services/personalize/README.md +835 -0
- package/docs/services/rekognition/README.md +1070 -0
- package/docs/services/route53/README.md +1783 -0
- package/docs/services/s3/README.md +3024 -0
- package/docs/services/scheduler/README.md +806 -0
- package/docs/services/secretsmanager/README.md +667 -0
- package/docs/services/ses/README.md +1011 -0
- package/docs/services/sns/README.md +1904 -0
- package/docs/services/sqs/README.md +1185 -0
- package/docs/services/ssm/README.md +1072 -0
- package/docs/services/stepfunctions/README.md +1332 -0
- package/docs/services/sts/README.md +245 -0
- package/docs/services/wafv2/README.md +1378 -0
- package/docs/terraform/README.md +248 -0
- package/docs/time/README.md +284 -0
- package/llms.txt +59 -0
- package/package.json +7 -4
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
# Simulated AWS SDK
|
|
2
|
+
|
|
3
|
+
Yulin can intercept AWS SDK clients and route their Commands to simulated AWS services. The code
|
|
4
|
+
under test uses the AWS SDK as it would in production, and needs no knowledge of the simulator.
|
|
5
|
+
|
|
6
|
+
This is the recommended way to test implementation code that already uses the AWS SDK. Direct
|
|
7
|
+
interaction with `SimAws` remains useful for seeding and inspecting simulated state from within
|
|
8
|
+
tests.
|
|
9
|
+
|
|
10
|
+
## How it works
|
|
11
|
+
|
|
12
|
+
`SimSdk` replaces the `send` method of an intercepted SDK client. Each sent Command is routed by
|
|
13
|
+
name to the matching operation of a simulated AWS service, and the result comes back to the caller
|
|
14
|
+
as a normal SDK response. Every Command is served in process.
|
|
15
|
+
|
|
16
|
+
Every `SimSdk` owns a simulated AWS environment. You can let it create its own, or give it an
|
|
17
|
+
existing one to share:
|
|
18
|
+
|
|
19
|
+
- `new SimSdk()` creates an isolated `SimAws` internally, available as `simSdk.simAws`.
|
|
20
|
+
- `new SimSdk({ simAws })` wraps a `SimAws` you already have.
|
|
21
|
+
|
|
22
|
+
## Basic usage
|
|
23
|
+
|
|
24
|
+
Intercept an SDK client class, then use the SDK as normal:
|
|
25
|
+
|
|
26
|
+
```typescript sim-sdk-intercept-s3
|
|
27
|
+
/**
|
|
28
|
+
* Intercepting the S3 SDK client with simulated AWS behind it.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import {
|
|
32
|
+
CreateBucketCommand,
|
|
33
|
+
GetObjectCommand,
|
|
34
|
+
PutObjectCommand,
|
|
35
|
+
S3Client,
|
|
36
|
+
} from "@aws-sdk/client-s3";
|
|
37
|
+
import { SimSdk } from "@kensio/yulin/sdk";
|
|
38
|
+
|
|
39
|
+
const simSdk = new SimSdk();
|
|
40
|
+
simSdk.intercept(S3Client); // Intercepts every instance of the class.
|
|
41
|
+
|
|
42
|
+
// From here on, this is ordinary AWS SDK code.
|
|
43
|
+
const s3Client = new S3Client({ region: "eu-west-2" });
|
|
44
|
+
await s3Client.send(new CreateBucketCommand({ Bucket: "foo-bucket" }));
|
|
45
|
+
await s3Client.send(
|
|
46
|
+
new PutObjectCommand({
|
|
47
|
+
Bucket: "foo-bucket",
|
|
48
|
+
Key: "hello.txt",
|
|
49
|
+
Body: "Hello, world!",
|
|
50
|
+
}),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const output = await s3Client.send(
|
|
54
|
+
new GetObjectCommand({ Bucket: "foo-bucket", Key: "hello.txt" }),
|
|
55
|
+
);
|
|
56
|
+
console.log(await output.Body?.transformToString()); // "Hello, world!"
|
|
57
|
+
|
|
58
|
+
simSdk.restoreAll();
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
You can intercept a client class or a client instance:
|
|
62
|
+
|
|
63
|
+
- **A class** (`simSdk.intercept(S3Client)`) intercepts every instance of it, including instances
|
|
64
|
+
the code under test constructs later. This is the most common choice.
|
|
65
|
+
- **An instance** (`simSdk.intercept(s3Client)`) intercepts only that instance. Use it when one
|
|
66
|
+
client should hit the simulator and the others are handled some other way.
|
|
67
|
+
|
|
68
|
+
A client can only have one interception at a time. Intercepting an already-intercepted client
|
|
69
|
+
throws a diagnostic error, and the existing interception stays in place.
|
|
70
|
+
|
|
71
|
+
## Account and Region scope
|
|
72
|
+
|
|
73
|
+
Each sent Command resolves its own simulated Account and Region scope:
|
|
74
|
+
|
|
75
|
+
1. The **Region** comes from the sending client's own configuration, such as
|
|
76
|
+
`new S3Client({ region: "eu-west-2" })`, falling back to the simulation default.
|
|
77
|
+
2. The **Account** comes from the ambient `simAws.runAs(...)` caller when one is set, falling back
|
|
78
|
+
to the simulation default Account.
|
|
79
|
+
|
|
80
|
+
The resolved caller reaches the simulated service, and simulated [IAM](https://yulinsim.dev/services/iam/)
|
|
81
|
+
authorization applies to it exactly as it does for direct sim service use. A caller without
|
|
82
|
+
permission for a Command is denied, as on real AWS. Where no caller can be identified, Commands run
|
|
83
|
+
as the simulation's `defaultCaller`, and as the default Account root where the simulation was given
|
|
84
|
+
none. See
|
|
85
|
+
[Name the caller a simulation uses by default](https://yulinsim.dev/services/iam/#name-the-caller-a-simulation-uses-by-default).
|
|
86
|
+
|
|
87
|
+
`runAs` runs a function with an ambient simulated caller, such as an IAM Role. Commands sent during
|
|
88
|
+
the run are attributed to that caller, with no changes to the client or the code under test:
|
|
89
|
+
|
|
90
|
+
```typescript sim-sdk-run-as
|
|
91
|
+
/**
|
|
92
|
+
* Attributing intercepted SDK Commands to a caller with runAs.
|
|
93
|
+
*/
|
|
94
|
+
|
|
95
|
+
import { CreateRoleCommand, PutRolePolicyCommand } from "@aws-sdk/client-iam";
|
|
96
|
+
import {
|
|
97
|
+
CreateBucketCommand,
|
|
98
|
+
ListBucketsCommand,
|
|
99
|
+
S3Client,
|
|
100
|
+
} from "@aws-sdk/client-s3";
|
|
101
|
+
import { SimAws } from "@kensio/yulin";
|
|
102
|
+
import { SimSdk } from "@kensio/yulin/sdk";
|
|
103
|
+
|
|
104
|
+
const simAws = new SimAws();
|
|
105
|
+
const simSdk = new SimSdk({ simAws });
|
|
106
|
+
|
|
107
|
+
// Seed a Bucket, and a Role allowed to list Buckets, in a simulated Account.
|
|
108
|
+
const account = simAws.account("222222222222");
|
|
109
|
+
await account
|
|
110
|
+
.s3()
|
|
111
|
+
.createBucket(new CreateBucketCommand({ Bucket: "team-bucket" }));
|
|
112
|
+
await account.iam().createRole(
|
|
113
|
+
new CreateRoleCommand({
|
|
114
|
+
RoleName: "TeamRole",
|
|
115
|
+
AssumeRolePolicyDocument: JSON.stringify({
|
|
116
|
+
Version: "2012-10-17",
|
|
117
|
+
Statement: {
|
|
118
|
+
Effect: "Allow",
|
|
119
|
+
Principal: { AWS: "arn:aws:iam::222222222222:root" },
|
|
120
|
+
Action: "sts:AssumeRole",
|
|
121
|
+
},
|
|
122
|
+
}),
|
|
123
|
+
}),
|
|
124
|
+
);
|
|
125
|
+
await account.iam().putRolePolicy(
|
|
126
|
+
new PutRolePolicyCommand({
|
|
127
|
+
RoleName: "TeamRole",
|
|
128
|
+
PolicyName: "list-buckets",
|
|
129
|
+
PolicyDocument: JSON.stringify({
|
|
130
|
+
Version: "2012-10-17",
|
|
131
|
+
Statement: {
|
|
132
|
+
Effect: "Allow",
|
|
133
|
+
Action: "s3:ListAllMyBuckets",
|
|
134
|
+
Resource: "*",
|
|
135
|
+
},
|
|
136
|
+
}),
|
|
137
|
+
}),
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const s3Client = new S3Client({ region: "us-east-1" });
|
|
141
|
+
simSdk.intercept(s3Client);
|
|
142
|
+
|
|
143
|
+
await simAws.runAs(
|
|
144
|
+
{ kind: "arn", arn: "arn:aws:iam::222222222222:role/TeamRole" },
|
|
145
|
+
async () => {
|
|
146
|
+
// Sent as the TeamRole caller: resolved in Account 222222222222 and
|
|
147
|
+
// authorized against the Role's simulated IAM permissions.
|
|
148
|
+
const output = await s3Client.send(new ListBucketsCommand({}));
|
|
149
|
+
console.log(output.Buckets); // [{ Name: "team-bucket" }]
|
|
150
|
+
},
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
simSdk.restoreAll();
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The ambient caller belongs to its own `SimAws` instance. Separate simulations in the same process
|
|
157
|
+
each keep their own.
|
|
158
|
+
|
|
159
|
+
## Restoring interception
|
|
160
|
+
|
|
161
|
+
Restoring puts back the client's real SDK `send`:
|
|
162
|
+
|
|
163
|
+
- `interception.restore()` restores one interception. `simSdk.intercept(...)` returns the handle.
|
|
164
|
+
- `simSdk.restoreAll()` restores everything intercepted through that `SimSdk`.
|
|
165
|
+
- `SimSdk` and interception handles are disposable. `using simSdk = new SimSdk();` restores
|
|
166
|
+
automatically at the end of the scope.
|
|
167
|
+
|
|
168
|
+
## Choosing Commands to intercept
|
|
169
|
+
|
|
170
|
+
By default every Command sent through an intercepted client is routed to the simulator. To
|
|
171
|
+
intercept only specific Commands, pass an allow list of Command classes or names:
|
|
172
|
+
`simSdk.intercept(s3Client, { commands: [GetObjectCommand] })`. Commands outside the allow list
|
|
173
|
+
throw a diagnostic error.
|
|
174
|
+
|
|
175
|
+
## The DynamoDB document client
|
|
176
|
+
|
|
177
|
+
`@aws-sdk/lib-dynamodb` takes plain JavaScript values. Application code writes
|
|
178
|
+
`{ id: "a", count: 1 }` where the base client wants `{ id: { S: "a" }, count: { N: "1" } }`.
|
|
179
|
+
Intercept the document client and its Commands reach simulated DynamoDB with the values already
|
|
180
|
+
converted. Code written against the document client runs against the simulator unchanged.
|
|
181
|
+
|
|
182
|
+
```typescript sim-sdk-document-client
|
|
183
|
+
/**
|
|
184
|
+
* An intercepted DynamoDB document client, writing plain JavaScript values.
|
|
185
|
+
*/
|
|
186
|
+
|
|
187
|
+
import { CreateTableCommand, DynamoDBClient } from "@aws-sdk/client-dynamodb";
|
|
188
|
+
import {
|
|
189
|
+
DynamoDBDocumentClient,
|
|
190
|
+
GetCommand,
|
|
191
|
+
PutCommand,
|
|
192
|
+
} from "@aws-sdk/lib-dynamodb";
|
|
193
|
+
|
|
194
|
+
import { SimSdk } from "@kensio/yulin/sdk";
|
|
195
|
+
|
|
196
|
+
using simSdk = new SimSdk();
|
|
197
|
+
|
|
198
|
+
const documents = DynamoDBDocumentClient.from(
|
|
199
|
+
new DynamoDBClient({ region: "eu-west-2" }),
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
// The document client is what gets intercepted, not the client it was built
|
|
203
|
+
// from.
|
|
204
|
+
simSdk.intercept(documents);
|
|
205
|
+
|
|
206
|
+
// A document client forwards a Command it has no document form of, so the
|
|
207
|
+
// table is created through the same client.
|
|
208
|
+
await documents.send(
|
|
209
|
+
new CreateTableCommand({
|
|
210
|
+
TableName: "OrdersTable",
|
|
211
|
+
KeySchema: [{ AttributeName: "orderId", KeyType: "HASH" }],
|
|
212
|
+
AttributeDefinitions: [{ AttributeName: "orderId", AttributeType: "S" }],
|
|
213
|
+
BillingMode: "PAY_PER_REQUEST",
|
|
214
|
+
}),
|
|
215
|
+
);
|
|
216
|
+
await simSdk.simAws.backgroundTasksComplete();
|
|
217
|
+
|
|
218
|
+
await documents.send(
|
|
219
|
+
new PutCommand({
|
|
220
|
+
TableName: "OrdersTable",
|
|
221
|
+
Item: { orderId: "order-1", total: 42, paid: true },
|
|
222
|
+
}),
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
const read = await documents.send(
|
|
226
|
+
new GetCommand({ TableName: "OrdersTable", Key: { orderId: "order-1" } }),
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
console.log(read.Item?.["total"]); // 42
|
|
230
|
+
console.log(read.Item?.["paid"]); // true
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
`DynamoDBDocumentClient.from(client)` builds a separate object of its own class. Intercepting the
|
|
234
|
+
base client therefore leaves Commands sent through the document client alone. Intercept the
|
|
235
|
+
document client. Both can be intercepted at once, and they reach
|
|
236
|
+
the same simulated tables, since the document client shares the base client's config and so
|
|
237
|
+
resolves the same Account and Region.
|
|
238
|
+
|
|
239
|
+
The real document client converts values in middleware, which runs inside the `send` that
|
|
240
|
+
interception replaces. So the conversion happens at the interception boundary instead, using the
|
|
241
|
+
option defaults `lib-dynamodb` sets (not the `util-dynamodb` ones). Which native types map to which
|
|
242
|
+
descriptors is in [the sim DynamoDB docs](https://yulinsim.dev/services/dynamodb/#the-document-client).
|
|
243
|
+
|
|
244
|
+
## Supported services and Commands
|
|
245
|
+
|
|
246
|
+
These simulated services support SDK interception: ACM, API Gateway v2, CloudFormation, CloudFront,
|
|
247
|
+
CloudWatch, CloudWatch Logs, Cognito, DynamoDB, DynamoDB Streams, ECS, Elastic Load Balancing v2,
|
|
248
|
+
EventBridge, EventBridge Scheduler, IAM, KMS, Lambda, Rekognition, Route53, S3, Secrets Manager,
|
|
249
|
+
SES, SNS, SQS, SSM, STS and WAFv2. Each service's own docs list the Commands it
|
|
250
|
+
simulates.
|
|
251
|
+
|
|
252
|
+
A gap in that coverage is refused on send, with a different error for each kind:
|
|
253
|
+
|
|
254
|
+
- A Command the simulated service doesn't support throws `SimSdkUnsupportedCommandError`, naming the
|
|
255
|
+
Command and listing the Commands that service does support.
|
|
256
|
+
- A client for an AWS service Yulin doesn't simulate at all throws `SimSdkUnknownServiceError`,
|
|
257
|
+
naming the service. There is no Command list to report, since no simulated service was resolved.
|
|
258
|
+
|
|
259
|
+
## Limitations
|
|
260
|
+
|
|
261
|
+
- Only `client.send(command)` is intercepted. SDK utilities that bypass `send`, such as
|
|
262
|
+
`getSignedUrl`, run against real AWS. Paginators and waiters go through `send`, so they work. Presigning works without interception. Point the client at the simulated endpoint, as
|
|
263
|
+
[the sim S3 presigned URL docs](https://yulinsim.dev/services/s3/#presigned-urls) show.
|
|
264
|
+
- Simulated errors carry SDK-shaped `name` and `$metadata`, but are not instances of the real SDK
|
|
265
|
+
exception classes. Match an error by its `error.name`. An `instanceof` check against the SDK class
|
|
266
|
+
fails.
|
|
267
|
+
- The callback form of `send(command, callback)` is not supported. Use the promise form.
|
|
268
|
+
- The translate config a document client is built with,
|
|
269
|
+
`DynamoDBDocumentClient.from(client, { marshallOptions, unmarshallOptions })`, is ignored. The
|
|
270
|
+
conversion always uses the defaults. `removeUndefinedValues: true` in particular has no effect
|
|
271
|
+
here, and an `undefined` attribute is refused where AWS would have dropped it. The refusal names
|
|
272
|
+
the attribute and says so.
|