@kensio/yulin 1.21.6 → 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 (74) hide show
  1. package/README.md +22 -2
  2. package/dist/service/eventbridge/cfn/bus/sim-cfn-event-bus-creator.js +1 -0
  3. package/dist/service/eventbridge/cfn/bus/sim-cfn-event-bus-properties.d.ts +4 -0
  4. package/dist/service/eventbridge/cfn/bus/sim-cfn-event-bus-properties.js +26 -1
  5. package/dist/service/eventbridge/cfn/rule/sim-cfn-event-rule-creator.js +3 -1
  6. package/dist/service/eventbridge/cfn/rule/sim-cfn-event-rule-properties.d.ts +0 -7
  7. package/dist/service/eventbridge/cfn/rule/sim-cfn-event-rule-properties.js +0 -20
  8. package/dist/service/eventbridge/cfn/rule/sim-cfn-event-rule-unsimulated-properties.d.ts +13 -0
  9. package/dist/service/eventbridge/cfn/rule/sim-cfn-event-rule-unsimulated-properties.js +59 -0
  10. package/dist/service/eventbridge/cfn/sim-cfn-event-bridge-resource-error.d.ts +1 -1
  11. package/dist/service/eventbridge/cfn/sim-cfn-event-bridge-resource-error.js +1 -1
  12. package/dist/service/lambda/cfn/event-source-mapping/sim-cfn-lambda-event-source-mapping-properties.js +1 -1
  13. package/dist/service/lambda/cfn/event-source-mapping/sim-cfn-lambda-event-source-mapping-property-rules.d.ts +3 -2
  14. package/dist/service/lambda/cfn/event-source-mapping/sim-cfn-lambda-event-source-mapping-property-rules.js +25 -3
  15. package/dist/service/ses/command/authorize/sim-ses-authorizer.d.ts +9 -0
  16. package/dist/service/ses/command/authorize/sim-ses-authorizer.js +18 -2
  17. package/dist/service/ses/command/send/sim-ses-send-email.js +2 -1
  18. package/dist/service/sns/cfn/topic/sim-cfn-sns-topic-creator.js +1 -0
  19. package/dist/service/sns/cfn/topic/sim-cfn-sns-topic-properties.d.ts +9 -4
  20. package/dist/service/sns/cfn/topic/sim-cfn-sns-topic-properties.js +18 -6
  21. package/dist/service/sns/cfn/topic/sim-cfn-sns-topic-property-names.d.ts +10 -0
  22. package/dist/service/sns/cfn/topic/sim-cfn-sns-topic-property-names.js +16 -1
  23. package/dist/service/ssm/cfn/parameter/sim-cfn-ssm-parameter-creator.js +1 -1
  24. package/dist/service/ssm/cfn/parameter/sim-cfn-ssm-parameter-properties.d.ts +11 -6
  25. package/dist/service/ssm/cfn/parameter/sim-cfn-ssm-parameter-properties.js +18 -9
  26. package/docs/README.md +40 -4
  27. package/docs/ai-skill/README.md +57 -54
  28. package/docs/cli/README.md +84 -94
  29. package/docs/factories/README.md +42 -54
  30. package/docs/lint/README.md +41 -67
  31. package/docs/non-aws-dependencies/README.md +72 -168
  32. package/docs/sdk/README.md +109 -95
  33. package/docs/serve/README.md +192 -898
  34. package/docs/services/acm/README.md +24 -40
  35. package/docs/services/apigateway/README.md +52 -71
  36. package/docs/services/apigatewayv2/README.md +55 -74
  37. package/docs/services/athena/README.md +17 -26
  38. package/docs/services/backup/README.md +29 -39
  39. package/docs/services/bedrock/README.md +38 -52
  40. package/docs/services/cloudformation/README.md +43 -55
  41. package/docs/services/cloudfront/README.md +69 -95
  42. package/docs/services/cloudwatch/README.md +40 -54
  43. package/docs/services/cognito/README.md +30 -45
  44. package/docs/services/dynamodb/README.md +34 -51
  45. package/docs/services/ecr/README.md +36 -77
  46. package/docs/services/ecs/README.md +26 -46
  47. package/docs/services/elbv2/README.md +19 -29
  48. package/docs/services/eventbridge/README.md +24 -20
  49. package/docs/services/firehose/README.md +24 -32
  50. package/docs/services/glue/README.md +41 -78
  51. package/docs/services/iam/README.md +13 -15
  52. package/docs/services/kinesis/README.md +53 -93
  53. package/docs/services/kms/README.md +22 -32
  54. package/docs/services/lambda/README.md +60 -80
  55. package/docs/services/logs/README.md +41 -50
  56. package/docs/services/organizations/README.md +50 -85
  57. package/docs/services/personalize/README.md +28 -44
  58. package/docs/services/rekognition/README.md +26 -38
  59. package/docs/services/route53/README.md +17 -17
  60. package/docs/services/s3/README.md +47 -51
  61. package/docs/services/scheduler/README.md +41 -52
  62. package/docs/services/secretsmanager/README.md +27 -42
  63. package/docs/services/ses/README.md +24 -34
  64. package/docs/services/sns/README.md +31 -33
  65. package/docs/services/sqs/README.md +14 -14
  66. package/docs/services/ssm/README.md +19 -21
  67. package/docs/services/stepfunctions/README.md +18 -20
  68. package/docs/services/sts/README.md +32 -45
  69. package/docs/services/wafv2/README.md +12 -17
  70. package/docs/terraform/README.md +108 -126
  71. package/docs/testing/README.md +228 -0
  72. package/docs/time/README.md +87 -117
  73. package/llms.txt +2 -1
  74. 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
 
