105 lines
5.6 KiB
JavaScript
105 lines
5.6 KiB
JavaScript
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 keep the task pending until the transactional PDA final complete', () => {
|
|
assert.match(convergence, /summary\?\.status === 'LOADING'/)
|
|
assert.match(convergence, /!operations\.some\(\(item\) => item\.actionType === 'COMPLETE_LOADING'\)/)
|
|
assert.match(customs, /\['AWAITING_REVIEW', 'LOADED'\]/)
|
|
ordered(finalize, [
|
|
'最终完成前拖车与报关清关门禁属于同一稳定柜次',
|
|
'最终完成前四元身份稳定且任务等待人工确认',
|
|
'读取最终完成并发前计划即时版本',
|
|
'Admin 增量与 PDA final complete 并发恰好一个事务成功',
|
|
'订舱拖车报关齐备后完成装柜',
|
|
'统一硬门禁通过后 task 与 workflow 同事务完成且身份不漂移',
|
|
'入库单终态 LOADED、版本递增且审计历史存在',
|
|
'统一硬门禁通过后五类有效证据仍完整',
|
|
'COMPLETED 后新增计划明细被拒绝',
|
|
'COMPLETED 后调整计划明细被拒绝',
|
|
'COMPLETED 后删除计划明细被拒绝',
|
|
])
|
|
assert.match(finalize, /`\$\{taskPath\}\/complete`/)
|
|
assert.match(finalize, /await Promise\.all\(/)
|
|
assert.match(finalize, /completeWon !== adjustWon/)
|
|
assert.doesNotMatch(finalize, /\/loading\/complete\?tenantId=/)
|
|
})
|
|
|
|
test('PDA final completion OpenAPI freezes zero-write gate semantics', () => {
|
|
assert.match(pdaOpenApi, /\/api\/platform\/v1\/first-leg\/pda-loading\/tasks\/\{taskId\}\/complete:/)
|
|
assert.match(pdaOpenApi, /先无写入校验全部关联入库单的RELEASED订舱、COMPLETED拖车和五类ACTIVE证据/)
|
|
assert.match(pdaOpenApi, /任何阻断返回A0409且task、plan、inbound仍保持LOADING/)
|
|
assert.match(pdaOpenApi, /全部满足后在同一事务内结算资源、完成task\/plan并推进inbound为LOADED/)
|
|
})
|