1import * as Cloudflare from "@/Cloudflare";
2import { CloudflareEnvironment } from "@/Cloudflare/CloudflareEnvironment";
3import { findZoneByName } from "@/Cloudflare/Zone/lookup";
4import * as Output from "@/Output";
5import * as Provider from "@/Provider";
6import * as Test from "@/Test/Alchemy";
7import { expect } from "alchemy-test";
8import * as Effect from "effect/Effect";
9import { MinimumLogLevel } from "effect/References";
10import { JWKS_KEY_1 } from "./fixtures/jwks.ts";
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
22// JWT validation (API Shield) is entitlement-gated. On the standard testing
23// account every token_validation call fails with the typed
24// `TokenValidationNotEntitled` error (Cloudflare code 10403), so the deploy
25// portion of the list test is gated behind an entitled zone id supplied via
26// env. The read-only list assertion below always runs: `list()` fans out over
27// every zone and skips unentitled/forbidden zones, returning a well-typed `[]`.
28const entitledZoneId = process.env.CLOUDFLARE_TEST_TOKEN_VALIDATION_ZONE_ID;
29
30const resolveZoneId = Effect.gen(function* () {
31 const { accountId } = yield* yield* CloudflareEnvironment;
32 const zone = yield* findZoneByName({ accountId, name: zoneName });
33 if (!zone) {
34 return yield* Effect.die(
35 new Error(`zone "${zoneName}" not found in account`),
36 );
37 }
38 return zone.id;
39});
40
41// Read-only: always safe. On an unentitled account every per-zone listRules
42// rejects with the typed `TokenValidationNotEntitled` (or `Forbidden`) tag,
43// which `list()` catches per zone -> the aggregate result is a well-typed `[]`.
44test.provider(
45 "list returns a typed array of token validation rules",
46 (stack) =>
47 Effect.gen(function* () {
48 yield* stack.destroy();
49
50 const provider = yield* Provider.findProvider(
51 Cloudflare.TokenValidation.Rule,
52 );
53 const all = yield* provider.list();
54
55 expect(Array.isArray(all)).toBe(true);
56 // Each element is the full `read` Attributes shape.
57 for (const rule of all) {
58 expect(typeof rule.ruleId).toBe("string");
59 expect(typeof rule.zoneId).toBe("string");
60 expect(typeof rule.action).toBe("string");
61 }
62
63 yield* stack.destroy();
64 }).pipe(logLevel),
65 {
66 tags: [
67 "provider:cloudflare",
68 "provider:cloudflare:tokenvalidation",
69 "live",
70 ],
71 timeout: 120_000,
72 },
73);
74
75// Full lifecycle: gated on an entitled zone. Deploy a rule, then assert it is
76// present in the exhaustively-paginated `list()` output.
77test.provider.skipIf(!entitledZoneId)(
78 "list enumerates the deployed token validation rule",
79 (stack) =>
80 Effect.gen(function* () {
81 const zoneId = entitledZoneId!;
82
83 yield* stack.destroy();
84
85 const deployed = yield* stack.deploy(
86 Effect.gen(function* () {
87 const config = yield* Cloudflare.TokenValidation.TokenConfiguration(
88 "JwtConfig",
89 {
90 zoneId,
91 description: "list-test",
92 tokenSources: ['http.request.headers["authorization"][0]'],
93 keys: [JWKS_KEY_1],
94 },
95 );
96 return yield* Cloudflare.TokenValidation.Rule("JwtRule", {
97 zoneId,
98 action: "log",
99 expression: Output.interpolate`is_jwt_valid("${config.configId}")`,
100 selector: { include: [{ host: [zoneName] }] },
101 });
102 }),
103 );
104
105 const provider = yield* Provider.findProvider(
106 Cloudflare.TokenValidation.Rule,
107 );
108 const all = yield* provider.list();
109
110 const found = all.find(
111 (r) => r.zoneId === zoneId && r.ruleId === deployed.ruleId,
112 );
113 expect(found).toBeDefined();
114 expect(found?.action).toEqual("log");
115
116 yield* stack.destroy();
117 }).pipe(logLevel),
118 {
119 tags: [
120 "provider:cloudflare",
121 "provider:cloudflare:tokenvalidation",
122 "live",
123 ],
124 timeout: 120_000,
125 },
126);