@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.
Files changed (95) hide show
  1. package/README.md +13 -0
  2. package/dist/sdk/module/sim-sdk-module-client-interceptor.d.ts +5 -0
  3. package/dist/sdk/module/sim-sdk-module-client-interceptor.js +14 -0
  4. package/dist/sdk/module/sim-sdk-static-client-factory.d.ts +26 -0
  5. package/dist/sdk/module/sim-sdk-static-client-factory.js +59 -0
  6. package/dist/sdk/send-patch.d.ts +8 -0
  7. package/dist/sdk/send-patch.js +11 -0
  8. package/dist/service/aws/caller/sim-aws-caller-resolver.d.ts +28 -6
  9. package/dist/service/aws/caller/sim-aws-caller-resolver.js +18 -11
  10. package/dist/service/aws/caller/sim-aws-caller.d.ts +9 -0
  11. package/dist/service/aws/caller/sim-aws-resolved-caller.d.ts +19 -0
  12. package/dist/service/aws/caller/sim-aws-resolved-caller.js +26 -0
  13. package/dist/service/aws/factory/sim-aws-account-region-service-builder.d.ts +6 -0
  14. package/dist/service/aws/factory/sim-aws-account-region-service-builder.js +3 -0
  15. package/dist/service/aws/factory/sim-aws-account-service-cache.d.ts +6 -0
  16. package/dist/service/aws/factory/sim-aws-account-service-cache.js +5 -0
  17. package/dist/service/aws/factory/sim-aws-service-factory.d.ts +5 -0
  18. package/dist/service/aws/factory/sim-aws-service-factory.js +6 -0
  19. package/dist/service/aws/sim-aws-properties.d.ts +15 -0
  20. package/dist/service/aws/sim-aws.js +2 -1
  21. package/dist/service/iam/authorize/context/sim-iam-auth-z-caller-context-builder.d.ts +30 -7
  22. package/dist/service/iam/authorize/context/sim-iam-auth-z-caller-context-builder.js +20 -11
  23. package/dist/service/iam/authorize/context/sim-iam-auth-z-context-builder.d.ts +2 -1
  24. package/dist/service/iam/authorize/context/sim-iam-auth-z-context-builder.js +5 -1
  25. package/dist/service/iam/authorize/match/sim-iam-policy-principal-matcher.d.ts +10 -0
  26. package/dist/service/iam/authorize/match/sim-iam-policy-principal-matcher.js +15 -3
  27. package/dist/service/iam/authorize/sim-iam-account-auth-z.d.ts +11 -4
  28. package/dist/service/iam/authorize/sim-iam-account-auth-z.js +5 -3
  29. package/dist/service/iam/authorize/sim-iam-authorizer.d.ts +2 -1
  30. package/dist/service/iam/authorize/sim-iam-authorizer.js +1 -0
  31. package/dist/service/iam/role/sim-iam-role-with-policy.factory.d.ts +9 -0
  32. package/dist/service/iam/role/sim-iam-role-with-policy.factory.js +4 -2
  33. package/dist/service/iam/sim-iam-account-parts.d.ts +8 -0
  34. package/dist/service/iam/sim-iam-account-parts.js +1 -0
  35. package/dist/service/sts/auth-z/assume-role-auth-z-coordinator.d.ts +9 -2
  36. package/dist/service/sts/auth-z/assume-role-auth-z-coordinator.js +2 -2
  37. package/dist/service/sts/auth-z/assume-role-source-account-auth-z.d.ts +10 -4
  38. package/dist/service/sts/auth-z/assume-role-source-account-auth-z.js +8 -7
  39. package/dist/service/sts/auth-z/assume-role-target-auth-z.d.ts +2 -2
  40. package/dist/service/sts/auth-z/assume-role-trust-policy-authorizer.d.ts +6 -2
  41. package/dist/service/sts/auth-z/assume-role-trust-policy-authorizer.js +14 -11
  42. package/dist/service/sts/command/assume-role/assume-role.handler.d.ts +5 -1
  43. package/dist/service/sts/command/assume-role/assume-role.handler.js +5 -2
  44. package/dist/service/sts/command/get-caller-identity/get-caller-identity.handler.d.ts +5 -1
  45. package/dist/service/sts/command/get-caller-identity/get-caller-identity.handler.js +4 -1
  46. package/dist/service/sts/service-role/sim-service-role.js +5 -1
  47. package/dist/service/sts/sim-sts.d.ts +9 -1
  48. package/dist/service/sts/sim-sts.js +4 -0
  49. package/docs/README.md +54 -0
  50. package/docs/ai-skill/README.md +69 -0
  51. package/docs/cli/README.md +284 -0
  52. package/docs/factories/README.md +140 -0
  53. package/docs/lint/README.md +176 -0
  54. package/docs/non-aws-dependencies/README.md +261 -0
  55. package/docs/sdk/README.md +272 -0
  56. package/docs/serve/README.md +1126 -0
  57. package/docs/services/acm/README.md +823 -0
  58. package/docs/services/apigateway/README.md +1919 -0
  59. package/docs/services/apigatewayv2/README.md +2678 -0
  60. package/docs/services/athena/README.md +1044 -0
  61. package/docs/services/bedrock/README.md +350 -0
  62. package/docs/services/cloudformation/README.md +3440 -0
  63. package/docs/services/cloudfront/README.md +2874 -0
  64. package/docs/services/cloudwatch/README.md +420 -0
  65. package/docs/services/cognito/README.md +4923 -0
  66. package/docs/services/dynamodb/README.md +3784 -0
  67. package/docs/services/ecr/README.md +302 -0
  68. package/docs/services/ecs/README.md +2010 -0
  69. package/docs/services/elbv2/README.md +1897 -0
  70. package/docs/services/eventbridge/README.md +1110 -0
  71. package/docs/services/firehose/README.md +805 -0
  72. package/docs/services/glue/README.md +501 -0
  73. package/docs/services/iam/README.md +1283 -0
  74. package/docs/services/kinesis/README.md +454 -0
  75. package/docs/services/kms/README.md +659 -0
  76. package/docs/services/lambda/README.md +3645 -0
  77. package/docs/services/logs/README.md +868 -0
  78. package/docs/services/organizations/README.md +574 -0
  79. package/docs/services/personalize/README.md +835 -0
  80. package/docs/services/rekognition/README.md +1070 -0
  81. package/docs/services/route53/README.md +1783 -0
  82. package/docs/services/s3/README.md +3024 -0
  83. package/docs/services/scheduler/README.md +806 -0
  84. package/docs/services/secretsmanager/README.md +667 -0
  85. package/docs/services/ses/README.md +1011 -0
  86. package/docs/services/sns/README.md +1904 -0
  87. package/docs/services/sqs/README.md +1185 -0
  88. package/docs/services/ssm/README.md +1072 -0
  89. package/docs/services/stepfunctions/README.md +1332 -0
  90. package/docs/services/sts/README.md +245 -0
  91. package/docs/services/wafv2/README.md +1378 -0
  92. package/docs/terraform/README.md +248 -0
  93. package/docs/time/README.md +284 -0
  94. package/llms.txt +59 -0
  95. package/package.json +7 -4
