import { describe, expect, test } from "bun:test"; import { parseStrictDuration, readOnlyLongPollTransportTimeoutMs, runReadOnlyLongPoll } from "./read-only-long-poll"; describe("read-only long poll", () => { test("returns an immediate terminal observation", async () => { const result = await runReadOnlyLongPoll({ observe: async () => "done", completed: (value) => value === "done", waitForChange: async () => { throw new Error("unexpected wait"); }, cursor: (value) => value }); expect(result).toMatchObject({ completed: true, timedOut: false, observations: 1, cursor: "done" }); }); test("returns after a backend-held change", async () => { let now = 0; let state = "running"; const result = await runReadOnlyLongPoll({ observe: async () => state, completed: (value) => value === "done", waitForChange: async () => { now += 25; state = "done"; }, cursor: (value) => value, timeoutMs: 100, clock: { now: () => now }, }); expect(result).toMatchObject({ completed: true, timedOut: false, elapsedMs: 25, observations: 2 }); }); test("returns the current state after an empty timeout", async () => { let now = 0; const result = await runReadOnlyLongPoll({ observe: async () => ({ state: "running", id: "run-1" }), completed: () => false, waitForChange: async (remainingMs) => { now += remainingMs; }, cursor: (value) => value.id, timeoutMs: 100, clock: { now: () => now }, }); expect(result).toMatchObject({ completed: false, timedOut: true, cancelled: false, elapsedMs: 100, cursor: "run-1" }); }); test("preserves backend errors and cancellation", async () => { await expect(runReadOnlyLongPoll({ observe: async () => "running", completed: () => false, waitForChange: async () => { throw new Error("watch disconnected"); }, cursor: (value) => value, timeoutMs: 10 })).rejects.toThrow("watch disconnected"); const controller = new AbortController(); controller.abort(); const cancelled = await runReadOnlyLongPoll({ observe: async () => "running", completed: () => false, waitForChange: async () => {}, cursor: (value) => value, signal: controller.signal }); expect(cancelled).toMatchObject({ completed: false, timedOut: false, cancelled: true }); }); test("validates strict duration syntax", () => { expect(parseStrictDuration("120s")).toBe(120_000); expect(parseStrictDuration("250ms")).toBe(250); expect(() => parseStrictDuration("1.5s")).toThrow(); expect(() => parseStrictDuration("120")).toThrow(); expect(() => parseStrictDuration("0s")).toThrow(); expect(readOnlyLongPollTransportTimeoutMs(60 * 60_000)).toBe(60 * 60_000 + 15_000); }); });