diff --git a/extensions/telegram/src/bot-message-context.thread-binding.test.ts b/extensions/telegram/src/bot-message-context.thread-binding.test.ts index de4a3d08ad7..70df2323385 100644 --- a/extensions/telegram/src/bot-message-context.thread-binding.test.ts +++ b/extensions/telegram/src/bot-message-context.thread-binding.test.ts @@ -1,35 +1,47 @@ import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; -const hoisted = vi.hoisted(() => { - const resolveByConversationMock = vi.fn(); - const recordInboundSessionMock = vi.fn().mockResolvedValue(undefined); - const touchMock = vi.fn(); - return { - recordInboundSessionMock, - resolveByConversationMock, - touchMock, - }; -}); +const recordInboundSessionMock = vi.hoisted(() => vi.fn().mockResolvedValue(undefined)); +const resolveTelegramConversationRouteMock = vi.hoisted(() => vi.fn()); vi.mock("openclaw/plugin-sdk/conversation-runtime", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, - recordInboundSession: (...args: unknown[]) => hoisted.recordInboundSessionMock(...args), - getSessionBindingService: () => ({ - bind: vi.fn(), - getCapabilities: vi.fn(), - listBySession: vi.fn(), - resolveByConversation: (ref: unknown) => hoisted.resolveByConversationMock(ref), - touch: (bindingId: string, at?: number) => hoisted.touchMock(bindingId, at), - unbind: vi.fn(), - }), + recordInboundSession: (...args: unknown[]) => recordInboundSessionMock(...args), + }; +}); +vi.mock("./conversation-route.js", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + resolveTelegramConversationRoute: (...args: unknown[]) => + resolveTelegramConversationRouteMock(...args), }; }); let buildTelegramMessageContextForTest: typeof import("./bot-message-context.test-harness.js").buildTelegramMessageContextForTest; -describe("buildTelegramMessageContext bound conversation override", () => { +function createBoundRoute(params: { + accountId: string; + sessionKey: string; + agentId: string; +}) { + return { + configuredBinding: null, + configuredBindingSessionKey: "", + route: { + accountId: params.accountId, + agentId: params.agentId, + channel: "telegram", + sessionKey: params.sessionKey, + mainSessionKey: `agent:${params.agentId}:main`, + matchedBy: "binding.channel", + lastRoutePolicy: "bound", + }, + } as const; +} + +describe("buildTelegramMessageContext thread binding override", () => { beforeAll(async () => { vi.resetModules(); ({ buildTelegramMessageContextForTest } = @@ -37,16 +49,18 @@ describe("buildTelegramMessageContext bound conversation override", () => { }); beforeEach(() => { - hoisted.recordInboundSessionMock.mockClear(); - hoisted.resolveByConversationMock.mockReset().mockReturnValue(null); - hoisted.touchMock.mockReset(); + recordInboundSessionMock.mockClear(); + resolveTelegramConversationRouteMock.mockReset(); }); - it("routes forum topic messages to the bound session", async () => { - hoisted.resolveByConversationMock.mockReturnValue({ - bindingId: "default:-100200300:topic:77", - targetSessionKey: "agent:codex-acp:session-1", - }); + it("passes forum topic messages through the route seam and uses the bound session", async () => { + resolveTelegramConversationRouteMock.mockReturnValue( + createBoundRoute({ + accountId: "default", + sessionKey: "agent:codex-acp:session-1", + agentId: "codex-acp", + }), + ); const ctx = await buildTelegramMessageContextForTest({ message: { @@ -61,23 +75,30 @@ describe("buildTelegramMessageContext bound conversation override", () => { resolveGroupActivation: () => true, }); - expect(hoisted.resolveByConversationMock).toHaveBeenCalledWith({ - channel: "telegram", - accountId: "default", - conversationId: "-100200300:topic:77", - }); + expect(resolveTelegramConversationRouteMock).toHaveBeenCalledWith( + expect.objectContaining({ + accountId: "default", + chatId: -100200300, + isGroup: true, + resolvedThreadId: 77, + replyThreadId: 77, + senderId: "42", + }), + ); expect(ctx?.ctxPayload?.SessionKey).toBe("agent:codex-acp:session-1"); - expect(hoisted.recordInboundSessionMock.mock.calls[0]?.[0]).toMatchObject({ + expect(recordInboundSessionMock.mock.calls[0]?.[0]).toMatchObject({ updateLastRoute: undefined, }); - expect(hoisted.touchMock).toHaveBeenCalledWith("default:-100200300:topic:77", undefined); }); it("treats named-account bound conversations as explicit route matches", async () => { - hoisted.resolveByConversationMock.mockReturnValue({ - bindingId: "work:-100200300:topic:77", - targetSessionKey: "agent:codex-acp:session-2", - }); + resolveTelegramConversationRouteMock.mockReturnValue( + createBoundRoute({ + accountId: "work", + sessionKey: "agent:codex-acp:session-2", + agentId: "codex-acp", + }), + ); const ctx = await buildTelegramMessageContextForTest({ accountId: "work", @@ -93,18 +114,30 @@ describe("buildTelegramMessageContext bound conversation override", () => { resolveGroupActivation: () => true, }); + expect(resolveTelegramConversationRouteMock).toHaveBeenCalledWith( + expect.objectContaining({ + accountId: "work", + chatId: -100200300, + isGroup: true, + resolvedThreadId: 77, + replyThreadId: 77, + senderId: "42", + }), + ); expect(ctx).not.toBeNull(); expect(ctx?.route.accountId).toBe("work"); expect(ctx?.route.matchedBy).toBe("binding.channel"); expect(ctx?.ctxPayload?.SessionKey).toBe("agent:codex-acp:session-2"); - expect(hoisted.touchMock).toHaveBeenCalledWith("work:-100200300:topic:77", undefined); }); - it("routes dm messages to the bound session", async () => { - hoisted.resolveByConversationMock.mockReturnValue({ - bindingId: "default:1234", - targetSessionKey: "agent:codex-acp:session-dm", - }); + it("passes dm messages through the route seam and uses the bound session", async () => { + resolveTelegramConversationRouteMock.mockReturnValue( + createBoundRoute({ + accountId: "default", + sessionKey: "agent:codex-acp:session-dm", + agentId: "codex-acp", + }), + ); const ctx = await buildTelegramMessageContextForTest({ message: { @@ -116,12 +149,16 @@ describe("buildTelegramMessageContext bound conversation override", () => { }, }); - expect(hoisted.resolveByConversationMock).toHaveBeenCalledWith({ - channel: "telegram", - accountId: "default", - conversationId: "1234", - }); + expect(resolveTelegramConversationRouteMock).toHaveBeenCalledWith( + expect.objectContaining({ + accountId: "default", + chatId: 1234, + isGroup: false, + resolvedThreadId: undefined, + replyThreadId: undefined, + senderId: "42", + }), + ); expect(ctx?.ctxPayload?.SessionKey).toBe("agent:codex-acp:session-dm"); - expect(hoisted.touchMock).toHaveBeenCalledWith("default:1234", undefined); }); });