Initial implementation of Telegram AI customer support bot
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>
This commit is contained in:
66
tests/utils/retry.test.ts
Normal file
66
tests/utils/retry.test.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
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');
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user