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 } 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[] = []; afterEach(() => { 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", ]); }); }); 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); }