import { describe, expect, it } from 'vitest'; import { findSodConflicts, isPermissionCode, isSpecialPermission, parsePermission, Permission, PERMISSION_CATALOG, PERMISSION_CODES, PERMISSION_MODULES, permissionMetaOf, permissionsByModule, ROLE_SOFT_LIMIT, SOD_PAIRS, SPECIAL_PERMISSIONS, splitPermissions, SUPER_ADMIN_SOFT_LIMIT, } from './permissions.js'; describe('权限点目录', () => { it('H1 口径:共 30 个权限点,编码全局唯一', () => { expect(PERMISSION_CODES).toHaveLength(30); expect(PERMISSION_CATALOG).toHaveLength(30); expect(new Set(PERMISSION_CODES).size).toBe(30); }); it('全部符合 模块:动作 命名且模块为小写', () => { for (const code of PERMISSION_CODES) { expect(code).toMatch(/^[a-z][a-z_]*:[a-z][a-z_]*$/); expect(parsePermission(code)).not.toBeNull(); } }); it('保留既有 read / write 编码,并补齐 H1 新增的 5 个权限点', () => { for (const code of [ 'script:read', 'script:write', 'material:read', 'material:write', 'asset:read', 'asset:write', 'delivery:read', 'customer:read', ]) { expect(PERMISSION_CODES).toContain(code); } expect(Permission.ROLE_VIEW).toBe('role:view'); expect(Permission.CUSTOMER_FIELD_APPLY).toBe('customer_field:apply'); expect(Permission.CUSTOMER_TRANSFER).toBe('customer:transfer'); expect(Permission.CUSTOMER_GROUP_MANAGE).toBe('customer_group:manage'); expect(Permission.ASSET_DELETE).toBe('asset:delete'); }); it('权限树为 10 个模块,顺序与名称对齐 design/a2-ui-v1/brief.md', () => { const groups = permissionsByModule(); expect(groups).toHaveLength(10); expect(PERMISSION_MODULES).toHaveLength(10); expect(groups.map((group) => group.module)).toEqual([ 'script', 'material', 'asset', 'delivery', 'customer', 'customer_group', 'identity', 'industry', 'platform', 'audit', ]); expect(groups.map((group) => group.moduleLabel)).toEqual([ '脚本', '素材', '资产', '导出交付', '客户', '客户组', '组织与角色', '行业包', '平台配置', '审计', ]); expect(groups.map((group) => group.sortOrder)).toEqual([ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, ]); expect(groups.flatMap((group) => group.permissions)).toHaveLength(30); }); it('权限点显示名与说明与 UI 原型一致(抽样关键项)', () => { const nameOf = (code: string): { name: string; description: string } => { const meta = permissionMetaOf(code); if (meta === undefined) { throw new Error(`权限点未登记:${code}`); } return { name: meta.name, description: meta.description }; }; expect(nameOf('script:read')).toEqual({ name: '查看脚本', description: '脚本库、模板与版本' }); expect(nameOf('delivery:export')).toEqual({ name: '发起导出', description: '数据离开平台的唯一出口(特殊)', }); expect(nameOf('user:manage')).toEqual({ name: '管理用户', description: '新建、停用、转交' }); expect(nameOf('customer_group:manage')).toEqual({ name: '管理客户组', description: '客户组增改与成员', }); expect(nameOf('audit:export')).toEqual({ name: '导出审计', description: '审计导出并留痕(特殊)', }); }); it('目录元数据完整(编码 / 模块 / 名称 / 说明 / 特殊标记 / 排序)', () => { for (const item of PERMISSION_CATALOG) { expect(permissionMetaOf(item.code)).toEqual(item); expect(item.name.length).toBeGreaterThan(0); expect(item.description.length).toBeGreaterThan(0); expect(item.moduleLabel.length).toBeGreaterThan(0); expect(item.isSpecial).toBe(isSpecialPermission(item.code)); } const sortOrders = PERMISSION_CATALOG.map((item) => item.sortOrder); expect(sortOrders).toEqual([...sortOrders].sort((a, b) => a - b)); }); it('特殊权限为 6 个编码(5 类),均已在目录登记', () => { expect(SPECIAL_PERMISSIONS).toHaveLength(6); expect([...SPECIAL_PERMISSIONS].sort()).toEqual( [ 'delivery:export', 'customer_field:approve', 'industry:publish', 'ai:configure', 'audit:read', 'audit:export', ].sort(), ); for (const code of SPECIAL_PERMISSIONS) { expect(PERMISSION_CODES).toContain(code); expect(isSpecialPermission(code)).toBe(true); } expect(isSpecialPermission('script:write')).toBe(false); }); it('软上限取自 PRD 建议值', () => { expect(ROLE_SOFT_LIMIT).toBe(20); expect(SUPER_ADMIN_SOFT_LIMIT).toBe(2); }); }); describe('SoD 三对', () => { it('互斥对为三对,且两侧都是已登记权限点', () => { expect(SOD_PAIRS).toHaveLength(3); expect(SOD_PAIRS.map((pair) => pair.id)).toEqual([ 'script-write-review', 'industry-manage-publish', 'customer-field-apply-approve', ]); for (const pair of SOD_PAIRS) { for (const code of pair.permissions) { expect(PERMISSION_CODES).toContain(code); } } }); it('同时拥有互斥两侧即命中;只拥有单侧不命中', () => { expect(findSodConflicts(['script:write', 'script:review'])).toEqual([ { pairId: 'script-write-review', label: '脚本生成 × 脚本审核', permissions: ['script:write', 'script:review'], }, ]); expect(findSodConflicts(['industry:manage', 'industry:publish']).map((c) => c.pairId)).toEqual([ 'industry-manage-publish', ]); expect( findSodConflicts(['customer_field:apply', 'customer_field:approve']).map((c) => c.pairId), ).toEqual(['customer-field-apply-approve']); expect(findSodConflicts(['script:write', 'script:read'])).toEqual([]); expect(findSodConflicts([])).toEqual([]); }); }); describe('splitPermissions', () => { it('拆分普通权限与特殊权限并去重排序', () => { expect( splitPermissions([ 'script:write', 'delivery:export', 'script:read', 'delivery:export', 'audit:read', ]), ).toEqual({ permissions: ['script:read', 'script:write'], specialPermissions: ['audit:read', 'delivery:export'], }); }); it('未登记编码抛错(调用方映射 40001)', () => { expect(() => splitPermissions(['script:delete_all'])).toThrow( 'INVALID_PERMISSION_CODE:script:delete_all', ); }); }); describe('parsePermission', () => { it('拆分模块与动作', () => { expect(parsePermission('material:render')).toEqual({ module: 'material', action: 'render' }); }); it('非法格式返回 null', () => { expect(parsePermission('scriptread')).toBeNull(); expect(parsePermission(':read')).toBeNull(); expect(parsePermission('script:')).toBeNull(); expect(parsePermission('a:b:c')).toBeNull(); }); }); describe('isPermissionCode', () => { it('只认可登记值', () => { expect(isPermissionCode(Permission.SCRIPT_READ)).toBe(true); expect(isPermissionCode('script:delete_all')).toBe(false); }); });