import { beforeEach, expect, mock, test } from "bun:test"; const requests: unknown[] = []; const clients: FakeClient[] = []; const requestWaiters: Array<() => void> = []; let autoAcknowledge = true; let importCounter = 0; type FakeClient = { emit: (event: string) => void; }; mock.module("node:net", () => ({ default: { createConnection(_path: string, onConnect: () => void) { const handlers = new Map void>(); const client = { write(input: string) { requests.push(JSON.parse(input.trim())); requestWaiters.shift()?.(); if (autoAcknowledge) { queueMicrotask(() => client.emit("data")); } }, setTimeout() {}, on(event: string, handler: () => void) { handlers.set(event, handler); }, destroy() {}, emit(event: string) { handlers.get(event)?.(); }, }; clients.push(client); queueMicrotask(onConnect); return client; }, }, })); beforeEach(() => { requests.length = 0; clients.length = 0; requestWaiters.length = 0; autoAcknowledge = true; process.env.HERDR_ENV = "1"; process.env.HERDR_SOCKET_PATH = "test.sock"; process.env.HERDR_PANE_ID = "test:p1"; }); async function loadPlugin() { importCounter += 1; const { HerdrAgentStatePlugin } = await import(`./herdr-agent-state.js?test=${importCounter}`); return HerdrAgentStatePlugin(); } function waitForNextRequest(): Promise { return new Promise((resolve) => requestWaiters.push(resolve)); } test("serializes lifecycle reports", async () => { autoAcknowledge = false; const plugin = await loadPlugin(); const firstDispatched = waitForNextRequest(); const working = plugin.event({ event: { type: "session.status", properties: { sessionID: "root-session", status: { type: "busy" } }, }, }); await firstDispatched; const secondDispatched = waitForNextRequest(); const idle = plugin.event({ event: { type: "session.status", properties: { sessionID: "root-session", status: { type: "idle" } }, }, }); expect(clients).toHaveLength(1); clients[0]?.emit("data"); await secondDispatched; expect(clients).toHaveLength(2); clients[1]?.emit("data"); await Promise.all([working, idle]); expect(requests.map(requestState)).toEqual(["working", "idle"]); const sequences = requests.map(requestSeq); expect(sequences[0]).toEqual(expect.any(Number)); expect(sequences[1]).toBe((sequences[0] as number) + 1); }); test("suppresses redundant same-session updates", async () => { const plugin = await loadPlugin(); await plugin.event({ event: { type: "session.status", properties: { sessionID: "root-session", status: { type: "busy" } }, }, }); await plugin.event({ event: { type: "session.updated", properties: { sessionID: "root-session" } }, }); await plugin.event({ event: { type: "session.updated", properties: { sessionID: "replacement-session" } }, }); expect(requests.map(requestMethod)).toEqual([ "pane.report_agent", "pane.report_agent_session", ]); expect(requests.map(requestSessionID)).toEqual(["root-session", "replacement-session"]); }); function requestMethod(request: unknown): unknown { return isRecord(request) ? request.method : undefined; } function requestState(request: unknown): unknown { return requestParam(request, "state"); } function requestSeq(request: unknown): unknown { return requestParam(request, "seq"); } function requestSessionID(request: unknown): unknown { return requestParam(request, "agent_session_id"); } function requestParam(request: unknown, name: string): unknown { if (!isRecord(request) || !isRecord(request.params)) { return undefined; } return request.params[name]; } function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null; }