/*
Unit tests for jsonl covering its documented public contract and regression-sensitive edge cases.
- Not a contract source of truth — the module under test owns the authoritative behavior; fixtures are minimal scaffolding only.
- RFC-1311: Compass header sweep — declares the jsonl suite's contract for test-file scaffold coverage.
*/
import { test, expect, beforeEach, afterEach } from "vitest";
import { mkdtempSync, rmSync, writeFileSync, readFileSync } from "node:fs";
import path from "node:path";
import os from "node:os";
import { appendRecord, readJournal, findIncompleteOperation, TornLineError } from "../jsonl.ts";
import type { JournalRecord } from "../types.ts";
let tmpDir: string;
let journalPath: string;
beforeEach(() => {
tmpDir = mkdtempSync(path.join(os.tmpdir(), "journal-jsonl-"));
journalPath = path.join(tmpDir, "journal.jsonl");
});
afterEach(() => {
rmSync(tmpDir, { recursive: true, force: true });
});
test("readJournal returns empty array for non-existent file", async () => {
const records = await readJournal(journalPath);
expect(records).toEqual([]);
});
test("readJournal returns empty array for empty file", async () => {
writeFileSync(journalPath, "");
const records = await readJournal(journalPath);
expect(records).toEqual([]);
});
test("appendRecord + readJournal round-trip", async () => {
const r1: JournalRecord = {
kind: "op-started",
opId: "op-1",
op: "mission.close",
missionId: "m-001",
at: "2026-08-27T00:00:00.000Z",
platformVersion: "1.0.0",
};
const r2: JournalRecord = {
kind: "step-started",
opId: "op-1",
step: "push-cache-to-bare",
seq: 0,
at: "2026-08-27T00:00:01.000Z",
};
await appendRecord(journalPath, r1);
await appendRecord(journalPath, r2);
const records = await readJournal(journalPath);
expect(records).toHaveLength(2);
expect(records[0]).toEqual(r1);
expect(records[1]).toEqual(r2);
});
test("readJournal discards torn final line (truncated JSON)", async () => {
const validLine =
JSON.stringify({
kind: "op-started",
opId: "op-1",
op: "mission.close",
missionId: "m-001",
at: "2026-08-27T00:00:00.000Z",
platformVersion: "1.0.0",
}) + "\n";
const tornLine = '{"kind":"step-started","opId":"op-1","step":"incomp';
writeFileSync(journalPath, validLine + tornLine);
await expect(readJournal(journalPath)).rejects.toThrow(TornLineError);
});
test("readJournal throws TornLineError for corrupt line in middle", async () => {
const valid = JSON.stringify({
kind: "op-started",
opId: "op-1",
op: "mission.close",
missionId: "m-001",
at: "2026-08-27T00:00:00.000Z",
platformVersion: "1.0.0",
});
const corrupt = "not valid json";
writeFileSync(journalPath, `${valid}\n${corrupt}\n${valid}\n`);
await expect(readJournal(journalPath)).rejects.toThrow(TornLineError);
});
test("appendRecord creates parent directories", async () => {
const nestedPath = path.join(tmpDir, "deep", "nested", "dir", "journal.jsonl");
await appendRecord(nestedPath, {
kind: "op-started",
opId: "op-1",
op: "mission.close",
missionId: "m-001",
at: "2026-08-27T00:00:00.000Z",
platformVersion: "1.0.0",
});
const content = readFileSync(nestedPath, "utf8");
expect(content).toContain("op-started");
});
test("findIncompleteOperation returns null for empty records", () => {
expect(findIncompleteOperation([])).toBeNull();
});
test("findIncompleteOperation returns null when op-done exists", () => {
const records: JournalRecord[] = [
{
kind: "op-started",
opId: "op-1",
op: "mission.close",
missionId: "m-001",
at: "t0",
platformVersion: "1.0.0",
},
{ kind: "step-started", opId: "op-1", step: "s0", seq: 0, at: "t1" },
{ kind: "step-done", opId: "op-1", step: "s0", seq: 0, at: "t2" },
{ kind: "op-done", opId: "op-1", at: "t3" },
];
expect(findIncompleteOperation(records)).toBeNull();
});
test("findIncompleteOperation returns null when op-abandoned exists", () => {
const records: JournalRecord[] = [
{
kind: "op-started",
opId: "op-1",
op: "mission.close",
missionId: "m-001",
at: "t0",
platformVersion: "1.0.0",
},
{ kind: "op-abandoned", opId: "op-1", at: "t1", reason: "manual" },
];
expect(findIncompleteOperation(records)).toBeNull();
});
test("findIncompleteOperation detects incomplete op with crash step", () => {
const records: JournalRecord[] = [
{
kind: "op-started",
opId: "op-1",
op: "mission.close",
missionId: "m-001",
at: "t0",
platformVersion: "1.0.0",
},
{ kind: "step-started", opId: "op-1", step: "s0", seq: 0, at: "t1" },
{ kind: "step-done", opId: "op-1", step: "s0", seq: 0, at: "t2" },
{ kind: "step-started", opId: "op-1", step: "s1", seq: 1, at: "t3" },
];
const result = findIncompleteOperation(records);
expect(result).toEqual({ opId: "op-1", op: "mission.close", lastSeq: 1, status: "running" });
});
test("findIncompleteOperation uses last op-started when multiple exist", () => {
const records: JournalRecord[] = [
{
kind: "op-started",
opId: "op-1",
op: "mission.close",
missionId: "m-001",
at: "t0",
platformVersion: "1.0.0",
},
{ kind: "op-done", opId: "op-1", at: "t1" },
{
kind: "op-started",
opId: "op-2",
op: "mission.reconcile",
missionId: "m-001",
at: "t2",
platformVersion: "1.0.0",
},
{ kind: "step-started", opId: "op-2", step: "s0", seq: 0, at: "t3" },
];
const result = findIncompleteOperation(records);
expect(result).toEqual({
opId: "op-2",
op: "mission.reconcile",
lastSeq: 0,
status: "running",
});
});