1import { describe, expect, it } from "@effect/vitest";
2
3import { parseClaudeLine } from "./claudeUsage.ts";
4
5/** Shaped after a real Claude Code assistant record. */
6function claudeLine(overrides: {
7 messageId: string;
8 contentType: string;
9 model?: string;
10 outputTokens?: number;
11 speed?: string;
12}): string {
13 return JSON.stringify({
14 type: "assistant",
15 timestamp: "2026-08-07T04:05:13.944Z",
16 sessionId: "5a128faa-8253-489e-b935-6c08e8e670c0",
17 cwd: "/home/theo/project",
18 message: {
19 id: overrides.messageId,
20 role: "assistant",
21 model: overrides.model ?? "claude-fable-5",
22 content: [{ type: overrides.contentType }],
23 usage: {
24 input_tokens: 2,
25 cache_creation_input_tokens: 66818,
26 cache_read_input_tokens: 1000,
27 output_tokens: overrides.outputTokens ?? 286,
28 ...(overrides.speed === undefined ? {} : { speed: overrides.speed }),
29 },
30 },
31 });
32}
33
34describe("parseClaudeLine", () => {
35 it("extracts token totals and a dedupe key", () => {
36 const record = parseClaudeLine(claudeLine({ messageId: "msg_1", contentType: "text" }));
37
38 expect(record).not.toBeNull();
39 expect(record?.provider).toBe("claude");
40 expect(record?.model).toBe("claude-fable-5");
41 expect(record?.totals).toEqual({
42 uncachedInputTokens: 2,
43 cachedInputTokens: 1000,
44 cacheCreationTokens: 66818,
45 outputTokens: 286,
46 reasoningTokens: 0,
47 });
48 expect(record?.dedupeKey).toBe("msg_1:");
49 expect(record?.speed).toBe("standard");
50 });
51
52 it("marks fast-mode requests", () => {
53 const line = (speed: string) =>
54 parseClaudeLine(claudeLine({ messageId: "msg_1", contentType: "text", speed }));
55
56 expect(line("fast")?.speed).toBe("fast");
57 expect(line("standard")?.speed).toBe("standard");
58 });
59
60 it("gives every content block of one message the same dedupe key", () => {
61 // T3 Code writes one record per content block, each repeating the parent
62 // message's full usage. Summing them would overcount ~2.4x on real data.
63 const text = parseClaudeLine(claudeLine({ messageId: "msg_2", contentType: "text" }));
64 const toolUse = parseClaudeLine(claudeLine({ messageId: "msg_2", contentType: "tool_use" }));
65
66 expect(text?.dedupeKey).toBe(toolUse?.dedupeKey);
67 expect(text?.totals).toEqual(toolUse?.totals);
68 });
69
70 it("ignores records that are not assistant messages", () => {
71 expect(parseClaudeLine(JSON.stringify({ type: "user", message: {} }))).toBeNull();
72 expect(parseClaudeLine("not json")).toBeNull();
73 });
74});