refactor(test): share server chat event harness

This commit is contained in:
Peter Steinberger
2026-02-15 15:44:14 +00:00
parent e683353cab
commit e687ad15ac

View File

@@ -7,15 +7,18 @@ import {
} from "./server-chat.js"; } from "./server-chat.js";
describe("agent event handler", () => { describe("agent event handler", () => {
it("emits chat delta for assistant text-only events", () => { function createHarness(params?: {
const nowSpy = vi.spyOn(Date, "now").mockReturnValue(1_000); now?: number;
resolveSessionKeyForRun?: (runId: string) => string | undefined;
}) {
const nowSpy =
params?.now === undefined ? undefined : vi.spyOn(Date, "now").mockReturnValue(params.now);
const broadcast = vi.fn(); const broadcast = vi.fn();
const broadcastToConnIds = vi.fn(); const broadcastToConnIds = vi.fn();
const nodeSendToSession = vi.fn(); const nodeSendToSession = vi.fn();
const agentRunSeq = new Map<string, number>(); const agentRunSeq = new Map<string, number>();
const chatRunState = createChatRunState(); const chatRunState = createChatRunState();
const toolEventRecipients = createToolEventRecipientRegistry(); const toolEventRecipients = createToolEventRecipientRegistry();
chatRunState.registry.add("run-1", { sessionKey: "session-1", clientRunId: "client-1" });
const handler = createAgentEventHandler({ const handler = createAgentEventHandler({
broadcast, broadcast,
@@ -23,11 +26,29 @@ describe("agent event handler", () => {
nodeSendToSession, nodeSendToSession,
agentRunSeq, agentRunSeq,
chatRunState, chatRunState,
resolveSessionKeyForRun: () => undefined, resolveSessionKeyForRun: params?.resolveSessionKeyForRun ?? (() => undefined),
clearAgentRunContext: vi.fn(), clearAgentRunContext: vi.fn(),
toolEventRecipients, toolEventRecipients,
}); });
return {
nowSpy,
broadcast,
broadcastToConnIds,
nodeSendToSession,
agentRunSeq,
chatRunState,
toolEventRecipients,
handler,
};
}
it("emits chat delta for assistant text-only events", () => {
const { broadcast, nodeSendToSession, chatRunState, handler, nowSpy } = createHarness({
now: 1_000,
});
chatRunState.registry.add("run-1", { sessionKey: "session-1", clientRunId: "client-1" });
handler({ handler({
runId: "run-1", runId: "run-1",
seq: 1, seq: 1,
@@ -46,29 +67,14 @@ describe("agent event handler", () => {
expect(payload.message?.content?.[0]?.text).toBe("Hello world"); expect(payload.message?.content?.[0]?.text).toBe("Hello world");
const sessionChatCalls = nodeSendToSession.mock.calls.filter(([, event]) => event === "chat"); const sessionChatCalls = nodeSendToSession.mock.calls.filter(([, event]) => event === "chat");
expect(sessionChatCalls).toHaveLength(1); expect(sessionChatCalls).toHaveLength(1);
nowSpy.mockRestore(); nowSpy?.mockRestore();
}); });
it("does not emit chat delta for NO_REPLY streaming text", () => { it("does not emit chat delta for NO_REPLY streaming text", () => {
const nowSpy = vi.spyOn(Date, "now").mockReturnValue(1_000); const { broadcast, nodeSendToSession, chatRunState, handler, nowSpy } = createHarness({
const broadcast = vi.fn(); now: 1_000,
const broadcastToConnIds = vi.fn();
const nodeSendToSession = vi.fn();
const agentRunSeq = new Map<string, number>();
const chatRunState = createChatRunState();
const toolEventRecipients = createToolEventRecipientRegistry();
chatRunState.registry.add("run-1", { sessionKey: "session-1", clientRunId: "client-1" });
const handler = createAgentEventHandler({
broadcast,
broadcastToConnIds,
nodeSendToSession,
agentRunSeq,
chatRunState,
resolveSessionKeyForRun: () => undefined,
clearAgentRunContext: vi.fn(),
toolEventRecipients,
}); });
chatRunState.registry.add("run-1", { sessionKey: "session-1", clientRunId: "client-1" });
handler({ handler({
runId: "run-1", runId: "run-1",
@@ -82,29 +88,14 @@ describe("agent event handler", () => {
expect(chatCalls).toHaveLength(0); expect(chatCalls).toHaveLength(0);
const sessionChatCalls = nodeSendToSession.mock.calls.filter(([, event]) => event === "chat"); const sessionChatCalls = nodeSendToSession.mock.calls.filter(([, event]) => event === "chat");
expect(sessionChatCalls).toHaveLength(0); expect(sessionChatCalls).toHaveLength(0);
nowSpy.mockRestore(); nowSpy?.mockRestore();
}); });
it("does not include NO_REPLY text in chat final message", () => { it("does not include NO_REPLY text in chat final message", () => {
const nowSpy = vi.spyOn(Date, "now").mockReturnValue(2_000); const { broadcast, nodeSendToSession, chatRunState, handler, nowSpy } = createHarness({
const broadcast = vi.fn(); now: 2_000,
const broadcastToConnIds = vi.fn();
const nodeSendToSession = vi.fn();
const agentRunSeq = new Map<string, number>();
const chatRunState = createChatRunState();
const toolEventRecipients = createToolEventRecipientRegistry();
chatRunState.registry.add("run-2", { sessionKey: "session-2", clientRunId: "client-2" });
const handler = createAgentEventHandler({
broadcast,
broadcastToConnIds,
nodeSendToSession,
agentRunSeq,
chatRunState,
resolveSessionKeyForRun: () => undefined,
clearAgentRunContext: vi.fn(),
toolEventRecipients,
}); });
chatRunState.registry.add("run-2", { sessionKey: "session-2", clientRunId: "client-2" });
handler({ handler({
runId: "run-2", runId: "run-2",
@@ -128,33 +119,16 @@ describe("agent event handler", () => {
expect(payload.message).toBeUndefined(); expect(payload.message).toBeUndefined();
const sessionChatCalls = nodeSendToSession.mock.calls.filter(([, event]) => event === "chat"); const sessionChatCalls = nodeSendToSession.mock.calls.filter(([, event]) => event === "chat");
expect(sessionChatCalls).toHaveLength(1); expect(sessionChatCalls).toHaveLength(1);
nowSpy.mockRestore(); nowSpy?.mockRestore();
}); });
it("cleans up agent run sequence tracking when lifecycle completes", () => { it("cleans up agent run sequence tracking when lifecycle completes", () => {
const nowSpy = vi.spyOn(Date, "now").mockReturnValue(2_500); const { agentRunSeq, chatRunState, handler, nowSpy } = createHarness({ now: 2_500 });
const broadcast = vi.fn();
const broadcastToConnIds = vi.fn();
const nodeSendToSession = vi.fn();
const agentRunSeq = new Map<string, number>();
const chatRunState = createChatRunState();
const toolEventRecipients = createToolEventRecipientRegistry();
chatRunState.registry.add("run-cleanup", { chatRunState.registry.add("run-cleanup", {
sessionKey: "session-cleanup", sessionKey: "session-cleanup",
clientRunId: "client-cleanup", clientRunId: "client-cleanup",
}); });
const handler = createAgentEventHandler({
broadcast,
broadcastToConnIds,
nodeSendToSession,
agentRunSeq,
chatRunState,
resolveSessionKeyForRun: () => undefined,
clearAgentRunContext: vi.fn(),
toolEventRecipients,
});
handler({ handler({
runId: "run-cleanup", runId: "run-cleanup",
seq: 1, seq: 1,
@@ -174,31 +148,17 @@ describe("agent event handler", () => {
expect(agentRunSeq.has("run-cleanup")).toBe(false); expect(agentRunSeq.has("run-cleanup")).toBe(false);
expect(agentRunSeq.has("client-cleanup")).toBe(false); expect(agentRunSeq.has("client-cleanup")).toBe(false);
nowSpy.mockRestore(); nowSpy?.mockRestore();
}); });
it("routes tool events only to registered recipients when verbose is enabled", () => { it("routes tool events only to registered recipients when verbose is enabled", () => {
const broadcast = vi.fn(); const { broadcast, broadcastToConnIds, toolEventRecipients, handler } = createHarness({
const broadcastToConnIds = vi.fn(); resolveSessionKeyForRun: () => "session-1",
const nodeSendToSession = vi.fn(); });
const agentRunSeq = new Map<string, number>();
const chatRunState = createChatRunState();
const toolEventRecipients = createToolEventRecipientRegistry();
registerAgentRunContext("run-tool", { sessionKey: "session-1", verboseLevel: "on" }); registerAgentRunContext("run-tool", { sessionKey: "session-1", verboseLevel: "on" });
toolEventRecipients.add("run-tool", "conn-1"); toolEventRecipients.add("run-tool", "conn-1");
const handler = createAgentEventHandler({
broadcast,
broadcastToConnIds,
nodeSendToSession,
agentRunSeq,
chatRunState,
resolveSessionKeyForRun: () => "session-1",
clearAgentRunContext: vi.fn(),
toolEventRecipients,
});
handler({ handler({
runId: "run-tool", runId: "run-tool",
seq: 1, seq: 1,
@@ -213,27 +173,13 @@ describe("agent event handler", () => {
}); });
it("broadcasts tool events to WS recipients even when verbose is off, but skips node send", () => { it("broadcasts tool events to WS recipients even when verbose is off, but skips node send", () => {
const broadcast = vi.fn(); const { broadcastToConnIds, nodeSendToSession, toolEventRecipients, handler } = createHarness({
const broadcastToConnIds = vi.fn(); resolveSessionKeyForRun: () => "session-1",
const nodeSendToSession = vi.fn(); });
const agentRunSeq = new Map<string, number>();
const chatRunState = createChatRunState();
const toolEventRecipients = createToolEventRecipientRegistry();
registerAgentRunContext("run-tool-off", { sessionKey: "session-1", verboseLevel: "off" }); registerAgentRunContext("run-tool-off", { sessionKey: "session-1", verboseLevel: "off" });
toolEventRecipients.add("run-tool-off", "conn-1"); toolEventRecipients.add("run-tool-off", "conn-1");
const handler = createAgentEventHandler({
broadcast,
broadcastToConnIds,
nodeSendToSession,
agentRunSeq,
chatRunState,
resolveSessionKeyForRun: () => "session-1",
clearAgentRunContext: vi.fn(),
toolEventRecipients,
});
handler({ handler({
runId: "run-tool-off", runId: "run-tool-off",
seq: 1, seq: 1,
@@ -251,27 +197,13 @@ describe("agent event handler", () => {
}); });
it("strips tool output when verbose is on", () => { it("strips tool output when verbose is on", () => {
const broadcast = vi.fn(); const { broadcastToConnIds, toolEventRecipients, handler } = createHarness({
const broadcastToConnIds = vi.fn(); resolveSessionKeyForRun: () => "session-1",
const nodeSendToSession = vi.fn(); });
const agentRunSeq = new Map<string, number>();
const chatRunState = createChatRunState();
const toolEventRecipients = createToolEventRecipientRegistry();
registerAgentRunContext("run-tool-on", { sessionKey: "session-1", verboseLevel: "on" }); registerAgentRunContext("run-tool-on", { sessionKey: "session-1", verboseLevel: "on" });
toolEventRecipients.add("run-tool-on", "conn-1"); toolEventRecipients.add("run-tool-on", "conn-1");
const handler = createAgentEventHandler({
broadcast,
broadcastToConnIds,
nodeSendToSession,
agentRunSeq,
chatRunState,
resolveSessionKeyForRun: () => "session-1",
clearAgentRunContext: vi.fn(),
toolEventRecipients,
});
handler({ handler({
runId: "run-tool-on", runId: "run-tool-on",
seq: 1, seq: 1,
@@ -294,27 +226,13 @@ describe("agent event handler", () => {
}); });
it("keeps tool output when verbose is full", () => { it("keeps tool output when verbose is full", () => {
const broadcast = vi.fn(); const { broadcastToConnIds, toolEventRecipients, handler } = createHarness({
const broadcastToConnIds = vi.fn(); resolveSessionKeyForRun: () => "session-1",
const nodeSendToSession = vi.fn(); });
const agentRunSeq = new Map<string, number>();
const chatRunState = createChatRunState();
const toolEventRecipients = createToolEventRecipientRegistry();
registerAgentRunContext("run-tool-full", { sessionKey: "session-1", verboseLevel: "full" }); registerAgentRunContext("run-tool-full", { sessionKey: "session-1", verboseLevel: "full" });
toolEventRecipients.add("run-tool-full", "conn-1"); toolEventRecipients.add("run-tool-full", "conn-1");
const handler = createAgentEventHandler({
broadcast,
broadcastToConnIds,
nodeSendToSession,
agentRunSeq,
chatRunState,
resolveSessionKeyForRun: () => "session-1",
clearAgentRunContext: vi.fn(),
toolEventRecipients,
});
const result = { content: [{ type: "text", text: "secret" }] }; const result = { content: [{ type: "text", text: "secret" }] };
handler({ handler({
runId: "run-tool-full", runId: "run-tool-full",