This commit is contained in:
2026-10-20 15:48:38 +08:00
parent da6b4685d7
commit 0ff1eefff6
50 changed files with 3933 additions and 78 deletions
@@ -26,7 +26,7 @@ function model(fault = '', countPatch = {}) {
let native
const cargo = () => transfers.map((transfer, n) => ({ dispatchItemId: `ITEM${n}`, customerId: transfer.customerId,
warehousePalletId: `PAL${n}`, palletNo: `PALLET${n}`, scheduledPackages: 1, pickedPackages: 0,
destination: { key: 'FBA:FBA' } }))
destination: { key: `FBA:${transfer.destinationWarehouseCode}` } }))
const parentFixture = { marker: 'PARENT', tenantId: 'PARENT-ID', password: 'parent-secret' }
const context = { marker: 'RUN-CROSS', token: 'admin-secret', options: { customerName: 'Parent customer' }, context: { merchantQualificationFixture: parentFixture },
createdResources: { merchantQualificationFixture: { marker: 'PARENT' } },
@@ -51,6 +51,8 @@ function model(fault = '', countPatch = {}) {
if (path.startsWith('/api/pda/')) assert.equal(token, options.pdaToken)
if (path.endsWith('/warehouse-admin/10')) return response(origin)
if (path.endsWith('/warehouse-admin/20')) return response(destination)
if (path.endsWith('/warehouse-admin/21')) return response({ ...destination,
warehouse: { ...destination.warehouse, id: '21', warehouseCode: 'FBA2', warehouseName: 'Second destination' } })
if (path === '/api/platform/v1/iam/tenants') {
const n = customers.length, id = fault === 'reused customer' ? '100' : String(100 + n)
const customer = { id, username: `user${n}`, password: `fresh-secret-${n}`, marker: body.internalRemark }
@@ -80,7 +82,7 @@ function model(fault = '', countPatch = {}) {
const transferNo = `IN${transfers.length}`
const itemId = `FORECAST-ITEM${transfers.length}`
const transfer = { transferNo, inboundCode: transferNo, customerId: merchant.id, operatingWarehouseId: body.operatingWarehouseId,
status: 'DRAFT', packageCount: 1, pallets: [],
status: 'DRAFT', packageCount: 1, pallets: [], destinationWarehouseCode: body.destinationWarehouseCode,
items: [{ ...copy(body.items[0]), id: itemId, ...(fault === 'missing declared quantity' ? { quantity: null } : {}) }],
cartons: fault === 'missing forecast carton' ? [] : [{ cartonMark: `BX-FORECAST${transfers.length}`, status: 'FORECAST',
transferItemId: fault === 'foreign forecast item' ? 'OTHER-ITEM' : itemId }] }
@@ -141,7 +143,7 @@ function model(fault = '', countPatch = {}) {
const n = transfers.findIndex(row => row.transferNo === url.searchParams.get('orderNo')), transfer = transfers[n]
assert.equal(transfer.status, 'READY_FOR_DISPATCH')
return response({ complete: true, candidates: [{ customerId: fault === 'foreign candidate' ? 'OTHER' : transfer.customerId,
warehouseId: '10', inboundCode: transfer.transferNo, palletNo: `PALLET${n}`, availablePackages: 1, destination: { key: 'FBA:FBA' },
warehouseId: '10', inboundCode: transfer.transferNo, palletNo: `PALLET${n}`, availablePackages: 1, destination: { key: `FBA:${transfer.destinationWarehouseCode}` },
sourceVersion: `SOURCE${n}`, blockReasons: [], selection: { sourceType: 'INBOUND_PLAN', planPalletId: `PLAN${n}`, sourceVersion: `SOURCE${n}` } }] })
}
if (path === '/api/platform/v1/last-leg/dispatches' && method === 'POST') {
@@ -152,10 +154,11 @@ function model(fault = '', countPatch = {}) {
counts: { itemCount: 2, packageCount: 2, pickedPackageCount: 0, shippedPackageCount: 0, ...countPatch }, reviews: {},
documents: { outbounds: [], deliveries: [] }, dispatchInfo: {} }
trip.dispatches = [{ dispatchNo: native.dispatchNo }]
Object.assign(trip.trip, { tripPurpose: 'DELIVERY', stops: [{ name: 'Origin' }, { name: 'FBA', destinationKey: 'FBA:FBA' }] })
Object.assign(trip.trip, { tripPurpose: 'DELIVERY', stops: [{ name: 'Origin' }, ...[...new Set(transfers.map(row => row.destinationWarehouseCode))]
.map(code => ({ name: code, destinationKey: `FBA:${code}` }))] })
dispatches.push(native); return response(native)
}
if (path.endsWith('/master-data/vehicles')) { trip.trip.vehiclePlate = body.plateNumber; return response({ id: 'VEHICLE' }) }
if (path.endsWith('/master-data/vehicles')) { trip.trip.vehiclePlate = body.plateNumber.toUpperCase(); return response({ id: 'VEHICLE' }) }
if (path.endsWith('/fleet/drivers')) return response({ id: 'DRIVER', recordVersion: '0' })
if (path.endsWith('/fleet/drivers/DRIVER/review')) return response({ reviewStatus: 'APPROVED' })
if (path === '/api/platform/v1/last-leg/fleet/trips') return response({ records: [trip.trip], total: 1, size: 200, page: 1 })
@@ -270,6 +273,26 @@ test('native Jackson Long count strings build the same complete one-main fixture
assert.ok(fixture.members.every(member => member.dispatchNo === fixture.dispatchNo))
})
test('explicit per-member destinations are read before writes and preserved in both receipts and route', async () => {
const m = model(); m.options.destinationWarehouseIds = ['20', '21']
const fixture = await createFleetCrossCustomerFixture(m.context, m.options)
assert.deepEqual(fixture.members.map(row => row.destination.key), ['FBA:FBA', 'FBA:FBA2'])
assert.deepEqual(m.trip.trip.stops.slice(1).map(row => row.destinationKey), ['FBA:FBA', 'FBA:FBA2'])
const secondDestination = m.calls.findIndex(row => row.raw.includes('/warehouse-admin/21'))
const firstWrite = m.calls.findIndex(row => row.method !== 'GET')
assert.ok(secondDestination >= 0 && secondDestination < firstWrite)
assert.deepEqual(m.calls.filter(row => row.method === 'POST' && row.raw === '/api/merchant/v1/last-leg/warehouse-transfers')
.map(row => row.body.destinationWarehouseId), ['20', '21'])
})
test('malformed member destination scope fails without network', async () => {
for (const destinations of [[], ['20'], ['20', '21', '22'], ['21', '20'], ['20', '']]) {
const m = model(); m.options.destinationWarehouseIds = destinations
await assert.rejects(createFleetCrossCustomerFixture(m.context, m.options), /Two explicit/)
assert.equal(m.calls.length, 0)
}
})
test('fixture count parser accepts only explicit non-negative safe integers and rejects coercion', () => {
for (const [value, expected] of [[0, 0], ['0', 0], [2, 2], ['2', 2], [Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER], [String(Number.MAX_SAFE_INTEGER), Number.MAX_SAFE_INTEGER]]) {
assert.equal(safeFixtureCount(value), expected)