1import { adopt } from "@/AdoptPolicy";
2import * as Cloudflare from "@/Cloudflare";
3import { CloudflareEnvironment } from "@/Cloudflare/CloudflareEnvironment";
4import { findZoneByName } from "@/Cloudflare/Zone/lookup";
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";
10
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
21const resolveZone = Effect.gen(function* () {
22 const { accountId } = yield* yield* CloudflareEnvironment;
23 const zone = yield* findZoneByName({ accountId, name: zoneName });
24 if (!zone) {
25 return yield* Effect.die(
26 new Error(`zone "${zoneName}" not found in account`),
27 );
28 }
29 return zone;
30});
31
32// Cloudflare has restricted Web3 gateways for new customers; on accounts
33// without the entitlement, the per-zone hostname list answers `Forbidden`,
34// so the content-list deploy+presence test is gated behind a Web3-entitled
35// account flag. The read-only list assertion below runs unconditionally.
36const web3Entitled = !!process.env.CLOUDFLARE_TEST_WEB3;
37
38// Read-only: `list()` enumerates every zone via `listAllZones`, lists each
39// zone's universal-path Web3 hostnames, and reads each one's content list.
40// On a non-entitled account each per-zone hostname list answers `Forbidden`
41// and is skipped, so the result is a well-typed (possibly empty) array. This
42// always runs and proves the enumeration shape.
43test.provider(
44 "list returns a well-typed array of web3 content lists",
45 (stack) =>
46 Effect.gen(function* () {
47 const provider = yield* Provider.findProvider(
48 Cloudflare.Web3.HostnameContentList,
49 );
50 const all = yield* provider.list();
51
52 expect(Array.isArray(all)).toBe(true);
53 for (const item of all) {
54 expect(typeof item.zoneId).toBe("string");
55 expect(typeof item.hostnameId).toBe("string");
56 expect(item.action).toBe("block");
57 expect(Array.isArray(item.entries)).toBe(true);
58 }
59
60 // Keep the destroy bookend so the harness state stays clean.
61 yield* stack.destroy();
62 }).pipe(logLevel),
63 { tags: ["provider:cloudflare", "provider:cloudflare:web3", "live"] },
64);
65
66// Deploy a real universal-path Web3 hostname + content list and assert
67// `list()` surfaces it. Gated behind a Web3-entitled account
68// (`CLOUDFLARE_TEST_WEB3=1`); on the default (non-entitled) testing account
69// the hostname list rejects with the typed `Forbidden` error.
70test.provider.skipIf(!web3Entitled)(
71 "list surfaces a deployed web3 content list (entitled account)",
72 (stack) =>
73 Effect.gen(function* () {
74 const zone = yield* resolveZone;
75 const name = `alchemy-web3-cl-list.${zone.name}`;
76
77 yield* stack.destroy();
78
79 const deployed = yield* stack.deploy(
80 Effect.gen(function* () {
81 const gateway = yield* Cloudflare.Web3.Hostname("ListGateway", {
82 zoneId: zone.id,
83 name,
84 target: "ipfs_universal_path",
85 }).pipe(adopt(true));
86 const contentList = yield* Cloudflare.Web3.HostnameContentList(
87 "ListBlocklist",
88 {
89 zoneId: zone.id,
90 hostnameId: gateway.hostnameId,
91 entries: [
92 {
93 type: "cid",
94 content: "QmXoypizjW3WknFiJnKLwHCnL72vedxjQkDDP1mXWo6uco",
95 description: "blocked CID",
96 },
97 ],
98 },
99 );
100 return { gateway, contentList };
101 }),
102 );
103
104 const provider = yield* Provider.findProvider(
105 Cloudflare.Web3.HostnameContentList,
106 );
107 const all = yield* provider.list();
108
109 expect(
110 all.some((cl) => cl.hostnameId === deployed.contentList.hostnameId),
111 ).toBe(true);
112
113 yield* stack.destroy();
114 }).pipe(logLevel),
115 {
116 tags: [
117 "provider:cloudflare",
118 "provider:cloudflare:web3",
119 "provider:cloudflare:zone",
120 "live",
121 ],
122 timeout: 120_000,
123 },
124);