222 lines
6.3 KiB
TypeScript
222 lines
6.3 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import type { BossState } from "../src/lib/boss-data";
|
|
import { canAccessDevice, canAccessProject } from "../src/lib/boss-permissions";
|
|
|
|
function baseState(): BossState {
|
|
const now = "2026-04-27T17:00:00+08:00";
|
|
return {
|
|
schemaVersion: 1,
|
|
migratedAt: now,
|
|
user: {} as BossState["user"],
|
|
devices: [
|
|
{
|
|
id: "device-a",
|
|
name: "A 公司 Mac",
|
|
avatar: "A",
|
|
account: "owner-a@example.com",
|
|
companyId: "tenant-a",
|
|
source: "production",
|
|
status: "online",
|
|
projects: ["project-a"],
|
|
quota5h: 0,
|
|
quota7d: 0,
|
|
lastSeenAt: now,
|
|
},
|
|
{
|
|
id: "device-b",
|
|
name: "B 公司 Mac",
|
|
avatar: "B",
|
|
account: "owner-b@example.com",
|
|
companyId: "tenant-b",
|
|
source: "production",
|
|
status: "online",
|
|
projects: ["project-b"],
|
|
quota5h: 0,
|
|
quota7d: 0,
|
|
lastSeenAt: now,
|
|
},
|
|
{
|
|
id: "legacy-device",
|
|
name: "历史设备",
|
|
avatar: "L",
|
|
account: "legacy@example.com",
|
|
source: "production",
|
|
status: "online",
|
|
projects: ["legacy-project"],
|
|
quota5h: 0,
|
|
quota7d: 0,
|
|
lastSeenAt: now,
|
|
},
|
|
],
|
|
projects: [
|
|
{
|
|
id: "project-b",
|
|
name: "B 公司项目",
|
|
pinned: false,
|
|
deviceIds: ["device-b"],
|
|
preview: "",
|
|
updatedAt: now,
|
|
lastMessageAt: now,
|
|
isGroup: false,
|
|
threadMeta: {
|
|
projectId: "project-b",
|
|
threadId: "thread-b",
|
|
threadDisplayName: "B 线程",
|
|
folderName: "b",
|
|
activityIconCount: 1,
|
|
updatedAt: now,
|
|
},
|
|
groupMembers: [],
|
|
createdByAgent: false,
|
|
collaborationMode: "development",
|
|
approvalState: "not_required",
|
|
unreadCount: 0,
|
|
riskLevel: "low",
|
|
messages: [],
|
|
goals: [],
|
|
versions: [],
|
|
},
|
|
{
|
|
id: "legacy-project",
|
|
name: "历史项目",
|
|
pinned: false,
|
|
deviceIds: ["legacy-device"],
|
|
preview: "",
|
|
updatedAt: now,
|
|
lastMessageAt: now,
|
|
isGroup: false,
|
|
threadMeta: {
|
|
projectId: "legacy-project",
|
|
threadId: "thread-legacy",
|
|
threadDisplayName: "历史线程",
|
|
folderName: "legacy",
|
|
activityIconCount: 1,
|
|
updatedAt: now,
|
|
},
|
|
groupMembers: [],
|
|
createdByAgent: false,
|
|
collaborationMode: "development",
|
|
approvalState: "not_required",
|
|
unreadCount: 0,
|
|
riskLevel: "low",
|
|
messages: [],
|
|
goals: [],
|
|
versions: [],
|
|
},
|
|
],
|
|
conversationHistoryClearedAt: undefined,
|
|
verificationCodes: [],
|
|
verificationDispatches: [],
|
|
adminCompanies: [],
|
|
authAccounts: [
|
|
{
|
|
id: "account-a",
|
|
account: "worker-a@example.com",
|
|
passwordHash: "hash",
|
|
displayName: "A 员工",
|
|
role: "member",
|
|
companyId: "tenant-a",
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
},
|
|
{
|
|
id: "account-admin",
|
|
account: "root@example.com",
|
|
passwordHash: "hash",
|
|
displayName: "平台管理员",
|
|
role: "highest_admin",
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
},
|
|
],
|
|
authSessions: [],
|
|
accountDeviceGrants: [
|
|
{
|
|
grantId: "cross-device-grant",
|
|
account: "worker-a@example.com",
|
|
deviceId: "device-b",
|
|
permissions: ["device.view", "computer.control"],
|
|
grantedBy: "root@example.com",
|
|
grantedAt: now,
|
|
},
|
|
{
|
|
grantId: "legacy-device-grant",
|
|
account: "worker-a@example.com",
|
|
deviceId: "legacy-device",
|
|
permissions: ["device.view"],
|
|
grantedBy: "root@example.com",
|
|
grantedAt: now,
|
|
},
|
|
],
|
|
accountProjectGrants: [
|
|
{
|
|
grantId: "cross-project-grant",
|
|
account: "worker-a@example.com",
|
|
projectId: "project-b",
|
|
permissions: ["project.view", "thread.chat"],
|
|
grantedBy: "root@example.com",
|
|
grantedAt: now,
|
|
},
|
|
],
|
|
accountSkillGrants: [],
|
|
skillCatalog: [],
|
|
skillLifecycleRequests: [],
|
|
permissionAuditLogs: [],
|
|
aiAccounts: [],
|
|
aiAccountSwitchHistory: [],
|
|
masterAgentTasks: [],
|
|
dispatchPlans: [],
|
|
dispatchExecutions: [],
|
|
deviceImportDrafts: [],
|
|
deviceImportResolutions: [],
|
|
threadStatusDocuments: [],
|
|
threadProgressEvents: [],
|
|
otaUpdates: [],
|
|
otaUpdateLogs: [],
|
|
deviceSkills: [],
|
|
appLogs: [],
|
|
userAttachmentStorageConfigs: [],
|
|
masterAgentPromptPolicy: null,
|
|
userMasterPrompts: [],
|
|
masterAgentMemories: [],
|
|
userProjectAgentControls: [],
|
|
threadContextSnapshots: [],
|
|
threadHandoffPackages: [],
|
|
threadContextAlerts: [],
|
|
deviceEnrollments: [],
|
|
opsFaults: [],
|
|
opsRepairTickets: [],
|
|
opsRepairVerifications: [],
|
|
auditRequests: [],
|
|
auditResults: [],
|
|
capabilities: [],
|
|
projectExecutionPolicies: [],
|
|
};
|
|
}
|
|
|
|
test("tenant guard blocks accidental cross-company grants for ordinary accounts", () => {
|
|
const state = baseState();
|
|
const session = { account: "worker-a@example.com", role: "member" as const, displayName: "A 员工" };
|
|
|
|
assert.equal(canAccessDevice(state, session, "device-b", "device.view"), false);
|
|
assert.equal(canAccessProject(state, session, "project-b", "project.view"), false);
|
|
assert.equal(canAccessProject(state, session, "project-b", "thread.chat"), false);
|
|
});
|
|
|
|
test("highest admin remains globally visible across tenants", () => {
|
|
const state = baseState();
|
|
const session = { account: "root@example.com", role: "highest_admin" as const, displayName: "平台管理员" };
|
|
|
|
assert.equal(canAccessDevice(state, session, "device-b", "device.view"), true);
|
|
assert.equal(canAccessProject(state, session, "project-b", "project.view"), true);
|
|
});
|
|
|
|
test("legacy unassigned devices keep explicit grant compatibility", () => {
|
|
const state = baseState();
|
|
const session = { account: "worker-a@example.com", role: "member" as const, displayName: "A 员工" };
|
|
|
|
assert.equal(canAccessDevice(state, session, "legacy-device", "device.view"), true);
|
|
assert.equal(canAccessProject(state, session, "legacy-project", "project.view"), true);
|
|
});
|