1import * as AWS from "@/AWS";
2import { ReceiptRuleSet } from "@/AWS/SES";
3import * as Test from "@/Test/Alchemy";
4import * as ses from "@distilled.cloud/aws/ses";
5import { expect } from "alchemy-test";
6import * as Data from "effect/Data";
7import * as Effect from "effect/Effect";
8import * as Schedule from "effect/Schedule";
9
10const { test } = Test.make({ providers: AWS.providers() });
11
12class RuleSetStillExists extends Data.TaggedError("RuleSetStillExists")<{
13 readonly name: string;
14}> {}
15
16const assertRuleSetDeleted = (name: string) =>
17 ses.describeReceiptRuleSet({ RuleSetName: name }).pipe(
18 Effect.flatMap(() => Effect.fail(new RuleSetStillExists({ name }))),
19 Effect.catchTag("RuleSetDoesNotExistException", () => Effect.void),
20 Effect.retry({
21 while: (e) => e._tag === "RuleSetStillExists",
22 schedule: Schedule.max([Schedule.exponential(500), Schedule.recurs(8)]),
23 }),
24 );
25
26test.provider(
27 "receipt rule set lifecycle: create, no-op convergence, delete",
28 (stack) =>
29 Effect.gen(function* () {
30 yield* stack.destroy();
31
32 const ruleSet = yield* stack.deploy(
33 Effect.gen(function* () {
34 return yield* ReceiptRuleSet("Inbound", {});
35 }),
36 );
37
38 expect(ruleSet.ruleSetName).toBeDefined();
39
40 // out-of-band verification via distilled
41 const observed = yield* ses.describeReceiptRuleSet({
42 RuleSetName: ruleSet.ruleSetName,
43 });
44 expect(observed.Metadata?.Name).toBe(ruleSet.ruleSetName);
45
46 // re-deploy converges to a no-op (the set already exists)
47 const again = yield* stack.deploy(
48 Effect.gen(function* () {
49 return yield* ReceiptRuleSet("Inbound", {});
50 }),
51 );
52 expect(again.ruleSetName).toBe(ruleSet.ruleSetName);
53
54 yield* stack.destroy();
55 yield* assertRuleSetDeleted(ruleSet.ruleSetName);
56 }),
57 { tags: ["provider:aws", "provider:aws:ses", "live"], timeout: 120_000 },
58);
59
60test.provider(
61 "custom name replaces on rename",
62 (stack) =>
63 Effect.gen(function* () {
64 yield* stack.destroy();
65
66 const first = yield* stack.deploy(
67 Effect.gen(function* () {
68 return yield* ReceiptRuleSet("Named", {
69 ruleSetName: "alchemy-test-ses-ruleset-a",
70 });
71 }),
72 );
73 expect(first.ruleSetName).toBe("alchemy-test-ses-ruleset-a");
74
75 const second = yield* stack.deploy(
76 Effect.gen(function* () {
77 return yield* ReceiptRuleSet("Named", {
78 ruleSetName: "alchemy-test-ses-ruleset-b",
79 });
80 }),
81 );
82 expect(second.ruleSetName).toBe("alchemy-test-ses-ruleset-b");
83 yield* assertRuleSetDeleted("alchemy-test-ses-ruleset-a");
84
85 yield* stack.destroy();
86 yield* assertRuleSetDeleted("alchemy-test-ses-ruleset-b");
87 }),
88 { tags: ["provider:aws", "provider:aws:ses", "live"], timeout: 120_000 },
89);