@@ -3,12 +3,19 @@ import type { SimIamAccountResolver } from "../iam/registry/sim-iam-account-reso
3
3
  import type { SimAssumeRoleCommand, SimAssumeRoleCommandOutput } from "./command/assume-role/assume-role.command.js";
4
4
  import type { SimGetCallerIdentityCommand, SimGetCallerIdentityCommandOutput } from "./command/get-caller-identity/get-caller-identity.command.js";
5
5
  import type { SimAwsAccountRegionScope } from "../aws/sim-aws-account-region-scope.js";
6
- import type { SimAwsCaller } from "../aws/caller/sim-aws-caller.js";
6
+ import type { SimAwsCaller, SimAwsDefaultCaller } from "../aws/caller/sim-aws-caller.js";
7
7
  import type { SimSdkCommandRouter } from "../../sdk/index.js";
8
8
  interface SimStsProperties {
9
9
  readonly accountRegionScope?: SimAwsAccountRegionScope;
10
10
  readonly iamResolver?: SimIamAccountResolver;
11
11
  readonly background?: BackgroundScheduler;
12
+ /**
13
+ * The caller this simulation attributes a request naming none to.
14
+ *
15
+ * Left out, such a request is the source Account's root principal, which is
16
+ * the identity GetCallerIdentity then reports.
17
+ */
18
+ readonly defaultCaller?: SimAwsDefaultCaller | undefined;
12
19
  }
