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 zones from "@distilled.cloud/cloudflare/zones";
7import { expect } from "alchemy-test";
8import * as Effect from "effect/Effect";
9import { MinimumLogLevel } from "effect/References";
10import * as Schedule from "effect/Schedule";
11
12const { test } = Test.make({ providers: Cloudflare.providers() });
13
14const logLevel = Effect.provideService(
15 MinimumLogLevel,
16 process.env.DEBUG ? "Debug" : "Info",
17);
18
19const zoneName =
20 process.env.CLOUDFLARE_TEST_DNS_ZONE_NAME ?? "alchemy-test-2.us";
21
22const resolveZoneId = Effect.gen(function* () {
23 const { accountId } = yield* yield* CloudflareEnvironment;
24 const zone = yield* findZoneByName({ accountId, name: zoneName });
25 if (!zone) {
26 return yield* Effect.die(
27 new Error(`zone "${zoneName}" not found in account`),
28 );
29 }
30 return zone.id;
31});
32
33// The scoped API token the test harness mints propagates eventually-
34// consistently across Cloudflare's edge — a fresh token intermittently 403s
35// with "Unable to authenticate request". Ride out the blips on the test's
36// own out-of-band calls by retrying the typed `Forbidden` error (part of
37// each setting operation's error union via distilled patches).
38const forbiddenRetrySchedule = Schedule.exponential("500 millis");
39
40const getSetting = (zoneId: string, settingId: string) =>
41 zones.getSetting({ zoneId, settingId }).pipe(
42 Effect.retry({
43 while: (e) => e._tag === "Forbidden",
44 schedule: forbiddenRetrySchedule,
45 times: 8,
46 }),
47 );
48
49// Normalize a setting to a known baseline so each run starts from the same
50// cloud state regardless of what a previous (possibly interrupted) run left
51// behind.
52const setBaseline = (zoneId: string, settingId: string, value: unknown) =>
53 zones.patchSetting({ zoneId, settingId, value }).pipe(
54 Effect.retry({
55 while: (e) => e._tag === "Forbidden",
56 schedule: forbiddenRetrySchedule,
57 times: 8,
58 }),
59 );
60
61const valueOf = (setting: zones.GetSettingResponse): unknown =>
62 (setting as { value?: unknown }).value;
63
64test.provider(
65 "pins a toggle setting and restores the original value on destroy",
66 (stack) =>
67 Effect.gen(function* () {
68 const zoneId = yield* resolveZoneId;
69
70 yield* stack.destroy();
71 // Known baseline: always_online defaults to "on".
72 yield* setBaseline(zoneId, "always_online", "on");
73
74 const setting = yield* stack.deploy(
75 Effect.gen(function* () {
76 return yield* Cloudflare.Zone.Setting("AlwaysOnline", {
77 zoneId,
78 settingId: "always_online",
79 value: "off",
80 });
81 }),
82 );
83
84 expect(setting.zoneId).toEqual(zoneId);
85 expect(setting.settingId).toEqual("always_online");
86 expect(setting.value).toEqual("off");
87 // The pre-management value was captured for restore-on-destroy.
88 expect(setting.initialValue).toEqual("on");
89
90 const live = yield* getSetting(zoneId, "always_online");
91 expect(valueOf(live)).toEqual("off");
92
93 yield* stack.destroy();
94
95 // Destroy restored the value the setting had before we managed it.
96 const restored = yield* getSetting(zoneId, "always_online");
97 expect(valueOf(restored)).toEqual("on");
98 }).pipe(logLevel),
99 { tags: ["provider:cloudflare", "provider:cloudflare:zone", "live"] },
100);
101
102test.provider(
103 "updates a numeric setting in place and keeps the captured initial value",
104 (stack) =>
105 Effect.gen(function* () {
106 const zoneId = yield* resolveZoneId;
107
108 yield* stack.destroy();
109 // Known baseline: browser_cache_ttl defaults to 14400 (4 hours).
110 yield* setBaseline(zoneId, "browser_cache_ttl", 14400);
111
112 const initial = yield* stack.deploy(
113 Effect.gen(function* () {
114 return yield* Cloudflare.Zone.Setting("BrowserCacheTtl", {
115 zoneId,
116 settingId: "browser_cache_ttl",
117 value: 1800,
118 });
119 }),
120 );
121
122 expect(initial.value).toEqual(1800);
123 expect(initial.initialValue).toEqual(14400);
124
125 const updated = yield* stack.deploy(
126 Effect.gen(function* () {
127 return yield* Cloudflare.Zone.Setting("BrowserCacheTtl", {
128 zoneId,
129 settingId: "browser_cache_ttl",
130 value: 3600,
131 });
132 }),
133 );
134
135 // Same singleton patched in place; the original value survives the
136 // update so destroy still restores the pre-management state.
137 expect(updated.value).toEqual(3600);
138 expect(updated.initialValue).toEqual(14400);
139
140 const live = yield* getSetting(zoneId, "browser_cache_ttl");
141 expect(valueOf(live)).toEqual(3600);
142
143 yield* stack.destroy();
144
145 const restored = yield* getSetting(zoneId, "browser_cache_ttl");
146 expect(valueOf(restored)).toEqual(14400);
147 }).pipe(logLevel),
148 { tags: ["provider:cloudflare", "provider:cloudflare:zone", "live"] },
149);
150
151test.provider(
152 "changing settingId replaces — old setting restored, new setting pinned",
153 (stack) =>
154 Effect.gen(function* () {
155 const zoneId = yield* resolveZoneId;
156
157 yield* stack.destroy();
158 // Known baselines: browser_check and email_obfuscation default to "on".
159 yield* setBaseline(zoneId, "browser_check", "on");
160 yield* setBaseline(zoneId, "email_obfuscation", "on");
161
162 const first = yield* stack.deploy(
163 Effect.gen(function* () {
164 return yield* Cloudflare.Zone.Setting("ReplaceSetting", {
165 zoneId,
166 settingId: "browser_check",
167 value: "off",
168 });
169 }),
170 );
171
172 expect(first.settingId).toEqual("browser_check");
173 const firstLive = yield* getSetting(zoneId, "browser_check");
174 expect(valueOf(firstLive)).toEqual("off");
175
176 // settingId is the resource's identity — switching it is a
177 // replacement: the new setting gets pinned and the old setting is
178 // restored to its pre-management value as the old instance deletes.
179 const replaced = yield* stack.deploy(
180 Effect.gen(function* () {
181 return yield* Cloudflare.Zone.Setting("ReplaceSetting", {
182 zoneId,
183 settingId: "email_obfuscation",
184 value: "off",
185 });
186 }),
187 );
188
189 expect(replaced.settingId).toEqual("email_obfuscation");
190 expect(replaced.initialValue).toEqual("on");
191
192 const newLive = yield* getSetting(zoneId, "email_obfuscation");
193 expect(valueOf(newLive)).toEqual("off");
194
195 const oldRestored = yield* getSetting(zoneId, "browser_check");
196 expect(valueOf(oldRestored)).toEqual("on");
197
198 yield* stack.destroy();
199
200 const finalRestored = yield* getSetting(zoneId, "email_obfuscation");
201 expect(valueOf(finalRestored)).toEqual("on");
202 }).pipe(logLevel),
203 { tags: ["provider:cloudflare", "provider:cloudflare:zone", "live"] },
204);
205
206// Canonical `list()` test. Zone settings are keyed by (zoneId, settingId):
207// `list()` enumerates every zone via `listAllZones` and reads each known
208// setting, emitting one Attributes per (zone, setting) — the same shape
209// `read` produces. Deploy a deterministic setting on the standing test zone
210// and assert that exact (zone, setting) entry shows up in the listing.
211test.provider(
212 "list enumerates the deployed (zone, setting) pair",
213 (stack) =>
214 Effect.gen(function* () {
215 const zoneId = yield* resolveZoneId;
216
217 yield* stack.destroy();
218 // Known baseline so the value we assert against is deterministic.
219 yield* setBaseline(zoneId, "always_use_https", "off");
220
221 const deployed = yield* stack.deploy(
222 Effect.gen(function* () {
223 return yield* Cloudflare.Zone.Setting("AlwaysUseHttps", {
224 zoneId,
225 settingId: "always_use_https",
226 value: "on",
227 });
228 }),
229 );
230 expect(deployed.settingId).toEqual("always_use_https");
231 expect(deployed.value).toEqual("on");
232
233 const provider = yield* Provider.findProvider(Cloudflare.Zone.Setting);
234 const all = yield* provider.list();
235
236 // The deployed (zone, setting) pair is present, hydrated into the exact
237 // `read`/`Attributes` shape (one row per (zoneId, settingId)).
238 expect(all.length).toBeGreaterThan(0);
239 const entry = all.find(
240 (s) => s.zoneId === zoneId && s.settingId === "always_use_https",
241 );
242 expect(entry).toBeDefined();
243 expect(entry?.value).toEqual("on");
244
245 yield* stack.destroy();
246
247 // Capture-and-restore: destroy put the setting back to its baseline.
248 const restored = yield* getSetting(zoneId, "always_use_https");
249 expect(valueOf(restored)).toEqual("off");
250 }).pipe(logLevel),
251 { tags: ["provider:cloudflare", "provider:cloudflare:zone", "live"] },
252);