165ff3a859
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
83 lines
3.0 KiB
JavaScript
83 lines
3.0 KiB
JavaScript
import { describe, it, expect } from 'vitest';
|
|
import { runUpdate, currentMonthFile, todayISO } from './cost-stop-hook.mjs';
|
|
import { PRICING } from './cost-pricing.mjs';
|
|
|
|
describe('runUpdate (pure)', () => {
|
|
const ep = (date, cost) => ({
|
|
timestamps: { started_at: `${date}T12:00:00.000Z` },
|
|
task_cost: cost,
|
|
});
|
|
|
|
it('adds today entry to empty existing state', () => {
|
|
const r = runUpdate({
|
|
episodes: [ep('2026-05-28', { classifier_input_tokens: 1_000_000 })],
|
|
dateISO: '2026-05-28',
|
|
existing: {},
|
|
pricing: PRICING,
|
|
});
|
|
expect(r['2026-05-28']).toBeDefined();
|
|
expect(r['2026-05-28'].classifier_usd).toBeCloseTo(3, 6);
|
|
expect(r['2026-05-28'].episode_count).toBe(1);
|
|
});
|
|
|
|
it('overwrites today entry but preserves other-day entries', () => {
|
|
const r = runUpdate({
|
|
episodes: [ep('2026-05-28', { classifier_input_tokens: 1_000_000 })],
|
|
dateISO: '2026-05-28',
|
|
existing: { '2026-05-27': { total_usd: 9.99, episode_count: 5 } },
|
|
pricing: PRICING,
|
|
});
|
|
expect(r['2026-05-27']).toEqual({ total_usd: 9.99, episode_count: 5 });
|
|
expect(r['2026-05-28'].classifier_usd).toBeCloseTo(3, 6);
|
|
});
|
|
|
|
it('writes zero-episode-count day when no episodes match', () => {
|
|
const r = runUpdate({
|
|
episodes: [],
|
|
dateISO: '2026-05-28',
|
|
existing: {},
|
|
pricing: PRICING,
|
|
});
|
|
expect(r['2026-05-28'].episode_count).toBe(0);
|
|
expect(r['2026-05-28'].total_usd).toBe(0);
|
|
});
|
|
|
|
it('does not mutate the existing input object', () => {
|
|
const existing = { '2026-05-27': { total_usd: 1 } };
|
|
runUpdate({ episodes: [], dateISO: '2026-05-28', existing, pricing: PRICING });
|
|
expect(Object.keys(existing)).toEqual(['2026-05-27']);
|
|
expect(existing['2026-05-27']).toEqual({ total_usd: 1 });
|
|
});
|
|
});
|
|
|
|
describe('todayISO', () => {
|
|
it('returns YYYY-MM-DD string for a Date instance', () => {
|
|
expect(todayISO(new Date('2026-05-28T15:30:00.000Z'))).toBe('2026-05-28');
|
|
});
|
|
|
|
it('uses UTC date components', () => {
|
|
// Date that's late evening UTC but next day in local — ISO must reflect UTC.
|
|
expect(todayISO(new Date('2026-05-28T23:59:59.000Z'))).toBe('2026-05-28');
|
|
});
|
|
});
|
|
|
|
describe('currentMonthFile', () => {
|
|
it('returns docs/observer/episodes-YYYY-MM.jsonl', () => {
|
|
const p = currentMonthFile(new Date('2026-05-28T12:00:00.000Z'), '/repo');
|
|
expect(p.replace(/\\/g, '/')).toBe('/repo/docs/observer/episodes-2026-05.jsonl');
|
|
});
|
|
|
|
it('pads month with leading zero', () => {
|
|
const p = currentMonthFile(new Date('2026-01-05T12:00:00.000Z'), '/repo');
|
|
expect(p.replace(/\\/g, '/')).toBe('/repo/docs/observer/episodes-2026-01.jsonl');
|
|
});
|
|
|
|
it('uses stateDir param (config-seam); дефолт docs/observer', () => {
|
|
const now = new Date('2026-06-15T12:00:00.000Z');
|
|
expect(currentMonthFile(now, '/repo', '.claude/brain-state').replace(/\\/g, '/'))
|
|
.toBe('/repo/.claude/brain-state/episodes-2026-06.jsonl');
|
|
expect(currentMonthFile(now, '/repo').replace(/\\/g, '/'))
|
|
.toBe('/repo/docs/observer/episodes-2026-06.jsonl');
|
|
});
|
|
});
|