dimensionlab-website/src/lib/server/db/dashboard-store.test.ts
2026-06-18 18:09:17 +02:00

149 lines
5 KiB
TypeScript

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);
}