voyage/tests/lib/cache-analyzer.test.mjs
Kjell Tore Guttormsen 68c9bef38f test(observability): close 2 MAJOR test gaps from SKAL-2 review
F1 cache-analyzer regression guard (SC5): pin percentile (wall_time_ms_p50/p90)
and time-range (oldest/newest_event_iso) — 2 of 3 'unchanged' categories were
previously un-asserted on the mixed-input fixture.

F2 lastMainChainModel: add direct coverage — last-wins across 2 distinct
main-chain models, sidechain exclusion (even when the sidechain is the last
record), and model-absent → null propagating to deriveCost refuse-to-estimate.

804 -> 807 tests (805 pass / 0 fail / 2 skipped). No test pins the test count.
2026-06-26 17:14:03 +02:00

84 lines
3.9 KiB
JavaScript

// tests/lib/cache-analyzer.test.mjs
// SKAL-2 Step 3 — token/cost aggregation in cache-analyzer's summarize().
// Also the first test coverage for cache-analyzer (previously zero-coverage):
// includes a regression guard that the pre-existing duration/event-name fields
// still compute correctly on mixed input.
//
// Hermetic: no LLM, network, time, or randomness. Idiom:
// tests/lib/token-usage.test.mjs (pure-module node:test style).
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { summarize } from '../../lib/stats/cache-analyzer.mjs';
const jsonl = (objs) => objs.map(o => JSON.stringify(o));
// ---- token/cost aggregation ------------------------------------------------
test('summarize — two token-bearing records → summed totals + cost', () => {
const s = summarize(jsonl([
{ ts: '2026-06-26T12:00:00Z', session_id: 'S1', scope: 'main-context', tokens_input: 1000, tokens_output: 200, tokens_cache_creation: 400, tokens_cache_read: 5000, cost_usd: 0.03675 },
{ ts: '2026-06-26T13:00:00Z', session_id: 'S2', scope: 'main-context', tokens_input: 2000, tokens_output: 300, tokens_cache_creation: 600, tokens_cache_read: 1000, cost_usd: 0.05 },
]));
assert.equal(s.total_tokens_input, 3000);
assert.equal(s.total_tokens_output, 500);
assert.equal(s.total_tokens_cache_creation, 1000);
assert.equal(s.total_tokens_cache_read, 6000);
assert.equal(s.sessions_with_tokens, 2);
assert.ok(Math.abs(s.total_cost_usd - 0.08675) < 1e-9, `cost ${s.total_cost_usd}`);
});
test('summarize — cost_usd:null counts the session but is excluded from total_cost_usd', () => {
const s = summarize(jsonl([
{ session_id: 'S1', tokens_input: 100, cost_usd: 0.01 },
{ session_id: 'S2', tokens_input: 200, cost_usd: null, is_estimate: true },
]));
assert.equal(s.sessions_with_tokens, 2, 'null-cost session must still be counted');
assert.equal(s.total_tokens_input, 300);
assert.ok(Math.abs(s.total_cost_usd - 0.01) < 1e-9, `cost ${s.total_cost_usd}`);
});
test('summarize — no token records → token totals stay zero (additive default)', () => {
const s = summarize(jsonl([
{ event: 'main-merge-gate', duration_ms: 100 },
]));
assert.equal(s.total_tokens_input, 0);
assert.equal(s.total_cost_usd, 0);
assert.equal(s.sessions_with_tokens, 0);
});
// ---- regression guard: pre-existing fields still computed ------------------
test('summarize — existing duration/event-name fields still correct on mixed input', () => {
const s = summarize(jsonl([
{ event: 'main-merge-gate', duration_ms: 100, ts: '2026-06-26T10:00:00Z' },
{ event: 'main-merge-approved', duration_ms: 200, ts: '2026-06-26T11:00:00Z' },
{ session_id: 'S1', tokens_input: 1000, cost_usd: 0.04, ts: '2026-06-26T12:00:00Z' },
]));
// Pre-existing behavior (regression guard)
assert.equal(s.total_events, 3);
assert.equal(s.events_with_duration, 2);
// Percentiles: durations [100,200] → floor(2·0.5)=floor(2·0.9)=idx 1 → both 200.
assert.equal(s.wall_time_ms_p50, 200);
assert.equal(s.wall_time_ms_p90, 200);
assert.equal(s.wall_time_ms_max, 200);
assert.deepEqual(s.unique_event_names, ['main-merge-approved', 'main-merge-gate']);
// Time range spans all ts-bearing lines (incl. the token-only record at 12:00).
assert.equal(s.oldest_event_iso, '2026-06-26T10:00:00.000Z');
assert.equal(s.newest_event_iso, '2026-06-26T12:00:00.000Z');
// New behavior coexists
assert.equal(s.total_tokens_input, 1000);
assert.equal(s.sessions_with_tokens, 1);
assert.ok(Math.abs(s.total_cost_usd - 0.04) < 1e-9);
});
test('summarize — malformed/empty lines tolerated alongside token records', () => {
const s = summarize([
'',
'not json',
JSON.stringify({ session_id: 'S1', tokens_input: 50, cost_usd: 0.001 }),
]);
assert.equal(s.total_events, 1);
assert.equal(s.sessions_with_tokens, 1);
assert.equal(s.total_tokens_input, 50);
});