import { describe, expect, it } from 'vitest' import type { KbxNavigationResolvedEntry } from '@kbx/contracts' import { buildKbxHomeQuickStart, buildKbxHomeWorkbench, matchKbxRoutePattern, resolveKbxSafeRecentPath, resolveKbxSafeWorkspacePath } from '@kbx/ui' const entry=(screenId:string,path:string,homePriority?:number):KbxNavigationResolvedEntry=>({screenId,path,title:screenId,module:'ERP',section:'업무',permissions:[],menuVisible:true,favoriteAllowed:true,recentPolicy:'screen',homePriority}) describe('KBX navigation hardening',()=>{ it('deduplicates home quick start in favorite, tab, recent, priority order',()=>{ const a=entry('A','/a',30),b=entry('B','/b',10),c=entry('C','/c',20) const result=buildKbxHomeQuickStart([a,b,c],[a],[{screenId:'B',path:'/b',title:'B',visitedAt:'2026-08-08T10:00:00Z'}],[{key:'C:/c',screenId:'C',title:'C',path:'/c',openedAt:'2026-08-08T09:00:00Z',lastActivatedAt:'2026-08-08T11:00:00Z'}]) expect(result.map(x=>x.screenId)).toEqual(['A','C','B']) }) it('never trusts a persisted route unless route mode and path boundary are both valid',()=>{ const base={...entry('A','/erp/items'),recentPolicy:'route' as const} expect(resolveKbxSafeRecentPath(base,'/erp/items/123?tab=stock')).toBe('/erp/items/123') expect(resolveKbxSafeRecentPath(base,'/erp/items-evil/123')).toBe('/erp/items') expect(resolveKbxSafeRecentPath(base,'https://evil.example/x')).toBe('/erp/items') expect(resolveKbxSafeRecentPath({...base,recentPolicy:'screen'},'/erp/items/123')).toBe('/erp/items') }) it('keeps dirty/open work ahead of favorites and avoids duplicate home launch rows',()=>{ const a=entry('A','/a',30),b=entry('B','/b',10) const result=buildKbxHomeWorkbench([a,b],[a],[],[ {key:'A:/a',screenId:'A',title:'A',path:'/a',openedAt:'2026-08-08T09:00:00Z',lastActivatedAt:'2026-08-08T11:00:00Z',dirty:true}, ]) expect(result.filter(x=>x.screenId==='A')).toHaveLength(1) expect(result[0]?.source).toBe('dirty') }) it('validates workspace deep links against the registered route capability',()=>{ expect(matchKbxRoutePattern('/oms/orders/:orderId/edit','/oms/orders/123/edit')).toBe(true) expect(matchKbxRoutePattern('/oms/orders/:orderId/edit','/oms/orders/123/delete')).toBe(false) expect(resolveKbxSafeWorkspacePath(['/oms/orders/:orderId/edit'],'/oms/orders/123/edit','/home')).toBe('/oms/orders/123/edit') expect(resolveKbxSafeWorkspacePath(['/oms/orders/:orderId/edit'],'https://evil.example/oms/orders/123/edit','/home')).toBe('/home') }) it('rejects oversized and control-character workspace paths',()=>{ const fallback='/home' expect(resolveKbxSafeWorkspacePath(['/erp/items/:itemId'],`/erp/items/${'a'.repeat(2100)}`,fallback)).toBe(fallback) expect(resolveKbxSafeWorkspacePath(['/erp/items/:itemId'],'/erp/items/abc\n123',fallback)).toBe(fallback) expect(resolveKbxSafeWorkspacePath(['/erp/items/:itemId'],'/erp/items/%2e%2e',fallback)).toBe(fallback) }) })