mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 09:21:09 +02:00
Three new test suites covering the critical BYOK paths: Pricing (14 tests): estimateCost for known/unknown models, scaling, formatCost edge cases, coverage check for all model IDs. ByokBackend (10 tests): tier identification, resolver behavior, provider dispatch, parameter passthrough, onUsage callback, error paths (no key, unregistered provider), invalidateAvailability. ByokVault (11 tests): encryption at rest verification, decryption round-trip, auto-default for first key, promoting default demotes previous, getForProvider logic, listMeta excludes apiKey, soft delete, recordUsage accumulation, cross-provider isolation. Updates docs/architecture/BYOK_PLAN.md with as-built status — phase table with commit references, deviations from original plan (no server-proxy fallback, no sensitive opt-in UI, no per-task provider override yet), test coverage matrix, troubleshooting guide, v2 follow-ups. Provider adapters remain unit-untested (need fetch mocking + SSE parsing) — smoke tests only. Total: 35/35 tests passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
100 lines
2.5 KiB
TypeScript
100 lines
2.5 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { estimateCost, formatCost, MODEL_PRICING } from './pricing';
|
|
|
|
describe('estimateCost', () => {
|
|
it('computes cost for known model', () => {
|
|
// gpt-4o-mini: input 0.3/M, output 1.2/M
|
|
// 1M input + 0.5M output = 0.3 + 0.6 = 0.9
|
|
const cost = estimateCost('gpt-4o-mini', 1_000_000, 500_000);
|
|
expect(cost).toBeCloseTo(0.9, 4);
|
|
});
|
|
|
|
it('returns 0 for unknown model', () => {
|
|
expect(estimateCost('unknown-model-xyz', 1000, 500)).toBe(0);
|
|
});
|
|
|
|
it('handles zero tokens', () => {
|
|
expect(estimateCost('gpt-4o', 0, 0)).toBe(0);
|
|
});
|
|
|
|
it('handles only input tokens', () => {
|
|
// claude-opus-4-6: input 15/M, output 75/M
|
|
const cost = estimateCost('claude-opus-4-6', 1_000_000, 0);
|
|
expect(cost).toBe(15);
|
|
});
|
|
|
|
it('handles only output tokens', () => {
|
|
// gemini-2.5-flash: input 0.15/M, output 0.6/M
|
|
const cost = estimateCost('gemini-2.5-flash', 0, 1_000_000);
|
|
expect(cost).toBe(0.6);
|
|
});
|
|
|
|
it('scales linearly with token count', () => {
|
|
const cost1k = estimateCost('gpt-4o', 1000, 1000);
|
|
const cost10k = estimateCost('gpt-4o', 10_000, 10_000);
|
|
expect(cost10k).toBeCloseTo(cost1k * 10, 6);
|
|
});
|
|
|
|
it('has pricing for all OpenAI models', () => {
|
|
const openaiModels = [
|
|
'gpt-5',
|
|
'gpt-5-mini',
|
|
'gpt-4o',
|
|
'gpt-4o-mini',
|
|
'gpt-4-turbo',
|
|
'o1',
|
|
'o1-mini',
|
|
];
|
|
for (const model of openaiModels) {
|
|
expect(MODEL_PRICING[model]).toBeDefined();
|
|
}
|
|
});
|
|
|
|
it('has pricing for all Anthropic models', () => {
|
|
const anthropicModels = [
|
|
'claude-opus-4-6',
|
|
'claude-opus-4-5',
|
|
'claude-sonnet-4-6',
|
|
'claude-sonnet-4-5',
|
|
'claude-haiku-4-5',
|
|
];
|
|
for (const model of anthropicModels) {
|
|
expect(MODEL_PRICING[model]).toBeDefined();
|
|
}
|
|
});
|
|
|
|
it('has pricing for all Gemini models', () => {
|
|
const geminiModels = [
|
|
'gemini-2.5-pro',
|
|
'gemini-2.5-flash',
|
|
'gemini-2.5-flash-lite',
|
|
'gemini-2.0-flash',
|
|
];
|
|
for (const model of geminiModels) {
|
|
expect(MODEL_PRICING[model]).toBeDefined();
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('formatCost', () => {
|
|
it('shows dash for zero', () => {
|
|
expect(formatCost(0)).toBe('—');
|
|
});
|
|
|
|
it('shows "< $0.0001" for very small amounts', () => {
|
|
expect(formatCost(0.00001)).toBe('< $0.0001');
|
|
});
|
|
|
|
it('shows 4 decimals for amounts < 0.01', () => {
|
|
expect(formatCost(0.005)).toBe('$0.0050');
|
|
});
|
|
|
|
it('shows 3 decimals for amounts < 1', () => {
|
|
expect(formatCost(0.123)).toBe('$0.123');
|
|
});
|
|
|
|
it('shows 2 decimals for amounts >= 1', () => {
|
|
expect(formatCost(1.234)).toBe('$1.23');
|
|
expect(formatCost(100.567)).toBe('$100.57');
|
|
});
|
|
});
|