import { describe, it, expect, beforeEach, afterEach } from "vitest"; import { mkdtempSync, writeFileSync, existsSync, readFileSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { readInbox, readHwm, writeHwm, unreadSinceHwm, DEFER_LIMIT, type PingEvent, type HwmState, } from "../src/inbox.js"; const ev = (over: Partial): PingEvent => ({ ts: "2026-05-06T10:00:00Z", id: "ping-aaa", from: "foreman", to: "bob", type: "INFO", payload: "test", ...over, }); let dir: string; beforeEach(() => { dir = mkdtempSync(join(tmpdir(), "watcher-test-")); }); afterEach(() => { rmSync(dir, { recursive: true, force: true }); }); describe("readInbox", () => { it("returns empty for missing file", () => { expect(readInbox(join(dir, "missing.inbox"))).toEqual([]); }); it("parses JSONL, skips blank + malformed lines", () => { const path = join(dir, "x.inbox"); writeFileSync( path, [ JSON.stringify(ev({ id: "ping-1" })), "", "{ not json", JSON.stringify(ev({ id: "ping-2", ts: "2026-05-06T11:00:00Z" })), "", ].join("\n"), ); const r = readInbox(path); expect(r.map((e) => e.id)).toEqual(["ping-1", "ping-2"]); }); it("requires id and ts on each line", () => { const path = join(dir, "x.inbox"); writeFileSync(path, [ JSON.stringify({ ts: "x", payload: "missing id" }), JSON.stringify({ id: "missing-ts", payload: "x" }), JSON.stringify(ev({ id: "good" })), ].join("\n")); expect(readInbox(path).map((e) => e.id)).toEqual(["good"]); }); }); describe("readHwm + writeHwm", () => { it("returns empty state for missing file", () => { const r = readHwm(join(dir, "missing.hwm")); expect(r).toEqual({ last_delivered_ts: "", pending_attempts: {} }); }); it("round-trips through atomic write", () => { const path = join(dir, ".hwm"); const state: HwmState = { last_delivered_ts: "2026-05-06T12:00:00Z", pending_attempts: { "ping-x": 2 }, }; writeHwm(path, state); expect(existsSync(path)).toBe(true); expect(existsSync(path + ".tmp")).toBe(false); expect(readHwm(path)).toEqual(state); }); it("recovers from corrupt file with empty state", () => { const path = join(dir, ".hwm"); writeFileSync(path, "{ corrupt"); expect(readHwm(path)).toEqual({ last_delivered_ts: "", pending_attempts: {} }); }); }); describe("unreadSinceHwm", () => { const yes = () => true; const no = () => false; it("returns all events when HWM is empty", () => { const events = [ev({ id: "a", ts: "2026-05-06T10:00:00Z" })]; const r = unreadSinceHwm(events, { last_delivered_ts: "", pending_attempts: {} }, yes); expect(r.deliverable.map((d) => d.event.id)).toEqual(["a"]); expect(r.deferred).toEqual([]); expect(r.nextHwm.last_delivered_ts).toBe("2026-05-06T10:00:00Z"); }); it("filters out events with ts <= HWM", () => { const events = [ ev({ id: "old", ts: "2026-05-06T09:00:00Z" }), ev({ id: "new", ts: "2026-05-06T11:00:00Z" }), ]; const r = unreadSinceHwm( events, { last_delivered_ts: "2026-05-06T10:00:00Z", pending_attempts: {} }, yes, ); expect(r.deliverable.map((d) => d.event.id)).toEqual(["new"]); }); it("defers events whose sentinel is missing, increments attempts", () => { const events = [ev({ id: "a", sentinel: "/nope" })]; const r = unreadSinceHwm(events, { last_delivered_ts: "", pending_attempts: {} }, no); expect(r.deliverable).toEqual([]); expect(r.deferred.map((e) => e.id)).toEqual(["a"]); expect(r.nextHwm.pending_attempts).toEqual({ a: 1 }); // HWM does NOT advance past the deferred event expect(r.nextHwm.last_delivered_ts).toBe(""); }); it("delivers with warning on the DEFER_LIMIT-th attempt", () => { const events = [ev({ id: "a", sentinel: "/nope" })]; const r = unreadSinceHwm( events, { last_delivered_ts: "", pending_attempts: { a: DEFER_LIMIT - 1 } }, no, ); expect(r.deliverable.length).toBe(1); expect(r.deliverable[0].warning).toMatch(/hasn't synced/); expect(r.deferred).toEqual([]); // attempts cleared once delivered expect(r.nextHwm.pending_attempts).toEqual({}); expect(r.nextHwm.last_delivered_ts).toBe("2026-05-06T10:00:00Z"); }); it("delivers immediately when sentinel exists", () => { const events = [ev({ id: "a", sentinel: "/exists" })]; const r = unreadSinceHwm(events, { last_delivered_ts: "", pending_attempts: {} }, yes); expect(r.deliverable.length).toBe(1); expect(r.deliverable[0].warning).toBeUndefined(); expect(r.nextHwm.pending_attempts).toEqual({}); }); it("HWM does not advance past deferred event even when later events deliver", () => { const events = [ ev({ id: "deferred", ts: "2026-05-06T10:00:00Z", sentinel: "/nope" }), ev({ id: "later", ts: "2026-05-06T11:00:00Z" }), ]; const sentinelExists = (p: string) => p !== "/nope"; const r = unreadSinceHwm( events, { last_delivered_ts: "", pending_attempts: {} }, sentinelExists, ); expect(r.deliverable.map((d) => d.event.id)).toEqual(["later"]); expect(r.deferred.map((e) => e.id)).toEqual(["deferred"]); // HWM stays empty so the deferred event is reconsidered next read expect(r.nextHwm.last_delivered_ts).toBe(""); }); it("clears pending_attempts when sentinel finally lands", () => { const events = [ev({ id: "a", sentinel: "/now-ok" })]; const r = unreadSinceHwm( events, { last_delivered_ts: "", pending_attempts: { a: 2 } }, yes, ); expect(r.deliverable.length).toBe(1); expect(r.nextHwm.pending_attempts).toEqual({}); }); });