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) } })