/** * group-join.test.ts — Behavior of GroupJoinManager's state machine and timers. * * Uses fake timers to assert deterministic timeout behavior without flakiness. * The class itself is exercised directly with real records — no mocks beyond * a spy on the delivery callback. */ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { GroupJoinManager } from "../src/group-join.js"; import type { AgentRecord } from "../src/types.js"; function makeRecord(id: string, overrides: Partial = {}): AgentRecord { return { id, type: "general-purpose", description: "test", status: "completed", toolUses: 0, startedAt: 0, lifetimeUsage: { input: 0, output: 0, cacheWrite: 0 }, ...overrides, }; } describe("GroupJoinManager", () => { beforeEach(() => vi.useFakeTimers()); afterEach(() => vi.useRealTimers()); it("returns 'pass' for unregistered agents and never invokes the callback", () => { const deliver = vi.fn(); const mgr = new GroupJoinManager(deliver); expect(mgr.onAgentComplete(makeRecord("a"))).toBe("pass"); expect(deliver).not.toHaveBeenCalled(); expect(mgr.isGrouped("a")).toBe(false); }); it("holds the first completion and arms the join timeout", () => { const deliver = vi.fn(); const mgr = new GroupJoinManager(deliver, 30_000); mgr.registerGroup("g", ["a", "b"]); expect(mgr.isGrouped("a")).toBe(true); expect(mgr.onAgentComplete(makeRecord("a"))).toBe("held"); vi.advanceTimersByTime(29_999); expect(deliver).not.toHaveBeenCalled(); }); it("delivers all records (partial=false) when the final completion arrives in time", () => { const deliver = vi.fn(); const mgr = new GroupJoinManager(deliver); mgr.registerGroup("g", ["a", "b"]); mgr.onAgentComplete(makeRecord("a", { result: "A" })); expect(mgr.onAgentComplete(makeRecord("b", { result: "B" }))).toBe("delivered"); expect(deliver).toHaveBeenCalledTimes(1); const [records, partial] = deliver.mock.calls[0]; expect(records.map((r: AgentRecord) => r.id).sort()).toEqual(["a", "b"]); expect(partial).toBe(false); // Group is cleaned up — no future deliveries can fire from these ids expect(mgr.isGrouped("a")).toBe(false); expect(mgr.isGrouped("b")).toBe(false); }); it("delivers partial=true on timeout and re-arms the group for stragglers", () => { const deliver = vi.fn(); const mgr = new GroupJoinManager(deliver, 30_000); mgr.registerGroup("g", ["a", "b", "c"]); mgr.onAgentComplete(makeRecord("a")); vi.advanceTimersByTime(30_000); expect(deliver).toHaveBeenCalledTimes(1); const [records, partial] = deliver.mock.calls[0]; expect(records.map((r: AgentRecord) => r.id)).toEqual(["a"]); expect(partial).toBe(true); // 'a' was delivered and is dropped from the group; 'b' and 'c' remain as stragglers expect(mgr.isGrouped("a")).toBe(false); expect(mgr.isGrouped("b")).toBe(true); expect(mgr.isGrouped("c")).toBe(true); }); it("uses the shorter straggler timeout (15s) regardless of the configured group timeout", () => { const deliver = vi.fn(); const mgr = new GroupJoinManager(deliver, 30_000); mgr.registerGroup("g", ["a", "b", "c"]); // First batch: 'a' alone, partial-delivered after 30s mgr.onAgentComplete(makeRecord("a")); vi.advanceTimersByTime(30_000); expect(deliver).toHaveBeenCalledTimes(1); // Straggler 'b' arrives — fires at 15s, not 30s mgr.onAgentComplete(makeRecord("b")); vi.advanceTimersByTime(14_999); expect(deliver).toHaveBeenCalledTimes(1); vi.advanceTimersByTime(1); expect(deliver).toHaveBeenCalledTimes(2); expect(deliver.mock.calls[1][0].map((r: AgentRecord) => r.id)).toEqual(["b"]); expect(deliver.mock.calls[1][1]).toBe(true); expect(mgr.isGrouped("c")).toBe(true); // 'c' is the remaining straggler now }); it("delivers stragglers as a complete batch (partial=false) when all complete before their timeout", () => { const deliver = vi.fn(); const mgr = new GroupJoinManager(deliver, 30_000); mgr.registerGroup("g", ["a", "b", "c"]); mgr.onAgentComplete(makeRecord("a")); vi.advanceTimersByTime(30_000); // partial: 'a' expect(deliver).toHaveBeenCalledTimes(1); mgr.onAgentComplete(makeRecord("b")); expect(mgr.onAgentComplete(makeRecord("c"))).toBe("delivered"); expect(deliver).toHaveBeenCalledTimes(2); expect(deliver.mock.calls[1][0].map((r: AgentRecord) => r.id).sort()).toEqual(["b", "c"]); expect(deliver.mock.calls[1][1]).toBe(false); }); it("returns 'pass' for late completions arriving after a group is already delivered", () => { const deliver = vi.fn(); const mgr = new GroupJoinManager(deliver); mgr.registerGroup("g", ["a", "b"]); mgr.onAgentComplete(makeRecord("a")); mgr.onAgentComplete(makeRecord("b")); // full delivery expect(deliver).toHaveBeenCalledTimes(1); // A duplicate/late completion must not trigger a second delivery expect(mgr.onAgentComplete(makeRecord("a"))).toBe("pass"); expect(deliver).toHaveBeenCalledTimes(1); }); it("dispose() clears pending timers so a partial delivery never fires post-dispose", () => { const deliver = vi.fn(); const mgr = new GroupJoinManager(deliver, 30_000); mgr.registerGroup("g", ["a", "b"]); mgr.onAgentComplete(makeRecord("a")); // arms 30s timeout mgr.dispose(); vi.advanceTimersByTime(60_000); expect(deliver).not.toHaveBeenCalled(); expect(mgr.isGrouped("a")).toBe(false); expect(mgr.isGrouped("b")).toBe(false); }); });