import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import { dirname, resolve } from 'node:path' import test from 'node:test' import { fileURLToPath } from 'node:url' const here = dirname(fileURLToPath(import.meta.url)) const read = (path) => readFileSync(resolve(here, path), 'utf8') const firstlegOpenApi = read('../../firstleg/src/main/resources/openapi/firstleg-v1.yaml') const pdaOpenApi = read('../../firstleg/src/main/resources/openapi/phase3-pda-convergence-v1.yaml') const entry = read('../admin-test-env-acceptance.mjs') const loading = read('./phase3-loading.mjs') const convergence = read('./phase3-pda-convergence.mjs') const customs = read('./phase3-customs.mjs') const finalize = read('./phase3-loading-finalize.mjs') function ordered(source, fragments) { let cursor = -1 for (const fragment of fragments) { const next = source.indexOf(fragment, cursor + 1) assert.ok(next > cursor, `${fragment} must appear in frozen order`) cursor = next } } test('OpenAPI freezes LOADING incremental plan capabilities and optimistic versions', () => { for (const endpoint of [ '/api/platform/v1/first-leg/smart-loading/plans/{planId}/assignments:', '/api/platform/v1/first-leg/smart-loading/plans/{planId}/items/{itemId}:', ]) assert.ok(firstlegOpenApi.includes(endpoint), `${endpoint} missing`) for (const field of [ 'inboundId', 'inboundDetailId', 'packageCount', 'expectedVersion', 'operationId', 'expectedPlanVersion', 'expectedItemVersion', 'canAssign', 'canEdit', 'blockedReason', 'finalCompletionStatus', 'pdaTaskId', 'pdaTaskStatus', 'pdaTaskVersion', 'pickedPackageCount', 'minPackageCount', 'canAdjust', 'canRemove', ]) assert.match(firstlegOpenApi, new RegExp(`\\b${field}\\b`)) assert.match(firstlegOpenApi, /enum: \[NOT_STARTED, IN_PROGRESS, COMPLETED\]/) }) test('default fixture publishes a second marker-owned inbound for the same-plan increment', () => { assert.match(entry, /const incrementalMarker = `\$\{originalMarker\}-LOAD-INC`/) assert.match(entry, /orders-phase3-loading-incremental-fixture/) assert.match(entry, /inbound-measurement-phase3-loading-incremental-fixture/) assert.match(entry, /phase3LoadingIncrementalPackageCount = 2/) assert.match(entry, /phase3LoadingIncremental: incrementalFixture/) assert.match(entry, /marker: originalMarker/) assert.doesNotMatch(entry, /phase3LoadingIncremental[\s\S]{0,300}(history|fallback|latest)/i) }) test('HTTP chain refreshes after stale inventory and keeps one stable PDA identity', () => { ordered(loading, [ 'PDA 原子批次完成拣货并等待人工确认', '五类装柜证据完整且铅封号持久化', 'LOADING 阶段向同一 PDA task 增加入库单', '增量保持稳定四元身份、LOADING 不回退并撤回最终完成门禁', '增量后旧 inventory token 明确 A0409 并要求刷新', '增量旧 token 冲突后 plan/task/items 零写入', '未拣动态增量明细允许删除', '未拣动态增量明细允许调整数量', '调整数量递增 plan/task/item 版本并保持稳定任务', '重新增量后刷新 PDA task detail', '重新增量后刷新 inventory snapshot', '动态新增项拣完后同一任务重新进入 AWAITING_REVIEW', '已拣项减量低于 minPackageCount 返回 A0409', '已拣项删除返回 A0409', '已拣项删除和减量失败后 plan/task/items 零写入', '订舱拖车报关未齐备时 LOADED 硬门禁拒绝', ]) assert.match(loading, /String\(staleIncrementBatch\.body\?\.message \|\| ''\)\.includes\('库存已变化,请刷新后重试'\)/) assert.match(loading, /id\(incrementedTask\?\.loadingPlanId\) === loadingPlanId/) assert.match(loading, /id\(incrementedTask\?\.containerBookingId\) === containerBookingId/) assert.match(loading, /loadingPlanStatus: 'LOADING', finalStatus: 'AWAITING_REVIEW'/) assert.doesNotMatch(loading, /\/loading\/complete\?tenantId=/) }) test('downstream modules stop before HTTP until the new truck-coupled complete contract freezes', () => { assert.match(convergence, /summary\?\.status === 'LOADING'/) assert.match(convergence, /!operations\.some\(\(item\) => item\.actionType === 'COMPLETE_LOADING'\)/) assert.match(customs, /\['AWAITING_REVIEW', 'LOADED'\]/) assert.match(customs, /ensureReleasedOceanBooking/) assert.doesNotMatch(customs, /truck\?\.finalStatus !== 'COMPLETED'/) ordered(finalize, [ '最终装柜前保持 LOADING 且版本有效', 'PDA complete 联动拖车并原子推进 LOADED', 'return', 'const taskPath =', ]) assert.match(finalize, /BLOCKED_CONTRACT:用户已确认拖车缺失或未完成不再阻断/) assert.doesNotMatch(finalize, /truck\?\.finalStatus === 'COMPLETED'/) assert.doesNotMatch(finalize, /\/loading\/complete\?tenantId=/) }) test('current PDA complete OpenAPI remains a blocker because it still requires pre-completed truck', () => { assert.match(pdaOpenApi, /\/api\/platform\/v1\/first-leg\/pda-loading\/tasks\/\{taskId\}\/complete:/) assert.match(pdaOpenApi, /先无写入校验全部关联入库单的RELEASED订舱、COMPLETED拖车和五类ACTIVE证据/) assert.match(finalize, /BLOCKED_CONTRACT/) })