Files
baiyue-firstleg/scripts/prod-release-overseas-stage.test.mjs
T
2026-10-20 10:35:52 +08:00

367 lines
26 KiB
JavaScript
Raw Permalink 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 { mkdtemp, writeFile, rm, readFile, rmdir } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { createHash } from 'node:crypto'
import { overseasCommands, runOverseasStage, validateOverseasInput, finishSingleOrderTrip } from './prod-release-overseas-stage.mjs'
const input = { environment: { mode: 'test', apiBase: 'https://test.example.invalid/api' }, 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', missingActual = false, foreignCargo = false,
foreignTrip = false, candidateComplete = true, loseCommand = null } = {}) {
const checkpoint = journal(), calls = []
const session = { containerNo: domestic.containerNo, overseasWarehouseId: '2', totalPackages: 1, pendingPackages: 0,
inbounds: [{ inboundCode: domestic.inboundCode, destinationType }] }
const dispatchNo = 'DISPATCH-TEST', dispatchPath = `/api/platform/v1/last-leg/dispatches/${dispatchNo}`, pdaPath = `/api/pda/v1/last-leg/dispatches/${dispatchNo}`
let stage = 'BEFORE_LOADING', recordVersion = 0, picked = 0, created = false, receivingRead = 0
const stops = [{ name: 'Origin' }, { name: 'Destination', destinationKey: 'FBA:DEST' }]
const counts = () => ({ packageCount: 1, pickedPackageCount: picked, shippedPackageCount: stage === 'DELIVERING' ? 1 : 0 })
const nativeResult = result => ({ dispatchNo, status: 'ACTIVE', stage, recordVersion, counts: counts(), result })
const ownItem = () => ({ dispatchItemId: '12', customerId: foreignCargo ? '99' : '1', inboundCode: domestic.inboundCode,
warehousePalletId: '9', palletNo: 'PLT-TEST', scheduledPackages: 1, pickedPackages: picked,
positions: [{ positionId: '13', availablePackages: 1 - picked }] })
const delivery = { customerId: '1', outboundNo: 'OUT-TEST', deliveryNo: 'DELIVERY-TEST', packageCount: 1, destination: { key: 'FBA:DEST' } }
const group = { groupKey: 'GROUP-1', groupCargoHash: 'group-hash', customerId: '1', destination: { key: 'FBA:DEST' },
cargo: [{ customerId: '1', inboundCode: domestic.inboundCode }], requiredServices: ['DELIVERY_FEE'], blockReasons: [],
externalQuotes: [], externalFreightHash: 'freight-hash', feeCoverageCargoHash: 'coverage-hash' }
const trip = () => ({ trip: { tripNo: 'TRIP-TEST', vehicleId: '3', driverId: '4', warehouseId: '2', vehiclePlate: 'TEST',
tripPurpose: 'DELIVERY', recordVersion: 1, stops }, dispatches: [{ dispatchNo: foreignTrip ? 'OTHER-DISPATCH' : dispatchNo }] })
const measured = () => ({ warehousePalletId: '9', palletNo: 'PLT-TEST', scheduledPackages: 1, pickedPackages: picked,
palletSpaces: missingActual ? null : 2, weightKg: 17.75, volumeCbm: '0.072', deliveryIndex: 1,
evidence: ['palletSpaces', 'weightKg', 'volumeCbm'].filter(field => !missingActual || field !== 'palletSpaces').map(field => ({ field, actual: true,
sourceType: 'WAREHOUSE_MEASUREMENT', sourceBusinessNo: 'RECEIPT-TEST', observedAt: '2026-10-19T10:00:00Z',
value: { palletSpaces: 2, weightKg: 17.75, volumeCbm: '0.072' }[field] })) })
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: body == null ? body : structuredClone(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: !created }, 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 ++receivingRead === 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.includes('/dispatches/candidates?')) return ok({ complete: candidateComplete, candidates: [{ inboundCode: domestic.inboundCode,
customerId: '1', warehouseId: '2', palletNo: 'PLT-TEST', availablePackages: 1, sourceVersion: 'real-source', blockReasons: [],
selection: { sourceType: 'INBOUND_PLAN', planPalletId: '11', sourceVersion: 'real-source' } }] })
if (path === '/api/platform/v1/last-leg/dispatches' && method === 'POST') { created = true; return ok(nativeResult(null)) }
if (path.startsWith('/api/platform/v1/last-leg/fleet/trips?')) return ok({ records: [trip().trip], total: 1, page: 1, size: 200 })
if (path.endsWith('/fleet/trips/TRIP-TEST')) return ok(trip())
if (path === dispatchPath && method === 'GET') return ok({ ...nativeResult(null), pickingReconciled: picked === 1,
orderDetails: [{ inboundCode: domestic.inboundCode, customerId: '1' }],
vehicleInfo: { vehicle: { vehicleId: '3' }, driver: { driverId: '4' } }, actions: { DEPART: { allowed: stage === 'AFTER_LOADING' } } })
if (path === `${dispatchPath}/items`) return ok({ dispatchNo, recordVersion, items: [ownItem()] })
if (path === pdaPath && method === 'GET') return ok({ ...nativeResult(null), warehouseId: '2', pickingReconciled: picked === 1,
items: [ownItem()], actions: { DEPART: { allowed: stage === 'AFTER_LOADING' } } })
if (path === `${pdaPath}/loading` && method === 'GET') return ok({ ...nativeResult(null), pickingReconciled: picked === 1,
loadingComplete: { allowed: picked === 1 }, manifestHash: 'real-manifest', pallets: [measured()] })
if (path === `${dispatchPath}/delivery-arrangement`) return ok({ ...nativeResult(null), arrange: { allowed: true },
cargoHash: 'real-cargo', loadingCompletionHash: 'real-load', groups: [group] })
if (method === 'POST' && (path.startsWith(`${dispatchPath}/`) || path.startsWith(`${pdaPath}/`))) {
assert.equal(body.recordVersion, recordVersion, `must use current main version: ${path}`)
recordVersion++
if (path.endsWith('/loading-review')) stage = 'LOADING'
if (path.endsWith('/pick')) { assert.equal(stage, 'LOADING'); picked = 1 }
if (path.endsWith('/loading-complete')) { stage = 'AFTER_LOADING'; return ok(nativeResult({ confirmedLoad: { pallets: body.measurements } })) }
if (path.endsWith('/transfer-delivery')) {
assert.equal(token, 'admin'); assert.equal(stage, 'AFTER_LOADING'); assert.equal(body.groups.length, 1)
assert.equal(body.groups[0].fees[0].currency, 'USD'); assert.equal(body.groups[0].fees[0].amount, 10)
return ok(nativeResult({ deliveries: [delivery], outbounds: [{ customerId: '1', outboundNo: 'OUT-TEST' }] }))
}
if (path.endsWith('/depart')) { stage = 'DELIVERING'; return ok(nativeResult({ shippedPackages: 1, deliveries: [delivery] })) }
if (path.endsWith(loseCommand || 'NOT_A_COMMAND')) throw Error('simulated response loss')
return ok(nativeResult(null))
}
if (path.includes('/outbound-orders/')) return ok({ outbound: { customerId: '1', dispatchNo, status: 'OUTBOUNDED', packageCount: 1 }, 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])
})
test('native successful continuation restores original dynamic requests without sending any business write again', async () => {
const f = fixture()
const result = await runOverseasStage(f)
const writes = f.calls.filter(row => row.method !== 'GET').length
const firstSubmit = structuredClone(f.checkpoint.state.commands['submit-review'].request)
assert.equal((await runOverseasStage(f)).dispatchNo, result.dispatchNo)
assert.equal(f.calls.filter(row => row.method !== 'GET' && !row.path.endsWith('/container-arrival-times/query')).length, writes - 1)
assert.deepEqual(f.checkpoint.state.commands['submit-review'].request, firstSubmit)
assert.equal(firstSubmit.body.recordVersion, 0)
assert.equal(f.calls.filter(row => row.path.endsWith('/loading-complete')).length, 1)
})
test('completed recorded commands retain original payload instead of reading a new current version', async () => {
const checkpoint = journal(); let calls = 0
const helper = overseasCommands({ request: async (_method, _path, body) => { calls++; return ok({ recordVersion: body.recordVersion + 1 }) } }, checkpoint)
await helper.recorded('review', 'POST', '/native/review', async () => ({ recordVersion: 0, idempotencyKey: 'original' }))
const saved = checkpoint.state.commands.review
assert.equal(saved.request.body.recordVersion, 0)
assert.deepEqual(await helper.recorded('review', 'POST', '/native/review', () => assert.fail('must not rebuild with current version')), { recordVersion: 1 })
assert.equal(calls, 1)
saved.request.body.recordVersion = 2
await assert.rejects(helper.recorded('review', 'POST', '/native/review', () => assert.fail('must stop')), /检查点不符/)
})
test('legacy flat dispatch checkpoint is explicitly rejected before network without converting old replies', async () => {
for (const name of ['dispatch', 'trip-bind', 'pickComplete', 'loading-review', 'depart']) {
const f = fixture(); f.checkpoint.state.commands = { [name]: { status: 'DONE', result: { dispatchNo: 'OLD' } } }
await assert.rejects(runOverseasStage(f), /旧排车协议/)
assert.equal(f.calls.length, 0)
}
})
test('real missing pallet spaces blocks loading before any arrangement or departure rather than fabricating one', async () => {
const f = fixture({ missingActual: true })
await assert.rejects(runOverseasStage(f), /palletSpaces/)
assert.ok(f.calls.some(row => row.path.endsWith('/pick')))
assert.equal(f.calls.filter(row => /loading-complete|transfer-delivery|\/depart$/.test(row.path)).length, 0)
assert.equal(f.checkpoint.state.commands['loading-complete'], undefined)
})
test('incomplete candidates, foreign cargo and wrong auto-trip binding cannot enter actual picking', async () => {
for (const options of [{ candidateComplete: false }, { foreignCargo: true }, { foreignTrip: true }]) {
const f = fixture(options)
await assert.rejects(runOverseasStage(f), /候选不完整|主单含其他票|自动Fleet资源记录不唯一/)
assert.equal(f.calls.filter(row => row.path.endsWith('/pick')).length, 0)
assert.equal(f.calls.filter(row => row.method === 'POST' && /\/fleet\/trips$|\/dispatches\/bind$/.test(row.path)).length, 0)
}
})
test('actual native loading keeps measured 2 spaces, 17.75kg and 0.072m3 and distinct explicit stages', async () => {
const f = fixture(); await runOverseasStage(f)
const load = f.calls.find(row => row.path.endsWith('/loading-complete'))
assert.deepEqual(load.body.measurements, [{ warehousePalletId: '9', palletSpaces: 2, weightKg: 17.75, volumeCbm: '0.072', deliveryIndex: 1 }])
const create = f.calls.find(row => row.method === 'POST' && row.path === '/api/platform/v1/last-leg/dispatches')
assert.deepEqual(create.body.vehicle, { mode: 'MASTER', vehicleId: '3' })
assert.equal(create.body.driver.mode, 'MASTER'); assert.equal(create.body.driver.driverId, '4')
assert.equal(create.body.driver.evidence.expectedTaskEndAt, f.checkpoint.state.schedule.expectedDriverEndAt)
assert.equal(Object.hasOwn(create.body, 'customerId'), false)
const pick = f.calls.findIndex(row => row.path.endsWith('/pick')), loading = f.calls.indexOf(load), arrangement = f.calls.findIndex(row => row.path.endsWith('/transfer-delivery')), depart = f.calls.findIndex(row => row.path.endsWith('/depart'))
assert.ok(pick < loading && loading < arrangement && arrangement < depart)
assert.equal(f.calls.filter(row => row.path === '/api/pda/v1/last-leg/dispatches/DISPATCH-TEST/complete').length, 0)
assert.equal(f.calls.filter(row => row.path.includes('/merchant/v1/last-leg/delivery-orders')).length, 0)
})
test('lost native review result preserves exact INTENT request and prevents all continuation HTTP', async () => {
const f = fixture({ loseCommand: '/dispatch-review' })
await assert.rejects(runOverseasStage(f), /response loss/)
const intent = f.checkpoint.state.commands['dispatch-review']
assert.equal(intent.status, 'INTENT')
assert.equal(intent.request.body.recordVersion, 1)
assert.equal(intent.request.body.idempotencyKey, `${input.batch}-dispatch-review`)
const calls = f.calls.length
await assert.rejects(runOverseasStage(f), /结果未知/)
assert.equal(f.calls.length, calls)
})
test('native successful checkpoint without its original request cannot rebuild a versioned command', async () => {
const checkpoint = journal(); checkpoint.state.commands = { review: { status: 'DONE', binding: 'old-hash', result: { recordVersion: 9 } } }
const helper = overseasCommands({ request: () => assert.fail('no HTTP') }, checkpoint)
await assert.rejects(helper.recorded('review', 'POST', '/native/review', () => assert.fail('no rebuild')), /原请求或检查点不符/)
})
function measurementReceipt(patch = {}) {
return { environment: 'TEST', batch: input.batch, dispatchNo: 'DISPATCH-TEST', pallets: [{ warehousePalletId: '9', palletSpaces: 2,
weightKg: 17.75, volumeCbm: '0.072', deliveryIndex: 1,
evidence: ['palletSpaces', 'weightKg', 'volumeCbm'].map(field => ({ field, actual: true, sourceType: 'WAREHOUSE_MEASUREMENT',
sourceBusinessNo: 'MEASURE-SAME-RUN', observedAt: '2026-10-19T10:00:00Z', value: { palletSpaces: 2, weightKg: 17.75, volumeCbm: '0.072' }[field] })) }], ...patch }
}
test('explicit fixed measurement file can be supplied after picking and continued without changing the original input', async () => {
const directory = await mkdtemp(join(tmpdir(), 'release-native-measurement-'))
try {
const file = join(directory, 'actual.json'), f = fixture({ missingActual: true })
f.input = { ...f.input, loadingMeasurementFile: file }
await assert.rejects(runOverseasStage(f), /等待真实测量/)
assert.equal(f.checkpoint.state.pickingVerified, true)
assert.equal(f.checkpoint.state.commands['loading-complete'], undefined)
const picks = f.calls.filter(row => row.path.endsWith('/pick')).length
await writeFile(file, JSON.stringify(measurementReceipt()), 'utf8')
assert.equal((await runOverseasStage(f)).status, 'OVERSEAS_OUTBOUNDED')
assert.equal(f.calls.filter(row => row.path.endsWith('/pick')).length, picks)
const proof = f.checkpoint.state.loadingMeasurementEvidence
assert.equal(proof.source, 'EXPLICIT_FILE'); assert.equal(proof.file, file)
assert.equal(proof.sourceSha256, createHash('sha256').update(await readFile(file)).digest('hex'))
assert.deepEqual(proof.receipt, measurementReceipt())
assert.equal(proof.measurements[0].palletSpaces, 2)
const original = structuredClone(f.checkpoint.state.commands['loading-complete'].request), writes = f.calls.filter(row => row.method !== 'GET').length
await writeFile(file, JSON.stringify(measurementReceipt({ batch: 'OTHER-BATCH', pallets: [] })), 'utf8')
await runOverseasStage(f)
assert.deepEqual(f.checkpoint.state.commands['loading-complete'].request, original)
assert.deepEqual(f.checkpoint.state.loadingMeasurementEvidence, proof)
assert.equal(f.calls.filter(row => row.method !== 'GET').length, writes + 1) // only the existing read-only arrival lookup
} finally { await rm(join(directory, 'actual.json'), { force: true }); await rmdir(directory) }
})
test('actual file environment, batch, dispatch, pallet and field evidence are checked before any loading command', async () => {
const directory = await mkdtemp(join(tmpdir(), 'release-native-measurement-scope-'))
try {
const file = join(directory, 'actual.json')
for (const alter of [receipt => { receipt.environment = 'GRAY' }, receipt => { receipt.batch = 'OTHER-BATCH' },
receipt => { receipt.dispatchNo = 'OTHER-DISPATCH' }, receipt => { receipt.pallets[0].warehousePalletId = '999' },
receipt => { receipt.pallets[0].evidence[0].actual = false }]) {
const receipt = measurementReceipt(); alter(receipt)
await writeFile(file, JSON.stringify(receipt), 'utf8')
const f = fixture({ missingActual: true }); f.input = { ...f.input, loadingMeasurementFile: file }
await assert.rejects(runOverseasStage(f), /绑定不符|其他托盘|actual来源证据/)
assert.equal(f.calls.filter(row => /loading-complete|transfer-delivery|\/depart$/.test(row.path)).length, 0)
assert.equal(f.checkpoint.state.commands['loading-complete'], undefined)
}
} finally { await rm(join(directory, 'actual.json'), { force: true }); await rmdir(directory) }
})
test('measurement input rejects ambiguous or relative paths before HTTP', () => {
assert.throws(() => validateOverseasInput({ ...input, loadingMeasurementFile: 'relative.json' }, domestic), /绝对路径/)
assert.throws(() => validateOverseasInput({ ...input, loadingMeasurementFile: join(tmpdir(), 'actual.json'), loadingMeasurements: measurementReceipt() }, domestic), /不能同时提供/)
})
test('changed file after an unknown loading write cannot replace frozen original evidence or trigger resend', async () => {
const directory = await mkdtemp(join(tmpdir(), 'release-native-measurement-unknown-'))
try {
const file = join(directory, 'actual.json'), f = fixture({ missingActual: true })
f.input = { ...f.input, loadingMeasurementFile: file }
await writeFile(file, JSON.stringify(measurementReceipt()), 'utf8')
const originalRequest = f.context.request
f.context.request = async (...args) => {
const result = await originalRequest(...args)
if (args[0] === 'POST' && args[1].endsWith('/loading-complete')) throw Error('loading response lost')
return result
}
await assert.rejects(runOverseasStage(f), /loading response lost/)
assert.equal(f.checkpoint.state.commands['loading-complete'].status, 'INTENT')
const proof = structuredClone(f.checkpoint.state.loadingMeasurementEvidence), original = structuredClone(f.checkpoint.state.commands['loading-complete'].request)
const edited = measurementReceipt(); edited.pallets[0].palletSpaces = 3; edited.pallets[0].evidence[0].value = 3
await writeFile(file, JSON.stringify(edited), 'utf8')
const calls = f.calls.length
await assert.rejects(runOverseasStage(f), /结果未知/)
assert.equal(f.calls.length, calls)
assert.deepEqual(f.checkpoint.state.loadingMeasurementEvidence, proof)
assert.deepEqual(f.checkpoint.state.commands['loading-complete'].request, original)
} finally { await rm(join(directory, 'actual.json'), { force: true }); await rmdir(directory) }
})