import { describe, it, expect } from 'vitest'; import { configSchema } from './schema.js'; describe('configSchema — sandbox', () => { const minimalConfig = { telegram: { bot_token: 'test', allowed_chat_ids: [1] }, models: { default: { provider: 'anthropic', model: 'claude-3' } }, }; it('defaults sandbox to disabled', () => { const result = configSchema.parse(minimalConfig); expect(result.sandbox.enabled).toBe(false); expect(result.sandbox.image).toBe('node:22-slim'); expect(result.sandbox.network).toBe('none'); expect(result.sandbox.memory_limit).toBe('512m'); expect(result.sandbox.cpu_limit).toBe('1.0'); expect(result.sandbox.timeout_seconds).toBe(300); }); it('accepts sandbox config', () => { const result = configSchema.parse({ ...minimalConfig, sandbox: { enabled: true, image: 'ubuntu:24.04', network: 'bridge' }, }); expect(result.sandbox.enabled).toBe(true); expect(result.sandbox.image).toBe('ubuntu:24.04'); expect(result.sandbox.network).toBe('bridge'); }); }); describe('configSchema — agent_configs', () => { const minimalConfig = { telegram: { bot_token: 'test', allowed_chat_ids: [1] }, models: { default: { provider: 'anthropic', model: 'claude-3' } }, }; it('defaults agent_configs to empty', () => { const result = configSchema.parse(minimalConfig); expect(result.agent_configs).toEqual({}); }); it('accepts named agent configs', () => { const result = configSchema.parse({ ...minimalConfig, agent_configs: { assistant: { system_prompt: 'You are helpful.', model_tier: 'default', tool_profile: 'messaging', }, coder: { model_tier: 'complex', tool_profile: 'coding', sandbox: true, }, }, }); expect(result.agent_configs.assistant.system_prompt).toBe('You are helpful.'); expect(result.agent_configs.assistant.tool_profile).toBe('messaging'); expect(result.agent_configs.coder.sandbox).toBe(true); }); }); describe('configSchema — routing', () => { const minimalConfig = { telegram: { bot_token: 'test', allowed_chat_ids: [1] }, models: { default: { provider: 'anthropic', model: 'claude-3' } }, }; it('defaults routing to empty', () => { const result = configSchema.parse(minimalConfig); expect(result.routing.default_agent).toBeUndefined(); expect(result.routing.channels).toEqual({}); expect(result.routing.senders).toEqual({}); }); it('accepts routing config', () => { const result = configSchema.parse({ ...minimalConfig, routing: { default_agent: 'assistant', channels: { discord: 'coder' }, senders: { 'telegram:12345': 'coder' }, }, }); expect(result.routing.default_agent).toBe('assistant'); expect(result.routing.channels.discord).toBe('coder'); expect(result.routing.senders['telegram:12345']).toBe('coder'); }); }); describe('configSchema — per-tier fallback', () => { const minimalConfig = { telegram: { bot_token: 'test', allowed_chat_ids: [1] }, models: { default: { provider: 'anthropic', model: 'claude-3' } }, }; it('accepts per-tier fallback config', () => { const result = configSchema.parse({ ...minimalConfig, models: { default: { provider: 'anthropic', model: 'claude-sonnet-4-5-20250929', fallback: { provider: 'github', model: 'claude-sonnet-4-5-20250929' }, }, fast: { provider: 'anthropic', model: 'claude-haiku-4-5-20251001', fallback: { provider: 'github', model: 'claude-haiku-4-5-20251001' }, }, }, }); expect(result.models.default.fallback?.provider).toBe('github'); expect(result.models.fast?.fallback?.provider).toBe('github'); }); it('works without fallback field (backward compat)', () => { const result = configSchema.parse(minimalConfig); expect(result.models.default.fallback).toBeUndefined(); }); it('fallback does not itself accept a nested fallback', () => { const result = configSchema.parse({ ...minimalConfig, models: { default: { provider: 'anthropic', model: 'claude-3', fallback: { provider: 'github', model: 'claude-3', fallback: { provider: 'ollama', model: 'llama' }, }, }, }, }); // Zod strips unknown keys from the base schema, so nested fallback is dropped expect((result.models.default.fallback as Record)?.fallback).toBeUndefined(); }); }); describe('configSchema automation', () => { const baseConfig = { telegram: { bot_token: 'test-token', allowed_chat_ids: [123] }, models: { default: { provider: 'anthropic', model: 'claude-sonnet' } }, }; it('accepts config without automation section', () => { const result = configSchema.parse(baseConfig); expect(result.automation).toBeDefined(); expect(result.automation.cron).toEqual([]); }); it('accepts config with cron jobs', () => { const result = configSchema.parse({ ...baseConfig, automation: { cron: [{ name: 'morning-briefing', schedule: '0 9 * * *', message: 'Good morning!', output: { channel: 'telegram', peer: '123' }, }], }, }); expect(result.automation.cron).toHaveLength(1); expect(result.automation.cron[0].name).toBe('morning-briefing'); expect(result.automation.cron[0].enabled).toBe(true); // default }); it('rejects cron job with empty name', () => { expect(() => configSchema.parse({ ...baseConfig, automation: { cron: [{ name: '', schedule: '0 9 * * *', message: 'test', output: { channel: 'telegram', peer: '123' }, }], }, })).toThrow(); }); it('rejects cron job with empty schedule', () => { expect(() => configSchema.parse({ ...baseConfig, automation: { cron: [{ name: 'test', schedule: '', message: 'test', output: { channel: 'telegram', peer: '123' }, }], }, })).toThrow(); }); it('accepts cron job with optional fields', () => { const result = configSchema.parse({ ...baseConfig, automation: { cron: [{ name: 'test', schedule: '0 9 * * *', message: 'test', output: { channel: 'telegram', peer: '123' }, enabled: false, timezone: 'America/New_York', }], }, }); expect(result.automation.cron[0].enabled).toBe(false); expect(result.automation.cron[0].timezone).toBe('America/New_York'); }); });