@kensio/yulin 1.21.1 → 1.21.3

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 (50) hide show
  1. package/dist/service/cloudformation/resource/factory/sim-cfn-resource-factory.type.d.ts +9 -0
  2. package/dist/service/cloudformation/resource/sim-cfn-resource.type.d.ts +11 -0
  3. package/dist/service/cloudformation/resource/update/sim-cfn-resource-update-validator.d.ts +26 -0
  4. package/dist/service/cloudformation/resource/update/sim-cfn-resource-update-validator.js +55 -0
  5. package/dist/service/cloudformation/stack/sim-cfn-stack-resource-operations.d.ts +5 -0
  6. package/dist/service/cloudformation/stack/sim-cfn-stack-resource-operations.js +17 -0
  7. package/dist/service/cloudformation/stack/update/sim-cfn-stack-update-plan.d.ts +7 -1
  8. package/dist/service/cloudformation/stack/update/sim-cfn-stack-update-plan.js +13 -0
  9. package/dist/service/cloudformation/stack/update/sim-cfn-stack-updater.js +1 -0
  10. package/dist/service/dynamodb/cfn/sim-cfn-dynamodb-resource-factory.d.ts +5 -0
  11. package/dist/service/dynamodb/cfn/sim-cfn-dynamodb-resource-factory.js +15 -0
  12. package/dist/service/dynamodb/cfn/table/sim-cfn-dynamodb-table-update-validator.d.ts +24 -0
  13. package/dist/service/dynamodb/cfn/table/sim-cfn-dynamodb-table-update-validator.js +38 -0
  14. package/dist/service/dynamodb/table/sim-dynamodb-index-update.js +1 -2
  15. package/dist/service/ses/command/configuration-set/sim-ses-configuration-set-commands.js +13 -5
  16. package/dist/service/ses/command/configuration-set/sim-ses-configuration-set-options.d.ts +1 -1
  17. package/dist/service/ses/command/configuration-set/sim-ses-configuration-set-options.js +9 -9
  18. package/dist/service/ses/command/send/send.command.d.ts +10 -1
  19. package/dist/service/ses/command/send/sim-ses-read-content.d.ts +2 -1
  20. package/dist/service/ses/command/send/sim-ses-send-email.js +1 -0
  21. package/dist/service/ses/command/send/sim-ses-service-send.js +1 -0
  22. package/dist/service/ses/command/send/sim-ses-simple-content.js +26 -5
  23. package/dist/service/ses/command/send/sim-ses-template-send.js +1 -0
  24. package/dist/service/ses/command/suppression/suppression.command.d.ts +3 -2
  25. package/dist/service/ses/configuration-set/sim-ses-configuration-set.d.ts +9 -10
  26. package/dist/service/ses/configuration-set/sim-ses-configuration-set.js +7 -8
  27. package/dist/service/ses/email/sim-ses-sent-email-store.d.ts +8 -0
  28. package/dist/service/ses/email/sim-ses-sent-email-store.js +17 -0
  29. package/dist/service/ses/email/sim-ses-sent-email.d.ts +18 -0
  30. package/dist/service/ses/email/sim-ses-sent-email.js +3 -0
  31. package/dist/service/ses/error/sim-ses.error.d.ts +10 -0
  32. package/dist/service/ses/error/sim-ses.error.js +10 -0
  33. package/dist/service/ses/feedback/sim-ses-feedback-recorder.d.ts +34 -0
  34. package/dist/service/ses/feedback/sim-ses-feedback-recorder.js +55 -0
  35. package/dist/service/ses/index.d.ts +3 -2
  36. package/dist/service/ses/index.js +2 -1
  37. package/dist/service/ses/sim-ses-commands.d.ts +2 -0
  38. package/dist/service/ses/sim-ses-commands.js +9 -0
  39. package/dist/service/ses/sim-ses-configuration-sets.d.ts +4 -4
  40. package/dist/service/ses/sim-ses-configuration-sets.js +4 -4
  41. package/dist/service/ses/sim-ses-suppression.d.ts +3 -2
  42. package/dist/service/ses/sim-ses-suppression.js +3 -2
  43. package/dist/service/ses/sim-ses-v2.d.ts +13 -0
  44. package/dist/service/ses/sim-ses-v2.js +13 -0
  45. package/dist/service/ses/suppression/sim-ses-suppressed-destination.d.ts +3 -4
  46. package/dist/service/ses/suppression/sim-ses-suppressed-destination.js +3 -4
  47. package/dist/service/ses/suppression/sim-ses-suppression-list.d.ts +4 -4
  48. package/dist/service/ses/suppression/sim-ses-suppression-list.js +4 -4
  49. package/docs/services/ses/README.md +164 -41
  50. package/package.json +2 -2
