Files

367 lines
26 KiB
JavaScript
Raw Permalink Normal View History

import test from 'node:test'
import assert from 'node:assert/strict'
2026-10-20 10:35:52 +08:00
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'
2026-10-20 10:35:52 +08:00
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/)
}
})
2026-10-20 10:35:52 +08:00
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 }] }
2026-10-20 10:35:52 +08:00
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) => {
2026-10-20 10:35:52 +08:00
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' })
2026-10-20 10:35:52 +08:00
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' }] }])
2026-10-20 10:35:52 +08:00
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' })
2026-10-20 10:35:52 +08:00
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])
})
2026-10-20 10:35:52 +08:00
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) }
})