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 ssl from "@distilled.cloud/cloudflare/ssl";
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
21const resolveZoneId = Effect.gen(function* () {
22 const { accountId } = yield* yield* CloudflareEnvironment;
23 const zone = yield* findZoneByName({ accountId, name: zoneName });
24 if (!zone) {
25 return yield* Effect.die(
26 new Error(`zone "${zoneName}" not found in account`),
27 );
28 }
29 return zone.id;
30});
31
32// The scoped API token the test harness mints propagates eventually-
33// consistently across Cloudflare's edge — a fresh token intermittently 403s
34// with "Unable to authenticate request". Ride out the blips on the test's
35// own out-of-band calls by retrying the typed `Forbidden` error (part of
36// each universal-setting operation's error union via distilled patches).
37//
38// The universal-settings PATCH endpoint is also rate-limited per zone
39// ("Rate limit reached for the update operation. Please try again in a
40// minute"), which surfaces as the typed `TooManyRequests` — back off and
41// retry that too. The exponential schedule reaches the ~1 minute window
42// within the bounded attempts.
43const transientRetrySchedule = Schedule.exponential("500 millis");
44
45const getUniversal = (zoneId: string) =>
46 ssl.getUniversalSetting({ zoneId }).pipe(
47 Effect.retry({
48 while: (e) => e._tag === "Forbidden" || e._tag === "TooManyRequests",
49 schedule: transientRetrySchedule,
50 times: 8,
51 }),
52 );
53
54// Normalize the setting to a known baseline so each run starts from the
55// same cloud state regardless of what a previous (possibly interrupted)
56// run left behind. Cloudflare's default for Universal SSL is enabled.
57const setBaseline = (zoneId: string, enabled: boolean) =>
58 ssl.patchUniversalSetting({ zoneId, enabled }).pipe(
59 Effect.retry({
60 while: (e) => e._tag === "Forbidden" || e._tag === "TooManyRequests",
61 schedule: transientRetrySchedule,
62 times: 8,
63 }),
64 );
65
66// Toggling Universal SSL off DELETES the standing zone's universal edge
67// certificate; Cloudflare re-issues it minutes later on re-enable — and
68// sometimes not at all (`enabled: true` with zero certificate packs), which
69// breaks every TLS-dependent test on the shared zone (worker routes, R2
70// domains, Hyperdrive origins) until someone re-orders the cert by toggling
71// the setting off→on. The toggle lifecycle is therefore opt-in; the
72// read-only `list` test below always runs.
73const destructive = !!process.env.CLOUDFLARE_TEST_UNIVERSAL_SSL;
74
75describe.sequential(
76 "UniversalSsl",
77 {
78 tags: [
79 "provider:cloudflare",
80 "provider:cloudflare:ssl",
81 "provider:cloudflare:zone",
82 "live",
83 ],
84 },
85 () => {
86 test.provider.skipIf(!destructive)(
87 "disables Universal SSL and restores the original value on destroy",
88 (stack) =>
89 Effect.gen(function* () {
90 const zoneId = yield* resolveZoneId;
91
92 yield* stack.destroy();
93 // Known baseline: Universal SSL defaults to enabled.
94 yield* setBaseline(zoneId, true);
95
96 const setting = yield* stack.deploy(
97 Effect.gen(function* () {
98 return yield* Cloudflare.Ssl.UniversalSsl("UniversalSsl", {
99 zoneId,
100 enabled: false,
101 });
102 }),
103 );
104
105 expect(setting.zoneId).toEqual(zoneId);
106 expect(setting.enabled).toEqual(false);
107 // The pre-management value was captured for restore-on-destroy.
108 expect(setting.initialEnabled).toEqual(true);
109
110 const live = yield* getUniversal(zoneId);
111 expect(live.enabled).toEqual(false);
112
113 yield* stack.destroy();
114
115 // Destroy restored the value the setting had before we managed it.
116 const restored = yield* getUniversal(zoneId);
117 expect(restored.enabled).toEqual(true);
118 }).pipe(logLevel),
119 { timeout: 180_000 },
120 );
121
122 test.provider.skipIf(!destructive)(
123 "updates enabled in place and keeps the captured initial value",
124 (stack) =>
125 Effect.gen(function* () {
126 const zoneId = yield* resolveZoneId;
127
128 yield* stack.destroy();
129 yield* setBaseline(zoneId, true);
130
131 const initial = yield* stack.deploy(
132 Effect.gen(function* () {
133 return yield* Cloudflare.Ssl.UniversalSsl("UniversalSsl", {
134 zoneId,
135 enabled: false,
136 });
137 }),
138 );
139
140 expect(initial.enabled).toEqual(false);
141 expect(initial.initialEnabled).toEqual(true);
142
143 const updated = yield* stack.deploy(
144 Effect.gen(function* () {
145 return yield* Cloudflare.Ssl.UniversalSsl("UniversalSsl", {
146 zoneId,
147 enabled: true,
148 });
149 }),
150 );
151
152 // Same singleton patched in place; the original value survives the
153 // update so destroy still restores the pre-management state.
154 expect(updated.enabled).toEqual(true);
155 expect(updated.initialEnabled).toEqual(true);
156
157 const live = yield* getUniversal(zoneId);
158 expect(live.enabled).toEqual(true);
159
160 yield* stack.destroy();
161
162 const restored = yield* getUniversal(zoneId);
163 expect(restored.enabled).toEqual(true);
164 }).pipe(logLevel),
165 { timeout: 180_000 },
166 );
167
168 test.provider.skipIf(!destructive)(
169 "destroy restores a disabled baseline when managing from a disabled zone",
170 (stack) =>
171 Effect.gen(function* () {
172 const zoneId = yield* resolveZoneId;
173
174 yield* stack.destroy();
175 // Pre-management state is disabled — the capture-and-restore must
176 // bring the zone back to disabled, not to Cloudflare's default.
177 yield* setBaseline(zoneId, false);
178
179 const setting = yield* stack.deploy(
180 Effect.gen(function* () {
181 return yield* Cloudflare.Ssl.UniversalSsl("UniversalSsl", {
182 zoneId,
183 enabled: true,
184 });
185 }),
186 );
187
188 expect(setting.enabled).toEqual(true);
189 expect(setting.initialEnabled).toEqual(false);
190
191 const live = yield* getUniversal(zoneId);
192 expect(live.enabled).toEqual(true);
193
194 yield* stack.destroy();
195
196 const restored = yield* getUniversal(zoneId);
197 expect(restored.enabled).toEqual(false);
198
199 // Leave the zone in its default state for other suites.
200 yield* setBaseline(zoneId, true);
201 }).pipe(logLevel),
202 { timeout: 180_000 },
203 );
204
205 // Canonical `list()` test (zone-scoped singleton): there is no account-wide
206 // API for this per-zone setting, so `list()` enumerates every zone via
207 // `listAllZones` and reads the singleton in each. Assert the result is
208 // non-empty and contains the standing test zone.
209 test.provider("list enumerates the setting across all zones", (stack) =>
210 Effect.gen(function* () {
211 const zoneId = yield* resolveZoneId;
212
213 const provider = yield* Provider.findProvider(
214 Cloudflare.Ssl.UniversalSsl,
215 );
216 const all = yield* provider.list();
217
218 expect(all.length).toBeGreaterThan(0);
219 expect(all.some((s) => s.zoneId === zoneId)).toBe(true);
220
221 // `stack` is unused here (the singleton always exists on every zone),
222 // but keep the destroy bookend so the harness state stays clean.
223 yield* stack.destroy();
224 }).pipe(logLevel),
225 );
226 },
227);