2026-10-03 06:26:33 +08:00
import test from 'node:test'
import assert from 'node:assert/strict'
import { prepareFleetTrip , confirmFleetLoading , controlledPalletMeasurements , finishFleetTrip } from './fleet-trip-fulfillment.mjs'
2026-10-20 10:35:52 +08:00
const D = '/api/platform/v1/last-leg/dispatches/DP-A'
const P = '/api/pda/v1/last-leg/dispatches/DP-A'
const F = '/api/platform/v1/last-leg/fleet/trips/TR-A'
const stops = [{ name : '真实起运仓' }, { name : 'ONT8' , destinationKey : 'FBA:ONT8' }]
const VID = '9007199254740993' , DRIVER = '9007199254740995' , PALLET = '9007199254740997'
const ok = data => ({ http : 200 , body : { code : '200' , data : structuredClone ( data ) } })
const conflict = message => ({ http : 200 , body : { code : 'A0409' , message } })
const evidence = ( field , value ) => ({ field , value , actual : true , sourceType : 'TEST_PRODUCER_MEASUREMENT' , sourceBusinessNo : 'RECEIPT-A' , observedAt : '2026-10-18T10:00:00Z' })
const pallet = () => ({ warehousePalletId : PALLET , palletNo : 'PLT-A' , scheduledPackages : 3 , pickedPackages : 3 ,
palletSpaces : null , weightKg : '187.25' , volumeCbm : '1.375' , deliveryIndex : 1 , missingFields : [ 'palletSpaces' ],
evidence : [ evidence ( 'weightKg' , '187.25' ), evidence ( 'volumeCbm' , '1.375' )] })
const receipt = () => ({ environment : 'TEST' , marker : 'TEST-NATIVE' , dispatchNo : 'DP-A' , pallets : [
{ warehousePalletId : PALLET , palletSpaces : 2 , deliveryIndex : 1 , evidence : [ evidence ( 'palletSpaces' , 2 )] },
] })
2026-10-03 06:26:33 +08:00
2026-10-20 10:35:52 +08:00
test ( 'real Jackson long counts accept safe decimal strings without changing pallet IDs' , () => {
const expected = controlledPalletMeasurements ([ pallet ()], stops , receipt (). pallets )
for ( const counts of [{ scheduledPackages : '3' , pickedPackages : '3' }, { scheduledPackages : '3' , pickedPackages : 3 }]) {
const actual = controlledPalletMeasurements ([{ ... pallet (), ... counts }], stops , receipt (). pallets )
assert . deepEqual ( actual , expected )
assert . equal ( actual [ 0 ]. warehousePalletId , PALLET )
2026-10-03 06:26:33 +08:00
}
2026-10-20 10:35:52 +08:00
for ( const value of [ null , undefined , '' , '03' , '3.0' , '3e0' , true , - 1 , '9007199254740993' , 9007199254740992 ]) {
assert . throws (() => controlledPalletMeasurements ([{ ... pallet (), scheduledPackages : value }], stops , receipt (). pallets ), /箱数/ )
assert . throws (() => controlledPalletMeasurements ([{ ... pallet (), pickedPackages : value }], stops , receipt (). pallets ), /箱数/ )
}
assert . throws (() => controlledPalletMeasurements ([{ ... pallet (), pickedPackages : '2' }], stops , receipt (). pallets ), /核平/ )
2026-10-03 06:26:33 +08:00
})
2026-10-20 10:35:52 +08:00
function server ( fault = '' ) {
const calls = [], assertions = [], saved = new Map (), loadingActors = new Map ()
const detail = { dispatchNo : 'DP-A' , status : 'ACTIVE' , stage : 'BEFORE_LOADING' , reviewState : 'DRAFT' , recordVersion : '9007199254740998' ,
counts : { packageCount : 3 , pickedPackageCount : 0 , shippedPackageCount : 0 }, reviews : {},
dispatchInfo : { estimatedArrivalAt : '2026-10-21T10:00:00Z' , fbaAppointments : [{ appointmentNo : 'FBA-ORIGINAL' }] }, vehicleInfo : {} }
const trip = { tripNo : 'TR-A' , status : 'PLANNED' , warehouseId : '92' , recordVersion : 4 , tripPurpose : 'DELIVERY' , stops }
let driverVersion = 0 , uploadComplete = false , driverReviewed = false , nextPlate = '' , checkpointError = false , nextTrip , nextDetail
let mutatedItems = false , mutatedLoading = false
const claims = new Map ([[ 'VEHICLE' , 'TR-A' ], [ 'DRIVER' , 'TR-A' ]])
const bump = () => detail . recordVersion = String ( BigInt ( detail . recordVersion ) + 1 n )
const result = ( action , body , extra = {}) => ({ dispatchNo : 'DP-A' , status : detail . status , stage : detail . stage , recordVersion : detail . recordVersion ,
action , idempotencyKey : body . idempotencyKey , result : extra , replayed : false , counts : structuredClone ( detail . counts ) })
const remember = ( action , body , extra ) => { const r = result ( action , body , extra ); saved . set ( body . idempotencyKey , { body : structuredClone ( body ), result : structuredClone ( r ) }); return ok ( r ) }
2026-10-03 06:26:33 +08:00
const context = {
2026-10-20 10:35:52 +08:00
marker : 'TEST-NATIVE' , createdResources : {},
checkpoint : async () => { if ( checkpointError ) throw new Error ( 'checkpoint failed' ) },
2026-10-03 06:26:33 +08:00
assert ( _module , label , _response , passed ) { assertions . push ({ label , passed }) },
2026-10-20 10:35:52 +08:00
apiSuccess ( _module , _label , response ) { return response ? . body ? . code === '200' ? response . body . data : null },
2026-10-03 06:26:33 +08:00
async requestMultipart ( method , path , file ) {
2026-10-20 10:35:52 +08:00
calls . push ({ method , path , file : { name : file . name , size : file . content . byteLength } })
assert . equal ( file . type , 'image/png' ); assert . match ( file . name , /TEST-LICENSE/ )
uploadComplete = true ; driverVersion ++
return ok ({ id : DRIVER , licenseFileId : '9007199254740999' , recordVersion : driverVersion })
2026-10-03 06:26:33 +08:00
},
2026-10-20 10:35:52 +08:00
async request ( method , path , body , token ) {
calls . push ({ method , path , body : structuredClone ( body ), actor : token ? 'PDA' : 'PLATFORM' })
if ( fault === 'unknown-edit' && method === 'PUT' && path === D ) throw new Error ( 'transport result unknown' )
if ( path . endsWith ( '/master-data/vehicles' )) {
assert . equal ( body . status , 'IN_SERVICE' ); assert . match ( body . remark , /TEST/ ); nextPlate = body . plateNumber . toUpperCase ()
return ok ({ id : VID })
}
if ( path . endsWith ( '/fleet/drivers' )) return ok ({ id : DRIVER , recordVersion : 0 })
if ( path . endsWith ( `/drivers/ ${ DRIVER } /review` )) {
assert . ok ( uploadComplete ); assert . equal ( body . recordVersion , driverVersion ); driverReviewed = true
return ok ({ id : DRIVER , reviewStatus : 'APPROVED' , recordVersion : ++ driverVersion })
}
if ( path . startsWith ( '/api/platform/v1/last-leg/fleet/trips?' )) {
const rows = fault === 'missing-trip' ? [] : [ trip , ...( nextTrip ? [ nextTrip ] : [])]
if ( fault === 'duplicate-trip' ) rows . push ({ ... trip , tripNo : 'TR-B' })
const wrong = { 'wrong-plate' : { vehiclePlate : ` ${ trip . vehiclePlate } -OTHER` },
'wrong-vehicle' : { vehicleId : 'OTHER-VEHICLE' }, 'wrong-driver' : { driverId : 'OTHER-DRIVER' },
'wrong-warehouse' : { warehouseId : 'OTHER-WAREHOUSE' } }[ fault ]
if ( wrong ) rows [ 0 ] = { ... rows [ 0 ], ... wrong }
return ok ({ records : rows , page : 1 , size : 200 , total : rows . length , pages : 1 })
}
if ( method === 'GET' && path === D ) return ok ( detail )
if ( method === 'GET' && path === ` ${ D . replace ( 'DP-A' , 'DP-NEXT' ) } ` ) return ok ( nextDetail )
if ( method === 'GET' && path === ` ${ D . replace ( 'DP-A' , 'DP-NEXT' ) } /items` ) return ok ({ dispatchNo : 'DP-NEXT' , recordVersion : nextDetail . recordVersion , items : [] })
if ( method === 'POST' && path === D . replace ( '/DP-A' , '' )) {
assert . deepEqual ( body . cargo , []); assert . deepEqual ( body . batchAllocations , [])
assert . equal ( body . vehicle . vehicleId , VID ); assert . equal ( body . driver . driverId , DRIVER )
assert . equal ( claims . size , 0 )
if ( fault === 'unknown-next-create' ) throw new Error ( 'next create unknown' )
nextTrip = { ... trip , tripNo : 'TR-NEXT' , status : 'PLANNED' , recordVersion : 0 , vehicleRelease : null , driverRelease : null }
nextDetail = { dispatchNo : 'DP-NEXT' , status : 'ACTIVE' , stage : 'BEFORE_LOADING' , recordVersion : 0 ,
counts : { itemCount : 0 , palletCount : 0 , packageCount : 0 , pickedPackageCount : 0 , shippedPackageCount : 0 } }
if ( fault === 'new-empty-has-cargo' ) nextDetail . counts . itemCount = 1
claims . set ( 'VEHICLE' , 'TR-NEXT' ); claims . set ( 'DRIVER' , 'TR-NEXT' )
return ok ({ ... nextDetail , result : { dispatchNo : 'DP-NEXT' } })
}
if ( method === 'POST' && path === ` ${ D . replace ( 'DP-A' , 'DP-NEXT' ) } /cancel` ) {
assert . equal ( body . recordVersion , nextDetail . recordVersion )
nextDetail . status = 'CANCELLED' ; nextDetail . recordVersion ++ ; claims . clear ()
return ok ( nextDetail )
}
if ( method === 'GET' && path === ` ${ D } /items` ) return ok ({ dispatchNo : 'DP-A' , recordVersion : detail . recordVersion , items : [{ warehousePalletId : PALLET , pickedPackages : detail . counts . pickedPackageCount , customerId : mutatedItems ? 'OTHER-CUSTOMER' : 'CUSTOMER-A' }] })
if ( method === 'GET' && [ D , P ]. some ( prefix => path === ` ${ prefix } /loading` )) {
return ok ({ dispatchNo : 'DP-A' , status : detail . status , stage : detail . stage , recordVersion : detail . recordVersion , pickingReconciled : detail . counts . pickedPackageCount === 3 ,
manifestHash : mutatedLoading ? 'CHANGED-MANIFEST' : 'REAL-MANIFEST' , loadingComplete : { allowed : detail . counts . pickedPackageCount === 3 , blockReason : '拣货未核平' },
pallets : [ pallet ()], missingFields : [ 'PLT-A:palletSpaces' ] })
}
if ( method === 'PUT' && path === D ) {
if ( detail . stage !== 'BEFORE_LOADING' ) {
if ( fault === 'edit-mutates-on-rejection' ) detail . counts . pickedPackageCount --
return conflict ( '仅装车前可编辑,请显式退回;已经扫码不得改派' )
2026-10-03 06:26:33 +08:00
}
2026-10-20 10:35:52 +08:00
assert . ok ( driverReviewed ); assert . equal ( body . recordVersion , detail . recordVersion )
assert . equal ( body . vehicle . mode , 'MASTER' ); assert . equal ( body . vehicle . vehicleId , VID )
assert . equal ( body . driver . driverId , DRIVER ); assert . ok ( body . driver . evidence . expectedTaskEndAt )
detail . vehicleInfo = { vehicle : { mode : 'MASTER' , vehicleId : VID , plate : nextPlate , carrierName : 'TEST自有车' , capacity : { palletSpaces : 30 , maxLoadKg : 18000 , maxVolumeCbm : 90 } },
driver : { mode : 'MASTER' , driverId : DRIVER , name : 'TEST司机' , phone : '19900000000' } }
Object . assign ( trip , { vehicleId : VID , vehiclePlate : nextPlate , driverId : DRIVER })
bump (); return remember ( 'EDIT' , body )
2026-10-03 06:26:33 +08:00
}
2026-10-20 10:35:52 +08:00
if ( method === 'POST' && path === ` ${ D } /return-to-review` ) {
assert . equal ( detail . counts . pickedPackageCount , 0 ); assert . equal ( body . recordVersion , detail . recordVersion )
detail . stage = 'BEFORE_LOADING' ; detail . reviewState = 'DRAFT' ; bump (); return remember ( 'RETURN_TO_REVIEW' , body )
2026-10-03 06:26:33 +08:00
}
2026-10-20 10:35:52 +08:00
if ( method === 'POST' && [ 'submit-review' , 'dispatch-review' , 'loading-review' ]. some ( action => path === ` ${ D } / ${ action } ` )) {
assert . equal ( body . recordVersion , detail . recordVersion ); assert . match ( body . idempotencyKey , /^[a-f0-9]{64}$/ )
if ( path . endsWith ( '/submit-review' )) { detail . reviewState = 'DISPATCH_PENDING' ; detail . reviews . dispatchReview = { status : 'PENDING' } }
else if ( path . endsWith ( '/dispatch-review' )) { assert . equal ( detail . reviewState , 'DISPATCH_PENDING' ); detail . reviewState = 'LOADING_PENDING' ; detail . reviews . dispatchReview . status = 'APPROVED' }
else { assert . equal ( detail . reviewState , 'LOADING_PENDING' ); detail . reviewState = 'APPROVED' ; detail . stage = 'LOADING' ; detail . reviews . loadingReview = { status : 'APPROVED' } }
bump (); return remember ( 'REVIEW' , body )
2026-10-03 06:26:33 +08:00
}
2026-10-20 10:35:52 +08:00
if ( method === 'POST' && [ D , P ]. some ( prefix => path === ` ${ prefix } /loading-complete` )) {
if ( saved . has ( body . idempotencyKey )) {
assert . deepEqual ( body , saved . get ( body . idempotencyKey ). body )
if ( loadingActors . get ( body . idempotencyKey ) !== ( token ? 'PDA' : 'PLATFORM' )) {
if ( fault === 'cross-actor-unknown' ) throw new Error ( 'cross actor result unknown' )
if ( fault === 'cross-actor-mutates-main' ) bump ()
if ( fault === 'cross-actor-mutates-items' ) mutatedItems = true
if ( fault === 'cross-actor-mutates-loading' ) mutatedLoading = true
if ( fault !== 'cross-actor-accepted' ) return { http : 200 , body : { code : 'A0403' ,
message : fault === 'cross-actor-generic-forbidden' ? '没有装车权限' : '该请求标识不属于当前操作员' } }
}
const replay = { ... saved . get ( body . idempotencyKey ). result , replayed : true }
if ( fault === 'replay-new-version' ) replay . recordVersion = String ( BigInt ( replay . recordVersion ) + 1 n )
return ok ( replay )
}
assert . equal ( detail . stage , 'LOADING' ); assert . equal ( detail . counts . pickedPackageCount , 3 )
assert . equal ( body . recordVersion , detail . recordVersion ); assert . equal ( body . manifestHash , 'REAL-MANIFEST' )
assert . deepEqual ( body . measurements , [{ warehousePalletId : PALLET , palletSpaces : 2 , weightKg : '187.25' , volumeCbm : '1.375' , deliveryIndex : 1 }])
loadingActors . set ( body . idempotencyKey , token ? 'PDA' : 'PLATFORM' )
2026-10-21 14:30:41 +08:00
detail . stage = 'DELIVERING' ; bump (); return remember ( 'LOADING_COMPLETE' , body , { confirmedLoad : { manifestHash : body . manifestHash , pallets : body . measurements } })
2026-10-03 06:26:33 +08:00
}
2026-10-20 10:35:52 +08:00
if ( method === 'GET' && path . startsWith ( ` ${ F } /resource-occupancy?` )) {
const query = new URL ( `https://unused.test ${ path } ` ). searchParams , kind = query . get ( 'kind' )
return ok ([{ resourceId : kind === 'VEHICLE' ? VID : DRIVER , occupiedTripNo : claims . get ( kind ) ?? null , available : true }])
2026-10-03 06:26:33 +08:00
}
2026-10-20 10:35:52 +08:00
if ( method === 'GET' && path === ` ${ F } /completion` ) return ok ({ tripNo : 'TR-A' , recordVersion : trip . recordVersion , canComplete : fault !== 'incomplete' , outcomes : [{ deliveryNo : 'DL-A' , packages : 3 }] })
if ( method === 'GET' && path === F ) return ok ({ trip , dispatches : fault === 'wrong-binding' ? [{ dispatchNo : 'DP-OTHER' }] : [{ dispatchNo : 'DP-A' }] })
if ( method === 'GET' && path === F . replace ( 'TR-A' , 'TR-B' )) return ok ({ trip : { ... trip , tripNo : 'TR-B' }, dispatches : [{ dispatchNo : 'DP-A' }] })
if ( method === 'GET' && path === F . replace ( 'TR-A' , 'TR-NEXT' )) return ok ({ trip : nextTrip , dispatches : [{ dispatchNo : 'DP-NEXT' }] })
if ( method === 'POST' && [ ` ${ F } /complete` , ` ${ F } /resources/release` ]. includes ( path )) {
if ( saved . has ( body . idempotencyKey )) {
assert . deepEqual ( body , saved . get ( body . idempotencyKey ))
if ( fault === 'old-release-clears-new-claim' && nextTrip && path . endsWith ( '/resources/release' )) claims . delete ( body . resourceKind )
2026-10-03 06:26:33 +08:00
return ok ({ trip })
}
2026-10-20 10:35:52 +08:00
assert . equal ( body . recordVersion , trip . recordVersion ); saved . set ( body . idempotencyKey , structuredClone ( body )); trip . recordVersion ++
if ( path . endsWith ( '/complete' )) {
trip . status = 'COMPLETED' ; trip . completedAt = '2026-10-19T00:00:00Z'
if ( fault === 'completion-auto-releases' ) { claims . clear (); trip . vehicleRelease = { actualEndAt : trip . completedAt } }
} else {
claims . delete ( body . resourceKind )
trip [ body . resourceKind === 'VEHICLE' ? 'vehicleRelease' : 'driverRelease' ] = { actualEndAt : body . actualEndAt , endLocation : body . endLocation }
if ( fault === 'vehicle-frees-driver' && body . resourceKind === 'VEHICLE' ) claims . delete ( 'DRIVER' )
2026-10-03 06:26:33 +08:00
}
return ok ({ trip })
}
2026-10-20 10:35:52 +08:00
assert . fail ( `retired or unexpected request ${ method } ${ path } ` )
2026-10-03 06:26:33 +08:00
},
}
2026-10-20 10:35:52 +08:00
return { context , detail , trip , calls , assertions , claims , pick () { detail . counts . pickedPackageCount = 3 ; bump () }, failCheckpoint () { checkpointError = true } }
2026-10-03 06:26:33 +08:00
}
2026-10-20 10:35:52 +08:00
const prepare = s => prepareFleetTrip ( s . context , 'native' , 'DP-A' , '92' )
function jacksonCountWire ( s ) {
const names = new Set ([ 'itemCount' , 'palletCount' , 'packageCount' , 'pickedPackageCount' , 'shippedPackageCount' , 'scheduledPackages' , 'pickedPackages' ])
const serialize = value => Array . isArray ( value ) ? value . map ( serialize )
: value && typeof value === 'object' ? Object . fromEntries ( Object . entries ( value ). map (([ name , nested ]) =>
[ name , names . has ( name ) && typeof nested === 'number' ? String ( nested ) : serialize ( nested )])) : value
const original = s . context . request
s . context . request = async (... args ) => serialize ( await original (... args ))
return s
}
test ( 'real string-zero counts permit preparation, PDA loading replay and safe empty-dispatch closure' , async () => {
const s = jacksonCountWire ( server ()), proof = await prepare ( s )
assert . equal ( proof . preparation , 'VERIFIED' )
s . pick ()
await confirmFleetLoading ( s . context , 'native' , proof , 'PDA-IN-MEMORY-ONLY' , receipt ())
s . trip . status = 'IN_TRANSIT'
const closure = await finishFleetTrip ( s . context , 'native' , proof )
assert . equal ( closure . status , 'CARGO_COMPLETED_AND_RESOURCES_RELEASED' )
assert . equal ( closure . nextDispatch . finalStatus , 'CANCELLED' )
assert . ok ( s . assertions . every ( row => row . passed ))
2026-10-03 06:26:33 +08:00
})
2026-10-20 10:35:52 +08:00
test ( 'missing or malformed zero counts cannot create resources before preparation' , async () => {
for ( const value of [ undefined , null , false , '' , '00' , '0.0' , '0e0' , '9007199254740993' ]) {
const s = server (); s . detail . counts . pickedPackageCount = value
await assert . rejects ( prepare ( s ), /箱数/ )
assert . deepEqual ( s . calls . map ( row => row . method ), [ 'GET' ])
}
2026-10-03 06:26:33 +08:00
})
2026-10-20 10:35:52 +08:00
test ( 'native preparation edits existing master then submits both reviews, discovers the real sole automatic Fleet record' , async () => {
const s = server (), proof = await prepare ( s )
assert . equal ( proof . tripNo , 'TR-A' ); assert . equal ( proof . preparation , 'VERIFIED' ); assert . equal ( proof . vehicleId , VID )
assert . equal ( proof . vehicle . plate , proof . marker . toUpperCase ()); assert . equal ( proof . driver . phone , '19900000000' )
assert . equal ( proof . recordVersion , '9007199254741002' ); assert . equal ( proof . fleetRecordVersion , 4 )
const writes = s . calls . filter ( c => [ 'POST' , 'PUT' ]. includes ( c . method ))
assert . deepEqual ( writes . slice ( - 4 ). map ( c => c . path ), [ D , ` ${ D } /submit-review` , ` ${ D } /dispatch-review` , ` ${ D } /loading-review` ])
assert . ok ( writes . every ( c => ! /\/trips(?:$|\/.*\/(?:assignment|plan|bind|review))/ . test ( c . path )))
assert . equal ( s . detail . dispatchInfo . fbaAppointments [ 0 ]. appointmentNo , 'FBA-ORIGINAL' )
assert . ok ( s . assertions . every ( a => a . passed ))
2026-10-03 06:26:33 +08:00
})
2026-10-20 10:35:52 +08:00
test ( 'zero-scan LOADING must explicitly return to review before editing and pass a new review round' , async () => {
const s = server (); s . detail . stage = 'LOADING' ; s . detail . reviewState = 'APPROVED'
const proof = await prepare ( s )
const returnIndex = s . calls . findIndex ( c => c . path . endsWith ( '/return-to-review' ))
assert . ok ( returnIndex >= 0 && returnIndex < s . calls . findIndex ( c => c . method === 'PUT' ))
assert . equal ( proof . returnRequest . recordVersion , '9007199254740998' ); assert . equal ( proof . preparation , 'VERIFIED' )
})
test ( 'authoritative uppercase vehicle plate discovers both the prepared and subsequent native dispatch without changing marker' , async () => {
const s = server (), request = s . context . request , marker = 'FT-1d529b3e-d15'
s . context . request = async (... args ) => {
// 固定本轮随机标识以精确复现真实HTTP大小写回执;首次GET仍在任何车辆写入之前。
if ( args [ 0 ] === 'GET' && args [ 1 ] === D && ! s . context . createdResources . fleetTrips [ 0 ]. vehicleId )
s . context . createdResources . fleetTrips [ 0 ]. marker = marker
return request (... args )
}
const proof = await prepare ( s )
assert . equal ( proof . marker , marker ); assert . equal ( proof . vehicle . plate , 'FT-1D529B3E-D15' )
assert . equal ( proof . tripNo , 'TR-A' )
s . trip . status = 'IN_TRANSIT'
const closure = await finishFleetTrip ( s . context , 'native' , proof )
assert . equal ( closure . nextDispatch . marker , marker ); assert . equal ( closure . nextDispatch . vehicle , undefined )
assert . equal ( closure . nextDispatch . tripNo , 'TR-NEXT' ); assert . equal ( closure . nextDispatch . finalStatus , 'CANCELLED' )
const queries = s . calls . filter ( c => c . path . startsWith ( '/api/platform/v1/last-leg/fleet/trips?' ))
assert . equal ( queries . length , 2 )
for ( const call of queries ) assert . equal ( new URL ( `https://unused.test ${ call . path } ` ). searchParams . get ( 'keyword' ), 'FT-1D529B3E-D15' )
})
for ( const fault of [ 'missing-trip' , 'duplicate-trip' , 'wrong-binding' , 'wrong-plate' , 'wrong-vehicle' , 'wrong-driver' , 'wrong-warehouse' ]) test ( `automatic Fleet identity fails closed: ${ fault } ` , async () => {
const s = server ( fault ); await assert . rejects ( prepare ( s ), /唯一|绑定/ )
assert . equal ( s . context . createdResources . fleetTrips [ 0 ]. preparation , 'CHECKING' )
})
test ( 'started picking cannot be silently prepared, reassigned or returned' , async () => {
const s = server (); s . detail . stage = 'LOADING' ; s . detail . counts . pickedPackageCount = 1
await assert . rejects ( prepare ( s ), /零扫码/ )
assert . equal ( s . calls . length , 1 )
})
test ( 'unknown write result retains the exact command, does not retry or generate another Fleet record' , async () => {
const s = server ( 'unknown-edit' ); await assert . rejects ( prepare ( s ), /unknown/ )
const last = s . context . createdResources . fleetTripCommands . at ( - 1 )
assert . equal ( last . status , 'UNKNOWN' ); assert . equal ( last . path , D )
assert . equal ( s . calls . filter ( c => c . method === 'PUT' ). length , 1 )
})
test ( 'checkpoint failure sends no business write' , async () => {
const s = server (); s . failCheckpoint (); await assert . rejects ( prepare ( s ), /checkpoint/ )
assert . deepEqual ( s . calls . map ( c => c . method ), [ 'GET' ])
})
test ( 'actual loading uses authoritative decimal measurements and a proven producer pallet-space measurement' , () => {
assert . deepEqual ( controlledPalletMeasurements ([ pallet ()], stops , receipt (). pallets ), [
{ warehousePalletId : PALLET , palletSpaces : 2 , weightKg : '187.25' , volumeCbm : '1.375' , deliveryIndex : 1 },
])
})
for ( const field of [ 'palletSpaces' , 'weightKg' , 'volumeCbm' ]) test ( `missing actual ${ field } blocks; billing values cannot substitute` , () => {
const row = pallet (), r = receipt (); row [ field ] = null ; delete r . pallets [ 0 ][ field ]
row . grossWeightKg = 999 ; row . billingWeight = 999
assert . throws (() => controlledPalletMeasurements ([ row ], stops , r . pallets ), /实际测量缺少/ )
})
test ( 'suggested measurements, duplicate physical pallets and unpicked cargo are rejected' , () => {
const row = pallet (); row . evidence [ 0 ]. actual = false
assert . throws (() => controlledPalletMeasurements ([ row ], stops , receipt (). pallets ), /真实来源/ )
assert . throws (() => controlledPalletMeasurements ([ pallet (), pallet ()], stops , receipt (). pallets ), /重复/ )
assert . throws (() => controlledPalletMeasurements ([{ ... pallet (), pickedPackages : 2 }], stops , receipt (). pallets ), /核平/ )
assert . throws (() => controlledPalletMeasurements ([ pallet ()], [{ name : 'Origin' }], receipt (). pallets ), /卸货站/ )
})
test ( 'extra or inconsistent producer measurements cannot alter another pallet or authoritative actual weight' , () => {
const r = receipt (); r . pallets [ 0 ]. weightKg = 100
assert . throws (() => controlledPalletMeasurements ([ pallet ()], stops , r . pallets ), /不一致/ )
assert . throws (() => controlledPalletMeasurements ([ pallet ()], stops , [... receipt (). pallets , { warehousePalletId : 'OTHER' }]), /本车以外/ )
})
test ( 'PDA original-actor replay restores loading while another platform actor is rejected without mutation' , async () => {
const s = server (), proof = await prepare ( s ); s . pick ()
const beforeCount = s . detail . counts . pickedPackageCount
const v = await confirmFleetLoading ( s . context , 'native' , proof , 'PDA-IN-MEMORY-ONLY' , receipt ())
assert . equal ( v , '9007199254741004' ); assert . equal ( proof . reassignment . status , 'REJECTED_WITHOUT_MUTATION' )
2026-10-21 14:30:41 +08:00
assert . equal ( s . detail . counts . pickedPackageCount , beforeCount ); assert . equal ( s . detail . stage , 'DELIVERING' )
2026-10-20 10:35:52 +08:00
const writes = s . calls . filter ( c => c . path . endsWith ( '/loading-complete' ))
assert . equal ( writes . length , 3 )
assert . deepEqual ( writes . map ( c => c . body ), [ writes [ 0 ]. body , writes [ 0 ]. body , writes [ 0 ]. body ])
assert . deepEqual ( writes . map ( c => [ c . path , c . actor ]), [[ ` ${ P } /loading-complete` , 'PDA' ], [ ` ${ P } /loading-complete` , 'PDA' ], [ ` ${ D } /loading-complete` , 'PLATFORM' ]])
assert . equal ( proof . loadingCommand . replay . replayed , true ); assert . equal ( proof . loadingCommand . platformRejection . code , 'A0403' )
assert . equal ( proof . loadingCommand . platformPositiveLoading , 'PENDING_SEPARATE_SCENARIO' )
assert . deepEqual ( proof . loadingCommand . beforeReplay , proof . loadingCommand . afterReplay )
assert . equal ( proof . loadingCommand . platformReplay , undefined )
assert . equal ( writes [ 0 ]. body . measurements [ 0 ]. weightKg , '187.25' )
assert . ok ( s . calls . every ( c => ! /\/fleet\/trips\/.*\/(?:assignment|loading-review|loading)$/ . test ( c . path )))
const count = s . calls . length
await assert . rejects ( confirmFleetLoading ( s . context , 'native' , proof , 'PDA' , receipt ()), /已开始/ )
assert . equal ( s . calls . length , count )
})
test ( 'platform positive loading remains executable in its own scenario with original-actor replay' , async () => {
const s = server (), proof = await prepare ( s ); s . pick ()
await confirmFleetLoading ( s . context , 'native' , proof , undefined , receipt ())
const writes = s . calls . filter ( c => c . path . endsWith ( '/loading-complete' ))
assert . deepEqual ( writes . map ( c => [ c . path , c . actor ]), [[ ` ${ D } /loading-complete` , 'PLATFORM' ], [ ` ${ D } /loading-complete` , 'PLATFORM' ]])
assert . equal ( proof . loadingCommand . replay . replayed , true )
assert . equal ( proof . loadingCommand . platformPositiveLoading , 'VERIFIED' ); assert . equal ( proof . loadingCommand . platformRejection , undefined )
2026-10-21 14:30:41 +08:00
assert . equal ( s . detail . stage , 'DELIVERING' )
2026-10-20 10:35:52 +08:00
})
for ( const fault of [ 'cross-actor-accepted' , 'cross-actor-generic-forbidden' , 'cross-actor-mutates-main' , 'cross-actor-mutates-items' , 'cross-actor-mutates-loading' , 'cross-actor-unknown' ]) test ( `cross-actor loading cannot claim isolation: ${ fault } ` , async () => {
const s = server ( fault ), proof = await prepare ( s ); s . pick ()
await assert . rejects ( confirmFleetLoading ( s . context , 'native' , proof , 'PDA' , receipt ()), /失败|unknown/ )
assert . equal ( proof . loadingCommand . platformPositiveLoading , undefined )
assert . equal ( s . calls . filter ( c => c . path . endsWith ( '/loading-complete' )). length , 3 )
if ( fault === 'cross-actor-unknown' ) assert . equal ( s . context . createdResources . fleetTripCommands . at ( - 1 ). status , 'UNKNOWN' )
const count = s . calls . length
await assert . rejects ( confirmFleetLoading ( s . context , 'native' , proof , 'PDA' , receipt ()), /已开始/ )
assert . equal ( s . calls . length , count )
})
for ( const fault of [ 'edit-mutates-on-rejection' , 'replay-new-version' ]) test ( `loading verification rejects native drift: ${ fault } ` , async () => {
const s = server ( fault ), proof = await prepare ( s ); s . pick ()
await assert . rejects ( confirmFleetLoading ( s . context , 'native' , proof , 'PDA' , receipt ()), /失败/ )
if ( fault === 'edit-mutates-on-rejection' ) assert . equal ( s . calls . some ( c => c . path . endsWith ( '/loading-complete' )), false )
})
test ( 'missing actual producer measurement blocks before any edit rejection or loading write' , async () => {
const s = server (), proof = await prepare ( s ); s . pick (); const count = s . calls . length
await assert . rejects ( confirmFleetLoading ( s . context , 'native' , proof , 'PDA' ), /palletSpaces/ )
assert . equal ( s . calls . length , count + 1 ); assert . equal ( s . calls . at ( - 1 ). method , 'GET' )
})
test ( 'wrong-run producer receipt and unreconciled picking block loading without mutation' , async () => {
const s = server (), proof = await prepare ( s )
await assert . rejects ( confirmFleetLoading ( s . context , 'native' , proof , 'PDA' , receipt ()), /未就绪/ )
s . pick (); await assert . rejects ( confirmFleetLoading ( s . context , 'native' , proof , 'PDA' , { ... receipt (), marker : 'OTHER-RUN' }), /不属于本轮/ )
assert . equal ( s . calls . some ( c => c . path . endsWith ( '/loading-complete' )), false )
})
test ( 'closure keeps cargo completion, vehicle ending and driver ending distinct using real occupancy owners' , async () => {
const s = server (), proof = await prepare ( s ); s . trip . status = 'IN_TRANSIT'
const state = await finishFleetTrip ( s . context , 'native' , proof )
assert . equal ( state . status , 'CARGO_COMPLETED_AND_RESOURCES_RELEASED' ); assert . equal ( s . claims . size , 0 )
assert . equal ( state . subsequentTripClaimProtection , 'VERIFIED_NATIVE_EMPTY_DISPATCH' )
assert . equal ( state . nextDispatch . tripNo , 'TR-NEXT' ); assert . equal ( state . nextDispatch . finalStatus , 'CANCELLED' )
assert . deepEqual ( state . occupancy . map ( c => [ c . kind , c . rows [ 0 ]. occupiedTripNo ]), [
[ 'VEHICLE' , 'TR-A' ], [ 'DRIVER' , 'TR-A' ], [ 'VEHICLE' , null ], [ 'DRIVER' , 'TR-A' ], [ 'VEHICLE' , null ], [ 'DRIVER' , null ],
[ 'VEHICLE' , 'TR-NEXT' ], [ 'DRIVER' , 'TR-NEXT' ], [ 'VEHICLE' , 'TR-NEXT' ], [ 'DRIVER' , 'TR-NEXT' ], [ 'VEHICLE' , null ], [ 'DRIVER' , null ],
])
assert . equal ( s . calls . filter ( c => c . path . endsWith ( '/resources/release' )). length , 4 )
assert . ok ( s . calls . every ( c => ! ( c . method === 'POST' && /\/fleet\/trips$|\/assignment$/ . test ( c . path ))))
await assert . rejects ( finishFleetTrip ( s . context , 'native' , proof ), /已开始/ )
})
for ( const fault of [ 'incomplete' , 'completion-auto-releases' , 'vehicle-frees-driver' , 'old-release-clears-new-claim' , 'new-empty-has-cargo' , 'unknown-next-create' ]) test ( `closure does not claim success on independent-resource failure: ${ fault } ` , async () => {
const s = server ( fault ), proof = await prepare ( s ); s . trip . status = 'IN_TRANSIT'
await assert . rejects ( finishFleetTrip ( s . context , 'native' , proof ), /失败|unknown/ )
assert . equal ( proof . closure . status , 'CHECKING' )
if ([ 'new-empty-has-cargo' , 'unknown-next-create' ]. includes ( fault )) assert . equal ( s . calls . some ( c => c . path . endsWith ( '/DP-NEXT/cancel' )), false )
2026-10-03 06:26:33 +08:00
})