import { describe, expect, it } from 'vitest'; import { ulid } from '../ulid.js'; import { createRoleInputSchema, permissionCodeSchema, permissionTreeSchema, roleDataScopeInputSchema, roleDetailSchema, roleListItemSchema, roleListQuerySchema, roleSaveResultSchema, updateRoleInputSchema, } from './roles.js'; const ISO = '2026-11-03T08:00:00.000Z'; function makeListItem(): Record { return { id: ulid(), code: 'material_operator', name: '素材运营', description: '素材生产、快筛、导出与归档', isSystem: false, status: 'active', permissionCount: 9, specialPermissionCount: 0, userCount: 2, dataScope: { type: 'owned_customers' }, createdAt: ISO, updatedAt: ISO, }; } describe('roleListItemSchema / roleDetailSchema', () => { it('列表项包含目录统计与数据范围', () => { const parsed = roleListItemSchema.parse(makeListItem()); expect(parsed.permissionCount).toBe(9); expect(parsed.userCount).toBe(2); expect(parsed.dataScope.type).toBe('owned_customers'); }); it('详情在列表项基础上含普通权限、特殊权限与绑定用户', () => { const detail = roleDetailSchema.parse({ ...makeListItem(), permissions: ['script:read', 'script:write'], specialPermissions: ['delivery:export'], boundUsers: [ { id: ulid(), name: '唐嘉', account: 'tang.jia', phoneMasked: '135****6641', departmentName: '素材一组', status: 'disabled', }, ], }); expect(detail.permissions).toHaveLength(2); expect(detail.specialPermissions).toEqual(['delivery:export']); expect(detail.boundUsers[0]?.phoneMasked).toBe('135****6641'); }); it('拒绝负统计值 / 未登记权限点 / 非法状态', () => { expect(() => roleListItemSchema.parse({ ...makeListItem(), userCount: -1 })).toThrow(); expect(() => roleDetailSchema.parse({ ...makeListItem(), permissions: ['script:delete_all'], specialPermissions: [], boundUsers: [], }), ).toThrow('未登记的权限点编码'); expect(() => roleListItemSchema.parse({ ...makeListItem(), status: 'enabled' })).toThrow(); }); }); describe('角色输入 schema', () => { it('新建角色:权限与特殊权限默认空,但数据范围必填(六类单选)', () => { expect( createRoleInputSchema.parse({ name: '运营', dataScope: { type: 'created_by_me' } }), ).toEqual({ name: '运营', dataScope: { type: 'created_by_me' } }); expect(() => createRoleInputSchema.parse({ name: '运营' })).toThrow(); }); it('权限点必须已登记,且特殊权限单独字段提交', () => { expect(() => createRoleInputSchema.parse({ name: '运营', dataScope: { type: 'all' }, permissions: ['script:delete_all'], }), ).toThrow('未登记的权限点编码'); expect(() => createRoleInputSchema.parse({ name: '运营', dataScope: { type: 'all' }, permissions: ['delivery:export'], }), ).toThrow('必须放入 specialPermissions'); expect(() => createRoleInputSchema.parse({ name: '运营', dataScope: { type: 'all' }, specialPermissions: ['script:read'], }), ).toThrow('不能放入 specialPermissions'); const parsed = createRoleInputSchema.parse({ name: '运营', dataScope: { type: 'customer_groups', targetIds: [] }, permissions: ['script:read'], specialPermissions: ['delivery:export'], }); expect(parsed.permissions).toEqual(['script:read']); expect(parsed.specialPermissions).toEqual(['delivery:export']); }); it('未知字段被拒绝(strict)', () => { expect(() => createRoleInputSchema.parse({ name: '运营', dataScope: { type: 'all' }, unknown: 1 }), ).toThrow(); expect(() => updateRoleInputSchema.parse({ copyFromRoleId: ulid() })).toThrow(); }); it('更新支持 status enabled / disabled 与停用原因', () => { expect(updateRoleInputSchema.parse({ status: 'disabled', reason: '岗位合并' })).toEqual({ status: 'disabled', reason: '岗位合并', }); expect(() => updateRoleInputSchema.parse({ status: 'active' })).toThrow(); }); it('权限点编码 schema 与目录同源', () => { expect(permissionCodeSchema.parse('role:view')).toBe('role:view'); expect(() => permissionCodeSchema.parse('role:delete')).toThrow(); }); }); describe('roleDataScopeInputSchema', () => { it('六类范围可用;指定客户组可带 targetIds', () => { expect(roleDataScopeInputSchema.parse({ type: 'all' })).toEqual({ type: 'all' }); const groups = roleDataScopeInputSchema.parse({ type: 'customer_groups', targetIds: [ulid()], }); expect(groups.targetIds).toHaveLength(1); expect(roleDataScopeInputSchema.parse({ type: 'customer_groups', targetIds: [] })).toEqual({ type: 'customer_groups', targetIds: [], }); }); it('非「指定客户组」范围不允许 targetIds', () => { expect(() => roleDataScopeInputSchema.parse({ type: 'all', targetIds: [ulid()] })).toThrow( '只有「指定客户组」范围允许填写 targetIds', ); }); }); describe('roleListQuerySchema', () => { it('默认 status=active(兼容 A1 角色多选)、sort=created_at、order=desc、limit=20', () => { expect(roleListQuerySchema.parse({})).toEqual({ status: 'active', sort: 'created_at', order: 'desc', limit: 20, }); }); it('白名单校验排序与状态', () => { expect(roleListQuerySchema.parse({ sort: 'name', order: 'asc', status: 'disabled' }).sort).toBe( 'name', ); expect(() => roleListQuerySchema.parse({ sort: 'permission_count' })).toThrow(); expect(() => roleListQuerySchema.parse({ status: 'enabled' })).toThrow(); }); }); describe('permissionTreeSchema / roleSaveResultSchema', () => { it('权限树含模块、特殊权限与 SoD 对', () => { const parsed = permissionTreeSchema.parse({ modules: [ { module: 'script', moduleLabel: '脚本', sortOrder: 50, permissions: [ { code: 'script:read', name: '脚本查看', description: '', isSpecial: false }, ], }, ], specialPermissions: [ { code: 'delivery:export', name: '交付导出', description: '', isSpecial: true }, ], sodPairs: [ { id: 'script-write-review', label: '脚本生成 × 脚本审核', description: '', permissions: ['script:write', 'script:review'], }, ], totalPermissions: 30, }); expect(parsed.modules).toHaveLength(1); expect(parsed.totalPermissions).toBe(30); }); it('保存结果含角色详情、影响用户数与软提示', () => { const result = roleSaveResultSchema.parse({ role: { ...makeListItem(), permissions: [], specialPermissions: [], boundUsers: [], }, affectedUserCount: 3, warnings: ['角色数量已达建议上限 20'], }); expect(result.affectedUserCount).toBe(3); expect(result.warnings).toHaveLength(1); }); });