import { describe, it, expect, beforeAll, beforeEach, afterEach, vi } from 'vitest'; import { initDb, getDb } from '../../db/index.js'; import { encrypt } from '../../lib/crypto.js'; import { getAllPenalties, recordRateLimitHit, routeRequest, setRoutingStrategy, } from '../../services/router.js'; describe('Router', () => { beforeAll(() => { process.env.ENCRYPTION_KEY = '0'.repeat(64); initDb(':memory:'); }); beforeEach(() => { const db = getDb(); // These cases assert the manual priority order specifically; pin it so the // bandit (now the default strategy) doesn't reorder by score. setRoutingStrategy('priority'); db.prepare('DELETE FROM api_keys').run(); // Reset fallback order to intelligence ranking const models = db.prepare('SELECT id, intelligence_rank FROM models ORDER BY intelligence_rank ASC').all() as any[]; const update = db.prepare('UPDATE fallback_config SET priority = ? WHERE model_db_id = ?'); for (let i = 0; i < models.length; i++) { update.run(i + 1, models[i].id); } }); afterEach(() => { vi.useRealTimers(); }); it('should throw when no keys are configured', () => { expect(() => routeRequest()).toThrow(/exhausted/i); }); it('should route to highest priority model with available key', () => { const db = getDb(); const { encrypted, iv, authTag } = encrypt('test-groq-key'); db.prepare(` INSERT INTO api_keys (platform, label, encrypted_key, iv, auth_tag, status, enabled) VALUES (?, ?, ?, ?, ?, ?, ?) `).run('groq', 'test', encrypted, iv, authTag, 'healthy', 1); const result = routeRequest(); expect(result.platform).toBe('groq'); expect(result.apiKey).toBe('test-groq-key'); }); it('should prefer higher-priority model when keys exist for multiple platforms', () => { const db = getDb(); const googleKey = encrypt('test-google-key'); db.prepare(` INSERT INTO api_keys (platform, label, encrypted_key, iv, auth_tag, status, enabled) VALUES (?, ?, ?, ?, ?, ?, ?) `).run('google', 'test', googleKey.encrypted, googleKey.iv, googleKey.authTag, 'healthy', 1); const groqKey = encrypt('test-groq-key'); db.prepare(` INSERT INTO api_keys (platform, label, encrypted_key, iv, auth_tag, status, enabled) VALUES (?, ?, ?, ?, ?, ?, ?) `).run('groq', 'test', groqKey.encrypted, groqKey.iv, groqKey.authTag, 'healthy', 1); // Post-V6: Google's gemini-3.1-pro-preview (rank 1, free-tier-eligible per // probe on 2026-04-25) outranks Groq's best free-tier model openai/gpt-oss-120b // (rank 6). With keys for both platforms, Google wins. const result = routeRequest(); expect(result.platform).toBe('google'); }); it('should skip disabled keys', () => { const db = getDb(); const googleKey = encrypt('test-google-key'); db.prepare(` INSERT INTO api_keys (platform, label, encrypted_key, iv, auth_tag, status, enabled) VALUES (?, ?, ?, ?, ?, ?, ?) `).run('google', 'disabled', googleKey.encrypted, googleKey.iv, googleKey.authTag, 'healthy', 0); const groqKey = encrypt('test-groq-key'); db.prepare(` INSERT INTO api_keys (platform, label, encrypted_key, iv, auth_tag, status, enabled) VALUES (?, ?, ?, ?, ?, ?, ?) `).run('groq', 'test', groqKey.encrypted, groqKey.iv, groqKey.authTag, 'healthy', 1); const result = routeRequest(); expect(result.platform).toBe('groq'); }); it('should skip invalid keys', () => { const db = getDb(); const invalidKey = encrypt('invalid-key'); db.prepare(` INSERT INTO api_keys (platform, label, encrypted_key, iv, auth_tag, status, enabled) VALUES (?, ?, ?, ?, ?, ?, ?) `).run('google', 'invalid', invalidKey.encrypted, invalidKey.iv, invalidKey.authTag, 'invalid', 1); const groqKey = encrypt('test-groq-key'); db.prepare(` INSERT INTO api_keys (platform, label, encrypted_key, iv, auth_tag, status, enabled) VALUES (?, ?, ?, ?, ?, ?, ?) `).run('groq', 'test', groqKey.encrypted, groqKey.iv, groqKey.authTag, 'healthy', 1); const result = routeRequest(); expect(result.platform).toBe('groq'); }); it('skips a model whose context window cannot hold the request (#167)', () => { const db = getDb(); const groqKey = encrypt('test-groq-key'); db.prepare(` INSERT INTO api_keys (platform, label, encrypted_key, iv, auth_tag, status, enabled) VALUES (?, ?, ?, ?, ?, ?, ?) `).run('groq', 'test', groqKey.encrypted, groqKey.iv, groqKey.authTag, 'healthy', 1); // Remove token rate-limit interference so we isolate the context-window // behavior (canUseTokens would otherwise also skip on a large estimate). db.prepare("UPDATE models SET tpm_limit = NULL, tpd_limit = NULL WHERE platform = 'groq'").run(); // Whatever model a small request lands on, give it a tiny context window. const baseline = routeRequest(5); db.prepare('UPDATE models SET context_window = 10 WHERE id = ?').run(baseline.modelDbId); // A small request still lands on it (5 < 10) ... expect(routeRequest(5).modelDbId).toBe(baseline.modelDbId); // ... but a request larger than its window is routed elsewhere (2000 > 10). const large = routeRequest(2000); expect(large.modelDbId).not.toBe(baseline.modelDbId); }); it('still routes a model with an unknown (null) context window (#167)', () => { const db = getDb(); const groqKey = encrypt('test-groq-key'); db.prepare(` INSERT INTO api_keys (platform, label, encrypted_key, iv, auth_tag, status, enabled) VALUES (?, ?, ?, ?, ?, ?, ?) `).run('groq', 'test', groqKey.encrypted, groqKey.iv, groqKey.authTag, 'healthy', 1); db.prepare("UPDATE models SET tpm_limit = NULL, tpd_limit = NULL WHERE platform = 'groq'").run(); // A null context_window means "unknown" — never filtered out, even for a huge request. db.prepare("UPDATE models SET context_window = NULL WHERE platform = 'groq'").run(); expect(() => routeRequest(500000)).not.toThrow(); }); it('should skip keys that cannot be decrypted and use a valid fallback key', () => { const db = getDb(); db.prepare(` INSERT INTO api_keys (platform, label, encrypted_key, iv, auth_tag, status, enabled) VALUES (?, ?, ?, ?, ?, ?, ?) `).run('google', 'corrupt', 'not-hex', 'not-hex', 'not-hex', 'healthy', 1); const groqKey = encrypt('test-groq-key'); db.prepare(` INSERT INTO api_keys (platform, label, encrypted_key, iv, auth_tag, status, enabled) VALUES (?, ?, ?, ?, ?, ?, ?) `).run('groq', 'test', groqKey.encrypted, groqKey.iv, groqKey.authTag, 'healthy', 1); const result = routeRequest(); const corruptKey = db.prepare("SELECT status FROM api_keys WHERE label = 'corrupt'").get() as { status: string }; expect(result.platform).toBe('groq'); expect(result.apiKey).toBe('test-groq-key'); expect(corruptKey.status).toBe('error'); }); it('applies elapsed decay before adding a new 429 penalty', () => { vi.useFakeTimers(); const modelDbId = 987654321; recordRateLimitHit(modelDbId); vi.advanceTimersByTime(10 * 60 * 1000); recordRateLimitHit(modelDbId); expect(getAllPenalties()).toContainEqual({ modelDbId, count: 2, penalty: 3, }); }); });