@@ -1,4 +1,5 @@
1
1
  import type { SimCfnResource, SimCloudFormationResourceCreateContext, SimCloudFormationResourceDeleteContext } from "../sim-cfn-resource.js";
2
+ import type { SimCloudFormationResourceUpdateContext } from "../sim-cfn-resource.type.js";
2
3
  export interface SimCfnServiceResourceFactory {
3
4
  create(resourceTypeName: string, resource: SimCfnResource, context: SimCloudFormationResourceCreateContext): Promise<object | undefined>;
4
5
  /**
@@ -15,6 +16,14 @@ export interface SimCfnServiceResourceFactory {
15
16
  * the teardown records it and carries on.
16
17
  */
17
18
  delete(resourceTypeName: string, resource: SimCfnResource, context: SimCloudFormationResourceDeleteContext): Promise<void>;
19
+ /**
20
+ * Refuse an invalid replacement before CloudFormation deletes anything.
21
+ *
22
+ * Most simulated Resource types have no update-specific validation. A
23
+ * service can implement this hook when an update carries constraints that a
24
+ * create request cannot check.
25
+ */
26
+ assertUpdateAllowed?(resourceTypeName: string, current: SimCfnResource, updated: SimCfnResource, context: SimCloudFormationResourceUpdateContext): Promise<void> | void;
18
27
  }
