From 8edfa5ee5250f393f35f1e11dc7849df0ef80df0 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 3 Aug 2026 00:02:57 -0700 Subject: [PATCH] test(gateway): consolidate deleted-agent session fixtures (#118549) Co-authored-by: Peter Steinberger --- .../deleted-agent-guard.test-helpers.ts | 16 +++--- .../sessions.send-deleted-agent.test.ts | 50 ------------------- .../sessions.send-followup-status.test.ts | 50 ++++++++++++++++--- 3 files changed, 49 insertions(+), 67 deletions(-) delete mode 100644 src/gateway/server-methods/sessions.send-deleted-agent.test.ts diff --git a/src/gateway/server-methods/deleted-agent-guard.test-helpers.ts b/src/gateway/server-methods/deleted-agent-guard.test-helpers.ts index 60e65cdfc564..e7f788455e10 100644 --- a/src/gateway/server-methods/deleted-agent-guard.test-helpers.ts +++ b/src/gateway/server-methods/deleted-agent-guard.test-helpers.ts @@ -5,22 +5,20 @@ import { vi } from "vitest"; const deletedAgentSessionMocks = vi.hoisted(() => ({ loadSessionEntry: vi.fn(), + loadSessionEntryReadOnly: vi.fn(), resolveDeletedAgentIdFromSessionKey: vi.fn(), })); -vi.mock("../session-utils.js", async () => { - const actual = await vi.importActual("../session-utils.js"); - return { - ...actual, - loadSessionEntry: deletedAgentSessionMocks.loadSessionEntry, - resolveDeletedAgentIdFromSessionKey: - deletedAgentSessionMocks.resolveDeletedAgentIdFromSessionKey, - }; -}); +vi.mock("../session-utils.js", () => ({ + loadSessionEntry: deletedAgentSessionMocks.loadSessionEntry, + loadSessionEntryReadOnly: deletedAgentSessionMocks.loadSessionEntryReadOnly, + resolveDeletedAgentIdFromSessionKey: deletedAgentSessionMocks.resolveDeletedAgentIdFromSessionKey, +})); /** Resets mocked deleted-agent session lookups between tests. */ export function resetDeletedAgentSessionMocks(): void { deletedAgentSessionMocks.loadSessionEntry.mockReset(); + deletedAgentSessionMocks.loadSessionEntryReadOnly.mockReset(); deletedAgentSessionMocks.resolveDeletedAgentIdFromSessionKey.mockReset(); } diff --git a/src/gateway/server-methods/sessions.send-deleted-agent.test.ts b/src/gateway/server-methods/sessions.send-deleted-agent.test.ts deleted file mode 100644 index bc7446a212ad..000000000000 --- a/src/gateway/server-methods/sessions.send-deleted-agent.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Tests that session send rejects sessions whose configured agent was deleted. - */ - -import { expectDefined } from "@openclaw/normalization-core"; -import { beforeEach, describe, expect, it, vi } from "vitest"; -import { ErrorCodes } from "../../../packages/gateway-protocol/src/index.js"; -import { - mockDeletedAgentSession, - resetDeletedAgentSessionMocks, -} from "./deleted-agent-guard.test-helpers.js"; -import { sessionMessagingHandlers } from "./sessions-messaging.js"; -import type { GatewayRequestContext, RespondFn } from "./types.js"; - -describe("sessions.send / sessions.steer deleted-agent guard", () => { - beforeEach(() => { - resetDeletedAgentSessionMocks(); - }); - - for (const method of ["sessions.send", "sessions.steer"] as const) { - it(`${method} rejects keys belonging to a deleted agent`, async () => { - const orphanKey = mockDeletedAgentSession(); - - const respond = vi.fn() as unknown as RespondFn; - const context = { - chatAbortControllers: new Map(), - broadcastToConnIds: vi.fn(), - getSessionEventSubscriberConnIds: () => new Set(), - getRuntimeConfig: () => ({}), - } as unknown as GatewayRequestContext; - - await expectDefined( - sessionMessagingHandlers[method], - "sessionMessagingHandlers[method] test invariant", - )({ - req: { id: "req-1" } as never, - params: { key: orphanKey, message: "hi" }, - respond, - context, - client: null, - isWebchatConnect: () => false, - }); - - expect(respond).toHaveBeenCalledWith(false, undefined, { - code: ErrorCodes.INVALID_REQUEST, - message: 'Agent "deleted-agent" no longer exists in configuration', - }); - }); - } -}); diff --git a/src/gateway/server-methods/sessions.send-followup-status.test.ts b/src/gateway/server-methods/sessions.send-followup-status.test.ts index 89bab18ad2e6..82cbf63a43cb 100644 --- a/src/gateway/server-methods/sessions.send-followup-status.test.ts +++ b/src/gateway/server-methods/sessions.send-followup-status.test.ts @@ -11,8 +11,10 @@ import { expectSubagentFollowupReactivation } from "./subagent-followup.test-hel import type { GatewayRequestContext, RespondFn } from "./types.js"; const loadSessionEntryMock = vi.fn(); +const loadSessionEntryReadOnlyMock = vi.fn(); const readSessionMessageCountAsyncMock = vi.fn(); const loadGatewaySessionRowMock = vi.fn(); +const resolveDeletedAgentIdFromSessionKeyMock = vi.fn(); const getLatestSubagentRunByChildSessionKeyMock = vi.fn(); const replaceSubagentRunAfterSteerMock = vi.fn(); const chatSendMock = vi.fn(); @@ -45,14 +47,13 @@ vi.mock("../../auto-reply/reply/queue/cleanup.js", async () => { }; }); -vi.mock("../session-utils.js", async () => { - const actual = await vi.importActual("../session-utils.js"); - return { - ...actual, - loadSessionEntry: (...args: unknown[]) => loadSessionEntryMock(...args), - loadGatewaySessionRow: (...args: unknown[]) => loadGatewaySessionRowMock(...args), - }; -}); +vi.mock("../session-utils.js", () => ({ + loadSessionEntry: (...args: unknown[]) => loadSessionEntryMock(...args), + loadSessionEntryReadOnly: (...args: unknown[]) => loadSessionEntryReadOnlyMock(...args), + loadGatewaySessionRow: (...args: unknown[]) => loadGatewaySessionRowMock(...args), + resolveDeletedAgentIdFromSessionKey: (...args: unknown[]) => + resolveDeletedAgentIdFromSessionKeyMock(...args), +})); vi.mock("../session-transcript-readers.js", async () => { const actual = await vi.importActual( @@ -112,8 +113,10 @@ function createRequestContext(overrides: Record = {}): GatewayR describe("sessions.send completed subagent follow-up status", () => { beforeEach(() => { loadSessionEntryMock.mockReset(); + loadSessionEntryReadOnlyMock.mockReset(); readSessionMessageCountAsyncMock.mockReset().mockResolvedValue(0); loadGatewaySessionRowMock.mockReset(); + resolveDeletedAgentIdFromSessionKeyMock.mockReset().mockReturnValue(null); getLatestSubagentRunByChildSessionKeyMock.mockReset(); replaceSubagentRunAfterSteerMock.mockReset(); chatSendMock.mockReset(); @@ -137,6 +140,37 @@ describe("sessions.send completed subagent follow-up status", () => { ); }); + for (const method of ["sessions.send", "sessions.steer"] as const) { + it(`${method} rejects keys belonging to a deleted agent`, async () => { + const orphanKey = "agent:deleted-agent:main"; + loadSessionEntryMock.mockReturnValue({ + cfg: {}, + canonicalKey: orphanKey, + storePath: "/tmp/sessions.json", + entry: { sessionId: "sess-orphan" }, + }); + resolveDeletedAgentIdFromSessionKeyMock.mockReturnValue("deleted-agent"); + + const respondMock = vi.fn(); + await expectDefined( + sessionMessagingHandlers[method], + "sessionMessagingHandlers[method] test invariant", + )({ + req: { id: "req-deleted-agent" } as never, + params: { key: orphanKey, message: "hi" }, + respond: respondMock as unknown as RespondFn, + context: createRequestContext(), + client: null, + isWebchatConnect: () => false, + }); + + expect(respondMock).toHaveBeenCalledWith(false, undefined, { + code: ErrorCodes.INVALID_REQUEST, + message: 'Agent "deleted-agent" no longer exists in configuration', + }); + }); + } + it("reactivates completed subagent sessions before broadcasting sessions.changed", async () => { const childSessionKey = "agent:main:subagent:followup"; const completedRun = {