Files
proffesor-for-testing__agen…/tests/unit/shared/llm/provider-manager.test.ts
Dragan Spiridonov d55fcfbbda refactor(adr-093): sweep retiring model IDs to Opus 4.7/Sonnet 4.6/Haiku 4.5
Commit 2 of 6 for ADR-093 Opus 4.7 migration. Real behavior change:
the AQE fleet now targets Sonnet 4.6, Haiku 4.5, Opus 4.7.

43 files changed, 253 insertions, 246 deletions.

Mechanical sed sweep across 29 src/ files and 14 tests/ files:
* 'claude-sonnet-4-20250514' → 'claude-sonnet-4-6'
* 'claude-3-5-haiku-20241022' → 'claude-haiku-4-5-20251001'
* 'claude-opus-4-5-20251101' → 'claude-opus-4-7'
* 'claude-opus-4-20250514' → 'claude-opus-4-7'

Data-definition files (model-mapping.ts, model-registry.ts,
cost-tracker.ts, model-mapping.test.ts, model-registry.test.ts)
deliberately excluded from sweep — they keep retiring IDs as
canonical data + regression coverage for backward-compat paths.

* src/shared/llm/cost-tracker.ts: added claude-opus-4-7 ($5/$25),
  claude-sonnet-4-6 ($3/$15), claude-haiku-4-5-20251001 ($1/$5)
  pricing entries alongside retained retiring entries.
* src/shared/llm/providers/bedrock.ts: rebuilt BEDROCK_MODEL_MAPPING
  to preserve both new ARNs (claude-opus-4-7-v1:0 etc.) and retiring
  ARNs; fixes duplicate-key compile error caused by sed collapsing
  'claude-opus-4-20250514' and 'claude-opus-4-5-20251101' to same key.
* src/shared/llm/interfaces.ts: ClaudeConfig.model type union
  updated to hint at new IDs; '| string' fallback keeps backward
  compat for lingering callers.
* tests updated: 3 provider/bedrock/cost-tracker assertions
  corrected to match new pricing + new ARN mappings.

Verification:
* 0 tsc errors (npx tsc --noEmit)
* 664/664 LLM unit tests pass
* 2302/2302 coordination + CLI tests pass
* 2884/2885 domain tests pass (1 pre-existing skip)
* grep -rcE 'retired-id-pattern' src/ returns 0 outside data files

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 12:32:24 +00:00

551 lines
15 KiB
TypeScript

