Files
baiyue-firstleg/scripts/fba-remaining-nodes-acceptance-transfer.test.mjs

236 lines
24 KiB
JavaScript

import {test} from 'node:test'
import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
import os from 'node:os'
import crypto from 'node:crypto'
import {validateSource,validateDestination,relabelPrerequisite,TRANSFER,CUSTOMER} from './fba-remaining-nodes-acceptance-transfer.mjs'
import {LA,NY,prepareOriginalTransferPlan,prepareTransferStage,runPreparedTransferStage,verifyTransferMilestone,redactTransferMessage} from './fba-remaining-nodes-acceptance-transfer.mjs'
const detail={transferNo:TRANSFER,status:'APPROVED',packageCount:1,allocations:[],operatingWarehouseId:'w',sourceOrders:[{inboundOrderId:'o'}]}
const balance={id:'b',customerId:CUSTOMER,inboundOrderId:'o',overseasWarehouseId:'w'}
const position={id:'p',balanceId:'b',customerId:CUSTOMER,palletId:'t',availablePackages:1,frozenPackages:0,pickedPackages:0}
test('original approved one-carton source passes exact ownership',()=>assert.deepEqual(validateSource(detail,balance,position),{inventoryPositionId:'p',palletId:'t',packageCount:1}))
test('other source, customer, warehouse and unavailable or occupied cartons stop before commands',()=>{
for(const b of [{...balance,inboundOrderId:'other'},{...balance,customerId:'other'},{...balance,overseasWarehouseId:'other'}])assert.throws(()=>validateSource(detail,b,position))
for(const p of [{...position,availablePackages:0},{...position,frozenPackages:1},{...position,holdPackages:1},{...position,allocatedPackages:1},{...position,pickedPackages:1},{...position,palletId:null}])assert.throws(()=>validateSource(detail,balance,p))
assert.throws(()=>validateSource({...detail,allocations:[{}]},balance,position))
})
test('missing destination or FBA master cannot silently become a receiving overseas resource',()=>{
assert.throws(()=>validateDestination(detail))
assert.throws(()=>validateDestination(detail,{warehouseType:'FBA',status:'ACTIVE'},{},{}))
const d={destinationWarehouseCode:'ABE4'},w={id:'ny',warehouseType:'OVERSEAS',status:'ACTIVE'}
const p={warehouseId:'ny',locationId:'loc',status:'IDLE',customerId:'0',fbaWarehouseCode:'ABE4'}
const l={id:'loc',warehouseId:'ny'}
assert.equal(validateDestination(d,w,p,l),true)
assert.throws(()=>validateDestination(d,w,{...p,warehouseId:'la'},l))
assert.throws(()=>validateDestination(d,w,{...p,status:'OCCUPIED'},l))
})
test('available original-source cartons cannot waive original ticket relabel binding',()=>{
const d={needRelabel:true,lastlegOrderId:'13',relabelBindings:[{quantity:1}]}
assert.equal(relabelPrerequisite(d,{relabelStatus:'PENDING_BINDING'}).ready,false)
assert.equal(relabelPrerequisite({...d,relabelNo:'original'},{relabelApplications:[{relabelNo:'other',lastlegOrderId:'13'}],relabelStatus:'COMPLETED'}).ready,false)
assert.equal(relabelPrerequisite({...d,relabelNo:'original'},{relabelApplications:[{relabelNo:'original',lastlegOrderId:'13'}],relabelStatus:'PENDING_PROCESSING'}).ready,false)
assert.equal(relabelPrerequisite({...d,relabelNo:'original'},{relabelApplications:[{relabelNo:'original',lastlegOrderId:'other'}],relabelStatus:'COMPLETED'}).ready,false)
assert.equal(relabelPrerequisite({...d,relabelNo:'original'},{relabelApplications:[{relabelNo:'original',lastlegOrderId:'13'}],relabelStatus:'COMPLETED'}).ready,true)
})
function linkedFixture(status='WAIT_RELABEL'){
const transfer={transferNo:TRANSFER,needRelabel:true,packageCount:1,operatingWarehouseId:LA,destinationWarehouseCode:'ABE4',
relabelNo:'RL-SYNTHETIC-ONLY',relabelApplicationId:'app',lastlegOrderId:'tail',status,
relabelBindings:[{newSku:'NEW'}],allocations:[{inventoryPositionId:'selected-old',palletId:'source-pallet',packageCount:1,status:'RESERVED',skuCode:'OLD'}]}
const linked={application:{id:'app',relabelNo:transfer.relabelNo,lastlegOrderId:'tail',customerOrderNo:TRANSFER,overseasWarehouseId:LA},items:[{cartonMark:'BOX-1'}]}
const task={relabelNo:transfer.relabelNo,customerOrderNo:TRANSFER,taskStatus:'PROCESSING',version:2,
expectedPackages:1,relabelledPackages:1,skippedPackages:0,exceptionPackages:0,
items:[{itemId:'item',expectedPackages:1,cartonMark:'BOX-1',fbaCartonMark:'FBA-BOX-1',operationType:'CARTON_AND_SKU',skuItems:[{skuCode:'OLD',newSkuCode:'NEW',quantity:1}]}],
unitFacts:[{factId:'fact',itemId:'item',result:'RELABELLED',quantity:1}],
history:[{action:'PALLET_BIND',detail:JSON.stringify({round:'INITIAL',cartonMarks:['BOX-1'],targetPallets:['WORK-1']})}]}
return {transfer,linked,task,plan:prepareOriginalTransferPlan(transfer,'offline-contract')}
}
const platform={kind:'platform',appCode:'PLATFORM',tenantId:'0',authorities:['platform:overseas-warehouse:relabel-order:status']}
const release=stage=>({execute:true,environment:'TEST',transferNo:TRANSFER,customerId:CUSTOMER,stage,runId:'offline-contract'})
function temporaryJournal(){const directory=fs.mkdtempSync(path.join(os.tmpdir(),'f33-stage-'));return {directory,cleanup(){for(const file of fs.readdirSync(directory))fs.unlinkSync(path.join(directory,file));fs.rmdirSync(directory)}}}
test('unallocated original ticket remains a zero-network plan without inventing a relabel number',async()=>{
const f=linkedFixture();delete f.transfer.relabelNo;delete f.transfer.relabelApplicationId
const plan=prepareOriginalTransferPlan(f.transfer,'unallocated')
assert.equal(plan.linked,false);assert.equal(plan.networkIssued,false)
assert.throws(()=>prepareTransferStage(plan,'approve',{linked:f.linked}),/WAIT_FOR_ORIGINAL_ALLOCATION/)
assert.throws(()=>prepareTransferStage(f.plan,'review-approve',{linked:f.linked}),/UNSUPPORTED_STAGE/)
const command=prepareTransferStage(f.plan,'approve',{linked:f.linked});let calls=0
assert.equal((await runPreparedTransferStage({command,transport:async()=>{calls++}})).mode,'PLAN');assert.equal(calls,0)
})
test('different relabel parent IDs or customer-order linkage cannot enter the original pipeline',()=>{
const f=linkedFixture()
for(const patch of [{id:'other'},{lastlegOrderId:'other'},{customerOrderNo:'OTHER-TICKET'},{overseasWarehouseId:NY}]){
assert.throws(()=>prepareTransferStage(f.plan,'approve',{linked:{...f.linked,application:{...f.linked.application,...patch}}}),/EXACT_ORIGINAL_RELABEL/)
}
})
test('actual labels and approved SKU piece counts are required for one combined unit fact',()=>{
const f=linkedFixture(),context={linked:f.linked,task:f.task,itemId:'item',observedOldLabel:'BOX-1',observedNewLabel:'FBA-BOX-1',observedSkus:[{skuCode:'OLD',newSkuCode:'NEW',quantity:1}]}
const command=prepareTransferStage(f.plan,'unit',context);assert.equal(command.body.expectedVersion,2);assert.match(command.path,/\/units$/)
assert.throws(()=>prepareTransferStage(f.plan,'unit',{...context,observedOldLabel:'OTHER'}))
assert.throws(()=>prepareTransferStage(f.plan,'unit',{...context,observedSkus:[{skuCode:'OLD',newSkuCode:'NEW',quantity:2}]}))
})
test('normal completion requires current-round physical binding photos and no skipped cartons',()=>{
const f=linkedFixture(),context={linked:f.linked,task:f.task,photoFileIds:['77']}
assert.equal(prepareTransferStage(f.plan,'complete',context).body.manualCompletion,false)
assert.throws(()=>prepareTransferStage(f.plan,'complete',{...context,task:{...f.task,reviewedAt:'NEW-ROUND'}}))
assert.throws(()=>prepareTransferStage(f.plan,'complete',{...context,task:{...f.task,skippedPackages:1}}))
assert.throws(()=>prepareTransferStage(f.plan,'complete',{...context,photoFileIds:[]}))
})
test('receipt consumes the current shipped output position and keeps New York separate from ABE4',()=>{
const f=linkedFixture('OUTBOUNDED');f.transfer.allocations=[{inventoryPositionId:'new-output',palletId:'work-pallet',packageCount:1,status:'SHIPPED',skuCode:'NEW'}]
const plan=prepareOriginalTransferPlan(f.transfer,'receipt')
const receiptTarget={warehouse:{id:NY,warehouseType:'OVERSEAS',status:'ACTIVE',warehouseCode:'US-NYC-01',warehouseName:'纽约仓'},
pallet:{id:'ny-pallet',warehouseId:NY,locationId:'ny-location',palletCode:'F33-NY',status:'IDLE',customerId:'0',fbaWarehouseCode:'ABE4'},location:{id:'ny-location',warehouseId:NY,locationCode:'F33-NY-LOC'}}
const command=prepareTransferStage(plan,'receipt',{linked:f.linked,receiptTarget})
assert.equal(command.body.sourceInventoryPositionId,'new-output');assert.equal(command.body.destinationOverseasWarehouseId,NY)
assert.equal(command.body.destinationWarehouseCode,'ABE4');assert.equal(command.body.destinationWarehouseId,'193000000000000003')
assert.throws(()=>prepareTransferStage(plan,'receipt',{linked:f.linked,receiptTarget:{...receiptTarget,warehouse:{...receiptTarget.warehouse,id:LA}}}))
})
test('write intent is durable before a single transport call; only known exact original replay is allowed',async()=>{
const f=linkedFixture(),command=prepareTransferStage(f.plan,'approve',{linked:f.linked}),temp=temporaryJournal();let count=0
try{
const transport=async c=>{count++;const j=JSON.parse(fs.readFileSync(path.join(temp.directory,`${c.runId}-${c.stage}.json`)));assert.equal(count===1?j.phase:j.replay.phase,'INTENT');return {http:200,code:'200',data:{approved:true}}}
const options={command,release:release('approve'),actor:platform,transport,journalDirectory:temp.directory}
await runPreparedTransferStage(options);await assert.rejects(runPreparedTransferStage(options));await runPreparedTransferStage({...options,replay:true})
assert.equal(count,2);const journal=JSON.parse(fs.readFileSync(path.join(temp.directory,`${command.runId}-${command.stage}.json`)));assert.equal(journal.businessVerified,false)
await assert.rejects(runPreparedTransferStage({...options,replay:true}));assert.equal(count,2)
}finally{temp.cleanup()}
})
test('unknown result or explicit rejection preserves evidence and blocks retry even with the original key',async()=>{
for(const unknown of [true,false]){
const f=linkedFixture(),command=prepareTransferStage(f.plan,'approve',{linked:f.linked}),temp=temporaryJournal();let calls=0
try{
const transport=async()=>{calls++;if(unknown)throw Error('secret diagnostics must not be recorded');return {http:409,code:'A0409',requestId:'req',traceId:'trace'}}
const options={command,release:release('approve'),actor:platform,transport,journalDirectory:temp.directory}
await assert.rejects(runPreparedTransferStage(options));await assert.rejects(runPreparedTransferStage({...options,replay:true}));assert.equal(calls,1)
const raw=fs.readFileSync(path.join(temp.directory,`${command.runId}-${command.stage}.json`),'utf8');assert.doesNotMatch(raw,/secret diagnostics/)
const j=JSON.parse(raw);assert.equal(j.phase,unknown?'OUTCOME_UNKNOWN':'ACKNOWLEDGED');if(!unknown)assert.equal(j.requestId,'req')
}finally{temp.cleanup()}
}
})
test('stage release actor or prepared-payload mismatch cannot send a request',async()=>{
const f=linkedFixture(),command=prepareTransferStage(f.plan,'approve',{linked:f.linked});let calls=0
const options={command,release:release('approve'),actor:platform,journalDirectory:'not-created',transport:async()=>{calls++}}
await assert.rejects(runPreparedTransferStage({...options,release:release('receipt')}))
await assert.rejects(runPreparedTransferStage({...options,actor:{...platform,tenantId:CUSTOMER}}))
await assert.rejects(runPreparedTransferStage({...options,command:{...command,body:{status:'REJECTED'}}}))
assert.equal(calls,0)
})
test('photo upload bytes are frozen and an acknowledged no-key upload cannot be repeated',async()=>{
const f=linkedFixture(),binary=Buffer.from('synthetic offline transport fixture')
const command=prepareTransferStage(f.plan,'photo',{linked:f.linked,photo:{fileName:'fixture.png',contentType:'image/png',sizeBytes:binary.length,sha256:crypto.createHash('sha256').update(binary).digest('hex')}})
const actor={kind:'pda',appCode:'PLATFORM',tenantId:'0',membershipId:'2106727769572478980',warehouseId:LA,roles:['OVERSEAS_WAREHOUSE_PDA_OPERATOR'],authorities:['lastleg:pda:operate']}
const temp=temporaryJournal();let calls=0
try{
const options={command,release:release('photo'),actor,binary,journalDirectory:temp.directory,transport:async()=>{calls++;return {http:200,code:'200',data:{fileId:'77'}}}}
await assert.rejects(runPreparedTransferStage({...options,binary:Buffer.from('changed')}));assert.equal(calls,0)
await runPreparedTransferStage(options);await assert.rejects(runPreparedTransferStage({...options,replay:true}));assert.equal(calls,1)
}finally{temp.cleanup()}
})
test('physical binding uses selected source and current versions without client customer/warehouse scope in PDA body',()=>{
const f=linkedFixture(),context={linked:f.linked,task:f.task,source:{warehouseId:LA,positionId:'selected-old',palletId:'source-pallet',palletCode:'SOURCE',palletVersion:3,positionVersion:4},target:{warehouseId:LA,palletCode:'WORK',palletVersion:0,destinationWarehouseCode:'ABE4'},observedCartonMarks:['BOX-1']}
const c=prepareTransferStage(f.plan,'bind',context)
assert.equal(c.body.actualSource.positionId,'selected-old');assert.equal(c.body.actualSource.packages,1);assert.equal(c.body.target.expectedVersion,0)
assert.equal(c.body.customerId,undefined);assert.equal(c.body.warehouseId,undefined)
assert.throws(()=>prepareTransferStage(f.plan,'bind',{...context,target:{...context.target,warehouseId:NY}}))
})
test('one original carton stays conserved and existing HOLD and append-only facts remain untouched',()=>{
const unchanged={positionId:'protected',availablePackages:0,holdPackages:1}
const ship={id:'ship',businessType:'SHIP',businessOrderNo:TRANSFER,deltaPackages:-1,skuCode:'NEW'}
const receipt={id:'receipt',businessType:'STOCK_IN',businessOrderNo:TRANSFER,overseasWarehouseId:NY,deltaPackages:1,skuCode:'NEW'}
const base={baselineSourceTotal:4,baselineTargetTotal:0,expectedNewSku:'NEW',protectedBefore:unchanged,protectedAfter:unchanged,ledgerBefore:[{id:'old',packages:1}],ledgerAfter:[ship,receipt,{id:'old',packages:1}]}
assert.equal(verifyTransferMilestone('customer-confirm',{...base,sourceTotal:4,targetTotal:0}).verified,true)
assert.equal(verifyTransferMilestone('outbound',{...base,sourceTotal:3,targetTotal:0}).inTransitCartons,1)
assert.equal(verifyTransferMilestone('receipt',{...base,sourceTotal:3,targetTotal:1}).inTransitCartons,0)
assert.throws(()=>verifyTransferMilestone('receipt',{...base,sourceTotal:2,targetTotal:1}))
assert.throws(()=>verifyTransferMilestone('receipt',{...base,sourceTotal:3,targetTotal:1,protectedAfter:{...unchanged,holdPackages:0}}))
assert.throws(()=>verifyTransferMilestone('receipt',{...base,sourceTotal:3,targetTotal:1,ledgerAfter:[{id:'old',packages:2}]}))
assert.throws(()=>verifyTransferMilestone('receipt',{...base,sourceTotal:3,targetTotal:1,ledgerAfter:[ship,{...receipt,skuCode:'OLD'},{id:'old',packages:1}]}))
})
test('actual PDA detail shape requires unit facts before bind and allows unit without PALLET_BIND history',()=>{
const f=linkedFixture(),context={linked:f.linked,task:f.task,
source:{warehouseId:LA,positionId:'selected-old',palletId:'source-pallet',palletCode:'SOURCE',palletVersion:1,positionVersion:4},
target:{warehouseId:LA,palletCode:'WORK',palletVersion:0,destinationWarehouseCode:'ABE4'},observedCartonMarks:['BOX-1']}
assert.ok(f.plan.phases.findIndex(x=>x.stage==='unit')<f.plan.phases.findIndex(x=>x.stage==='bind'))
assert.throws(()=>prepareTransferStage(f.plan,'bind',{...context,task:{...f.task,relabelledPackages:0,unitFacts:[]}}),/COMPLETE_UNIT_FACTS/)
for(const facts of [[],[{itemId:'other',result:'RELABELLED',quantity:1}],[{itemId:'item',result:'SKIPPED',quantity:1}],[{itemId:'item',result:'RELABELLED',quantity:2}]])
assert.throws(()=>prepareTransferStage(f.plan,'bind',{...context,task:{...f.task,unitFacts:facts}}),/OWN_RELABELLED_UNIT_FACT/)
for(const expectedPackages of [undefined,null,0,NaN,2,'1'])
assert.throws(()=>prepareTransferStage(f.plan,'bind',{...context,task:{...f.task,items:f.task.items.map(i=>({...i,expectedPackages}))}}),/OWN_RELABELLED_UNIT_FACT/)
for(const quantity of [undefined,null,0,NaN,-1,'1'])
assert.throws(()=>prepareTransferStage(f.plan,'bind',{...context,task:{...f.task,unitFacts:[{itemId:'item',result:'RELABELLED',quantity}]}}),/OWN_RELABELLED_UNIT_FACT/)
assert.throws(()=>prepareTransferStage(f.plan,'bind',{...context,task:{...f.task,unitFacts:[{itemId:'item',result:'RELABELLED',quantity:-1},{itemId:'item',result:'RELABELLED',quantity:2}]}}),/OWN_RELABELLED_UNIT_FACT/)
const actual={...f.task,history:[],unitFacts:[{factId:'2106799999999999999',itemId:'item',oldLabel:'BOX-1',newLabel:'FBA-BOX-1',result:'RELABELLED',quantity:1,operatorName:'SYNTHETIC',occurredAt:'2026-10-05T00:00:00'}]}
assert.ok(prepareTransferStage(f.plan,'bind',{...context,task:actual}))
assert.ok(prepareTransferStage(f.plan,'unit',{linked:f.linked,task:{...actual,relabelledPackages:0,unitFacts:[]},itemId:'item',observedOldLabel:'BOX-1',observedNewLabel:'FBA-BOX-1',observedSkus:[{skuCode:'OLD',newSkuCode:'NEW',quantity:1}]}))
})
function originalBindRecoveryFixture(directory){
const command={stage:'bind',runId:'f33-original-20261005-r1',transferNo:TRANSFER,customerId:CUSTOMER,actor:'pda',permission:'lastleg:pda:operate',replay:'KEYED',method:'POST',path:'/pda/v1/last-leg/pallet-operations/relabel-disassemble',body:{relabelNo:'RL-WH-20261005-174913',idempotencyKey:'F33-WT-430594-f33-original-20261005-r1-bind',source:{palletCode:'DP-261002-0018',expectedVersion:1},target:{palletCode:'PLT-USLAX01-261005-G08C1U1NQMMA',expectedVersion:0},cartonMarks:['CM-ADMIN-API-20261002042009973'],actualSource:{positionId:'2105878074206765058',expectedPositionVersion:4,packages:1},reason:'F33-WT-430594-f33-original-20261005-r1-bind'},preparedHash:'bb1ccb10ebaef5f37d8f16782ae6f8f2051d31061142533a61e8ab020c1e2a0d'}
const journal={command,phase:'ACKNOWLEDGED',http:200,code:'A0409',success:false,businessVerified:false}
const rollback={verified:true,unchanged:['transfer','positions','ledger','linked','order','task']}
const fresh={runId:command.runId,transferNo:TRANSFER,relabelNo:command.body.relabelNo,originalKeyNotExecuted:true,unitFactsVerified:true,unitFactCount:1,relabelledPackages:1,stockAndLedgerUnchangedByUnit:true,sourcePalletCode:command.body.source.palletCode,targetPalletCode:command.body.target.palletCode,sourcePalletVersion:1,targetPalletVersion:0,sourcePositionId:command.body.actualSource.positionId,sourcePositionVersion:4}
const journalFile=path.join(directory,command.runId+'-bind.json'),rollbackFile=path.join(directory,'rollback.json'),freshFile=path.join(directory,'fresh.json')
const store=()=>{fs.writeFileSync(journalFile,JSON.stringify(journal));fs.writeFileSync(rollbackFile,JSON.stringify(rollback));fs.writeFileSync(freshFile,JSON.stringify(fresh))}
const sha=p=>crypto.createHash('sha256').update(fs.readFileSync(p)).digest('hex')
store()
const recovery=()=>({authorization:'F33_BIND_A0409_AFTER_UNIT',originalJournalSha256:sha(journalFile),rollbackProofFile:'rollback.json',rollbackProofSha256:sha(rollbackFile),freshProofFile:'fresh.json',freshProofSha256:sha(freshFile)})
const actor={kind:'pda',appCode:'PLATFORM',tenantId:'0',membershipId:'2106727769572478980',warehouseId:LA,roles:['OVERSEAS_WAREHOUSE_PDA_OPERATOR'],authorities:['lastleg:pda:operate']}
return {command,journal,rollback,fresh,journalFile,store,recovery,options:{command,release:{...release('bind'),runId:command.runId},actor,journalDirectory:directory}}
}
test('released exact A0409 binding recovers once into durable side journal and preserves original failure bytes',async()=>{
const temp=temporaryJournal();let calls=0
try{
const f=originalBindRecoveryFixture(temp.directory),original=fs.readFileSync(f.journalFile)
const side=path.join(temp.directory,f.command.runId+'-bind-recovery.json')
const transport=async()=>{calls++;const j=JSON.parse(fs.readFileSync(side));assert.equal(calls===1?j.phase:j.replay.phase,'INTENT');return {http:200,code:'200',data:{replayed:calls>1},message:'完成'}}
const options={...f.options,recovery:f.recovery(),transport}
await assert.rejects(runPreparedTransferStage({...options,recovery:undefined}));assert.equal(calls,0)
await runPreparedTransferStage(options);assert.deepEqual(fs.readFileSync(f.journalFile),original)
await assert.rejects(runPreparedTransferStage(options));assert.equal(calls,1)
await runPreparedTransferStage({...options,replay:true});assert.deepEqual(fs.readFileSync(f.journalFile),original)
await assert.rejects(runPreparedTransferStage({...options,replay:true}));assert.equal(calls,2)
}finally{temp.cleanup()}
})
test('recovery never releases unknown, other rejects, changed payload, incomplete proof or changed live versions',async()=>{
for(const patch of [f=>f.journal.phase='OUTCOME_UNKNOWN',f=>f.journal.code='A0500',f=>f.journal.http=409,
f=>f.journal.success=true,f=>f.rollback.unchanged=['transfer'],f=>f.fresh.sourcePositionVersion=5,
f=>f.fresh.targetPalletVersion=1,f=>f.fresh.unitFactCount=0,f=>f.fresh.originalKeyNotExecuted=false]){
const temp=temporaryJournal();let calls=0
try{const f=originalBindRecoveryFixture(temp.directory);patch(f);f.store();await assert.rejects(runPreparedTransferStage({...f.options,recovery:f.recovery(),transport:async()=>{calls++}}));assert.equal(calls,0)}finally{temp.cleanup()}
}
const temp=temporaryJournal();let calls=0
try{const f=originalBindRecoveryFixture(temp.directory),options={...f.options,transport:async()=>{calls++}}
await assert.rejects(runPreparedTransferStage({...options,recovery:{...f.recovery(),authorization:'OTHER'}}))
await assert.rejects(runPreparedTransferStage({...options,recovery:{...f.recovery(),originalJournalSha256:'0'.repeat(64)}}))
await assert.rejects(runPreparedTransferStage({...options,recovery:f.recovery(),command:{...f.command,body:{...f.command.body,reason:'NEW PAYLOAD'}}}))
assert.equal(calls,0)
}finally{temp.cleanup()}
})
test('business rejection message is retained with tokens credentials and endpoint URLs redacted',async()=>{
assert.equal(redactTransferMessage('请先完成本箱全部换标作业,再进行组托'),'请先完成本箱全部换标作业,再进行组托')
const temp=temporaryJournal()
try{const f=linkedFixture(),command=prepareTransferStage(f.plan,'approve',{linked:f.linked});await assert.rejects(runPreparedTransferStage({command,release:release('approve'),actor:platform,journalDirectory:temp.directory,transport:async()=>({http:200,code:'A0409',message:'业务拒绝 password=secret Bearer token123 https://private.example/path'})}));
const raw=fs.readFileSync(path.join(temp.directory,'offline-contract-approve.json'),'utf8');assert.match(raw,/业务拒绝/);assert.doesNotMatch(raw,/secret|token123|private\.example/)
}finally{temp.cleanup()}
})
test('combined /units SKU-only actual response needs carton scan before one final fact and physical bind',()=>{
const f=linkedFixture();const task={...f.task,version:1,relabelledPackages:0,unitFacts:[],history:[{action:'MIXED_SKU',detail:JSON.stringify({itemId:'item',round:'INITIAL',stage:'SKU'})}],
items:f.task.items.map(i=>({...i,relabelledPackages:0,cartonProcessedPackages:0,skuProcessedPackages:1}))}
const context={linked:f.linked,task,itemId:'item',observedOldLabel:'BOX-1',observedNewLabel:'FBA-BOX-1'}
const command=prepareTransferStage(f.plan,'unit-carton',context)
assert.match(command.path,/\/cartons\/scan$/);assert.equal(command.body.expectedVersion,1);assert.equal(command.body.cartonMark,'BOX-1')
assert.ok(f.plan.phases.findIndex(p=>p.stage==='unit')<f.plan.phases.findIndex(p=>p.stage==='unit-carton'))
assert.ok(f.plan.phases.findIndex(p=>p.stage==='unit-carton')<f.plan.phases.findIndex(p=>p.stage==='bind'))
for(const patch of [{skuProcessedPackages:0},{cartonProcessedPackages:1},{expectedPackages:undefined}])
assert.throws(()=>prepareTransferStage(f.plan,'unit-carton',{...context,task:{...task,items:task.items.map(i=>({...i,...patch}))}}),/OWN_SKU_ONLY_PARTIAL/)
assert.throws(()=>prepareTransferStage(f.plan,'unit-carton',{...context,task:{...task,relabelledPackages:1}}))
assert.throws(()=>prepareTransferStage(f.plan,'unit-carton',{...context,observedNewLabel:'OTHER'}))
})