1import * as Cloudflare from "@/Cloudflare";
2import { CloudflareEnvironment } from "@/Cloudflare/CloudflareEnvironment";
3import * as Provider from "@/Provider";
4import * as Test from "@/Test/Alchemy";
5import * as emailSecurity from "@distilled.cloud/cloudflare/email-security";
6import { expect } from "alchemy-test";
7import * as Effect from "effect/Effect";
8import { MinimumLogLevel } from "effect/References";
9import * as Schedule from "effect/Schedule";
10import * as Stream from "effect/Stream";
11
12const { test } = Test.make({ providers: Cloudflare.providers() });
13
14const logLevel = Effect.provideService(
15 MinimumLogLevel,
16 process.env.DEBUG ? "Debug" : "Info",
17);
18
19// Email Security (Area 1) is an enterprise add-on — the standard testing
20// account is not entitled (typed `EmailSecurityNotEntitled`), so the
21// lifecycle test is gated behind an entitled account flagged via env.
22const entitled = !!process.env.CLOUDFLARE_EMAIL_SECURITY;
23
24const forbiddenRetrySchedule = Schedule.exponential("500 millis");
25
26const pattern = "alchemy-block-sender@alchemy-test-2.us";
27
28const findByPattern = (accountId: string) =>
29 emailSecurity.listSettingBlockSenders.items({ accountId, pattern }).pipe(
30 Stream.runCollect,
31 Effect.map((chunk) => Array.from(chunk).find((p) => p.pattern === pattern)),
32 Effect.retry({
33 while: (e) => e._tag === "Forbidden",
34 schedule: forbiddenRetrySchedule,
35 times: 8,
36 }),
37 );
38
39test.provider.skipIf(!entitled)(
40 "create, update in place, destroy",
41 (stack) =>
42 Effect.gen(function* () {
43 const { accountId } = yield* yield* CloudflareEnvironment;
44
45 yield* stack.destroy();
46
47 // Block a sender address.
48 const created = yield* stack.deploy(
49 Effect.gen(function* () {
50 return yield* Cloudflare.Email.BlockSender("Blocked", {
51 pattern,
52 patternType: "EMAIL",
53 comments: "v1",
54 });
55 }),
56 );
57 expect(created.blockSenderId).toBeDefined();
58 expect(created.accountId).toEqual(accountId);
59 expect(created.pattern).toEqual(pattern);
60 expect(created.patternType).toEqual("EMAIL");
61 expect(created.isRegex).toEqual(false);
62 expect(created.comments).toEqual("v1");
63
64 // Out-of-band verification via the distilled API.
65 const live = yield* findByPattern(accountId);
66 expect(live?.id).toEqual(created.blockSenderId);
67
68 // Update mutable fields in place — same physical entry.
69 const updated = yield* stack.deploy(
70 Effect.gen(function* () {
71 return yield* Cloudflare.Email.BlockSender("Blocked", {
72 pattern,
73 patternType: "EMAIL",
74 comments: "v2",
75 });
76 }),
77 );
78 expect(updated.blockSenderId).toEqual(created.blockSenderId);
79 expect(updated.comments).toEqual("v2");
80
81 yield* stack.destroy();
82
83 // The entry is gone after destroy.
84 const gone = yield* findByPattern(accountId).pipe(
85 Effect.repeat({
86 schedule: Schedule.spaced("2 seconds"),
87 until: (p) => p === undefined,
88 times: 10,
89 }),
90 );
91 expect(gone).toBeUndefined();
92 }).pipe(logLevel),
93 {
94 tags: ["provider:cloudflare", "provider:cloudflare:email", "live"],
95 timeout: 120_000,
96 },
97);
98
99// list() enumerates the account's blocked senders. On the entitled account it
100// asserts the freshly-deployed entry is present; on the standard (un-entitled)
101// account the typed `EmailSecurityNotEntitled` is caught and list() yields the
102// well-typed empty array.
103test.provider(
104 "list enumerates the deployed block sender",
105 (stack) =>
106 Effect.gen(function* () {
107 yield* stack.destroy();
108
109 const provider = yield* Provider.findProvider(
110 Cloudflare.Email.BlockSender,
111 );
112
113 if (entitled) {
114 const deployed = yield* stack.deploy(
115 Effect.gen(function* () {
116 return yield* Cloudflare.Email.BlockSender("ListBlocked", {
117 pattern,
118 patternType: "EMAIL",
119 });
120 }),
121 );
122
123 const all = yield* provider.list();
124 expect(
125 all.some((x) => x.blockSenderId === deployed.blockSenderId),
126 ).toBe(true);
127 } else {
128 const all = yield* provider.list();
129 expect(all).toEqual([]);
130 }
131
132 yield* stack.destroy();
133 }).pipe(logLevel),
134 {
135 tags: ["provider:cloudflare", "provider:cloudflare:email", "live"],
136 timeout: 120_000,
137 },
138);