Why: the rdm-audit entry was an unconditional builtin, so on a plain base without any rdm.inspect.* commands the workspace opened straight into a rejected receipt (400) - diagnosed 2026-10-12. The page also rendered raw receipt JSON instead of a readable error. Evidence: vitest 23 files / 112 tests pass incl. the new contract matrix (workspaces.test.ts, rdm-errors.test.ts) and design-guard; tsc -b && vite build pass; e2e plain-base scenario added (driven by dev-repo scripts/e2e.sh phase 4). Impact: audit entry shows only when GET /api/commands lists all seven rdm.inspect.* commands enabled (store.rdmAuditAvailable, refreshed with the workspace list); audit errors classify into capability-missing / auth-expired / query-failed with the raw receipt kept in a Collapsible. Refs: 2026-10-12 diagnostic report, increment 2.
76 lines
3.5 KiB
TypeScript
76 lines
3.5 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
||
import { buildWorkspaceEntries, rdmAuditReady, RDM_AUDIT_COMMANDS } from '../src/workspaces'
|
||
|
||
// 三栏工作台(2026-10-11):文件浏览并入第三栏「文件」入口;体系工具内置「代理」。
|
||
// 2026-10-12 增量 2(审计入口能力门控):「追溯审计」不再无条件内置——普通基座
|
||
// 没有任何 rdm.inspect.* 命令,入口展示后进入即 400。新契约:仅当命令面探测
|
||
// 通过(opts.rdmAudit)才纳入 rdm-audit;代理仍无条件以 ⌘1 补位。
|
||
describe('buildWorkspaceEntries', () => {
|
||
it('没有领域插件且无 RDM 能力:只有代理占 ⌘1', () => {
|
||
const entries = buildWorkspaceEntries([])
|
||
expect(entries.map(e => `${e.shortcut} ${e.title}`)).toEqual(['⌘1 代理'])
|
||
})
|
||
|
||
it('没有领域插件且 RDM 能力通过:代理 ⌘1、追溯审计 ⌘2', () => {
|
||
const entries = buildWorkspaceEntries([], { rdmAudit: true })
|
||
expect(entries.map(e => `${e.shortcut} ${e.title}`)).toEqual(['⌘1 代理', '⌘2 追溯审计'])
|
||
})
|
||
|
||
it('声明了 shortcut 的工作区占住该位,内置工具让位(能力开)', () => {
|
||
const entries = buildWorkspaceEntries([
|
||
{ id: 'trace', title: '追溯网', shortcut: '1' },
|
||
], { rdmAudit: true })
|
||
expect(entries.map(e => e.title)).toEqual(['追溯网', '代理', '追溯审计'])
|
||
expect(entries.map(e => e.shortcut)).toEqual(['⌘1', '⌘2', '⌘3'])
|
||
expect(entries[0].key).toBe('ws:trace')
|
||
})
|
||
|
||
it('无 RDM 能力:让位后只有代理,不出现追溯审计', () => {
|
||
const entries = buildWorkspaceEntries([
|
||
{ id: 'trace', title: '追溯网', shortcut: '1' },
|
||
])
|
||
expect(entries.map(e => e.title)).toEqual(['追溯网', '代理'])
|
||
})
|
||
|
||
it('未编号的工作区排在内置工具后面', () => {
|
||
const entries = buildWorkspaceEntries([
|
||
{ id: 'mem', title: '记忆库' },
|
||
], { rdmAudit: true })
|
||
expect(entries.map(e => e.title)).toEqual(['代理', '追溯审计', '记忆库'])
|
||
})
|
||
|
||
it('九位占满时低序内置保留,溢出项截断', () => {
|
||
const server = Array.from({ length: 8 }, (_, i) => ({ id: `w${i}`, title: `w${i}`, shortcut: String(i + 1) }))
|
||
const entries = buildWorkspaceEntries(server, { rdmAudit: true })
|
||
// ⌘1–8 被声明占住;代理取 ⌘9;追溯审计溢出截断(slice 0,9)。
|
||
expect(entries.filter(e => e.key.startsWith('ws:'))).toHaveLength(8)
|
||
expect(entries.map(e => e.title)).toContain('代理')
|
||
expect(entries.map(e => e.title)).not.toContain('追溯审计')
|
||
})
|
||
})
|
||
|
||
// 能力探测依据后端 GET /api/commands(server.go listCommands:{ id, title, …, enabled })。
|
||
// 审计工作区消费七个 rdm.inspect.* 命令;任一缺失或 enabled=false 都视为能力未就绪。
|
||
describe('rdmAuditReady', () => {
|
||
const all = RDM_AUDIT_COMMANDS.map(id => ({ id, enabled: true }))
|
||
|
||
it('七命令齐备且启用 → true', () => {
|
||
expect(rdmAuditReady(all)).toBe(true)
|
||
})
|
||
|
||
it('普通基座空清单 / 缺任一命令 → false', () => {
|
||
expect(rdmAuditReady([])).toBe(false)
|
||
expect(rdmAuditReady(all.filter(c => c.id !== 'rdm.inspect.history'))).toBe(false)
|
||
})
|
||
|
||
it('存在但 enabled=false → false', () => {
|
||
const disabled = all.map(c => (c.id === 'rdm.inspect.capabilities' ? { ...c, enabled: false } : c))
|
||
expect(rdmAuditReady(disabled)).toBe(false)
|
||
})
|
||
|
||
it('清单不可用(未登录/请求失败)→ false,不展示入口', () => {
|
||
expect(rdmAuditReady(null)).toBe(false)
|
||
expect(rdmAuditReady(undefined)).toBe(false)
|
||
})
|
||
})
|