UNPKG

@toolprint/hypertool-mcp

Version:

HyperTool MCP proxy server for routing requests between clients and multiple underlying MCP servers

228 lines • 6.22 kB
/** * Async test helpers for preventing hanging tests * Provides utilities for timeouts, retries, and cleanup */ import { vi } from "vitest"; /** * Timeout configuration for different test types */ export const TEST_TIMEOUTS = { unit: 3000, // 3 seconds for unit tests integration: 10000, // 10 seconds for integration tests e2e: 30000, // 30 seconds for e2e tests default: 5000, // 5 seconds default }; /** * Promise with timeout utility * Prevents tests from hanging indefinitely */ export function withTimeout(promise, timeoutMs = TEST_TIMEOUTS.default, errorMessage) { return new Promise((resolve, reject) => { const timer = setTimeout(() => { reject(new Error(errorMessage || `Operation timed out after ${timeoutMs}ms`)); }, timeoutMs); promise .then((result) => { clearTimeout(timer); resolve(result); }) .catch((error) => { clearTimeout(timer); reject(error); }); }); } /** * Retry utility for flaky operations * Useful for tests that occasionally fail due to timing issues */ export async function retry(fn, options = {}) { const { maxRetries = 3, delay = 100, backoff = true, timeout = TEST_TIMEOUTS.default, } = options; let lastError; for (let attempt = 0; attempt < maxRetries; attempt++) { try { return await withTimeout(fn(), timeout); } catch (error) { lastError = error; if (attempt < maxRetries - 1) { const waitTime = backoff ? delay * Math.pow(2, attempt) : delay; await new Promise((resolve) => setTimeout(resolve, waitTime)); } } } throw lastError; } /** * Wait for a condition to be true * Useful for waiting for async state changes */ export async function waitFor(condition, options = {}) { const { timeout = TEST_TIMEOUTS.default, interval = 50, errorMessage = "Condition not met within timeout", } = options; const startTime = Date.now(); while (Date.now() - startTime < timeout) { const result = await condition(); if (result) { return; } await new Promise((resolve) => setTimeout(resolve, interval)); } throw new Error(errorMessage); } /** * Create a deferred promise for testing async flows */ export function createDeferred() { let resolve; let reject; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); return { promise, resolve: resolve, reject: reject }; } /** * Test fixture for managing async resources * Ensures cleanup even if test fails */ export class AsyncTestFixture { cleanupFns = []; activeTimers = new Set(); activeIntervals = new Set(); /** * Register a cleanup function */ addCleanup(fn) { this.cleanupFns.push(fn); } /** * Create a timer that will be automatically cleaned up */ setTimeout(fn, delay) { const timer = setTimeout(() => { this.activeTimers.delete(timer); fn(); }, delay); this.activeTimers.add(timer); return timer; } /** * Create an interval that will be automatically cleaned up */ setInterval(fn, delay) { const interval = setInterval(fn, delay); this.activeIntervals.add(interval); return interval; } /** * Clear a timer */ clearTimeout(timer) { clearTimeout(timer); this.activeTimers.delete(timer); } /** * Clear an interval */ clearInterval(interval) { clearInterval(interval); this.activeIntervals.delete(interval); } /** * Run all cleanup functions */ async cleanup() { // Clear all timers for (const timer of this.activeTimers) { clearTimeout(timer); } for (const interval of this.activeIntervals) { clearInterval(interval); } this.activeTimers.clear(); this.activeIntervals.clear(); // Run cleanup functions in reverse order const fns = [...this.cleanupFns].reverse(); this.cleanupFns = []; for (const fn of fns) { try { await fn(); } catch (error) { console.error("Cleanup error:", error); } } } } /** * Mock timer utilities for deterministic async tests */ export const mockTimers = { /** * Use fake timers and return cleanup function */ useFakeTimers() { vi.useFakeTimers(); return () => { vi.clearAllTimers(); vi.useRealTimers(); }; }, /** * Advance timers by time and flush promises */ async advanceTimersByTime(ms) { vi.advanceTimersByTime(ms); await flushPromises(); }, /** * Run all pending timers and flush promises */ async runAllTimers() { vi.runAllTimers(); await flushPromises(); }, /** * Run only pending timers (not recursive) and flush promises */ async runOnlyPendingTimers() { vi.runOnlyPendingTimers(); await flushPromises(); }, }; /** * Flush all pending promises * Useful for ensuring async operations complete */ export function flushPromises() { return new Promise((resolve) => { setImmediate(resolve); }); } /** * Create a test timeout guard * Automatically fails test if it takes too long */ export function createTimeoutGuard(timeoutMs = TEST_TIMEOUTS.default) { const timer = setTimeout(() => { throw new Error(`Test exceeded timeout of ${timeoutMs}ms`); }, timeoutMs); return { cancel: () => clearTimeout(timer), }; } /** * Wrap an async test with automatic timeout */ export function withTestTimeout(testFn, timeoutMs = TEST_TIMEOUTS.default) { return async () => { const guard = createTimeoutGuard(timeoutMs); try { return await testFn(); } finally { guard.cancel(); } }; } //# sourceMappingURL=async-helpers.js.map