Files
lingxi/apps/web/src/pages/system/roles/role-display.spec.ts
T
Stev_Wang 61894df401 feat(identity): 完成 A2 角色权限与数据范围全栈落地
背景:A2 开卡前完成 H1 权限门,确认权限点目录、特殊权限、SoD 三对、六类数据范围与超管隐式全集口径(ADR-001)。

改动:
- shared:30 权限点 / 10 模块目录与元数据、特殊权限 6 编码、SoD 三对、角色与权限树契约;/me 改 dataScopes 多角色并集。
- prisma:expand 迁移新增 permissions / data_scopes;种子按原型口径重写角色权限、数据范围与 8 项特殊授予。
- api:角色 CRUD / 复制 / 停用 / 删除、权限点启动同步、SoD 40304、系统角色保护、六类范围 ScopeContext 与 CustomerDirectory 端口、变更审计与 outbox。
- web:方案 B 主从分栏三角色页面(列表 / 编辑器 / 新建),权限树、范围选择、特殊权限二次确认、保存影响、SoD 冲突、只读态与模板预填。
- 测试:角色集成 13 条 + A2 e2e 1 条;A1 全量回归。

验证:format:check / lint / typecheck / test(306)/ test:int(27)/ A2 e2e(1,复跑通过)/ openapi:export 全绿。
2026-11-03 12:26:51 +08:00

155 lines
5.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { PermissionTree, RoleListItem } from '@lingxi/shared';
import { permissionTreeSchema, roleListItemSchema } from '@lingxi/shared';
import { describe, expect, it } from 'vitest';
import {
buildPermissionMetaMap,
DEFAULT_ROLE_LIST_FILTERS,
filterRoles,
formatRoleDateTime,
hasActiveRoleFilters,
permissionNameOf,
scopeLabelOf,
summarizePermissionModules,
summarizeRoles,
} from './role-display';
let index = 0;
function buildRole(overrides: Record<string, unknown> = {}): RoleListItem {
index += 1;
return roleListItemSchema.parse({
id: '01HZZZZZZZZZZZZZZZZZZZZZZZ',
code: `role_${String(index)}`,
name: `角色 ${String(index)}`,
description: '角色说明',
isSystem: false,
status: 'active',
permissionCount: 4,
specialPermissionCount: 0,
userCount: 3,
dataScope: { type: 'department' },
createdAt: '2026-10-27T02:40:00.000Z',
updatedAt: '2026-10-27T02:40:00.000Z',
...overrides,
});
}
const tree: PermissionTree = permissionTreeSchema.parse({
modules: [
{
module: 'script',
moduleLabel: '脚本',
sortOrder: 10,
permissions: [
{ code: 'script:read', name: '查看脚本', description: '脚本库', isSpecial: false },
{ code: 'script:write', name: '编辑与生成脚本', description: '编辑', isSpecial: false },
{ code: 'script:review', name: '审核脚本', description: '审核', isSpecial: false },
],
},
{
module: 'delivery',
moduleLabel: '导出交付',
sortOrder: 40,
permissions: [
{ code: 'delivery:read', name: '查看交付记录', description: '交付清单', isSpecial: false },
{ code: 'delivery:export', name: '发起导出', description: '数据出口', isSpecial: true },
],
},
],
specialPermissions: [
{ code: 'delivery:export', name: '发起导出', description: '数据出口', isSpecial: true },
],
sodPairs: [
{
id: 'script-write-review',
label: '脚本生成 × 脚本审核',
description: '不得同时拥有',
permissions: ['script:write', 'script:review'],
},
],
totalPermissions: 5,
});
describe('权限点摘要与元数据', () => {
it('按 10 模块口径统计已选 / 总数(含特殊权限)', () => {
const summary = summarizePermissionModules(tree, [
'script:read',
'script:review',
'delivery:export',
]);
expect(summary).toEqual([
{ module: 'script', moduleLabel: '脚本', selected: 2, total: 3 },
{ module: 'delivery', moduleLabel: '导出交付', selected: 1, total: 2 },
]);
});
it('权限点编码映射到名称,缺失时回退编码', () => {
const meta = buildPermissionMetaMap(tree);
expect(permissionNameOf(meta, 'delivery:export')).toBe('发起导出');
expect(meta.get('delivery:export')?.isSpecial).toBe(true);
expect(permissionNameOf(meta, 'ghost:code')).toBe('ghost:code');
});
});
describe('数据范围与时间展示', () => {
it('六类数据范围名称与未知类型兜底', () => {
expect(scopeLabelOf('customer_groups')).toBe('指定客户组');
expect(scopeLabelOf('department_and_children')).toBe('本部门及下级');
expect(scopeLabelOf('created_by_me')).toBe('本人创建');
});
it('ISO 时间转 YYYY-MM-DD HH:mm,空值与非法值显示占位符', () => {
expect(formatRoleDateTime('2026-10-27T02:40:00.000Z')).toMatch(/^2026-10-27 \d{2}:40$/);
expect(formatRoleDateTime(null)).toBe('—');
expect(formatRoleDateTime('not-a-date')).toBe('—');
});
});
describe('角色统计与筛选', () => {
it('汇总角色 / 启用 / 停用 / 绑定用户 / 特殊授予 / 内置数', () => {
const stats = summarizeRoles([
buildRole({ isSystem: true, userCount: 2, specialPermissionCount: 6 }),
buildRole({ status: 'disabled', userCount: 0 }),
buildRole({ userCount: 5, specialPermissionCount: 1 }),
]);
expect(stats).toEqual({
total: 3,
active: 2,
disabled: 1,
boundUsers: 7,
specialGrants: 7,
systemCount: 1,
});
});
it('按关键词 / 状态 / 只看特殊权限筛选', () => {
const roles = [
buildRole({ name: '审核员', code: 'reviewer', description: '脚本与素材审核' }),
buildRole({ name: '设计师', code: 'designer', status: 'disabled' }),
buildRole({ name: '行业配置', specialPermissionCount: 1 }),
];
expect(
filterRoles(roles, { ...DEFAULT_ROLE_LIST_FILTERS, keyword: '审核' }).map(
(role) => role.code,
),
).toEqual(['reviewer']);
expect(
filterRoles(roles, { ...DEFAULT_ROLE_LIST_FILTERS, status: 'disabled' }).map(
(role) => role.name,
),
).toEqual(['设计师']);
expect(
filterRoles(roles, { ...DEFAULT_ROLE_LIST_FILTERS, specialOnly: true }).map(
(role) => role.name,
),
).toEqual(['行业配置']);
});
it('识别是否存在生效筛选', () => {
expect(hasActiveRoleFilters(DEFAULT_ROLE_LIST_FILTERS)).toBe(false);
expect(hasActiveRoleFilters({ ...DEFAULT_ROLE_LIST_FILTERS, keyword: ' ' })).toBe(false);
expect(hasActiveRoleFilters({ ...DEFAULT_ROLE_LIST_FILTERS, specialOnly: true })).toBe(true);
});
});