Files
baiyue-firstleg/scripts/admin-test-env-modules/looseboard-pool-receipt-contract.test.mjs
T
2026-10-22 14:37:59 +08:00

90 lines
5.7 KiB
JavaScript

import test from 'node:test'
import assert from 'node:assert/strict'
import { receiveLooseboardPool } from './looseboard-pool-receipt.mjs'
function fixture(fault = '') {
const cargo = { destinationKey: 'FBA:TEST', packageCount: 3, unitPrice: 25 }
const tripProof = { tripNo: 'TR-1', warehouseId: '42' }
const demand = { marker: 'fixture', orderNo: 'LB-1', customerId: '101', poolBinding: {
tripNo: 'TR-1', bookingNo: 'BK-1', status: 'CONFIRMED_ORIGINAL_ORDER', createRequest: { cargo, expectedPalletCount: 2 } } }
const booking = { bookingNo: 'BK-1', tripNo: 'TR-1', customerId: '101', status: 'CONFIRMED', recordVersion: 1, cargo }
const pallets = [], calls = [], snapshots = []; let incomingWrites = 0, receiptWrites = 0, palletized = false
const source = () => ({ incomingNo: 'IN-1', warehouseId: fault === 'warehouse' ? '99' : '42', destinationKey: cargo.destinationKey,
status: palletized ? 'WAIT_CUSTOMER_CONFIRMATION' : 'WAIT_PDA_RECEIVING', receiptHash: 'hash',
receivedPackages: fault === 'quantity' ? 4 : 3,
pallets: palletized ? pallets.map(p => ({ warehousePalletId: p.warehousePalletId, palletNo: p.palletNo, packages: p.cartonCount })) : [] })
const context = { marker: 'fixture', createdResources: { phase4OverseasBusiness: { looseboard: demand }, fleetTrips: [tripProof] },
assert(_m, name, _r, ok) { assert.ok(ok, name) }, apiSuccess() {},
async checkpoint() { if (fault === 'disk') throw Error('disk'); snapshots.push(structuredClone(demand.receipt)) },
async request(method, path, body, token) {
calls.push({ method, path, body: structuredClone(body) })
let data
if (path.endsWith('/bookings')) data = { tripRecordVersion: 1, records: [booking] }
else if (path.endsWith('/incoming') && method === 'POST') {
incomingWrites += 1
if (fault === 'network') return { http: 0 }
data = { tripRecordVersion: 2, booking: { ...booking, incomingNo: 'IN-1' }, replayed: incomingWrites > 1 }
if (fault === 'replay' && incomingWrites > 1) data.booking.incomingNo = 'IN-2'
} else if (path.endsWith('/fulfillment')) data = { preview: { tripRecordVersion: 2, recordVersion: 2, incoming: source() } }
else if (path.endsWith('/pallets/drafts')) {
assert.equal(token, 'pda'); assert.equal(body.cartonCount, pallets.length ? 1 : 2)
pallets.push({ id: String(pallets.length + 1), cartonCount: body.cartonCount, status: 'DRAFT' })
data = { pallets }
} else if (path.endsWith('/generate')) {
const pallet = pallets.find(p => path.includes(`/pallets/${p.id}/`))
Object.assign(pallet, { palletNo: `P-${pallet.id}`, warehousePalletId: String(70 + Number(pallet.id)) })
data = { pallets }
} else if (path.endsWith('/bind')) data = { pallets }
else if (path.endsWith('/palletize')) {
assert.deepEqual(body.photoFileIds, ['9'])
palletized = true; pallets.forEach(p => { p.status = 'COMPLETED' })
data = { status: 'WAIT_CUSTOMER_CONFIRMATION', pallets }
} else if (path.endsWith('/receipt-confirmation')) {
receiptWrites += 1
assert.deepEqual(body.pallets, [{ warehousePalletId: '71', palletSpaces: 1 }, { warehousePalletId: '72', palletSpaces: 1 }])
assert.equal(body.unitPrice, 25)
data = { tripRecordVersion: 3, booking: { ...booking, status: 'RECEIVED', incomingNo: 'IN-1',
receipt: { source: { ...source(), status: 'READY_FOR_DISPATCH' } } }, replayed: receiptWrites > 1 }
} else throw Error(`Unexpected ${method} ${path}`)
if (path.includes('/warehouse-transfers/')) data.transferNo = fault === 'wrong incoming' ? 'OTHER' : 'IN-1'
return { http: 200, body: { code: '200', data: structuredClone(data) }, requestId: 'req', traceId: 'trace' }
},
async requestMultipart(method, path, file, token) {
assert.equal(token, 'pda'); assert.equal(file.type, 'image/png'); assert.ok(file.content.length > 0)
calls.push({ method, path })
return { http: 200, body: { code: '200', data: { fileId: fault === 'file' ? null : '9' } } }
} }
return { context, calls, snapshots, counts: () => ({ incomingWrites, receiptWrites }),
args: { demand, tripProof, pdaToken: 'pda', measurements: [2, 1].map(cartonCount => ({ cartonCount, lengthCm: 100, widthCm: 100,
heightCm: 90, volumeCbm: 0.9, grossWeightKg: 40 })) } }
}
test('original looseboard booking receives two pallets three boxes and confirms once with replay', async () => {
const f = fixture()
const result = await receiveLooseboardPool(f.context, f.args)
assert.equal(result.status, 'RECEIVED_READY_FOR_DISPATCH')
assert.deepEqual(result.pallets.map(p => p.packages), [2, 1])
assert.deepEqual(f.counts(), { incomingWrites: 2, receiptWrites: 2 })
assert.equal(JSON.stringify(f.snapshots).includes('pdaToken'), false)
await assert.rejects(receiveLooseboardPool(f.context, f.args))
})
for (const fault of ['network', 'replay', 'warehouse', 'quantity', 'file', 'disk', 'wrong incoming']) {
test(`receipt stops on ${fault} before operational confirmation`, async () => {
const f = fixture(fault)
await assert.rejects(receiveLooseboardPool(f.context, f.args))
assert.equal(f.args.demand.receipt.status, 'FAILED')
assert.equal(f.counts().receiptWrites, 0)
if (fault === 'network') assert.equal(f.counts().incomingWrites, 1)
if (fault === 'disk') assert.equal(f.calls.length, 0)
})
}
test('wrong batch and inconsistent measured totals are blocked before HTTP', async () => {
for (const change of [f => { f.args.demand.marker = 'other' }, f => { f.args.measurements[0].cartonCount = 3 }]) {
const f = fixture(); change(f)
await assert.rejects(receiveLooseboardPool(f.context, f.args))
assert.equal(f.calls.length, 0)
}
})