| 1 | import * as Cloudflare from "@/Cloudflare"; |
| 2 | import * as Provider from "@/Provider"; |
| 3 | import * as RemovalPolicy from "@/RemovalPolicy"; |
| 4 | import * as Test from "@/Test/Alchemy"; |
| 5 | import { poll } from "@/Util/poll.ts"; |
| 6 | import { expect } from "alchemy-test"; |
| 7 | import * as Effect from "effect/Effect"; |
| 8 | import { MinimumLogLevel } from "effect/References"; |
| 9 | import * as Schedule from "effect/Schedule"; |
| 10 | import { emailRoutingScoped } from "./scope.ts"; |
| 11 | |
| 12 | const { test } = Test.make({ providers: Cloudflare.providers() }); |
| 13 | |
| 14 | const logLevel = Effect.provideService( |
| 15 | MinimumLogLevel, |
| 16 | process.env.DEBUG ? "Debug" : "Info", |
| 17 | ); |
| 18 | |
| 19 | // A deterministic STANDING destination address used for the list test. |
| 20 | // Cloudflare sends a verification email on first create; the address still |
| 21 | // shows up in the account-scoped list whether or not it has been verified, |
| 22 | // which is all the list() assertion needs. |
| 23 | // |
| 24 | // The address is retained (never deleted): Cloudflare refuses to delete a |
| 25 | // destination address for ~15 minutes after creation |
| 26 | // (`EmailAddressCreatedTooRecently`, code 2032), so create-and-destroy |
| 27 | // within one test run is impossible. The provider's reconcile adopts the |
| 28 | // standing address by email on every subsequent run, and `scripts/nuke.sh` |
| 29 | // excludes it from the leak census like the standing test zone. |
| 30 | const testEmail = "alchemy-list-test@alchemy-test-2.us"; |
| 31 | |
| 32 | // Canonical `list()` test (account-scoped collection): register a real |
| 33 | // destination address, resolve the provider from context via the typed |
| 34 | // `findProvider`, call `list()`, and assert the deployed address appears in |
| 35 | // the exhaustively-paginated result. |
| 36 | test.provider.skipIf(!emailRoutingScoped)( |
| 37 | "list enumerates the deployed email address", |
| 38 | (stack) => |
| 39 | Effect.gen(function* () { |
| 40 | yield* stack.destroy(); |
| 41 | |
| 42 | const address = yield* stack.deploy( |
| 43 | Effect.gen(function* () { |
| 44 | return yield* Cloudflare.Email.Address("ListAddress", { |
| 45 | email: testEmail, |
| 46 | }).pipe(RemovalPolicy.retain()); |
| 47 | }), |
| 48 | ); |
| 49 | |
| 50 | expect(address.email).toEqual(testEmail); |
| 51 | |
| 52 | const provider = yield* Provider.findProvider(Cloudflare.Email.Address); |
| 53 | |
| 54 | // A freshly-deployed address is eventually consistent in the account-wide |
| 55 | // list(); poll until it appears before asserting. |
| 56 | const all = yield* poll({ |
| 57 | description: "list() includes the deployed email address", |
| 58 | effect: provider.list(), |
| 59 | predicate: (all) => all.some((a) => a.email === testEmail), |
| 60 | schedule: Schedule.max([ |
| 61 | Schedule.spaced("3 seconds"), |
| 62 | Schedule.recurs(20), |
| 63 | ]), |
| 64 | }); |
| 65 | |
| 66 | expect(all.some((a) => a.addressId === address.addressId)).toBe(true); |
| 67 | expect(all.some((a) => a.email === testEmail)).toBe(true); |
| 68 | |
| 69 | yield* stack.destroy(); |
| 70 | }).pipe(logLevel), |
| 71 | { tags: ["provider:cloudflare", "provider:cloudflare:email", "live"] }, |
| 72 | ); |