From 69daa24869793f8d37536c33849fbf1a069d496f Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 17 Dec 2025 22:12:16 +0100 Subject: [PATCH] fix(test): stabilize chat.abort --- src/gateway/server.test.ts | 181 ++++++++++++++++++++----------------- 1 file changed, 96 insertions(+), 85 deletions(-) diff --git a/src/gateway/server.test.ts b/src/gateway/server.test.ts index 8eff73edb..37324c43c 100644 --- a/src/gateway/server.test.ts +++ b/src/gateway/server.test.ts @@ -1970,102 +1970,113 @@ describe("gateway server", () => { await server.close(); }); - test("chat.abort cancels an in-flight chat.send", { timeout: 15000 }, async () => { - const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-gw-")); - testSessionStorePath = path.join(dir, "sessions.json"); - await fs.writeFile( - testSessionStorePath, - JSON.stringify( - { - main: { - sessionId: "sess-main", - updatedAt: Date.now(), + test( + "chat.abort cancels an in-flight chat.send", + { timeout: 15000 }, + async () => { + const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-gw-")); + testSessionStorePath = path.join(dir, "sessions.json"); + await fs.writeFile( + testSessionStorePath, + JSON.stringify( + { + main: { + sessionId: "sess-main", + updatedAt: Date.now(), + }, }, - }, - null, - 2, - ), - "utf-8", - ); + null, + 2, + ), + "utf-8", + ); - const { server, ws } = await startServerWithClient(); - try { - await connectOk(ws); + const { server, ws } = await startServerWithClient(); + let inFlight: Promise | undefined; + try { + await connectOk(ws); - const spy = vi.mocked(agentCommand); - spy.mockImplementationOnce(async (opts) => { - const signal = (opts as { abortSignal?: AbortSignal }).abortSignal; - await new Promise((resolve) => { - if (!signal) return resolve(); - if (signal.aborted) return resolve(); - signal.addEventListener("abort", () => resolve(), { once: true }); + const spy = vi.mocked(agentCommand); + const callsBefore = spy.mock.calls.length; + spy.mockImplementationOnce(async (opts) => { + const signal = (opts as { abortSignal?: AbortSignal }).abortSignal; + await new Promise((resolve) => { + if (!signal) return resolve(); + if (signal.aborted) return resolve(); + signal.addEventListener("abort", () => resolve(), { once: true }); + }); }); - }); - const sendResP = onceMessage( - ws, - (o) => o.type === "res" && o.id === "send-abort-1", - 8000, - ); - const abortResP = onceMessage( - ws, - (o) => o.type === "res" && o.id === "abort-1", - 8000, - ); - const abortedEventP = onceMessage( - ws, - (o) => o.type === "event" && o.event === "chat" && o.payload?.state === "aborted", - 8000, - ); + const sendResP = onceMessage( + ws, + (o) => o.type === "res" && o.id === "send-abort-1", + 8000, + ); + const abortResP = onceMessage( + ws, + (o) => o.type === "res" && o.id === "abort-1", + 8000, + ); + const abortedEventP = onceMessage( + ws, + (o) => + o.type === "event" && + o.event === "chat" && + o.payload?.state === "aborted", + 8000, + ); + inFlight = Promise.allSettled([sendResP, abortResP, abortedEventP]); - ws.send( - JSON.stringify({ - type: "req", - id: "send-abort-1", - method: "chat.send", - params: { - sessionKey: "main", - message: "hello", - idempotencyKey: "idem-abort-1", - timeoutMs: 30_000, - }, - }), - ); + ws.send( + JSON.stringify({ + type: "req", + id: "send-abort-1", + method: "chat.send", + params: { + sessionKey: "main", + message: "hello", + idempotencyKey: "idem-abort-1", + timeoutMs: 30_000, + }, + }), + ); - await new Promise((resolve, reject) => { - const deadline = Date.now() + 1000; - const tick = () => { - if (spy.mock.calls.length > 0) return resolve(); - if (Date.now() > deadline) - return reject(new Error("timeout waiting for agentCommand")); - setTimeout(tick, 5); - }; - tick(); - }); + await new Promise((resolve, reject) => { + const deadline = Date.now() + 1000; + const tick = () => { + if (spy.mock.calls.length > callsBefore) return resolve(); + if (Date.now() > deadline) + return reject(new Error("timeout waiting for agentCommand")); + setTimeout(tick, 5); + }; + tick(); + }); - ws.send( - JSON.stringify({ - type: "req", - id: "abort-1", - method: "chat.abort", - params: { sessionKey: "main", runId: "idem-abort-1" }, - }), - ); + ws.send( + JSON.stringify({ + type: "req", + id: "abort-1", + method: "chat.abort", + params: { sessionKey: "main", runId: "idem-abort-1" }, + }), + ); - const abortRes = await abortResP; - expect(abortRes.ok).toBe(true); + const abortRes = await abortResP; + expect(abortRes.ok).toBe(true); - const sendRes = await sendResP; - expect(sendRes.ok).toBe(true); + const sendRes = await sendResP; + expect(sendRes.ok).toBe(true); - const evt = await abortedEventP; - expect(evt.payload?.runId).toBe("idem-abort-1"); - expect(evt.payload?.sessionKey).toBe("main"); - } finally { - ws.close(); - await server.close(); - } - }); + const evt = await abortedEventP; + expect(evt.payload?.runId).toBe("idem-abort-1"); + expect(evt.payload?.sessionKey).toBe("main"); + } finally { + ws.close(); + await inFlight; + await server.close(); + } + }, + ); test("bridge RPC chat.history returns session messages", async () => { const dir = await fs.mkdtemp(path.join(os.tmpdir(), "clawdis-gw-"));