Files
proffesor-for-testing__agen…/tests/unit/shared/llm/model-mapping.test.ts
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

406 lines
14 KiB
TypeScript

/**
* Agentic QE v3 - Model Mapping Unit Tests
* ADR-043: Vendor-Independent LLM Support (Milestone 2)
*/
import { describe, it, expect } from 'vitest';
import {
mapModelId,
normalizeModelId,
getCanonicalName,
getModelFamily,
getModelTier,
isModelAvailableOnProvider,
getSupportedProviders,
listCanonicalModels,
listModelsByProvider,
listModelsByFamily,
listModelsByTier,
getModelMapping,
MODEL_MAPPINGS,
type ProviderType,
} from '../../../../src/shared/llm/model-mapping';
describe('Model Mapping', () => {
describe('mapModelId', () => {
it('should map canonical claude model to anthropic provider', () => {
const result = mapModelId('claude-sonnet-4-5', 'anthropic');
expect(result).toBe('claude-sonnet-4-5-20250929');
});
it('should map canonical claude model to openrouter provider', () => {
const result = mapModelId('claude-sonnet-4-5', 'openrouter');
expect(result).toBe('anthropic/claude-sonnet-4.5');
});
it('should map canonical claude model to bedrock provider', () => {
const result = mapModelId('claude-sonnet-4-5', 'bedrock');
expect(result).toBe('anthropic.claude-sonnet-4-5-v2:0');
});
it('should map gpt-4o to openai provider', () => {
const result = mapModelId('gpt-4o', 'openai');
expect(result).toBe('gpt-4o');
});
it('should map gpt-4o to azure provider', () => {
const result = mapModelId('gpt-4o', 'azure');
expect(result).toBe('gpt-4o');
});
it('should map gpt-4o to openrouter provider', () => {
const result = mapModelId('gpt-4o', 'openrouter');
expect(result).toBe('openai/gpt-4o');
});
it('should map gemini-pro to gemini provider', () => {
const result = mapModelId('gemini-pro', 'gemini');
expect(result).toBe('gemini-pro');
});
it('should map gemini-pro to openrouter provider', () => {
const result = mapModelId('gemini-pro', 'openrouter');
expect(result).toBe('google/gemini-pro');
});
it('should throw error for unsupported provider', () => {
expect(() => mapModelId('claude-sonnet-4-5', 'gemini')).toThrow(
"Model 'claude-sonnet-4-5' (Claude Sonnet 4.5) is not available on provider 'gemini'"
);
});
it('should throw error for unknown model', () => {
expect(() => mapModelId('unknown-model', 'anthropic')).toThrow(
'Unknown model ID: unknown-model'
);
});
it('should handle provider-specific ID as input', () => {
// Map from OpenRouter format to Bedrock
const result = mapModelId('anthropic/claude-sonnet-4.5', 'bedrock');
expect(result).toBe('anthropic.claude-sonnet-4-5-v2:0');
});
it('should handle Anthropic API format as input', () => {
const result = mapModelId('claude-sonnet-4-5-20250929', 'openrouter');
expect(result).toBe('anthropic/claude-sonnet-4.5');
});
});
describe('normalizeModelId', () => {
it('should normalize anthropic API model ID', () => {
const result = normalizeModelId('claude-sonnet-4-5-20250929');
expect(result).toBe('claude-sonnet-4-5');
});
it('should normalize openrouter model ID', () => {
const result = normalizeModelId('anthropic/claude-sonnet-4.5');
expect(result).toBe('claude-sonnet-4-5');
});
it('should normalize openrouter gpt model ID', () => {
const result = normalizeModelId('openai/gpt-4o');
expect(result).toBe('gpt-4o');
});
it('should return canonical ID unchanged', () => {
const result = normalizeModelId('claude-sonnet-4-5');
expect(result).toBe('claude-sonnet-4-5');
});
it('should handle case-insensitive matching', () => {
const result = normalizeModelId('CLAUDE-SONNET-4-5');
expect(result).toBe('claude-sonnet-4-5');
});
it('should throw for unknown model ID', () => {
expect(() => normalizeModelId('not-a-real-model')).toThrow(
'Unknown model ID: not-a-real-model'
);
});
it('should normalize bedrock model ID', () => {
const result = normalizeModelId('anthropic.claude-sonnet-4-5-v2:0');
expect(result).toBe('claude-sonnet-4-5');
});
it('should normalize gemini model ID', () => {
const result = normalizeModelId('gemini-1.5-pro');
expect(result).toBe('gemini-pro-1.5');
});
});
describe('getCanonicalName', () => {
it('should return human-readable name for claude model', () => {
const result = getCanonicalName('claude-sonnet-4-5');
expect(result).toBe('Claude Sonnet 4.5');
});
it('should return human-readable name for gpt model', () => {
const result = getCanonicalName('gpt-4o');
expect(result).toBe('GPT-4o');
});
it('should return human-readable name for gemini model', () => {
const result = getCanonicalName('gemini-pro');
expect(result).toBe('Gemini Pro');
});
it('should work with provider-specific IDs', () => {
const result = getCanonicalName('anthropic/claude-opus-4');
expect(result).toBe('Claude Opus 4');
});
it('should return input for unknown models', () => {
const result = getCanonicalName('unknown-model');
expect(result).toBe('unknown-model');
});
});
describe('getModelFamily', () => {
it('should return claude family for claude models', () => {
expect(getModelFamily('claude-sonnet-4-5')).toBe('claude');
expect(getModelFamily('claude-opus-4')).toBe('claude');
expect(getModelFamily('claude-haiku-3-5')).toBe('claude');
});
it('should return gpt family for openai models', () => {
expect(getModelFamily('gpt-4o')).toBe('gpt');
expect(getModelFamily('gpt-4o-mini')).toBe('gpt');
expect(getModelFamily('o1')).toBe('gpt');
});
it('should return gemini family for google models', () => {
expect(getModelFamily('gemini-pro')).toBe('gemini');
expect(getModelFamily('gemini-flash-1.5')).toBe('gemini');
});
it('should return llama family for meta models', () => {
expect(getModelFamily('llama3')).toBe('llama');
expect(getModelFamily('llama3.1')).toBe('llama');
expect(getModelFamily('codellama')).toBe('llama');
});
it('should return unknown for unrecognized models', () => {
expect(getModelFamily('not-a-model')).toBe('unknown');
});
});
describe('getModelTier', () => {
it('should return correct tier for flagship models', () => {
expect(getModelTier('claude-opus-4')).toBe('flagship');
expect(getModelTier('claude-opus-4-5')).toBe('flagship');
expect(getModelTier('o1')).toBe('flagship');
});
it('should return correct tier for standard models', () => {
expect(getModelTier('claude-sonnet-4-5')).toBe('standard');
expect(getModelTier('gpt-4o')).toBe('standard');
expect(getModelTier('gemini-pro-1.5')).toBe('standard');
});
it('should return correct tier for economy models', () => {
expect(getModelTier('claude-haiku-3-5')).toBe('economy');
expect(getModelTier('gpt-4o-mini')).toBe('economy');
expect(getModelTier('llama3')).toBe('economy');
});
it('should return standard as default for unknown models', () => {
expect(getModelTier('unknown-model')).toBe('standard');
});
});
describe('isModelAvailableOnProvider', () => {
it('should return true for available models', () => {
expect(isModelAvailableOnProvider('claude-sonnet-4-5', 'anthropic')).toBe(true);
expect(isModelAvailableOnProvider('claude-sonnet-4-5', 'openrouter')).toBe(true);
expect(isModelAvailableOnProvider('gpt-4o', 'openai')).toBe(true);
expect(isModelAvailableOnProvider('gpt-4o', 'azure')).toBe(true);
});
it('should return false for unavailable models', () => {
expect(isModelAvailableOnProvider('claude-sonnet-4-5', 'gemini')).toBe(false);
expect(isModelAvailableOnProvider('gpt-4o', 'anthropic')).toBe(false);
expect(isModelAvailableOnProvider('gemini-pro', 'anthropic')).toBe(false);
});
it('should return false for unknown models', () => {
expect(isModelAvailableOnProvider('unknown-model', 'anthropic')).toBe(false);
});
});
describe('getSupportedProviders', () => {
it('should return all providers for claude models', () => {
const providers = getSupportedProviders('claude-sonnet-4-5');
expect(providers).toContain('anthropic');
expect(providers).toContain('openrouter');
expect(providers).toContain('bedrock');
expect(providers).not.toContain('gemini');
});
it('should return all providers for gpt models', () => {
const providers = getSupportedProviders('gpt-4o');
expect(providers).toContain('openai');
expect(providers).toContain('azure');
expect(providers).toContain('openrouter');
});
it('should return empty array for unknown models', () => {
const providers = getSupportedProviders('unknown-model');
expect(providers).toEqual([]);
});
});
describe('listCanonicalModels', () => {
it('should return all canonical model IDs', () => {
const models = listCanonicalModels();
expect(models).toContain('claude-sonnet-4-5');
expect(models).toContain('gpt-4o');
expect(models).toContain('gemini-pro');
expect(models).toContain('llama3');
expect(models.length).toBeGreaterThan(15);
});
});
describe('listModelsByProvider', () => {
it('should list anthropic models', () => {
const models = listModelsByProvider('anthropic');
expect(models).toContain('claude-sonnet-4-5');
expect(models).toContain('claude-opus-4');
expect(models).not.toContain('gpt-4o');
});
it('should list openai models', () => {
const models = listModelsByProvider('openai');
expect(models).toContain('gpt-4o');
expect(models).toContain('gpt-4o-mini');
expect(models).not.toContain('claude-sonnet-4-5');
});
it('should list ollama models', () => {
const models = listModelsByProvider('ollama');
expect(models).toContain('llama3');
expect(models).toContain('mistral');
expect(models).not.toContain('gpt-4o');
});
it('should list openrouter models (many)', () => {
const models = listModelsByProvider('openrouter');
expect(models).toContain('claude-sonnet-4-5');
expect(models).toContain('gpt-4o');
expect(models).toContain('gemini-pro');
});
});
describe('listModelsByFamily', () => {
it('should list claude family models', () => {
const models = listModelsByFamily('claude');
expect(models).toContain('claude-sonnet-4-5');
expect(models).toContain('claude-opus-4');
expect(models).toContain('claude-haiku-3-5');
expect(models).not.toContain('gpt-4o');
});
it('should list gpt family models', () => {
const models = listModelsByFamily('gpt');
expect(models).toContain('gpt-4o');
expect(models).toContain('gpt-4o-mini');
expect(models).toContain('o1');
expect(models).not.toContain('claude-sonnet-4-5');
});
it('should return empty array for unknown family', () => {
const models = listModelsByFamily('unknown-family');
expect(models).toEqual([]);
});
});
describe('listModelsByTier', () => {
it('should list flagship tier models', () => {
const models = listModelsByTier('flagship');
expect(models).toContain('claude-opus-4');
expect(models).toContain('o1');
expect(models).not.toContain('gpt-4o-mini');
});
it('should list economy tier models', () => {
const models = listModelsByTier('economy');
expect(models).toContain('claude-haiku-3-5');
expect(models).toContain('gpt-4o-mini');
expect(models).toContain('llama3');
expect(models).not.toContain('claude-opus-4');
});
});
describe('getModelMapping', () => {
it('should return full mapping for valid model', () => {
const mapping = getModelMapping('claude-sonnet-4-5');
expect(mapping).toBeDefined();
expect(mapping?.canonical).toBe('Claude Sonnet 4.5');
expect(mapping?.family).toBe('claude');
expect(mapping?.tier).toBe('standard');
expect(mapping?.providers.anthropic).toBe('claude-sonnet-4-5-20250929');
});
it('should return undefined for unknown model', () => {
const mapping = getModelMapping('unknown-model');
expect(mapping).toBeUndefined();
});
it('should work with provider-specific IDs', () => {
const mapping = getModelMapping('openai/gpt-4o');
expect(mapping).toBeDefined();
expect(mapping?.canonical).toBe('GPT-4o');
});
});
describe('MODEL_MAPPINGS structure', () => {
it('should have all required claude models', () => {
expect(MODEL_MAPPINGS['claude-sonnet-4-5']).toBeDefined();
expect(MODEL_MAPPINGS['claude-opus-4']).toBeDefined();
expect(MODEL_MAPPINGS['claude-opus-4-5']).toBeDefined();
expect(MODEL_MAPPINGS['claude-haiku-3-5']).toBeDefined();
});
it('should have all required openai models', () => {
expect(MODEL_MAPPINGS['gpt-4o']).toBeDefined();
expect(MODEL_MAPPINGS['gpt-4o-mini']).toBeDefined();
expect(MODEL_MAPPINGS['gpt-4-turbo']).toBeDefined();
expect(MODEL_MAPPINGS['o1']).toBeDefined();
});
it('should have all required gemini models', () => {
expect(MODEL_MAPPINGS['gemini-pro']).toBeDefined();
expect(MODEL_MAPPINGS['gemini-pro-1.5']).toBeDefined();
expect(MODEL_MAPPINGS['gemini-flash-1.5']).toBeDefined();
});
it('should have valid provider mappings', () => {
for (const [id, mapping] of Object.entries(MODEL_MAPPINGS)) {
expect(mapping.canonical).toBeTruthy();
expect(mapping.family).toBeTruthy();
expect(mapping.tier).toMatch(/^(economy|standard|premium|flagship)$/);
expect(Object.keys(mapping.providers).length).toBeGreaterThan(0);
}
});
});
describe('bidirectional mapping consistency', () => {
it('should round-trip through normalization and mapping', () => {
const providers: ProviderType[] = ['anthropic', 'openai', 'openrouter', 'gemini'];
for (const [canonicalId, mapping] of Object.entries(MODEL_MAPPINGS)) {
for (const provider of providers) {
const providerId = mapping.providers[provider];
if (providerId) {
// Map canonical -> provider -> normalize should return canonical
const mapped = mapModelId(canonicalId, provider);
const normalized = normalizeModelId(mapped);
expect(normalized).toBe(canonicalId);
}
}
}
});
});
});