1import * as Cloudflare from "@/Cloudflare";
2import { CloudflareEnvironment } from "@/Cloudflare/CloudflareEnvironment";
3import * as Provider from "@/Provider";
4import * as Test from "@/Test/Alchemy";
5import * as addressing from "@distilled.cloud/cloudflare/addressing";
6import { expect } from "alchemy-test";
7import * as Effect from "effect/Effect";
8import * as Result from "effect/Result";
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
19// A freshly minted scoped token propagates eventually-consistently across
20// Cloudflare's edge — retry the typed `Forbidden` blips on out-of-band calls.
21const forbiddenRetrySchedule = Schedule.exponential("500 millis");
22
23const resolveAccountId = Effect.gen(function* () {
24 const { accountId } = yield* yield* CloudflareEnvironment;
25 return accountId;
26});
27
28const getMap = (accountId: string, addressMapId: string) =>
29 addressing.getAddressMap({ accountId, addressMapId }).pipe(
30 Effect.retry({
31 while: (e) => e._tag === "Forbidden",
32 schedule: forbiddenRetrySchedule,
33 times: 8,
34 }),
35 Effect.catchTag("AddressMapNotFound", () => Effect.succeed(undefined)),
36 );
37
38// Address Maps require the BYOIP add-on or Cloudflare-assigned static IPs
39// (Enterprise). On the standard testing account every mutating call fails
40// with the typed `FeatureNotEnabled` error
41// (`address_maps_not_enabled_on_account`). The test probes once: unentitled
42// accounts assert the typed tag; entitled accounts run the full lifecycle.
43test.provider(
44 "address map lifecycle (typed FeatureNotEnabled on unentitled accounts)",
45 (stack) =>
46 Effect.gen(function* () {
47 const accountId = yield* resolveAccountId;
48
49 yield* stack.destroy();
50
51 const probe = yield* addressing
52 .createAddressMap({
53 accountId,
54 description: "alchemy-addressmap-probe",
55 enabled: false,
56 })
57 .pipe(
58 Effect.retry({
59 while: (e) => e._tag === "Forbidden",
60 schedule: forbiddenRetrySchedule,
61 times: 8,
62 }),
63 Effect.result,
64 );
65
66 if (Result.isFailure(probe)) {
67 // Unentitled — the distilled call must fail with the typed
68 // entitlement tag, never an untyped catch-all.
69 expect(probe.failure._tag).toEqual("FeatureNotEnabled");
70 yield* stack.destroy();
71 return;
72 }
73
74 // Entitled — clean up the probe map and run the full lifecycle.
75 if (probe.success.id) {
76 yield* addressing
77 .deleteAddressMap({ accountId, addressMapId: probe.success.id })
78 .pipe(Effect.catchTag("AddressMapNotFound", () => Effect.void));
79 }
80
81 // Create a disabled map with a description.
82 const created = yield* stack.deploy(
83 Effect.gen(function* () {
84 return yield* Cloudflare.Addressing.AddressMap("Map", {
85 description: "alchemy-addressmap v1",
86 enabled: false,
87 });
88 }),
89 );
90 expect(created.addressMapId).toBeDefined();
91 expect(created.accountId).toEqual(accountId);
92 expect(created.description).toEqual("alchemy-addressmap v1");
93 expect(created.enabled).toEqual(false);
94
95 // Out-of-band verification via the distilled API.
96 const live = yield* getMap(accountId, created.addressMapId);
97 expect(live?.description).toEqual("alchemy-addressmap v1");
98
99 // Update the description in place — same physical map.
100 const updated = yield* stack.deploy(
101 Effect.gen(function* () {
102 return yield* Cloudflare.Addressing.AddressMap("Map", {
103 description: "alchemy-addressmap v2",
104 enabled: false,
105 });
106 }),
107 );
108 expect(updated.addressMapId).toEqual(created.addressMapId);
109 expect(updated.description).toEqual("alchemy-addressmap v2");
110
111 const patched = yield* getMap(accountId, updated.addressMapId);
112 expect(patched?.description).toEqual("alchemy-addressmap v2");
113
114 // Destroy and verify the map is gone (typed not-found read).
115 yield* stack.destroy();
116 const gone = yield* getMap(accountId, created.addressMapId);
117 expect(gone).toBeUndefined();
118 }).pipe(logLevel),
119 {
120 tags: ["provider:cloudflare", "provider:cloudflare:addressing", "live"],
121 timeout: 120_000,
122 },
123);
124
125// `list()` enumerates every Address Map on the account (paginated) and
126// hydrates each into the full `read` shape. Requires the same BYOIP /
127// static-IP entitlement as the lifecycle: unentitled accounts can't create
128// a map to observe, so we probe once and assert the typed `FeatureNotEnabled`
129// tag (clean skip); entitled accounts run the full deploy + list assertion.
130test.provider(
131 "list enumerates the deployed address map",
132 (stack) =>
133 Effect.gen(function* () {
134 const accountId = yield* resolveAccountId;
135
136 yield* stack.destroy();
137
138 const probe = yield* addressing
139 .createAddressMap({
140 accountId,
141 description: "alchemy-addressmap-list-probe",
142 enabled: false,
143 })
144 .pipe(
145 Effect.retry({
146 while: (e) => e._tag === "Forbidden",
147 schedule: forbiddenRetrySchedule,
148 times: 8,
149 }),
150 Effect.result,
151 );
152
153 if (Result.isFailure(probe)) {
154 // Unentitled — the distilled call must fail with the typed
155 // entitlement tag, never an untyped catch-all.
156 expect(probe.failure._tag).toEqual("FeatureNotEnabled");
157 yield* stack.destroy();
158 return;
159 }
160
161 // Entitled — clean up the probe map and run the full lifecycle.
162 if (probe.success.id) {
163 yield* addressing
164 .deleteAddressMap({ accountId, addressMapId: probe.success.id })
165 .pipe(Effect.catchTag("AddressMapNotFound", () => Effect.void));
166 }
167
168 const deployed = yield* stack.deploy(
169 Effect.gen(function* () {
170 return yield* Cloudflare.Addressing.AddressMap("ListMap", {
171 description: "alchemy-addressmap-list",
172 enabled: false,
173 });
174 }),
175 );
176 expect(deployed.addressMapId).toBeDefined();
177
178 const provider = yield* Provider.findProvider(
179 Cloudflare.Addressing.AddressMap,
180 );
181 const all = yield* provider.list();
182
183 // The deployed map appears in the exhaustively-paginated result, fully
184 // hydrated into the `read` shape (ips/memberships present).
185 const found = all.find((m) => m.addressMapId === deployed.addressMapId);
186 expect(found).toBeDefined();
187 expect(found?.accountId).toEqual(accountId);
188 expect(found?.description).toEqual("alchemy-addressmap-list");
189 expect(found?.ips).toBeDefined();
190 expect(found?.memberships).toBeDefined();
191
192 yield* stack.destroy();
193 }).pipe(logLevel),
194 {
195 tags: ["provider:cloudflare", "provider:cloudflare:addressing", "live"],
196 timeout: 120_000,
197 },
198);