test: add unit tests for readSessionMessages stable ID
Verify that readSessionMessages includes the JSONL entry ID in returned messages. Tests cover: - Messages with stable IDs are preserved - Messages without IDs handle gracefully (id: undefined) - Invalid JSON and empty lines are skipped - Message content and metadata are preserved - Nonexistent files return empty array
This commit is contained in:
parent
3a48941c96
commit
56692db403
@ -5,6 +5,7 @@ import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|||||||
import {
|
import {
|
||||||
readFirstUserMessageFromTranscript,
|
readFirstUserMessageFromTranscript,
|
||||||
readLastMessagePreviewFromTranscript,
|
readLastMessagePreviewFromTranscript,
|
||||||
|
readSessionMessages,
|
||||||
readSessionPreviewItemsFromTranscript,
|
readSessionPreviewItemsFromTranscript,
|
||||||
} from "./session-utils.fs.js";
|
} from "./session-utils.fs.js";
|
||||||
|
|
||||||
@ -404,3 +405,119 @@ describe("readSessionPreviewItemsFromTranscript", () => {
|
|||||||
expect(result[0]?.text.endsWith("...")).toBe(true);
|
expect(result[0]?.text.endsWith("...")).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("readSessionMessages", () => {
|
||||||
|
let tmpDir: string;
|
||||||
|
let sessionFile: string;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "session-messages-test-"));
|
||||||
|
sessionFile = path.join(tmpDir, "test-session.jsonl");
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
test("includes stable entry ID in returned messages", () => {
|
||||||
|
// Create JSONL transcript with entries that have IDs
|
||||||
|
const entries = [
|
||||||
|
{
|
||||||
|
id: "entry-1",
|
||||||
|
message: { role: "user", content: "Hello" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "entry-2",
|
||||||
|
message: { role: "assistant", content: "Hi there!" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "entry-3",
|
||||||
|
message: { role: "user", content: "How are you?" },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const jsonl = entries.map((e) => JSON.stringify(e)).join("\n");
|
||||||
|
fs.writeFileSync(sessionFile, jsonl, "utf-8");
|
||||||
|
|
||||||
|
// Read messages
|
||||||
|
const messages = readSessionMessages("test-session", undefined, sessionFile);
|
||||||
|
|
||||||
|
// Verify each message includes the stable entry ID
|
||||||
|
expect(messages).toHaveLength(3);
|
||||||
|
expect(messages[0]).toMatchObject({ role: "user", content: "Hello", id: "entry-1" });
|
||||||
|
expect(messages[1]).toMatchObject({ role: "assistant", content: "Hi there!", id: "entry-2" });
|
||||||
|
expect(messages[2]).toMatchObject({ role: "user", content: "How are you?", id: "entry-3" });
|
||||||
|
});
|
||||||
|
|
||||||
|
test("handles entries without IDs gracefully", () => {
|
||||||
|
// Create JSONL with mixed entries (some with IDs, some without)
|
||||||
|
const entries = [
|
||||||
|
{
|
||||||
|
id: "entry-1",
|
||||||
|
message: { role: "user", content: "Hello" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// No id field
|
||||||
|
message: { role: "assistant", content: "Hi!" },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const jsonl = entries.map((e) => JSON.stringify(e)).join("\n");
|
||||||
|
fs.writeFileSync(sessionFile, jsonl, "utf-8");
|
||||||
|
|
||||||
|
const messages = readSessionMessages("test-session", undefined, sessionFile);
|
||||||
|
|
||||||
|
expect(messages).toHaveLength(2);
|
||||||
|
expect(messages[0]).toMatchObject({ role: "user", content: "Hello", id: "entry-1" });
|
||||||
|
// Second message should have id: undefined (spread of undefined value)
|
||||||
|
expect(messages[1]).toMatchObject({ role: "assistant", content: "Hi!", id: undefined });
|
||||||
|
});
|
||||||
|
|
||||||
|
test("skips empty lines and invalid JSON", () => {
|
||||||
|
const jsonl = [
|
||||||
|
JSON.stringify({ id: "entry-1", message: { role: "user", content: "Valid" } }),
|
||||||
|
"", // Empty line
|
||||||
|
"invalid json {",
|
||||||
|
JSON.stringify({ id: "entry-2", message: { role: "user", content: "Also valid" } }),
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
fs.writeFileSync(sessionFile, jsonl, "utf-8");
|
||||||
|
|
||||||
|
const messages = readSessionMessages("test-session", undefined, sessionFile);
|
||||||
|
|
||||||
|
expect(messages).toHaveLength(2);
|
||||||
|
expect(messages[0]).toMatchObject({ id: "entry-1" });
|
||||||
|
expect(messages[1]).toMatchObject({ id: "entry-2" });
|
||||||
|
});
|
||||||
|
|
||||||
|
test("returns empty array when session file does not exist", () => {
|
||||||
|
const messages = readSessionMessages("nonexistent", undefined, "/nonexistent/path.jsonl");
|
||||||
|
expect(messages).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("preserves message content and metadata", () => {
|
||||||
|
const entries = [
|
||||||
|
{
|
||||||
|
id: "msg-1",
|
||||||
|
message: {
|
||||||
|
role: "assistant",
|
||||||
|
content: [{ type: "text", text: "Complex content" }],
|
||||||
|
metadata: { foo: "bar" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const jsonl = entries.map((e) => JSON.stringify(e)).join("\n");
|
||||||
|
fs.writeFileSync(sessionFile, jsonl, "utf-8");
|
||||||
|
|
||||||
|
const messages = readSessionMessages("test-session", undefined, sessionFile);
|
||||||
|
|
||||||
|
expect(messages).toHaveLength(1);
|
||||||
|
expect(messages[0]).toMatchObject({
|
||||||
|
role: "assistant",
|
||||||
|
content: [{ type: "text", text: "Complex content" }],
|
||||||
|
metadata: { foo: "bar" },
|
||||||
|
id: "msg-1",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user