diff --git a/extensions/azure-speech/voices-timeout.test.ts b/extensions/azure-speech/voices-timeout.test.ts index 511e8fd29b8b..de26152d9a18 100644 --- a/extensions/azure-speech/voices-timeout.test.ts +++ b/extensions/azure-speech/voices-timeout.test.ts @@ -3,6 +3,7 @@ // the real fetch abort path without depending on Azure latency. import { createServer, type Server } from "node:http"; import type { AddressInfo } from "node:net"; +import { clearTimeout as clearRealTimeout, setTimeout as setRealTimeout } from "node:timers"; import { afterEach, describe, expect, it, vi } from "vitest"; import { listAzureSpeechVoices } from "./tts.js"; @@ -39,8 +40,13 @@ describe("listAzureSpeechVoices timeout", () => { { timeout: 2_000 }, async () => { let requestCount = 0; - const server = createServer((_req, _res) => { + let notifyRequest = () => {}; + const requestReceived = new Promise((resolve) => { + notifyRequest = resolve; + }); + const server = createServer((_request, _response) => { requestCount += 1; + notifyRequest(); }); const port = await listenLocal(server); @@ -56,26 +62,32 @@ describe("listAzureSpeechVoices timeout", () => { ); const startedAt = Date.now(); - let watchdog: ReturnType | undefined; + let watchdog: ReturnType | undefined; try { - await expect( - Promise.race([ - listAzureSpeechVoices({ - apiKey: "not-a-real", - baseUrl: "https://custom.example.com", - timeoutMs: 100, - }), - new Promise((_, reject) => { - watchdog = setTimeout(() => reject(new Error("voices list did not time out")), 1_000); - }), - ]), - ).rejects.toThrow(/aborted|timeout|timed out/i); - expect(Date.now() - startedAt).toBeLessThan(1_000); + vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] }); + const watchdogPromise = new Promise((_, reject) => { + watchdog = setRealTimeout(() => reject(new Error("voices list did not time out")), 1_000); + }); + const request = Promise.race([ + listAzureSpeechVoices({ + apiKey: "not-a-real", + baseUrl: "https://custom.example.com", + timeoutMs: 100, + }), + watchdogPromise, + ]); + const rejection = expect(request).rejects.toThrow(/aborted|timeout|timed out/i); + + await Promise.race([requestReceived, watchdogPromise]); expect(requestCount).toBe(1); + await vi.advanceTimersByTimeAsync(100); + await rejection; + expect(Date.now() - startedAt).toBeLessThan(1_000); } finally { + vi.useRealTimers(); if (watchdog) { - clearTimeout(watchdog); + clearRealTimeout(watchdog); } await closeServer(server); } diff --git a/extensions/inworld/voices-timeout.test.ts b/extensions/inworld/voices-timeout.test.ts index ff34c8ea26f7..f1eef5e57bcc 100644 --- a/extensions/inworld/voices-timeout.test.ts +++ b/extensions/inworld/voices-timeout.test.ts @@ -1,3 +1,4 @@ +import { clearTimeout as clearRealTimeout, setTimeout as setRealTimeout } from "node:timers"; import { withServer } from "openclaw/plugin-sdk/test-env"; import { afterEach, describe, expect, it, vi } from "vitest"; import { listInworldVoices } from "./tts.js"; @@ -14,9 +15,14 @@ describe("listInworldVoices live timeout", () => { { timeout: 2_000 }, async () => { let requestCount = 0; + let notifyRequest = () => {}; + const requestReceived = new Promise((resolve) => { + notifyRequest = resolve; + }); await withServer( (request) => { requestCount += 1; + notifyRequest(); request.resume(); }, async (baseUrl) => { @@ -27,14 +33,35 @@ describe("listInworldVoices live timeout", () => { }) as unknown as typeof globalThis.fetch, ); - await expect( - listInworldVoices({ - apiKey: "test-key", - baseUrl: "https://custom.inworld.example.com", - timeoutMs: 250, - }), - ).rejects.toThrow(/aborted|timeout|timed out/i); - expect(requestCount).toBe(1); + let watchdog: ReturnType | undefined; + try { + vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] }); + const watchdogPromise = new Promise((_, reject) => { + watchdog = setRealTimeout( + () => reject(new Error("voices list did not time out")), + 1_000, + ); + }); + const request = Promise.race([ + listInworldVoices({ + apiKey: "test-key", + baseUrl: "https://custom.inworld.example.com", + timeoutMs: 250, + }), + watchdogPromise, + ]); + const rejection = expect(request).rejects.toThrow(/aborted|timeout|timed out/i); + + await Promise.race([requestReceived, watchdogPromise]); + expect(requestCount).toBe(1); + await vi.advanceTimersByTimeAsync(250); + await rejection; + } finally { + vi.useRealTimers(); + if (watchdog) { + clearRealTimeout(watchdog); + } + } }, ); },