import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { describe, expect, it, beforeEach, afterEach } from "vitest";
import { truncateJsonlAfterMessage } from "./jsonl-truncate.js";
let tmpDir: string;
beforeEach(async () => {
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "jsonl-trunc-"));
});
afterEach(async () => {
await fs.rm(tmpDir, { recursive: true, force: true });
});
async function write(file: string, content: string): Promise<void> {
await fs.writeFile(file, content, "utf8");
}
async function read(file: string): Promise<string> {
return fs.readFile(file, "utf8");
}
describe("truncateJsonlAfterMessage", () => {
it("truncates everything after the line containing the target message id", async () => {
const file = path.join(tmpDir, "s.jsonl");
const lines = [
`{"type":"user_message","id":"m1","text":"hi"}`,
`{"type":"assistant_message","id":"m2","text":"hello"}`,
`{"type":"user_message","id":"m3","text":"more"}`,
`{"type":"assistant_message","id":"m4","text":"reply"}`,
];
await write(file, lines.join("\n") + "\n");
const result = await truncateJsonlAfterMessage(file, "m2");
expect(result.truncated).toBe(true);
const after = await read(file);
expect(after).toBe(lines.slice(0, 2).join("\n") + "\n");
});
it("is a no-op when the target id is the last line", async () => {
const file = path.join(tmpDir, "s.jsonl");
const lines = [`{"id":"m1"}`, `{"id":"m2"}`];
await write(file, lines.join("\n") + "\n");
const result = await truncateJsonlAfterMessage(file, "m2");
expect(result.truncated).toBe(false);
const after = await read(file);
expect(after).toBe(lines.join("\n") + "\n");
});
it("throws if the message id is not found", async () => {
const file = path.join(tmpDir, "s.jsonl");
await write(file, `{"id":"m1"}\n`);
await expect(truncateJsonlAfterMessage(file, "missing")).rejects.toThrow(/not found/);
});
it("handles a file without trailing newline", async () => {
const file = path.join(tmpDir, "s.jsonl");
await write(file, `{"id":"m1"}\n{"id":"m2"}`);
const result = await truncateJsonlAfterMessage(file, "m1");
expect(result.truncated).toBe(true);
expect(await read(file)).toBe(`{"id":"m1"}\n`);
});
it("handles missing file by treating message as already truncated", async () => {
const file = path.join(tmpDir, "missing.jsonl");
const result = await truncateJsonlAfterMessage(file, "m1", { missingOk: true });
expect(result.truncated).toBe(false);
expect(result.fileExisted).toBe(false);
expect(result.idFound).toBe(false);
});
it("treats id-not-found as no-op when idNotFoundOk is true", async () => {
const file = path.join(tmpDir, "s.jsonl");
const lines = [`{"id":"m1"}`, `{"id":"m2"}`];
await write(file, lines.join("\n") + "\n");
const result = await truncateJsonlAfterMessage(file, "missing", { idNotFoundOk: true });
expect(result.fileExisted).toBe(true);
expect(result.truncated).toBe(false);
expect(result.idFound).toBe(false);
expect(await read(file)).toBe(lines.join("\n") + "\n");
});
});