Files
qiming/qimingclaw/crates/agent-gui-server/tests/config/config.test.ts

200 lines
6.7 KiB
TypeScript

/**
* Unit tests for config.ts — required field validation, defaults, range checks.
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { loadConfig } from '../../src/config.js';
// Suppress logger output during tests
vi.mock('../../src/utils/logger.js', () => ({
logInfo: vi.fn(),
logWarn: vi.fn(),
logError: vi.fn(),
logDebug: vi.fn(),
}));
describe('loadConfig', () => {
const originalEnv = { ...process.env };
beforeEach(() => {
// Clear all GUI_AGENT_* env vars
Object.keys(process.env).forEach(key => {
if (key.startsWith('GUI_AGENT_')) delete process.env[key];
});
});
afterEach(() => {
process.env = { ...originalEnv };
});
// --- Required field validation ---
it('throws ConfigError when API_KEY is missing', () => {
expect(() => loadConfig()).toThrow('GUI_AGENT_API_KEY is required');
});
it('accepts API_KEY from env', () => {
process.env.GUI_AGENT_API_KEY = 'test-key';
const config = loadConfig();
expect(config.apiKey).toBe('test-key');
});
it('accepts API_KEY from overrides', () => {
const config = loadConfig({ apiKey: 'override-key' });
expect(config.apiKey).toBe('override-key');
});
// --- Default values ---
it('has correct defaults', () => {
const config = loadConfig({ apiKey: 'test-key' });
expect(config.provider).toBe('anthropic');
expect(config.apiProtocol).toBe('anthropic');
expect(config.model).toBe('claude-sonnet-4-20250514');
expect(config.port).toBe(60008);
expect(config.transport).toBe('http');
expect(config.maxSteps).toBe(50);
expect(config.stepDelayMs).toBe(1500);
expect(config.stuckThreshold).toBe(3);
expect(config.jpegQuality).toBe(75);
expect(config.displayIndex).toBe(0);
expect(config.coordinateMode).toBeUndefined();
expect(config.baseUrl).toBeUndefined();
expect(config.memoryProvider).toBeUndefined();
expect(config.memoryModel).toBeUndefined();
expect(config.logFile).toBeUndefined();
});
// --- Transport validation ---
it('throws on invalid transport', () => {
expect(() => loadConfig({ apiKey: 'test-key', transport: 'websocket' as any }))
.toThrow('GUI_AGENT_TRANSPORT must be "http" or "stdio"');
});
it('accepts stdio transport', () => {
const config = loadConfig({ apiKey: 'test-key', transport: 'stdio' });
expect(config.transport).toBe('stdio');
});
// --- Numeric range validation ---
it('throws when maxSteps exceeds range', () => {
process.env.GUI_AGENT_API_KEY = 'test-key';
process.env.GUI_AGENT_MAX_STEPS = '999';
expect(() => loadConfig()).toThrow('GUI_AGENT_MAX_STEPS must be between 1 and 200');
});
it('throws when maxSteps is below range', () => {
process.env.GUI_AGENT_API_KEY = 'test-key';
process.env.GUI_AGENT_MAX_STEPS = '0';
expect(() => loadConfig()).toThrow('GUI_AGENT_MAX_STEPS must be between 1 and 200');
});
it('throws when jpegQuality exceeds range', () => {
process.env.GUI_AGENT_API_KEY = 'test-key';
process.env.GUI_AGENT_JPEG_QUALITY = '101';
expect(() => loadConfig()).toThrow('GUI_AGENT_JPEG_QUALITY must be between 1 and 100');
});
it('throws when stepDelayMs exceeds range', () => {
process.env.GUI_AGENT_API_KEY = 'test-key';
process.env.GUI_AGENT_STEP_DELAY_MS = '50000';
expect(() => loadConfig()).toThrow('GUI_AGENT_STEP_DELAY_MS must be between 100 and 30000');
});
it('throws when port is invalid', () => {
process.env.GUI_AGENT_API_KEY = 'test-key';
process.env.GUI_AGENT_PORT = '99999';
expect(() => loadConfig()).toThrow('GUI_AGENT_PORT must be between 1 and 65535');
});
it('accepts valid numeric values', () => {
const config = loadConfig({
apiKey: 'test-key',
maxSteps: 100,
jpegQuality: 50,
stepDelayMs: 2000,
port: 8080,
});
expect(config.maxSteps).toBe(100);
expect(config.jpegQuality).toBe(50);
expect(config.stepDelayMs).toBe(2000);
expect(config.port).toBe(8080);
});
// --- apiProtocol validation ---
it('throws on invalid apiProtocol', () => {
expect(() => loadConfig({ apiKey: 'test-key', apiProtocol: 'grpc' as any }))
.toThrow('GUI_AGENT_API_PROTOCOL must be "anthropic" or "openai"');
});
it('accepts openai apiProtocol', () => {
const config = loadConfig({ apiKey: 'test-key', apiProtocol: 'openai' });
expect(config.apiProtocol).toBe('openai');
});
it('reads apiProtocol from env', () => {
process.env.GUI_AGENT_API_KEY = 'test-key';
process.env.GUI_AGENT_API_PROTOCOL = 'openai';
const config = loadConfig();
expect(config.apiProtocol).toBe('openai');
});
it('overrides take precedence for apiProtocol', () => {
process.env.GUI_AGENT_API_KEY = 'test-key';
process.env.GUI_AGENT_API_PROTOCOL = 'openai';
const config = loadConfig({ apiKey: 'test-key', apiProtocol: 'anthropic' });
expect(config.apiProtocol).toBe('anthropic');
});
// --- coordinateMode validation ---
it('throws on invalid coordinateMode', () => {
expect(() => loadConfig({ apiKey: 'test-key', coordinateMode: 'foobar' as any }))
.toThrow('GUI_AGENT_COORDINATE_MODE must be one of');
});
it('accepts valid coordinateMode', () => {
const config = loadConfig({ apiKey: 'test-key', coordinateMode: 'normalized-1000' });
expect(config.coordinateMode).toBe('normalized-1000');
});
it('treats "auto" coordinateMode as undefined (auto-detect)', () => {
const config = loadConfig({ apiKey: 'test-key', coordinateMode: 'auto' as any });
expect(config.coordinateMode).toBeUndefined();
});
// --- Environment variable overrides ---
it('reads all env vars', () => {
process.env.GUI_AGENT_API_KEY = 'env-key';
process.env.GUI_AGENT_PROVIDER = 'openai';
process.env.GUI_AGENT_MODEL = 'gpt-4o';
process.env.GUI_AGENT_BASE_URL = 'https://example.com';
process.env.GUI_AGENT_MEMORY_PROVIDER = 'anthropic';
process.env.GUI_AGENT_MEMORY_MODEL = 'haiku';
process.env.GUI_AGENT_LOG_FILE = '/tmp/gui.log';
const config = loadConfig();
expect(config.provider).toBe('openai');
expect(config.model).toBe('gpt-4o');
expect(config.baseUrl).toBe('https://example.com');
expect(config.memoryProvider).toBe('anthropic');
expect(config.memoryModel).toBe('haiku');
expect(config.logFile).toBe('/tmp/gui.log');
});
// --- Overrides take precedence over env ---
it('overrides take precedence over env vars', () => {
process.env.GUI_AGENT_API_KEY = 'env-key';
process.env.GUI_AGENT_MODEL = 'env-model';
const config = loadConfig({ apiKey: 'override-key', model: 'override-model' });
expect(config.apiKey).toBe('override-key');
expect(config.model).toBe('override-model');
});
});