import { existsSync, rmSync } from "node:fs"; import { mkdtemp } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, describe, expect, test, vi } from "vitest"; import { genericDashboardFixture } from "$lib/model/fixtures/generic"; import type { DashboardDocument } from "$lib/model"; import { DashboardPersistenceValidationError, createDashboardStore, type DashboardStore, } from "./dashboard-store"; const stores: DashboardStore[] = []; const tempRoots: string[] = []; const originalCwd = process.cwd(); afterEach(() => { vi.useRealTimers(); process.chdir(originalCwd); stores.splice(0).forEach((store) => store.close()); tempRoots.splice(0).forEach((path) => rmSync(path, { force: true, recursive: true })); }); describe("dashboard persistence store", () => { test("seeds the first active dashboard and creates the sqlite file", async () => { const { dbPath, store } = await createTestStore(); const seed = store.seedDashboardIfEmpty(genericDashboardFixture, { actor: "test-seed", message: "initial fixture", }); expect(existsSync(dbPath)).toBe(true); expect(seed.operation).toBe("seed"); expect(seed.actor).toBe("test-seed"); expect(seed.schemaVersion).toBe(genericDashboardFixture.schemaVersion); expect(seed.document.metadata.title).toBe("Operations Console"); const active = store.getActiveDashboard(); expect(active?.currentRevisionId).toBe(seed.id); expect(active?.document.metadata.title).toBe("Operations Console"); expect(store.listRevisions()).toHaveLength(1); }); test("commits validated updates and records revision metadata", async () => { const { store } = await createTestStore(); const seed = store.seedDashboardIfEmpty(genericDashboardFixture, { actor: "test-seed", }); const updated = cloneDashboard({ ...genericDashboardFixture, metadata: { ...genericDashboardFixture.metadata, title: "Operations Console Updated", }, }); const revision = store.commitDashboard(updated, { actor: "agent", message: "rename dashboard", }); expect(revision.operation).toBe("commit"); expect(revision.actor).toBe("agent"); expect(revision.message).toBe("rename dashboard"); expect(revision.document.metadata.title).toBe("Operations Console Updated"); expect(store.getActiveDashboard()?.currentRevisionId).toBe(revision.id); expect(store.getRevision(seed.id)?.document.metadata.title).toBe("Operations Console"); expect(store.listRevisions().map((item) => item.id)).toEqual([ revision.id, seed.id, ]); }); test("rejects invalid writes without changing the active revision", async () => { const { store } = await createTestStore(); const seed = store.seedDashboardIfEmpty(genericDashboardFixture, { actor: "test-seed", }); const invalid = cloneDashboard({ ...genericDashboardFixture, layout: { ...genericDashboardFixture.layout, telemetry: ["missing-card"], }, }); expect(() => store.commitDashboard(invalid, { actor: "agent", message: "invalid update", }), ).toThrow(DashboardPersistenceValidationError); const active = store.getActiveDashboard(); expect(active?.currentRevisionId).toBe(seed.id); expect(active?.document.layout.telemetry).toEqual(genericDashboardFixture.layout.telemetry); expect(store.listRevisions()).toHaveLength(1); }); test("loads specific revisions and rolls back to a prior valid document", async () => { const { store } = await createTestStore(); const seed = store.seedDashboardIfEmpty(genericDashboardFixture, { actor: "test-seed", }); const updated = cloneDashboard({ ...genericDashboardFixture, metadata: { ...genericDashboardFixture.metadata, title: "Changed Dashboard", }, }); const change = store.commitDashboard(updated, { actor: "agent", message: "change title", }); const rollback = store.rollbackToRevision(seed.id, { actor: "operator", message: "restore seed", }); expect(store.getRevision(change.id)?.document.metadata.title).toBe("Changed Dashboard"); expect(rollback.operation).toBe("rollback"); expect(rollback.sourceRevisionId).toBe(seed.id); expect(rollback.document.metadata.title).toBe("Operations Console"); expect(store.getActiveDashboard()?.currentRevisionId).toBe(rollback.id); expect(store.getActiveDashboard()?.document.metadata.title).toBe("Operations Console"); expect(store.listRevisions().map((item) => item.operation)).toEqual([ "rollback", "commit", "seed", ]); }); test("assigns monotonic revision timestamps for stable history ordering", async () => { vi.useFakeTimers(); vi.setSystemTime(new Date("2026-06-18T12:00:00.000Z")); const { store } = await createTestStore(); const seed = store.seedDashboardIfEmpty(genericDashboardFixture, { actor: "test-seed", }); const updated = cloneDashboard({ ...genericDashboardFixture, metadata: { ...genericDashboardFixture.metadata, title: "Changed Dashboard", }, }); const commit = store.commitDashboard(updated, { actor: "agent" }); const rollback = store.rollbackToRevision(seed.id, { actor: "operator" }); expect([ seed.createdAt.toISOString(), commit.createdAt.toISOString(), rollback.createdAt.toISOString(), ]).toEqual([ "2026-06-18T12:00:00.000Z", "2026-06-18T12:00:00.001Z", "2026-06-18T12:00:00.002Z", ]); expect(store.listRevisions().map((item) => item.id)).toEqual([ rollback.id, commit.id, seed.id, ]); }); test("applies migrations when launched outside the repository root", async () => { const root = await mkdtemp(join(tmpdir(), "dimensionlab-dashboard-cwd-")); tempRoots.push(root); process.chdir(root); vi.resetModules(); const { createDashboardStore: createStore } = await import("./dashboard-store"); const store = createStore({ databaseUrl: `file:${join(root, "dashboard.sqlite")}`, }); stores.push(store); const seed = store.seedDashboardIfEmpty(genericDashboardFixture, { actor: "test-seed", }); expect(seed.document.metadata.title).toBe("Operations Console"); expect(store.getActiveDashboard()?.currentRevisionId).toBe(seed.id); }); }); async function createTestStore() { const root = await mkdtemp(join(tmpdir(), "dimensionlab-dashboard-store-")); tempRoots.push(root); const dbPath = join(root, "nested", "dashboard.sqlite"); const store = createDashboardStore({ databaseUrl: `file:${dbPath}`, }); stores.push(store); return { dbPath, store }; } function cloneDashboard(document: DashboardDocument): DashboardDocument { return structuredClone(document); }