13
20
  /**
14
21
  * Simulated AWS Security Token Service.
@@ -21,6 +28,7 @@ export declare class SimSts {
21
28
  private readonly accountRegionScope;
22
29
  private readonly background;
23
30
  private readonly iamResolver;
31
+ private readonly defaultCaller?;
24
32
  private readonly sdkRouter;
25
33
  constructor(properties?: SimStsProperties);
26
34
  /**
@@ -15,12 +15,14 @@ export class SimSts {
15
15
  accountRegionScope;
16
16
  background;
17
17
  iamResolver;
18
+ defaultCaller;
18
19
  sdkRouter = new SimStsSdkCommandRouter(this);
19
20
  constructor(properties = {}) {
20
21
  this.accountRegionScope =
21
22
  properties.accountRegionScope ?? simAwsAccountRegionScopeFactory.make();
22
23
  this.iamResolver = properties.iamResolver ?? new SimIamRegistry();
23
24
  this.background = properties.background ?? new BackgroundTasks();
25
+ this.defaultCaller = properties.defaultCaller;
24
26
  }
25
27
  /**
26
28
  * Handle an AssumeRoleCommand from the SDK.
@@ -31,6 +33,7 @@ export class SimSts {
31
33
  regionName: this.accountRegionScope.regionName,
32
34
  iamResolver: this.iamResolver,
33
35
  background: this.background,
36
+ defaultCaller: this.defaultCaller,
34
37
  });
35
38
  return await handler.handle(command, options);
36
39
  }
@@ -41,6 +44,7 @@ export class SimSts {
41
44
  const handler = new GetCallerIdentityCommandHandler({
42
45
  sourceAccountId: this.accountRegionScope.accountId,
43
46
  iamResolver: this.iamResolver,
47
+ defaultCaller: this.defaultCaller,
44
48
  });
45
49
  return await handler.handle(command, options);
46
50
  }
package/docs/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # Simulated AWS usage documentation
2
+
3
+ This directory contains area-specific documentation for Yulin. Each page explains the simulated
4
+ behaviour and includes example code that can be copied into tests or local development scripts.
5
+
6
+ ## Service documentation
7
+
8
+ - [ACM](https://yulinsim.dev/services/acm/ "Simulated ACM usage docs")
9
+ - [API Gateway HTTP APIs](https://yulinsim.dev/services/apigatewayv2/ "Simulated API Gateway HTTP API usage docs")
10
+ - [API Gateway REST APIs](https://yulinsim.dev/services/apigateway/ "Simulated API Gateway REST API usage docs")
11
+ - [Athena](https://yulinsim.dev/services/athena/ "Simulated Amazon Athena usage docs")
12
+ - [Bedrock](https://yulinsim.dev/services/bedrock/ "Simulated Amazon Bedrock usage docs")
13
+ - [CloudFormation](https://yulinsim.dev/services/cloudformation/ "Simulated CloudFormation usage docs")
14
+ - [CloudFront](https://yulinsim.dev/services/cloudfront/ "Simulated CloudFront usage docs")
15
+ - [CloudWatch metrics](https://yulinsim.dev/services/cloudwatch/ "Simulated CloudWatch metrics usage docs")
16
+ - [Cognito user pools](https://yulinsim.dev/services/cognito/ "Simulated Cognito user pools usage docs")
17
+ - [DynamoDB](https://yulinsim.dev/services/dynamodb/ "Simulated DynamoDB usage docs")
18
+ - [ECR](https://yulinsim.dev/services/ecr/ "Simulated ECR usage docs")
19
+ - [ECS](https://yulinsim.dev/services/ecs/ "Simulated ECS usage docs")
20
+ - [Elastic Load Balancing](https://yulinsim.dev/services/elbv2/ "Simulated Application Load Balancer usage docs")
21
+ - [EventBridge](https://yulinsim.dev/services/eventbridge/ "Simulated EventBridge usage docs")
22
+ - [Glue](https://yulinsim.dev/services/glue/ "Simulated Glue Data Catalog usage docs")
23
+ - [IAM](https://yulinsim.dev/services/iam/ "Simulated IAM usage docs")
24
+ - [Kinesis Data Firehose](https://yulinsim.dev/services/firehose/ "Simulated Kinesis Data Firehose usage docs")
25
+ - [Kinesis Data Streams](https://yulinsim.dev/services/kinesis/ "Simulated Kinesis Data Streams usage docs")
26
+ - [KMS](https://yulinsim.dev/services/kms/ "Simulated KMS usage docs")
27
+ - [Lambda](https://yulinsim.dev/services/lambda/ "Simulated Lambda usage docs")
28
+ - [CloudWatch Logs](https://yulinsim.dev/services/logs/ "Simulated CloudWatch Logs usage docs")
29
+ - [Organizations](https://yulinsim.dev/services/organizations/ "Simulated Organizations service control policies usage docs")
30
+ - [Personalize](https://yulinsim.dev/services/personalize/ "Simulated Amazon Personalize usage docs")
31
+ - [Rekognition](https://yulinsim.dev/services/rekognition/ "Simulated Rekognition usage docs")
32
+ - [Route53](https://yulinsim.dev/services/route53/ "Simulated Route53 usage docs")
33
+ - [S3](https://yulinsim.dev/services/s3/ "Simulated S3 usage docs")
34
+ - [Scheduler](https://yulinsim.dev/services/scheduler/ "Simulated EventBridge Scheduler usage docs")
35
+ - [Secrets Manager](https://yulinsim.dev/services/secretsmanager/ "Simulated Secrets Manager usage docs")
36
+ - [SES](https://yulinsim.dev/services/ses/ "Simulated SES usage docs")
37
+ - [SNS](https://yulinsim.dev/services/sns/ "Simulated SNS usage docs")
38
+ - [SQS](https://yulinsim.dev/services/sqs/ "Simulated SQS usage docs")
39
+ - [SSM Parameter Store](https://yulinsim.dev/services/ssm/ "Simulated SSM Parameter Store usage docs")
40
+ - [Step Functions](https://yulinsim.dev/services/stepfunctions/ "Simulated Step Functions usage docs")
41
+ - [STS](https://yulinsim.dev/services/sts/ "Simulated STS usage docs")
42
+ - [WAFv2](https://yulinsim.dev/services/wafv2/ "Simulated WAFv2 usage docs")
43
+
44
+ ## Feature documentation
45
+
46
+ - [AI skill](https://yulinsim.dev/ai-skill/ "Yulin AI skill usage docs")
47
+ - [The AWS CLI](https://yulinsim.dev/cli/ "The AWS CLI against simulated AWS usage docs")
48
+ - [AWS SDK interception](https://yulinsim.dev/sdk/ "Simulated AWS SDK usage docs")
49
+ - [Event factories](https://yulinsim.dev/factories/ "Test factories for AWS event shapes usage docs")
50
+ - [Linting CloudFront Functions JS2](https://yulinsim.dev/lint/ "CloudFront Functions JS2 lint config usage docs")
51
+ - [Non-AWS dependencies](https://yulinsim.dev/non-aws-dependencies/ "Dependencies Yulin does not simulate usage docs")
52
+ - [Serving on localhost](https://yulinsim.dev/serve/ "Serving simulated AWS on localhost usage docs")
53
+ - [Simulated time](https://yulinsim.dev/time/ "Simulated time usage docs")
54
+ - [Terraform](https://yulinsim.dev/terraform/ "Deploying Terraform into simulated AWS usage docs")
@@ -0,0 +1,69 @@
1
+ # AI skill
2
+
3
+ Yulin has an AI skill that teaches a coding agent how to test AWS code with the simulator well. It
4
+ is `yulin-aws-simulation`, a `SKILL.md` written to the
5
+ [Agent Skills specification](https://agentskills.io/specification) and installable into Claude Code,
6
+ Codex CLI, Cursor, VS Code and anything else that reads one. It covers the part that lives outside
7
+ the API.
8
+
9
+ These docs and the skill answer different questions. A page here says what a simulated service does
10
+ and what its commands take. The skill says what to do with that. An AI agent reaching for Yulin
11
+ without it tends to build a harness around the simulator, leave hand-rolled stubs in place beside
12
+ it, or write an `instanceof` check against an SDK exception class that passes in production and
13
+ fails against the simulation.
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ npx @kensio/skills add yulin-aws-simulation
19
+ ```
20
+
21
+ That copies the skill into `.agents/skills/`, the directory Codex CLI, Cursor, VS Code and Gemini
22
+ CLI read. `--agent claude` puts it in `.claude/skills/`, and `--user` installs it under your home
23
+ directory for every project.
24
+
25
+ As a Claude Code plugin:
26
+
27
+ ```bash
28
+ claude plugin marketplace add KensioSoftware/kensio.ai
29
+ claude plugin install yulin-aws-simulation@kensio
30
+ ```
31
+
32
+ It is also on npm as `@kensio/yulin-aws-simulation`, and every
33
+ [kensio.ai release](https://github.com/KensioSoftware/kensio.ai/releases) carries it as a zip for a
34
+ machine with no registry reach.
35
+
36
+ ## What it covers
37
+
38
+ - Using `SimAws` and `SimSdk` directly, and spotting the helper class or `setupSimulatedAws()`
39
+ wrapper that starts to grow around them.
40
+ - One synthesized CDK template behind the tests, the dev server and production, deployed with
41
+ `deployTemplateFile` or `deployCdkOut`.
42
+ - When to register a resource at a chosen id, and when to deploy the stack that creates it.
43
+ - Interception over hand-rolled stubs, down to the requests a fake accepts and the simulation
44
+ refuses.
45
+ - Freezing the clock, then advancing it on purpose.
46
+ - Assertions that read the simulation back.
47
+ - Why service errors match by `name` and not by `instanceof`.
48
+ - Deploying an expensive stack once per test file.
49
+ - Running a handler as a real simulated Lambda, under its execution role, its declared environment
50
+ and its own log group.
51
+ - Refusals as a feature, and gaps raised upstream.
52
+
53
+ ## Reading the API alongside it
54
+
55
+ The skill sends the AI agent to these docs for anything API-shaped, and
56
+ [llms.txt](https://yulinsim.dev/llms.txt) is the index it uses. Every page here is available as
57
+ plain markdown by appending `llms.txt` to its URL, one file per guide and one per simulated service.
58
+ That index works with or without the skill installed.
59
+
60
+ The same pages ship inside the package. An installed project holds them under
61
+ `node_modules/@kensio/yulin/docs/`, indexed by `node_modules/@kensio/yulin/llms.txt`, and they
62
+ document the version in that package rather than the current release. An agent with no network
63
+ reach has them, and so does one working on a project held a few versions back. Ripgrep and most
64
+ editor search skip `node_modules` by default. An agent finds these files when something names the
65
+ path for it.
66
+
67
+ The skill lives at
68
+ [kensio.ai/skills/yulin-aws-simulation](https://kensio.ai/skills/yulin-aws-simulation), versioned
69
+ separately from Yulin and licensed Apache-2.0.
@@ -0,0 +1,284 @@
1
+ # AWS CLI
2
+
3
+ The real `aws` CLI reaches a served simulated environment over a local endpoint URL, and twenty of
4
+ Yulin's twenty-five SDK-facing services answer it.
5
+
6
+ [Serving on localhost](https://yulinsim.dev/serve/) is the reference for what each service serves. This page
7
+ covers the way in from a shell.
8
+
9
+ ## An endpoint and a key to sign with
10
+
11
+ Serving binds a simulated environment to a port. A served request runs as whoever signed it, and the
12
+ access key to sign the first one comes from simulated IAM in the process that built the environment:
13
+
14
+ ```typescript sim-cli-endpoint
15
+ /**
16
+ * Serving a simulated environment for the aws CLI to reach.
17
+ */
18
+
19
+ import {
20
+ CreateAccessKeyCommand,
21
+ CreateUserCommand,
22
+ PutUserPolicyCommand,
23
+ } from "@aws-sdk/client-iam";
24
+ import { SimAws } from "@kensio/yulin";
25
+ import { serveSimAws } from "@kensio/yulin/serve";
26
+
27
+ const simAws = new SimAws();
28
+ const simIam = simAws.iam();
29
+
30
+ await simIam.createUser(new CreateUserCommand({ UserName: "Operator" }));
31
+ await simIam.putUserPolicy(
32
+ new PutUserPolicyCommand({
33
+ UserName: "Operator",
34
+ PolicyName: "Everything",
35
+ PolicyDocument: JSON.stringify({
36
+ Version: "2012-10-17",
37
+ Statement: { Effect: "Allow", Action: "*", Resource: "*" },
38
+ }),
39
+ }),
40
+ );
41
+ const created = await simIam.createAccessKey(
42
+ new CreateAccessKeyCommand({ UserName: "Operator" }),
43
+ );
44
+
45
+ const srv = await serveSimAws({ simAws, port: 8787 });
46
+
47
+ // Paste these into the shell the CLI runs in.
48
+ console.log(`export AWS_ENDPOINT_URL=http://localhost:${srv.port}`);
49
+ console.log(`export AWS_ACCESS_KEY_ID=${created.AccessKey.AccessKeyId}`);
50
+ console.log(
51
+ `export AWS_SECRET_ACCESS_KEY=${created.AccessKey.SecretAccessKey}`,
52
+ );
53
+ console.log(`export AWS_DEFAULT_REGION=${simAws.defaultRegionName}`);
54
+ ```
55
+
56
+ Pin the port when the URL has to stay the same between runs. Without one the server takes whatever
57
+ is free.
58
+
59
+ ## Configuring the CLI
60
+
61
+ Four environment variables are the whole configuration:
62
+
63
+ ```bash
64
+ export AWS_ENDPOINT_URL=http://localhost:8787
65
+ export AWS_ACCESS_KEY_ID=AKIAVEXOWARWMKBOA0MP
66
+ export AWS_SECRET_ACCESS_KEY=RzIvKRp1sd5yXfEifA1twsUTd4GlHL5JpzvECpox
67
+ export AWS_DEFAULT_REGION=us-east-1
68
+ ```
69
+
70
+ `sts get-caller-identity` is the call to check the wiring with. It reports the principal behind the
71
+ key that signed the request:
72
+
73
+ ```bash
74
+ aws sts get-caller-identity
75
+ {
76
+ "UserId": "AIDARNFLISEC7SCUNSDY",
77
+ "Account": "888888888888",
78
+ "Arn": "arn:aws:iam::888888888888:user/Operator"
79
+ }
80
+ ```
81
+
82
+ The credentials have to come from simulated IAM. Any other key is refused with `403 Forbidden`, and
83
+ an unsigned request reaches nothing.
84
+
85
+ A Region is required, as it is against real AWS. Changing it moves the CLI between simulated
86
+ Regions, and a Queue created under `eu-west-2` is invisible to `AWS_DEFAULT_REGION=us-east-1`.
87
+
88
+ ### A named profile instead
89
+
90
+ A profile in the CLI's own config file carries the same four values. The simulation then stays out
91
+ of the ambient environment:
92
+
93
+ ```ini
94
+ [profile sim]
95
+ region = us-east-1
96
+ endpoint_url = http://localhost:8787
97
+ aws_access_key_id = AKIAVEXOWARWMKBOA0MP
98
+ aws_secret_access_key = RzIvKRp1sd5yXfEifA1twsUTd4GlHL5JpzvECpox
99
+ ```
100
+
101
+ `aws --profile sim sts get-caller-identity` then reaches the simulation while a bare `aws` still
102
+ goes to real AWS. The profile has to carry credentials of its own. Once `--profile` is given the CLI
103
+ stops reading `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` from the environment.
104
+
105
+ `role_arn` and `source_profile` work too. The CLI assumes the simulated Role for itself and signs
106
+ with the session it gets back:
107
+
108
+ ```ini
109
+ [profile reader]
110
+ region = us-east-1
111
+ endpoint_url = http://localhost:8787
112
+ role_arn = arn:aws:iam::888888888888:role/Reader
113
+ source_profile = sim
114
+ ```
115
+
116
+ ## Building the rest of the identities from the shell
117
+
118
+ Only the first key has to come from the process that built the simulation. `aws iam` builds
119
+ everything after it:
120
+
121
+ ```bash
122
+ aws iam create-user --user-name shopper
123
+ aws iam put-user-policy --user-name shopper --policy-name read-buckets \
124
+ --policy-document '{"Version":"2012-10-17","Statement":{"Effect":"Allow","Action":"s3:ListAllMyBuckets","Resource":"*"}}'
125
+ aws iam create-access-key --user-name shopper
126
+ ```
127
+
128
+ `create-access-key` answers with the secret once. Signing with the new key reports the new User, and
129
+ simulated IAM authorizes every request against the policy it was given:
130
+
131
+ ```bash
132
+ aws s3api create-bucket --bucket nope
133
+
134
+ aws: [ERROR]: An error occurred (AccessDenied) when calling the CreateBucket operation: User:
135
+ arn:aws:iam::888888888888:user/shopper is not authorized to perform: s3:CreateBucket on resource:
136
+ arn:aws:s3:::nope
137
+ ```
138
+
139
+ `aws sts assume-role` answers with temporary credentials. Export the three values it returns and the
140
+ rest of the session runs as the Role:
141
+
142
+ ```bash
143
+ aws sts assume-role --role-arn arn:aws:iam::888888888888:role/Reader --role-session-name probe
144
+ export AWS_ACCESS_KEY_ID=ASIAKBEUIHDMON9VBZXF
145
+ export AWS_SECRET_ACCESS_KEY=jm2N56vfVtLgJEo11OtbIXbgnJBgxpUMrszPrQdl
146
+ export AWS_SESSION_TOKEN=11568oBDksY9czECUMiAWk9tzmvG7zlQNjtLI1WmhZFS...
147
+ ```
148
+
149
+ The expiry is stamped from the simulation's own clock, and
150
+ [advancing it](https://yulinsim.dev/time/) past the expiry stops the session authenticating.
151
+
152
+ ## What answers
153
+
154
+ Twenty simulated services answer the endpoint. S3, STS, IAM, ELBv2, SNS, CloudFormation and Lambda,
155
+ along with the AWS JSON protocol services: DynamoDB, DynamoDB Streams, SQS, Cognito Identity
156
+ Provider, EventBridge, ECS, SSM, ACM, CloudWatch, CloudWatch Logs, KMS, Secrets Manager and
157
+ Rekognition.
158
+
159
+ A quick tour of the ones a shell reaches for most:
160
+
161
+ ```bash
162
+ aws s3 cp ./index.html s3://widgets/index.html
163
+ aws s3 sync ./site s3://widgets/site/
164
+ aws dynamodb put-item --table-name widgets --item '{"id":{"S":"w1"}}'
165
+ aws sqs create-queue --queue-name orders
166
+ aws ssm get-parameter --name /shop/url --query 'Parameter.Value' --output text
167
+ aws secretsmanager get-secret-value --secret-id shop/db --query SecretString --output text
168
+ aws logs describe-log-groups --query 'logGroups[].logGroupName' --output text
169
+ ```
170
+
171
+ `--query`, `--output text` and the rest of the CLI's own machinery work throughout, because they run
172
+ client-side over an ordinary AWS response.
173
+
174
+ [The serve docs](https://yulinsim.dev/serve/#which-services-answer) list the operations each service
175
+ implements. Anything outside those lists is refused as `NotImplemented`.
176
+
177
+ ## CLI traps
178
+
179
+ Each of these is CLI behaviour, and each catches people out against real AWS too. They are collected
180
+ here because a simulated endpoint is often where someone meets them first.
181
+
182
+ ### `--payload` needs `--cli-binary-format`
183
+
184
+ CLI v2 reads `--payload` as base64 by default:
185
+
186
+ ```bash
187
+ aws lambda invoke --function-name orders --payload '{"id":1}' out.json
188
+
189
+ aws: [ERROR]: Invalid base64: "{"id":1}"
190
+ ```
191
+
192
+ Pass `--cli-binary-format raw-in-base64-out` and the same call runs the function:
193
+
194
+ ```bash
195
+ aws lambda invoke --function-name orders --payload '{"id":1}' \
196
+ --cli-binary-format raw-in-base64-out out.json
197
+ cat out.json
198
+ ```
199
+
200
+ `--invocation-type Event` answers `202` and runs the handler on the background scheduler. A script
201
+ reading what the function did waits on `simAws.backgroundTasksComplete()` first.
202
+
203
+ ### S3 addressing style
204
+
205
+ The CLI needs nothing here. Its default `auto` style sends path-style requests to a custom endpoint,
206
+ which is what this endpoint routes. An SDK client is the one that needs `forcePathStyle: true`.
207
+
208
+ Forcing `addressing_style = virtual` in the config file breaks it. The Bucket moves into a hostname
209
+ the endpoint has no route for, and `list-objects-v2` comes back empty while `head-object` comes back
210
+ `404`.
211
+
212
+ ### `aws cloudformation deploy` uses change sets
213
+
214
+ `deploy` is a CLI-side wrapper over `CreateChangeSet`, and simulated CloudFormation implements four
215
+ operations that do not include it:
216
+
217
+ ```bash
218
+ aws cloudformation deploy --stack-name site --template-file template.json
219
+
220
+ aws: [ERROR]: An error occurred (NotImplemented) when calling the CreateChangeSet operation:
221
+ Simulated CloudFormation does not serve CreateChangeSet
222
+ ```
223
+
224
+ `create-stack` and `describe-stacks` do work. A deployment starts in the background and
225
+ `create-stack` is answered before the Resources exist, as real CloudFormation answers it.
226
+
227
+ ### Presigning needs the S3 service hostname
228
+
229
+ `aws s3 presign` signs whatever endpoint the CLI is configured with, and a URL built over the
230
+ general endpoint has no Bucket in it for the endpoint to route on. Point the one command at
231
+ simulated S3's own hostname on the served port:
232
+
233
+ ```bash
234
+ aws --endpoint-url http://s3.us-east-1.sim-aws.localhost:8787 s3 presign s3://widgets/one.txt
235
+ ```
236
+
237
+ The URL that comes back is fetchable by anything, including `curl` and a browser. The same hostname
238
+ serves [presigned URLs built by the SDK](https://yulinsim.dev/services/s3/#presigned-urls).
239
+
240
+ ### A bad key looks like an XML parse failure
241
+
242
+ STS, IAM and ELBv2 speak the AWS Query protocol and expect an XML body. The endpoint answers a
243
+ rejected signature as JSON. The CLI reports the body it could not parse:
244
+
245
+ ```bash
246
+ aws sts get-caller-identity
247
+
248
+ aws: [ERROR]: Unable to parse response (not well-formed (invalid token): line 1, column 0), invalid
249
+ XML received. Further retries may succeed:
250
+ b'{"Message":"Forbidden"}'
251
+ ```
252
+
253
+ `Forbidden` in the quoted body is the real answer. The other seventeen services report the same
254
+ rejection as a plain `403`.
255
+
256
+ ## Limitations
257
+
258
+ - Five services are refused with `501 Not Implemented`. Route 53 and CloudFront speak REST-XML, and
259
+ API Gateway v2, SES v2 and EventBridge Scheduler speak REST-JSON. Every one of them is reachable
260
+ in process through `SimAws` and through [SDK interception](https://yulinsim.dev/sdk/). Simulated ECR is
261
+ refused the same way and has no AWS API surface at all, since its images are registered in
262
+ process.
263
+ - An operation a served service has not implemented is refused as `NotImplemented`. That is a
264
+ separate answer from the protocol refusal above. `aws iam list-users` reports
265
+ `Simulated IAM does not serve ListUsers`, and `aws lambda list-functions` names the path it
266
+ arrived at.
267
+ - `aws cloudwatch get-metric-statistics` and `aws cloudwatch get-metric-data` fail with
268
+ `TypeError: time.getTime is not a function`. The JSON protocol carries a timestamp as epoch
269
+ seconds and the endpoint hands that number to the simulation where a `Date` is expected. Both
270
+ reads work in process and through SDK interception. `put-metric-data` and `list-metrics` are
271
+ unaffected.
272
+ - `aws s3 cp` and `aws s3 sync` corrupt a **download** above the CLI's 8MB threshold
273
+ ([#717](https://github.com/KensioSoftware/yulin/issues/717)). The CLI splits
274
+ the download into ranged GETs, simulated S3 ignores `Range` and returns the whole Object for each
275
+ one, and the parts land on top of each other. A 12MB Object arrives as a 20MB file. Uploads above
276
+ the threshold are fine, and so is any download under it. Two ways round it, both verified.
277
+ `aws s3api get-object` issues one unranged GET. Raising the threshold in the config file keeps
278
+ `aws s3 cp` on a single GET too.
279
+
280
+ ```ini
281
+ [profile sim]
282
+ s3 =
283
+ multipart_threshold = 5GB
284
+ ```
@@ -0,0 +1,140 @@
1
+ # Event factories
2
+
3
+ A handler is invoked with an event, and a test of a handler has to produce one. The events AWS
4
+ delivers are large, most of each one is fields the handler never reads, and the two or three the
5
+ test is about are buried in them. Written out by hand, that literal is copied between files and
6
+ drifts.
7
+
8
+ Yulin exports [`@kensio/part-factory`](https://partfactory.dev/) factories for those event shapes. A
9
+ test made with one says what the request or the message was and leaves the rest of the event alone.
10
+ They are ordinary factories, made in-process, and they need no `SimAws` instance and no simulated
11
+ service running. A handler test that runs without a simulator is who they are for. A test that does want
12
+ one (a Function URL served over HTTP, a queue with a real event source mapping) gets its events from
13
+ the simulator, and these factories make the same shapes that simulator delivers.
14
+
15
+ ## What is exported
16
+
17
+ | Factory | Import | Event |
18
+ | ----------------------------------------------------------------------------- | ---------------------------- | ----------------------------------- |
19
+ | `lambdaFunctionUrlEventFactory` | `@kensio/yulin/lambda` | A Lambda Function URL invocation |
20
+ | `lambdaSqsEventFactory`, `lambdaSqsEventRecordFactory` | `@kensio/yulin/lambda` | An SQS event source mapping's batch |
21
+ | `lambdaDynamoDbStreamEventFactory`, `lambdaDynamoDbStreamEventRecordFactory` | `@kensio/yulin/lambda` | A DynamoDB stream mapping's batch |
22
+ | `httpApiProxyEventFactory` | `@kensio/yulin/apigatewayv2` | An HTTP API `AWS_PROXY` invocation |
23
+ | `s3NotificationEventFactory`, `s3NotificationEventRecordFactory` | `@kensio/yulin/s3` | An S3 event notification |
24
+ | `cloudFrontViewerRequestEventFactory`, `cloudFrontViewerResponseEventFactory` | `@kensio/yulin/cloudfront` | A CloudFront Functions event |
25
+
26
+ Each service's own documentation covers what its events mean. This page is about how the factories
27
+ are shaped and what they have in common.
28
+
29
+ ## One factory per shape, and one per record
30
+
31
+ An event that carries a list of records has two factories, one for a record and one for the event
32
+ around it. The record factory makes one record, and the event factory completes as many records as
33
+ the test asks for.
34
+
35
+ ```typescript factories-records-and-events
36
+ /**
37
+ * Making the SQS event a batch of messages arrives in.
38
+ */
39
+
40
+ import {
41
+ lambdaSqsEventFactory,
42
+ lambdaSqsEventRecordFactory,
43
+ type SimLambdaSqsEvent,
44
+ } from "@kensio/yulin/lambda";
45
+
46
+ function ordersHandler(event: SimLambdaSqsEvent): readonly string[] {
47
+ return event.Records.map(
48
+ (record) => (JSON.parse(record.body) as { orderId: string }).orderId,
49
+ );
50
+ }
51
+
52
+ // One record, for a test about a single message.
53
+ const record = lambdaSqsEventRecordFactory.make({
54
+ body: '{"orderId":"YL-1"}',
55
+ });
56
+
57
+ // [ 'YL-1' ]
58
+ console.log(ordersHandler({ Records: [record] }));
59
+
60
+ // A batch, saying only what each message carries. Everything else about each
61
+ // record — its message id, its receipt handle, the digest of its body — is
62
+ // filled in as a delivered record's is.
63
+ const event = lambdaSqsEventFactory.make({
64
+ Records: [{ body: '{"orderId":"YL-1"}' }, { body: '{"orderId":"YL-2"}' }],
65
+ });
66
+
67
+ // [ 'YL-1', 'YL-2' ]
68
+ console.log(ordersHandler(event));
69
+ ```
70
+
71
+ The event factory exists so a test can pass partial records like that. A `DynamicFactory` whose
72
+ defaults hold one record would not manage it. Overrides are merged onto defaults, and merging
73
+ replaces a list whole rather than element by element. A partial record passed that way would reach
74
+ the handler as the only thing in those records, typed as a complete one and missing every field the
75
+ handler reads.
76
+
77
+ ## Named variations
78
+
79
+ Every factory here is an `ItemFactory`. A variation with a name is a `VariantFactory` around it, the
80
+ same as for any other `part-factory` factory. That is usually the tidiest way to describe the kind
81
+ of request or message one application receives:
82
+
83
+ ```typescript factories-variants
84
+ /**
85
+ * Naming the variations of an event an application receives.
86
+ */
87
+
88
+ import { VariantFactory } from "@kensio/part-factory";
89
+
90
+ import { httpApiProxyEventFactory } from "@kensio/yulin/apigatewayv2";
91
+ import { s3NotificationEventFactory } from "@kensio/yulin/s3";
92
+
93
+ const signedInRequestFactory = new VariantFactory(httpApiProxyEventFactory, {
94
+ requestContext: {
95
+ authorizer: { jwt: { claims: { sub: "YL-1" }, scopes: null } },
96
+ },
97
+ });
98
+
99
+ const request = signedInRequestFactory.make({ rawPath: "/account" });
100
+
101
+ // GET /account YL-1
102
+ console.log(
103
+ request.routeKey,
104
+ request.requestContext.authorizer?.jwt?.claims["sub"],
105
+ );
106
+
107
+ const objectRemovedFactory = new VariantFactory(s3NotificationEventFactory, {
108
+ Records: [{ eventName: "ObjectRemoved:Delete" }],
109
+ });
110
+
111
+ const removal = objectRemovedFactory.make();
112
+
113
+ // ObjectRemoved:Delete undefined
114
+ console.log(
115
+ removal.Records[0]?.eventName,
116
+ // A removal reports no size, because the Object is gone.
117
+ removal.Records[0]?.s3.object.size,
118
+ );
119
+ ```
120
+
121
+ ## Events that agree with themselves
122
+
123
+ A real AWS event says the same thing in more than one field, and those copies are where a
124
+ hand-written literal goes wrong. It asks for `/user/status` and leaves the request context saying
125
+ `/`, or gives an SQS record the digest of some other body, or reports a DynamoDB insert on a record
126
+ that carries an old image.
127
+
128
+ Each factory's defaults are computed from the overrides, so supplying either copy settles the other.
129
+ What that covers is listed in each factory's own documentation, and in outline it is:
130
+
131
+ - **Function URL and HTTP API events** — the path, the query, the route key, the endpoint's id,
132
+ hostname and `host` header, the caller's user agent and address, and the invocation time
133
+ - **SQS records** — the digest of the body, and the Region of the queue ARN
134
+ - **DynamoDB stream records** — the images the reported change would carry, the view type naming
135
+ the images that are there, and the Region of the stream ARN
136
+ - **S3 notification records** — the ARN of the Bucket named, and whether the Object still exists to
137
+ have a size and an eTag
138
+
139
+ Overriding both copies of one of those with different values is still allowed. A test that wants an
140
+ event no real invocation would produce is entitled to one. It just has to ask for it twice.