Files
baiyue-firstleg/scripts/prod-release-overseas-stage.test.mjs
T

159 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import test from 'node:test'
import assert from 'node:assert/strict'
import { overseasCommands, runOverseasStage, validateOverseasInput, finishSingleOrderTrip } from './prod-release-overseas-stage.mjs'
const input = { batch: 'release-test-001', customerId: '1', warehouseId: '2', packageCount: 1,
resources: { vehicleId: '3', driverId: '4', carrierName: '验收既有车队' } }
const domestic = { containerBookingId: '5', sourceInboundId: '6', orderId: '7', containerNo: 'TEST0000001',
inboundCode: 'IN-TEST', orderNo: 'ORDER-TEST', cartonMarks: ['CARTON-TEST'] }
const ok = data => ({ http: 200, body: { code: '200', data } })
const journal = () => { const state = {}; return { state, save: async patch => Object.assign(state, patch) } }
test('reject multi-ticket quantities and duplicate frozen cartons before HTTP', () => {
assert.throws(() => validateOverseasInput({ ...input, packageCount: 3 }, domestic), /1~2/)
assert.throws(() => validateOverseasInput({ ...input, packageCount: 2 }, { ...domestic, cartonMarks: ['a', 'a'] }), /箱码/)
})
test('command records intent before HTTP; uncertain result blocks resend', async () => {
const checkpoint = journal(); let calls = 0
const { command } = overseasCommands({ request: async () => {
calls++; assert.equal(checkpoint.state.commands.create.status, 'INTENT'); throw new Error('timeout')
} }, checkpoint)
await assert.rejects(command('create', 'POST', '/example', { x: 1 }), /timeout/)
await assert.rejects(command('create', 'POST', '/example', { x: 1 }), /结果未知/)
assert.equal(calls, 1)
})
test('completed command restores result without HTTP and rejects changed request', async () => {
const checkpoint = journal(); let calls = 0
const { command } = overseasCommands({ request: async () => { calls++; return ok({ id: '99' }) } }, checkpoint)
await command('create', 'POST', '/example', { x: 1 })
assert.deepEqual(await command('create', 'POST', '/example', { x: 1 }), { id: '99' })
await assert.rejects(command('create', 'POST', '/example', { x: 2 }), /不符/)
assert.equal(calls, 1)
})
test('unknown prior command blocks even read/other writes on continuation', async () => {
const checkpoint = journal(); checkpoint.state.commands = { lost: { status: 'INTENT' } }
await assert.rejects(runOverseasStage({ input, domestic, checkpoint,
context: { overseasPdaToken: 'pda', merchantToken: 'merchant', request: () => assert.fail('must not call HTTP') } }), /结果未知/)
})
test('only initial current receiving probe accepts HTTP 200 A0404 as no session', async () => {
const calls = []
const helpers = overseasCommands({ request: async (method, path, body, token) => {
calls.push({ method, path, token }); return { http: 200, body: { code: 'A0404' } }
} }, journal())
assert.equal(await helpers.readCurrentReceivingSession('pda'), null)
assert.deepEqual(calls[0], { method: 'GET', path: '/api/pda/v1/last-leg/receiving-containers/current', token: 'pda' })
await assert.rejects(helpers.read('/api/pda/v1/last-leg/receiving-containers/current', 'pda'), /A0404/)
await assert.rejects(helpers.read('/api/platform/v1/last-leg/orders/1'), /A0404/)
})
test('current receiving probe preserves auth, transport and other business errors', async () => {
for (const [http, code] of [[200, 'A0403'], [200, 'A0500'], [401, 'A0404'], [404, 'A0404'], [503, 'A0404']]) {
const helpers = overseasCommands({ request: async () => ({ http, body: { code, requestId: 'request-test' } }) }, journal())
await assert.rejects(helpers.readCurrentReceivingSession('pda'), /request-test/)
}
})
function fixture({ foreignSource = false, destinationType = 'OVERSEAS' } = {}) {
const checkpoint = journal(), calls = []
const session = { containerNo: domestic.containerNo, overseasWarehouseId: '2', totalPackages: 1, pendingPackages: 0,
inbounds: [{ inboundCode: domestic.inboundCode, destinationType }] }
const trip = { trip: { tripNo: 'TRIP-TEST', recordVersion: 1, stops: [] } }
const cargo = [{ warehousePalletId: '9', palletNo: 'PLT-TEST', destinationKey: 'DEST', destinationLabel: 'Destination' }]
const context = { token: 'admin', overseasPdaToken: 'pda', merchantToken: 'merchant', wait: async () => {},
requestWithBody: async (method, path, form, token) => {
calls.push({ method, path, token }); assert.equal(token, 'pda'); assert.ok(form instanceof FormData); return ok({ fileId: '10' })
}, request: async (method, path, body, token) => {
calls.push({ method, path, body, token })
if (path.includes('/master-data/vehicles/')) return ok({ id: '3', region: 'OVERSEAS', status: 'IN_SERVICE', plateNumber: 'TEST' })
if (path.includes('/fleet/drivers/')) return ok({ id: '4', qualification: { canDispatch: true }, details: { name: 'Driver', phone: '10000000000' } })
if (path.includes('/orders?containerNo=')) return ok({ records: [{ sourceContainerBookingId: '5', customerId: foreignSource ? '99' : '1', sourceInboundId: '6',
inboundCode: domestic.inboundCode, overseasWarehouseId: '2', id: '8' }] })
if (path.endsWith('/container-arrival-times/query')) return ok([{ sourceContainerBookingId: '5', version: 0 }])
if (path.includes('/container-preview?')) return ok({ ready: true, totalOrderCount: 1, pendingOrderCount: 1, snapshotVersion: 1, snapshotHash: 'hash' })
if (path.endsWith('/convert-container')) return ok([{ lines: [{ inboundCode: domestic.inboundCode }], pallets: [{ palletNo: 'PLT-TEST', warehousePalletId: '9' }] }])
if (path.endsWith('/current') && method === 'GET') return calls.filter(c => c.path.endsWith('/current')).length === 1
? { http: 200, body: { code: 'A0404' } } : ok(session)
if (path.includes('/receiving-containers/TEST0000001?')) return ok(session)
if (path.endsWith('/pallets/resolve')) return ok({ compatible: true })
if (path.endsWith('/cartons/resolve')) return ok({ inboundCode: domestic.inboundCode })
if (path.endsWith('/terminal-drayages/5')) return ok({ containerBookingId: '5', status: 'UNLOADED',
version: 1, unloadingCompletedAt: '2026-10-12T12:00:00', allowedActions: ['RECORD_EMPTY_RETURN'] })
if (path.endsWith('/empty-return')) return ok({ status: 'EMPTY_RETURNED' })
if (path.endsWith('/candidates?sourceStage=RECEIVED')) return ok([{ sourceStage: 'RECEIVED', inboundCode: domestic.inboundCode,
customerId: '1', overseasWarehouseId: '2', packageCount: 1, planPalletId: '11' }])
if (path.endsWith('/pre-arrange')) return ok({ dispatchNo: 'DISPATCH-TEST' })
if (path.includes('/merchant/v1/last-leg/delivery-orders?')) return ok({ outboundNo: 'OUT-TEST', deliveries: [{ deliveryNo: 'DELIVERY-TEST' }] })
if (path.endsWith('/loading') && method === 'GET') return ok({ cargo, recordVersion: 1, manifestHash: 'manifest' })
if (path.endsWith('/loading') && method === 'POST') return ok({ recordVersion: 2 })
if (path.includes('/fleet/trips')) return ok(trip)
if (path === '/api/pda/v1/last-leg/dispatches/DISPATCH-TEST') return ok({ status: 'COMPLETED', canDepart: true,
items: [{ dispatchItemId: '12', scheduledPackages: 1, pickedPackages: 0, positions: [{ id: '13', availablePackages: 1 }] }] })
if (path === '/api/platform/v1/last-leg/dispatches/DISPATCH-TEST' && method === 'GET') return ok({ items: [{ id: '12', inboundCode: domestic.inboundCode }] })
if (path.includes('/outbound-orders/')) return ok({ outbound: { status: 'OUTBOUNDED' }, pallets: [{ inboundCode: domestic.inboundCode }] })
if (method === 'GET') assert.fail(`unexpected GET ${path}`)
return ok({})
} }
return { input, domestic, context, checkpoint, calls }
}
test('normal single-ticket HTTP chain reaches outbound with existing master resources and PDA identity', async () => {
const f = fixture(), result = await runOverseasStage(f)
assert.equal(result.status, 'OVERSEAS_OUTBOUNDED')
assert.equal(result.fullFlowVerified, false)
assert.equal(result.deliveryNo, 'DELIVERY-TEST')
assert.ok(f.calls.some(row => row.path.endsWith('/receipts/carton') && row.token === 'pda'))
assert.ok(f.calls.some(row => row.path.endsWith('/depart') && row.token === 'pda'))
assert.equal(f.calls.filter(row => row.path.endsWith('/terminal-drayages/5')).length, 1)
assert.equal(f.calls.filter(row => row.path.endsWith('/empty-return')).length, 1)
assert.equal(f.calls.filter(row => row.method !== 'GET' && /\/master-data\/|\/fleet\/drivers|\/iam\//.test(row.path)).length, 0)
assert.ok(Object.values(f.checkpoint.state.commands).every(row => row.status === 'DONE'))
})
test('foreign customer source stops before plan conversion or receiving', async () => {
const f = fixture({ foreignSource: true })
await assert.rejects(runOverseasStage(f), /来源不属于/)
assert.equal(f.calls.filter(row => row.method !== 'GET').length, 0)
})
test('FBA receives cumulative package count without fabricated or resolved carton marks', async () => {
const f = fixture({ destinationType: 'FBA' })
f.domestic = { ...f.domestic }; delete f.domestic.cartonMarks
const result = await runOverseasStage(f)
assert.equal(result.status, 'OVERSEAS_OUTBOUNDED')
const received = f.calls.filter(row => row.path.endsWith('/receipts/bulk'))
assert.equal(received.length, 1)
assert.equal(received[0].body.packageCount, 1)
assert.equal(f.calls.filter(row => /\/cartons\/resolve|\/receipts\/carton/.test(row.path)).length, 0)
})
test('OVERSEAS missing frozen carton marks fails before opening receiving session', async () => {
const f = fixture(); f.domestic = { ...f.domestic }; delete f.domestic.cartonMarks
await assert.rejects(runOverseasStage(f), /正式冻结箱码/)
assert.equal(f.calls.filter(row => /\/prepare\?|\/current\/sign|\/receipts\//.test(row.path)).length, 0)
})
test('signed single ticket closes its trip and releases only its existing resources', async () => {
const checkpoint = journal(), writes = []
const overseas = { tripNo: 'TRIP-TEST', dispatchNo: 'DISPATCH-TEST', deliveryNo: 'DELIVERY-TEST' }
let trip = { tripNo: overseas.tripNo, vehicleId: '3', driverId: '4', recordVersion: 1,
stops: [{ name: 'Origin' }, { name: 'Destination' }] }
const context = { request: async (method, path, body) => {
if (path.includes('/delivery-orders/')) return ok({ order: { customerId: '1', status: 'DELIVERED', dispatchNo: overseas.dispatchNo } })
if (path.endsWith('/completion')) return ok({ canComplete: true, recordVersion: 1, outcomes: [{}] })
if (method === 'POST') {
writes.push({ path, body }); trip = { ...trip, recordVersion: trip.recordVersion + 1 }
if (path.endsWith('/complete')) trip = { ...trip, status: 'COMPLETED', completedAt: '2026-10-12T12:00:00Z' }
else if (body.resourceKind === 'VEHICLE') trip.vehicleRelease = { ended: true }
else trip.driverRelease = { ended: true }
}
return ok({ trip, dispatches: [{ dispatchNo: overseas.dispatchNo }] })
} }
assert.equal((await finishSingleOrderTrip({ input, overseas, context, checkpoint })).status, 'CARGO_COMPLETED_AND_RESOURCES_RELEASED')
assert.equal(writes.length, 3)
assert.deepEqual(writes.map(row => row.body.recordVersion), [1, 2, 3])
})