diff --git a/src/client/index.test.ts b/src/client/index.test.ts index 0d7eb941..55a113ea 100644 --- a/src/client/index.test.ts +++ b/src/client/index.test.ts @@ -14,6 +14,7 @@ import { ListToolsRequestSchema, CreateMessageRequestSchema, ListRootsRequestSchema, + ErrorCode, } from "../types.js"; import { Transport } from "../shared/transport.js"; import { Server } from "../server/index.js"; @@ -491,3 +492,58 @@ test("should handle client cancelling a request", async () => { // Request should be rejected await expect(listResourcesPromise).rejects.toBe("Cancelled by test"); }); + +test("should handle request timeout", async () => { + const server = new Server( + { + name: "test server", + version: "1.0", + }, + { + capabilities: { + resources: {}, + }, + }, + ); + + // Set up server with a delayed response + server.setRequestHandler( + ListResourcesRequestSchema, + async (_request, extra) => { + const timer = new Promise((resolve) => { + const timeout = setTimeout(resolve, 100); + extra.signal.addEventListener("abort", () => clearTimeout(timeout)); + }); + + await timer; + return { + resources: [], + }; + }, + ); + + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + + const client = new Client( + { + name: "test client", + version: "1.0", + }, + { + capabilities: {}, + }, + ); + + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + // Request with 0 msec timeout should fail immediately + await expect( + client.listResources(undefined, { timeout: 0 }), + ).rejects.toMatchObject({ + code: ErrorCode.RequestTimeout, + }); +}); diff --git a/src/server/index.test.ts b/src/server/index.test.ts index 0697cc5c..0a23955a 100644 --- a/src/server/index.test.ts +++ b/src/server/index.test.ts @@ -14,6 +14,7 @@ import { ListResourcesRequestSchema, ListToolsRequestSchema, SetLevelRequestSchema, + ErrorCode, } from "../types.js"; import { Transport } from "../shared/transport.js"; import { InMemoryTransport } from "../inMemory.js"; @@ -475,3 +476,72 @@ test("should handle server cancelling a request", async () => { // Request should be rejected await expect(createMessagePromise).rejects.toBe("Cancelled by test"); }); +test("should handle request timeout", async () => { + const server = new Server( + { + name: "test server", + version: "1.0", + }, + { + capabilities: { + sampling: {}, + }, + }, + ); + + // Set up client that delays responses + const client = new Client( + { + name: "test client", + version: "1.0", + }, + { + capabilities: { + sampling: {}, + }, + }, + ); + + client.setRequestHandler( + CreateMessageRequestSchema, + async (_request, extra) => { + await new Promise((resolve, reject) => { + const timeout = setTimeout(resolve, 100); + extra.signal.addEventListener("abort", () => { + clearTimeout(timeout); + reject(extra.signal.reason); + }); + }); + + return { + model: "test", + role: "assistant", + content: { + type: "text", + text: "Test response", + }, + }; + }, + ); + + const [clientTransport, serverTransport] = + InMemoryTransport.createLinkedPair(); + + await Promise.all([ + client.connect(clientTransport), + server.connect(serverTransport), + ]); + + // Request with 0 msec timeout should fail immediately + await expect( + server.createMessage( + { + messages: [], + maxTokens: 10, + }, + { timeout: 0 }, + ), + ).rejects.toMatchObject({ + code: ErrorCode.RequestTimeout, + }); +});