@@ -24,6 +24,7 @@ export class SimCfnEventBusCreator {
24
24
  properties,
25
25
  });
26
26
  busProperties.refuseUnsimulated();
27
+ busProperties.recordIgnoredProperties();
27
28
  const name = busProperties.name();
28
29
  const description = busProperties.description();
29
30
  return await simCfnEventBridgeResourceCreation(eventBusResourceType, resource.logicalId, async () => {
@@ -27,6 +27,10 @@ export declare class SimCfnEventBusProperties {
27
27
  * Refuse what this simulation does not model, naming the property.
28
28
  */
29
29
  refuseUnsimulated(): void;
30
+ /**
31
+ * Record the properties the bus is created without acting on.
32
+ */
33
+ recordIgnoredProperties(): void;
30
34
  private propertyError;
31
35
  }
32
36
  export {};
@@ -19,8 +19,23 @@ const unsimulatedProperties = [
19
19
  ["EventSourceName", "partner event buses are not simulated"],
20
20
  ["KmsKeyIdentifier", "events are not encrypted with a customer managed key"],
21
21
  ["LogConfig", "event bus logging is not simulated"],
22
- ["Tags", "event bus tags are not simulated"],
23
22
  ];
23
+ /**
24
+ * What a template can say about an event bus that this simulation has nothing
25
+ * to act on, and why.
26
+ *
27
+ * `Tags` is the whole list, and it is recorded rather than refused. A bus
28
+ * behaves the same way with a tag and without one, and a CDK app calling
29
+ * `Tags.of(app).add(...)` tags every bus in it.
30
+ */
31
+ const ignoredPropertyReasons = new Map([
32
+ [
33
+ "Tags",
34
+ `${eventBusResourceType} property Tags is not simulated, so the bus is ` +
35
+ "created without them. Nothing reads them back and nothing is grouped " +
36
+ "or billed by them.",
37
+ ],
38
+ ]);
24
39
  /**
25
40
  * Reads AWS::Events::EventBus properties into the shape CreateEventBus takes.
26
41
  */
@@ -71,6 +86,16 @@ export class SimCfnEventBusProperties {
71
86
  }
72
87
  }
73
88
  }
