import { tap, expect } from '@git.zone/tstest/tapbundle'; import { delay, retryWithBackoff, withTimeout, parallelLimit, debounceAsync, AsyncMutex, CircuitBreaker } from '../../../ts/core/utils/async-utils.js'; tap.test('delay should pause execution for specified milliseconds', async () => { const startTime = Date.now(); await delay(100); const elapsed = Date.now() - startTime; // Allow some tolerance for timing expect(elapsed).toBeGreaterThan(90); expect(elapsed).toBeLessThan(150); }); tap.test('retryWithBackoff should retry failed operations', async () => { let attempts = 0; const operation = async () => { attempts++; if (attempts < 3) { throw new Error('Test error'); } return 'success'; }; const result = await retryWithBackoff(operation, { maxAttempts: 3, initialDelay: 10 }); expect(result).toEqual('success'); expect(attempts).toEqual(3); }); tap.test('retryWithBackoff should throw after max attempts', async () => { let attempts = 0; const operation = async () => { attempts++; throw new Error('Always fails'); }; let error: Error | null = null; try { await retryWithBackoff(operation, { maxAttempts: 2, initialDelay: 10 }); } catch (e: any) { error = e; } expect(error).not.toBeNull(); expect(error?.message).toEqual('Always fails'); expect(attempts).toEqual(2); }); tap.test('withTimeout should complete operations within timeout', async () => { const operation = async () => { await delay(50); return 'completed'; }; const result = await withTimeout(operation, 100); expect(result).toEqual('completed'); }); tap.test('withTimeout should throw on timeout', async () => { const operation = async () => { await delay(200); return 'never happens'; }; let error: Error | null = null; try { await withTimeout(operation, 50); } catch (e: any) { error = e; } expect(error).not.toBeNull(); expect(error?.message).toContain('timed out'); }); tap.test('parallelLimit should respect concurrency limit', async () => { let concurrent = 0; let maxConcurrent = 0; const items = [1, 2, 3, 4, 5, 6]; const operation = async (item: number) => { concurrent++; maxConcurrent = Math.max(maxConcurrent, concurrent); await delay(50); concurrent--; return item * 2; }; const results = await parallelLimit(items, operation, 2); expect(results).toEqual([2, 4, 6, 8, 10, 12]); expect(maxConcurrent).toBeLessThan(3); expect(maxConcurrent).toBeGreaterThan(0); }); tap.test('debounceAsync should debounce function calls', async () => { let callCount = 0; const fn = async (value: string) => { callCount++; return value; }; const debounced = debounceAsync(fn, 50); // Make multiple calls quickly debounced('a'); debounced('b'); debounced('c'); const result = await debounced('d'); // Wait a bit to ensure no more calls await delay(100); expect(result).toEqual('d'); expect(callCount).toEqual(1); // Only the last call should execute }); tap.test('AsyncMutex should ensure exclusive access', async () => { const mutex = new AsyncMutex(); const results: number[] = []; const operation = async (value: number) => { await mutex.runExclusive(async () => { results.push(value); await delay(10); results.push(value * 10); }); }; // Run operations concurrently await Promise.all([ operation(1), operation(2), operation(3) ]); // Results should show sequential execution expect(results).toEqual([1, 10, 2, 20, 3, 30]); }); tap.test('CircuitBreaker should open after failures', async () => { const breaker = new CircuitBreaker({ failureThreshold: 2, resetTimeout: 100 }); let attempt = 0; const failingOperation = async () => { attempt++; throw new Error('Test failure'); }; // First two failures for (let i = 0; i < 2; i++) { try { await breaker.execute(failingOperation); } catch (e) { // Expected } } expect(breaker.isOpen()).toBeTrue(); // Next attempt should fail immediately let error: Error | null = null; try { await breaker.execute(failingOperation); } catch (e: any) { error = e; } expect(error?.message).toEqual('Circuit breaker is open'); expect(attempt).toEqual(2); // Operation not called when circuit is open // Wait for reset timeout await delay(150); // Circuit should be half-open now, allowing one attempt const successOperation = async () => 'success'; const result = await breaker.execute(successOperation); expect(result).toEqual('success'); expect(breaker.getState()).toEqual('closed'); }); tap.start();