import { test } from 'node:test'; import assert from 'node:assert/strict'; import { Store } from '../src/store/index.js'; import { computeCost, addUsage } from '../src/model/pricing.js'; test('pricing: computeCost 按模型×token 折算(USD/1M)', () => { // opus: in15 / out75 → 各 1M token = 15 + 75 = 90 assert.equal(computeCost('claude-opus-4-8', { input: 1_000_000, output: 1_000_000, cacheRead: 0, cacheWrite: 0 }), 90); // sonnet: in3 → 2M input = 6 assert.equal(computeCost('claude-sonnet-4-6', { input: 2_000_000, output: 0, cacheRead: 0, cacheWrite: 0 }), 6); // haiku: cacheRead 0.1 → 10M = 1 assert.equal(computeCost('claude-haiku-4-5', { input: 0, output: 0, cacheRead: 10_000_000, cacheWrite: 0 }), 1); // 未知模型 → 0(不阻断) assert.equal(computeCost('mystery-model', { input: 9_999_999, output: 0, cacheRead: 0, cacheWrite: 0 }), 0); // 前缀匹配(容忍日期后缀) assert.equal(computeCost('claude-opus-4-8-20260101', { input: 1_000_000, output: 0, cacheRead: 0, cacheWrite: 0 }), 15); }); test('pricing: addUsage 逐项累加', () => { const a = { input: 1, output: 2, cacheRead: 3, cacheWrite: 4 }; assert.deepEqual(addUsage(a, a), { input: 2, output: 4, cacheRead: 6, cacheWrite: 8 }); }); test('store: setRunUsage 累加成本 + costSummary 聚合', () => { const s = new Store(':memory:'); const p = s.createProject({ name: 'b', repoPath: '/tmp/b-' + Math.random() }); const t = s.createTask({ projectId: p.id, title: 'x', complexity: 'easy' }); const r = s.startRun(t.id, 'executor'); // 一个 run 内多次 CC 调用(executor + 复审)→ 累加 const c1 = s.setRunUsage(r.id, { input: 1_000_000, output: 0, cacheRead: 0, cacheWrite: 0 }, 'claude-opus-4-8'); assert.equal(c1, 15); const c2 = s.setRunUsage(r.id, { input: 0, output: 1_000_000, cacheRead: 0, cacheWrite: 0 }, 'claude-opus-4-8'); assert.equal(c2, 90); const sum = s.costSummary('month'); assert.equal(sum.total, 90); assert.equal(sum.byProject.length, 1); assert.equal(sum.byProject[0].projectId, p.id); assert.equal(sum.byProject[0].costUsd, 90); assert.equal(sum.byProject[0].tokens, 2_000_000); s.close(); }); test('store: enforceBudget 超额 → 暂停项目 + 广播 budget.exceeded', () => { const s = new Store(':memory:'); const p = s.createProject({ name: 'b', repoPath: '/tmp/b-' + Math.random() }); const t = s.createTask({ projectId: p.id, title: 'x', complexity: 'easy' }); const r = s.startRun(t.id, 'executor'); s.setRunUsage(r.id, { input: 0, output: 1_000_000, cacheRead: 0, cacheWrite: 0 }, 'claude-opus-4-8'); // $75 // 无预算 → 不干预 assert.equal(s.enforceBudget(p.id), false); assert.equal(s.getProject(p.id)!.status, 'active'); // 设 $50 上限 → 超额、暂停、发事件 s.patchProject(p.id, { budgetUsd: 50, budgetPeriod: 'month' }); assert.equal(s.enforceBudget(p.id), true); assert.equal(s.getProject(p.id)!.status, 'paused'); const ev = s.listEvents(p.id).find((e) => e.type === 'budget.exceeded'); assert.ok(ev, '应有 budget.exceeded 事件'); assert.equal((ev!.payload as { budget: number }).budget, 50); s.close(); });