1import * as Cloudflare from "@/Cloudflare/index.ts";
2import * as Alchemy from "@/index.ts";
3import * as Test from "@/Test/Alchemy";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import * as Schedule from "effect/Schedule";
7import * as HttpClient from "effect/http/HttpClient";
8import { expectUrlContains } from "../Utils/Http.ts";
9import HttpServerWorker, {
10 readyMarker,
11 sensitiveContext,
12} from "./fixtures/http-server-worker.ts";
13
14const { test, beforeAll, afterAll, deploy, destroy } = Test.make({
15 providers: Cloudflare.providers(),
16});
17
18const Stack = Alchemy.Stack(
19 "WorkersHttpServerStack",
20 { providers: Cloudflare.providers(), state: Cloudflare.state() },
21 Effect.gen(function* () {
22 const worker = yield* HttpServerWorker;
23 return { url: worker.url.as<string>() };
24 }),
25);
26
27const stack = beforeAll(deploy(Stack));
28afterAll.skipIf(!!process.env.NO_DESTROY)(destroy(Stack));
29
30/**
31 * GET `url` until it serves `status` with an empty body. Both error routes
32 * respond with no body, which also distinguishes them from the workers.dev
33 * placeholder page (a 404 *with* a body) during edge propagation.
34 */
35const getEmptyResponse = (url: string, status: number) =>
36 Effect.gen(function* () {
37 const client = yield* HttpClient.HttpClient;
38 const response = yield* client.get(url);
39 const body = yield* response.text;
40 if (response.status !== status || body !== "") {
41 return yield* Effect.fail(
42 new Error(
43 `expected empty ${status} from ${url}, got ${response.status}: ${body.slice(0, 160)}`,
44 ),
45 );
46 }
47 return response;
48 }).pipe(
49 Effect.retry({ schedule: Schedule.spaced("1500 millis"), times: 20 }),
50 );
51
52test(
53 "a Respondable defect keeps its intended response over the wire",
54 Effect.gen(function* () {
55 const { url } = yield* stack;
56 yield* expectUrlContains(url, readyMarker);
57
58 yield* getEmptyResponse(`${url}/missing`, 404);
59 }),
60 {
61 tags: ["provider:cloudflare", "provider:cloudflare:worker", "live"],
62 timeout: 180_000,
63 },
64);
65
66test(
67 "a failed handler responds 500 without exposing the cause",
68 Effect.gen(function* () {
69 const { url } = yield* stack;
70 yield* expectUrlContains(url, readyMarker);
71
72 const response = yield* getEmptyResponse(`${url}/boom`, 500);
73 const wireResponse = JSON.stringify(response.headers);
74 for (const sensitiveValue of sensitiveContext) {
75 expect(wireResponse).not.toContain(sensitiveValue);
76 }
77 }),
78 {
79 tags: ["provider:cloudflare", "provider:cloudflare:worker", "live"],
80 timeout: 180_000,
81 },
82);