Files
Profa 2253d05618 feat: flatten project structure — archive v2, promote v3 to root
Remove the nested v3/ subdirectory by moving all active source code,
tests, configs, and assets directly to the project root. Archive v2/
(dead code, 847MB) to v2-archive.tar.gz.

Key changes:
- Move v3/src/ → src/, v3/tests/ → tests/, v3/packages/ → packages/
- Move v3/tsconfig.json, v3/vitest.config.ts to root
- Merge v3/package.json into root package.json (ESM, scripts, deps, exports)
- Merge v3/scripts/ into scripts/, v3/implementation/ into docs/implementation/
- Update all CI workflows to remove "cd v3 &&" and v3/ path prefixes
- Fix all init installer path resolution (3-level-up → 2-level-up)
- Fix hooks to use "node ./dist/cli/bundle.js" instead of bare "aqe"
- Update settings.json ADR/DDD directories to docs/implementation/
- Fix helper scripts (statusline, adr-compliance, ddd-tracker, guidance-hooks)
- Update 58 agent definitions: aqe/v3/ → aqe/ memory namespaces
- Update 15 guidance shards: v3/src/ → src/ path references
- Fix sync interfaces: remove v3/ and v2/ path references
- Fix test path calculations (process.cwd() + '..' no longer needed)
- Fix flaky perf test threshold (500ms → 1000ms for CI environments)
- Fix complexity-analyzer test to match ADR-051 keyword-based eligibility

Build passes, 17,901 tests pass, CLI and MCP bundles verified.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 11:35:41 +00:00

397 lines
14 KiB
TypeScript

