/* * ★ 캡스톤 · 스트림라인 전 사슬(in-process) — 모델 → model → 커널 → 라이브 payload. * master(템플릿 산출 동형) → masterToTwin → WmsKernel.loadTwinModel → tick → getSnapshot → buildEntityDeltas. * 이 한 테스트가 조각들이 "함께" 동작함을 증명(개별 단위테스트 위의 통합). */ import { test } from 'node:test' import assert from 'node:assert/strict' import { WmsKernel, TwinRuntime } from '@operato/twin-kernel' import { masterToTwin } from '../server/service/reference/reference-master.ts' import { buildEntityDeltas } from '../shared/entity-delta.ts' const wmsMaster = () => ({ source: 'wh-e2e', system: 'wms' as const, siteName: 'E2E 창고', space: { width: 90, depth: 40, unit: 'm' }, locations: [ { id: 'a-dc', type: 'warehouse', role: 'area' as const, name: '창고' }, { id: 'dock-recv', type: 'dock', role: 'location' as const, name: '입고 도크', capacity: 100 }, { id: 'rack-1', type: 'storage', role: 'location' as const, name: '랙1', parentId: 'a-dc', capacity: 60 }, { id: 'rack-2', type: 'storage', role: 'location' as const, name: '랙2', parentId: 'a-dc', capacity: 60 }, { id: 'staging', type: 'staging', role: 'location' as const, name: '스테이징', capacity: 80 }, { id: 'dock-ship', type: 'dock-ship', role: 'location' as const, name: '출고 도크', capacity: 100 } ], equipment: [ { id: 'fk-1', kind: 'forklift', homeLocationId: 'dock-recv' }, { id: 'fk-2', kind: 'forklift', homeLocationId: 'dock-recv' } ] }) const snapshotState = (kernel: any) => { const snap = kernel.getSnapshot() return snap?.locations || snap?.equipment ? snap : snap?.state ?? snap } test('★ 캡스톤: master → masterToTwin → 커널 로드 → tick → 라이브 payload (전 사슬 결합)', () => { // 1) 모델 → model 토폴로지 const { model } = masterToTwin(wmsMaster()) assert.equal(model.locations.length, 5, '노드 5') assert.equal(model.equipment.length, 2, '무버 2') // 2) 커널 로드 + 런타임 tick const kernel = new WmsKernel('e2e') kernel.loadTwinModel(model) const runtime = new TwinRuntime(kernel) for (let i = 0; i < 5; i++) runtime.tick(1000) // 3) 스냅샷 → 라이브 payload(보드 컴포넌트가 구독할 data(tag)) const state = snapshotState(kernel) const deltas = buildEntityDeltas(state, 't1') const tags = new Set(deltas.map(d => d.tag)) // 4) 모델의 모든 노드·무버가 payload 에 도달(커널→보드 결합 성립) for (const n of model.locations) assert.ok(tags.has(n.id), `노드 ${n.id} payload 도달`) for (const m of model.equipment) assert.ok(tags.has(m.id), `무버 ${m.id} payload 도달`) // 5) 능력별 상태필드가 실림 — storable 노드 occupancy/capacity, mobile+operable 무버 location/status const rack = deltas.find(d => d.tag === 'rack-1')!.data as any assert.ok('occupancy' in rack && 'capacity' in rack, 'storable payload 필드') assert.equal(rack.capacity, 60, '랙 용량 전달') const fk = deltas.find(d => d.tag === 'fk-1')!.data as any assert.ok('location' in fk && 'status' in fk, 'mobile+operable payload 필드') }) test('캡스톤: 결정성 — 같은 model+tick 은 같은 payload 시그니처', () => { const run = () => { const { model } = masterToTwin(wmsMaster()) const kernel = new WmsKernel('det') kernel.loadTwinModel(model) const runtime = new TwinRuntime(kernel) for (let i = 0; i < 8; i++) runtime.tick(1000) return JSON.stringify(buildEntityDeltas(snapshotState(kernel), 't1')) } assert.equal(run(), run(), '동일 입력 → 동일 payload(결정적)') })