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 waitingRooms from "@distilled.cloud/cloudflare/waiting-rooms";
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// Enabling the crawler bypass requires a Waiting Rooms entitlement
22// (Business/Enterprise + Advanced) — on the testing zone the PUT fails with
23// the typed `ZoneNotEntitled` error (code 1034). GETs work on every plan,
24// and a no-op reconcile never calls the PUT, so the baseline test below runs
25// everywhere. The full toggle test is gated behind an entitled zone id.
26const entitledZoneId = process.env.CLOUDFLARE_TEST_WAITING_ROOM_ZONE_ID;
27
28const resolveZoneId = Effect.gen(function* () {
29 const { accountId } = yield* yield* CloudflareEnvironment;
30 const zone = yield* findZoneByName({ accountId, name: zoneName });
31 if (!zone) {
32 return yield* Effect.die(
33 new Error(`zone "${zoneName}" not found in account`),
34 );
35 }
36 return zone.id;
37});
38
39const forbiddenRetrySchedule = Schedule.exponential("500 millis");
40
41const getSetting = (zoneId: string) =>
42 waitingRooms.getSetting({ zoneId }).pipe(
43 Effect.retry({
44 while: (e) => e._tag === "Forbidden",
45 schedule: forbiddenRetrySchedule,
46 times: 8,
47 }),
48 );
49
50// Both cases mutate the same zone-level Waiting Room settings singleton; run them serially so they don't corrupt each other's captured baseline under the global concurrent test config.
51describe(
52 "Settings",
53 { tags: ["provider:cloudflare", "provider:cloudflare:waitingroom", "live"] },
54 () => {
55 test.provider(
56 "pins the settings to the default baseline without touching the API",
57 (stack) =>
58 Effect.gen(function* () {
59 const zoneId = yield* resolveZoneId;
60
61 yield* stack.destroy();
62
63 // The zone's baseline is the Cloudflare default (false). Desired ==
64 // observed, so reconcile skips the plan-gated PUT entirely — this
65 // converges even on unentitled zones.
66 const settings = yield* stack.deploy(
67 Effect.gen(function* () {
68 return yield* Cloudflare.WaitingRoom.Settings("Settings", {
69 zoneId,
70 searchEngineCrawlerBypass: false,
71 });
72 }),
73 );
74
75 expect(settings.zoneId).toEqual(zoneId);
76 expect(settings.searchEngineCrawlerBypass).toEqual(false);
77 // The pre-management value was captured for restore-on-destroy.
78 expect(settings.initialSearchEngineCrawlerBypass).toEqual(false);
79
80 // Out-of-band verification via the distilled API.
81 const live = yield* getSetting(zoneId);
82 expect(live.searchEngineCrawlerBypass).toEqual(false);
83
84 // Destroy restores the initial value — also a no-op here.
85 yield* stack.destroy();
86
87 const restored = yield* getSetting(zoneId);
88 expect(restored.searchEngineCrawlerBypass).toEqual(false);
89 }).pipe(logLevel),
90 { tags: ["provider:cloudflare:zone"] },
91 );
92
93 test.provider(
94 "surfaces the typed ZoneNotEntitled error when enabling on unentitled zones",
95 (stack) =>
96 Effect.gen(function* () {
97 const zoneId = yield* resolveZoneId;
98
99 yield* stack.destroy();
100
101 // The plan-gated PUT must fail with the typed entitlement tag.
102 const error = yield* waitingRooms
103 .putSetting({ zoneId, searchEngineCrawlerBypass: true })
104 .pipe(
105 Effect.retry({
106 while: (e) => e._tag === "Forbidden",
107 schedule: forbiddenRetrySchedule,
108 times: 8,
109 }),
110 Effect.flip,
111 );
112 expect(error._tag).toEqual("ZoneNotEntitled");
113
114 yield* stack.destroy();
115 }).pipe(logLevel),
116 { tags: ["provider:cloudflare:zone"] },
117 );
118
119 test.provider.skipIf(!entitledZoneId)(
120 "enables the crawler bypass and restores the original value on destroy",
121 (stack) =>
122 Effect.gen(function* () {
123 const zoneId = entitledZoneId!;
124
125 yield* stack.destroy();
126 // Known baseline: the bypass defaults to false.
127 yield* waitingRooms.putSetting({
128 zoneId,
129 searchEngineCrawlerBypass: false,
130 });
131
132 const settings = yield* stack.deploy(
133 Effect.gen(function* () {
134 return yield* Cloudflare.WaitingRoom.Settings("Settings", {
135 zoneId,
136 searchEngineCrawlerBypass: true,
137 });
138 }),
139 );
140
141 expect(settings.searchEngineCrawlerBypass).toEqual(true);
142 expect(settings.initialSearchEngineCrawlerBypass).toEqual(false);
143
144 const live = yield* getSetting(zoneId);
145 expect(live.searchEngineCrawlerBypass).toEqual(true);
146
147 yield* stack.destroy();
148
149 // Destroy restored the value the zone had before we managed it.
150 const restored = yield* getSetting(zoneId);
151 expect(restored.searchEngineCrawlerBypass).toEqual(false);
152 }).pipe(logLevel),
153 { timeout: 120_000 },
154 );
155
156 // Canonical `list()` test (zone-scoped singleton): there is no account-wide
157 // API for these per-zone settings, so `list()` enumerates every zone via
158 // `listAllZones` and reads the singleton in each. Assert the result is
159 // non-empty and contains the standing test zone.
160 test.provider(
161 "list enumerates the settings across all zones",
162 (stack) =>
163 Effect.gen(function* () {
164 const zoneId = yield* resolveZoneId;
165
166 const provider = yield* Provider.findProvider(
167 Cloudflare.WaitingRoom.Settings,
168 );
169 const all = yield* provider.list();
170
171 expect(all.length).toBeGreaterThan(0);
172 expect(all.some((s) => s.zoneId === zoneId)).toBe(true);
173
174 // `stack` is unused here (the singleton always exists on every zone),
175 // but keep the destroy bookend so the harness state stays clean.
176 yield* stack.destroy();
177 }).pipe(logLevel),
178 { tags: ["provider:cloudflare:zone"] },
179 );
180 },
181);