1import * as AWS from "@/AWS";
2import { AccountSettings } from "@/AWS/SES";
3import * as Test from "@/Test/Alchemy";
4import * as sesv2 from "@distilled.cloud/aws/sesv2";
5import { expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7
8const { test } = Test.make({ providers: AWS.providers() });
9
10const getSuppressedReasons = sesv2
11 .getAccount({})
12 .pipe(
13 Effect.map(
14 (response) => response.SuppressionAttributes?.SuppressedReasons ?? [],
15 ),
16 );
17
18// Account/region-global singleton: capture the live suppression configuration
19// up front and restore it on scope close so the test leaves the account exactly
20// as it found it, even if an assertion fails mid-way. Deleting the resource is
21// a no-op (it leaves settings in place), so the restore MUST be explicit.
22// `exclusive` because this mutates process-global account state.
23test.provider(
24 "manages the account suppression list in place",
25 (stack) =>
26 Effect.gen(function* () {
27 yield* stack.destroy();
28
29 const originalReasons = yield* getSuppressedReasons;
30 yield* Effect.addFinalizer(() =>
31 sesv2
32 .putAccountSuppressionAttributes({
33 SuppressedReasons: originalReasons,
34 })
35 .pipe(Effect.ignore),
36 );
37
38 // Create — converge the account suppression list onto both reasons.
39 const created = yield* stack.deploy(
40 AccountSettings("Account", {
41 suppression: { reasons: ["BOUNCE", "COMPLAINT"] },
42 }),
43 );
44 expect([...created.suppressedReasons].sort()).toEqual([
45 "BOUNCE",
46 "COMPLAINT",
47 ]);
48 expect([...(yield* getSuppressedReasons)].sort()).toEqual([
49 "BOUNCE",
50 "COMPLAINT",
51 ]);
52
53 // Update in place — narrow the suppression list to a single reason.
54 const updated = yield* stack.deploy(
55 AccountSettings("Account", {
56 suppression: { reasons: ["BOUNCE"] },
57 }),
58 );
59 expect(updated.suppressedReasons).toEqual(["BOUNCE"]);
60 expect(yield* getSuppressedReasons).toEqual(["BOUNCE"]);
61
62 // Destroy is a no-op: the account settings remain as last configured
63 // until the finalizer restores the captured original.
64 yield* stack.destroy();
65 expect(yield* getSuppressedReasons).toEqual(["BOUNCE"]);
66 }),
67 {
68 tags: ["provider:aws", "provider:aws:ses", "live"],
69 timeout: 120_000,
70 exclusive: true,
71 },
72);
73
74const getSendingEnabled = sesv2
75 .getAccount({})
76 .pipe(Effect.map((response) => response.SendingEnabled ?? false));
77
78const restoreSendingEnabled = (enabled: boolean) =>
79 sesv2
80 .putAccountSendingAttributes({ SendingEnabled: enabled })
81 .pipe(Effect.ignore);
82
83// Toggling `sendingEnabled` pauses ALL sending for the account in the run's
84// region. It cannot be isolated to a throwaway region: `AWS.providers()`
85// merges `Region.fromEnvironment` into the provider layer itself, so a
86// `Region` provided around the test body never reaches the provider's SDK
87// calls. Containment is therefore `exclusive` (no other test runs in-process
88// while this one holds the lock) plus a finalizer that restores the captured
89// value on failure and interruption as well as success.
90test.provider(
91 "toggles account-level sending in place",
92 (stack) =>
93 Effect.gen(function* () {
94 yield* stack.destroy();
95
96 const original = yield* getSendingEnabled;
97 yield* Effect.addFinalizer(() => restoreSendingEnabled(original));
98
99 // Sending starts enabled; converge it to disabled.
100 const disabled = yield* stack.deploy(
101 AccountSettings("Account", { sendingEnabled: false }),
102 );
103 expect(disabled.sendingEnabled).toBe(false);
104 expect(yield* getSendingEnabled).toBe(false);
105
106 // Re-enable in place — the same reconciler converges either direction.
107 const enabled = yield* stack.deploy(
108 AccountSettings("Account", { sendingEnabled: true }),
109 );
110 expect(enabled.sendingEnabled).toBe(true);
111 expect(yield* getSendingEnabled).toBe(true);
112
113 yield* stack.destroy();
114 }),
115 {
116 tags: ["provider:aws", "provider:aws:ses", "live"],
117 timeout: 120_000,
118 exclusive: true,
119 },
120);