Why: when rdm.role.list is rejected (e.g. the operator lost the RDM domain-admin ledger to an acceptance bootstrap), the roles table spun on '加载中…' forever and the banner dumped the raw 400 receipt JSON. Evidence: vitest test/rdmroles-reject.test.ts (SELF, run below); manual repro on 8788 dev 2026-10-13 before the ledger fix. Impact: placeholder row comes from rolesListPlaceholder (failure row when a fetch rejected), domain-admin denials map via describeRoleListError to a readable message naming the rdm.perm.transfer channel; other errors pass through. No style tokens touched (design-guard clean). Refs: Dev task feature/rdm-admin-lockout-recovery (R3)
40 lines
1.9 KiB
TypeScript
40 lines
1.9 KiB
TypeScript
// RDM 角色权限面板拒绝态(2026-10-13 8788 dev 锁死复现):rdm.role.list 被拒后
|
||
// 表格停留「加载中…」且错误为原始 400 JSON——测纯函数两件事:占位行不再假装
|
||
// 加载中;域管理员权限拒绝映射为人话 + 恢复通道。UI 不重算权限(KTD5),文案
|
||
// 只描述拒绝事实,不判权限。
|
||
import { describe, expect, it } from 'vitest'
|
||
import { rolesListPlaceholder, describeRoleListError } from '../src/rdmManagement'
|
||
|
||
describe('rolesListPlaceholder', () => {
|
||
it('keeps 加载中 only while loading without error', () => {
|
||
expect(rolesListPlaceholder(null, '')).toBe('加载中…')
|
||
})
|
||
|
||
it('replaces the loading row with a failure row once the fetch rejected', () => {
|
||
expect(rolesListPlaceholder(null, 'Error: 400: {"receipt":…}')).toBe('角色清单未加载(见下方提示)')
|
||
})
|
||
|
||
it('returns the empty label for an empty list and nothing otherwise', () => {
|
||
expect(rolesListPlaceholder([], '')).toBe('暂无专业角色')
|
||
expect(rolesListPlaceholder([{ roleId: 'r1' }], '')).toBe('')
|
||
// 已有数据时即便残留 err 也不再渲染占位行(表格有内容即事实来源)
|
||
expect(rolesListPlaceholder([{ roleId: 'r1' }], 'stale')).toBe('')
|
||
})
|
||
})
|
||
|
||
describe('describeRoleListError', () => {
|
||
const denied = 'Error: 400: {"receipt":{"commandId":"rdm.role.list","state":"rejected",'
|
||
+ '"reason":"rejected: 权限拒绝:weichen 不是系统管理员(全局动作 project.settings)"}}'
|
||
|
||
it('maps the domain-admin denial to a readable message with the transfer channel', () => {
|
||
const got = describeRoleListError(denied)
|
||
expect(got).toContain('不是 RDM 域管理员')
|
||
expect(got).toContain('rdm.perm.transfer')
|
||
expect(got).not.toContain('"receipt"')
|
||
})
|
||
|
||
it('passes other errors through untouched', () => {
|
||
expect(describeRoleListError('Error: 500: boom')).toBe('Error: 500: boom')
|
||
})
|
||
})
|