From 00bbecede748f480e0c0169e414fed400dff8945 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 22 Feb 2026 20:26:19 +0100 Subject: [PATCH] test(gateway): add telegram-session chat.send final-event e2e coverage --- test/gateway.multi.e2e.test.ts | 96 ++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/test/gateway.multi.e2e.test.ts b/test/gateway.multi.e2e.test.ts index bca47ced553..61f9de79b25 100644 --- a/test/gateway.multi.e2e.test.ts +++ b/test/gateway.multi.e2e.test.ts @@ -29,6 +29,13 @@ type NodeListPayload = { nodes?: Array<{ nodeId?: string; connected?: boolean; paired?: boolean }>; }; +type ChatEventPayload = { + runId?: string; + sessionKey?: string; + state?: string; + message?: unknown; +}; + const GATEWAY_START_TIMEOUT_MS = 20_000; const GATEWAY_STOP_TIMEOUT_MS = 1_500; const E2E_TIMEOUT_MS = 120_000; @@ -340,14 +347,54 @@ const waitForNodeStatus = async ( throw new Error(`timeout waiting for node status for ${nodeId}`); }; +function extractFirstTextBlock(message: unknown): string | undefined { + if (!message || typeof message !== "object") { + return undefined; + } + const content = (message as { content?: unknown }).content; + if (!Array.isArray(content) || content.length === 0) { + return undefined; + } + const first = content[0]; + if (!first || typeof first !== "object") { + return undefined; + } + const text = (first as { text?: unknown }).text; + return typeof text === "string" ? text : undefined; +} + +const waitForChatFinalEvent = async (params: { + events: ChatEventPayload[]; + runId: string; + sessionKey: string; + timeoutMs?: number; +}): Promise => { + const deadline = Date.now() + (params.timeoutMs ?? 15_000); + while (Date.now() < deadline) { + const match = params.events.find( + (evt) => + evt.runId === params.runId && evt.sessionKey === params.sessionKey && evt.state === "final", + ); + if (match) { + return match; + } + await sleep(20); + } + throw new Error(`timeout waiting for final chat event (runId=${params.runId})`); +}; + describe("gateway multi-instance e2e", () => { const instances: GatewayInstance[] = []; const nodeClients: GatewayClient[] = []; + const webchatClients: GatewayClient[] = []; afterAll(async () => { for (const client of nodeClients) { client.stop(); } + for (const client of webchatClients) { + client.stop(); + } for (const inst of instances) { await stopGatewayInstance(inst); } @@ -395,4 +442,53 @@ describe("gateway multi-instance e2e", () => { ]); }, ); + + it( + "delivers final chat event for telegram-shaped session keys", + { timeout: E2E_TIMEOUT_MS }, + async () => { + const gw = await spawnGatewayInstance("chat-telegram-fixture"); + instances.push(gw); + + const chatEvents: ChatEventPayload[] = []; + const webchatClient = await connectGatewayClient({ + url: `ws://127.0.0.1:${gw.port}`, + token: gw.gatewayToken, + clientName: GATEWAY_CLIENT_NAMES.CONTROL_UI, + clientDisplayName: "chat-e2e", + clientVersion: "1.0.0", + platform: "web", + mode: GATEWAY_CLIENT_MODES.WEBCHAT, + onEvent: (evt) => { + if (evt.event === "chat" && evt.payload && typeof evt.payload === "object") { + chatEvents.push(evt.payload as ChatEventPayload); + } + }, + }); + webchatClients.push(webchatClient); + + const sessionKey = "agent:main:telegram:direct:123456"; + const idempotencyKey = `idem-${randomUUID()}`; + const sendRes = await webchatClient.request<{ runId?: string; status?: string }>( + "chat.send", + { + sessionKey, + message: "/context list", + idempotencyKey, + }, + ); + expect(sendRes.status).toBe("started"); + const runId = sendRes.runId; + expect(typeof runId).toBe("string"); + + const finalEvent = await waitForChatFinalEvent({ + events: chatEvents, + runId: String(runId), + sessionKey, + }); + const finalText = extractFirstTextBlock(finalEvent.message); + expect(typeof finalText).toBe("string"); + expect(finalText?.length).toBeGreaterThan(0); + }, + ); });