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 pageShield from "@distilled.cloud/cloudflare/page-shield";
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// Fresh scoped tokens propagate eventually-consistently across Cloudflare's
34// edge — retry the typed `Forbidden` error on out-of-band calls.
35const forbiddenRetrySchedule = Schedule.exponential("500 millis");
36
37const getSettings = (zoneId: string) =>
38 pageShield.getPageShield({ zoneId }).pipe(
39 Effect.retry({
40 while: (e) => e._tag === "Forbidden",
41 schedule: forbiddenRetrySchedule,
42 times: 8,
43 }),
44 );
45
46// Known baseline for the singleton: Page Shield off, API defaults for the
47// reporting flags. Tests start from here so `initial*` capture is
48// deterministic, and destroy must put the zone back here.
49const setBaseline = (zoneId: string) =>
50 pageShield
51 .putPageShield({
52 zoneId,
53 enabled: false,
54 useCloudflareReportingEndpoint: true,
55 useConnectionUrlPath: false,
56 })
57 .pipe(
58 Effect.retry({
59 while: (e) => e._tag === "Forbidden",
60 schedule: forbiddenRetrySchedule,
61 times: 8,
62 }),
63 );
64
65test.provider(
66 "enables Page Shield, updates in place, and restores the baseline on destroy",
67 (stack) =>
68 Effect.gen(function* () {
69 const zoneId = yield* resolveZoneId;
70
71 yield* stack.destroy();
72 yield* setBaseline(zoneId);
73
74 // Create — enable Page Shield with defaults.
75 const settings = yield* stack.deploy(
76 Effect.gen(function* () {
77 return yield* Cloudflare.PageShield.Settings("PageShield", {
78 zoneId,
79 });
80 }),
81 );
82
83 expect(settings.zoneId).toEqual(zoneId);
84 expect(settings.enabled).toEqual(true);
85 expect(settings.useCloudflareReportingEndpoint).toEqual(true);
86 expect(settings.useConnectionUrlPath).toEqual(false);
87 // The pre-management state was captured for restore-on-destroy.
88 expect(settings.initialEnabled).toEqual(false);
89 expect(settings.initialUseCloudflareReportingEndpoint).toEqual(true);
90 expect(settings.initialUseConnectionUrlPath).toEqual(false);
91
92 // Out-of-band verification via the distilled API.
93 const live = yield* getSettings(zoneId);
94 expect(live.enabled).toEqual(true);
95
96 // Update in place — flip `enabled` (the only flag writable without
97 // extra entitlements on the test zone); same singleton, initial*
98 // survive.
99 const updated = yield* stack.deploy(
100 Effect.gen(function* () {
101 return yield* Cloudflare.PageShield.Settings("PageShield", {
102 zoneId,
103 enabled: false,
104 });
105 }),
106 );
107 expect(updated.enabled).toEqual(false);
108 expect(updated.initialEnabled).toEqual(false);
109
110 const liveDisabled = yield* getSettings(zoneId);
111 expect(liveDisabled.enabled).toEqual(false);
112
113 // And back on — the last managed state differs from the baseline,
114 // so the restore below is observable.
115 const reenabled = yield* stack.deploy(
116 Effect.gen(function* () {
117 return yield* Cloudflare.PageShield.Settings("PageShield", {
118 zoneId,
119 enabled: true,
120 });
121 }),
122 );
123 expect(reenabled.enabled).toEqual(true);
124 expect(reenabled.initialEnabled).toEqual(false);
125
126 const liveReenabled = yield* getSettings(zoneId);
127 expect(liveReenabled.enabled).toEqual(true);
128
129 yield* stack.destroy();
130
131 // Destroy restored the configuration the zone had before us.
132 const restored = yield* getSettings(zoneId);
133 expect(restored.enabled).toEqual(false);
134 expect(restored.useCloudflareReportingEndpoint).toEqual(true);
135 expect(restored.useConnectionUrlPath).toEqual(false);
136 }).pipe(logLevel),
137 {
138 tags: [
139 "provider:cloudflare",
140 "provider:cloudflare:pageshield",
141 "provider:cloudflare:zone",
142 "live",
143 ],
144 timeout: 120_000,
145 },
146);
147
148test.provider(
149 "surfaces the typed NotEntitled error for plan-gated flags",
150 (stack) =>
151 Effect.gen(function* () {
152 const zoneId = yield* resolveZoneId;
153
154 yield* stack.destroy();
155
156 // The test zone lacks the connection-monitor entitlement — the
157 // distilled call must fail with the typed entitlement tag.
158 const error = yield* pageShield
159 .putPageShield({
160 zoneId,
161 enabled: true,
162 useCloudflareReportingEndpoint: true,
163 useConnectionUrlPath: true,
164 })
165 .pipe(
166 Effect.retry({
167 while: (e) => e._tag === "Forbidden",
168 schedule: forbiddenRetrySchedule,
169 times: 8,
170 }),
171 Effect.flip,
172 );
173 expect(error._tag).toEqual("NotEntitled");
174
175 yield* stack.destroy();
176 }).pipe(logLevel),
177 {
178 tags: [
179 "provider:cloudflare",
180 "provider:cloudflare:pageshield",
181 "provider:cloudflare:zone",
182 "live",
183 ],
184 timeout: 120_000,
185 },
186);
187
188// Canonical `list()` test (zone-scoped singleton): there is no account-wide
189// API for this per-zone configuration, so `list()` enumerates every zone via
190// `listAllZones` and reads the singleton in each. Assert the result is
191// non-empty and contains the standing test zone.
192test.provider(
193 "list enumerates Page Shield settings across all zones",
194 (stack) =>
195 Effect.gen(function* () {
196 const zoneId = yield* resolveZoneId;
197
198 const provider = yield* Provider.findProvider(
199 Cloudflare.PageShield.Settings,
200 );
201 const all = yield* provider.list();
202
203 expect(all.length).toBeGreaterThan(0);
204 expect(all.some((s) => s.zoneId === zoneId)).toBe(true);
205
206 // `stack` is unused here (the singleton always exists on every zone),
207 // but keep the destroy bookend so the harness state stays clean.
208 yield* stack.destroy();
209 }).pipe(logLevel),
210 {
211 tags: [
212 "provider:cloudflare",
213 "provider:cloudflare:pageshield",
214 "provider:cloudflare:zone",
215 "live",
216 ],
217 timeout: 120_000,
218 },
219);