import { describe, expect, it, vi } from "vitest"; import { compensateLateHandle, createAbortableOperation, } from "../../src/adapters/platform/abortable-operation.ts"; /** * BT-X-01 golden tests. The utility owns abort mechanics only: it must never * import or imply a subsystem result taxonomy. */ describe("shared abortable operation mechanics", () => { it("records the first terminal owner and never overwrites it", async () => { const caller = new AbortController(); const timers: Array<() => void> = []; const operation = createAbortableOperation({ signal: caller.signal, timeoutMs: 10, setTimer: (callback) => { timers.push(callback); return timers.length; }, clearTimer: () => {}, }); expect(operation.terminal()).toBeNull(); caller.abort(); expect(operation.terminal()).toBe("CALLER_ABORT"); // A later deadline or close cannot rewrite the owner. timers.forEach((callback) => callback()); operation.close(); expect(operation.terminal()).toBe("CALLER_ABORT"); }); it("reports a deadline owner and aborts the composed signal", async () => { const timers: Array<() => void> = []; const operation = createAbortableOperation({ timeoutMs: 5, setTimer: (callback) => { timers.push(callback); return timers.length; }, clearTimer: () => {}, }); timers[0]?.(); expect(operation.terminal()).toBe("DEADLINE"); expect(operation.signal.aborted).toBe(true); }); it("race returns a terminal owner instead of a bare value", async () => { const caller = new AbortController(); const operation = createAbortableOperation({ signal: caller.signal }); let release: ((value: string) => void) | undefined; const pending = new Promise((resolve) => { release = resolve; }); const raced = operation.race(pending); caller.abort(); await expect(raced).resolves.toEqual({ kind: "TERMINAL", terminal: "CALLER_ABORT", }); // The late value is observed and discarded. release?.("late"); await expect(operation.race(pending)).resolves.toEqual({ kind: "TERMINAL", terminal: "CALLER_ABORT", }); }); it("race returns the value when nothing terminal happened", async () => { const operation = createAbortableOperation(); await expect(operation.race(Promise.resolve(7))).resolves.toEqual({ kind: "VALUE", value: 7, }); operation.close(); expect(operation.terminal()).toBe("CLOSED"); }); it("observes a late rejection instead of leaking it", async () => { const operation = createAbortableOperation(); const rejected = Promise.reject(new Error("late")); await expect(operation.race(rejected)).resolves.toEqual({ kind: "TERMINAL", terminal: "CLOSED", }); }); it("close is idempotent and releases listeners and timers exactly once", () => { const caller = new AbortController(); const remove = vi.spyOn(caller.signal, "removeEventListener"); const clearTimer = vi.fn(); const operation = createAbortableOperation({ signal: caller.signal, timeoutMs: 10, setTimer: () => "handle", clearTimer, }); operation.close(); operation.close(); operation.close(); expect(remove).toHaveBeenCalledTimes(1); expect(clearTimer).toHaveBeenCalledTimes(1); }); it("survives a throwing scheduler without leaking a listener", () => { const caller = new AbortController(); const remove = vi.spyOn(caller.signal, "removeEventListener"); const operation = createAbortableOperation({ signal: caller.signal, timeoutMs: 10, setTimer: () => { throw new TypeError("scheduler exploded"); }, clearTimer: () => {}, }); expect(operation.terminal()).toBeNull(); expect(remove).toHaveBeenCalledTimes(1); }); it("compensates a late native handle without changing the outcome", async () => { const cancel = vi.fn(async () => {}); compensateLateHandle(Promise.resolve({ body: { cancel } })); await Promise.resolve(); await Promise.resolve(); expect(cancel).toHaveBeenCalledOnce(); // A rejecting handle is swallowed. compensateLateHandle(Promise.reject(new Error("late"))); await Promise.resolve(); }); });