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 schemaValidation from "@distilled.cloud/cloudflare/schema-validation";
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// Retry 403 blips while the harness-minted scoped token propagates.
34const getOverrideOob = (zoneId: string, operationId: string) =>
35 schemaValidation.getSettingOperation({ zoneId, operationId }).pipe(
36 Effect.retry({
37 while: (e) => e._tag === "Forbidden",
38 schedule: Schedule.exponential("500 millis"),
39 times: 8,
40 }),
41 );
42
43test.provider(
44 "sets a per-operation override, updates it in place, and clears it on destroy",
45 (stack) =>
46 Effect.gen(function* () {
47 const zoneId = yield* resolveZoneId;
48
49 yield* stack.destroy();
50
51 const program = (
52 mitigationAction: Cloudflare.SchemaValidation.OperationMitigationAction,
53 ) =>
54 Effect.gen(function* () {
55 const op = yield* Cloudflare.ApiShield.Operation("TestOp", {
56 zoneId,
57 method: "GET",
58 host: zoneName,
59 endpoint: "/alchemy-sv-operation-setting-test",
60 });
61 const override = yield* Cloudflare.SchemaValidation.OperationSetting(
62 "TestOverride",
63 {
64 zoneId,
65 operationId: op.operationId,
66 mitigationAction,
67 },
68 );
69 return { op, override };
70 });
71
72 // "log" is plan-gated on this zone (UnentitledMitigationAction,
73 // code 11400) — exercise the entitled "block"/"none" actions.
74 const first = yield* stack.deploy(program("block"));
75 expect(first.override.zoneId).toEqual(zoneId);
76 expect(first.override.operationId).toEqual(first.op.operationId);
77 expect(first.override.mitigationAction).toEqual("block");
78
79 const live = yield* getOverrideOob(zoneId, first.op.operationId);
80 expect(live.mitigationAction).toEqual("block");
81
82 // The PUT is a true upsert — flipping the action updates in place.
83 const second = yield* stack.deploy(program("none"));
84 expect(second.override.operationId).toEqual(first.op.operationId);
85 expect(second.override.mitigationAction).toEqual("none");
86
87 const liveUpdated = yield* getOverrideOob(zoneId, first.op.operationId);
88 expect(liveUpdated.mitigationAction).toEqual("none");
89
90 const operationId = first.op.operationId;
91 yield* stack.destroy();
92
93 // Destroy cleared the override (the operation itself is destroyed
94 // too, which cascades — either way the override is gone).
95 const gone = yield* schemaValidation
96 .getSettingOperation({ zoneId, operationId })
97 .pipe(Effect.flip);
98 expect(gone._tag).toEqual("OperationNotFound");
99 }).pipe(logLevel),
100 {
101 tags: [
102 "provider:cloudflare",
103 "provider:cloudflare:apishield",
104 "provider:cloudflare:schemavalidation",
105 "provider:cloudflare:zone",
106 "live",
107 ],
108 timeout: 120_000,
109 },
110);
111
112// Canonical `list()` test (zone-scoped collection): there is no account-wide
113// API for per-operation overrides, so `list()` enumerates every zone via
114// `listAllZones` and exhaustively paginates each zone's operation settings.
115// Deploy one override and assert it appears in the result, keyed by its
116// (zoneId, operationId) identity.
117test.provider(
118 "list enumerates the deployed per-operation override",
119 (stack) =>
120 Effect.gen(function* () {
121 const zoneId = yield* resolveZoneId;
122
123 yield* stack.destroy();
124
125 const deployed = yield* stack.deploy(
126 Effect.gen(function* () {
127 const op = yield* Cloudflare.ApiShield.Operation("ListOp", {
128 zoneId,
129 method: "GET",
130 host: zoneName,
131 endpoint: "/alchemy-sv-operation-setting-list-test",
132 });
133 const override = yield* Cloudflare.SchemaValidation.OperationSetting(
134 "ListOverride",
135 {
136 zoneId,
137 operationId: op.operationId,
138 mitigationAction: "block",
139 },
140 );
141 return { op, override };
142 }),
143 );
144
145 const provider = yield* Provider.findProvider(
146 Cloudflare.SchemaValidation.OperationSetting,
147 );
148 const all = yield* provider.list();
149
150 expect(
151 all.some(
152 (o) =>
153 o.zoneId === zoneId &&
154 o.operationId === deployed.op.operationId &&
155 o.mitigationAction === "block",
156 ),
157 ).toBe(true);
158
159 yield* stack.destroy();
160 }).pipe(logLevel),
161 {
162 tags: [
163 "provider:cloudflare",
164 "provider:cloudflare:apishield",
165 "provider:cloudflare:schemavalidation",
166 "provider:cloudflare:zone",
167 "live",
168 ],
169 timeout: 120_000,
170 },
171);