110 lines
3.9 KiB
TypeScript
110 lines
3.9 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { mkdtemp, rm, readFile } from "node:fs/promises";
|
|
|
|
let runtimeRoot = "";
|
|
let data: typeof import("../src/lib/boss-data");
|
|
|
|
async function setup() {
|
|
if (runtimeRoot) return;
|
|
runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-state-migrations-"));
|
|
process.env.BOSS_RUNTIME_ROOT = runtimeRoot;
|
|
process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json");
|
|
data = await import("../src/lib/boss-data.ts");
|
|
}
|
|
|
|
test.after(async () => {
|
|
if (runtimeRoot) await rm(runtimeRoot, { recursive: true, force: true });
|
|
});
|
|
|
|
test("migrates legacy unversioned state into the current schema metadata and RBAC/Skill arrays", async () => {
|
|
await setup();
|
|
|
|
const migrated = data.migrateBossState({
|
|
accountDeviceGrants: [
|
|
{
|
|
account: "worker@example.com",
|
|
deviceId: "mac-studio",
|
|
permissions: ["device.view", "device.view", "invalid.permission"],
|
|
},
|
|
],
|
|
accountProjectGrants: [
|
|
{
|
|
account: "worker@example.com",
|
|
projectId: "master-agent",
|
|
permissions: ["project.view", "thread.chat"],
|
|
},
|
|
{
|
|
account: "broken@example.com",
|
|
projectId: "master-agent",
|
|
permissions: ["not-real"],
|
|
},
|
|
],
|
|
accountSkillGrants: undefined,
|
|
skillLifecycleRequests: [
|
|
{
|
|
deviceId: "mac-studio",
|
|
sourceUrl: "https://example.com/skills/demo.git",
|
|
action: "not-a-real-action",
|
|
status: "not-a-real-status",
|
|
},
|
|
],
|
|
permissionAuditLogs: [
|
|
{
|
|
actorAccount: "krisolo",
|
|
action: "unexpected-action",
|
|
targetAccount: "worker@example.com",
|
|
permissions: ["device.view", "bad.permission"],
|
|
},
|
|
],
|
|
} as unknown as Partial<data.BossState>);
|
|
|
|
assert.equal(migrated.schemaVersion, data.CURRENT_BOSS_STATE_SCHEMA_VERSION);
|
|
assert.match(migrated.migratedAt, /^\d{4}-\d{2}-\d{2}T/);
|
|
assert.deepEqual(migrated.accountDeviceGrants[0]?.permissions, ["device.view"]);
|
|
assert.deepEqual(migrated.accountProjectGrants.map((grant) => grant.account), ["worker@example.com"]);
|
|
assert.deepEqual(migrated.accountSkillGrants, []);
|
|
assert.equal(migrated.skillLifecycleRequests[0]?.action, "install");
|
|
assert.equal(migrated.skillLifecycleRequests[0]?.status, "pending");
|
|
assert.equal(migrated.permissionAuditLogs[0]?.action, "grant.updated");
|
|
assert.deepEqual(migrated.permissionAuditLogs[0]?.permissions, ["device.view"]);
|
|
});
|
|
|
|
test("preserves current schema migration metadata instead of rewriting it on every normalize", async () => {
|
|
await setup();
|
|
|
|
const migrated = data.migrateBossState({
|
|
schemaVersion: data.CURRENT_BOSS_STATE_SCHEMA_VERSION,
|
|
migratedAt: "2026-04-20T08:00:00.000Z",
|
|
accountDeviceGrants: [],
|
|
accountProjectGrants: [],
|
|
accountSkillGrants: [],
|
|
skillLifecycleRequests: [],
|
|
permissionAuditLogs: [],
|
|
} as Partial<data.BossState>);
|
|
|
|
assert.equal(migrated.schemaVersion, data.CURRENT_BOSS_STATE_SCHEMA_VERSION);
|
|
assert.equal(migrated.migratedAt, "2026-04-20T08:00:00.000Z");
|
|
});
|
|
|
|
test("writeState persists schema metadata while keeping the state file JSON-compatible", async () => {
|
|
await setup();
|
|
|
|
const state = await data.readState();
|
|
assert.equal(state.schemaVersion, data.CURRENT_BOSS_STATE_SCHEMA_VERSION);
|
|
assert.match(state.migratedAt, /^\d{4}-\d{2}-\d{2}T/);
|
|
|
|
await data.writeState(state);
|
|
|
|
const persisted = JSON.parse(await readFile(process.env.BOSS_STATE_FILE as string, "utf8"));
|
|
assert.equal(persisted.schemaVersion, data.CURRENT_BOSS_STATE_SCHEMA_VERSION);
|
|
assert.equal(persisted.migratedAt, state.migratedAt);
|
|
assert.ok(Array.isArray(persisted.accountDeviceGrants));
|
|
assert.ok(Array.isArray(persisted.accountProjectGrants));
|
|
assert.ok(Array.isArray(persisted.accountSkillGrants));
|
|
assert.ok(Array.isArray(persisted.skillLifecycleRequests));
|
|
assert.ok(Array.isArray(persisted.permissionAuditLogs));
|
|
});
|