Move src/ → apps/server/ and packages/web/ → apps/web/ to adopt standard monorepo conventions (apps/ for runnable apps, packages/ for reusable libraries). Update all config files, shared package imports, test fixtures, and documentation to reflect new paths. Key fixes: - Update workspace config to ["apps/*", "packages/*"] - Update tsconfig.json rootDir/include for apps/server/ - Add apps/web/** to vitest exclude list - Update drizzle.config.ts schema path - Fix ensure-schema.ts migration path detection (3 levels up in dev, 2 levels up in dist) - Fix tests/integration/cli-server.test.ts import paths - Update packages/shared imports to apps/server/ paths - Update all docs/ files with new paths
146 lines
4.4 KiB
TypeScript
146 lines
4.4 KiB
TypeScript
/**
|
|
* RetryPolicy Tests — Verify retry logic for different error types.
|
|
*/
|
|
|
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
import { DefaultRetryPolicy, type AgentError } from './retry-policy.js';
|
|
|
|
describe('DefaultRetryPolicy', () => {
|
|
let retryPolicy: DefaultRetryPolicy;
|
|
|
|
beforeEach(() => {
|
|
retryPolicy = new DefaultRetryPolicy();
|
|
});
|
|
|
|
describe('configuration', () => {
|
|
it('should have correct max attempts', () => {
|
|
expect(retryPolicy.maxAttempts).toBe(3);
|
|
});
|
|
|
|
it('should have exponential backoff delays', () => {
|
|
expect(retryPolicy.backoffMs).toEqual([1000, 2000, 4000]);
|
|
});
|
|
});
|
|
|
|
describe('shouldRetry', () => {
|
|
it('should retry auth failures', () => {
|
|
const error: AgentError = {
|
|
type: 'auth_failure',
|
|
message: 'Unauthorized',
|
|
isTransient: true,
|
|
requiresAccountSwitch: false,
|
|
shouldPersistToDB: true
|
|
};
|
|
|
|
expect(retryPolicy.shouldRetry(error, 1)).toBe(true);
|
|
expect(retryPolicy.shouldRetry(error, 2)).toBe(true);
|
|
expect(retryPolicy.shouldRetry(error, 3)).toBe(false); // At max attempts
|
|
});
|
|
|
|
it('should not retry usage limit errors', () => {
|
|
const error: AgentError = {
|
|
type: 'usage_limit',
|
|
message: 'Rate limit exceeded',
|
|
isTransient: false,
|
|
requiresAccountSwitch: true,
|
|
shouldPersistToDB: true
|
|
};
|
|
|
|
expect(retryPolicy.shouldRetry(error, 1)).toBe(false);
|
|
expect(retryPolicy.shouldRetry(error, 2)).toBe(false);
|
|
});
|
|
|
|
it('should retry missing signal errors', () => {
|
|
const error: AgentError = {
|
|
type: 'missing_signal',
|
|
message: 'No signal.json found',
|
|
isTransient: true,
|
|
requiresAccountSwitch: false,
|
|
shouldPersistToDB: false
|
|
};
|
|
|
|
expect(retryPolicy.shouldRetry(error, 1)).toBe(true);
|
|
expect(retryPolicy.shouldRetry(error, 2)).toBe(true);
|
|
expect(retryPolicy.shouldRetry(error, 3)).toBe(false); // At max attempts
|
|
});
|
|
|
|
it('should retry transient process crashes', () => {
|
|
const error: AgentError = {
|
|
type: 'process_crash',
|
|
message: 'Process died',
|
|
isTransient: true,
|
|
requiresAccountSwitch: false,
|
|
shouldPersistToDB: true
|
|
};
|
|
|
|
expect(retryPolicy.shouldRetry(error, 1)).toBe(true);
|
|
expect(retryPolicy.shouldRetry(error, 2)).toBe(true);
|
|
});
|
|
|
|
it('should not retry non-transient process crashes', () => {
|
|
const error: AgentError = {
|
|
type: 'process_crash',
|
|
message: 'Segmentation fault',
|
|
isTransient: false,
|
|
requiresAccountSwitch: false,
|
|
shouldPersistToDB: true
|
|
};
|
|
|
|
expect(retryPolicy.shouldRetry(error, 1)).toBe(false);
|
|
expect(retryPolicy.shouldRetry(error, 2)).toBe(false);
|
|
});
|
|
|
|
it('should retry timeouts', () => {
|
|
const error: AgentError = {
|
|
type: 'timeout',
|
|
message: 'Process timed out',
|
|
isTransient: true,
|
|
requiresAccountSwitch: false,
|
|
shouldPersistToDB: true
|
|
};
|
|
|
|
expect(retryPolicy.shouldRetry(error, 1)).toBe(true);
|
|
expect(retryPolicy.shouldRetry(error, 2)).toBe(true);
|
|
expect(retryPolicy.shouldRetry(error, 3)).toBe(false); // At max attempts
|
|
});
|
|
|
|
it('should not retry unknown errors', () => {
|
|
const error: AgentError = {
|
|
type: 'unknown',
|
|
message: 'Something weird happened',
|
|
isTransient: false,
|
|
requiresAccountSwitch: false,
|
|
shouldPersistToDB: true
|
|
};
|
|
|
|
expect(retryPolicy.shouldRetry(error, 1)).toBe(false);
|
|
expect(retryPolicy.shouldRetry(error, 2)).toBe(false);
|
|
});
|
|
|
|
it('should not retry when at max attempts regardless of error type', () => {
|
|
const error: AgentError = {
|
|
type: 'auth_failure',
|
|
message: 'Unauthorized',
|
|
isTransient: true,
|
|
requiresAccountSwitch: false,
|
|
shouldPersistToDB: true
|
|
};
|
|
|
|
expect(retryPolicy.shouldRetry(error, 3)).toBe(false);
|
|
expect(retryPolicy.shouldRetry(error, 4)).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('getRetryDelay', () => {
|
|
it('should return correct delay for each attempt', () => {
|
|
expect(retryPolicy.getRetryDelay(1)).toBe(1000);
|
|
expect(retryPolicy.getRetryDelay(2)).toBe(2000);
|
|
expect(retryPolicy.getRetryDelay(3)).toBe(4000);
|
|
});
|
|
|
|
it('should cap delay at maximum for high attempts', () => {
|
|
expect(retryPolicy.getRetryDelay(4)).toBe(4000);
|
|
expect(retryPolicy.getRetryDelay(10)).toBe(4000);
|
|
});
|
|
});
|
|
}); |