Files
stack/packages/cli/tests/usage.test.mjs
T
jason.woltjeandClaude Opus 5.5 70504d23a4 bus, cli, webui: token accounting per run, schema 3h usage_records (row 124, #1618)
Rocko's round 2 candidate e09313aba4d155a1c5af7bfb227ec397e96c0d57444782cd0b48593102bcc684
(38 paths, base 23533625d), approved by Dewey in #1618 comment 28923 (queue rev 1757).
Packet: agents/rocko/work/queue-124/r2/.

- Schema 3g to 3h: packages/bus/src/migrations/3g-3h.sql adds an append-only
  usage_records table; nothing earlier changes. usage_records is in timedTables.
- The transcript usage-source module reads token counts from Pi and Claude
  Code session transcripts and follows no symlink.
- `mosaic usage` in the CLI and a read-only usage section in the Console's
  Settings (one GET route; 400 on a query string).

Decision 123 go: Mos, mos-0737. Activation (3g to 3h) is a migrating run
with Jason present, in Mos's combined re-pin; landing changes nothing live.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-18 11:08:17 -05:00

31 lines
2.1 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { main } from '../src/cli.mjs';
import { fixture, io, tmp } from './helpers.mjs';
const count = { input: 11, output: 3, cacheWrite: 5, cacheRead: 7, unknownTurns: 2 };
const view = { total: count, byRun: [{ run: 'run1', ...count }], byInstance: [{ instance: 'coder', ...count }],
byModel: [{ provider: 'anthropic', model: 'fixture-model', ...count }], byProvider: [{ provider: 'anthropic', ...count }],
byAccount: [{ account: null, ...count }] };
test('REQ-LIMIT-4 mosaic usage shows each token kind and unknown turns beside every total', async t => {
const f = fixture(tmp(t), 'acme'), calls = [];
const deps = { transport: business => async (verb, args) => { calls.push({ business, verb, args }); return view; } };
const text = io(f.env); await main(['usage', '--business', 'acme'], text, deps);
for (const expected of ['input=11', 'output=3', 'cacheWrite=5', 'cacheRead=7', 'unknown=2', 'run1', 'coder', 'fixture-model', 'anthropic', '(unassigned)']) assert.ok(text.out.text.includes(expected), expected);
assert.equal((text.out.text.match(/unknown=2/g) ?? []).length, 6);
const json = io(f.env); await main(['usage', '--business', 'acme', '--json'], json, deps);
assert.deepEqual(JSON.parse(json.out.text), view);
assert.deepEqual(calls[0], { business: 'acme', verb: 'usage', args: {} });
await assert.rejects(main(['usage', '--bad'], io(f.env), deps), { exitCode: 4 });
});
test('usage is dispatched by scripts/mosaic and the shared read adapter', async () => {
const { spawnSync } = await import('node:child_process');
const { views } = await import('../../bus/src/views.mjs');
const calls = [];
const result = await views({ call: (...args) => { calls.push(args); return view; } }).usage();
assert.deepEqual(result, view); assert.deepEqual(calls, [['usage']]);
const script = new URL('../../../scripts/mosaic', import.meta.url).pathname;
const command = spawnSync(script, ['usage', '--bad'], { encoding: 'utf8', env: { PATH: process.env.PATH } });
assert.equal(command.status, 4); assert.match(command.stderr, /mosaic usage/);
});