From c7af05776fe77f4166b5f47e25e6f8a3b859a2ee Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 10 May 2026 13:09:33 +0100 Subject: [PATCH] test: clear gateway agent event broad matchers --- src/gateway/server-chat.agent-events.test.ts | 321 ++++++++++--------- 1 file changed, 178 insertions(+), 143 deletions(-) diff --git a/src/gateway/server-chat.agent-events.test.ts b/src/gateway/server-chat.agent-events.test.ts index 326592592633..a81be82d46bf 100644 --- a/src/gateway/server-chat.agent-events.test.ts +++ b/src/gateway/server-chat.agent-events.test.ts @@ -155,6 +155,44 @@ describe("agent event handler", () => { return call; } + function requireRecord(value: unknown, label: string): Record { + expect(typeof value).toBe("object"); + expect(value).not.toBeNull(); + if (typeof value !== "object" || value === null) { + throw new Error(`${label} was not an object`); + } + return value as Record; + } + + function expectRecordFields(record: Record, fields: Record) { + for (const [key, value] of Object.entries(fields)) { + expect(record[key]).toEqual(value); + } + } + + function expectPayloadFields(value: unknown, fields: Record) { + expectRecordFields(requireRecord(value, "event payload"), fields); + } + + function expectPayloadDataFields(value: unknown, fields: Record) { + const payload = requireRecord(value, "event payload"); + expectRecordFields(requireRecord(payload.data, "event payload data"), fields); + } + + function requireMockPayload( + mock: ReturnType, + index: number, + payloadIndex: number, + label: string, + ) { + const call = mock.mock.calls[index]; + expect(call).toBeDefined(); + if (!call) { + throw new Error(`missing ${label} call ${index + 1}`); + } + return requireRecord(call[payloadIndex], label); + } + const FALLBACK_LIFECYCLE_DATA = { phase: "fallback", selectedProvider: "fireworks", @@ -816,15 +854,12 @@ describe("agent event handler", () => { const nodeToolCalls = nodeSendToSession.mock.calls.filter(([, event]) => event === "agent"); expect(nodeToolCalls).toHaveLength(1); - expect(nodeToolCalls[0]?.[2]).toEqual( - expect.objectContaining({ - stream: "tool", - data: expect.objectContaining({ - phase: "start", - name: "read", - }), - }), - ); + const payload = requireRecord(nodeToolCalls[0]?.[2], "node tool payload"); + expect(payload.stream).toBe("tool"); + expectRecordFields(requireRecord(payload.data, "node tool payload data"), { + phase: "start", + name: "read", + }); resetAgentRunContextForTest(); }); @@ -897,32 +932,31 @@ describe("agent event handler", () => { }); expect(broadcastToConnIds).toHaveBeenCalledTimes(1); - expect(broadcastToConnIds).toHaveBeenCalledWith( - "session.tool", - expect.objectContaining({ - runId: "run-session-tool", - sessionKey: "session-1", - spawnedBy: "agent:main:main", - spawnedWorkspaceDir: "/tmp/subagent", - forkedFromParent: true, - spawnDepth: 2, - subagentRole: "orchestrator", - subagentControlScope: "children", - lastThreadId: 42, - fastMode: true, - verboseLevel: "on", - stream: "tool", - ts: 1_234, - data: expect.objectContaining({ - phase: "start", - name: "exec", - toolCallId: "tool-session-1", - args: { command: "echo hi" }, - }), - }), - new Set(["conn-session"]), - { dropIfSlow: true }, - ); + expect(broadcastToConnIds.mock.calls[0]?.[0]).toBe("session.tool"); + const sessionToolPayload = requireMockPayload(broadcastToConnIds, 0, 1, "session tool payload"); + expectRecordFields(sessionToolPayload, { + runId: "run-session-tool", + sessionKey: "session-1", + spawnedBy: "agent:main:main", + spawnedWorkspaceDir: "/tmp/subagent", + forkedFromParent: true, + spawnDepth: 2, + subagentRole: "orchestrator", + subagentControlScope: "children", + lastThreadId: 42, + fastMode: true, + verboseLevel: "on", + stream: "tool", + ts: 1_234, + }); + expectRecordFields(requireRecord(sessionToolPayload.data, "session tool payload data"), { + phase: "start", + name: "exec", + toolCallId: "tool-session-1", + args: { command: "echo hi" }, + }); + expect(broadcastToConnIds.mock.calls[0]?.[2]).toEqual(new Set(["conn-session"])); + expect(broadcastToConnIds.mock.calls[0]?.[3]).toEqual({ dropIfSlow: true }); resetAgentRunContextForTest(); }); @@ -963,31 +997,30 @@ describe("agent event handler", () => { }); expect(broadcastToConnIds).toHaveBeenCalledTimes(1); - expect(broadcastToConnIds).toHaveBeenCalledWith( - "agent", - expect.objectContaining({ - runId: "run-tool-owner", - sessionKey: "session-1", - spawnedBy: "agent:main:main", - spawnedWorkspaceDir: "/tmp/subagent", - forkedFromParent: true, - spawnDepth: 2, - subagentRole: "orchestrator", - subagentControlScope: "children", - lastThreadId: 42, - fastMode: true, - verboseLevel: "on", - stream: "tool", - ts: 1_234, - data: expect.objectContaining({ - phase: "start", - name: "exec", - toolCallId: "tool-run-1", - args: { command: "echo hi" }, - }), - }), - new Set(["conn-run"]), - ); + expect(broadcastToConnIds.mock.calls[0]?.[0]).toBe("agent"); + const runToolPayload = requireMockPayload(broadcastToConnIds, 0, 1, "run tool payload"); + expectRecordFields(runToolPayload, { + runId: "run-tool-owner", + sessionKey: "session-1", + spawnedBy: "agent:main:main", + spawnedWorkspaceDir: "/tmp/subagent", + forkedFromParent: true, + spawnDepth: 2, + subagentRole: "orchestrator", + subagentControlScope: "children", + lastThreadId: 42, + fastMode: true, + verboseLevel: "on", + stream: "tool", + ts: 1_234, + }); + expectRecordFields(requireRecord(runToolPayload.data, "run tool payload data"), { + phase: "start", + name: "exec", + toolCallId: "tool-run-1", + args: { command: "echo hi" }, + }); + expect(broadcastToConnIds.mock.calls[0]?.[2]).toEqual(new Set(["conn-run"])); resetAgentRunContextForTest(); }); @@ -1026,31 +1059,30 @@ describe("agent event handler", () => { }, }); - expect(nodeSendToSession).toHaveBeenCalledWith( - "session-1", - "agent", - expect.objectContaining({ - runId: "run-tool-node", - sessionKey: "session-1", - spawnedBy: "agent:main:main", - spawnedWorkspaceDir: "/tmp/subagent", - forkedFromParent: true, - spawnDepth: 2, - subagentRole: "orchestrator", - subagentControlScope: "children", - lastThreadId: 42, - fastMode: true, - verboseLevel: "on", - stream: "tool", - ts: 1_234, - data: expect.objectContaining({ - phase: "start", - name: "exec", - toolCallId: "tool-node-1", - args: { command: "echo hi" }, - }), - }), - ); + expect(nodeSendToSession.mock.calls[0]?.[0]).toBe("session-1"); + expect(nodeSendToSession.mock.calls[0]?.[1]).toBe("agent"); + const nodeToolPayload = requireMockPayload(nodeSendToSession, 0, 2, "node tool payload"); + expectRecordFields(nodeToolPayload, { + runId: "run-tool-node", + sessionKey: "session-1", + spawnedBy: "agent:main:main", + spawnedWorkspaceDir: "/tmp/subagent", + forkedFromParent: true, + spawnDepth: 2, + subagentRole: "orchestrator", + subagentControlScope: "children", + lastThreadId: 42, + fastMode: true, + verboseLevel: "on", + stream: "tool", + ts: 1_234, + }); + expectRecordFields(requireRecord(nodeToolPayload.data, "node tool payload data"), { + phase: "start", + name: "exec", + toolCallId: "tool-node-1", + args: { command: "echo hi" }, + }); resetAgentRunContextForTest(); }); @@ -1091,25 +1123,29 @@ describe("agent event handler", () => { ([event]) => event === "sessions.changed", ); expect(sessionsChangedCalls).toHaveLength(2); - expect(sessionsChangedCalls[1]?.[1]).toEqual( - expect.objectContaining({ - sessionKey: "session-finished", - phase: "end", - status: "done", - startedAt: 900, - endedAt: 1_700, - runtimeMs: 800, - updatedAt: 1_700, - abortedLastRun: false, - }), - ); - expect(persistGatewaySessionLifecycleEventMock).toHaveBeenCalledWith({ + expectPayloadFields(sessionsChangedCalls[1]?.[1], { sessionKey: "session-finished", - event: expect.objectContaining({ - runId: "run-finished", - data: expect.objectContaining({ phase: "end" }), - }), + phase: "end", + status: "done", + startedAt: 900, + endedAt: 1_700, + runtimeMs: 800, + updatedAt: 1_700, + abortedLastRun: false, }); + const persistParams = requireRecord( + persistGatewaySessionLifecycleEventMock.mock.calls + .map((call) => call[0]) + .find((params) => { + const event = (params as { event?: { data?: { phase?: string } } } | undefined)?.event; + return event?.data?.phase === "end"; + }), + "persist lifecycle params", + ); + expect(persistParams.sessionKey).toBe("session-finished"); + const persistEvent = requireRecord(persistParams.event, "persist lifecycle event"); + expect(persistEvent.runId).toBe("run-finished"); + expect(requireRecord(persistEvent.data, "persist lifecycle event data").phase).toBe("end"); resetAgentRunContextForTest(); }); @@ -1182,30 +1218,28 @@ describe("agent event handler", () => { }, }); - expect(broadcastToConnIds).toHaveBeenCalledWith( - "sessions.changed", - expect.objectContaining({ - sessionKey: "session-finished", - phase: "end", - spawnedBy: "agent:main:main", - spawnedWorkspaceDir: "/tmp/subagent", - forkedFromParent: true, - spawnDepth: 2, - subagentRole: "orchestrator", - subagentControlScope: "children", - fastMode: true, - sendPolicy: "deny", - verboseLevel: "on", - responseUsage: "full", - totalTokens: 42, - totalTokensFresh: true, - contextTokens: 21, - estimatedCostUsd: 0.12, - lastThreadId: 42, - }), - new Set(["conn-session"]), - { dropIfSlow: true }, - ); + expect(broadcastToConnIds.mock.calls[0]?.[0]).toBe("sessions.changed"); + expectPayloadFields(broadcastToConnIds.mock.calls[0]?.[1], { + sessionKey: "session-finished", + phase: "end", + spawnedBy: "agent:main:main", + spawnedWorkspaceDir: "/tmp/subagent", + forkedFromParent: true, + spawnDepth: 2, + subagentRole: "orchestrator", + subagentControlScope: "children", + fastMode: true, + sendPolicy: "deny", + verboseLevel: "on", + responseUsage: "full", + totalTokens: 42, + totalTokensFresh: true, + contextTokens: 21, + estimatedCostUsd: 0.12, + lastThreadId: 42, + }); + expect(broadcastToConnIds.mock.calls[0]?.[2]).toEqual(new Set(["conn-session"])); + expect(broadcastToConnIds.mock.calls[0]?.[3]).toEqual({ dropIfSlow: true }); }); it("keeps tool output for Control UI recipients when verbose is on", () => { @@ -1507,7 +1541,7 @@ describe("agent event handler", () => { ([, payload]) => (payload as { state?: string }).state === "error", ); expect(chatErrors).toHaveLength(1); - expect(chatErrors[0]?.[1]).toMatchObject({ + expectPayloadFields(chatErrors[0]?.[1], { runId: "run-chat-send", sessionKey: "session-chat-send", state: "error", @@ -1540,13 +1574,14 @@ describe("agent event handler", () => { expect(chatBroadcastCalls(broadcast)).toHaveLength(0); expect(broadcast.mock.calls.some(([event]) => event === "agent")).toBe(false); expect(nodeSendToSession).not.toHaveBeenCalled(); - expect(persistGatewaySessionLifecycleEventMock).toHaveBeenCalledWith({ - sessionKey: "session-hidden", - event: expect.objectContaining({ - runId: "run-hidden", - data: expect.objectContaining({ phase: "end" }), - }), - }); + const persistParams = requireRecord( + persistGatewaySessionLifecycleEventMock.mock.calls[0]?.[0], + "persist lifecycle params", + ); + expect(persistParams.sessionKey).toBe("session-hidden"); + const persistEvent = requireRecord(persistParams.event, "persist lifecycle event"); + expect(persistEvent.runId).toBe("run-hidden"); + expect(requireRecord(persistEvent.data, "persist lifecycle event data").phase).toBe("end"); }); it("uses agent event sessionKey when run-context lookup cannot resolve", () => { @@ -1697,7 +1732,7 @@ describe("agent event handler", () => { const chatCalls = chatBroadcastCalls(broadcast); expect(chatCalls.length).toBeGreaterThanOrEqual(1); const [, payload] = chatCalls[0]; - expect(payload).toMatchObject({ + expectPayloadFields(payload, { sessionKey: "agent:coder:subagent:abc", spawnedBy: "agent:conductor:task:parent-1", state: "delta", @@ -1705,7 +1740,7 @@ describe("agent event handler", () => { const nodeCalls = sessionChatCalls(nodeSendToSession); expect(nodeCalls.length).toBeGreaterThanOrEqual(1); - expect(nodeCalls[0][2]).toMatchObject({ + expectPayloadFields(nodeCalls[0]?.[2], { spawnedBy: "agent:conductor:task:parent-1", }); }); @@ -1748,7 +1783,7 @@ describe("agent event handler", () => { chatCalls.find(([, p]) => p.state === "final"), "final chat call", ); - expect(finalCall[1]).toMatchObject({ + expectPayloadFields(finalCall[1], { sessionKey: "agent:coder:subagent:abc", spawnedBy: "agent:conductor:task:parent-1", state: "final", @@ -1839,7 +1874,7 @@ describe("agent event handler", () => { const agentCalls = broadcast.mock.calls.filter(([event]) => event === "agent"); expect(agentCalls.length).toBeGreaterThanOrEqual(1); - expect(agentCalls[0][1]).toMatchObject({ + expectPayloadFields(agentCalls[0]?.[1], { sessionKey: "agent:coder:subagent:xyz", spawnedBy: "agent:conductor:task:parent-2", }); @@ -1886,7 +1921,7 @@ describe("agent event handler", () => { chatCalls.find(([, p]) => p.state === "error"), "error chat call", ); - expect(errorCall[1]).toMatchObject({ + expectPayloadFields(errorCall[1], { sessionKey: "agent:coder:subagent:err", spawnedBy: "agent:conductor:task:parent-err", state: "error", @@ -1951,7 +1986,7 @@ describe("agent event handler", () => { ), "flushed delta chat call", ); - expect(flushedDelta[1]).toMatchObject({ + expectPayloadFields(flushedDelta[1], { spawnedBy: "agent:conductor:task:parent-flush", }); @@ -1994,11 +2029,11 @@ describe("agent event handler", () => { agentCalls.find(([, p]) => p.stream === "error" && p.data?.reason === "seq gap"), "seq gap error agent call", ); - expect(gapError[1]).toMatchObject({ + expectPayloadFields(gapError[1], { sessionKey: "agent:coder:subagent:gap", spawnedBy: "agent:conductor:task:parent-gap", - data: { reason: "seq gap", expected: 2, received: 5 }, }); + expectPayloadDataFields(gapError[1], { reason: "seq gap", expected: 2, received: 5 }); resetAgentRunContextForTest(); }); @@ -2051,7 +2086,7 @@ describe("agent event handler", () => { // All broadcasts still have correct spawnedBy const chatCalls = chatBroadcastCalls(broadcast); for (const [, payload] of chatCalls) { - expect(payload).toMatchObject({ + expectPayloadFields(payload, { spawnedBy: "agent:conductor:task:parent-cache", }); }