import test from "node:test"; import assert from "node:assert/strict"; import os from "node:os"; import path from "node:path"; import { mkdtemp, rm } from "node:fs/promises"; let runtimeRoot = ""; let data: typeof import("../src/lib/boss-data"); let permissions: typeof import("../src/lib/boss-permissions"); let baseState: Awaited>; async function setup() { if (!runtimeRoot) { runtimeRoot = await mkdtemp(path.join(os.tmpdir(), "boss-rbac-permissions-")); process.env.BOSS_RUNTIME_ROOT = runtimeRoot; process.env.BOSS_STATE_FILE = path.join(runtimeRoot, "boss-state.json"); } if (!data) { data = await import("../src/lib/boss-data.ts"); baseState = structuredClone(await data.readState()); } if (!permissions) { permissions = await import("../src/lib/boss-permissions.ts"); } } test.after(async () => { if (runtimeRoot) { await rm(runtimeRoot, { recursive: true, force: true }); } }); test.beforeEach(async () => { await setup(); await data.writeState({ ...structuredClone(baseState), accountDeviceGrants: [], accountProjectGrants: [], accountSkillGrants: [], skillCatalog: [], permissionAuditLogs: [], }); }); test("highest admin can access every device and project without explicit grants", async () => { const state = await data.readState(); const session = { account: "krisolo", role: "highest_admin" as const, displayName: "Boss 超级管理员", }; assert.equal(permissions.canAccessDevice(state, session, "mac-studio", "device.view"), true); assert.equal(permissions.canAccessProject(state, session, "audit-collab", "project.view"), true); }); test("device.view grant gives project read visibility but not thread chat", async () => { const state = await data.readState(); state.accountDeviceGrants = [ { grantId: "grant-device-view", account: "worker@example.com", deviceId: "mac-studio", permissions: ["device.view"], grantedBy: "krisolo", grantedAt: "2026-04-26T12:00:00+08:00", }, ]; await data.writeState(state); const next = await data.readState(); const session = { account: "worker@example.com", role: "member" as const, displayName: "Worker", }; assert.equal(permissions.canAccessDevice(next, session, "mac-studio", "device.view"), true); assert.equal(permissions.canAccessProject(next, session, "master-agent", "project.view"), true); assert.equal(permissions.canAccessProject(next, session, "master-agent", "thread.chat"), false); }); test("explicit project thread.chat grant allows posting to that project", async () => { const state = await data.readState(); state.accountProjectGrants = [ { grantId: "grant-thread-chat", account: "worker@example.com", projectId: "master-agent", permissions: ["project.view", "thread.chat", "master_agent.ask"], grantedBy: "krisolo", grantedAt: "2026-04-26T12:00:00+08:00", }, ]; await data.writeState(state); const next = await data.readState(); const session = { account: "worker@example.com", role: "member" as const, displayName: "Worker", }; assert.equal(permissions.canAccessProject(next, session, "master-agent", "project.view"), true); assert.equal(permissions.canAccessProject(next, session, "master-agent", "thread.chat"), true); assert.equal(permissions.canAccessProject(next, session, "master-agent", "computer.control"), false); }); test("expired grants are ignored", async () => { const state = await data.readState(); state.accountDeviceGrants = [ { grantId: "expired-device-grant", account: "worker@example.com", deviceId: "mac-studio", permissions: ["device.view"], grantedBy: "krisolo", grantedAt: "2026-04-25T12:00:00+08:00", expiresAt: "2000-01-01T00:00:00.000Z", }, ]; await data.writeState(state); const next = await data.readState(); const session = { account: "worker@example.com", role: "member" as const, displayName: "Worker", }; assert.equal(permissions.canAccessDevice(next, session, "mac-studio", "device.view"), false); }); test("legacy device account ownership remains a compatibility fallback", async () => { const state = await data.readState(); state.devices.push({ id: "worker-mac", name: "Worker Mac", avatar: "W", account: "worker@example.com", source: "production", status: "online", projects: ["worker-project"], quota5h: 0, quota7d: 0, lastSeenAt: "2026-04-26T12:00:00+08:00", preferredExecutionMode: "cli", }); state.projects.push({ id: "worker-project", name: "Worker Project", pinned: false, systemPinned: false, deviceIds: ["worker-mac"], preview: "Owned by worker.", updatedAt: "2026-04-26T12:00:00+08:00", lastMessageAt: "2026-04-26T12:00:00+08:00", isGroup: false, threadMeta: { projectId: "worker-project", threadId: "thread-worker-project", threadDisplayName: "Worker Project", folderName: "Worker", activityIconCount: 0, updatedAt: "2026-04-26T12:00:00+08:00", codexThreadRef: "thread-worker-project", codexFolderRef: "worker", }, groupMembers: [], createdByAgent: true, collaborationMode: "development", approvalState: "not_required", unreadCount: 0, riskLevel: "low", messages: [], goals: [], versions: [], }); await data.writeState(state); const next = await data.readState(); const session = { account: "worker@example.com", role: "member" as const, displayName: "Worker", }; assert.equal(permissions.canAccessDevice(next, session, "worker-mac", "device.view"), true); assert.equal(permissions.canAccessProject(next, session, "worker-project", "project.view"), true); });