Cloudflare Workers + Hono + D1 + KV + R2 stack with 4 specialized AI agents (onboarding, troubleshoot, asset, billing), OpenAI function calling with 7 tool definitions, human escalation, pending action approval workflow, feedback collection, audit logging, i18n (ko/en), and Workers AI fallback. 43 source files, 45 tests passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
67 lines
2.0 KiB
TypeScript
67 lines
2.0 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { retryWithBackoff, RetryError } from '../../src/utils/retry';
|
|
|
|
describe('retryWithBackoff', () => {
|
|
it('succeeds on first attempt without retrying', async () => {
|
|
let callCount = 0;
|
|
const result = await retryWithBackoff(async () => {
|
|
callCount++;
|
|
return 'ok';
|
|
}, { maxRetries: 3, initialDelayMs: 1, jitter: false });
|
|
|
|
expect(result).toBe('ok');
|
|
expect(callCount).toBe(1);
|
|
});
|
|
|
|
it('retries and succeeds on 2nd attempt', async () => {
|
|
let callCount = 0;
|
|
const result = await retryWithBackoff(async () => {
|
|
callCount++;
|
|
if (callCount < 2) throw new Error('transient');
|
|
return 'recovered';
|
|
}, { maxRetries: 3, initialDelayMs: 1, jitter: false });
|
|
|
|
expect(result).toBe('recovered');
|
|
expect(callCount).toBe(2);
|
|
});
|
|
|
|
it('throws RetryError after all attempts exhausted', async () => {
|
|
let callCount = 0;
|
|
await expect(
|
|
retryWithBackoff(async () => {
|
|
callCount++;
|
|
throw new Error('permanent');
|
|
}, { maxRetries: 2, initialDelayMs: 1, jitter: false })
|
|
).rejects.toThrow(RetryError);
|
|
|
|
// 1 initial + 2 retries = 3 total
|
|
expect(callCount).toBe(3);
|
|
});
|
|
|
|
it('respects maxRetries option', async () => {
|
|
let callCount = 0;
|
|
await expect(
|
|
retryWithBackoff(async () => {
|
|
callCount++;
|
|
throw new Error('fail');
|
|
}, { maxRetries: 1, initialDelayMs: 1, jitter: false })
|
|
).rejects.toThrow(RetryError);
|
|
|
|
// 1 initial + 1 retry = 2 total
|
|
expect(callCount).toBe(2);
|
|
});
|
|
|
|
it('RetryError contains attempt count and last error', async () => {
|
|
try {
|
|
await retryWithBackoff(async () => {
|
|
throw new Error('specific failure');
|
|
}, { maxRetries: 2, initialDelayMs: 1, jitter: false });
|
|
} catch (error) {
|
|
expect(error).toBeInstanceOf(RetryError);
|
|
const retryErr = error as RetryError;
|
|
expect(retryErr.attempts).toBe(3);
|
|
expect(retryErr.lastError.message).toBe('specific failure');
|
|
}
|
|
});
|
|
});
|