/**
* LLM Router CLI Commands Tests
* ADR-043: Vendor-Independent LLM Support - Milestone 10
*/
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
import { createLLMRouterCommand } from '../../../../src/cli/commands/llm-router.js';
import type { Command } from 'commander';
// Mock console.log and console.error for output testing
let mockConsoleLog: ReturnType<typeof vi.spyOn>;
let mockConsoleError: ReturnType<typeof vi.spyOn>;
let mockExit: ReturnType<typeof vi.spyOn>;
// Helper to parse command and execute it
async function executeCommand(
cmd: Command,
args: string[],
): Promise<void> {
// Configure commander to not exit on error
cmd.exitOverride();
cmd.configureOutput({
writeOut: () => {},
writeErr: () => {},
});
// Parse will trigger the action - use 'node' as default (first two items are node and script)
await cmd.parseAsync(args, { from: 'user' });
}
describe('LLM Router CLI Commands', () => {
let command: ReturnType<typeof createLLMRouterCommand>;
beforeEach(() => {
command = createLLMRouterCommand();
mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {});
mockConsoleError = vi.spyOn(console, 'error').mockImplementation(() => {});
mockExit = vi.spyOn(process, 'exit').mockImplementation((() => {}) as never);
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('createLLMRouterCommand', () => {
it('should create a command with name "llm"', () => {
expect(command.name()).toBe('llm');
});
it('should have a description', () => {
expect(command.description()).toBeTruthy();
expect(command.description()).toContain('LLM Router');
});
it('should have subcommands', () => {
const subcommandNames = command.commands.map(c => c.name());
expect(subcommandNames).toContain('providers');
expect(subcommandNames).toContain('models');
expect(subcommandNames).toContain('route');
expect(subcommandNames).toContain('config');
expect(subcommandNames).toContain('health');
expect(subcommandNames).toContain('cost');
});
});
describe('providers subcommand', () => {
it('should exist and have correct options', () => {
const providersCmd = command.commands.find(c => c.name() === 'providers');
expect(providersCmd).toBeDefined();
const options = providersCmd!.options.map(o => o.long);
expect(options).toContain('--json');
expect(options).toContain('--verbose');
});
it('should list all provider types via parse', async () => {
await executeCommand(command, ['providers', '--json']);
expect(mockConsoleLog).toHaveBeenCalled();
// Get the JSON output
const jsonOutput = mockConsoleLog.mock.calls[0][0];
const providers = JSON.parse(jsonOutput);
expect(Array.isArray(providers)).toBe(true);
expect(providers.length).toBeGreaterThan(0);
// Check for expected providers
const providerNames = providers.map((p: { provider: string }) => p.provider);
expect(providerNames).toContain('claude');
expect(providerNames).toContain('openai');
expect(providerNames).toContain('ollama');
});
});
describe('models subcommand', () => {
it('should exist and have correct options', () => {
const modelsCmd = command.commands.find(c => c.name() === 'models');
expect(modelsCmd).toBeDefined();
const options = modelsCmd!.options.map(o => o.long);
expect(options).toContain('--provider');
expect(options).toContain('--tier');
expect(options).toContain('--json');
});
it('should list models as JSON', async () => {
await executeCommand(command, ['models', '--json']);
expect(mockConsoleLog).toHaveBeenCalled();
const jsonOutput = mockConsoleLog.mock.calls[0][0];
const models = JSON.parse(jsonOutput);
expect(Array.isArray(models)).toBe(true);
expect(models.length).toBeGreaterThan(0);
// Check model structure
const firstModel = models[0];
expect(firstModel).toHaveProperty('canonicalId');
expect(firstModel).toHaveProperty('tier');
expect(firstModel).toHaveProperty('family');
});
it('should filter models by provider', async () => {
await executeCommand(command, ['models', '--json', '--provider', 'claude']);
const jsonOutput = mockConsoleLog.mock.calls[0][0];
const models = JSON.parse(jsonOutput);
expect(models.length).toBeGreaterThan(0);
// All returned models should have Claude provider ID
for (const model of models) {
expect(model.providerIds).toHaveProperty('claude');
}
});
it('should filter models by tier', async () => {
await executeCommand(command, ['models', '--json', '--tier', 'flagship']);
const jsonOutput = mockConsoleLog.mock.calls[0][0];
const models = JSON.parse(jsonOutput);
expect(models.length).toBeGreaterThan(0);
// All returned models should be flagship tier
for (const model of models) {
expect(model.tier).toBe('flagship');
}
});
});
describe('route subcommand', () => {
it('should exist and have correct arguments and options', () => {
const routeCmd = command.commands.find(c => c.name() === 'route');
expect(routeCmd).toBeDefined();
// Check for task argument
// eslint-disable-next-line @typescript-eslint/no-explicit-any
expect((routeCmd as any).registeredArguments.length).toBe(1);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
expect((routeCmd as any).registeredArguments[0].name()).toBe('task');
const options = routeCmd!.options.map(o => o.long);
expect(options).toContain('--agent');
expect(options).toContain('--complexity');
expect(options).toContain('--mode');
expect(options).toContain('--json');
});
it('should route a security task to Claude', async () => {
await executeCommand(command, ['route', 'security vulnerability scan', '--json']);
const jsonOutput = mockConsoleLog.mock.calls[0][0];
const decision = JSON.parse(jsonOutput);
expect(decision).toHaveProperty('provider');
expect(decision).toHaveProperty('model');
expect(decision).toHaveProperty('reason');
expect(decision).toHaveProperty('confidence');
expect(decision.provider).toBe('claude');
});
it('should route with agent type consideration', async () => {
await executeCommand(command, ['route', 'analyze code', '--json', '--agent', 'security-auditor']);
const jsonOutput = mockConsoleLog.mock.calls[0][0];
const decision = JSON.parse(jsonOutput);
expect(decision.provider).toBe('claude');
expect(decision.reason).toContain('Security');
});
it('should route simple tasks to efficient models', async () => {
await executeCommand(command, [
'route',
'simple formatting task',
'--json',
'--complexity',
'trivial',
'--mode',
'cost-optimized',
]);
const jsonOutput = mockConsoleLog.mock.calls[0][0];
const decision = JSON.parse(jsonOutput);
// Should select cost-effective model
expect(decision.model).toContain('mini');
});
});
describe('config subcommand', () => {
it('should exist and have correct options', () => {
const configCmd = command.commands.find(c => c.name() === 'config');
expect(configCmd).toBeDefined();
const options = configCmd!.options.map(o => o.long);
expect(options).toContain('--set');
expect(options).toContain('--json');
});
it('should output configuration as JSON', async () => {
await executeCommand(command, ['config', '--json']);
const jsonOutput = mockConsoleLog.mock.calls[0][0];
const config = JSON.parse(jsonOutput);
expect(config).toHaveProperty('mode');
expect(config).toHaveProperty('defaultProvider');
expect(config).toHaveProperty('defaultModel');
expect(config).toHaveProperty('enableMetrics');
});
it('should set and persist configuration value', async () => {
// Set mode to cost-optimized
await executeCommand(command, ['config', '--set', 'mode=cost-optimized']);
expect(mockConsoleLog).toHaveBeenCalled();
// Verify the setting was applied
mockConsoleLog.mockClear();
// Create new command instance to verify persistence (in-memory for tests)
const newCommand = createLLMRouterCommand();
await executeCommand(newCommand, ['config', '--json']);
const jsonOutput = mockConsoleLog.mock.calls[0][0];
const config = JSON.parse(jsonOutput);
expect(config.mode).toBe('cost-optimized');
});
});
describe('health subcommand', () => {
it('should exist and have correct options', () => {
const healthCmd = command.commands.find(c => c.name() === 'health');
expect(healthCmd).toBeDefined();
const options = healthCmd!.options.map(o => o.long);
expect(options).toContain('--json');
expect(options).toContain('--timeout');
});
it('should return health status for all providers', async () => {
await executeCommand(command, ['health', '--json']);
const jsonOutput = mockConsoleLog.mock.calls[0][0];
const healthResults = JSON.parse(jsonOutput);
expect(Array.isArray(healthResults)).toBe(true);
expect(healthResults.length).toBeGreaterThan(0);
// Check structure of health result
const firstResult = healthResults[0];
expect(firstResult).toHaveProperty('provider');
expect(firstResult).toHaveProperty('healthy');
});
});
describe('cost subcommand', () => {
it('should exist and have correct arguments and options', () => {
const costCmd = command.commands.find(c => c.name() === 'cost');
expect(costCmd).toBeDefined();
// Check for model argument
// eslint-disable-next-line @typescript-eslint/no-explicit-any
expect((costCmd as any).registeredArguments.length).toBe(1);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
expect((costCmd as any).registeredArguments[0].name()).toBe('model');
const options = costCmd!.options.map(o => o.long);
expect(options).toContain('--tokens');
expect(options).toContain('--json');
});
it('should estimate cost for a known model', async () => {
await executeCommand(command, ['cost', 'claude-sonnet-4', '--json', '--tokens', '10000']);
const jsonOutput = mockConsoleLog.mock.calls[0][0];
const costEstimate = JSON.parse(jsonOutput);
expect(costEstimate).toHaveProperty('model');
expect(costEstimate).toHaveProperty('tokens');
expect(costEstimate).toHaveProperty('inputCostPer1M');
expect(costEstimate).toHaveProperty('outputCostPer1M');
expect(costEstimate).toHaveProperty('estimatedTotalCost');
expect(costEstimate.tokens).toBe(10000);
});
it('should error for unknown model', async () => {
// Unknown model triggers console.error and process.exit
await executeCommand(command, ['cost', 'unknown-model-xyz', '--json']);
// Expected - console.error was called
expect(mockConsoleError).toHaveBeenCalled();
});
});
describe('command help', () => {
it('should have help text with subcommands listed', () => {
const helpText = command.helpInformation();
expect(helpText).toContain('providers');
expect(helpText).toContain('models');
expect(helpText).toContain('route');
expect(helpText).toContain('config');
expect(helpText).toContain('health');
expect(helpText).toContain('cost');
});
});
});
describe('LLM Router Integration', () => {
beforeEach(() => {
mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {});
mockConsoleError = vi.spyOn(console, 'error').mockImplementation(() => {});
mockExit = vi.spyOn(process, 'exit').mockImplementation((() => {}) as never);
});
afterEach(() => {
vi.restoreAllMocks();
});
it('should support all 8 provider types', async () => {
const expectedProviders = [
'claude',
'openai',
'ollama',
'openrouter',
'gemini',
'azure-openai',
'bedrock',
'onnx',
];
const command = createLLMRouterCommand();
await executeCommand(command, ['providers', '--json']);
const jsonOutput = mockConsoleLog.mock.calls[0][0];
const providers = JSON.parse(jsonOutput);
const providerNames = providers.map((p: { provider: string }) => p.provider);
for (const expected of expectedProviders) {
expect(providerNames).toContain(expected);
}
});
it('should support 4 routing modes', async () => {
const modes = ['manual', 'rule-based', 'cost-optimized', 'performance-optimized'];
for (const mode of modes) {
mockConsoleLog.mockClear();
const command = createLLMRouterCommand();
await executeCommand(command, ['route', 'test task', '--json', '--mode', mode]);
expect(mockConsoleLog).toHaveBeenCalled();
const jsonOutput = mockConsoleLog.mock.calls[0][0];
const decision = JSON.parse(jsonOutput);
expect(decision).toHaveProperty('provider');
expect(decision).toHaveProperty('model');
}
});
it('should provide accurate cost estimates', async () => {
const command = createLLMRouterCommand();
await executeCommand(command, ['cost', 'gpt-4o-mini', '--json', '--tokens', '1000000']);
const jsonOutput = mockConsoleLog.mock.calls[0][0];
const costEstimate = JSON.parse(jsonOutput);
// GPT-4o-mini pricing: $0.15 input, $0.6 output per 1M tokens
expect(costEstimate.inputCostPer1M).toBe(0.15);
expect(costEstimate.outputCostPer1M).toBe(0.6);
// At 1M tokens, cost should be: 0.15 + 0.6 = 0.75
expect(costEstimate.estimatedTotalCost).toBeCloseTo(0.75, 2);
});
});