/** * 三面骨架的唯一事实源 —— 区归属、亮灯、旧链接改写。 * * 这里守的是「配置自洽」:每个区恰属一面(不重不漏);亮灯只看该面功能;旧深链一条不死。 * 三者都是纯函数,壳层只消费 —— 配置错了在这里就红,不必等到点开界面才发现。 */ import { describe, expect, it } from 'vitest'; import type { Session } from '@contracts'; import { sessionSchema } from '@contracts'; import { GOV_ZONES } from '../gov-route.ts'; import { SURFACE_ZONES, canSeeSurface, legacyGovTarget, surfaceOfZone } from '../surfaces.ts'; /** 经 schema 构造合法会话 —— §12.5 禁 `as unknown as`,测试同样遵守 */ const sessionWith = (fns: readonly string[]): Session => sessionSchema.parse({ staff: { id: 's-1', account: 'a', name: 'n', positionId: 'p-1', state: 'active' }, position: { id: 'p-1', code: 'PM', name: '项目经理' }, grants: fns.map((fn) => ({ positionId: 'p-1', object: { kind: 'governance', function: fn }, action: 'read', })), }); describe('SURFACE_ZONES —— 每个区恰属一面', () => { it('两面之和即全部区,不重不漏', () => { const all = [...SURFACE_ZONES.design, ...SURFACE_ZONES.ops]; expect([...all].sort()).toEqual([...GOV_ZONES].sort()); expect(new Set(all).size).toBe(all.length); }); it('设计面没有任何运行态区', () => { for (const zone of ['tasks', 'cockpit', 'ledgers'] as const) { expect(SURFACE_ZONES.design).not.toContain(zone); } }); it('surfaceOfZone 与区表一致', () => { expect(surfaceOfZone('workflows')).toBe('design'); expect(surfaceOfZone('ledgers')).toBe('ops'); }); }); describe('canSeeSurface —— 入口按授权亮灯', () => { it('持有该面任一功能的授权即亮', () => { expect(canSeeSurface(sessionWith(['process']), 'design')).toBe(true); expect(canSeeSurface(sessionWith(['task']), 'ops')).toBe(true); }); it('只有另一面的授权不亮', () => { expect(canSeeSurface(sessionWith(['task']), 'design')).toBe(false); expect(canSeeSurface(sessionWith(['activity']), 'ops')).toBe(false); }); it('trigger 与 data 两面都算 —— 定义归设计,处置与行数据归运营', () => { expect(canSeeSurface(sessionWith(['trigger']), 'design')).toBe(true); expect(canSeeSurface(sessionWith(['trigger']), 'ops')).toBe(true); expect(canSeeSurface(sessionWith(['data']), 'design')).toBe(true); expect(canSeeSurface(sessionWith(['data']), 'ops')).toBe(true); }); it('无会话不亮;无治理授权不亮', () => { expect(canSeeSurface(undefined, 'design')).toBe(false); expect(canSeeSurface(sessionWith([]), 'ops')).toBe(false); }); }); describe('legacyGovTarget —— 旧 #/gov 深链一条不死', () => { it('设计态区改写到 /design,深链原样带走', () => { expect(legacyGovTarget('#/gov/workflows/pkgs/pkg:unified-quote%404')).toBe( '/design#/design/workflows/pkgs/pkg:unified-quote%404', ); }); it('运行态区改写到 /ops', () => { // 看板已迁「工作流」区(2026-07-30):工作流是流程包的运行时,一级看板归它 expect(legacyGovTarget('#/gov/tasks/board')).toBe('/ops#/ops/runs/board'); }); it('已删的区落到对应新家', () => { expect(legacyGovTarget('#/gov/memory/metrics')).toBe('/ops#/ops/cockpit/dash'); // 活动记忆那一区 2026-07-29 整个撤了(运营 · 数据)—— 旧链接落运营首页,不给白屏 expect(legacyGovTarget('#/gov/memory/layers')).toBe('/ops#/ops/cockpit/dash'); }); /* * 触发器与大模型**又独立成区**了(2026-07-29):触发器一度并进流程包页签、 * 模型一度住在系统区 —— 那两段旧链接要各回各家,不能停在中途那一站。 */ it('搬回自己区的:触发器与大模型', () => { expect(legacyGovTarget('#/gov/triggers/rules')).toBe('/design#/design/triggers/rules'); expect(legacyGovTarget('#/gov/workflows/rules')).toBe('/design#/design/triggers/rules'); expect(legacyGovTarget('#/gov/system/llm')).toBe('/design#/design/models/llm'); }); it('裸 #/gov 落运营首页;非 gov 哈希不管', () => { expect(legacyGovTarget('#/gov')).toBe('/ops#/ops/cockpit/dash'); expect(legacyGovTarget('')).toBeUndefined(); expect(legacyGovTarget('#/whatever')).toBeUndefined(); }); });