89
+ /**
90
+ * Record the properties the bus is created without acting on.
91
+ */
92
+ recordIgnoredProperties() {
93
+ for (const [property, reason] of ignoredPropertyReasons) {
94
+ if (this.properties.has(property)) {
95
+ this.resource.ignoreProperty(property, reason);
96
+ }
97
+ }
98
+ }
74
99
  propertyError(reason) {
75
100
  return simCfnEventBridgeResourceError(eventBusResourceType, this.resource.logicalId, reason);
76
101
  }
@@ -2,6 +2,7 @@ import { assertDefined } from "../../../../util/type-guard/defined.js";
2
2
  import { simCfnEventBridgeResourceCreation } from "../sim-cfn-event-bridge-resource-error.js";
3
3
  import { eventRuleResourceType } from "../sim-cfn-event-bridge-resource-types.js";
4
4
  import { SimCfnEventRuleProperties } from "./sim-cfn-event-rule-properties.js";
5
+ import { recordIgnoredEventRuleProperties, refuseUnsimulatedEventRuleProperties, } from "./sim-cfn-event-rule-unsimulated-properties.js";
5
6
  /**
6
7
  * Creates simulated rules from AWS::Events::Rule Resources.
7
8
  *
@@ -27,7 +28,8 @@ export class SimCfnEventRuleCreator {
27
28
  resource,
28
29
  properties,
29
30
  });
30
- ruleProperties.refuseUnsimulated();
31
+ refuseUnsimulatedEventRuleProperties(resource, properties);
32
+ recordIgnoredEventRuleProperties(resource, properties);
31
33
  const name = ruleProperties.name();
32
34
  const busName = ruleProperties.busName();
33
35
  const targets = ruleProperties.targets();
@@ -48,13 +48,6 @@ export declare class SimCfnEventRuleProperties {
48
48
  * The rule's inline targets.
49
49
  */
50
50
  targets(): readonly SimCfnEventRuleTarget[];
51
- /**
52
- * Refuse what this simulation does not model, naming the property.
53
- *
54
- * Both are asked by key rather than by value, so a template writing `null`
55
- * for one is refused rather than read as having left it out.
56
- */
57
- refuseUnsimulated(): void;
58
51
  private stringProperty;
59
52
  private propertyError;
60
53
  }
