1import * as Cloudflare from "@/Cloudflare/index.ts";
2import * as Test from "@/Test/Alchemy";
3import { expect } from "alchemy-test";
4import * as Data from "effect/Data";
5import * as Effect from "effect/Effect";
6import { MinimumLogLevel } from "effect/References";
7import * as Schedule from "effect/Schedule";
8import * as HttpClient from "effect/http/HttpClient";
9import RandomEnvWorker from "./fixtures/random-env/worker.ts";
10
11const { test } = Test.make({
12 providers: Cloudflare.providers(),
13 dev: true,
14});
15
16const logLevel = Effect.provideService(
17 MinimumLogLevel,
18 process.env.DEBUG ? "Debug" : "Info",
19);
20
21interface RandomEnvBody {
22 resolvedType: string;
23 isHexSecret: boolean;
24}
25
26class WorkerNotReady extends Data.TaggedError("WorkerNotReady")<{
27 status: number;
28}> {}
29
30/**
31 * Pins that an `Alchemy.Random` output bound during init resolves to its
32 * minted value under the LOCAL worker provider, the same as in a real
33 * deploy.
34 *
35 * Regression shape (beta.62, fixed by #829's request-time env reification):
36 * the runtime accessor read of an output-bound env entry came back
37 * `undefined` under `alchemy dev` while the same code worked deployed —
38 * the first consumer crashed far from the cause (e.g.
39 * `Redacted.value(undefined)` dying inside effect internals). Nothing else
40 * in the suite exercises a generated-secret Output accessor under the
41 * local provider, so this pins the deploy/dev parity directly.
42 */
43test.provider(
44 "a Random output bound at init resolves under the local provider",
45 (stack) =>
46 Effect.gen(function* () {
47 yield* stack.destroy();
48
49 const { worker } = yield* stack.deploy(
50 Effect.gen(function* () {
51 const w = yield* RandomEnvWorker;
52 return { worker: w };
53 }),
54 );
55
56 const client = yield* HttpClient.HttpClient;
57 const body = yield* Effect.gen(function* () {
58 const res = yield* client.get(worker.url!).pipe(
59 Effect.flatMap((res) =>
60 res.status === 200
61 ? Effect.succeed(res)
62 : Effect.fail(new WorkerNotReady({ status: res.status })),
63 ),
64 Effect.retry({
65 while: (e): e is WorkerNotReady => e instanceof WorkerNotReady,
66 schedule: Schedule.max([
67 Schedule.exponential("500 millis"),
68 Schedule.recurs(10),
69 ]),
70 }),
71 );
72 return (yield* res.json) as unknown as RandomEnvBody;
73 }).pipe(Effect.orDie);
74
75 // The runtime read must observe the minted 32-byte hex secret —
76 // never `undefined`.
77 expect(body.resolvedType).toBe("string");
78 expect(body.isHexSecret).toBe(true);
79
80 yield* stack.destroy();
81 }).pipe(logLevel),
82 {
83 tags: ["provider:cloudflare", "provider:cloudflare:worker", "local"],
84 timeout: 240_000,
85 },
86);