test(fba): preserve verified fleet and fulfillment acceptance workflows

This commit is contained in:
2026-10-03 06:26:33 +08:00
parent c55bd7f4b6
commit d858357c5d
52 changed files with 4217 additions and 103 deletions
@@ -2,6 +2,7 @@ import test from 'node:test'
import assert from 'node:assert/strict'
import { readFile } from 'node:fs/promises'
import { fileURLToPath } from 'node:url'
import { verifyDiscardApproval } from './phase4-overseas-client-services.mjs'
import { parseArguments } from '../admin-test-env-acceptance.mjs'
const source = await readFile(fileURLToPath(new URL('./phase4-overseas-client-services.mjs', import.meta.url)), 'utf8')
@@ -14,7 +15,7 @@ test('client services module uses only stable merchant self-service paths', () =
'/api/merchant/v1/last-leg/cargo-discards',
'inventory-candidates',
]) assert.ok(source.includes(path), `missing ${path}`)
for (const forbidden of ['/api/pda/', '/api/platform/v1/last-leg/dispatches', '/outbound', '/delivery', '/dispatch', '/depart']) {
for (const forbidden of ['/api/platform/v1/last-leg/dispatches', '/outbound', '/delivery', '/dispatch', '/depart']) {
assert.equal(source.includes(forbidden), false, `must exclude ${forbidden}`)
}
})
@@ -62,7 +63,7 @@ test('module records marker-owned resources without forbidden fulfillment cleanu
assert.match(source, /relabelNo = relabelNo/)
assert.match(source, /transferNo = transferNo/)
assert.match(source, /discardNo = discardNo/)
assert.match(source, /no outbound, dispatch, PDA or delivery action is invoked/)
assert.match(source, /no outbound, dispatch or delivery action is invoked/)
})
test('focused client services keeps the marker-owned merchant fixture when reusable credentials exist', () => {
@@ -75,3 +76,42 @@ test('focused client services keeps the marker-owned merchant fixture when reusa
const clientServices = parsed.modules.indexOf('phase4-overseas-client-services')
assert.ok(fixture >= 0 && fixture < clientServices)
})
test('discard follows formal approval deduction and keeps physical ledger evidence pending', () => {
assert.match(source, /正式废弃合同要求审批事务扣减一箱/)
assert.match(source, /废弃拒绝释放原箱且全部库存数量流水不变/)
assert.match(source, /APPROVAL_HTTP_VERIFIED_CARTON_SQL_PENDING/)
assert.doesNotMatch(source, /INSERT INTO|UPDATE ll_|fleet-trip|\/api\/pda\//)
})
for (const fault of ['', 'pending deducts', 'reject does not release', 'approve does not deduct', 'create conflict mutates']) {
test(`discard approval stock boundaries: ${fault || 'formal approval contract'}`, async () => {
let state = 'PENDING_REVIEW', approved = false, conflictSeen = false
const baseline = { balances: [{customerId:'123',overseasWarehouseId:'42',availablePackages:'4'}], ledger:[],
source:{orderId:'9',overseasWarehouseId:'42',inboundCode:'IP',availablePackages:'4'},detail:null }
const proof = {sourceOrderId:'9',customerId:'123',warehouseId:'42',inboundCode:'IP',snapshots:{before:baseline}}
const calls=[]
const context={marker:'fixture',token:'admin',addStep(){},
assert(_m,name,_r,passed){ assert.ok(passed,name); if(name==='重复批准不会提前扣库存或新增历史') throw new Error('CHECKPOINT_PASSED') },
apiSuccess(_m,_n,r){ assert.equal(r.body.code,'200');return r.body.data },
async request(method,raw,body){
calls.push({method,raw,body})
const path=new URL(raw,'https://offline.invalid').pathname
let data
if(method==='POST' && path.endsWith('/review')) {state=body.decision==='REJECT'?'REJECTED':'APPROVED';data={}}
else if(method==='POST' && path.endsWith('/cargo-discards')) {
if(body.remark.endsWith('-DIFFERENT')) {conflictSeen=true;return {http:200,body:{code:'A0409'},requestId:'req',traceId:'trace'}}
approved=true;state='PENDING_REVIEW';data={summary:{discardNo:'NEW'}}
} else if(path.endsWith('/balances')) {
const changed=(state==='PENDING_REVIEW' && fault==='pending deducts') || (state==='APPROVED' && fault!=='approve does not deduct') || (conflictSeen && fault==='create conflict mutates')
data={total:'1',records:[{...baseline.balances[0],availablePackages:changed?'3':'4'}]}
} else if(path.endsWith('/ledger')) data={total:'0',records:[]}
else if(path.endsWith('/inventory-candidates')) data=[{...baseline.source,availablePackages:state==='REJECTED' && fault!=='reject does not release'?'4':'3'}]
else data={summary:{discardNo:approved?'NEW':'OLD',status:state}}
return {http:200,body:{code:'200',data}}
}}
await assert.rejects(verifyDiscardApproval(context,'merchant',{idempotencyKey:'key',remark:'fixture'},'OLD',proof),
fault ? /仅锁定|释放原箱|审批事务扣减|异载荷拒绝后/ : /CHECKPOINT_PASSED/)
assert.ok(!calls.some(call=>call.raw.includes('/pda/')))
})
}