test(acceptance): reuse fixed customers and align native fulfillment flows
This commit is contained in:
@@ -2,11 +2,53 @@ import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { verifyDiscardApproval, submitClientTransfer } from './phase4-overseas-client-services.mjs'
|
||||
import { verifyDiscardApproval, submitClientTransfer, currentOrders, discardSnapshot } 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')
|
||||
|
||||
for (const fault of ['', 'foreign inbound', 'mixed aggregate']) test(`discard resolves source balance rather than inventing inbound on aggregate: ${fault || 'native grouped list'}`, async () => {
|
||||
const aggregate = { id: '901', customerId: '101', overseasWarehouseId: '201', skuCode: 'SKU', totalPackages: '5', availablePackages: '5',
|
||||
holdPackages: '0', allocatedPackages: '0', pickedPackages: '0', inTransitPackages: '0', receivingPackages: '0', procurementPackages: '0' }
|
||||
const proof = { customerId: '101', warehouseId: '201', skuCode: 'SKU', inboundCode: 'RK', sourceOrderId: '301' }
|
||||
const calls = []
|
||||
const context = { token: 'admin', assert(_m, name, _r, passed) { assert.ok(passed, name) },
|
||||
apiSuccess(_m, _n, r) { assert.equal(r.body.code, '200'); return r.body.data },
|
||||
async request(method, path) {
|
||||
assert.equal(method, 'GET'); calls.push(path)
|
||||
const pathname = new URL(path, 'https://offline.invalid').pathname
|
||||
const data = pathname.endsWith('/balances') ? { total: '1', records: [aggregate] }
|
||||
: pathname.endsWith('/ledger') ? { total: '0', records: [] }
|
||||
: pathname.endsWith('/balances/901') ? { ...aggregate, inboundCode: fault === 'foreign inbound' ? 'OTHER' : 'RK', totalPackages: fault === 'mixed aggregate' ? '4' : '5' }
|
||||
: [{ orderId: '301', inboundCode: 'RK' }]
|
||||
return { http: 200, body: { code: '200', data } }
|
||||
} }
|
||||
if (fault) await assert.rejects(discardSnapshot(context, 'merchant', proof), /来源余额身份与聚合数量一致/)
|
||||
else {
|
||||
const result = await discardSnapshot(context, 'merchant', proof)
|
||||
assert.equal(result.balances[0].inboundCode, 'RK')
|
||||
assert.equal(aggregate.inboundCode, undefined)
|
||||
assert.ok(calls.some(path => path.includes('/balances/901?customerId=101')))
|
||||
}
|
||||
})
|
||||
|
||||
for (const fault of ['', 'duplicate-page', 'missing-total']) test(`current customer orders preserve dropship and fully page before selection: ${fault || 'page two'}`, async () => {
|
||||
const calls = [], row = { id: 'DROP', containerNo: 'CURRENT', orderType: 'DROPSHIP' }
|
||||
const context = { createdResources: { phase4OverseasPda: { completedContainers: ['CURRENT'], orderId: 'DROP' } },
|
||||
assert(_m, label, _r, passed) { assert.ok(passed, label) }, apiSuccess(_m, _n, response) { return response.body.data },
|
||||
async request(method, path) {
|
||||
calls.push(path); assert.equal(method, 'GET')
|
||||
const url = new URL(path, 'https://offline.invalid'); assert.equal(url.searchParams.has('sailingStage'), false)
|
||||
const data = url.pathname.endsWith('/DROP') ? { order: row, cargo: [] }
|
||||
: { total: fault === 'missing-total' ? null : 201, records: url.searchParams.get('page') === '1' || fault === 'duplicate-page'
|
||||
? Array.from({ length: 200 }, (_, n) => ({ id: `OTHER-${n}`, containerNo: 'HISTORY' })) : [row] }
|
||||
return { http: 200, body: { code: '200', data } }
|
||||
} }
|
||||
if (fault) await assert.rejects(currentOrders(context, 'merchant'), /分页/)
|
||||
else assert.deepEqual((await currentOrders(context, 'merchant')).map(item => item.row.id), ['DROP'])
|
||||
assert.ok(calls.every(path => !path.endsWith('/OTHER')))
|
||||
})
|
||||
|
||||
for (const fault of ['', 'network', 'server', 'rejected', 'wrong order', 'wrong status', 'throws']) {
|
||||
test(`transfer submit sends once and stops on uncertain result: ${fault || 'success'}`, async () => {
|
||||
const calls = []
|
||||
@@ -73,7 +115,9 @@ test('relabel transfer and discard cover create replay conflict list detail and
|
||||
'operationWarehouseId:', 'sourceInventoryBalanceId:', 'sourceInventoryPositionId:', 'sourceInboundOrderId:',
|
||||
'destinationFbaWarehouseCode:',
|
||||
]) assert.match(source, new RegExp(field))
|
||||
assert.match(source, /query\(`\$\{RELABEL\}\/inventory-candidates`, \{/)
|
||||
assert.match(source, /await readCurrentBoxedInventory\(context, token,/)
|
||||
assert.match(source, /cartonMark: carton.cartonMark/)
|
||||
assert.match(source, /cartonIds: \[id\(carton.cartonId\)\]/)
|
||||
for (const token of [
|
||||
'客户创建贴标申请', '客户贴标申请幂等重放', '贴标同幂等键异载荷返回A0409',
|
||||
'客户查询自身贴标申请列表', '客户查询自身贴标申请详情', 'PENDING_REVIEW',
|
||||
@@ -124,9 +168,10 @@ test('discard approval retains reservation and keeps actual confirmation evidenc
|
||||
for (const fault of ['', 'pending deducts', 'reject does not release', 'approve deducts', '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:[],
|
||||
const baseline = { balances: [{id:'777',customerId:'123',overseasWarehouseId:'42',skuCode:'SKU',inboundCode:'IP',totalPackages:'4',availablePackages:'4',
|
||||
holdPackages:'0',allocatedPackages:'0',pickedPackages:'0',inTransitPackages:'0',receivingPackages:'0',procurementPackages:'0'}], 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 proof = {sourceOrderId:'9',customerId:'123',warehouseId:'42',inboundCode:'IP',skuCode:'SKU',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') },
|
||||
@@ -139,9 +184,10 @@ for (const fault of ['', 'pending deducts', 'reject does not release', 'approve
|
||||
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')) {
|
||||
} else if(path.endsWith('/balances') || path.endsWith('/balances/777')) {
|
||||
const changed=(state==='PENDING_REVIEW' && fault==='pending deducts') || (state==='APPROVED' && fault==='approve deducts') || (conflictSeen && fault==='create conflict mutates')
|
||||
data={total:'1',records:[{...baseline.balances[0],availablePackages:changed?'3':'4'}]}
|
||||
const balance={...baseline.balances[0],availablePackages:changed?'3':'4'}
|
||||
data=path.endsWith('/777')?balance:{total:'1',records:[balance]}
|
||||
} 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}}
|
||||
|
||||
Reference in New Issue
Block a user