1// Pins expectUrlContains's time bounds against hostile servers: the total
2// timeout must fire even when the underlying fetch never settles. A wedged
3// bun fetch (keep-alive reuse against a closed connection) once pinned the
4// timeout's interruption and turned a 300s assertion bound into a
5// suite-timeout hang — these three shapes keep that class of bug dead.
6import { expect, test } from "alchemy-test";
7import * as Effect from "effect/Effect";
8import * as Result from "effect/Result";
9import { createServer } from "node:http";
10import { expectUrlContains } from "./Http.ts";
11
12const timed = <A, E>(eff: Effect.Effect<A, E>) =>
13 Effect.gen(function* () {
14 const start = Date.now();
15 const result = yield* Effect.result(eff);
16 return { ms: Date.now() - start, ok: Result.isSuccess(result) };
17 });
18
19test(
20 "total timeout fires against a connect-blackhole",
21 async () =>
22 await Effect.runPromise(
23 Effect.scoped(
24 Effect.gen(function* () {
25 const { ms, ok } = yield* timed(
26 expectUrlContains("https://192.0.2.1/", "nope", {
27 timeout: "250 millis",
28 label: "blackhole",
29 }),
30 );
31 expect(ok).toBe(false);
32 expect(ms).toBeLessThan(3_000);
33 }),
34 ),
35 ),
36 { tags: ["unit", "provider:cloudflare", "local"], timeout: 5_000 },
37);
38
39test(
40 "total timeout fires against an accept-but-never-respond server",
41 async () =>
42 await Effect.runPromise(
43 Effect.scoped(
44 Effect.gen(function* () {
45 const server = yield* Effect.acquireRelease(
46 Effect.callback<ReturnType<typeof createServer>>((resume) => {
47 const s = createServer(() => {
48 /* accept request, never respond */
49 });
50 s.listen(0, "127.0.0.1", () => resume(Effect.succeed(s)));
51 }),
52 (s) => Effect.sync(() => void s.close()),
53 );
54 const port = (server.address() as { port: number }).port;
55 const { ms, ok } = yield* timed(
56 expectUrlContains(`http://127.0.0.1:${port}/`, "nope", {
57 timeout: "250 millis",
58 label: "black-hole-response",
59 }),
60 );
61 expect(ok).toBe(false);
62 expect(ms).toBeLessThan(3_000);
63 }),
64 ),
65 ),
66 { tags: ["unit", "provider:cloudflare", "local"], timeout: 5_000 },
67);
68
69test(
70 "total timeout fires against a fast keep-alive 502 server",
71 async () =>
72 await Effect.runPromise(
73 Effect.scoped(
74 Effect.gen(function* () {
75 const server = yield* Effect.acquireRelease(
76 Effect.callback<ReturnType<typeof createServer>>((resume) => {
77 const s = createServer((_req, res) => {
78 res.writeHead(502, {
79 "content-type": "text/html",
80 connection: "keep-alive",
81 });
82 res.end("<html>Bad gateway</html>");
83 });
84 s.listen(0, "127.0.0.1", () => resume(Effect.succeed(s)));
85 }),
86 (s) => Effect.sync(() => void s.close()),
87 );
88 const port = (server.address() as { port: number }).port;
89 const { ms, ok } = yield* timed(
90 expectUrlContains(`http://127.0.0.1:${port}/`, "nope", {
91 timeout: "250 millis",
92 label: "fast-502",
93 }),
94 );
95 expect(ok).toBe(false);
96 expect(ms).toBeLessThan(3_000);
97 }),
98 ),
99 ),
100 { tags: ["unit", "provider:cloudflare", "local"], timeout: 5_000 },
101);