import { describe, it, expect } from "vitest"; import { parseSessionContent } from "../../src/server/services/session-parser.js"; import fs from "fs/promises"; import path from "path"; describe("session-parser", () => { it("parses user messages with string content", () => { const line = JSON.stringify({ type: "user", message: { role: "user", content: "Hello world" }, uuid: "u-1", timestamp: "2025-10-15T10:00:00Z", }); const msgs = parseSessionContent(line); expect(msgs).toHaveLength(1); expect(msgs[0].category).toBe("user_message"); expect(msgs[0].content).toBe("Hello world"); expect(msgs[0].uuid).toBe("u-1"); expect(msgs[0].timestamp).toBe("2025-10-15T10:00:00Z"); }); it("parses user messages with tool_result array content", () => { const line = JSON.stringify({ type: "user", message: { role: "user", content: [ { type: "tool_result", tool_use_id: "toolu_01", content: "File contents here" }, ], }, uuid: "u-2", }); const msgs = parseSessionContent(line); expect(msgs).toHaveLength(1); expect(msgs[0].category).toBe("tool_result"); expect(msgs[0].content).toBe("File contents here"); }); it("parses assistant text blocks", () => { const line = JSON.stringify({ type: "assistant", message: { role: "assistant", content: [{ type: "text", text: "Here is my response" }], }, uuid: "a-1", }); const msgs = parseSessionContent(line); expect(msgs).toHaveLength(1); expect(msgs[0].category).toBe("assistant_text"); expect(msgs[0].content).toBe("Here is my response"); }); it("parses thinking blocks", () => { const line = JSON.stringify({ type: "assistant", message: { role: "assistant", content: [{ type: "thinking", thinking: "Let me think about this..." }], }, uuid: "a-2", }); const msgs = parseSessionContent(line); expect(msgs).toHaveLength(1); expect(msgs[0].category).toBe("thinking"); expect(msgs[0].content).toBe("Let me think about this..."); }); it("parses tool_use blocks", () => { const line = JSON.stringify({ type: "assistant", message: { role: "assistant", content: [ { type: "tool_use", name: "Read", input: { file_path: "/src/index.ts" } }, ], }, uuid: "a-3", }); const msgs = parseSessionContent(line); expect(msgs).toHaveLength(1); expect(msgs[0].category).toBe("tool_call"); expect(msgs[0].toolName).toBe("Read"); expect(msgs[0].toolInput).toContain("/src/index.ts"); }); it("parses progress messages from data field", () => { const line = JSON.stringify({ type: "progress", data: { type: "hook", hookEvent: "PreToolUse", hookName: "security_check" }, uuid: "p-1", timestamp: "2025-10-15T10:00:00Z", }); const msgs = parseSessionContent(line); expect(msgs).toHaveLength(1); expect(msgs[0].category).toBe("hook_progress"); expect(msgs[0].content).toContain("PreToolUse"); expect(msgs[0].content).toContain("security_check"); }); it("parses file-history-snapshot messages", () => { const line = JSON.stringify({ type: "file-history-snapshot", messageId: "snap-1", snapshot: { messageId: "snap-1", trackedFileBackups: [], timestamp: "2025-10-15T10:00:00Z" }, }); const msgs = parseSessionContent(line); expect(msgs).toHaveLength(1); expect(msgs[0].category).toBe("file_snapshot"); }); it("parses summary messages from summary field", () => { const line = JSON.stringify({ type: "summary", summary: "Session summary here", leafUuid: "msg-10", }); const msgs = parseSessionContent(line); expect(msgs).toHaveLength(1); expect(msgs[0].category).toBe("summary"); expect(msgs[0].content).toBe("Session summary here"); }); it("skips system metadata lines (turn_duration)", () => { const line = JSON.stringify({ type: "system", subtype: "turn_duration", durationMs: 50000, uuid: "sys-1", }); const msgs = parseSessionContent(line); expect(msgs).toHaveLength(0); }); it("skips queue-operation lines", () => { const line = JSON.stringify({ type: "queue-operation", uuid: "qo-1", }); const msgs = parseSessionContent(line); expect(msgs).toHaveLength(0); }); it("detects system-reminder content in user messages", () => { const line = JSON.stringify({ type: "user", message: { role: "user", content: "Some reminder", }, uuid: "u-sr", }); const msgs = parseSessionContent(line); expect(msgs).toHaveLength(1); expect(msgs[0].category).toBe("system_message"); }); it("skips malformed JSONL lines without crashing", () => { const content = [ "not valid json", JSON.stringify({ type: "user", message: { role: "user", content: "Valid message" }, uuid: "u-valid", }), "{broken}", ].join("\n"); const msgs = parseSessionContent(content); expect(msgs).toHaveLength(1); expect(msgs[0].content).toBe("Valid message"); }); it("returns empty array for empty files", () => { const msgs = parseSessionContent(""); expect(msgs).toEqual([]); }); it("uses uuid from the JSONL line, not random", () => { const line = JSON.stringify({ type: "user", message: { role: "user", content: "Test" }, uuid: "my-specific-uuid-123", }); const msgs = parseSessionContent(line); expect(msgs[0].uuid).toBe("my-specific-uuid-123"); }); it("parses the full sample session fixture", async () => { const fixturePath = path.join( __dirname, "../fixtures/sample-session.jsonl" ); const content = await fs.readFile(fixturePath, "utf-8"); const msgs = parseSessionContent(content); const categories = new Set(msgs.map((m) => m.category)); expect(categories.has("user_message")).toBe(true); expect(categories.has("assistant_text")).toBe(true); expect(categories.has("thinking")).toBe(true); expect(categories.has("tool_call")).toBe(true); expect(categories.has("tool_result")).toBe(true); expect(categories.has("system_message")).toBe(true); expect(categories.has("hook_progress")).toBe(true); expect(categories.has("summary")).toBe(true); expect(categories.has("file_snapshot")).toBe(true); }); it("handles edge-cases fixture (corrupt lines)", async () => { const fixturePath = path.join( __dirname, "../fixtures/edge-cases.jsonl" ); const content = await fs.readFile(fixturePath, "utf-8"); const msgs = parseSessionContent(content); expect(msgs).toHaveLength(2); expect(msgs[0].category).toBe("user_message"); expect(msgs[1].category).toBe("assistant_text"); }); });