import { describe, it, expect, beforeEach, afterEach } from "vitest"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; import { BaselineStore, normalizeReason, type Baseline } from "../src/baselineStore"; let dir: string; beforeEach(() => { dir = fs.mkdtempSync(path.join(os.tmpdir(), "baseline-store-")); }); afterEach(() => { fs.rmSync(dir, { recursive: true, force: true }); }); const KEY = "a1b2c3"; // a stand-in for the controller's sha256(uri) key function sample(uri = "file:///ws/notes/chapter-1.md"): Baseline { return { uri, text: "hello\nworld\n", capturedAt: "2026-06-11T00:00:00.000Z", reason: "entered" }; } describe("normalizeReason (INV-7 legacy migration)", () => { it("migrates legacy 'opened' → 'entered'", () => { expect(normalizeReason("opened")).toBe("entered"); }); it("migrates legacy 'machine-landing' → 'entered'", () => { expect(normalizeReason("machine-landing")).toBe("entered"); }); it("passes current reasons through unchanged", () => { expect(normalizeReason("entered")).toBe("entered"); expect(normalizeReason("pinned")).toBe("pinned"); expect(normalizeReason("head")).toBe("head"); }); }); describe("BaselineStore", () => { it("returns null for a key with no baseline", () => { expect(new BaselineStore(dir).load(KEY)).toBeNull(); }); it("computes the baseline path as baselines/.json", () => { const store = new BaselineStore(dir); expect(store.baselinePath(KEY)).toBe(path.join(dir, "baselines", "a1b2c3.json")); }); it("save then load round-trips the baseline (including the uri identity)", () => { const store = new BaselineStore(dir); const b = sample(); store.save(KEY, b); expect(store.load(KEY)).toEqual(b); }); it("migrates a legacy on-disk reason ('opened') to 'entered' on load", () => { const store = new BaselineStore(dir); fs.mkdirSync(path.dirname(store.baselinePath(KEY)), { recursive: true }); fs.writeFileSync( store.baselinePath(KEY), JSON.stringify({ uri: "file:///a.md", text: "legacy\n", capturedAt: "2026-06-11T00:00:00.000Z", reason: "opened" }), "utf8", ); expect(store.load(KEY)?.reason).toBe("entered"); }); it("migrates a legacy on-disk reason ('machine-landing') to 'entered' on load", () => { const store = new BaselineStore(dir); fs.mkdirSync(path.dirname(store.baselinePath(KEY)), { recursive: true }); fs.writeFileSync( store.baselinePath(KEY), JSON.stringify({ uri: "file:///a.md", text: "legacy\n", capturedAt: "2026-06-11T00:00:00.000Z", reason: "machine-landing" }), "utf8", ); expect(store.load(KEY)?.reason).toBe("entered"); }); it("overwrites in place: the newest epoch wins, no history kept", () => { const store = new BaselineStore(dir); store.save(KEY, { uri: "untitled:Untitled-1", text: "v1", capturedAt: "2026-06-11T00:00:00.000Z", reason: "entered" }); store.save(KEY, { uri: "untitled:Untitled-1", text: "v2", capturedAt: "2026-06-11T00:01:00.000Z", reason: "pinned" }); expect(store.load(KEY)).toEqual({ uri: "untitled:Untitled-1", text: "v2", capturedAt: "2026-06-11T00:01:00.000Z", reason: "pinned" }); }); it("writes pretty JSON with a trailing newline", () => { const store = new BaselineStore(dir); const b = sample(); store.save(KEY, b); const raw = fs.readFileSync(store.baselinePath(KEY), "utf8"); expect(raw).toBe(JSON.stringify(b, null, 2) + "\n"); }); it("distinct keys are independent files (no collision)", () => { const store = new BaselineStore(dir); store.save("k1", sample("file:///a.md")); store.save("k2", sample("file:///b.md")); expect(store.load("k1")!.uri).toBe("file:///a.md"); expect(store.load("k2")!.uri).toBe("file:///b.md"); }); });