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 keylessCertificates from "@distilled.cloud/cloudflare/keyless-certificates";
7import { expect } from "alchemy-test";
8import * as Effect from "effect/Effect";
9import { MinimumLogLevel } from "effect/References";
10import * as Schedule from "effect/Schedule";
11import * as Stream from "effect/Stream";
12import { CERT_1, CERT_2 } from "./fixtures/certs.ts";
13
14const { test } = Test.make({ providers: Cloudflare.providers() });
15
16const logLevel = Effect.provideService(
17 MinimumLogLevel,
18 process.env.DEBUG ? "Debug" : "Info",
19);
20
21const zoneName =
22 process.env.CLOUDFLARE_TEST_DNS_ZONE_NAME ?? "alchemy-test-2.us";
23
24// Keyless SSL is Enterprise-only. On the testing account's zone every
25// createKeylessCertificate fails with Cloudflare code 1067 ("Keyless SSL is
26// not available for this zone. Please contact support."), surfaced as the
27// typed `KeylessSslNotAvailable` error. The full lifecycle test below is
28// gated behind an Enterprise zone id supplied via env.
29const enterpriseZoneId = process.env.CLOUDFLARE_TEST_ENTERPRISE_ZONE_ID;
30
31const resolveZoneId = Effect.gen(function* () {
32 const { accountId } = yield* yield* CloudflareEnvironment;
33 const zone = yield* findZoneByName({ accountId, name: zoneName });
34 if (!zone) {
35 return yield* Effect.die(
36 new Error(`zone "${zoneName}" not found in account`),
37 );
38 }
39 return zone.id;
40});
41
42// The scoped API token the test harness mints propagates eventually-
43// consistently across Cloudflare's edge — ride out 403 blips on the test's
44// own out-of-band calls by retrying the typed `Forbidden` error.
45const forbiddenRetrySchedule = Schedule.exponential("500 millis");
46
47const getKeyless = (zoneId: string, keylessCertificateId: string) =>
48 keylessCertificates
49 .getKeylessCertificate({ zoneId, keylessCertificateId })
50 .pipe(
51 Effect.retry({
52 while: (e) => e._tag === "Forbidden",
53 schedule: forbiddenRetrySchedule,
54 times: 8,
55 }),
56 );
57
58// A destroyed Keyless SSL configuration either disappears (typed
59// `KeylessCertificateNotFound`, Cloudflare code 1005) or briefly lingers as
60// a "deleted" tombstone — both count as gone.
61const expectGone = (zoneId: string, keylessCertificateId: string) =>
62 getKeyless(zoneId, keylessCertificateId).pipe(
63 Effect.flatMap((observed) =>
64 observed.status === "deleted"
65 ? Effect.void
66 : Effect.fail({ _tag: "KeylessNotDeleted" } as const),
67 ),
68 Effect.catchTag("KeylessCertificateNotFound", () => Effect.void),
69 Effect.retry({
70 while: (e) => e._tag === "KeylessNotDeleted",
71 schedule: Schedule.max([
72 Schedule.exponential("500 millis"),
73 Schedule.recurs(10),
74 ]),
75 }),
76 );
77
78test.provider(
79 "surfaces the typed KeylessSslNotAvailable error on non-Enterprise zones",
80 (stack) =>
81 Effect.gen(function* () {
82 const zoneId = yield* resolveZoneId;
83
84 yield* stack.destroy();
85
86 // Listing works on every plan — verifies token scope and the typed
87 // pagination surface out of band.
88 const existing = yield* keylessCertificates.listKeylessCertificates
89 .items({ zoneId })
90 .pipe(
91 Stream.runCollect,
92 Effect.retry({
93 while: (e) => e._tag === "Forbidden",
94 schedule: forbiddenRetrySchedule,
95 times: 8,
96 }),
97 );
98 expect(
99 Array.from(existing).filter((k) => k.status !== "deleted"),
100 ).toEqual([]);
101
102 // Reading a non-existent configuration surfaces the typed
103 // `KeylessCertificateNotFound` (Cloudflare code 1005).
104 const readError = yield* getKeyless(
105 zoneId,
106 "00000000000000000000000000000000",
107 ).pipe(Effect.flip);
108 expect(readError._tag).toEqual("KeylessCertificateNotFound");
109
110 // Deleting a non-existent configuration converges idempotently once
111 // the typed not-found tag is absorbed — exactly what the provider's
112 // delete does.
113 yield* keylessCertificates
114 .deleteKeylessCertificate({
115 zoneId,
116 keylessCertificateId: "00000000000000000000000000000000",
117 })
118 .pipe(
119 Effect.catchTag("KeylessCertificateNotFound", () => Effect.void),
120 Effect.retry({
121 while: (e) => e._tag === "Forbidden",
122 schedule: forbiddenRetrySchedule,
123 times: 8,
124 }),
125 );
126
127 // Creating on a non-Enterprise zone fails with the typed entitlement
128 // tag (Cloudflare code 1067).
129 const createError = yield* keylessCertificates
130 .createKeylessCertificate({
131 zoneId,
132 certificate: CERT_1,
133 host: `keyless.${zoneName}`,
134 port: 24008,
135 name: "alchemy-keyless-entitlement-probe",
136 })
137 .pipe(
138 Effect.retry({
139 while: (e) => e._tag === "Forbidden",
140 schedule: forbiddenRetrySchedule,
141 times: 8,
142 }),
143 Effect.flip,
144 );
145 expect(createError._tag).toEqual("KeylessSslNotAvailable");
146
147 yield* stack.destroy();
148 }).pipe(logLevel),
149 {
150 tags: [
151 "provider:cloudflare",
152 "provider:cloudflare:keylesscertificate",
153 "provider:cloudflare:zone",
154 "live",
155 ],
156 },
157);
158
159// `list()` enumerates every zone in the account and lists its Keyless SSL
160// configurations (zone-scoped collection, pattern (c)). This read-only
161// assertion runs on every plan: the testing account has no Keyless SSL
162// configurations (Enterprise-only), so the exhaustively-paginated result is a
163// well-typed (possibly empty) array whose elements match `read`'s Attributes.
164test.provider(
165 "list enumerates keyless certificates across zones",
166 (stack) =>
167 Effect.gen(function* () {
168 yield* stack.destroy();
169
170 const provider = yield* Provider.findProvider(
171 Cloudflare.KeylessCertificate.KeylessCertificate,
172 );
173 const all = yield* provider.list().pipe(
174 Effect.retry({
175 while: (e) => e._tag === "Forbidden",
176 schedule: forbiddenRetrySchedule,
177 times: 8,
178 }),
179 );
180
181 expect(Array.isArray(all)).toBe(true);
182 for (const item of all) {
183 expect(typeof item.keylessCertificateId).toBe("string");
184 expect(typeof item.zoneId).toBe("string");
185 expect(item.status).not.toEqual("deleted");
186 }
187
188 yield* stack.destroy();
189 }).pipe(logLevel),
190 {
191 tags: [
192 "provider:cloudflare",
193 "provider:cloudflare:keylesscertificate",
194 "live",
195 ],
196 },
197);
198
199// On an entitled Enterprise zone, the deployed configuration must appear in the
200// exhaustively-paginated `list()` result. Gated behind an Enterprise zone id
201// (creation otherwise fails with the typed `KeylessSslNotAvailable`, code 1067).
202test.provider.skipIf(!enterpriseZoneId)(
203 "list includes a deployed keyless certificate",
204 (stack) =>
205 Effect.gen(function* () {
206 const zoneId = enterpriseZoneId!;
207
208 yield* stack.destroy();
209
210 const deployed = yield* stack.deploy(
211 Cloudflare.KeylessCertificate.KeylessCertificate("ListKeyless", {
212 zoneId,
213 certificate: CERT_1,
214 host: `keyless.${zoneName}`,
215 port: 24008,
216 name: "alchemy-keyless-list",
217 }),
218 );
219
220 const provider = yield* Provider.findProvider(
221 Cloudflare.KeylessCertificate.KeylessCertificate,
222 );
223 const all = yield* provider.list().pipe(
224 Effect.retry({
225 while: (e) => e._tag === "Forbidden",
226 schedule: forbiddenRetrySchedule,
227 times: 8,
228 }),
229 );
230
231 expect(
232 all.some(
233 (k) => k.keylessCertificateId === deployed.keylessCertificateId,
234 ),
235 ).toBe(true);
236
237 yield* stack.destroy();
238 }).pipe(logLevel),
239 {
240 tags: [
241 "provider:cloudflare",
242 "provider:cloudflare:keylesscertificate",
243 "live",
244 ],
245 timeout: 120_000,
246 },
247);
248
249test.provider.skipIf(!enterpriseZoneId)(
250 "creates, updates in place, replaces on certificate change, and destroys",
251 (stack) =>
252 Effect.gen(function* () {
253 const zoneId = enterpriseZoneId!;
254
255 yield* stack.destroy();
256
257 // Create with an explicit name and conventional port.
258 const created = yield* stack.deploy(
259 Cloudflare.KeylessCertificate.KeylessCertificate("Keyless", {
260 zoneId,
261 certificate: CERT_1,
262 host: `keyless.${zoneName}`,
263 port: 24008,
264 name: "alchemy-keyless-lifecycle",
265 }),
266 );
267
268 expect(created.keylessCertificateId).toBeDefined();
269 expect(created.zoneId).toEqual(zoneId);
270 expect(created.name).toEqual("alchemy-keyless-lifecycle");
271 expect(created.host).toEqual(`keyless.${zoneName}`);
272 expect(created.port).toEqual(24008);
273 expect(created.status).toEqual("active");
274
275 // Out-of-band verify against the live API.
276 const live = yield* getKeyless(zoneId, created.keylessCertificateId);
277 expect(live.id).toEqual(created.keylessCertificateId);
278 expect(live.host).toEqual(`keyless.${zoneName}`);
279 expect(live.port).toEqual(24008);
280
281 // Update mutable props in place — same identifier.
282 const updated = yield* stack.deploy(
283 Cloudflare.KeylessCertificate.KeylessCertificate("Keyless", {
284 zoneId,
285 certificate: CERT_1,
286 host: `keyless2.${zoneName}`,
287 port: 24009,
288 name: "alchemy-keyless-lifecycle-v2",
289 enabled: false,
290 }),
291 );
292 expect(updated.keylessCertificateId).toEqual(
293 created.keylessCertificateId,
294 );
295 expect(updated.host).toEqual(`keyless2.${zoneName}`);
296 expect(updated.port).toEqual(24009);
297 expect(updated.name).toEqual("alchemy-keyless-lifecycle-v2");
298 expect(updated.enabled).toEqual(false);
299
300 const liveUpdated = yield* getKeyless(
301 zoneId,
302 updated.keylessCertificateId,
303 );
304 expect(liveUpdated.host).toEqual(`keyless2.${zoneName}`);
305 expect(liveUpdated.port).toEqual(24009);
306 expect(liveUpdated.enabled).toEqual(false);
307
308 // Changing the certificate replaces the configuration (the PATCH API
309 // has no certificate field) — a new identifier is assigned.
310 const replaced = yield* stack.deploy(
311 Cloudflare.KeylessCertificate.KeylessCertificate("Keyless", {
312 zoneId,
313 certificate: CERT_2,
314 host: `keyless2.${zoneName}`,
315 port: 24009,
316 name: "alchemy-keyless-lifecycle-v2",
317 enabled: false,
318 }),
319 );
320 expect(replaced.keylessCertificateId).not.toEqual(
321 updated.keylessCertificateId,
322 );
323 yield* expectGone(zoneId, updated.keylessCertificateId);
324
325 yield* stack.destroy();
326
327 yield* expectGone(zoneId, replaced.keylessCertificateId);
328 }).pipe(logLevel),
329 {
330 tags: [
331 "provider:cloudflare",
332 "provider:cloudflare:keylesscertificate",
333 "live",
334 ],
335 timeout: 120_000,
336 },
337);