/**
* Agentic QE v3 - Provider Manager Unit Tests
*/
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
import {
ProviderManager,
createProviderManager,
createQEProviderManager,
} from '../../../../src/shared/llm/provider-manager';
import { resetGlobalCostTracker } from '../../../../src/shared/llm/cost-tracker';
// Mock fetch globally
const mockFetch = vi.fn();
global.fetch = mockFetch;
describe('ProviderManager', () => {
let manager: ProviderManager;
beforeEach(() => {
vi.clearAllMocks();
resetGlobalCostTracker();
});
afterEach(async () => {
if (manager) {
await manager.dispose();
}
});
describe('initialization', () => {
it('should initialize with default config', async () => {
// Mock health checks for initialization
mockFetch.mockResolvedValue({
ok: true,
json: () => Promise.resolve({
models: [{ name: 'llama3.1' }],
}),
});
manager = createProviderManager();
await manager.initialize();
expect(manager.getAvailableProviders().length).toBeGreaterThan(0);
});
it('should create QE-optimized manager', () => {
manager = createQEProviderManager();
expect(manager).toBeDefined();
});
it('should throw if no providers can be initialized', async () => {
// Mock all providers failing
mockFetch.mockRejectedValue(new Error('All providers unavailable'));
manager = new ProviderManager({
primary: 'claude',
fallbacks: [],
loadBalancing: 'round-robin',
providers: {
claude: { model: 'claude-sonnet-4-6', apiKey: undefined },
},
});
// Clear env vars
const origClaude = process.env.ANTHROPIC_API_KEY;
const origOpenai = process.env.OPENAI_API_KEY;
delete process.env.ANTHROPIC_API_KEY;
delete process.env.OPENAI_API_KEY;
// Should still initialize since we have providers config
await manager.initialize();
// Restore
if (origClaude) process.env.ANTHROPIC_API_KEY = origClaude;
if (origOpenai) process.env.OPENAI_API_KEY = origOpenai;
});
});
describe('generate', () => {
beforeEach(async () => {
// Setup mock for Ollama (local provider that doesn't need API key)
mockFetch.mockImplementation((url: string) => {
if (url.includes('/api/tags')) {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({ models: [{ name: 'llama3.1' }] }),
});
}
if (url.includes('/api/generate')) {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({
model: 'llama3.1',
response: 'Generated text',
done: true,
prompt_eval_count: 10,
eval_count: 20,
}),
});
}
if (url.includes('/api/chat')) {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({
model: 'llama3.1',
message: { role: 'assistant', content: 'Chat response' },
done: true,
prompt_eval_count: 10,
eval_count: 20,
}),
});
}
return Promise.resolve({ ok: false });
});
manager = new ProviderManager({
primary: 'ollama',
fallbacks: [],
loadBalancing: 'round-robin',
providers: {
ollama: { model: 'llama3.1' },
},
});
await manager.initialize();
});
it('should generate text', async () => {
const response = await manager.generate('Test prompt');
expect(response.content).toBe('Generated text');
expect(response.provider).toBe('ollama');
});
it('should cache responses', async () => {
const response1 = await manager.generate('Test prompt');
expect(response1.cached).toBe(false);
const response2 = await manager.generate('Test prompt');
expect(response2.cached).toBe(true);
expect(response2.content).toBe(response1.content);
});
it('should skip cache when requested', async () => {
await manager.generate('Test prompt');
// Reset fetch call count
mockFetch.mockClear();
const response = await manager.generate('Test prompt', { skipCache: true });
expect(response.cached).toBe(false);
});
it('should track metrics', async () => {
await manager.generate('Test 1');
await manager.generate('Test 2');
const metrics = manager.getMetrics();
expect(metrics.ollama.totalRequests).toBeGreaterThanOrEqual(2);
});
});
describe('embed', () => {
beforeEach(async () => {
const mockEmbedding = new Array(384).fill(0).map((_, i) => i / 384);
mockFetch.mockImplementation((url: string) => {
if (url.includes('/api/tags')) {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({ models: [{ name: 'all-MiniLM-L6-v2' }] }),
});
}
if (url.includes('/api/embeddings')) {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({ embedding: mockEmbedding }),
});
}
return Promise.resolve({ ok: false });
});
manager = new ProviderManager({
primary: 'ollama',
fallbacks: [],
loadBalancing: 'round-robin',
providers: {
ollama: { model: 'llama3.1' },
},
});
await manager.initialize();
});
it('should generate embeddings', async () => {
const response = await manager.embed('Test text');
expect(response.embedding.length).toBe(384);
expect(response.provider).toBe('ollama');
});
it('should cache embeddings', async () => {
const response1 = await manager.embed('Test text');
const response2 = await manager.embed('Test text');
expect(response2.cached).toBe(true);
});
});
describe('complete', () => {
beforeEach(async () => {
mockFetch.mockImplementation((url: string) => {
if (url.includes('/api/tags')) {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({ models: [{ name: 'llama3.1' }] }),
});
}
if (url.includes('/api/generate')) {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({
model: 'llama3.1',
response: 'a, b) { return a + b; }',
done: true,
prompt_eval_count: 5,
eval_count: 10,
}),
});
}
return Promise.resolve({ ok: false });
});
manager = new ProviderManager({
primary: 'ollama',
fallbacks: [],
loadBalancing: 'round-robin',
providers: {
ollama: { model: 'llama3.1' },
},
});
await manager.initialize();
});
it('should complete text', async () => {
const response = await manager.complete('function add(');
expect(response.completion).toContain('return');
expect(response.provider).toBe('ollama');
});
});
describe('failover', () => {
it('should track failures in metrics', async () => {
let callCount = 0;
mockFetch.mockImplementation((url: string) => {
if (url.includes('/api/tags')) {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({ models: [{ name: 'llama3.1' }] }),
});
}
if (url.includes('/api/generate')) {
callCount++;
if (callCount === 1) {
// First call fails
return Promise.resolve({
ok: false,
status: 500,
text: () => Promise.resolve('Server error'),
});
}
// Second call succeeds
return Promise.resolve({
ok: true,
json: () => Promise.resolve({
model: 'llama3.1',
response: 'Success after retry',
done: true,
prompt_eval_count: 10,
eval_count: 20,
}),
});
}
return Promise.resolve({ ok: false });
});
manager = new ProviderManager({
primary: 'ollama',
fallbacks: [],
loadBalancing: 'round-robin',
providers: {
ollama: { model: 'llama3.1' },
},
});
await manager.initialize();
// First request fails
try {
await manager.generate('Test');
} catch {
// Expected to fail
}
// Check that failure was tracked in metrics
const metrics = manager.getMetrics();
expect(metrics.ollama.failureCount).toBeGreaterThanOrEqual(1);
});
});
describe('load balancing', () => {
beforeEach(async () => {
mockFetch.mockImplementation((url: string) => {
if (url.includes('/api/tags')) {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({ models: [{ name: 'llama3.1' }] }),
});
}
if (url.includes('/api/generate')) {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({
model: 'llama3.1',
response: 'Response',
done: true,
prompt_eval_count: 10,
eval_count: 20,
}),
});
}
return Promise.resolve({ ok: false });
});
});
it('should use round-robin strategy', async () => {
manager = new ProviderManager({
primary: 'ollama',
fallbacks: [],
loadBalancing: 'round-robin',
providers: {
ollama: { model: 'llama3.1' },
},
});
await manager.initialize();
await manager.generate('Test 1');
await manager.generate('Test 2');
// With single provider, all requests go to ollama
const metrics = manager.getMetrics();
expect(metrics.ollama.totalRequests).toBeGreaterThanOrEqual(2);
});
it('should use least-cost strategy', async () => {
manager = new ProviderManager({
primary: 'ollama',
fallbacks: [],
loadBalancing: 'least-cost',
providers: {
ollama: { model: 'llama3.1' },
},
});
await manager.initialize();
const response = await manager.generate('Test');
// Ollama is always cheapest (free)
expect(response.provider).toBe('ollama');
});
});
describe('health check', () => {
it('should check health of all providers', async () => {
mockFetch.mockImplementation((url: string) => {
if (url.includes('/api/tags')) {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({ models: [{ name: 'llama3.1' }] }),
});
}
return Promise.resolve({ ok: false });
});
manager = new ProviderManager({
primary: 'ollama',
fallbacks: [],
loadBalancing: 'round-robin',
providers: {
ollama: { model: 'llama3.1' },
},
});
await manager.initialize();
const health = await manager.healthCheck();
expect(health.ollama).toBeDefined();
expect(health.ollama.healthy).toBe(true);
});
});
describe('cache management', () => {
beforeEach(async () => {
mockFetch.mockImplementation((url: string) => {
if (url.includes('/api/tags')) {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({ models: [{ name: 'llama3.1' }] }),
});
}
if (url.includes('/api/generate')) {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({
model: 'llama3.1',
response: 'Response',
done: true,
prompt_eval_count: 10,
eval_count: 20,
}),
});
}
return Promise.resolve({ ok: false });
});
manager = new ProviderManager({
primary: 'ollama',
fallbacks: [],
loadBalancing: 'round-robin',
providers: {
ollama: { model: 'llama3.1' },
},
});
await manager.initialize();
});
it('should get cache stats', async () => {
await manager.generate('Test');
const stats = manager.getCacheStats();
expect(stats.total.size).toBeGreaterThanOrEqual(1);
});
it('should clear cache', async () => {
await manager.generate('Test');
manager.clearCache();
const stats = manager.getCacheStats();
expect(stats.total.size).toBe(0);
});
});
describe('cost tracking', () => {
beforeEach(async () => {
mockFetch.mockImplementation((url: string) => {
if (url.includes('/api/tags')) {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({ models: [{ name: 'llama3.1' }] }),
});
}
if (url.includes('/api/generate')) {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({
model: 'llama3.1',
response: 'Response',
done: true,
prompt_eval_count: 10,
eval_count: 20,
}),
});
}
return Promise.resolve({ ok: false });
});
manager = new ProviderManager({
primary: 'ollama',
fallbacks: [],
loadBalancing: 'round-robin',
providers: {
ollama: { model: 'llama3.1' },
},
});
await manager.initialize();
});
it('should track costs', async () => {
await manager.generate('Test', { skipCache: true });
const summary = manager.getCostSummary('all');
expect(summary.totalRequests).toBeGreaterThanOrEqual(1);
expect(summary.totalCost).toBe(0); // Ollama is free
});
});
describe('circuit breaker', () => {
it('should reset circuit breakers', async () => {
mockFetch.mockImplementation((url: string) => {
if (url.includes('/api/tags')) {
return Promise.resolve({
ok: true,
json: () => Promise.resolve({ models: [{ name: 'llama3.1' }] }),
});
}
return Promise.resolve({ ok: false });
});
manager = new ProviderManager({
primary: 'ollama',
fallbacks: [],
loadBalancing: 'round-robin',
providers: {
ollama: { model: 'llama3.1' },
},
});
await manager.initialize();
// Reset should not throw
manager.resetCircuitBreakers();
expect(manager.getAvailableProviders()).toContain('ollama');
});
});
});
describe('Factory functions', () => {
it('should create provider manager with createProviderManager', () => {
const manager = createProviderManager();
expect(manager).toBeInstanceOf(ProviderManager);
});
it('should create QE provider manager with createQEProviderManager', () => {
const manager = createQEProviderManager();
expect(manager).toBeInstanceOf(ProviderManager);
});
});