2026-08-25 00:24:43 +08:00
import assert from 'node:assert/strict'
2026-10-20 10:35:52 +08:00
import test from 'node:test'
import { readFileSync } from 'node:fs'
import { mkdtemp , readFile , rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join , resolve , basename } from 'node:path'
import { DISPATCH_BASE as BASE , dispatchEvidenceContext } from './grouped-dispatch-acceptance.mjs'
import { run , selectDispatchRouteFixture , dispatchRouteMatches , dispatchBusinessSnapshot , verifyDispatchRouteLifecycle } from './phase4-overseas-dispatch.mjs'
2026-08-25 00:24:43 +08:00
2026-10-20 10:35:52 +08:00
const clone = value => structuredClone ( value )
const rows = () => [ 1 , 2 , 3 ]. map ( n => ({ selection : { sourceType : 'INBOUND_PLAN' , planPalletId : `2 ${ n } ` , returnReceiptId : null , relabelNo : null , warehousePalletId : `3 ${ n } ` , sourceVersion : ` ${ n } ` . repeat ( 64 ) },
sourceKey : `PLAN:2 ${ n } ` , sourceVersion : ` ${ n } ` . repeat ( 64 ), customerId : '55' , warehouseId : '44' , inboundCode : `IN- ${ n } ` , palletNo : `P- ${ n } ` ,
destination : { key : `FBA:A ${ n === 3 ? 1 : n } ` , type : 'FBA' , warehouseCode : `A ${ n === 3 ? 1 : n } ` }, availablePackages : n + 1 , appointmentBatches : [], blockReasons : [] }))
// All state here is offline test data. This server rejects missing versions and preserves original-key results.
function server ( fault = '' , initial = false ) {
const candidates = rows (), drafts = new Map (), orders = new Map (), ledger = new Map (), calls = [], checks = []
let serial = 0 , containerId = 60 , itemId = 70
const counts = o => ({ itemCount : o . items . length , palletCount : o . items . length , packageCount : o . items . reduce (( n , i ) => n + i . scheduledPackages , 0 ), pickedPackageCount : 0 , shippedPackageCount : 0 })
const item = c => ({ dispatchItemId : String ( ++ itemId ), sourceType : c . selection . sourceType , sourceKey : c . sourceKey , sourceVersion : c . sourceVersion , customerId : c . customerId ,
planPalletId : c . selection . planPalletId , warehousePalletId : c . selection . warehousePalletId , inboundCode : c . inboundCode , destination : clone ( c . destination ), scheduledPackages : c . availablePackages , pickedPackages : 0 , remove : { allowed : true } })
const syncRoute = o => { o . keys = [... new Set ([...( fault === 'append-only-route' ? o . keys || [] : []), ... o . items . map ( i => i . destination . key )])] }
const create = cargo => { const o = { dispatchNo : `DS- ${ orders . size + 1 } ` , recordVersion : initial && orders . size === 0 ? 0 : 1 , items : cargo . map ( s => item ( candidates . find ( c => c . selection . planPalletId === s . planPalletId ))) }; syncRoute ( o ); orders . set ( o . dispatchNo , o ); return o }
const command = ( o , action ) => ({ dispatchNo : o . dispatchNo , action , recordVersion : o . recordVersion , status : 'ACTIVE' , stage : 'BEFORE_LOADING' , counts : counts ( o ), replayed : false })
const view = o => ({ dispatchNo : o . dispatchNo , status : 'ACTIVE' , stage : 'BEFORE_LOADING' , recordVersion : o . recordVersion , counts : counts ( o ),
dispatchInfo : { route : [... new Set ( o . items . map ( i => i . destination . warehouseCode ))]. join ( ',' ) },
orderDetails : o . items . map ( i => ({ sourceKey : i . sourceKey , customerId : i . customerId , inboundCode : i . inboundCode , destinationFbaWarehouse : i . destination . warehouseCode })) })
const stops = o => [{ index : 0 , name : 'Actual Origin' , address : 'Warehouse 44' , destinationKey : 'ORIGIN:44' }, ... o . keys . map (( key , n ) => ({ index : n + 1 , name : key , destinationKey : key }))]
const response = ( data , code = '200' ) => ({ http : 200 , body : { code , data : clone ( data ) }, requestId : 'OFFLINE-REQ' , traceId : 'OFFLINE-TRACE' })
const context = { marker : 'CURRENT' , createdResources : { phase4OverseasPda : { marker : 'CURRENT' , finalStatus : 'COMPLETED' , customerId : '55' , overseasWarehouseId : '44' , palletIds : [ '31' , '32' , '33' ] } },
checkpoint : async () => {}, // Explicit offline double; full-run test below removes it and verifies actual disk writes.
assert : ( _module , label , _response , passed ) => checks . push ({ label , passed }),
async request ( method , raw , body ) {
calls . push ({ method , path : raw , body : clone ( body ) })
const path = raw . split ( '?' )[ 0 ], params = new URLSearchParams ( raw . split ( '?' )[ 1 ]), write = method !== 'GET'
const key = ` ${ method } : ${ path } : ${ body ? . idempotencyKey } `
if ( write ) {
assert . ok ( body ? . idempotencyKey , raw )
if ( ledger . has ( key )) {
const old = ledger . get ( key ); assert . equal ( JSON . stringify ( body ), old . body , 'replay must use original body and version' )
if ( fault === 'replay-row-drift' && path . endsWith ( '/items' )) orders . get ( 'DS-1' ). items [ 0 ]. dispatchItemId = '9999'
const data = clone ( old . data ); if ( data ? . action || data ? . vehicles ) data . replayed = true ; return response ( data )
2026-10-03 06:26:33 +08:00
}
}
2026-10-20 10:35:52 +08:00
const remember = data => { ledger . set ( key , { body : JSON . stringify ( body ), data : clone ( data ) }); return response ( data ) }
const draft = drafts . get ( body ? . draftToken )
if ( method === 'POST' && path === ` ${ BASE } /drafts` ) { const d = { draftToken : `SERVER- ${ ++ serial } ` , recordVersion : 0 , warehouseId : body . warehouseId , status : 'ACTIVE' , sources : [], containers : [] }; drafts . set ( d . draftToken , d ); return remember ( d ) }
if ( method === 'GET' && path === ` ${ BASE } /drafts` ) return response ([... drafts . values ()]. filter ( d => d . status === 'ACTIVE' ))
if ( path === ` ${ BASE } /candidates` ) {
assert . equal ( params . get ( 'warehouseId' ), '44' ); assert . equal ( params . get ( 'sourceStage' ), 'RECEIVED' )
const claimed = new Set ([... orders . values ()]. flatMap ( o => o . items . map ( i => i . sourceKey )))
for ( const d of drafts . values ()) if ( d . status === 'ACTIVE' && d . draftToken !== params . get ( 'draftToken' )) for ( const c of d . sources ) claimed . add ( c . sourceKey )
return response ({ candidates : candidates . filter ( c => ! claimed . has ( c . sourceKey )), complete : true , snapshotAt : '2026-10-19' })
2026-10-03 06:26:33 +08:00
}
2026-10-20 10:35:52 +08:00
if ( path === ` ${ BASE } /drafts/containers` ) { assert . equal ( body . recordVersion , draft . recordVersion ); draft . containers . push ({ draftContainerId : String ( ++ containerId ), sequenceNo : draft . containers . length + 1 , capacity : body . capacity , sourceKeys : [] }); draft . recordVersion ++ ; return remember ( draft ) }
const deleteContainer = path . match ( /\/drafts\/([^/]+)\/containers\/(\d+)$/ )
if ( deleteContainer ) { const d = drafts . get ( deleteContainer [ 1 ]); assert . equal ( body . recordVersion , d . recordVersion ); const c = d . containers . find ( c => c . draftContainerId === deleteContainer [ 2 ]); if ( c . sourceKeys . length ) return response ( null , 'A0409' ); d . containers = d . containers . filter ( c => c . draftContainerId !== deleteContainer [ 2 ]); d . recordVersion ++ ; return remember ( d ) }
if ( path === ` ${ BASE } /draft-locks/lock` || path === ` ${ BASE } /draft-locks/unlock` ) {
assert . equal ( body . recordVersion , draft . recordVersion )
const c = candidates . find ( c => c . selection . planPalletId === body . source . planPalletId ), container = draft . containers . find ( g => g . draftContainerId === body . draftContainerId )
assert . deepEqual ( body . source , c . selection )
if ( path . endsWith ( '/lock' )) {
if ([... orders . values ()]. some ( o => o . items . some ( i => i . sourceKey === c . sourceKey ))) return response ( null , 'A0409' )
draft . sources . push ( clone ( c )); container . sourceKeys . push ( c . sourceKey )
} else { draft . sources = draft . sources . filter ( s => s . sourceKey !== c . sourceKey ); container . sourceKeys = container . sourceKeys . filter ( k => k !== c . sourceKey ) }
draft . recordVersion ++ ; return remember ( draft )
}
if ( path === ` ${ BASE } /draft-locks/release` ) { assert . equal ( body . recordVersion , draft . recordVersion ); assert . equal ( draft . sources . length , 0 ); draft . status = 'RELEASED' ; draft . recordVersion ++ ; return remember ( draft ) }
if ( path === ` ${ BASE } /drafts/snapshot` ) { assert . equal ( body . recordVersion , draft . recordVersion ); assert . ok ( Array . isArray ( body . planIds )); assert . ok ( Array . isArray ( body . containers )); draft . containers = clone ( body . containers ); draft . recordVersion ++ ; return remember ( draft ) }
if ( path === ` ${ BASE } /drafts/pre-arrange` ) {
assert . equal ( body . recordVersion , draft . recordVersion )
if ( ! body . groups . every ( g => JSON . stringify ( g . dispatch . cargo . map ( s => `PLAN: ${ s . planPalletId } ` ). sort ()) === JSON . stringify ([... draft . containers . find ( c => c . draftContainerId === g . draftContainerId ). sourceKeys ]. sort ()))) return response ( null , 'A0409' )
const vehicles = body . groups . map ( g => ({ draftContainerId : g . draftContainerId , dispatch : command ( create ( g . dispatch . cargo ), 'CREATE' ) })); draft . status = 'CONVERTED'
return remember ({ draftToken : draft . draftToken , vehicles , replayed : false })
}
if ( path === BASE && method === 'POST' ) { assert . equal ( body . vehicle , null ); assert . equal ( body . driver , null ); return remember ( command ( create ( body . cargo ), 'CREATE' )) }
if ( path === BASE && method === 'GET' ) return response ({ records : [... orders . values ()]. filter ( o => o . dispatchNo === params . get ( 'dispatchNo' )). map ( o => ({ ... command ( o , 'READ' ), warehouseId : '44' })) })
const match = path . slice ( BASE . length ). match ( /^\/([^/]+)(?:\/(items|route)(?:\/([^/]+)\/remove)?)?$/ ), o = orders . get ( match ? .[ 1 ]); assert . ok ( o , ` ${ method } ${ raw } ` )
if ( write ) {
assert . equal ( body . recordVersion , o . recordVersion , 'explicit server version, including zero' )
if ( match [ 2 ] === 'route' ) { assert . equal ( method , 'PUT' ); assert . deepEqual ([... body . destinationKeys ]. sort (), [... o . keys ]. sort ()); o . keys = clone ( body . destinationKeys ); o . recordVersion ++ ; return remember ( command ( o , 'UPDATE_ROUTE' )) }
if ( match [ 3 ]) {
if ( ! o . items . some ( i => i . dispatchItemId === match [ 3 ])) { if ( fault === 'rejected-remove-mutates' ) o . recordVersion ++ ; return response ( null , 'A0404' ) }
o . items = o . items . filter ( i => i . dispatchItemId !== match [ 3 ]); o . recordVersion ++ ; syncRoute ( o ); return remember ( command ( o , 'REMOVE_CARGO' ))
}
assert . equal ( method , 'POST' ); assert . equal ( match [ 2 ], 'items' )
if ( new Set ( body . cargo . map ( s => s . planPalletId )). size !== body . cargo . length ) return response ( null , 'A0400' )
o . items . push (... body . cargo . map ( s => item ( candidates . find ( c => c . selection . planPalletId === s . planPalletId )))); o . recordVersion ++ ; syncRoute ( o )
if ( fault === 'unknown-add' ) return { http : 0 , body : null }
if ( fault === 'route-drift' ) o . keys = [ 'FBA:FOREIGN' ]
return remember ( command ( o , 'ADD_CARGO' ))
}
if ( match [ 2 ] === 'items' ) return response ({ dispatchNo : o . dispatchNo , recordVersion : o . recordVersion , status : 'ACTIVE' , stage : 'BEFORE_LOADING' , items : o . items })
if ( match [ 2 ] === 'route' ) return response ({ dispatchNo : o . dispatchNo , recordVersion : o . recordVersion , stops : stops ( o ), edit : { allowed : true } })
return response ( view ( o ))
2026-10-03 06:26:33 +08:00
} }
2026-10-20 10:35:52 +08:00
if ( initial ) { create ( candidates . slice ( 0 , 2 ). map ( c => c . selection )); context . createdResources . phase4OverseasDispatch = {} }
return { context , candidates , calls , checks , orders , bundle : () => { const o = orders . get ( 'DS-1' ); return { data : view ( o ), warehouseId : '44' , items : { items : clone ( o . items ) }, route : { stops : stops ( o ) } } } }
2026-10-03 06:26:33 +08:00
}
2026-10-20 10:35:52 +08:00
test ( 'native route fixture uses real physical pallets and requires two destinations plus a repeated destination' , () => {
const c = rows (), selected = selectDispatchRouteFixture ( c )
assert . deepEqual ( selected . selected . map ( c => c . sourceKey ), [ 'PLAN:21' , 'PLAN:22' ]); assert . equal ( selected . duplicate . sourceKey , 'PLAN:23' )
assert . equal ( selectDispatchRouteFixture ( c . slice ( 0 , 2 )), null )
c [ 2 ]. selection . warehousePalletId = c [ 0 ]. selection . warehousePalletId ; assert . equal ( selectDispatchRouteFixture ( c ), null )
2026-10-03 06:26:33 +08:00
})
2026-10-20 10:35:52 +08:00
test ( 'native route proof rejects drift in actual identity, physical scope, counts and destination keys' , () => {
const h = server ( '' , true ), original = h . bundle (), expected = h . candidates . slice ( 0 , 2 )
assert . equal ( dispatchRouteMatches ( original , expected ), true )
for ( const mutate of [ b => { b . warehouseId = '999' }, b => { b . items . items [ 0 ]. customerId = '999' }, b => { b . items . items [ 0 ]. warehousePalletId = '999' }, b => { b . items . items [ 0 ]. sourceVersion = '0' . repeat ( 64 ) }, b => { b . data . counts . packageCount ++ }, b => { b . route . stops [ 1 ]. destinationKey = 'FBA:OTHER' }, b => { b . data . dispatchInfo . route = 'A1,A1,A2' }, b => { b . items . items [ 1 ]. dispatchItemId = b . items . items [ 0 ]. dispatchItemId }, b => { b . data . orderDetails [ 0 ]. customerId = '999' }, b => { b . data . orderDetails [ 0 ]. sourceKey = 'PLAN:999' }, b => { b . data . orderDetails [ 0 ]. destinationFbaWarehouse = 'OTHER' }]) { const bad = clone ( original ); mutate ( bad ); assert . equal ( dispatchRouteMatches ( bad , expected ), false ) }
const changed = clone ( original ); changed . items . items [ 0 ]. dispatchItemId = '9999' ; assert . notEqual ( dispatchBusinessSnapshot ( changed ), dispatchBusinessSnapshot ( original ))
})
test ( 'C52 uses native versioned cargo commands, immutable replay, actual readback and route keys' , async () => {
const h = server ( '' , true ), current = await verifyDispatchRouteLifecycle ( h . context , { dispatchNo : 'DS-1' , candidates : h . candidates . slice ( 0 , 2 ), duplicate : h . candidates [ 2 ] })
assert . equal ( h . context . createdResources . phase4OverseasDispatch . routeLifecycle . status , 'VERIFIED' ); assert . ok ( h . checks . every ( c => c . passed ))
assert . equal ( current . data . counts . packageCount , 5 ); assert . equal ( current . data . recordVersion , 6 )
const firstAdd = h . calls . find ( c => c . method === 'POST' && c . path . endsWith ( '/items' )); assert . equal ( firstAdd . body . recordVersion , 0 )
assert . deepEqual ( h . calls . filter ( c => c . method === 'PUT' ). map ( c => c . body . destinationKeys ), [[ 'FBA:A2' , 'FBA:A1' ], [ 'FBA:A2' , 'FBA:A1' ], [ 'FBA:A1' , 'FBA:A2' ], [ 'FBA:A1' , 'FBA:A2' ]])
assert . equal ( h . calls . some ( c => c . method === 'DELETE' ), false )
})
test ( 'C52 rejects replay row drift, rejection side effects, wrong route and unknown writes without cleanup' , async () => {
for ( const fault of [ 'replay-row-drift' , 'rejected-remove-mutates' , 'route-drift' , 'unknown-add' ]) {
const h = server ( fault , true )
await assert . rejects ( verifyDispatchRouteLifecycle ( h . context , { dispatchNo : 'DS-1' , candidates : h . candidates . slice ( 0 , 2 ), duplicate : h . candidates [ 2 ] }))
assert . notEqual ( h . context . createdResources . phase4OverseasDispatch . routeLifecycle . status , 'VERIFIED' )
assert . equal ( h . calls . some ( c => c . method === 'PUT' || c . method === 'DELETE' ), false )
if ( fault === 'unknown-add' ) { assert . equal ( h . context . createdResources . dispatchWriteBoundary . outcomeUnknown , true ); assert . equal ( h . calls . filter ( c => c . method !== 'GET' ). length , 1 ); assert . equal ( h . orders . get ( 'DS-1' ). items . length , 3 ) }
2026-10-03 06:26:33 +08:00
}
})
2026-10-20 10:35:52 +08:00
test ( 'phase4 missing current marker or real multi-destination fixture is explicit NOT_RUN before writes' , async () => {
const h = server (); h . context . createdResources . phase4OverseasPda . marker = 'OLD' ; assert . equal (( await run ( h . context )). status , 'NOT_RUN' ); assert . equal ( h . calls . length , 0 )
const other = server (); other . candidates . pop (); assert . equal (( await run ( other . context )). status , 'NOT_RUN' ); assert . equal ( other . calls . filter ( c => c . method !== 'GET' ). length , 0 )
const ambiguous = server (); ambiguous . candidates [ 2 ]. appointmentBatches = [{}, {}]; assert . equal (( await run ( ambiguous . context )). status , 'NOT_RUN' ); assert . equal ( ambiguous . calls . filter ( c => c . method !== 'GET' ). length , 0 )
2026-08-25 00:24:43 +08:00
})
2026-10-20 10:35:52 +08:00
test ( 'complete offline phase4 hands off the actual native Detail and current source keys at BEFORE_LOADING' , async () => {
const directory = await mkdtemp ( join ( tmpdir (), 'phase4-dispatch-contract-' ))
try {
const h = server (); delete h . context . checkpoint ; h . context . options = { output : join ( directory , 'report.json' ) }
h . context . createdResources . dispatchWriteBoundary = { marker : 'CURRENT' , stopped : false , password : 'PRIVATE_PASSWORD' , token : 'PRIVATE_LOGIN_TOKEN' }
const originalRequest = h . context . request
h . context . request = async ( method , path , body ) => {
if ( method !== 'GET' ) {
const evidence = JSON . parse ( await readFile ( join ( directory , 'phase4-dispatch-checkpoints' , 'CURRENT.json' ), 'utf8' ))
const intents = [...( evidence . phase4OverseasDispatch . requests || []), ...( evidence . groupedDispatch ? . requests || []), ...( evidence . phase4OverseasDispatch . routeLifecycle ? . requests || [])]
assert . ok ( intents . some ( r => r . status === 'SENDING' && r . method === method && r . path === path && JSON . stringify ( r . body ) === JSON . stringify ( body )), 'original write must already exist on disk' )
}
return originalRequest ( method , path , body )
}
const proof = await run ( h . context )
assert . equal ( proof . status , 'VERIFIED' ); assert . equal ( proof . finalStatus , 'ACTIVE' ); assert . equal ( proof . stage , 'BEFORE_LOADING' ); assert . ok ( h . checks . every ( c => c . passed ))
assert . deepEqual ( proof . sourceKeys , [ 'PLAN:21' , 'PLAN:22' ]); assert . deepEqual ( proof . destinationWarehouseCodes , [ 'A1' , 'A2' ]); assert . equal ( proof . warehouseId , '44' )
const actual = await h . context . request ( 'GET' , ` ${ BASE } / ${ proof . dispatchNo } ` ); assert . deepEqual ( proof . data , actual . body . data )
assert . equal ( Object . hasOwn ( proof . data , 'items' ), false ); assert . equal ( Object . hasOwn ( proof . data , 'route' ), false ); assert . equal ( Object . hasOwn ( proof , 'dispatchId' ), false )
assert . equal ( Object . hasOwn ( proof , 'loadingMeasurements' ), false ); assert . equal ( proof . loadingEvidenceStatus , 'NOT_RUN_MISSING_ACTUAL_MEASUREMENTS' )
assert . equal ( h . calls . some ( c => / \ / review | \ / pick | \ / scan | \ / loading | \ / depart | \ / transfer - delivery / . test ( c . path )), false )
assert . equal ( h . calls . some ( c => c . path === ` ${ BASE } /pre-arrange` || c . body ? . containersJson ), false )
const text = await readFile ( proof . checkpointPath , 'utf8' ), journal = JSON . parse ( text )
assert . equal ( journal . phase4OverseasDispatch . status , 'VERIFIED' ); assert . ok ( journal . phase4OverseasDispatch . draftToken )
assert . equal ( text . includes ( 'PRIVATE_PASSWORD' ), false ); assert . equal ( text . includes ( 'PRIVATE_LOGIN_TOKEN' ), false )
await assert . rejects ( dispatchEvidenceContext ({ marker : 'CURRENT' , options : h . context . options , createdResources : { phase4OverseasDispatch : {} } }), /已存在本轮排车检查点/ )
const stale = server ( 'append-only-route' ); await assert . rejects ( run ( stale . context ), /最后实物移出后真实数量归零/ )
assert . equal ( stale . context . createdResources . phase4OverseasDispatch . status , 'UNPROVEN' ); assert . equal ( stale . orders . size , 1 )
assert . equal ( stale . calls . some ( c => c . path . endsWith ( '/drafts' )), false )
} finally {
assert . ok ( basename ( directory ). startsWith ( 'phase4-dispatch-contract-' )); assert . equal ( resolve ( directory , '..' ), resolve ( tmpdir ()))
await rm ( directory , { recursive : true , force : true })
2026-09-14 13:46:32 +08:00
}
2026-10-20 10:35:52 +08:00
})
test ( 'relabel creation cannot prematurely settle fulfillment or fabricate loading measurements' , () => {
const source = readFileSync ( new URL ( './phase4-overseas-dispatch.mjs' , import . meta . url ), 'utf8' )
assert . equal ( source . includes ( 'settleRelabelFulfillment' ), false )
assert . equal ( source . includes ( 'vehicleAppointmentStatus' ), false )
assert . equal ( source . includes ( 'loadingMeasurements:' ), false )
assert . ok ( source . includes ( 'fixture.targetPalletIds' )); assert . ok ( source . includes ( 'READY_FOR_OUTBOUND' ))
2026-09-14 13:46:32 +08:00
})