Files
baiyue-firstleg/scripts/admin-test-env-modules/phase3-producer-chain-contract.test.mjs
T

761 lines
44 KiB
JavaScript

import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import test from 'node:test'
import { selectCustomsFlow } from './phase3-customs.mjs'
import { buildFinalizeResource } from './phase3-loading-finalize.mjs'
import { selectContainerLoadingFixture, selectPlatformChannelIsolationPair } from './phase3-container-loading.mjs'
import { diffPushPreloadStability, projectPushPreloadStability } from './push-preload-stability.mjs'
import { AcceptanceRun, normalizeModuleOrder, parseArguments } from '../admin-test-env-acceptance.mjs'
function source(name) {
return readFileSync(new URL(name, import.meta.url), 'utf8')
}
function assertOrder(value, snippets) {
let cursor = -1
for (const snippet of snippets) {
const next = value.indexOf(snippet)
assert.ok(next > cursor, `${snippet} must appear after the previous production step`)
cursor = next
}
}
test('pairs customs source fixture with the matching loading container', () => {
const resources = {
orders: { tenantId: 10001, orderId: 101, inboundId: 201 },
orchestrationFixtures: {
phase3Container: { tenantId: 10001, orderId: 102, inboundId: 202 },
},
phase3Loading: { tenantId: 10001, inboundId: 201, containerBookingId: 301 },
phase3ContainerLoading: { tenantId: 10001, inboundId: 202, containerBookingId: 302 },
}
const selected = selectCustomsFlow(resources)
assert.equal(selected.fixture.orderId, 101)
assert.equal(selected.container.containerBookingId, 301)
})
test('falls back to the matching container fixture without crossing source identities', () => {
const resources = {
orchestrationFixtures: { phase3Container: { tenantId: 10001, orderId: 102, inboundId: 202 } },
phase3Loading: { tenantId: 10001, inboundId: 201, containerBookingId: 301 },
phase3ContainerLoading: { tenantId: 10001, inboundId: 202, containerBookingId: 302 },
}
const selected = selectCustomsFlow(resources)
assert.equal(selected.fixture.orderId, 102)
assert.equal(selected.container.containerBookingId, 302)
})
test('records LOADING and a cleanup reason when finalize returns S0500', () => {
const result = buildFinalizeResource(
{ inboundStatus: 'LOADING', inboundVersion: 5, finalStatus: 'COMPLETED' },
{ summary: { processStatus: 'LOADING', version: 5 } },
{ http: 200, body: { code: 'S0500' } },
)
assert.equal(result.finalStatus, 'LOADING')
assert.equal(result.finalizeSucceeded, false)
assert.match(result.notCleanedReason, /S0500/)
})
test('records LOADED and clears the reason only after a real LOADED reread', () => {
const result = buildFinalizeResource(
{ inboundStatus: 'LOADING', inboundVersion: 5, finalStatus: 'COMPLETED' },
{ summary: { processStatus: 'LOADED', version: 6 } },
{ http: 200, body: { code: '200' } },
)
assert.equal(result.finalStatus, 'LOADED')
assert.equal(result.finalizeSucceeded, true)
assert.equal(result.notCleanedReason, null)
})
test('container loading follows allocation sync, claim, real pick, completed sequence', () => {
const value = source('./phase3-container-loading.mjs')
assert.match(value, /\['EDITING', 'WAIT_LOADING'\]\.includes\(containerDetail\?\.planStatus\)/)
assertOrder(value, [
"synchronizedPlan?.status === 'EDITING'",
"claimedPlan?.status === 'LOADING'",
"const pickResponse = await request",
"const completeResponse = await request",
"finalPlan?.status === 'COMPLETED'",
])
assert.match(value, /picked\?\.workflowStatus === 'AWAITING_REVIEW'/)
assert.match(value, /`\$\{PDA\}\/tasks\/\$\{taskId\}\/complete`/)
assert.match(value, /expectedTaskVersion: Number\(picked\?\.version\)/)
assert.match(value, /operationId: `\$\{context\.marker\}-pda-complete`\.slice\(0, 128\)/)
})
test('bridge loading confirms preload before creating and claiming the PDA task', () => {
const value = source('./phase3-loading.mjs')
assertOrder(value, [
'读取柜次创建时原子生成的智能装柜计划',
'分配同步建立 bridge 关联',
'无真实柜号时 PDA 查询明确 A0404',
'无号且未关联订舱仍无 PDA 任务',
'三期订舱 RELEASED 冻结运输快照',
'模拟柜原地关联三期 RELEASED 订舱需求并补真实柜号',
'订舱 RELEASED 但预装未 CONFIRMED 仍无 PDA 任务',
'计划级确认前读取即时版本与就绪门禁',
'计划级原子确认整份预装并触发唯一 PDA 任务',
'计划确认后 task 待领取且 claim 前仍 WAIT_SPLIT/EDITING',
'读取柜次级 PDA 唯一事实任务',
"claimedPlan?.status === 'LOADING'",
"const canonicalPick = await request",
"pickedPlan?.status === 'LOADING'",
"pickedPlan?.finalCompletionStatus === 'IN_PROGRESS'",
])
assert.match(value, /const confirmPath = `\/api\/platform\/v1\/first-leg\/loading-plans\/\$\{loadingPlanId\}\/confirm`/)
assert.match(value, /confirmed\?\.confirmationStatus === 'CONFIRMED'/)
assert.match(value, /confirmed\?\.readiness\?\.readyForPda === true/)
assert.match(value, /canonicalTaskId === confirmedTaskId/)
assert.match(value, /计划级确认同 key 异参返回冲突/)
assert.match(value, /已确认计划使用新 key 重复确认返回冲突/)
assert.doesNotMatch(value, /\/preload\/confirming|\/preload\/confirm\?tenantId=/)
assert.doesNotMatch(value.slice(value.indexOf('分配同步建立 bridge 关联'), value.indexOf('const linkResponse')),
/\/transport/)
assert.match(value, /missingContainerTaskResponse\.http === 200 && codeOf\(missingContainerTaskResponse\) === 'A0404'/)
assert.doesNotMatch(value, /push-preload|无真实柜号拒绝推送柜次计划|PDA complete后补确认/)
assert.match(value, /billOfLadingNo:[\s\S]*vesselName:[\s\S]*voyageNo:[\s\S]*estimatedSailingAt:[\s\S]*estimatedArrivalAt:/)
assert.doesNotMatch(value, /P3-SHIPPING|P3-AGENT/)
assert.match(value, /const frozenTransport = Object\.fromEntries\(transportFields\.map/)
assert.match(value, /const frozenTransportMatches = frozenTransportValid[\s\S]*transportFields\.every/)
assert.match(value, /RELEASED 订舱回显完整冻结运输快照且无真实柜号[\s\S]*if \(!bookingId \|\| !requirement\?\.id \|\| !frozenTransportMatches\) return/)
assert.doesNotMatch(value, /new Date\(Date\.now\(\)/)
assert.match(value, /bookingConfirmed\?\.oceanBooking\?\.status === 'RELEASED'/)
})
test('late container gate mode proves the final gate without requiring evidence completion', () => {
const value = source('./phase3-loading.mjs')
assert.match(value, /LATE_CONTAINER_GATE_ONLY = process\.env\.LATE_CONTAINER_GATE_ONLY === '1'/)
assertOrder(value, [
'无号模拟柜原地关联三期 RELEASED 订舱需求',
'计划级原子确认整份预装并触发唯一 PDA 任务',
'领取柜次 PDA 任务',
'原子提交柜次 PDA 整批拣货',
'无号拣货完成后 PDA complete 精确返回 A0409',
'LOADING 阶段通过预装运输信息首次补录真实柜号',
'补号后 complete 已越过柜号门禁并受理异步命令',
'补号后已越过柜号门禁并允许进入柜级证据门禁',
])
assert.match(value, /String\(missingComplete\.body\?\.message \|\| ''\)\.includes\('真实集装箱号'\)/)
assert.match(value, /nextStage === 'CABINET_EVIDENCE' && nextCode === 'A0409'/)
assert.match(value, /!nextMessage\.includes\('集装箱号'\)/)
assert.match(value, /context\.createdResources\.phase3LateContainerGate/)
assert.match(value, /独立柜未创建拖车单也不阻塞 PDA 装柜完成命令/)
})
test('PDA completion production and HTTP chain never call or gate on truck appointments', () => {
const service = source('../../firstleg/src/main/java/com/baiyue/firstleg/service/LoadingPickService.java')
const serviceTest = source('../../firstleg/src/test/java/com/baiyue/firstleg/service/LoadingPickServiceTest.java')
const loading = source('./phase3-loading.mjs')
assert.doesNotMatch(service, /TruckAppointmentService|completeLoadingFromPda|completeTruckAfterCommit/)
assert.match(serviceTest, /completionWorkerIsIndependentOfEveryTruckAppointmentState/)
for (const state of ['NO_APPOINTMENT', 'DRAFT', 'IN_PROGRESS', 'COMPLETED']) {
assert.match(serviceTest, new RegExp(state))
}
assert.match(serviceTest, /failedSameOperationIdCanResumeWithoutCreatingAnotherCommand[\s\S]*assertNoTruckCoupling/)
assert.match(serviceTest, /secondCustomerOwnerReadyFailureLeavesEveryOwnerUnsettledAndUncompleted[\s\S]*assertNoTruckCoupling/)
assert.match(serviceTest, /workerRestartResumesFromOwnerCheckpointsWithoutRepeatingSettlements[\s\S]*assertNoTruckCoupling/)
assert.doesNotMatch(loading, /\/truck-appointments|completeLoadingFromPda/)
})
test('allocation synchronizes the complete pre-PDA snapshot without a push protocol', () => {
const value = source('./phase3-loading.mjs')
assert.match(value, /plans\/by-booking\/\$\{encodeURIComponent\(containerBookingId\)\}/)
assert.match(value, /分配同步建立 bridge 关联/)
assert.match(value, /分配事务完成态计划与 bridge 一致且保持 EDITING/)
assert.match(value, /Number\(synchronizedPlan\?\.version\) === Number\(link\?\.loadingPlanVersion\)/)
assert.doesNotMatch(value, /push-preload|pushRequest|projectPushPreloadStability|diffPushPreloadStability/)
})
test('push stability projection sorts unordered facts and reports persisted field drift', () => {
const baseline = projectPushPreloadStability({
plan: { id: '10', bookingId: '20', status: 'EDITING', version: 2,
items: [{ id: '2', inboundId: '42', packageCount: 1, version: 1 },
{ id: '1', inboundId: '41', packageCount: 1, version: 1 }] },
detail: { summary: { planId: '10', containerBookingId: '20', status: 'EDITING', version: 2,
readiness: { preloadPushed: true, preloadLinkCount: 2, confirmationStatus: 'PENDING',
blockingReasons: ['CONTAINER_NO_MISSING', 'OCEAN_BOOKING_NOT_LINKED'] } },
items: [{ itemId: '2', inboundId: '42', packageCount: 1 }, { itemId: '1', inboundId: '41', packageCount: 1 }],
histories: [{ historyId: '2', actionType: 'PUSH' }, { historyId: '1', actionType: 'CREATE' }] },
links: [{ id: '2', inboundId: '42', loadingPlanId: '10', allocatedPackageCount: 1 },
{ id: '1', inboundId: '41', loadingPlanId: '10', allocatedPackageCount: 1 }],
task: { http: 200, code: 'A0404' },
})
const reordered = projectPushPreloadStability({
plan: { id: '10', bookingId: '20', status: 'EDITING', version: 2,
items: [{ id: '1', inboundId: '41', packageCount: 1, version: 1 },
{ id: '2', inboundId: '42', packageCount: 1, version: 1 }], updatedAt: 'ignored' },
detail: { summary: { planId: '10', containerBookingId: '20', status: 'EDITING', version: 2,
readiness: { preloadPushed: true, preloadLinkCount: 2, confirmationStatus: 'PENDING',
blockingReasons: ['OCEAN_BOOKING_NOT_LINKED', 'CONTAINER_NO_MISSING'] } },
items: [{ itemId: '1', inboundId: '41', packageCount: 1 }, { itemId: '2', inboundId: '42', packageCount: 1 }],
histories: [{ historyId: '1', actionType: 'CREATE' }, { historyId: '2', actionType: 'PUSH' }] },
links: [{ id: '1', inboundId: '41', loadingPlanId: '10', allocatedPackageCount: 1 },
{ id: '2', inboundId: '42', loadingPlanId: '10', allocatedPackageCount: 1 }],
task: { http: 200, code: 'A0404' },
})
assert.deepEqual(diffPushPreloadStability(baseline, reordered), [])
const drifted = structuredClone(reordered)
drifted.plan.version = 3
drifted.links[0].preloadVersion = 2
assert.deepEqual(diffPushPreloadStability(baseline, drifted), [
'links[0].preloadVersion:null->2', 'plan.version:2->3',
])
})
test('allocation orchestration is transactional and binds the preload before PDA creation', () => {
const service = source('../../firstleg/src/main/java/com/baiyue/firstleg/service/SmartLoadingService.java')
const assign = service.slice(service.indexOf('private Plan assign('), service.indexOf('public Plan assignInbound'))
assert.match(service, /@Transactional[\s\S]*public Plan assign/)
assert.match(assign, /ensurePendingForLoadingAllocation[\s\S]*loadingModelBridge\.bindIncrementalAllocation/)
assert.match(assign, /loadingModelBridge\.bindIncrementalAllocation[\s\S]*loadingPickService\.sync/)
assert.doesNotMatch(source('../../firstleg/src/main/java/com/baiyue/firstleg/controller/PlatformSmartLoadingController.java'), /push-preload/)
})
test('finalize rechecks the same unified order detail and list as LOADED', () => {
const value = source('./phase3-loading-finalize.mjs')
assertOrder(value, [
'入库单终态 LOADED、版本递增且审计历史存在',
'LOADED 后回查同一统一订单详情',
'统一订单详情投影同一订单为 LOADED',
'LOADED 后回查同一统一订单列表',
'统一订单列表投影同一订单为 LOADED',
])
assert.match(value, /summary\?\.mainStatus === 'LOADED'/)
assert.match(value, /unifiedRow\?\.mainStatus === 'LOADED'/)
assert.match(source('./phase3-loading.mjs'), /tenantId, orderId: id\(source\?\.orderId\), inboundId/)
})
test('PDA inventory snapshot gates batch and records the only safe mutation gap', () => {
const loading = source('./phase3-loading.mjs')
assertOrder(loading, [
'读取 PDA 提交前实时库存快照',
'未发生库存变化时重复读取实时库存快照',
'客户身份无权读取平台 PDA 实时库存快照',
'const overflowResource =',
'通过公开 A0 换绑接口制造真实库存变化',
'真实库存变化后旧 snapshot token 提交返回冲突',
'旧 token 冲突对任务、明细、已拣数量和操作历史零写入',
'库存变化后刷新 PDA 实时库存快照',
'const canonicalPick = await request',
'const canonicalBatchReplay = await request',
])
assert.match(loading, /inventorySnapshotToken: refreshedInventory\.snapshotToken/)
assert.match(loading, /warehouseId, loadingPlanId, containerBookingId,/)
assert.match(loading,
/LATE_CONTAINER_GATE_ONLY \? inventory\?\.currentContainerNo == null : inventory\?\.currentContainerNo === containerNo/)
const batchPayload = loading.slice(loading.indexOf('const batchPayload ='), loading.indexOf('const staleBatch ='))
assert.doesNotMatch(batchPayload, /containerNo[,\s]/)
assert.doesNotMatch(loading, /\/loading\/picking\/(?:start|complete)/)
assert.match(loading, /inventorySnapshotVerified: inventorySnapshotVerified && atomicBatchReplayVerified/)
assert.match(loading, /inventoryMutationVerified, inventoryMutationCleanup/)
assert.match(loading, /当前唯一平台代理库存变更入口只允许 A0 临时占用换绑/)
const convergence = source('./phase3-pda-convergence.mjs')
assert.doesNotMatch(convergence, /未公开实时库存查询或库存版本快照端点/)
assert.match(convergence, /fixture\?\.inventorySnapshotVerified !== true/)
assert.match(convergence, /fixture\?\.inventoryMutationVerified !== true/)
})
test('container loading arranges a simulation before linking a released booking requirement', () => {
const value = source('./phase3-container-loading.mjs')
assert.match(value, /candidates\.find\(\(candidate\) => candidate\.marker === context\.marker\)/)
assertOrder(value, [
"const simulationResponse = await request(context, 'POST', CONTAINERS",
"const candidateResponse = await context.request('GET', `${SMART}/candidates?bookingId=",
"const planResponse = await context.request('GET', `${SMART}/plans/by-booking/",
"const assignResponse = await request",
'分配同步建立 bridge',
"const bookingCreate = await request",
"const linkResponse = await request(context, 'POST', `${CONTAINERS}/${containerId}/ocean-booking-link`",
])
assert.match(value, /publishPlatformLogisticsChannels\(context, warehouseId, MODULE\)/)
const draft = value.slice(value.indexOf('const bookingDraft = {'), value.indexOf('context.assert(MODULE, \'订舱渠道与仓库快照完整\''))
assert.doesNotMatch(draft, /warehouseName\s*:/)
for (const field of ['billOfLadingNo', 'vesselName', 'voyageNo', 'estimatedSailingAt', 'estimatedArrivalAt']) {
assert.match(draft, new RegExp(`${field}:`))
}
assert.doesNotMatch(value, /loading-booking-(?:submit|confirm)|ocean-bookings\/.*\/(?:submit|confirm)/)
assert.match(value, /confirmedBooking\.containerRequirements\.length === 2/)
assert.match(value, /oceanBookingId: bookingId, requirementId: id\(requirement\.id\), containerNo: realContainerNo/)
assert.match(value, /function responseEvidence\(response\)[\s\S]*HTTP=\$\{response\?\.http \?\? 'missing'\}[\s\S]*code=\$\{codeOf\(response\)\}[\s\S]*requestId=\$\{response\?\.requestId \|\| 'missing'\}[\s\S]*traceId=\$\{response\?\.traceId \|\| 'missing'\}/)
assert.match(value, /关联后列表仅保留模拟柜原地补全的一条 ACTIVE 操作身份/)
assert.match(value, /订舱 allocation 原子创建并指向稳定模拟柜身份/)
assert.match(value, /已消费订舱需求拒绝再次关联/)
assertOrder(value, [
"const linkedContainer = context.apiSuccess(MODULE, '首次关联 RELEASED 订舱需求并补真实柜号', linkResponse,",
'if (!linkedContainer) return',
"const afterLinkPlanResponse = await context.request('GET'",
"const duplicateSimulationResponse = await request(context, 'POST', CONTAINERS",
])
assert.doesNotMatch(value, /realContainerBookingId|MERGED/)
assert.match(value, /关联前后内部柜次、计划和排货明细身份稳定/)
assert.match(value, /bookingId=\$\{encodeURIComponent\(containerId\)\}&warehouseId=/)
assert.match(value, /tasks\?loadingPlanId=\$\{encodeURIComponent\(planId\)\}&warehouseId=/)
assert.match(value, /loadingPlanId: planId, containerBookingId: containerId/)
assert.doesNotMatch(value.slice(value.indexOf('分配同步建立 bridge'), value.indexOf('const linkResponse')),
/\/transport/)
assert.match(value, /订舱放舱为 RELEASED 且不预生成真实货柜/)
assert.match(value, /分配同步建立 bridge/)
assert.match(value, /订舱 RELEASED 但预装未 CONFIRMED 不生成 PDA 任务/)
assert.match(value, /计划级原子确认整份预装并触发唯一 PDA 任务/)
assert.match(value, /confirmationStatus === 'CONFIRMED'/)
assert.match(value, /readiness\?\.readyForPda === true/)
assert.match(value, /taskId === confirmedTaskId/)
assert.match(value, /计划级确认同 key 异参返回冲突/)
assert.match(value, /已确认计划使用新 key 重复确认返回冲突/)
assert.doesNotMatch(value, /\/preload\/confirming|\/preload\/confirm\?tenantId=/)
assert.match(value, /POST', CONTAINERS/)
})
test('container loading selects the current marker instead of a stale CAB fixture', () => {
const selected = selectContainerLoadingFixture({
marker: 'ADMIN-RUN-AUTO1',
createdResources: {
orchestrationFixtures: {
phase3Container: { marker: 'ADMIN-RUN-CAB', inboundId: 'CAB-INBOUND' },
},
inboundMeasurement: { marker: 'ADMIN-RUN-AUTO1', inboundId: 'FINANCE-INBOUND' },
orders: { marker: 'ADMIN-RUN-AUTO1', inboundId: 'FINANCE-ORDER-INBOUND' },
},
})
assert.equal(selected.inboundId, 'FINANCE-INBOUND')
assert.equal(selected.marker, 'ADMIN-RUN-AUTO1')
})
test('container loading keeps an unmatched candidate so marker failure stays observable', () => {
const selected = selectContainerLoadingFixture({
marker: 'ADMIN-RUN-AUTO1',
createdResources: {
orchestrationFixtures: {
phase3Container: { marker: 'ADMIN-RUN-CAB', inboundId: 'CAB-INBOUND' },
},
},
})
assert.equal(selected.marker, 'ADMIN-RUN-CAB')
})
test('channel isolation accepts only two active channels from the current platform warehouse catalog', () => {
const selected = selectPlatformChannelIsolationPair({
marker: 'ADMIN-RUN-AUTO1', originWarehouseId: '41',
sourcePath: '/api/platform/v1/frozen-channel-contract?originWarehouseId=41',
channels: [
{ channelCode: 'SEA-A', channelName: '海运 A', status: 'ACTIVE' },
{ channelCode: 'SEA-B', channelName: '海运 B', status: 'ACTIVE' },
{ channelCode: 'SEA-C', channelName: '海运 C', status: 'DISABLED' },
],
}, 'ADMIN-RUN-AUTO1', '41', 'SEA-A')
assert.deepEqual(selected.pair, {
primary: { channelCode: 'SEA-A', channelName: '海运 A' },
foreign: { channelCode: 'SEA-B', channelName: '海运 B' },
})
assert.equal(selected.reason, null)
})
test('channel isolation reports BLOCKED_FIXTURE instead of inventing a second channel', () => {
for (const catalog of [
{ marker: 'OLD-RUN', originWarehouseId: '41', sourcePath: '/api/platform/v1/channels', channels: [] },
{ marker: 'ADMIN-RUN-AUTO1', originWarehouseId: '42', sourcePath: '/api/platform/v1/channels', channels: [] },
{ marker: 'ADMIN-RUN-AUTO1', originWarehouseId: '41', sourcePath: '/api/merchant/v1/first-leg/pricing/channels', channels: [] },
{ marker: 'ADMIN-RUN-AUTO1', originWarehouseId: '41', sourcePath: '/api/platform/v1/channels',
channels: [{ channelCode: 'SEA-A', channelName: '海运 A', status: 'ACTIVE' }] },
]) {
const selected = selectPlatformChannelIsolationPair(catalog, 'ADMIN-RUN-AUTO1', '41', 'SEA-A')
assert.equal(selected.pair, null)
assert.match(selected.reason, /^BLOCKED_FIXTURE:/)
}
const value = source('./phase3-container-loading.mjs')
assert.doesNotMatch(value, /foreignChannelCode|\$\{channelCode\}-OTHER|异渠道-\$\{suffix\}/)
assert.match(value, /context\.createdResources\?\.platformLogisticsChannels/)
assert.match(value, /codeOf\(foreignAssignResponse\) === 'A0422'/)
})
test('container loading enforces channel isolation and stable PDA identity across container number changes', () => {
const value = source('./phase3-container-loading.mjs')
assertOrder(value, [
'新增内部模拟柜',
'新增真实异渠道模拟柜',
'两个真实有效渠道之间拒绝跨渠道装柜',
'人工分配本轮候选货物',
'分配同步建立 bridge',
'无真实柜号时 PDA 查询 A0404',
'无号且未关联订舱不生成 PDA 任务',
'订舱放舱为 RELEASED 且不预生成真实货柜',
'首次关联 RELEASED 订舱需求并补真实柜号',
'订舱 RELEASED 但预装未 CONFIRMED 不生成 PDA 任务',
'计划级确认前读取即时版本与就绪门禁',
'计划级原子确认整份预装并触发唯一 PDA 任务',
'计划确认后 task 待领取且 claim 前仍 WAIT_SPLIT/EDITING',
'换柜号前读取 PDA 稳定库存快照',
'PDA 任务创建后替换真实柜号',
'PDA 任务创建后清空真实柜号失败',
'换柜号后按稳定计划身份回查 PDA 任务',
'换柜号不改变任务计划柜次柜次号和库存快照身份',
])
assert.match(value, /containerDetail\?\.channelCode === channelCode/)
assert.match(value, /id\(replacedTask\?\.cabinetNo\) === stableIdentity\.cabinetNo/)
assert.match(value, /afterReplacementSnapshot\?\.snapshotToken === beforeReplacementSnapshot\?\.snapshotToken/)
assert.match(value, /pdaIdentityStable: true/)
assert.match(value, /category=BOOKING&status=ACTIVE/)
assert.match(value, /shippingCompanyId: id\(shippingCarrier\.id\), shippingCompanyName: shippingCarrier\.name, carrierCode: 'MATSON'/)
assert.match(value, /bookingAgentId: id\(bookingAgent\.id\), bookingAgentName: bookingAgent\.name/)
assert.match(value, /const frozenTransport = Object\.fromEntries\(transportFields\.map/)
assert.match(value, /if \(!frozenTransportMatches\) return/)
assert.match(value, /const releaseVersion = Number\(bookingDocument\?\.bookingVersion\)/)
assert.match(value, /expectedVersion: releaseVersion, reason: context\.marker/)
assertOrder(value, ['上传有效订舱确认文件', '订舱确认文件已关联且版本递增',
'订舱放舱为 RELEASED 且不预生成真实货柜'])
assert.doesNotMatch(value, /const sailingAt = new Date\(Date\.now\(\)/)
const batch = value.slice(value.indexOf('const staleBatchResponse'), value.indexOf('const completePath'))
assert.doesNotMatch(batch, /containerNo:/)
})
test('preload and loading versions use immediate allocation and claim baselines', () => {
const container = source('./phase3-container-loading.mjs')
assert.match(container, /plans\/by-booking\/\$\{encodeURIComponent\(containerId\)\}/)
assert.match(container, /分配同步建立 bridge/)
assert.doesNotMatch(container, /push-preload|pushRequest/)
assert.match(container, /const beforeClaimPlanVersion = Number\(beforeClaimPlan\?\.version\)/)
assert.match(container, /Number\(claimedPlan\?\.version\) > beforeClaimPlanVersion/)
const loading = source('./phase3-loading.mjs')
assert.match(loading, /plans\/by-booking\/\$\{encodeURIComponent\(containerBookingId\)\}/)
assert.match(loading, /分配事务完成态计划与 bridge 一致且保持 EDITING/)
assert.doesNotMatch(loading, /push-preload|pushRequest/)
assert.match(loading, /Number\(claimedPlan\?\.version\) > beforeClaimPlanVersion/)
assert.match(loading, /MATSON_SUPPLIER_NAME = 'MATSON 开发联调订舱代理'/)
assert.match(loading, /category=BOOKING&status=ACTIVE&carrierCode=MATSON&page=1&size=200/)
assert.match(loading, /ensureMatsonSupplier\(context, supplierRecords\)/)
assert.match(loading, /shippingCompanyId: id\(shippingCarrier\.id\), shippingCompanyName: shippingCarrier\.name/)
assert.match(loading, /bookingAgentId: id\(supplier\.id\), bookingAgentName: supplier\.name/)
assert.doesNotMatch(loading, /shippingCompanyId: id\(supplier\.id\), shippingCompanyName: supplier\.name/)
assert.match(loading, /cartonMarks: stableLoadedCartonMarks\(stagedTask\?\.items, loadingPlanId\)/)
assert.match(loading, /createHash\('sha256'\)\.update\(`\$\{seed\}:\$\{left\}`\)/)
assert.match(source('./phase3-loading-finalize.mjs'), /cartonMarks: Array\.isArray\(fixture\.cartonMarks\)/)
})
test('PDA completion acceptance verifies immutable BATCH and COMPLETE_LOADING audit with operator metadata', () => {
const loading = source('./phase3-loading.mjs')
const container = source('./phase3-container-loading.mjs')
const convergence = source('./phase3-pda-convergence.mjs')
assertOrder(container, [
'最后一箱拣货后 PDA 任务等待人工确认装柜完成',
'上传并绑定柜次 ${evidenceType} 真实证据文件',
'${evidenceType} 有效证据归属稳定且锁码门禁完整',
'PDA 完成前回查五类有效装柜证据与锁码',
'PDA 提交人工确认装柜完成',
'等待 PDA 装柜完成异步收敛',
])
assert.match(container, /const evidenceTypes = \['PHOTO_STEP_1', 'PHOTO_STEP_2', 'PHOTO_STEP_3', 'PHOTO_STEP_4', 'SEAL'\]/)
assert.match(container, /form\.append\('evidenceType', evidenceType\)/)
assert.match(container, /new Blob\(\[png\], \{ type: 'image\/png' \}\)/)
assert.match(container, /item\.evidenceType === 'SEAL' && item\.sealNo === sealNo/)
assert.match(container, /if \(!evidenceTypes\.every[\s\S]*\) return/)
assert.match(container, /requestBinary\('GET',[\s\S]*\/evidence\/\$\{encodeURIComponent\(evidenceId\)\}\/content/)
assert.match(container, /回查完成后的 PDA 操作审计/)
assert.match(container, /item\.actionType === 'BATCH'/)
assert.match(container, /item\.actionType === 'COMPLETE_LOADING'/)
assert.match(container, /Boolean\(item\.operatorId\) && Boolean\(item\.createdAt\)/)
assert.match(convergence, /PDA 原子批次完成审计包含 BATCH 操作/)
assert.match(convergence, /item\.actionType === 'BATCH'/)
for (const key of ['bridge-batch', 'exception-skip-batch', 'exception-partial-batch', 'dynamic-pick']) {
assert.match(loading, new RegExp(key))
}
assert.match(loading, /expectedBatchOperationIds = dropshipWholePallet\s*\? \[batchKey\]\s*:\s*\[batchKey, skipBatchKey, partialBatchKey, dynamicBatchKey\]/)
assert.match(loading, /skipBatchVerified, partialBatchVerified, dynamicIncrementVerified/)
assert.match(convergence, /expectedBatchCount = fixture\?\.fulfillmentType === 'ONE_CLICK_DROPSHIP' \? 1 : 4/)
assert.match(convergence, /expectedBatchOperationIds\.length === expectedBatchCount/)
})
test('phase3 loading proves mixed-line rollback across task, all items and operations', () => {
const loading = source('./phase3-loading.mjs')
assertOrder(loading, [
'非法混合批次前读取 PDA 全量任务基线',
'PDA 原子批次任一非法明细时整批拒绝',
'回查失败批次后的 PDA 任务快照',
'非法混合批次后回查 PDA 操作历史',
'合法明细加非法明细失败后任务、全部明细和操作历史零写入',
])
assert.match(loading, /taskItemsSignature\(unchangedAfterInvalid\) === taskItemsSignature\(beforeInvalidTask\)/)
assert.match(loading, /operationsSignature\(afterInvalidOperations\) === operationsSignature\(beforeInvalidOperations\)/)
assert.doesNotMatch(loading, /tasks\?containerNo=/)
assert.equal((loading.match(/tasks\?loadingPlanId=/g) || []).length >= 7, true)
})
test('phase3 loading proves stale inventory zero-write and defers final completion audit', () => {
const loading = source('./phase3-loading.mjs')
assert.match(loading, /taskItemsSignature\(afterStaleTask\) === taskItemsSignature\(beforeMutationTask\)/)
assert.match(loading, /动态新增计划明细按 inboundDetailId 唯一定位/)
assert.match(loading, /重新加入动态明细仍按 inboundDetailId 唯一定位/)
assert.equal((loading.match(/id\(item\?\.inboundDetailId\) === id\(dynamicCandidate\.inboundDetailId\)/g) || []).length, 2)
assert.doesNotMatch(loading, /items\?\.find\?\.\(\(item\) => id\(item\?\.inboundId\) === incrementalInboundId\)/)
assertOrder(loading, [
'旧 token 冲突对任务、明细、已拣数量和操作历史零写入',
'库存变化后刷新 PDA 实时库存快照',
'原子提交柜次 PDA 整批拣货',
'PDA 原子批次同参同键重放保持待人工确认',
'回查下游门禁前原子 Batch 审计',
'最终确认前仅保留 CLAIM/BATCH 且无 COMPLETE_LOADING',
'报关与最终完成前保持 LOADING 且不提前写 COMPLETE_LOADING',
])
assert.doesNotMatch(loading, /complete-before-downstream/)
})
test('phase3 loading resolves legal SKIP and PARTIAL exceptions before downstream completion', () => {
const loading = source('./phase3-loading.mjs')
const entry = source('../admin-test-env-acceptance.mjs')
const measurement = source('./inbound-measurement.mjs')
assertOrder(loading, [
'本轮增量 fixture 提供独立 SKIP 与 PARTIAL 合法货物明细',
'加入本轮待跳拣异常明细',
'SKIP 携带异常原因并原子解决待拣明细',
'合法 SKIP 保存零实拣事实并恢复 AWAITING_REVIEW',
'加入本轮部分拣货异常明细',
'未解决异常完成前读取 PDA 操作审计基线',
'存在未解决异常明细时禁止确认装柜完成',
'未解决异常完成失败后 task/items/operation 全部零写入',
'合法 PARTIAL 解决未完成明细',
'部分拣货数量守恒并恢复最终可确认状态',
'回查柜次 PDA 异常原因与已解决进度',
'SKIP 异常原因持久化且 PARTIAL 后无未解决明细',
'报关与最终完成前保持 LOADING 且不提前写 COMPLETE_LOADING',
])
assert.match(loading, /action: 'SKIP', version: Number\(skipTaskItem\?\.version\),\s*reason: skipReason/)
assert.match(loading, /skipReason = `\$\{context\.marker\}-箱唛破损待复核`/)
assert.match(loading, /itemStatus === 'SKIPPED'[\s\S]*pickedPackageCount\) === 0/)
assert.match(loading, /action: 'PARTIAL', finalPickedPackageCount: 1/)
assert.match(loading, /plannedPackageCount\) === 2[\s\S]*pickedPackageCount\) === 1/)
assert.match(loading, /String\(unresolvedComplete\.body\?\.message \|\| ''\)\.includes\('尚未处理'\)/)
assert.match(loading, /taskItemsSignature\(afterUnresolvedCompleteTask\) === taskItemsSignature\(unresolvedTask\)/)
assert.match(loading, /operationsSignature\(operationsAfterUnresolvedComplete\) === operationsSignature\(operationsBeforeUnresolvedComplete\)/)
assert.match(loading, /exceptionOverview\?\.pda\?\.exceptionReasons\?\.includes\?\.\(skipReason\)/)
assert.match(loading, /Number\(exceptionOverview\?\.pda\?\.unresolvedItemCount\) === 0/)
assert.doesNotMatch(loading, /addCoverageGap\([^)]*(SKIP|PARTIAL|未解决异常)/)
assert.match(entry, /this\.context\.phase3LoadingIncrementalDetailCount = 4/)
assert.match(measurement, /增量 fixture 形成动态真实独立货物测量明细/)
assert.match(measurement, /detailBoxCode = `\$\{inboundCode\}#\$\{originalPackageCount\}-\$\{cartonIndex\}`/)
assert.match(measurement, /items: draftItems/)
assert.match(measurement, /expectedPackages === originalPackageCount/)
assert.match(loading, /skipBatchVerified = context\.assert/)
assert.match(loading, /partialBatchVerified = context\.assert/)
assert.doesNotMatch(loading, /if \(Number\(dynamicCandidate\?\.availablePackageCount\) < 2\) return/)
})
test('phase3 loading fixture paginates A0 candidates and refuses mass location mutations', () => {
const entry = source('../admin-test-env-acceptance.mjs')
const orders = source('./orders.mjs')
assertOrder(entry, [
'disableAvailableLocationsForA0(warehouseId, requiredPalletCount)',
'this.context.phase3LoadingRequireSharedOverflow = requireSharedOverflow',
'createPhase3FreshMeasurementPallet(warehouseId)',
"executeModule('inbound-measurement-phase3-loading-fixture'",
'restoreAvailableLocationsAfterA0(warehouseId, isolation)',
])
assert.match(entry, /page=\$\{pageNumber\}&size=\$\{A0_LOCATION_PAGE_SIZE\}/)
assert.match(entry, /A0_MAX_LOCATION_MUTATIONS = 8/)
assert.match(entry, /A0 大仓保护:不批量停用普通库位/)
assert.match(entry, /创建本轮库存快照专用称重托盘/)
assert.match(entry, /\/api\/platform\/v1\/pallets\?tenantId=0/)
assert.match(entry, /measurementPalletOverride/)
assert.doesNotMatch(entry, /internal\/warehouse.*inventory-snapshot-a0/)
assert.match(entry, /remainingNormal\.length < isolation\.requiredSlots/)
assert.match(entry, /\/locations\/\$\{encodeURIComponent\(location\.id\)\}\/status/)
assert.match(entry, /\{ status: 'DISABLED', version: Number\(location\.version\) \}/)
assert.match(entry, /\{ status: 'AVAILABLE', version: location\.version \}/)
assert.match(orders, /receivingWarehouseId: id\(master\.receiving\.id\)/)
const loading = source('./phase3-loading.mjs')
assert.match(loading, /context\.context\?\.phase3LoadingRequireSharedOverflow === true/)
assert.match(loading, /附加生产者不重复要求 A0 库存快照变更专项/)
})
test('customs reuses the loading chain released booking without pre-completing truck', () => {
const value = source('./phase3-customs.mjs')
assertOrder(value, [
'selectCustomsFlow(context.createdResources)',
'if (!await ensureReleasedOceanBooking(context, container)) return',
'if (!await verifyContainerOperationalDocuments(context, containerBookingId))',
'const customs = await completeContainerCustoms(context, loadingPlanId)',
])
assert.match(value, /const oceanBookingId = id\(container\?\.oceanBookingId\)/)
assert.match(value, /ocean-bookings\/\$\{encodeURIComponent\(oceanBookingId\)\}/)
assert.match(value, /detail\?\.containers\?\.some/)
assert.doesNotMatch(value, /oceanBookings\.run\(context\)/)
assert.doesNotMatch(value, /serviceCenter\.run\(context\)/)
assert.doesNotMatch(value, /truck\?\.finalStatus !== 'COMPLETED'/)
assert.match(value, /PLATFORM_CUSTOMS = '\/api\/platform\/v1\/customs\/loading-plans'/)
assert.match(value, /complete-all-buy/)
assert.match(value, /finalStatus: 'COMPLETED'/)
assert.doesNotMatch(value, /checklist-versions|submission-batches|category: 'CUSTOMS'/)
})
test('downstream container consumers prefer the phase3Loading fixture', () => {
const ocean = source('./ocean-bookings.mjs')
const customs = source('./phase3-customs.mjs')
const serviceCenter = source('./service-center-flow.mjs')
assert.match(customs, /createdResources\?\.phase3Loading,\s*\n\s*createdResources\?\.phase3ContainerLoading/)
assert.match(serviceCenter, /context\.createdResources\?\.phase3Loading\s*\|\|\s*context\.createdResources\?\.phase3ContainerLoading/)
assert.match(ocean, /完整 DRAFT 放舱为 RELEASED 且冻结五项运输事实/)
assert.match(ocean, /finalStatus: 'RELEASED', containerBookingId: null, loadingPlanId: null/)
assert.match(source('./phase3-loading.mjs'), /oceanBookingId: bookingId, loadingPlanId, containerBookingId/)
assert.match(serviceCenter, /if \(!allocationMatches\) return/)
assert.match(serviceCenter, /category=TRUCKING&status=ACTIVE/)
assert.match(serviceCenter, /oceanBookingId: ocean\.id/)
assert.match(serviceCenter, /FORBIDDEN_TRUCK_FIELDS = \['customerId', 'customerName', 'containerBookingId', 'loadingPlanId'/)
assert.match(serviceCenter, /bookingLevel\(payload\)/)
assert.match(serviceCenter, /bookingLevel\(appointment\)/)
assert.doesNotMatch(serviceCenter, /containerBookingId:\s*linkContainer|loadingPlanId:\s*linkContainer/)
assert.match(serviceCenter, /carrierId: id\(carrier\.id\), carrierName: carrier\.name/)
assert.doesNotMatch(serviceCenter, /customerId:|customerName:/)
assert.doesNotMatch(serviceCenter, /链路承运商-/)
assert.doesNotMatch(serviceCenter, /customerName: `服务中心链路客户-/)
const schedules = source('./phase3-port-schedules.mjs')
assert.match(schedules, /ensureOceanBooking\(context, suffix, master, flow\?\.oceanBookingId\)/)
assert.match(schedules, /const containerBookingId = id\(flow\?\.containerBookingId\) \|\| null/)
assert.doesNotMatch(schedules, /\['COMPLETED', 'LOADED'\]\.includes\(flow\?\.finalStatus\)/)
assert.match(schedules, /allocatedContainerBookingIds: \(detail\.containers \|\| \[\]\)/)
assert.match(schedules, /ocean\.allocatedContainerBookingIds\?\.includes\(containerBookingId\)/)
assert.match(schedules, /if \(containerBookingId && !knownAllocation\)/)
assert.match(schedules, /outbound-dispatches/)
assert.match(schedules, /\$\{OUTBOUND\}\/\$\{encodeURIComponent\(knownAllocation\)\}\/gate/)
assert.match(schedules, /item\?\.code !== 'SAILING_SCHEDULE'/)
assert.doesNotMatch(schedules, /\/check-gate\?|find\?\.\(\(item\) => item\?\.code === 'SAILING_SCHEDULE'/)
})
test('default acceptance order finalizes LOADED before customs reads fulfillment slices', () => {
const entry = source('../admin-test-env-acceptance.mjs')
assertOrder(entry, [
"'phase3-loading'",
"'phase3-loading-model-bridge'",
"'phase3-pda-convergence'",
"'phase3-evidence-gate'",
"'phase3-loading-finalize'",
"'phase3-customs'",
])
})
test('container overview always follows phase3 loading in default and custom module selections', () => {
const reordered = normalizeModuleOrder(['phase3-container-overview', 'orders', 'phase3-loading', 'finance'])
assert.deepEqual(reordered, ['merchant-qualification-fixture', 'orders', 'inbound-measurement',
'phase3-container-loading', 'phase3-loading', 'phase3-container-overview', 'finance'])
assert.deepEqual(parseArguments(['--modules', 'phase3-container-overview,phase3-loading']).modules,
['merchant-qualification-fixture', 'orders', 'inbound-measurement', 'phase3-container-loading',
'phase3-loading', 'phase3-container-overview'])
const entry = source('../admin-test-env-acceptance.mjs')
assert.match(entry, /const DEFAULT_MODULES = normalizeModuleOrder\(CONFIGURED_MODULES\)/)
assert.match(entry, /const modules = normalizeModuleOrder\(/)
assert.match(entry, /return \{[\s\S]{0,500}modules,/)
})
test('overview does not fall back to a container fixture when loading evidence was requested', () => {
const overview = source('./phase3-container-overview.mjs')
assert.match(overview, /loadingRequested[\s\S]*\? context\.createdResources\?\.phase3Loading/)
assert.match(overview, /source=\$\{loadingRequested \? 'phase3Loading-required'/)
})
test('phase3 loading provisions its own marker-isolated WAIT_SPLIT fixture', async () => {
const run = new AcceptanceRun({ gateway: 'http://127.0.0.1', modules: [], username: '', password: '', output: '' })
const entry = source('../admin-test-env-acceptance.mjs')
assert.match(entry, /module === 'phase3-loading'/)
assert.match(entry, /preparePhase3LoadingFixture\(\)/)
assert.match(entry, /orders-phase3-loading-fixture/)
assert.match(entry, /inbound-measurement-phase3-loading-fixture/)
assert.match(entry, /marker: originalMarker/)
assert.match(entry, /sourceMarker: fixtureMarker/)
assert.match(entry, /phase3Loading: loadingFixture,\s*\n\s*phase3LoadingIncremental: incrementalFixture,\s*\n\s*inboundMeasurement: loadingFixture/)
assert.equal(run.marker.includes('-LOAD'), false)
})
test('container fixture isolates order idempotency while publishing the run marker', () => {
const entry = source('../admin-test-env-acceptance.mjs')
assert.match(entry, /const fixtureMarker = `\$\{originalMarker\}-CAB`/)
assert.match(entry, /marker: originalMarker,\s*\n\s*sourceMarker: fixtureMarker/)
assert.match(entry, /fixture\.finalStatus !== 'APPROVED'/)
assert.match(entry, /禁止回退使用 WAIT_PRICING 订单/)
})
test('measurement fixture polls asynchronous completion before exporting loading identity', () => {
const measurement = source('./inbound-measurement.mjs')
assert.match(measurement, /waitForCompletedMeasurement/)
assert.match(measurement, /expectedStatuses\.includes\(measurement\?\.processStatus\)/)
assert.match(measurement, /completeDropshipPalletization/)
assert.match(measurement, /setTimeout\(resolve, 1_000\)/)
})
test('customs and finalize consume the same loading inbound identity', () => {
const loadingFixture = { marker: 'RUN-1', tenantId: 10001, orderId: 101, inboundId: 201 }
const loading = {
marker: 'RUN-1', tenantId: 10001, inboundId: 201,
containerBookingId: 301, loadingPlanId: 401, finalStatus: 'COMPLETED',
}
const selected = selectCustomsFlow({
orchestrationFixtures: {
phase3Loading: loadingFixture,
inboundMeasurement: loadingFixture,
},
phase3Loading: loading,
})
assert.equal(selected.fixture.marker, loading.marker)
assert.equal(selected.fixture.inboundId, loading.inboundId)
assert.equal(selected.container.inboundId, loading.inboundId)
const finalized = buildFinalizeResource(loading, {
summary: { processStatus: 'LOADED', version: 6 },
}, { http: 200, body: { code: '200' } })
assert.equal(finalized.marker, loading.marker)
assert.equal(finalized.inboundId, loading.inboundId)
assert.equal(finalized.finalStatus, 'LOADED')
})
test('loading evidence acceptance covers idempotency, reversible missing-type gate and scope isolation', () => {
const loading = source('./phase3-loading.mjs')
assertOrder(loading, [
'装柜证据 task 上传同键同载荷重放',
'装柜证据 task 上传同键异载荷返回冲突',
'装柜证据作废旧版本返回冲突',
'临时作废唯一 PHOTO_STEP_4 证据',
'已作废 task 证据重复作废返回冲突',
'已作废 task 证据异参重试仍返回冲突',
'已作废证据使用新请求重复作废返回冲突',
'作废唯一证据类型后不提前占用异步完成命令',
'补传恢复 PHOTO_STEP_4 有效证据',
'客户身份无权访问平台装柜证据',
'错误仓库作用域不可读取本轮 task 装柜证据',
'错误 taskId 不可读取本轮装柜证据',
])
assert.match(loading, /missingTypeVoidedEvidenceId/)
assert.match(loading, /replacementEvidenceFileId/)
assert.match(source('./phase3-loading-finalize.mjs'), /waitForCompletion\(context, taskDetailPath/)
})
test('loaded boundary acceptance proves evidence and transport immutability with zero drift', () => {
const finalize = source('./phase3-loading-finalize.mjs')
assertOrder(finalize, [
'LOADED 后使用新键重复 complete 返回冲突',
'LOADED complete 同键异参返回冲突',
'LOADED 后新增装柜证据被拒绝',
'LOADED 后作废装柜证据被拒绝',
'LOADED 后运输信息修改被拒绝',
'回查 LOADED 高风险边界后的入库快照',
'回查 LOADED 高风险边界后的证据快照',
'回查 LOADED 高风险边界后的计划快照',
'LOADED 后拒绝与幂等请求保持状态、版本、证据、计划和审计零漂移',
])
assert.match(finalize, /evidenceSignature\(afterEvidence\) === immutableEvidenceSignature/)
assert.match(finalize, /afterHistorySignature === historySignature/)
assert.match(finalize, /afterPlanSignature === immutablePlanSignature/)
assert.match(finalize, /warehouseId: id\(fixture\.warehouseId\)/)
assert.doesNotMatch(finalize, /warehouseId: Number\(fixture\.warehouseId\)/)
})
test('finalize publishes a current-marker producer correlation for cross-region verification', () => {
const finalize = source('./phase3-loading-finalize.mjs')
assert.match(finalize, /const producer = \{/)
assert.match(finalize, /createdResources\.phase3LoadingCompletedProducer = producer/)
assert.match(finalize, /marker: context\.context\?\.phase4ProducerMatrixMarker \|\| context\.marker/)
assert.match(finalize, /eventType: 'FIRST_LEG_LOADING_PLAN_COMPLETED'/)
assert.match(finalize, /eventKey: `FIRSTLEG_LOADING_COMPLETED:\$\{loadingPlanId\}:\$\{tenantId\}:2`/)
assert.match(finalize, /sourceStatus: 'LOADED'/)
assert.match(finalize, /PRODUCED_IN_COMPLETE_TRANSACTION_PENDING_BRIDGE_CONSUMER_HTTP/)
})