import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import {
_resetCockpitCacheForTests,
computeCockpit,
computeWarm,
} from "../src/server/api/cockpit.js";
import type { DashboardContext } from "../src/server/context.js";
const DAY = 86_400_000;
const NOW = Date.UTC(2026, 4, 1, 12, 0, 0);
function ctxOnly(usageLogPath: string): DashboardContext {
return { configPath: "", usageLogPath, mode: "attached" };
}
function record(opts: {
ts: number;
prompt?: number;
completion?: number;
hit?: number;
miss?: number;
cost?: number;
model?: string;
}): string {
return JSON.stringify({
ts: opts.ts,
session: null,
model: opts.model ?? "deepseek-v4-flash",
promptTokens: opts.prompt ?? 1000,
completionTokens: opts.completion ?? 200,
cacheHitTokens: opts.hit ?? 800,
cacheMissTokens: opts.miss ?? 200,
costUsd: opts.cost ?? 0.001,
claudeEquivUsd: 0.01,
});
}
describe("computeWarm", () => {
let dir: string;
let usagePath: string;
let sessionsDir: string;
beforeEach(() => {
dir = mkdtempSync(join(tmpdir(), "rx-cockpit-"));
usagePath = join(dir, "usage.jsonl");
sessionsDir = join(dir, "sessions");
_resetCockpitCacheForTests();
});
afterEach(() => {
rmSync(dir, { recursive: true, force: true });
});
it("returns nulls when the usage log is empty", () => {
const out = computeWarm(usagePath, NOW, sessionsDir);
expect(out.tokens7d).toBeNull();
expect(out.cacheHit7d).toBeNull();
expect(out.costTrend14d).toBeNull();
});
it("rolls up tokens7d as prompt + completion across the trailing week", () => {
const lines = [
record({ ts: NOW - 1 * DAY, prompt: 10_000, completion: 2_000 }),
record({ ts: NOW - 5 * DAY, prompt: 30_000, completion: 8_000 }),
record({ ts: NOW - 8 * DAY, prompt: 99_999, completion: 99_999 }),
].join("\n");
writeFileSync(usagePath, `${lines}\n`);
const out = computeWarm(usagePath, NOW, sessionsDir);
expect(out.tokens7d?.total).toBe(50_000);
});
it("computes tokens7d deltaPct vs the prior 7-day window", () => {
const inWeek = record({ ts: NOW - 1 * DAY, prompt: 10_000, completion: 0 });
const priorWeek = record({ ts: NOW - 9 * DAY, prompt: 5_000, completion: 0 });
writeFileSync(usagePath, `${inWeek}\n${priorWeek}\n`);
const out = computeWarm(usagePath, NOW, sessionsDir);
expect(out.tokens7d?.deltaPct).toBeCloseTo(100, 5);
});
it("returns null deltaPct when the prior week has no records", () => {
writeFileSync(usagePath, `${record({ ts: NOW - 1 * DAY })}\n`);
const out = computeWarm(usagePath, NOW, sessionsDir);
expect(out.tokens7d?.deltaPct).toBeNull();
});
it("derives cacheHit7d ratio from the trailing-week bucket", () => {
writeFileSync(
usagePath,
`${record({ ts: NOW - 1 * DAY, hit: 900, miss: 100 })}\n${record({ ts: NOW - 2 * DAY, hit: 100, miss: 900 })}\n`,
);
const out = computeWarm(usagePath, NOW, sessionsDir);
expect(out.cacheHit7d?.ratio).toBeCloseTo(0.5, 5);
});
it("sparkline has exactly `days` entries even when most days are empty", () => {
writeFileSync(usagePath, `${record({ ts: NOW - 2 * DAY, cost: 0.05 })}\n`);
const out = computeWarm(usagePath, NOW, sessionsDir);
expect(out.costTrend14d).toHaveLength(14);
const total = (out.costTrend14d ?? []).reduce((s, d) => s + d.usd, 0);
expect(total).toBeCloseTo(0.05, 5);
});
it("sparkline drops records older than the window", () => {
writeFileSync(usagePath, `${record({ ts: NOW - 30 * DAY, cost: 99 })}\n`);
const out = computeWarm(usagePath, NOW, sessionsDir);
const total = (out.costTrend14d ?? []).reduce((s, d) => s + d.usd, 0);
expect(total).toBe(0);
});
});
describe("computeCockpit", () => {
let dir: string;
let usagePath: string;
let sessionsDir: string;
beforeEach(() => {
dir = mkdtempSync(join(tmpdir(), "rx-cockpit-"));
usagePath = join(dir, "usage.jsonl");
sessionsDir = join(dir, "sessions");
_resetCockpitCacheForTests();
});
afterEach(() => {
rmSync(dir, { recursive: true, force: true });
});
function ctx(extra: Partial<DashboardContext> = {}): DashboardContext {
return { ...ctxOnly(usagePath), sessionsDir, ...extra };
}
it("returns null cockpit fields when ctx has no loop, no stats, no log", () => {
const out = computeCockpit(ctx(), NOW);
expect(out.balance).toBeNull();
expect(out.currentSession).toBeNull();
expect(out.tokens7d).toBeNull();
expect(out.toolCalls24h).toBeNull();
expect(out.recentPlans).toBeNull();
expect(out.toolActivity).toBeNull();
});
it("surfaces balance from getStats", () => {
const out = computeCockpit(
ctx({
getStats: () => ({
turns: 0,
totalCostUsd: 0,
lastTurnCostUsd: 0,
totalInputCostUsd: 0,
totalOutputCostUsd: 0,
cacheHitRatio: 0,
lastPromptTokens: 0,
contextCapTokens: 1_000_000,
balance: [{ currency: "CNY", total_balance: "48.20" }],
}),
}),
NOW,
);
expect(out.balance).toEqual({ currency: "CNY", total: "48.20" });
});
it("warm fields are reused from cache within the TTL window", () => {
writeFileSync(usagePath, `${record({ ts: NOW - 1 * DAY, prompt: 10_000 })}\n`);
const first = computeCockpit(ctx(), NOW);
writeFileSync(usagePath, `${record({ ts: NOW - 1 * DAY, prompt: 999_999 })}\n`);
const second = computeCockpit(ctx(), NOW + 5_000);
expect(second.tokens7d?.total).toBe(first.tokens7d?.total);
});
it("warm fields refresh once the TTL has elapsed", () => {
writeFileSync(usagePath, `${record({ ts: NOW - 1 * DAY, prompt: 10_000 })}\n`);
const first = computeCockpit(ctx(), NOW);
writeFileSync(usagePath, `${record({ ts: NOW - 1 * DAY, prompt: 999_999 })}\n`);
const second = computeCockpit(ctx(), NOW + 60_000);
expect(second.tokens7d?.total).not.toBe(first.tokens7d?.total);
expect(second.tokens7d?.total).toBeGreaterThan(first.tokens7d?.total ?? 0);
});
});