19
28
  export interface SimCloudFormationParsedResourceType {
20
29
  readonly providerName: string;
@@ -86,3 +86,14 @@ export interface SimCloudFormationResourceDeleteContext {
86
86
  /** The principal the teardown runs as, as creation carries it. */
87
87
  readonly caller?: SimAwsCaller | undefined;
88
88
  }
89
+ /**
90
+ * The two resolved Resource definitions available to update validation.
91
+ */
92
+ export interface SimCloudFormationResourceUpdateContext {
93
+ readonly simAws: SimAws;
94
+ readonly currentResources: ReadonlyMap<string, SimCfnResource>;
95
+ readonly updatedResources: ReadonlyMap<string, SimCfnResource>;
96
+ readonly currentResolvedProperties: SimCfnTemplateValueRecord;
97
+ readonly updatedResolvedProperties: SimCfnTemplateValueRecord;
98
+ readonly caller?: SimAwsCaller | undefined;
99
+ }
@@ -0,0 +1,26 @@
1
+ import type { SimAws } from "../../../aws/sim-aws.js";
2
+ import type { SimAwsCaller } from "../../../aws/caller/sim-aws-caller.js";
3
+ import type { SimCfnResource } from "../sim-cfn-resource.js";
4
+ interface SimCfnResourceUpdateValidatorProperties {
5
+ readonly current: SimCfnResource;
6
+ readonly updated: SimCfnResource;
7
+ }
8
+ interface AssertSimCfnResourceUpdateAllowedProperties {
9
+ readonly simAws: SimAws;
10
+ readonly currentResources: ReadonlyMap<string, SimCfnResource>;
11
+ readonly updatedResources: ReadonlyMap<string, SimCfnResource>;
12
+ readonly caller?: SimAwsCaller | undefined;
13
+ }
14
+ /**
15
+ * Runs service-specific validation for one Resource replacement.
16
+ */
17
+ export declare class SimCfnResourceUpdateValidator {
18
+ private readonly current;
19
+ private readonly updated;
20
+ constructor(properties: SimCfnResourceUpdateValidatorProperties);
21
+ /**
22
+ * Refuse an invalid update before its current Resource is deleted.
23
+ */
24
+ assertAllowed(properties: AssertSimCfnResourceUpdateAllowedProperties): Promise<void>;
25
+ }
26
+ export {};
@@ -0,0 +1,55 @@
1
+ import { parseSimCloudFormationResourceType } from "../parser/sim-cfn-resource-parser.js";
2
+ import { resolveSimCloudFormationServiceResourceFactory } from "../resolve/service/sim-cfn-service-resolver.js";
3
+ import { isSimCfnUnsupportedResourceError } from "../unsupported/sim-cfn-unsupported-resource.js";
4
+ /**
5
+ * Runs service-specific validation for one Resource replacement.
6
+ */
7
+ export class SimCfnResourceUpdateValidator {
8
+ current;
9
+ updated;
10
+ constructor(properties) {
11
+ this.current = properties.current;
12
+ this.updated = properties.updated;
13
+ }
14
+ /**
15
+ * Refuse an invalid update before its current Resource is deleted.
16
+ */
17
+ async assertAllowed(properties) {
18
+ const { current, updated } = this;
19
+ const { simAws, currentResources, updatedResources, caller } = properties;
20
+ const { type } = updated;
21
+ if (type === undefined || current.type !== type) {
22
+ return;
23
+ }
24
+ const resourceType = parseSimCloudFormationResourceType(type);
25
+ let factory;
26
+ try {
27
+ factory = resolveSimCloudFormationServiceResourceFactory(simAws, updated.accountRegionScope, resourceType);
28
+ }
29
+ catch (error) {
30
+ if (isSimCfnUnsupportedResourceError(error)) {
31
+ return;
32
+ }
33
+ throw error;
34
+ }
35
+ if (factory.assertUpdateAllowed === undefined) {
36
+ return;
37
+ }
38
+ await factory.assertUpdateAllowed(resourceType.resourceTypeName, current, updated, {
39
+ simAws,
40
+ currentResources,
41
+ updatedResources,
42
+ currentResolvedProperties: await current.resolvedProperties({
43
+ simAws,
44
+ resources: currentResources,
45
+ caller,
46
+ }),
47
+ updatedResolvedProperties: await updated.resolvedProperties({
48
+ simAws,
49
+ resources: updatedResources,
50
+ caller,
51
+ }),
52
+ caller,
53
+ });
54
+ }
55
+ }
@@ -8,6 +8,7 @@ import type { SimCfnResource } from "../resource/sim-cfn-resource.js";
8
8
  import type { SimCfnTemplate } from "../template/sim-cfn-template.js";
9
9
  import type { SimCfnResourceOrder } from "./deploy/sim-cfn-resource-order.js";
10
10
  import { SimCfnStackUpdater } from "./update/sim-cfn-stack-updater.js";
11
+ import type { SimCfnStackResourceReplacement } from "./update/sim-cfn-stack-update-plan.js";
11
12
  import type { SimCloudFormationStackName } from "./sim-cfn-stack.js";
12
13
  interface SimCfnStackUpdateProperties {
13
14
  readonly background: BackgroundScheduler;
@@ -87,6 +88,10 @@ export declare class SimCfnStackResourceOperations {
87
88
  * Stack.
88
89
  */
89
90
  delete(resources: ReadonlyMap<string, SimCfnResource>, deleting: readonly SimCfnResource[]): Promise<void>;
91
+ /**
92
+ * Validate every Resource replacement before the update changes the Stack.
93
+ */
94
+ assertUpdatesAllowed(currentResources: ReadonlyMap<string, SimCfnResource>, updatedResources: ReadonlyMap<string, SimCfnResource>, replacements: readonly SimCfnStackResourceReplacement[]): Promise<void>;
90
95
  /**
91
96
  * An update of this Stack from a changed template, in the same scope.
92
97
  */
@@ -2,6 +2,7 @@ import { SimCdkAssetsPublisher } from "../cdk/assets/sim-cdk-assets-publisher.js
2
2
  import { SimCfnStackResourceCreator } from "./deploy/sim-cfn-stack-resource-creator.js";
3
3
  import { SimCfnStackResourceDeleter } from "./teardown/sim-cfn-stack-resource-deleter.js";
4
4
  import { SimCfnStackUpdater } from "./update/sim-cfn-stack-updater.js";
5
+ import { SimCfnResourceUpdateValidator } from "../resource/update/sim-cfn-resource-update-validator.js";
5
6
  /**
6
7
  * What a Stack can ask simulated AWS to do with its Resources.
7
8
  *
@@ -77,6 +78,22 @@ export class SimCfnStackResourceOperations {
77
78
  async delete(resources, deleting) {
78
79
  await this.deleter(resources).delete(deleting);
79
80
  }
81
+ /**
82
+ * Validate every Resource replacement before the update changes the Stack.
83
+ */
84
+ async assertUpdatesAllowed(currentResources, updatedResources, replacements) {
85
+ await Promise.all(replacements.map(async ({ current, updated }) => {
86
+ await new SimCfnResourceUpdateValidator({
87
+ current,
88
+ updated,
89
+ }).assertAllowed({
90
+ simAws: this.simAws,
91
+ currentResources,
92
+ updatedResources,
93
+ caller: this.caller,
94
+ });
95
+ }));
96
+ }
80
97
  /**
81
98
  * An update of this Stack from a changed template, in the same scope.
82
99
  */
@@ -3,6 +3,10 @@ interface SimCfnStackUpdatePlanProperties {
3
3
  readonly current: ReadonlyMap<string, SimCfnResource>;
4
4
  readonly updated: ReadonlyMap<string, SimCfnResource>;
5
5
  }
6
+ export interface SimCfnStackResourceReplacement {
7
+ readonly current: SimCfnResource;
8
+ readonly updated: SimCfnResource;
9
+ }
6
10
  /**
7
11
  * What an update has to do to a Stack's Resources, worked out before any of it
8
12
  * happens.
@@ -17,6 +21,8 @@ interface SimCfnStackUpdatePlanProperties {
17
21
  * simCfnStackReplacedLogicalIds decides what changed.
18
22
  */
19
23
  export declare class SimCfnStackUpdatePlan {
24
+ /** The current and updated halves of each Resource replacement. */
25
+ readonly replacements: readonly SimCfnStackResourceReplacement[];
20
26
  /**
21
27
  * The deployed Resources to delete: the ones the new template drops, and the
22
28
  * deployed halves of the ones it replaces.
@@ -27,7 +33,7 @@ export declare class SimCfnStackUpdatePlan {
27
33
  * halves of the ones it replaces.
28
34
  */
29
35
  readonly creations: readonly SimCfnResource[];
30
- private readonly updated;
36
+ readonly updated: ReadonlyMap<string, SimCfnResource>;
31
37
  constructor(properties: SimCfnStackUpdatePlanProperties);
32
38
  /**
33
39
  * Whether the update changes any Resource at all.
@@ -13,6 +13,8 @@ import { simCfnStackReplacedLogicalIds } from "./sim-cfn-stack-replaced-resource
13
13
  * simCfnStackReplacedLogicalIds decides what changed.
14
14
  */
15
15
  export class SimCfnStackUpdatePlan {
16
+ /** The current and updated halves of each Resource replacement. */
17
+ replacements;
16
18
  /**
17
19
  * The deployed Resources to delete: the ones the new template drops, and the
18
20
  * deployed halves of the ones it replaces.
@@ -28,6 +30,17 @@ export class SimCfnStackUpdatePlan {
28
30
  const { current, updated } = properties;
29
31
  const replaced = simCfnStackReplacedLogicalIds({ current, updated });
30
32
  this.updated = updated;
33
+ this.replacements = replaced
34
+ .values()
35
+ .map((logicalId) => {
36
+ const currentResource = current.get(logicalId);
37
+ const updatedResource = updated.get(logicalId);
38
+ if (currentResource === undefined || updatedResource === undefined) {
39
+ throw new Error(`CloudFormation replacement ${logicalId} is missing one of its Resource definitions`);
40
+ }
41
+ return { current: currentResource, updated: updatedResource };
42
+ })
43
+ .toArray();
31
44
  // Both lists are worked out now rather than on demand, because the Stack's
32
45
  // Resource map is the current one and applying the plan changes it.
33
46
  this.deletions = current
@@ -57,6 +57,7 @@ export class SimCfnStackUpdater {
57
57
  async apply() {
58
58
  const { resources, operations } = this.properties;
59
59
  const { plan } = this;
60
+ await operations.assertUpdatesAllowed(resources, plan.updated, plan.replacements);
60
61
  await operations.delete(resources, plan.deletions);
61
62
  plan.applyTo(resources);
62
63
  await operations.create(resources, plan.creations);
@@ -1,5 +1,6 @@
1
1
  import type { SimCfnServiceResourceFactory } from "../../cloudformation/resource/factory/sim-cfn-resource-factory.type.js";
2
2
  import type { SimCfnResource, SimCloudFormationResourceCreateContext, SimCloudFormationResourceDeleteContext } from "../../cloudformation/resource/sim-cfn-resource.js";
3
+ import type { SimCloudFormationResourceUpdateContext } from "../../cloudformation/resource/sim-cfn-resource.type.js";
3
4
  import type { SimDynamoDb } from "../sim-dynamodb.js";
4
5
  interface SimDynamoDbCfnResourceFactoryProperties {
5
6
  readonly dynamoDb: SimDynamoDb;
@@ -26,5 +27,9 @@ export declare class SimDynamoDbCfnResourceFactory implements SimCfnServiceResou
26
27
  * Delete a simulated DynamoDB resource created from a CloudFormation Resource.
27
28
  */
28
29
  delete(resourceTypeName: string, resource: SimCfnResource, context: SimCloudFormationResourceDeleteContext): Promise<void>;
30
+ /**
31
+ * Validate a DynamoDB Resource replacement before its table is deleted.
32
+ */
33
+ assertUpdateAllowed(resourceTypeName: string, current: SimCfnResource, updated: SimCfnResource, context: SimCloudFormationResourceUpdateContext): void;
29
34
  }
30
35
  export {};
@@ -2,6 +2,7 @@ import { simCfnResourceCallerOptions } from "../../cloudformation/resource/calle
2
2
  import { SimCfnDynamoDbGlobalTableCreator } from "./global-table/sim-cfn-dynamodb-global-table-creator.js";
3
3
  import { SimCfnDynamoDbTableCreator } from "./table/sim-cfn-dynamodb-table-creator.js";
4
4
  import { SimCfnDynamoDbResourceDeleter } from "./sim-cfn-dynamodb-resource-deleter.js";
5
+ import { SimCfnDynamoDbTableUpdateValidator } from "./table/sim-cfn-dynamodb-table-update-validator.js";
5
6
  /**
6
7
  * CloudFormation Resource factory for simulated DynamoDB resources.
7
8
  */
@@ -50,4 +51,18 @@ export class SimDynamoDbCfnResourceFactory {
50
51
  async delete(resourceTypeName, resource, context) {
51
52
  await this.deleter.delete(resourceTypeName, resource, simCfnResourceCallerOptions(context.caller));
52
53
  }
54
+ /**
55
+ * Validate a DynamoDB Resource replacement before its table is deleted.
56
+ */
57
+ assertUpdateAllowed(resourceTypeName, current, updated, context) {
58
+ if (resourceTypeName !== "Table") {
59
+ return;
60
+ }
61
+ new SimCfnDynamoDbTableUpdateValidator({
62
+ currentResource: current,
63
+ updatedResource: updated,
64
+ currentProperties: context.currentResolvedProperties,
65
+ updatedProperties: context.updatedResolvedProperties,
66
+ }).assertAllowed();
67
+ }
53
68
  }
@@ -0,0 +1,24 @@
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
+ interface SimCfnDynamoDbTableUpdateValidatorProperties {
4
+ readonly currentResource: SimCfnResource;
5
+ readonly updatedResource: SimCfnResource;
6
+ readonly currentProperties: SimCfnTemplateValueRecord;
7
+ readonly updatedProperties: SimCfnTemplateValueRecord;
8
+ }
9
+ /**
10
+ * Validates constraints that apply when CloudFormation updates a table.
11
+ */
12
+ export declare class SimCfnDynamoDbTableUpdateValidator {
13
+ private readonly properties;
14
+ constructor(properties: SimCfnDynamoDbTableUpdateValidatorProperties);
15
+ /**
16
+ * Refuse a template diff that asks DynamoDB for too many index operations.
17
+ *
18
+ * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html
19
+ */
20
+ assertAllowed(): void;
21
+ private tableProperties;
22
+ private indexUpdates;
23
+ }
24
+ export {};
@@ -0,0 +1,38 @@
1
+ import { readSimDynamoDbIndexName } from "../../secondary-index/sim-dynamodb-index-name.js";
2
+ import { SimDynamoDbIndexUpdate } from "../../table/sim-dynamodb-index-update.js";
3
+ import { SimCfnDynamoDbTableProperties } from "./sim-cfn-dynamodb-table-properties.js";
4
+ /**
5
+ * Validates constraints that apply when CloudFormation updates a table.
6
+ */
7
+ export class SimCfnDynamoDbTableUpdateValidator {
8
+ properties;
9
+ constructor(properties) {
10
+ this.properties = properties;
11
+ }
12
+ /**
13
+ * Refuse a template diff that asks DynamoDB for too many index operations.
14
+ *
15
+ * https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-dynamodb-table.html
16
+ */
17
+ assertAllowed() {
18
+ const { currentResource, updatedResource } = this.properties;
19
+ const current = this.tableProperties(currentResource, this.properties.currentProperties).globalSecondaryIndexes();
20
+ const updated = this.tableProperties(updatedResource, this.properties.updatedProperties).globalSecondaryIndexes();
21
+ SimDynamoDbIndexUpdate.fromInput(this.indexUpdates(current, updated));
22
+ }
23
+ tableProperties(resource, properties) {
24
+ return new SimCfnDynamoDbTableProperties({ resource, properties });
25
+ }
26
+ indexUpdates(current, updated) {
27
+ const currentNames = new Set(current.map((index) => readSimDynamoDbIndexName(index.IndexName)));
28
+ const updatedNames = new Set(updated.map((index) => readSimDynamoDbIndexName(index.IndexName)));
29
+ return [
30
+ ...updated
31
+ .filter((index) => !currentNames.has(readSimDynamoDbIndexName(index.IndexName)))
32
+ .map((index) => ({ Create: index })),
33
+ ...current
34
+ .filter((index) => !updatedNames.has(readSimDynamoDbIndexName(index.IndexName)))
35
+ .map((index) => ({ Delete: { IndexName: index.IndexName } })),
36
+ ];
37
+ }
38
+ }
@@ -73,8 +73,7 @@ export class SimDynamoDbIndexUpdate {
73
73
  .map((entry) => deletedIndexName(entry))
74
74
  .filter((name) => name !== undefined);
75
75
  if (created.length + deleted.length > 1) {
76
- throw new SimDynamoDbValidationException("You can create or delete only one global secondary index per " +
77
- "UpdateTable operation");
76
+ throw new SimDynamoDbValidationException("Cannot perform more than one GSI creation or deletion in a single update");
78
77
  }
79
78
  return new this({ created: created.at(0), deletedName: deleted.at(0) });
80
79
  }
@@ -82,15 +82,13 @@ export class SimSesConfigurationSetCommands {
82
82
  /**
83
83
  * What GetConfigurationSet answers with.
84
84
  *
85
- * Every group is reported, including the ones the set never declared, because
86
- * real SES answers with the defaults it applied rather than leaving them out.
85
+ * Sending, delivery and reputation options report their applied defaults.
86
+ * Suppression options stay absent unless the set declared an override.
87
87
  */
88
88
  function reported(configurationSet) {
89
89
  return {
90
90
  ConfigurationSetName: configurationSet.configurationSetName,
91
- SuppressionOptions: {
92
- SuppressedReasons: configurationSet.suppressedReasons,
93
- },
91
+ ...reportedSuppressionOptions(configurationSet),
94
92
  SendingOptions: { SendingEnabled: configurationSet.sendingEnabled },
95
93
  DeliveryOptions: {
96
94
  TlsPolicy: configurationSet.deliveryOptions.tlsPolicy,
@@ -102,3 +100,13 @@ function reported(configurationSet) {
102
100
  },
103
101
  };
104
102
  }
103
+ function reportedSuppressionOptions(configurationSet) {
104
+ if (configurationSet.suppressedReasons === undefined) {
105
+ return {};
106
+ }
107
+ return {
108
+ SuppressionOptions: {
109
+ SuppressedReasons: configurationSet.suppressedReasons,
110
+ },
111
+ };
112
+ }
@@ -5,6 +5,6 @@ import type { SimCreateConfigurationSetCommandInput } from "./configuration-set.
5
5
  * refuses.
6
6
  *
7
7
  * A set declaring none of these gets the defaults real SES applies: sending
8
- * on, no suppression reasons, optional TLS and no reputation metrics.
8
+ * on, account-level suppression, optional TLS and no reputation metrics.
9
9
  */
10
10
  export declare function readSimSesConfigurationSetOptions(input: SimCreateConfigurationSetCommandInput): SimSesConfigurationSetOptions;
@@ -11,11 +11,11 @@ const tlsPolicies = new Set(["REQUIRE", "OPTIONAL"]);
11
11
  * refuses.
12
12
  *
13
13
  * A set declaring none of these gets the defaults real SES applies: sending
14
- * on, no suppression reasons, optional TLS and no reputation metrics.
14
+ * on, account-level suppression, optional TLS and no reputation metrics.
15
15
  */
16
16
  export function readSimSesConfigurationSetOptions(input) {
17
17
  return {
18
- suppressedReasons: readSuppressedReasons(input.SuppressionOptions?.SuppressedReasons),
18
+ suppressedReasons: readSuppressedReasons(input.SuppressionOptions),
19
19
  sendingEnabled: input.SendingOptions?.SendingEnabled ?? true,
20
20
  deliveryOptions: readDeliveryOptions(input.DeliveryOptions),
21
21
  reputationOptions: {
@@ -26,15 +26,15 @@ export function readSimSesConfigurationSetOptions(input) {
26
26
  /**
27
27
  * The suppression reasons a set names, refusing one SES has no meaning for.
28
28
  *
29
- * An absent `SuppressionOptions` leaves this empty. Real SES falls back to the
30
- * account-level setting there, and this simulation has no account suppression
31
- * list to fall back to.
29
+ * An absent `SuppressionOptions` returns undefined, which preserves the
30
+ * account-level fallback. A present group with no reasons returns an empty
31
+ * override and disables suppression for messages sent through the set.
32
32
  */
33
- function readSuppressedReasons(reasons) {
34
- if (reasons === undefined) {
35
- return [];
33
+ function readSuppressedReasons(options) {
34
+ if (options === undefined) {
35
+ return undefined;
36
36
  }
37
- return reasons.map((reason) => {
37
+ return (options.SuppressedReasons ?? []).map((reason) => {
38
38
  if (!isSuppressedReason(reason)) {
39
39
  throw new SimSesBadRequestException(`1 validation error detected: Value '${reason}' at ` +
40
40
  `'suppressionOptions.suppressedReasons' failed to satisfy ` +
@@ -11,11 +11,20 @@ export interface SimSesBody {
11
11
  readonly Text?: SimSesContent | undefined;
12
12
  readonly Html?: SimSesContent | undefined;
13
13
  }
14
+ export interface SimSesAttachment {
15
+ readonly RawContent?: Uint8Array | undefined;
16
+ readonly ContentDisposition?: "ATTACHMENT" | "INLINE" | undefined;
17
+ readonly FileName?: string | undefined;
18
+ readonly ContentDescription?: string | undefined;
19
+ readonly ContentId?: string | undefined;
20
+ readonly ContentTransferEncoding?: "BASE64" | "QUOTED_PRINTABLE" | "SEVEN_BIT" | undefined;
21
+ readonly ContentType?: string | undefined;
22
+ }
14
23
  export interface SimSesMessage {
15
24
  readonly Subject?: SimSesContent | undefined;
16
25
  readonly Body?: SimSesBody | undefined;
17
26
  readonly Headers?: readonly unknown[] | undefined;
18
- readonly Attachments?: readonly unknown[] | undefined;
27
+ readonly Attachments?: readonly SimSesAttachment[] | undefined;
19
28
  }
20
29
  export interface SimSesRawMessage {
21
30
  readonly Data?: Uint8Array | string | undefined;
@@ -1,4 +1,4 @@
1
- import type { SimSesSentEmailBody } from "../../email/sim-ses-sent-email.js";
1
+ import type { SimSesSentEmailAttachment, SimSesSentEmailBody } from "../../email/sim-ses-sent-email.js";
2
2
  /**
3
3
  * What a message says, read out of the content of a send.
4
4
  *
@@ -11,6 +11,7 @@ import type { SimSesSentEmailBody } from "../../email/sim-ses-sent-email.js";
11
11
  export interface SimSesReadContent {
12
12
  readonly subject: string;
13
13
  readonly body: SimSesSentEmailBody;
14
+ readonly attachments: readonly SimSesSentEmailAttachment[];
14
15
  /** The template this was rendered from, if it was rendered from a stored one. */
15
16
  readonly templateName: string | undefined;
16
17
  /** The data the placeholders were filled from, parsed out of the JSON. */
@@ -65,6 +65,7 @@ export class SimSesSendEmail {
65
65
  replyToAddresses: [...(input.ReplyToAddresses ?? [])],
66
66
  subject: content.subject,
67
67
  body: content.body,
68
+ attachments: content.attachments,
68
69
  templateName: content.templateName,
69
70
  templateData: content.templateData,
70
71
  configurationSetName,
@@ -62,6 +62,7 @@ export class SimSesServiceSend {
62
62
  replyToAddresses: [...request.replyToAddresses],
63
63
  subject: request.subject,
64
64
  body: { text: request.body, html: undefined },
65
+ attachments: [],
65
66
  templateName: undefined,
66
67
  templateData: undefined,
67
68
  configurationSetName,
@@ -1,4 +1,4 @@
1
- import { SimSesBadRequestException, SimSesUnsupportedOperationException, } from "../../error/sim-ses.error.js";
1
+ import { SimSesBadRequestException } from "../../error/sim-ses.error.js";
2
2
  /**
3
3
  * Read a message a send wrote out in full, refusing what real SES refuses and
4
4
  * what this simulation does not model.
@@ -8,10 +8,6 @@ import { SimSesBadRequestException, SimSesUnsupportedOperationException, } from
8
8
  * not have accepted.
9
9
  */
10
10
  export function readSimSesSimpleMessage(message) {
11
- if (message.Attachments !== undefined) {
12
- throw new SimSesUnsupportedOperationException("Attachments are not simulated, so SendEmail refuses them rather than " +
13
- "recording a message without them");
14
- }
15
11
  const subject = message.Subject?.Data;
16
12
  if (subject === undefined) {
17
13
  throw new SimSesBadRequestException("1 validation error detected: Value at 'content.simple.subject' " +
@@ -26,7 +22,32 @@ export function readSimSesSimpleMessage(message) {
26
22
  return {
27
23
  subject,
28
24
  body: { text, html },
25
+ attachments: (message.Attachments ?? []).map((attachment, index) => readAttachment(attachment, index)),
29
26
  templateName: undefined,
30
27
  templateData: undefined,
31
28
  };
32
29
  }
30
+ /** Read and validate one attachment from a simple message. */
31
+ function readAttachment(attachment, index) {
32
+ if (attachment.RawContent === undefined) {
33
+ throw missingAttachmentMember(index, "rawContent");
34
+ }
35
+ if (attachment.FileName === undefined) {
36
+ throw missingAttachmentMember(index, "fileName");
37
+ }
38
+ return {
39
+ rawContent: Uint8Array.from(attachment.RawContent),
40
+ fileName: attachment.FileName,
41
+ contentType: attachment.ContentType,
42
+ contentDisposition: attachment.ContentDisposition,
43
+ contentDescription: attachment.ContentDescription,
44
+ contentId: attachment.ContentId,
45
+ contentTransferEncoding: attachment.ContentTransferEncoding,
46
+ };
47
+ }
48
+ /** Build the SES validation error for a missing attachment member. */
49
+ function missingAttachmentMember(index, member) {
50
+ return new SimSesBadRequestException("1 validation error detected: Value at " +
51
+ `'content.simple.attachments.${String(index)}.${member}' failed to ` +
52
+ "satisfy constraint: Member must not be null");
53
+ }
@@ -35,6 +35,7 @@ export class SimSesTemplateSendReader {
35
35
  text: renderOptional(content.text, data),
36
36
  html: renderOptional(content.html, data),
37
37
  },
38
+ attachments: [],
38
39
  templateName: template.TemplateName,
39
40
  templateData: data,
40
41
  };
@@ -2,8 +2,9 @@ import type { SimResponseMetadata } from "../../../aws/metadata/response-metadat
2
2
  /**
3
3
  * What a suppression list entry says, in the shape the SES v2 API reports it.
4
4
  *
5
- * `Attributes` is left out. It describes the bounce or complaint SES put the
6
- * address on the list for, and nothing bounces here.
5
+ * `Attributes` is left out. Real SES uses it for details of the feedback event
6
+ * that put the address on the list. Explicit simulator feedback records the
7
+ * reason and time only.
7
8
  */
8
9
  export interface SimSesSuppressedDestinationDetail {
9
10
  readonly EmailAddress: string;
@@ -32,7 +32,7 @@ export interface SimSesConfigurationSetReputationOptions {
32
32
  * Everything a configuration set holds apart from its name.
33
33
  */
34
34
  export interface SimSesConfigurationSetOptions {
35
- readonly suppressedReasons: readonly SimSesSuppressedReason[];
35
+ readonly suppressedReasons: readonly SimSesSuppressedReason[] | undefined;
36
36
  readonly sendingEnabled: boolean;
37
37
  readonly deliveryOptions: SimSesConfigurationSetDeliveryOptions;
38
38
  readonly reputationOptions: SimSesConfigurationSetReputationOptions;
@@ -48,11 +48,9 @@ interface SimSesConfigurationSetProperties {
48
48
  * A configuration set is a named group of settings a send can be made under,
49
49
  * covering suppression, whether sending is on, how the message is handed on
50
50
  * and whether reputation metrics are published. None of it changes what a
51
- * message says, which is why this is state to read back rather than behaviour
52
- * to reproduce.
53
- *
54
- * Sending is the exception, and it is left to the work that attaches a set to
55
- * a send. Here the switch is recorded and acted on by nothing.
51
+ * message says. The sending switch controls acceptance, and the suppression
52
+ * reasons control feedback. Delivery and reputation options remain state for
53
+ * a test to read back.
56
54
  */
57
55
  export declare class SimSesConfigurationSet {
58
56
  readonly configurationSetName: string;
@@ -60,11 +58,12 @@ export declare class SimSesConfigurationSet {
60
58
  /**
61
59
  * The reasons that would put a recipient on the account suppression list.
62
60
  *
63
- * Empty where the set declares no `SuppressionOptions`. Real SES falls back
64
- * to the account setting there, and this simulation has no account
65
- * suppression list to fall back to.
61
+ * Undefined where the set declares no `SuppressionOptions`, which makes a
62
+ * send fall back to the account setting. An empty list is an explicit
63
+ * override that disables suppression for feedback recorded for a message
64
+ * sent through this set.
66
65
  */
67
- readonly suppressedReasons: readonly SimSesSuppressedReason[];
66
+ readonly suppressedReasons: readonly SimSesSuppressedReason[] | undefined;
68
67
  /** Whether SES would accept a send made through this set. */
69
68
  readonly sendingEnabled: boolean;
70
69
  readonly deliveryOptions: SimSesConfigurationSetDeliveryOptions;
@@ -10,11 +10,9 @@ export const simSesSuppressedReasons = [
10
10
  * A configuration set is a named group of settings a send can be made under,
11
11
  * covering suppression, whether sending is on, how the message is handed on
12
12
  * and whether reputation metrics are published. None of it changes what a
13
- * message says, which is why this is state to read back rather than behaviour
14
- * to reproduce.
15
- *
16
- * Sending is the exception, and it is left to the work that attaches a set to
17
- * a send. Here the switch is recorded and acted on by nothing.
13
+ * message says. The sending switch controls acceptance, and the suppression
14
+ * reasons control feedback. Delivery and reputation options remain state for
15
+ * a test to read back.
18
16
  */
19
17
  export class SimSesConfigurationSet {
20
18
  configurationSetName;
@@ -22,9 +20,10 @@ export class SimSesConfigurationSet {
22
20
  /**
23
21
  * The reasons that would put a recipient on the account suppression list.
24
22
  *
25
- * Empty where the set declares no `SuppressionOptions`. Real SES falls back
26
- * to the account setting there, and this simulation has no account
27
- * suppression list to fall back to.
23
+ * Undefined where the set declares no `SuppressionOptions`, which makes a
24
+ * send fall back to the account setting. An empty list is an explicit
25
+ * override that disables suppression for feedback recorded for a message
26
+ * sent through this set.
28
27
  */
29
28
  suppressedReasons;
30
29
  /** Whether SES would accept a send made through this set. */