Stop persisting roles to localStorage as authority; AdminSettingsModal reads and writes role matrices via /api/v1/admin/roles; hasPermission denies when capabilities are not loaded from /auth/me. Co-authored-by: Cursor <cursoragent@cursor.com>
260 lines
10 KiB
TypeScript
260 lines
10 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {
|
|
DEFAULT_ROLES,
|
|
hasPermission,
|
|
isDeliverableVisible,
|
|
filterVisibleDeliverablesWithRBAC,
|
|
canPerformDocAction,
|
|
capabilitiesFromRole,
|
|
} from './rbac';
|
|
import { UserProfile } from '../types/docgov';
|
|
import { DeliverableItem } from '../types/workbench';
|
|
|
|
const sampleUsers: Record<string, UserProfile> = {
|
|
admin: {
|
|
id: 100,
|
|
name: '系统管理员',
|
|
employeeId: 'ADMIN-001',
|
|
department: '系统运维部',
|
|
position: '系统管理员',
|
|
roleId: 'role-admin',
|
|
},
|
|
director: {
|
|
id: 4,
|
|
name: '王总监',
|
|
employeeId: 'SMDF-0018',
|
|
department: '医疗器械研发事业部',
|
|
position: '微创器械研发总监',
|
|
roleId: 'role-director',
|
|
},
|
|
lead: {
|
|
id: 3,
|
|
name: '李主管',
|
|
employeeId: 'SMDF-0205',
|
|
department: '机器人与微创内窥镜研发中心',
|
|
position: '研发项目技术主管',
|
|
roleId: 'role-tech-lead',
|
|
},
|
|
engineer: {
|
|
id: 2,
|
|
name: '张工',
|
|
employeeId: 'SMDF-0821',
|
|
department: '机器人与微创内窥镜研发中心',
|
|
position: '资深医疗器械方案工程师',
|
|
roleId: 'role-engineer',
|
|
},
|
|
guest: {
|
|
id: 99,
|
|
name: '外部审计专员',
|
|
employeeId: 'GUEST-001',
|
|
department: '第三方认证机构',
|
|
position: '外部合规审查员',
|
|
roleId: 'role-guest',
|
|
},
|
|
};
|
|
|
|
const createMockDoc = (overrides: Partial<DeliverableItem> = {}): DeliverableItem => ({
|
|
docId: 'doc-mock-001',
|
|
projectId: 1,
|
|
taskKey: 'feasibility',
|
|
title: '测试受控交付物',
|
|
stage: 'V0.1',
|
|
category: 'workflow',
|
|
creatorId: 2,
|
|
creatorName: '张工',
|
|
isFixedWorkflow: true,
|
|
isTemplateOnly: false,
|
|
lifecycleState: 'drafting',
|
|
isClaimed: true,
|
|
author: '张工',
|
|
reviewer: '李主管',
|
|
approver: '王总监',
|
|
updatedAt: '2026-09-08 12:00',
|
|
...overrides,
|
|
});
|
|
|
|
test('用例 1: DEFAULT_ROLES 包含 5 种默认角色且具备预期权限', () => {
|
|
assert.equal(DEFAULT_ROLES.length, 5);
|
|
const admin = DEFAULT_ROLES.find((r) => r.code === 'ADMIN');
|
|
const engineer = DEFAULT_ROLES.find((r) => r.code === 'ENGINEER');
|
|
const guest = DEFAULT_ROLES.find((r) => r.code === 'GUEST');
|
|
|
|
assert.ok(admin);
|
|
assert.ok(admin.isSystem);
|
|
assert.ok(admin.permissions.includes('perm:manage'));
|
|
|
|
assert.ok(engineer);
|
|
assert.ok(engineer.permissions.includes('workflow:view_all'));
|
|
assert.equal(engineer.permissions.includes('project:create'), false);
|
|
|
|
assert.ok(guest);
|
|
assert.equal(guest.permissions.includes('workflow:view_all'), false);
|
|
});
|
|
|
|
test('用例 2: 系统管理员 (ADMIN) 与具备 perm:manage 用户的通配豁免特权', () => {
|
|
const adminCaps = capabilitiesFromRole(DEFAULT_ROLES, 'role-admin');
|
|
const engineerCaps = capabilitiesFromRole(DEFAULT_ROLES, 'role-engineer');
|
|
assert.equal(hasPermission(sampleUsers.admin, DEFAULT_ROLES, 'project:create', adminCaps), true);
|
|
assert.equal(hasPermission(sampleUsers.admin, DEFAULT_ROLES, 'perm:manage', adminCaps), true);
|
|
assert.equal(hasPermission(sampleUsers.admin, DEFAULT_ROLES, 'doc:override_visibility', adminCaps), true);
|
|
|
|
// 普通工程师无管理权限
|
|
assert.equal(hasPermission(sampleUsers.engineer, DEFAULT_ROLES, 'perm:manage', engineerCaps), false);
|
|
assert.equal(hasPermission(sampleUsers.engineer, DEFAULT_ROLES, 'project:create', engineerCaps), false);
|
|
// fail closed:未加载 capabilities 时拒绝
|
|
assert.equal(hasPermission(sampleUsers.admin, DEFAULT_ROLES, 'project:create'), false);
|
|
});
|
|
|
|
test('用例 3: 常规工作流基线可见性 (workflow:view_all) 判定', () => {
|
|
const normalDoc = createMockDoc();
|
|
|
|
// 工程师有 workflow:view_all -> 可见
|
|
assert.equal(isDeliverableVisible(sampleUsers.engineer, DEFAULT_ROLES, normalDoc), true);
|
|
|
|
// 外部审计员无 workflow:view_all 且不是责任人 -> 不可见
|
|
assert.equal(isDeliverableVisible(sampleUsers.guest, DEFAULT_ROLES, normalDoc), false);
|
|
});
|
|
|
|
test('用例 4: 单文档覆盖 - designated_only (仅编制/审核/批准人可见,其他用户被精确拦截)', () => {
|
|
const restrictedDoc = createMockDoc({
|
|
author: '张工',
|
|
reviewer: '李主管',
|
|
approver: '赵院长', // 王总监不是责任人
|
|
visibilityRule: {
|
|
mode: 'designated_only',
|
|
overrideReason: '敏感临床事故报告,收紧仅责任人可见',
|
|
configuredBy: '李主管',
|
|
configuredAt: '2026-09-08 14:00',
|
|
},
|
|
});
|
|
|
|
// 张工是编制人 -> 可见
|
|
assert.equal(isDeliverableVisible(sampleUsers.engineer, DEFAULT_ROLES, restrictedDoc), true);
|
|
|
|
// 李主管是审核人 -> 可见
|
|
assert.equal(isDeliverableVisible(sampleUsers.lead, DEFAULT_ROLES, restrictedDoc), true);
|
|
|
|
// 王总监虽为研发总监,但非三权责任人 -> 被单文档覆盖拦截 (不可见)
|
|
assert.equal(isDeliverableVisible(sampleUsers.director, DEFAULT_ROLES, restrictedDoc), false);
|
|
|
|
// 系统管理员超级权限仍可见
|
|
assert.equal(isDeliverableVisible(sampleUsers.admin, DEFAULT_ROLES, restrictedDoc), true);
|
|
});
|
|
|
|
test('用例 5: 单文档覆盖 - role_whitelist (仅白名单内角色可见,其他角色被精确拦截)', () => {
|
|
const secretPatentDoc = createMockDoc({
|
|
author: '外聘专家',
|
|
reviewer: '外聘审查',
|
|
approver: '外聘顾问',
|
|
visibilityRule: {
|
|
mode: 'role_whitelist',
|
|
allowedRoleIds: ['role-director'], // 仅总监可见
|
|
overrideReason: '核心专利保密算法拆解',
|
|
},
|
|
});
|
|
|
|
// 王总监拥有 role-director -> 可见
|
|
assert.equal(isDeliverableVisible(sampleUsers.director, DEFAULT_ROLES, secretPatentDoc), true);
|
|
|
|
// 张工 (role-engineer) 不在白名单中 -> 不可见
|
|
assert.equal(isDeliverableVisible(sampleUsers.engineer, DEFAULT_ROLES, secretPatentDoc), false);
|
|
|
|
// 李主管 (role-tech-lead) 不在白名单中 -> 不可见
|
|
assert.equal(isDeliverableVisible(sampleUsers.lead, DEFAULT_ROLES, secretPatentDoc), false);
|
|
});
|
|
|
|
test('用例 6: 单文档覆盖 - public (放宽可见性,无 workflow:view_all 权限的访客也能查阅)', () => {
|
|
const publicSpecDoc = createMockDoc({
|
|
author: '张工',
|
|
reviewer: '李主管',
|
|
approver: '王总监',
|
|
visibilityRule: {
|
|
mode: 'public',
|
|
overrideReason: '产品公开白皮书,供外部审计或用户公开查阅',
|
|
},
|
|
});
|
|
|
|
// 访客原本无 workflow:view_all,但单文档设为 public -> 可见
|
|
assert.equal(isDeliverableVisible(sampleUsers.guest, DEFAULT_ROLES, publicSpecDoc), true);
|
|
});
|
|
|
|
test('用例 7: 私密文档隔离强红线 (category === private 无论任何管理员或单文档覆盖,非创建者绝对不可见)', () => {
|
|
const privateDraft = createMockDoc({
|
|
category: 'private',
|
|
creatorId: 2, // 张工创建
|
|
creatorName: '张工',
|
|
visibilityRule: {
|
|
mode: 'public', // 即使误设 public 也绝不能穿透 private 隔离
|
|
},
|
|
});
|
|
|
|
// 创建者本人可见
|
|
assert.equal(isDeliverableVisible(sampleUsers.engineer, DEFAULT_ROLES, privateDraft), true);
|
|
|
|
// 其他人不可见
|
|
assert.equal(isDeliverableVisible(sampleUsers.lead, DEFAULT_ROLES, privateDraft), false);
|
|
assert.equal(isDeliverableVisible(sampleUsers.director, DEFAULT_ROLES, privateDraft), false);
|
|
assert.equal(isDeliverableVisible(sampleUsers.admin, DEFAULT_ROLES, privateDraft), false);
|
|
assert.equal(isDeliverableVisible(sampleUsers.guest, DEFAULT_ROLES, privateDraft), false);
|
|
});
|
|
|
|
test('用例 8: 核心业务动作权限拦截判定 (canPerformDocAction & hasPermission)', () => {
|
|
const directorCaps = capabilitiesFromRole(DEFAULT_ROLES, 'role-director');
|
|
const leadCaps = capabilitiesFromRole(DEFAULT_ROLES, 'role-tech-lead');
|
|
const engineerCaps = capabilitiesFromRole(DEFAULT_ROLES, 'role-engineer');
|
|
const guestCaps = capabilitiesFromRole(DEFAULT_ROLES, 'role-guest');
|
|
|
|
// 1. 创建项目权限
|
|
assert.equal(canPerformDocAction('create_project', sampleUsers.director, DEFAULT_ROLES, directorCaps), true);
|
|
assert.equal(canPerformDocAction('create_project', sampleUsers.lead, DEFAULT_ROLES, leadCaps), true);
|
|
assert.equal(canPerformDocAction('create_project', sampleUsers.engineer, DEFAULT_ROLES, engineerCaps), false);
|
|
assert.equal(canPerformDocAction('create_project', sampleUsers.guest, DEFAULT_ROLES, guestCaps), false);
|
|
|
|
// 2. 指派文档法定三权责任人
|
|
assert.equal(canPerformDocAction('assign_lead', sampleUsers.director, DEFAULT_ROLES, directorCaps), true);
|
|
assert.equal(canPerformDocAction('assign_lead', sampleUsers.lead, DEFAULT_ROLES, leadCaps), true);
|
|
assert.equal(canPerformDocAction('assign_lead', sampleUsers.engineer, DEFAULT_ROLES, engineerCaps), false);
|
|
|
|
// 3. 配置单文档覆盖
|
|
assert.equal(canPerformDocAction('override_visibility', sampleUsers.director, DEFAULT_ROLES, directorCaps), true);
|
|
assert.equal(canPerformDocAction('override_visibility', sampleUsers.lead, DEFAULT_ROLES, leadCaps), true);
|
|
assert.equal(canPerformDocAction('override_visibility', sampleUsers.engineer, DEFAULT_ROLES, engineerCaps), false);
|
|
|
|
// 4. 服务端下发升级后的 capabilities 立即生效
|
|
const upgradedEngineerCaps = [...engineerCaps, 'project:create' as const];
|
|
assert.equal(canPerformDocAction('create_project', sampleUsers.engineer, DEFAULT_ROLES, upgradedEngineerCaps), true);
|
|
});
|
|
|
|
test('用例 9: filterVisibleDeliverablesWithRBAC 能够批处理过滤大盘交付物', () => {
|
|
const docs: DeliverableItem[] = [
|
|
createMockDoc({ docId: 'doc-1', title: '常规工作流' }),
|
|
createMockDoc({
|
|
docId: 'doc-2',
|
|
title: '绝密算法',
|
|
author: '外聘专家',
|
|
visibilityRule: { mode: 'role_whitelist', allowedRoleIds: ['role-director'] },
|
|
}),
|
|
createMockDoc({
|
|
docId: 'doc-3',
|
|
title: '张工个人草案',
|
|
category: 'private',
|
|
creatorId: 2,
|
|
}),
|
|
];
|
|
|
|
// 张工视角: 能看常规工作流(doc-1)和个人草案(doc-3),不能看绝密算法(doc-2)
|
|
const zhangVisible = filterVisibleDeliverablesWithRBAC(docs, sampleUsers.engineer, DEFAULT_ROLES);
|
|
assert.deepEqual(
|
|
zhangVisible.map((d) => d.docId),
|
|
['doc-1', 'doc-3']
|
|
);
|
|
|
|
// 王总监视角: 能看常规工作流(doc-1)和绝密算法(doc-2),不能看张工私密草案(doc-3)
|
|
const wangVisible = filterVisibleDeliverablesWithRBAC(docs, sampleUsers.director, DEFAULT_ROLES);
|
|
assert.deepEqual(
|
|
wangVisible.map((d) => d.docId),
|
|
['doc-1', 'doc-2']
|
|
);
|
|
});
|