import { describe, it, expect } from "vitest"; import { extractSessionMetadata } from "../../src/server/services/session-metadata.js"; import { parseSessionContent } from "../../src/server/services/session-parser.js"; import fs from "fs/promises"; import path from "path"; describe("session-metadata", () => { it("messageCount matches parseSessionContent on sample-session.jsonl", async () => { const fixturePath = path.join(__dirname, "../fixtures/sample-session.jsonl"); const content = await fs.readFile(fixturePath, "utf-8"); const meta = extractSessionMetadata(content); const parsed = parseSessionContent(content); expect(meta.messageCount).toBe(parsed.length); }); it("messageCount matches parseSessionContent on edge-cases.jsonl", async () => { const fixturePath = path.join(__dirname, "../fixtures/edge-cases.jsonl"); const content = await fs.readFile(fixturePath, "utf-8"); const meta = extractSessionMetadata(content); const parsed = parseSessionContent(content); expect(meta.messageCount).toBe(parsed.length); }); it("firstPrompt skips system-reminder messages", () => { const content = [ JSON.stringify({ type: "user", message: { role: "user", content: "hook output" }, uuid: "u-sr", timestamp: "2025-01-01T00:00:00Z", }), JSON.stringify({ type: "user", message: { role: "user", content: "What is the project structure?" }, uuid: "u-real", timestamp: "2025-01-01T00:00:01Z", }), ].join("\n"); const meta = extractSessionMetadata(content); expect(meta.firstPrompt).toBe("What is the project structure?"); }); it("firstPrompt truncated to 200 chars", () => { const longMessage = "a".repeat(300); const content = JSON.stringify({ type: "user", message: { role: "user", content: longMessage }, uuid: "u-long", timestamp: "2025-01-01T00:00:00Z", }); const meta = extractSessionMetadata(content); expect(meta.firstPrompt).toHaveLength(200); expect(meta.firstPrompt).toBe("a".repeat(200)); }); it("summary captures the LAST summary line", () => { const content = [ JSON.stringify({ type: "summary", summary: "First summary", uuid: "s-1" }), JSON.stringify({ type: "user", message: { role: "user", content: "Hello" }, uuid: "u-1", }), JSON.stringify({ type: "summary", summary: "Last summary", uuid: "s-2" }), ].join("\n"); const meta = extractSessionMetadata(content); expect(meta.summary).toBe("Last summary"); }); it("timestamps captured from first and last lines with timestamps", () => { const content = [ JSON.stringify({ type: "user", message: { role: "user", content: "Hello" }, uuid: "u-1", timestamp: "2025-01-01T10:00:00Z", }), JSON.stringify({ type: "assistant", message: { role: "assistant", content: "Hi" }, uuid: "a-1", timestamp: "2025-01-01T10:05:00Z", }), JSON.stringify({ type: "summary", summary: "Session done", uuid: "s-1", }), ].join("\n"); const meta = extractSessionMetadata(content); expect(meta.firstTimestamp).toBe("2025-01-01T10:00:00Z"); expect(meta.lastTimestamp).toBe("2025-01-01T10:05:00Z"); }); it("empty content returns zero counts and empty strings", () => { const meta = extractSessionMetadata(""); expect(meta.messageCount).toBe(0); expect(meta.firstPrompt).toBe(""); expect(meta.summary).toBe(""); expect(meta.firstTimestamp).toBe(""); expect(meta.lastTimestamp).toBe(""); expect(meta.parseErrors).toBe(0); }); it("JSONL with no user messages returns empty firstPrompt", () => { const content = [ JSON.stringify({ type: "summary", summary: "No user", uuid: "s-1" }), JSON.stringify({ type: "progress", data: { type: "hook" }, uuid: "p-1" }), ].join("\n"); const meta = extractSessionMetadata(content); expect(meta.firstPrompt).toBe(""); }); it("JSONL with all system-reminder users returns empty firstPrompt", () => { const content = [ JSON.stringify({ type: "user", message: { role: "user", content: "r1" }, uuid: "u-1", }), JSON.stringify({ type: "user", message: { role: "user", content: "r2" }, uuid: "u-2", }), ].join("\n"); const meta = extractSessionMetadata(content); expect(meta.firstPrompt).toBe(""); }); it("single-line JSONL: firstTimestamp equals lastTimestamp", () => { const content = JSON.stringify({ type: "user", message: { role: "user", content: "solo" }, uuid: "u-solo", timestamp: "2025-06-15T12:00:00Z", }); const meta = extractSessionMetadata(content); expect(meta.firstTimestamp).toBe("2025-06-15T12:00:00Z"); expect(meta.lastTimestamp).toBe("2025-06-15T12:00:00Z"); }); it("reports parseErrors from malformed lines", () => { const content = [ "broken json", JSON.stringify({ type: "user", message: { role: "user", content: "ok" }, uuid: "u-1", }), "{truncated", ].join("\n"); const meta = extractSessionMetadata(content); expect(meta.parseErrors).toBe(2); expect(meta.messageCount).toBe(1); }); it("skips array user content for firstPrompt (only captures string content)", () => { const content = [ JSON.stringify({ type: "user", message: { role: "user", content: [ { type: "tool_result", tool_use_id: "t1", content: "result" }, ], }, uuid: "u-arr", }), JSON.stringify({ type: "user", message: { role: "user", content: "Second prompt as string" }, uuid: "u-str", }), ].join("\n"); const meta = extractSessionMetadata(content); expect(meta.firstPrompt).toBe("Second prompt as string"); }); });