| 1 | import * as AWS from "@/AWS"; |
| 2 | import { AccountSettings } from "@/AWS/SES"; |
| 3 | import * as Test from "@/Test/Alchemy"; |
| 4 | import * as sesv2 from "@distilled.cloud/aws/sesv2"; |
| 5 | import { expect } from "alchemy-test"; |
| 6 | import * as Effect from "effect/Effect"; |
| 7 | |
| 8 | const { test } = Test.make({ providers: AWS.providers() }); |
| 9 | |
| 10 | const 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. |
| 23 | test.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 | |
| 74 | const getSendingEnabled = sesv2 |
| 75 | .getAccount({}) |
| 76 | .pipe(Effect.map((response) => response.SendingEnabled ?? false)); |
| 77 | |
| 78 | const 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. |
| 90 | test.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 | ); |