From 61396ea156bb60a14b95c8d0e92aa81ad25db9c3 Mon Sep 17 00:00:00 2001 From: Jerry Qian <36783706+JQ100@users.noreply.github.com> Date: Tue, 27 Oct 2026 14:11:54 -0700 Subject: [PATCH] test(e2e): wait for the call to reach the server in protocol:timeout:max-total The test switched to fake timers and advanced the clock immediately after starting tools/call. Over SSE the request is a real HTTP POST that fake timers do not wait for, so when it was slow to land the 500ms per-request timeout fired before any progress was sent, and the test failed without ever exercising maxTotalTimeout. Resolve a promise when the tool handler starts and await it before advancing. Awaiting a promise does not move the fake clock, unlike vi.waitFor, which advances fake timers while it polls. On macOS, -t "timeout:basic|timeout:max-total" failed 5 of 20 SSE variants before this change and 0 of 40 after. Delaying only the tools/call POST by 50ms of real time fails both SSE variants on every run without the change and passes all 12 variants with it. Fixes #3045 --- test/e2e/scenarios/protocol.test.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/e2e/scenarios/protocol.test.ts b/test/e2e/scenarios/protocol.test.ts index 4abcde078d..66f3f5f2e7 100644 --- a/test/e2e/scenarios/protocol.test.ts +++ b/test/e2e/scenarios/protocol.test.ts @@ -662,12 +662,15 @@ verifies('protocol:timeout:basic', async ({ transport }: TestArgs) => { verifies('protocol:timeout:max-total', async ({ transport }: TestArgs) => { vi.useFakeTimers(); try { + let markCallReceived!: () => void; + const callReceived = new Promise(resolve => (markCallReceived = resolve)); const makeServer = () => { const s = new McpServer({ name: 's', version: '0' }); s.registerTool( 'slow-progress', { inputSchema: z.object({ delayMs: z.number(), steps: z.number() }) }, async ({ delayMs, steps }, ctx) => { + markCallReceived(); const token = ctx.mcpReq._meta?.progressToken; if (token !== undefined) { for (let i = 1; i <= steps; i++) { @@ -703,6 +706,12 @@ verifies('protocol:timeout:max-total', async ({ transport }: TestArgs) => { ); call.catch(() => {}); + // Over SSE the tools/call POST is real I/O that fake timers do not wait for. + // Advancing before it lands lets the per-request timeout fire before any + // progress is sent, so maxTotalTimeout is never exercised (#3045). Awaiting a + // promise, unlike vi.waitFor, does not move the fake clock. + await callReceived; + for (let elapsed = 0; elapsed < maxTotal + perChunk; elapsed += delayMs) { await vi.advanceTimersByTimeAsync(delayMs); }