Skip to main content
Glama

hypertool-mcp

async-helpers.tsโ€ข6.61 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 } as const; /** * Promise with timeout utility * Prevents tests from hanging indefinitely */ export function withTimeout<T>( promise: Promise<T>, timeoutMs: number = TEST_TIMEOUTS.default, errorMessage?: string ): Promise<T> { 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<T>( fn: () => Promise<T>, options: { maxRetries?: number; delay?: number; backoff?: boolean; timeout?: number; } = {} ): Promise<T> { const { maxRetries = 3, delay = 100, backoff = true, timeout = TEST_TIMEOUTS.default, } = options; let lastError: Error; for (let attempt = 0; attempt < maxRetries; attempt++) { try { return await withTimeout(fn(), timeout); } catch (error) { lastError = error as 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: () => boolean | Promise<boolean>, options: { timeout?: number; interval?: number; errorMessage?: string; } = {} ): Promise<void> { 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<T>(): { promise: Promise<T>; resolve: (value: T) => void; reject: (error: Error) => void; } { let resolve: (value: T) => void; let reject: (error: Error) => void; const promise = new Promise<T>((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 { private cleanupFns: Array<() => void | Promise<void>> = []; private activeTimers = new Set<NodeJS.Timeout>(); private activeIntervals = new Set<NodeJS.Timeout>(); /** * Register a cleanup function */ addCleanup(fn: () => void | Promise<void>): void { this.cleanupFns.push(fn); } /** * Create a timer that will be automatically cleaned up */ setTimeout(fn: () => void, delay: number): NodeJS.Timeout { 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: () => void, delay: number): NodeJS.Timeout { const interval = setInterval(fn, delay); this.activeIntervals.add(interval); return interval; } /** * Clear a timer */ clearTimeout(timer: NodeJS.Timeout): void { clearTimeout(timer); this.activeTimers.delete(timer); } /** * Clear an interval */ clearInterval(interval: NodeJS.Timeout): void { clearInterval(interval); this.activeIntervals.delete(interval); } /** * Run all cleanup functions */ async cleanup(): Promise<void> { // 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(): () => void { vi.useFakeTimers(); return () => { vi.clearAllTimers(); vi.useRealTimers(); }; }, /** * Advance timers by time and flush promises */ async advanceTimersByTime(ms: number): Promise<void> { vi.advanceTimersByTime(ms); await flushPromises(); }, /** * Run all pending timers and flush promises */ async runAllTimers(): Promise<void> { vi.runAllTimers(); await flushPromises(); }, /** * Run only pending timers (not recursive) and flush promises */ async runOnlyPendingTimers(): Promise<void> { vi.runOnlyPendingTimers(); await flushPromises(); }, }; /** * Flush all pending promises * Useful for ensuring async operations complete */ export function flushPromises(): Promise<void> { return new Promise((resolve) => { setImmediate(resolve); }); } /** * Create a test timeout guard * Automatically fails test if it takes too long */ export function createTimeoutGuard(timeoutMs: number = TEST_TIMEOUTS.default): { cancel: () => void; } { 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<T>( testFn: () => Promise<T>, timeoutMs: number = TEST_TIMEOUTS.default ): () => Promise<T> { return async () => { const guard = createTimeoutGuard(timeoutMs); try { return await testFn(); } finally { guard.cancel(); } }; }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/toolprint/hypertool-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server