1import * as Cloudflare from "@/Cloudflare";
2import { CloudflareEnvironment } from "@/Cloudflare/CloudflareEnvironment";
3import { findZoneByName } from "@/Cloudflare/Zone/lookup";
4import * as Provider from "@/Provider";
5import * as Test from "@/Test/Alchemy";
6import * as acm from "@distilled.cloud/cloudflare/acm";
7import { describe, expect } from "alchemy-test";
8import * as Effect from "effect/Effect";
9import { MinimumLogLevel } from "effect/References";
10import * as Schedule from "effect/Schedule";
11const { test } = Test.make({ providers: Cloudflare.providers() });
12
13const logLevel = Effect.provideService(
14 MinimumLogLevel,
15 process.env.DEBUG ? "Debug" : "Info",
16);
17
18const zoneName =
19 process.env.CLOUDFLARE_TEST_DNS_ZONE_NAME ?? "alchemy-test-2.us";
20
21// Total TLS writes require the Advanced Certificate Manager add-on
22// (~$10/mo per zone). The standard testing zone does not have it — every
23// POST fails with the typed `AdvancedCertificateManagerRequired` (code
24// 1450) error, while reads succeed and report `enabled: false`. The full
25// enable/update/restore lifecycle is gated behind an entitled zone id
26// supplied via env.
27const entitledZoneId = process.env.CLOUDFLARE_TEST_ACM_ZONE_ID;
28
29const resolveZoneId = Effect.gen(function* () {
30 const { accountId } = yield* yield* CloudflareEnvironment;
31 const zone = yield* findZoneByName({ accountId, name: zoneName });
32 if (!zone) {
33 return yield* Effect.die(
34 new Error(`zone "${zoneName}" not found in account`),
35 );
36 }
37 return zone.id;
38});
39
40// Freshly-minted scoped API tokens propagate eventually-consistently across
41// Cloudflare's edge — ride out intermittent 403s via the typed `Forbidden`
42// tag in each acm operation's error union.
43const forbiddenRetrySchedule = Schedule.exponential("500 millis");
44
45const getTotalTls = (zoneId: string) =>
46 acm.getTotalTl({ zoneId }).pipe(
47 Effect.retry({
48 while: (e) => e._tag === "Forbidden",
49 schedule: forbiddenRetrySchedule,
50 times: 8,
51 }),
52 );
53
54// Both cases mutate the same zone-level Total TLS singleton; run them serially so they don't corrupt each other's captured `initialEnabled` under the global concurrent test config.
55describe.sequential(
56 "TotalTls",
57 { tags: ["provider:cloudflare", "provider:cloudflare:acm", "live"] },
58 () => {
59 test.provider(
60 "converges enabled:false as a no-op on a zone without the ACM entitlement",
61 (stack) =>
62 Effect.gen(function* () {
63 const zoneId = yield* resolveZoneId;
64
65 yield* stack.destroy();
66
67 // Total TLS defaults to disabled. Desiring `enabled: false` matches
68 // the observed state, so reconcile never POSTs — the deploy succeeds
69 // even though the zone lacks the ACM entitlement.
70 const setting = yield* stack.deploy(
71 Effect.gen(function* () {
72 return yield* Cloudflare.Acm.TotalTls("TotalTls", {
73 zoneId,
74 enabled: false,
75 });
76 }),
77 );
78
79 expect(setting.zoneId).toEqual(zoneId);
80 expect(setting.enabled).toEqual(false);
81 // The pre-management state was captured for restore-on-destroy.
82 expect(setting.initialEnabled).toEqual(false);
83
84 // Out-of-band verification via the distilled API.
85 const live = yield* getTotalTls(zoneId);
86 expect(live.enabled ?? false).toEqual(false);
87
88 // Destroy restores the (identical) baseline — also a no-op POST.
89 yield* stack.destroy();
90 const after = yield* getTotalTls(zoneId);
91 expect(after.enabled ?? false).toEqual(false);
92 }).pipe(logLevel),
93 { tags: ["provider:cloudflare:zone"] },
94 );
95
96 test.provider(
97 "surfaces the typed AdvancedCertificateManagerRequired error when enabling without the entitlement",
98 (stack) =>
99 Effect.gen(function* () {
100 const zoneId = yield* resolveZoneId;
101
102 yield* stack.destroy();
103
104 // Enabling Total TLS requires the ACM add-on; the distilled call
105 // must fail with the typed entitlement tag (code 1450).
106 const error = yield* acm
107 .updateTotalTl({ zoneId, enabled: true })
108 .pipe(
109 Effect.retry({
110 while: (e) => e._tag === "Forbidden",
111 schedule: forbiddenRetrySchedule,
112 times: 8,
113 }),
114 Effect.flip,
115 );
116 expect(error._tag).toEqual("AdvancedCertificateManagerRequired");
117
118 yield* stack.destroy();
119 }).pipe(logLevel),
120 { tags: ["provider:cloudflare:zone"] },
121 );
122
123 test.provider.skipIf(!entitledZoneId)(
124 "enables Total TLS, updates the CA in place, and restores the baseline on destroy",
125 (stack) =>
126 Effect.gen(function* () {
127 const zoneId = entitledZoneId!;
128
129 yield* stack.destroy();
130 // Known baseline: disabled.
131 const baseline = yield* getTotalTls(zoneId);
132
133 const setting = yield* stack.deploy(
134 Effect.gen(function* () {
135 return yield* Cloudflare.Acm.TotalTls("TotalTls", {
136 zoneId,
137 enabled: true,
138 });
139 }),
140 );
141
142 expect(setting.zoneId).toEqual(zoneId);
143 expect(setting.enabled).toEqual(true);
144 expect(setting.initialEnabled).toEqual(baseline.enabled ?? false);
145
146 // Out-of-band verification.
147 const live = yield* getTotalTls(zoneId);
148 expect(live.enabled).toEqual(true);
149
150 // Update in place — pin the issuing Certificate Authority. The
151 // singleton's identity (zoneId) is unchanged.
152 const updated = yield* stack.deploy(
153 Effect.gen(function* () {
154 return yield* Cloudflare.Acm.TotalTls("TotalTls", {
155 zoneId,
156 enabled: true,
157 certificateAuthority: "lets_encrypt",
158 });
159 }),
160 );
161 expect(updated.enabled).toEqual(true);
162 expect(updated.certificateAuthority).toEqual("lets_encrypt");
163 expect(updated.initialEnabled).toEqual(baseline.enabled ?? false);
164
165 // Destroy restores the pre-management state.
166 yield* stack.destroy();
167 const after = yield* getTotalTls(zoneId);
168 expect(after.enabled ?? false).toEqual(baseline.enabled ?? false);
169 }).pipe(logLevel),
170 { timeout: 120_000 },
171 );
172
173 // Canonical `list()` test (zone-scoped singleton): there is no account-wide
174 // API for this per-zone setting, so `list()` enumerates every zone via
175 // `listAllZones` and reads the singleton in each (reads succeed without the
176 // ACM entitlement). Assert the result is non-empty and contains the standing
177 // test zone.
178 test.provider(
179 "list enumerates the setting across all zones",
180 (stack) =>
181 Effect.gen(function* () {
182 const zoneId = yield* resolveZoneId;
183
184 const provider = yield* Provider.findProvider(
185 Cloudflare.Acm.TotalTls,
186 );
187 const all = yield* provider.list();
188
189 expect(all.length).toBeGreaterThan(0);
190 expect(all.some((s) => s.zoneId === zoneId)).toBe(true);
191
192 // `stack` is unused here (the singleton always exists on every zone),
193 // but keep the destroy bookend so the harness state stays clean.
194 yield* stack.destroy();
195 }).pipe(logLevel),
196 { tags: ["provider:cloudflare:zone"] },
197 );
198 },
199);