import { describe, expect, it, vi } from "vitest"; import type { BrowserRouteContext } from "../server-context.js"; vi.mock("./index.js", () => { return { registerBrowserRoutes(app: { get: (path: string, handler: unknown) => void }) { app.get( "/slow", async (req: { signal?: AbortSignal }, res: { json: (body: unknown) => void }) => { const signal = req.signal; await new Promise((resolve, reject) => { if (signal?.aborted) { reject(signal.reason ?? new Error("aborted")); return; } const onAbort = () => reject(signal?.reason ?? new Error("aborted")); signal?.addEventListener("abort", onAbort, { once: true }); queueMicrotask(() => { signal?.removeEventListener("abort", onAbort); resolve(); }); }); res.json({ ok: true }); }, ); app.get( "/echo/:id", async ( req: { params?: Record }, res: { json: (body: unknown) => void }, ) => { res.json({ id: req.params?.id ?? null }); }, ); }, }; }); describe("browser route dispatcher (abort)", () => { it("propagates AbortSignal and lets handlers observe abort", async () => { const { createBrowserRouteDispatcher } = await import("./dispatcher.js"); const dispatcher = createBrowserRouteDispatcher({} as BrowserRouteContext); const ctrl = new AbortController(); const promise = dispatcher.dispatch({ method: "GET", path: "/slow", signal: ctrl.signal, }); ctrl.abort(new Error("timed out")); await expect(promise).resolves.toMatchObject({ status: 500, body: { error: expect.stringContaining("timed out") }, }); }); it("returns 400 for malformed percent-encoding in route params", async () => { const { createBrowserRouteDispatcher } = await import("./dispatcher.js"); const dispatcher = createBrowserRouteDispatcher({} as BrowserRouteContext); await expect( dispatcher.dispatch({ method: "GET", path: "/echo/%E0%A4%A", }), ).resolves.toMatchObject({ status: 400, body: { error: expect.stringContaining("invalid path parameter encoding") }, }); }); });