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 cache from "@distilled.cloud/cloudflare/cache";
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// Cache Reserve is a usage-billed add-on. On the testing account's zone
22// every GET/PATCH fails with "Sorry, this zone setting is not available for
23// your plan type.", surfaced as the typed `SettingUnavailableForPlan` error.
24// The full lifecycle test below is gated behind an entitled zone id
25// supplied via env.
26const entitledZoneId = process.env.CLOUDFLARE_TEST_CACHE_RESERVE_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 getCacheReserve = (zoneId: string) =>
42 cache.getCacheReserve({ zoneId }).pipe(
43 Effect.retry({
44 while: (e) => e._tag === "Forbidden",
45 schedule: forbiddenRetrySchedule,
46 times: 8,
47 }),
48 );
49
50const setBaseline = (zoneId: string, value: "on" | "off") =>
51 cache.patchCacheReserve({ zoneId, value }).pipe(
52 Effect.retry({
53 while: (e) => e._tag === "Forbidden",
54 schedule: forbiddenRetrySchedule,
55 times: 8,
56 }),
57 );
58
59// Both cases mutate the same zone-level Cache Reserve singleton; run them serially so they don't corrupt each other's captured `initialValue` under the global concurrent test config.
60describe.sequential(
61 "Reserve",
62 { tags: ["provider:cloudflare", "provider:cloudflare:cache", "live"] },
63 () => {
64 test.provider(
65 "surfaces the typed SettingUnavailableForPlan error on unentitled zones",
66 (stack) =>
67 Effect.gen(function* () {
68 const zoneId = yield* resolveZoneId;
69
70 yield* stack.destroy();
71
72 // The standard testing zone lacks the Cache Reserve subscription —
73 // the distilled call must fail with the typed entitlement tag.
74 const error = yield* cache.getCacheReserve({ zoneId }).pipe(
75 Effect.retry({
76 while: (e) => e._tag === "Forbidden",
77 schedule: forbiddenRetrySchedule,
78 times: 8,
79 }),
80 Effect.flip,
81 );
82 expect(error._tag).toEqual("SettingUnavailableForPlan");
83
84 yield* stack.destroy();
85 }).pipe(logLevel),
86 { tags: ["provider:cloudflare:zone"] },
87 );
88
89 test.provider.skipIf(!entitledZoneId)(
90 "enables Cache Reserve and restores the original value on destroy",
91 (stack) =>
92 Effect.gen(function* () {
93 const zoneId = entitledZoneId!;
94
95 yield* stack.destroy();
96 // Known baseline: Cache Reserve defaults to "off".
97 yield* setBaseline(zoneId, "off");
98
99 const setting = yield* stack.deploy(
100 Effect.gen(function* () {
101 return yield* Cloudflare.Cache.Reserve("Reserve", {
102 zoneId,
103 });
104 }),
105 );
106
107 expect(setting.zoneId).toEqual(zoneId);
108 expect(setting.value).toEqual("on");
109 // The pre-management value was captured for restore-on-destroy.
110 expect(setting.initialValue).toEqual("off");
111
112 // Out-of-band verification via the distilled API.
113 const live = yield* getCacheReserve(zoneId);
114 expect(live.value).toEqual("on");
115
116 // Update in place — same singleton, initialValue survives.
117 const updated = yield* stack.deploy(
118 Effect.gen(function* () {
119 return yield* Cloudflare.Cache.Reserve("Reserve", {
120 zoneId,
121 enabled: false,
122 });
123 }),
124 );
125 expect(updated.value).toEqual("off");
126 expect(updated.initialValue).toEqual("off");
127
128 yield* stack.destroy();
129
130 // Destroy restored the value the setting had before we managed it.
131 const restored = yield* getCacheReserve(zoneId);
132 expect(restored.value).toEqual("off");
133 }).pipe(logLevel),
134 );
135
136 // Canonical `list()` test (zone-scoped singleton): there is no account-wide
137 // API for this per-zone setting, so `list()` enumerates every zone via
138 // `listAllZones` and reads the singleton in each. Cache Reserve is an
139 // entitlement-gated add-on, so unentitled zones are filtered out via the
140 // typed `SettingUnavailableForPlan` skip — on the standard testing account
141 // the result is therefore an array (possibly empty) and must not throw.
142 test.provider("list enumerates Cache Reserve across all zones", (stack) =>
143 Effect.gen(function* () {
144 yield* stack.destroy();
145
146 const provider = yield* Provider.findProvider(Cloudflare.Cache.Reserve);
147 const all = yield* provider.list();
148
149 // Enumeration + typed entitlement skip succeed: a plain array, every
150 // element carrying the full Attributes shape.
151 expect(Array.isArray(all)).toBe(true);
152 for (const entry of all) {
153 expect(typeof entry.zoneId).toBe("string");
154 expect(typeof entry.value).toBe("string");
155 }
156
157 yield* stack.destroy();
158 }).pipe(logLevel),
159 );
160
161 // On an entitled account, the entitled zone's singleton is returned by
162 // `list()`. Gated behind the same env var as the lifecycle test.
163 test.provider.skipIf(!entitledZoneId)(
164 "list includes the entitled zone's Cache Reserve setting",
165 (stack) =>
166 Effect.gen(function* () {
167 const zoneId = entitledZoneId!;
168
169 const provider = yield* Provider.findProvider(
170 Cloudflare.Cache.Reserve,
171 );
172 const all = yield* provider.list();
173
174 expect(all.some((s) => s.zoneId === zoneId)).toBe(true);
175
176 // `stack` is unused (the singleton always exists on entitled zones),
177 // but keep the destroy bookend so the harness state stays clean.
178 yield* stack.destroy();
179 }).pipe(logLevel),
180 );
181 },
182);