Files
Codewalkers/apps/server/agent/lifecycle/error-analyzer.test.ts
Lukas May 34578d39c6 refactor: Restructure monorepo to apps/server/ and apps/web/ layout
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
2026-03-03 11:22:53 +01:00

214 lines
7.8 KiB
TypeScript

/**
* ErrorAnalyzer Tests — Verify error classification patterns.
*/
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { AgentErrorAnalyzer } from './error-analyzer.js';
import type { SignalManager } from './signal-manager.js';
describe('AgentErrorAnalyzer', () => {
let errorAnalyzer: AgentErrorAnalyzer;
let mockSignalManager: SignalManager;
beforeEach(() => {
mockSignalManager = {
clearSignal: vi.fn(),
checkSignalExists: vi.fn(),
readSignal: vi.fn(),
waitForSignal: vi.fn(),
validateSignalFile: vi.fn(),
};
errorAnalyzer = new AgentErrorAnalyzer(mockSignalManager);
});
describe('analyzeError', () => {
describe('auth failure detection', () => {
it('should detect unauthorized errors', async () => {
const error = new Error('Unauthorized access');
const result = await errorAnalyzer.analyzeError(error);
expect(result.type).toBe('auth_failure');
expect(result.isTransient).toBe(true);
expect(result.requiresAccountSwitch).toBe(false);
expect(result.shouldPersistToDB).toBe(true);
});
it('should detect invalid token errors', async () => {
const error = new Error('Invalid token provided');
const result = await errorAnalyzer.analyzeError(error);
expect(result.type).toBe('auth_failure');
expect(result.isTransient).toBe(true);
});
it('should detect 401 errors', async () => {
const error = new Error('HTTP 401 - Authentication failed');
const result = await errorAnalyzer.analyzeError(error);
expect(result.type).toBe('auth_failure');
});
it('should detect auth failures in stderr', async () => {
const error = new Error('Process failed');
const stderr = 'Error: Authentication failed - expired token';
const result = await errorAnalyzer.analyzeError(error, null, stderr);
expect(result.type).toBe('auth_failure');
});
});
describe('usage limit detection', () => {
it('should detect rate limit errors', async () => {
const error = new Error('Rate limit exceeded');
const result = await errorAnalyzer.analyzeError(error);
expect(result.type).toBe('usage_limit');
expect(result.isTransient).toBe(false);
expect(result.requiresAccountSwitch).toBe(true);
expect(result.shouldPersistToDB).toBe(true);
});
it('should detect quota exceeded errors', async () => {
const error = new Error('Quota exceeded for this month');
const result = await errorAnalyzer.analyzeError(error);
expect(result.type).toBe('usage_limit');
});
it('should detect 429 errors', async () => {
const error = new Error('HTTP 429 - Too many requests');
const result = await errorAnalyzer.analyzeError(error);
expect(result.type).toBe('usage_limit');
});
it('should detect usage limits in stderr', async () => {
const error = new Error('Request failed');
const stderr = 'API usage limit reached. Try again later.';
const result = await errorAnalyzer.analyzeError(error, null, stderr);
expect(result.type).toBe('usage_limit');
});
});
describe('timeout detection', () => {
it('should detect timeout errors', async () => {
const error = new Error('Request timeout');
const result = await errorAnalyzer.analyzeError(error);
expect(result.type).toBe('timeout');
expect(result.isTransient).toBe(true);
expect(result.requiresAccountSwitch).toBe(false);
});
it('should detect timed out errors', async () => {
const error = new Error('Connection timed out');
const result = await errorAnalyzer.analyzeError(error);
expect(result.type).toBe('timeout');
});
});
describe('missing signal detection', () => {
it('should detect missing signal when process exits successfully', async () => {
vi.mocked(mockSignalManager.checkSignalExists).mockResolvedValue(false);
const error = new Error('No output');
const result = await errorAnalyzer.analyzeError(error, 0, undefined, '/agent/workdir');
expect(result.type).toBe('missing_signal');
expect(result.isTransient).toBe(true);
expect(result.requiresAccountSwitch).toBe(false);
expect(result.shouldPersistToDB).toBe(false);
expect(mockSignalManager.checkSignalExists).toHaveBeenCalledWith('/agent/workdir');
});
it('should not detect missing signal when signal exists', async () => {
vi.mocked(mockSignalManager.checkSignalExists).mockResolvedValue(true);
const error = new Error('No output');
const result = await errorAnalyzer.analyzeError(error, 0, undefined, '/agent/workdir');
expect(result.type).toBe('unknown');
});
it('should not detect missing signal for non-zero exit codes', async () => {
const error = new Error('Process failed');
const result = await errorAnalyzer.analyzeError(error, 1, undefined, '/agent/workdir');
expect(result.type).toBe('process_crash');
});
});
describe('process crash detection', () => {
it('should detect crashes with non-zero exit code', async () => {
const error = new Error('Process exited with code 1');
const result = await errorAnalyzer.analyzeError(error, 1);
expect(result.type).toBe('process_crash');
expect(result.exitCode).toBe(1);
expect(result.shouldPersistToDB).toBe(true);
});
it('should detect transient crashes based on exit code', async () => {
const error = new Error('Process interrupted');
const result = await errorAnalyzer.analyzeError(error, 130); // SIGINT
expect(result.type).toBe('process_crash');
expect(result.isTransient).toBe(true);
});
it('should detect signal-based crashes as transient', async () => {
const error = new Error('Segmentation fault');
const result = await errorAnalyzer.analyzeError(error, 139); // SIGSEGV (128+11, signal-based)
expect(result.type).toBe('process_crash');
expect(result.isTransient).toBe(true); // signal-based exit codes (128-255) are transient
});
it('should detect transient patterns in stderr', async () => {
const error = new Error('Process failed');
const stderr = 'Network error: connection refused';
const result = await errorAnalyzer.analyzeError(error, 1, stderr);
expect(result.type).toBe('process_crash');
expect(result.isTransient).toBe(true);
});
});
describe('unknown error handling', () => {
it('should classify unrecognized errors as unknown', async () => {
const error = new Error('Something very weird happened');
const result = await errorAnalyzer.analyzeError(error);
expect(result.type).toBe('unknown');
expect(result.isTransient).toBe(false);
expect(result.requiresAccountSwitch).toBe(false);
expect(result.shouldPersistToDB).toBe(true);
});
it('should handle string errors', async () => {
const result = await errorAnalyzer.analyzeError('String error message');
expect(result.type).toBe('unknown');
expect(result.message).toBe('String error message');
});
});
describe('error context preservation', () => {
it('should preserve original error object', async () => {
const originalError = new Error('Test error');
const result = await errorAnalyzer.analyzeError(originalError);
expect(result.originalError).toBe(originalError);
});
it('should preserve exit code and signal', async () => {
const error = new Error('Process failed');
const result = await errorAnalyzer.analyzeError(error, 42, 'stderr output');
expect(result.exitCode).toBe(42);
});
});
});
});