@@ -89,26 +89,6 @@ export class SimCfnEventRuleProperties {
89
89
  targets() {
90
90
  return simCfnEventRuleTargets(this.properties.get("Targets"), (reason) => this.propertyError(reason));
91
91
  }
92
- /**
93
- * Refuse what this simulation does not model, naming the property.
94
- *
95
- * Both are asked by key rather than by value, so a template writing `null`
96
- * for one is refused rather than read as having left it out.
97
- */
98
- refuseUnsimulated() {
99
- if (this.properties.has("RoleArn")) {
100
- throw this.propertyError("RoleArn is not simulated, so the Resource is refused rather than " +
101
- "deployed without it: a rule reaches its target as the EventBridge " +
102
- "service principal, admitted by the target's own resource policy");
103
- }
104
- // Rule tags never reach PutRule, which is the one place that would
105
- // otherwise refuse them, so a tagged rule would deploy and quietly lose
106
- // its tags.
107
- if (this.properties.has("Tags")) {
108
- throw this.propertyError("Tags are not simulated, so the Resource is refused rather than " +
109
- "deployed without them");
110
- }
111
- }
112
92
  stringProperty(name) {
113
93
  const value = this.properties.get(name);
114
94
  if (value === undefined) {
@@ -0,0 +1,13 @@
1
+ import type { SimCfnResource } from "../../../cloudformation/resource/sim-cfn-resource.js";
2
+ import type { SimCfnTemplateValueRecord } from "../../../cloudformation/template/value/sim-cfn-template-value.js";
3
+ /**
4
+ * Refuse what this simulation does not model, naming the property.
5
+ *
6
+ * Asked by key rather than by value, so a template writing `null` for one of
7
+ * these is refused rather than read as having left it out.
8
+ */
9
+ export declare function refuseUnsimulatedEventRuleProperties(resource: SimCfnResource, properties: SimCfnTemplateValueRecord): void;
10
+ /**
11
+ * Record the properties the rule is created without acting on.
12
+ */
13
+ export declare function recordIgnoredEventRuleProperties(resource: SimCfnResource, properties: SimCfnTemplateValueRecord): void;
@@ -0,0 +1,59 @@
1
+ import { simCfnEventBridgeResourceError } from "../sim-cfn-event-bridge-resource-error.js";
2
+ import { eventRuleResourceType } from "../sim-cfn-event-bridge-resource-types.js";
3
+ /**
4
+ * What a template can say about a rule that this simulation does not model,
5
+ * and why each one fails the Resource.
6
+ *
7
+ * `RoleArn` is the whole list. A rule reaches its target as the EventBridge
8
+ * service principal, admitted by the target's own resource policy, so a rule
9
+ * deployed with a role would sit in a test looking as though the role decided
10
+ * something.
11
+ */
12
+ const refusedPropertyReasons = new Map([
13
+ [
14
+ "RoleArn",
15
+ "RoleArn is not simulated, so the Resource is refused rather than " +
16
+ "deployed without it: a rule reaches its target as the EventBridge " +
17
+ "service principal, admitted by the target's own resource policy",
18
+ ],
19
+ ]);
20
+ /**
21
+ * What a template can say about a rule that this simulation has nothing to act
22
+ * on, and why each one is recorded rather than refused.
23
+ *
24
+ * `Tags` is the whole list. Rule tags never reach `PutRule`, so the record is
25
+ * the only place a tagged rule says it lost them. A rule routes the same
26
+ * events with a tag and without one, and a CDK app calling
27
+ * `Tags.of(app).add(...)` tags every rule in it.
28
+ */
29
+ const ignoredPropertyReasons = new Map([
30
+ [
31
+ "Tags",
32
+ `${eventRuleResourceType} property Tags is not simulated, so the rule is ` +
33
+ "created without them. Nothing reads them back and nothing is grouped " +
34
+ "or billed by them.",
35
+ ],
36
+ ]);
37
+ /**
38
+ * Refuse what this simulation does not model, naming the property.
39
+ *
40
+ * Asked by key rather than by value, so a template writing `null` for one of
41
+ * these is refused rather than read as having left it out.
42
+ */
43
+ export function refuseUnsimulatedEventRuleProperties(resource, properties) {
44
+ for (const [property, reason] of refusedPropertyReasons) {
45
+ if (Object.hasOwn(properties, property)) {
46
+ throw simCfnEventBridgeResourceError(eventRuleResourceType, resource.logicalId, reason);
47
+ }
48
+ }
49
+ }
50
+ /**
51
+ * Record the properties the rule is created without acting on.
52
+ */
53
+ export function recordIgnoredEventRuleProperties(resource, properties) {
54
+ for (const [property, reason] of ignoredPropertyReasons) {
55
+ if (Object.hasOwn(properties, property)) {
56
+ resource.ignoreProperty(property, reason);
57
+ }
58
+ }
59
+ }
@@ -15,7 +15,7 @@ export declare function simCfnEventBridgeResourceError(resourceType: string, log
15
15
  * Every Resource type here goes through the ordinary EventBridge commands, so
16
16
  * what a template may ask for is decided once, by simulated EventBridge, rather
17
17
  * than again in the CloudFormation layer. What that leaves out is where the
18
- * request came from: a deployment failing with `Rule tags are not simulated`
18
+ * request came from: a deployment failing with `Rule pattern is not valid`
19
19
  * says nothing about which Resource asked for it. Only EventBridge's own errors
20
20
  * are renamed, so a refusal the CloudFormation layer decided keeps the wording
21
21
  * it was written with.
@@ -20,7 +20,7 @@ export function simCfnEventBridgeResourceError(resourceType, logicalId, reason,
20
20
  * Every Resource type here goes through the ordinary EventBridge commands, so
21
21
  * what a template may ask for is decided once, by simulated EventBridge, rather
22
22
  * than again in the CloudFormation layer. What that leaves out is where the
23
- * request came from: a deployment failing with `Rule tags are not simulated`
23
+ * request came from: a deployment failing with `Rule pattern is not valid`
24
24
  * says nothing about which Resource asked for it. Only EventBridge's own errors
25
25
  * are renamed, so a refusal the CloudFormation layer decided keeps the wording
26
26
  * it was written with.
@@ -31,7 +31,7 @@ export class SimCfnLambdaEventSourceMappingProperties {
31
31
  */
32
32
  createInput() {
33
33
  const eventSourceArn = this.parser.requiredString(this.resource, this.properties["EventSourceArn"], "EventSourceArn");
34
- assertSimulatedEventSourceMappingProperties(this.resource.logicalId, this.properties);
34
+ assertSimulatedEventSourceMappingProperties(this.resource, this.properties);
35
35
  return {
36
36
  EventSourceArn: eventSourceArn,
37
37
  FunctionName: simCfnLambdaTargetFunctionName(this.parser.requiredString(this.resource, this.properties["FunctionName"], "FunctionName")),
@@ -1,3 +1,4 @@
1
+ import type { SimCfnResource } from "../../../cloudformation/resource/sim-cfn-resource.js";
1
2
  import type { SimCfnTemplateValueRecord } from "../../../cloudformation/template/value/sim-cfn-template-value.js";
2
3
  /**
3
4
  * Build the error a property of an AWS::Lambda::EventSourceMapping Resource is
@@ -11,6 +12,6 @@ import type { SimCfnTemplateValueRecord } from "../../../cloudformation/template
11
12
  export declare function eventSourceMappingPropertyError(logicalId: string, reason: string): Error;
12
13
  /**
13
14
  * Refuse everything about an AWS::Lambda::EventSourceMapping Resource that is
14
- * not simulated.
15
+ * not simulated, and record what the mapping is created without.
15
16
  */
16
- export declare function assertSimulatedEventSourceMappingProperties(logicalId: string, properties: SimCfnTemplateValueRecord): void;
17
+ export declare function assertSimulatedEventSourceMappingProperties(resource: SimCfnResource, properties: SimCfnTemplateValueRecord): void;
@@ -44,10 +44,26 @@ const unsimulatedPropertyNames = new Set([
44
44
  "SelfManagedEventSource",
45
45
  "SelfManagedKafkaEventSourceConfig",
46
46
  "SourceAccessConfigurations",
47
- "Tags",
48
47
  "Topics",
49
48
  "TumblingWindowInSeconds",
50
49
  ]);
50
+ /**
51
+ * Real AWS::Lambda::EventSourceMapping properties this simulation has nothing
52
+ * to act on and no reason to fail a stack over.
53
+ *
54
+ * `Tags` is the whole list. The mapping is created without them and the
55
+ * omission is recorded against the Resource. A mapping delivers the same
56
+ * records with a tag and without one, and a CDK app calling
57
+ * `Tags.of(app).add(...)` tags every mapping in it.
58
+ */
59
+ const ignoredPropertyReasons = new Map([
60
+ [
61
+ "Tags",
62
+ "AWS::Lambda::EventSourceMapping property Tags is not simulated, so the " +
63
+ "mapping is created without them. Nothing reads them back and nothing " +
64
+ "is grouped or billed by them.",
65
+ ],
66
+ ]);
51
67
  /**
52
68
  * Build the error a property of an AWS::Lambda::EventSourceMapping Resource is
53
69
  * refused with.
@@ -62,10 +78,16 @@ export function eventSourceMappingPropertyError(logicalId, reason) {
62
78
  }
63
79
  /**
64
80
  * Refuse everything about an AWS::Lambda::EventSourceMapping Resource that is
65
- * not simulated.
81
+ * not simulated, and record what the mapping is created without.
66
82
  */
67
- export function assertSimulatedEventSourceMappingProperties(logicalId, properties) {
83
+ export function assertSimulatedEventSourceMappingProperties(resource, properties) {
84
+ const logicalId = resource.logicalId;
68
85
  for (const name of Object.keys(properties)) {
86
+ const ignoredReason = ignoredPropertyReasons.get(name);
87
+ if (ignoredReason !== undefined) {
88
+ resource.ignoreProperty(name, ignoredReason);
89
+ continue;
90
+ }
69
91
  if (unsimulatedPropertyNames.has(name)) {
70
92
  throw eventSourceMappingPropertyError(logicalId, `${name} is a real AWS::Lambda::EventSourceMapping property that ` +
71
93
  "this simulation does not simulate, so it is refused rather than " +
@@ -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) {
@@ -23,6 +23,7 @@ export class SimCfnSnsTopicCreator {
23
23
  resource,
24
24
  properties,
25
25
  });
26
+ topicProperties.recordIgnoredProperties();
26
27
  const name = topicProperties.name();
27
28
  const attributes = topicProperties.attributes();
28
29
  const inline = topicProperties.inlineSubscriptions();
@@ -43,13 +43,18 @@ export declare class SimCfnSnsTopicProperties {
43
43
  * The subscriptions the topic declares inside itself.
44
44
  */
45
45
  inlineSubscriptions(): readonly SimCfnSnsInlineSubscription[];
46
+ /**
47
+ * Record the properties the topic is created without acting on.
48
+ */
49
+ recordIgnoredProperties(): void;
46
50
  /**
47
51
  * Whether a property is handed to CreateTopic as an attribute.
48
52
  *
49
- * The two properties the CloudFormation layer reads itself are not. The rest
50
- * are refused as they are reached: the ones with a reason of their own with
51
- * that reason, and anything else as a property AWS::SNS::Topic does not have,
52
- * which is how real CloudFormation answers one too.
53
+ * The two properties the CloudFormation layer reads itself are not, and
54
+ * neither are the ones recorded as ignored. The rest are refused as they are
55
+ * reached: the ones with a reason of their own with that reason, and
56
+ * anything else as a property AWS::SNS::Topic does not have, which is how
57
+ * real CloudFormation answers one too.
53
58
  */
54
59
  private isAttributeProperty;
55
60
  private propertyError;
@@ -3,7 +3,7 @@ import { simCfnSnsResourceError } from "../sim-cfn-sns-resource-error.js";
3
3
  import { snsTopicResourceType } from "../sim-cfn-sns-resource-types.js";
4
4
  import { simCfnSnsInlineSubscriptions, } from "./sim-cfn-sns-inline-subscriptions.js";
5
5
  import { SimCfnSnsTopicName } from "./sim-cfn-sns-topic-name.js";
6
- import { attributePropertyNames, topicNamePropertyName, topicSubscriptionPropertyName, unsimulatedPropertyReasons, } from "./sim-cfn-sns-topic-property-names.js";
6
+ import { attributePropertyNames, ignoredPropertyReasons, topicNamePropertyName, topicSubscriptionPropertyName, unsimulatedPropertyReasons, } from "./sim-cfn-sns-topic-property-names.js";
7
7
  /**
8
8
  * Reads AWS::SNS::Topic CloudFormation properties into the shape CreateTopic
9
9
  * takes.
@@ -63,17 +63,29 @@ export class SimCfnSnsTopicProperties {
63
63
  inlineSubscriptions() {
64
64
  return simCfnSnsInlineSubscriptions(this.resource.logicalId, this.properties.get(topicSubscriptionPropertyName));
65
65
  }
66
+ /**
67
+ * Record the properties the topic is created without acting on.
68
+ */
69
+ recordIgnoredProperties() {
70
+ for (const [property, reason] of ignoredPropertyReasons) {
71
+ if (this.properties.has(property)) {
72
+ this.resource.ignoreProperty(property, reason);
73
+ }
74
+ }
75
+ }
66
76
  /**
67
77
  * Whether a property is handed to CreateTopic as an attribute.
68
78
  *
69
- * The two properties the CloudFormation layer reads itself are not. The rest
70
- * are refused as they are reached: the ones with a reason of their own with
71
- * that reason, and anything else as a property AWS::SNS::Topic does not have,
72
- * which is how real CloudFormation answers one too.
79
+ * The two properties the CloudFormation layer reads itself are not, and
80
+ * neither are the ones recorded as ignored. The rest are refused as they are
81
+ * reached: the ones with a reason of their own with that reason, and
82
+ * anything else as a property AWS::SNS::Topic does not have, which is how
83
+ * real CloudFormation answers one too.
73
84
  */
74
85
  isAttributeProperty(name) {
75
86
  if (name === topicNamePropertyName ||
76
- name === topicSubscriptionPropertyName) {
87
+ name === topicSubscriptionPropertyName ||
88
+ ignoredPropertyReasons.has(name)) {
77
89
  return false;
78
90
  }
79
91
  if (attributePropertyNames.has(name)) {
@@ -28,3 +28,13 @@ export declare const attributePropertyNames: ReadonlySet<string>;
28
28
  * become fifteen separate attributes.
29
29
  */
30
30
  export declare const unsimulatedPropertyReasons: ReadonlyMap<string, string>;
31
+ /**
32
+ * The real AWS::SNS::Topic properties this simulation has nothing to act on
33
+ * and no reason to fail a stack over.
34
+ *
35
+ * `Tags` is the whole list. The topic is created without them and the omission
36
+ * is recorded against the Resource. A topic behaves the same way with a tag
37
+ * and without one, and a CDK app calling `Tags.of(app).add(...)` tags every
38
+ * topic in it.
39
+ */
40
+ export declare const ignoredPropertyReasons: ReadonlyMap<string, string>;
@@ -47,5 +47,20 @@ export const unsimulatedPropertyReasons = new Map([
47
47
  "DeliveryStatusLogging",
48
48
  "delivery status logging writes to CloudWatch Logs, which is not simulated",
49
49
  ],
50
- ["Tags", "no simulated service reads a topic tag"],
50
+ ]);
51
+ /**
52
+ * The real AWS::SNS::Topic properties this simulation has nothing to act on
53
+ * and no reason to fail a stack over.
54
+ *
55
+ * `Tags` is the whole list. The topic is created without them and the omission
56
+ * is recorded against the Resource. A topic behaves the same way with a tag
57
+ * and without one, and a CDK app calling `Tags.of(app).add(...)` tags every
58
+ * topic in it.
59
+ */
60
+ export const ignoredPropertyReasons = new Map([
61
+ [
62
+ "Tags",
63
+ "AWS::SNS::Topic property Tags is not simulated, so the topic is created " +
64
+ "without them. No simulated service reads a topic tag.",
65
+ ],
51
66
  ]);
@@ -28,6 +28,7 @@ export class SimCfnSsmParameterCreator {
28
28
  properties,
29
29
  });
30
30
  const name = parameterProperties.name();
31
+ parameterProperties.recordIgnoredTags();
31
32
  await this.ssm.putParameter({
32
33
  input: {
33
34
  Name: name,
@@ -38,7 +39,6 @@ export class SimCfnSsmParameterCreator {
38
39
  AllowedPattern: parameterProperties.allowedPattern(),
39
40
  DataType: parameterProperties.dataType(),
40
41
  Policies: parameterProperties.policies(),
41
- Tags: parameterProperties.tags(),
42
42
  },
43
43
  }, options);
44
44
  const parameter = this.ssm.findParameter(name);
@@ -1,6 +1,5 @@
1
1
  import type { SimCfnResource } from "../../../cloudformation/resource/sim-cfn-resource.js";
2
2
  import type { SimCfnTemplateValueRecord } from "../../../cloudformation/template/value/sim-cfn-template-value.js";
3
- import type { SimSsmTag } from "../../command/parameter/parameter.command.js";
4
3
  interface SimCfnSsmParameterPropertiesProperties {
5
4
  readonly resource: SimCfnResource;
6
5
  readonly properties: SimCfnTemplateValueRecord;
@@ -60,13 +59,19 @@ export declare class SimCfnSsmParameterProperties {
60
59
  */
61
60
  policies(): string | undefined;
62
61
  /**
63
- * The parameter Tags, which PutParameter refuses.
62
+ * Record the tags the parameter is written without.
64
63
  *
65
- * CloudFormation carries these as a map of names to values for this
66
- * Resource type, rather than the list of Key/Value pairs most Resource
67
- * types use, so they are turned into the list shape PutParameter takes.
64
+ * `Tags` is the one difference from `PutParameter`, which refuses it
65
+ * outright. A template's tags are usually the whole stack's, and a CDK app
66
+ * calling `Tags.of(app).add(...)` tags every parameter in it. The parameter
67
+ * holds the same value either way, so the deploy stands and the omission is
68
+ * recorded.
69
+ *
70
+ * The shape is still read, because CloudFormation carries these as a map of
71
+ * names to values for this Resource type and a template AWS refuses should
72
+ * not deploy here.
68
73
  */
69
- tags(): readonly SimSsmTag[] | undefined;
74
+ recordIgnoredTags(): void;
70
75
  private string;
71
76
  private stringValue;
72
77
  private propertyError;
@@ -88,23 +88,32 @@ export class SimCfnSsmParameterProperties {
88
88
  return this.string(this.properties["Policies"], "Policies");
89
89
  }
90
90
  /**
91
- * The parameter Tags, which PutParameter refuses.
91
+ * Record the tags the parameter is written without.
92
92
  *
93
- * CloudFormation carries these as a map of names to values for this
94
- * Resource type, rather than the list of Key/Value pairs most Resource
95
- * types use, so they are turned into the list shape PutParameter takes.
93
+ * `Tags` is the one difference from `PutParameter`, which refuses it
94
+ * outright. A template's tags are usually the whole stack's, and a CDK app
95
+ * calling `Tags.of(app).add(...)` tags every parameter in it. The parameter
96
+ * holds the same value either way, so the deploy stands and the omission is
97
+ * recorded.
98
+ *
99
+ * The shape is still read, because CloudFormation carries these as a map of
100
+ * names to values for this Resource type and a template AWS refuses should
101
+ * not deploy here.
96
102
  */
97
- tags() {
103
+ recordIgnoredTags() {
98
104
  const tags = this.properties["Tags"];
99
105
  if (tags === undefined) {
100
- return undefined;
106
+ return;
101
107
  }
102
108
  if (typeof tags !== "object" || tags === null || Array.isArray(tags)) {
103
109
  throw this.propertyError("Tags must be an object");
104
110
  }
105
- return Object.entries(tags).map(([key, value]) => {
106
- return { Key: key, Value: this.stringValue(value, `Tags.${key}`) };
107
- });
111
+ for (const [key, value] of Object.entries(tags)) {
112
+ this.stringValue(value, `Tags.${key}`);
113
+ }
114
+ this.resource.ignoreProperty("Tags", "AWS::SSM::Parameter property Tags is not simulated, so the parameter " +
115
+ "is written without them. Nothing reads them back and no " +
116
+ "aws:ResourceTag condition key matches them.");
108
117
  }
109
118
  string(value, name) {
110
119
  if (value === undefined) {