305 lines
25 KiB
JavaScript
305 lines
25 KiB
JavaScript
import test from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { after } from 'node:test'
|
|
import { writeFileSync, unlinkSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { randomUUID, createHash } from 'node:crypto'
|
|
import { verifyDispatchRepair } from './dispatch-repair-acceptance.mjs'
|
|
|
|
const commit = 'a'.repeat(40)
|
|
const ok = data => ({ http: 200, body: { code: '200', data: structuredClone(data) }, requestId: 'synthetic-request', traceId: 'synthetic-trace' })
|
|
const reject = (code = 'A0409', http = 409) => ({ http, body: { code }, requestId: 'synthetic-request', traceId: 'synthetic-trace' })
|
|
const fresh = more => ({ customerId: 'C', warehouseId: 'W', ...more })
|
|
const evidenceFiles = []
|
|
after(() => { for (const file of evidenceFiles) { assert.ok(file.startsWith(join(tmpdir(), 'dispatch-repair-proof-'))); unlinkSync(file) } })
|
|
function producerReport() {
|
|
const requests = []
|
|
const event = (method, path, body, result, actor = 'PLATFORM') => requests.push({ method, path, body, result, actor,
|
|
status: 'RESPONDED', http: 200, code: '200', requestId: `actual-producer-${requests.length}`, traceId: `actual-trace-${requests.length}` })
|
|
function stock(no, inboundCode, pallets, relabelNo) {
|
|
event('POST', '/api/platform/v1/last-leg/warehouse-transfers?customerId=C', { operatingWarehouseId: 'W', remark: 'CURRENT new stock' }, { transferNo: no, operatingWarehouseId: 'W' })
|
|
event('POST', `/api/pda/v1/last-leg/warehouse-transfers/${no}/palletize`, {}, { transferNo: no, operatingWarehouseId: 'W', inboundCode, relabelNo,
|
|
pallets: pallets.map(([warehousePalletId, cartonCount]) => ({ warehousePalletId, palletNo: `PAL-${warehousePalletId}`, cartonCount })) })
|
|
}
|
|
function candidate(planPalletId, warehousePalletId, inboundCode, extra = {}) {
|
|
const row = { planPalletId, warehousePalletId, inboundCode, orderRefs: inboundCode, customerId: 'C', overseasWarehouseId: 'W', sourceStage: 'RECEIVED', ...extra }
|
|
event('GET', '/api/platform/v1/last-leg/dispatches/candidates', undefined, [row]); return row
|
|
}
|
|
function dispatch(no, rows, relabelNo, packages = 1) {
|
|
event('POST', '/api/platform/v1/last-leg/dispatches/pre-arrange', { planPalletIds: rows.map(x => relabelNo ? x.warehousePalletId : x.planPalletId), relabelNo },
|
|
{ dispatchNo: no, overseasWarehouseId: 'W', packageCount: packages, items: rows })
|
|
}
|
|
function trip(no) { event('POST', '/api/platform/v1/last-leg/fleet/trips', { remark: 'CURRENT new trip' }, { trip: { tripNo: no, warehouseId: 'W' } }) }
|
|
function external(no, tripNo, transferNo, inboundCode, palletId, destinationPallet) {
|
|
trip(tripNo)
|
|
const booking = { bookingNo: no, tripNo, customerId: 'C', cargo: { priceBasis: 'PALLET', palletSpaces: 1, unitPrice: 2, currency: 'USD' }, agreedAmount: 2 }
|
|
event('POST', `/api/platform/v1/last-leg/fleet/trips/${tripNo}/bookings`, {}, { booking })
|
|
event('POST', `/api/platform/v1/last-leg/fleet/trips/${tripNo}/bookings/${no}/incoming`, {}, { booking: { ...booking, incomingNo: transferNo } })
|
|
event('POST', `/api/pda/v1/last-leg/warehouse-transfers/${transferNo}/palletize`, {}, { transferNo, inboundCode, operatingWarehouseId: 'W', pallets: [{ warehousePalletId: palletId, palletNo: `PAL-${palletId}`, cartonCount: 2 }] })
|
|
event('POST', `/api/platform/v1/last-leg/fleet/trips/${tripNo}/bookings/${no}/receipt-confirmation`, {}, { booking: { ...booking, status: 'RECEIVED', incomingNo: transferNo,
|
|
receipt: { source: { receiptHash: 'OLD', inboundCode, destinationKey: 'DST', pallets: [{ warehousePalletId: destinationPallet, packages: 2 }] } } } })
|
|
}
|
|
stock('TC', 'IN-C', [['101', 1]])
|
|
dispatch('DP', [candidate('11', '101', 'IN-C')])
|
|
stock('TR', 'IN-R', [['201', 2], ['202', 2]], 'RL')
|
|
for (const pallet of ['201', '202']) candidate(null, pallet, 'IN-R', { sourceStage: 'RELABEL', orderRefs: 'RL', orderNo: 'CUSTOMER-ORDER' })
|
|
for (const i of [1, 2]) {
|
|
stock(`TN${i}`, `IN-N${i}`, [[`30${i}`, 3]])
|
|
external(`BK${i}`, `T${i}`, `TE${i}`, `IN-E${i}`, `40${i}`, `30${i}`)
|
|
const row = candidate(`3${i}`, `30${i}`, i === 1 ? `IN-N${i}` : `IN-E${i}`)
|
|
dispatch(i === 1 ? 'DP-N' : 'DP-E', [row], undefined, 5)
|
|
}
|
|
external('BK', 'TR', 'TF', 'IN-F', '501', '31')
|
|
function pallet(palletId, palletCode, locationId) {
|
|
event('POST', '/api/platform/v1/overseas-warehouse-resources/W/pallets', { palletCode }, palletId)
|
|
event('GET', `/api/platform/v1/overseas-warehouse-resources/W/pallets/${palletId}`, undefined, { id: palletId, warehouseId: 'W', palletCode, locationId })
|
|
}
|
|
pallet('31', 'CURRENT-NEW-GEOMETRY', '113')
|
|
pallet('601', 'CURRENT-SOURCE', '114'); pallet('111', 'CURRENT-TARGET', '112')
|
|
event('GET', '/api/platform/v1/overseas-warehouse-resources/W/locations', undefined, { records: [{ id: '112', locationCode: 'LOCATION' }] })
|
|
event('GET', '/api/merchant/v1/iam/tenant/profile', undefined, { id: 'C' }, 'MERCHANT')
|
|
event('POST', '/api/merchant/v1/last-leg/return-orders', { remark: 'CURRENT new return' }, { order: { returnNo: 'RT', overseasWarehouseId: 'W' } }, 'MERCHANT')
|
|
event('GET', '/api/platform/v1/last-leg/return-inventory/cartons/BOX', undefined, { cartonMark: 'BOX', sourceReturnNo: 'RT', customerId: 'C', overseasWarehouseId: 'W', status: 'AVAILABLE', items: [{ skuCode: 'SKU-A', quantity: 3 }, { skuCode: 'SKU-B', quantity: 2 }] })
|
|
event('POST', '/api/merchant/v1/last-leg/relabel-applications', { operationWarehouseId: 'W' }, { application: { relabelNo: 'RL-N', customerId: 'C', overseasWarehouseId: 'W' } }, 'MERCHANT')
|
|
dispatch('NDP', [candidate(null, '601', null, { sourceStage: 'RELABEL', orderRefs: 'RL-N', returnCartonSource: true })], 'RL-N', 1)
|
|
event('POST', '/api/platform/v1/last-leg/dispatches/NDP/transfer-delivery', {}, { outboundNo: 'O', deliveries: [{ deliveryNo: 'D' }] })
|
|
event('GET', '/api/platform/v1/last-leg/delivery-orders/D/return-inbound-options?customerId=C', undefined, [{ customerId: 'C', lastMileOrderId: '91', deliveryNo: 'D', outboundNo: 'O', returnSources: [{ returnCartonId: '81', cartonMark: 'BOX', inventoryPositionId: null }] }])
|
|
return { environment: 'TEST', marker: 'CURRENT', sourceCommit: commit, requests }
|
|
}
|
|
function writeProducer(ctx, report = producerReport()) {
|
|
const bytes = Buffer.from(JSON.stringify(report)), path = join(tmpdir(), `dispatch-repair-proof-${randomUUID()}.json`)
|
|
writeFileSync(path, bytes); evidenceFiles.push(path); ctx.producerEvidence = { path, sha256: createHash('sha256').update(bytes).digest('hex') }
|
|
}
|
|
function context(handler = () => assert.fail('No request expected')) {
|
|
const ctx = { marker: 'CURRENT', environment: 'TEST', release: { lastlegHealthy: true, adminHealthy: true, schemaReady: true, lastlegCommit: commit },
|
|
createdResources: {}, calls: [], checkpoints: [],
|
|
async checkpoint() { this.checkpoints.push(structuredClone(this.createdResources.dispatchRepairAcceptance)) },
|
|
async request(method, path, body) { this.calls.push({ method, path, body: structuredClone(body) }); return handler(method, path, body) } }
|
|
writeProducer(ctx); return ctx
|
|
}
|
|
const execute = (ctx, fixtures) => verifyDispatchRepair(ctx, fixtures, { execute: true })
|
|
|
|
test('import/plan is zero-network and missing current fixtures never become a pass', async () => {
|
|
const ctx = context(); assert.equal((await verifyDispatchRepair(ctx, {})).status, 'PLAN_ONLY'); assert.equal(ctx.calls.length, 0)
|
|
const result = await execute(ctx, {})
|
|
assert.equal(result.status, 'PARTIAL_NOT_ALL_VERIFIED')
|
|
assert.equal(Object.values(result.cases).every(row => row.status === 'NOT_RUN'), true)
|
|
assert.equal(result.sourceEventsVerified, false)
|
|
})
|
|
for (const field of ['lastlegHealthy', 'adminHealthy', 'schemaReady']) test(`unready ${field} blocks before any HTTP`, async () => {
|
|
const ctx = context(); ctx.release[field] = false
|
|
await assert.rejects(execute(ctx, {}), /healthy TEST releases/); assert.equal(ctx.calls.length, 0)
|
|
})
|
|
test('production target and an old run fixture cannot borrow TEST authorization', async () => {
|
|
const ctx = context(); ctx.environment = 'PRODUCTION'; await assert.rejects(execute(ctx, {}), /TEST releases/)
|
|
const owned = context(); await assert.rejects(execute(owned, { departedGate: fresh({ outboundNo: 'OLD-O', deliveryNo: 'OLD-D' }) }), /Unowned outbound/)
|
|
assert.equal(owned.calls.length, 0)
|
|
})
|
|
test('self-declared current flags cannot authorize an old dispatch absent from producer receipts', async () => {
|
|
const ctx = context()
|
|
await assert.rejects(execute(ctx, { cancellation: fresh({ dispatchNo: 'OLD-DP', allowDispatchCancellation: true,
|
|
marker: 'CURRENT', createdInThisRun: true, producerRequestId: 'claimed-current-request', producerTraceId: 'claimed-current-trace',
|
|
transferBody: {}, rearrangeBody: { planPalletIds: ['11'] } }) }), /Unowned dispatch/)
|
|
assert.equal(ctx.calls.length, 0)
|
|
})
|
|
test('a later foreign shipment blocks all cases before the first HTTP request', async () => {
|
|
const ctx = context()
|
|
await assert.rejects(execute(ctx, { cancellation: fresh({ dispatchNo: 'DP', allowDispatchCancellation: true,
|
|
transferBody: {}, rearrangeBody: { planPalletIds: ['11'] } }), departedGate: fresh({ outboundNo: 'OLD-O', deliveryNo: 'D' }) }), /Unowned outbound/)
|
|
assert.equal(ctx.calls.length, 0)
|
|
})
|
|
test('changed producer hash or response warehouse binding is rejected with zero HTTP', async () => {
|
|
const ctx = context(); ctx.producerEvidence.sha256 = '0'.repeat(64)
|
|
await assert.rejects(execute(ctx, {}), /hash mismatch/); assert.equal(ctx.calls.length, 0)
|
|
const scoped = context(), report = producerReport()
|
|
report.requests.find(row => row.method === 'POST' && row.path.endsWith('/pre-arrange')).result.overseasWarehouseId = 'OTHER'
|
|
writeProducer(scoped, report)
|
|
await assert.rejects(execute(scoped, {}), /Dispatch birth response/); assert.equal(scoped.calls.length, 0)
|
|
})
|
|
test('failed pre-send checkpoint leaves the command unsent', async () => {
|
|
const ctx = context(); ctx.checkpoint = async () => { throw Error('disk full') }
|
|
await assert.rejects(execute(ctx, { departedGate: fresh({ outboundNo: 'O', deliveryNo: 'D' }) }), /disk full/)
|
|
assert.equal(ctx.calls.length, 0)
|
|
})
|
|
|
|
test('new unpicked dispatch cancels outbound, retains cancelled history, re-arranges released pallets and replays old transfer', async () => {
|
|
let cancelled = false, dispatchCancelled = false, transfers = 0
|
|
const ctx = context((method, path, body) => {
|
|
if (path.endsWith('/transfer-delivery')) return ok({ outboundNo: 'O', deliveries: [{ deliveryNo: 'D' }], replayed: transfers++ > 0 })
|
|
if (path.includes('/delivery-orders/') && path.includes('/status')) { assert.ok(['DELIVERING', 'REJECTED', 'CANCELLED'].includes(body.status)); assert.equal(body.expectedVersion, 0); return reject() }
|
|
if (path.includes('/delivery-orders/')) return ok({ order: { deliveryNo: 'D', outboundNo: 'O', status: cancelled ? 'CANCELLED' : 'WAIT_DELIVERY', version: 0, packageCount: 1 } })
|
|
if (path.endsWith('/outbound-orders/O/cancel')) { cancelled = true; return ok(null) }
|
|
if (path.endsWith('/outbound-orders/O')) return ok({ outbound: { status: cancelled ? 'CANCELLED' : 'WAIT_OUTBOUND' } })
|
|
if (method === 'DELETE') { assert.equal(path, '/api/platform/v1/last-leg/dispatches/DP'); assert.equal(cancelled, true); dispatchCancelled = true; return ok(null) }
|
|
if (path.endsWith('/dispatches/DP')) return ok({ status: dispatchCancelled ? 'CANCELLED' : 'PENDING_DEPARTURE', createdAt: '2026-10-18T16:00:00', actions: { canTransfer: true } })
|
|
if (path.includes('/dispatches/candidates?')) return ok([{ planPalletId: '11' }])
|
|
if (path.endsWith('/pre-arrange')) { assert.deepEqual(body.planPalletIds, ['11']); assert.equal(dispatchCancelled, true); return ok({ dispatchNo: 'DP-NEW', status: 'PENDING_DEPARTURE', items: [{ planPalletId: '11' }] }) }
|
|
assert.fail(path)
|
|
})
|
|
const result = await execute(ctx, { cancellation: fresh({ allowDispatchCancellation: true, dispatchNo: 'DP', transferBody: { fees: [] }, rearrangeBody: { planPalletIds: ['11'] }, candidateQuery: { sourceStage: 'RECEIVED' } }) })
|
|
assert.equal(result.cases.cancellation.status, 'PASS')
|
|
assert.equal(ctx.calls.filter(row => row.method === 'DELETE').length, 1)
|
|
assert.equal(ctx.calls.some(row => row.method === 'DELETE' && row.path.includes('/items/')), false)
|
|
})
|
|
|
|
test('domain rejection cannot be faked by a missing permission; actual shipment cancellation leaves facts unchanged', async () => {
|
|
for (const forbidden of [false, true]) {
|
|
const order = { status: 'WAIT_DELIVERY', version: 4, packageCount: 1, outboundNo: 'O' }
|
|
const ctx = context((method, path) => {
|
|
if (method === 'GET') return ok(path.includes('/outbound-orders/') ? { outbound: { status: 'OUTBOUNDED' } } : { order })
|
|
return forbidden ? reject('A0403', 403) : reject()
|
|
})
|
|
const run = execute(ctx, { departedGate: fresh({ outboundNo: 'O', deliveryNo: 'D' }) })
|
|
if (forbidden) await assert.rejects(run, /Unexpected result/)
|
|
else assert.equal((await run).cases.departedGate.status, 'PASS')
|
|
}
|
|
})
|
|
|
|
for (const broken of [false, true]) test(`two-pallet relabel rejects partial selection with unchanged candidates then accepts all; broken=${broken}`, async () => {
|
|
const ids = ['201', '202']
|
|
const ctx = context((method, path, body) => {
|
|
if (path.includes('/relabel-applications/')) return ok({ application: { relabelNo: 'RL', status: 'READY_FOR_OUTBOUND', fulfillmentStatus: 'WAIT_PRE_DISPATCH' } })
|
|
if (path.includes('/candidates?')) return ok(ids.map(warehousePalletId => ({ orderRefs: 'RL', orderNo: 'CUSTOMER-ORDER', warehousePalletId, planPalletId: null })))
|
|
assert.equal(method, 'POST'); assert.equal(body.relabelNo, 'RL')
|
|
if (body.planPalletIds.length === 1 && !broken) return reject()
|
|
return ok({ status: 'PENDING_DEPARTURE', dispatchNo: 'DP', items: body.planPalletIds.map(warehousePalletId => ({ warehousePalletId, planPalletId: null })) })
|
|
})
|
|
const run = execute(ctx, { wholeRelabel: fresh({ relabelNo: 'RL' }) })
|
|
if (broken) await assert.rejects(run, /Unexpected result/)
|
|
else assert.equal((await run).cases.wholeRelabel.status, 'PASS')
|
|
})
|
|
|
|
function mixedFixture(fault = '') {
|
|
const samples = [1, 2].map(i => ({ displayKind: i === 1 ? 'ORDINARY' : 'EXTERNAL', dispatchNo: i === 1 ? 'DP-N' : 'DP-E',
|
|
ordinaryInboundCode: `IN-N${i}`, externalInboundCode: `IN-E${i}`, bookingNo: `BK${i}`, palletId: `30${i}` }))
|
|
const ctx = context((method, path) => {
|
|
if (path.includes('/inventory/balances') && !path.includes('/positions')) return ok({ total: 4,
|
|
records: [1, 2].flatMap(i => ['N', 'E'].map(kind => ({ id: `${kind}${i}`, customerId: 'C', overseasWarehouseId: 'W', inboundCode: `IN-${kind}${i}` }))) })
|
|
if (path.includes('/positions')) {
|
|
const balance = path.match(/balances\/([NE][12])\//)[1], kind = balance[0], i = balance[1]
|
|
return ok([{ id: `POS-${balance}`, customerId: 'C', overseasWarehouseId: 'W', inboundCode: `IN-${balance}`,
|
|
palletId: `30${i}`, availablePackages: kind === 'N' ? fault === 'wrong-component' ? 4 : 3 : 2, pickedPackages: 0 }])
|
|
}
|
|
const sample = samples.find(row => path.includes(row.dispatchNo)), i = sample.dispatchNo === 'DP-N' ? 1 : 2
|
|
if (path.endsWith('/external-freight')) return ok({ requiresDeliveryFee: true, snapshotHash: `hash-${i}`, quotes: [{ bookingNo: `BK${i}`, inboundCode: `IN-E${i}`,
|
|
quantity: fault === 'wrong-quantity' ? 2 : 1, amount: fault === 'wrong-amount' ? 3 : 2, unitPrice: 2, currency: 'USD', destinationKey: 'DST' }] })
|
|
return ok({ overseasWarehouseId: 'W', items: [{ warehousePalletId: `30${i}`, palletNo: `PAL-30${i}`,
|
|
inboundCode: i === 1 && fault !== 'wrong-display' ? 'IN-N1' : `IN-E${i}` }] })
|
|
})
|
|
if (fault === 'duplicate-sample') samples[1] = { ...samples[0], displayKind: 'EXTERNAL' }
|
|
return { ctx, fixture: fresh({ samples }) }
|
|
}
|
|
test('independent mixed display permutations prove per-source packages and commercial quantity/amount', async () => {
|
|
const f = mixedFixture(), result = await execute(f.ctx, { mixedSources: f.fixture })
|
|
assert.equal(result.cases.mixedSources.status, 'PASS')
|
|
assert.deepEqual(result.cases.mixedSources.evidence.map(x => x.expectedSources.map(y => y.packages)), [[3, 2], [3, 2]])
|
|
})
|
|
for (const fault of ['duplicate-sample', 'wrong-display', 'wrong-component', 'wrong-quantity', 'wrong-amount']) test(`mixed source ${fault} cannot pass`, async () => {
|
|
const f = mixedFixture(fault)
|
|
await assert.rejects(execute(f.ctx, { mixedSources: f.fixture }), /independent|Displayed source|Physical pallet|external quote/)
|
|
assert.ok(f.ctx.calls.every(row => row.method === 'GET'))
|
|
if (fault === 'duplicate-sample') assert.equal(f.ctx.calls.length, 0)
|
|
})
|
|
|
|
test('changed receiving geometry requires fresh measurements and new key; exact confirmation replay remains identical', async () => {
|
|
const bodies = []
|
|
const receipt = { source: { receiptHash: 'NEW', pallets: [{ warehousePalletId: '31', packages: 2 }] }, pallets: [{ warehousePalletId: '31', palletSpaces: 1, weightKg: 7, volumeCbm: 0.5 }] }
|
|
const ctx = context((method, path, body) => {
|
|
if (method === 'GET' && path.endsWith('/bookings')) return ok({ records: [{ bookingNo: 'BK', customerId: 'C', status: 'RECEIVED', dispatchNo: null, receipt: { source: { receiptHash: 'OLD' } } }] })
|
|
if (method === 'GET') return ok({ tripRecordVersion: 5, recordVersion: 6, incoming: { receiptHash: 'NEW', pallets: [{ warehousePalletId: '31', weightKg: null, volumeCbm: null }] } })
|
|
bodies.push(structuredClone(body)); assert.equal(body.receiptHash, 'NEW'); assert.equal(body.tripRecordVersion, 5); assert.equal(body.recordVersion, 6)
|
|
if (bodies.length === 1) { assert.equal(body.pallets[0].weightKg, undefined); return reject() }
|
|
assert.deepEqual(body.pallets, receipt.pallets)
|
|
return ok({ booking: { receipt }, replayed: bodies.length === 3 })
|
|
})
|
|
const result = await execute(ctx, { receiptRefresh: fresh({ tripNo: 'TR', bookingNo: 'BK', oldReceiptHash: 'OLD', unitPrice: 2,
|
|
measurements: [{ warehousePalletId: '31', weightKg: 7, volumeCbm: 0.5 }] }) })
|
|
assert.equal(result.cases.receiptRefresh.status, 'PASS')
|
|
assert.notEqual(bodies[0].idempotencyKey, bodies[1].idempotencyKey); assert.deepEqual(bodies[1], bodies[2])
|
|
})
|
|
|
|
function nativeFixture(fault = '') {
|
|
let marked = false, completeCount = 0
|
|
const quantity = sku => sku === 'SKU-A' ? 3 : 2
|
|
const page = (u, rows) => ok({ total: rows.length, records: rows.slice(Number(u.searchParams.get('page') || 1) - 1, Number(u.searchParams.get('page') || 1)) })
|
|
const ctx = context((method, path, body) => {
|
|
const u = new URL(path, 'https://test.invalid'), sku = u.searchParams.get('skuCode'), Q = quantity(sku)
|
|
if (u.pathname === '/api/platform/v1/last-leg/return-inventory') {
|
|
let total = completeCount ? Q : 0
|
|
if (fault === 'missing-sku' && sku === 'SKU-B') total = 0
|
|
if (fault === 'replay-adds-stock' && completeCount >= 2) total = Q * 2
|
|
return page(u, [{ customerId: 'OTHER', overseasWarehouseId: 'W', skuCode: sku, totalQuantity: 999, availableQuantity: 999, cartonQuantity: 9 },
|
|
{ customerId: 'C', overseasWarehouseId: 'W', skuCode: `${sku}-FUZZY`, totalQuantity: 999, availableQuantity: 999, cartonQuantity: 9 },
|
|
{ customerId: 'C', overseasWarehouseId: 'W', skuCode: sku, totalQuantity: total,
|
|
availableQuantity: fault === 'availability-not-restored' ? 0 : total,
|
|
cartonQuantity: completeCount && fault !== 'cartons-not-restored' ? 1 : 0,
|
|
updatedAt: fault === 'replay-updates-time' && completeCount >= 2 ? '2026-10-18T16:02:00' : completeCount ? '2026-10-18T16:01:00' : '2026-10-18T16:00:00' }])
|
|
}
|
|
if (u.pathname.endsWith('/return-inventory/movements')) {
|
|
const base = { customerId: 'C', overseasWarehouseId: 'W', skuCode: sku, occurredAt: '2026-10-18T16:00:00' }
|
|
const incoming = { ...base, movementType: 'EXCEPTION_RETURN_IN', referenceNo: 'NATIVE-RECEIPT', totalDelta: Q, availableDelta: Q, occurredAt: '2026-10-18T16:01:00' }
|
|
const rows = [{ ...base, skuCode: `${sku}-FUZZY`, movementType: 'OUTBOUND', referenceNo: 'NDP', totalDelta: -999, availableDelta: -999 },
|
|
{ ...base, movementType: 'OUTBOUND', referenceNo: fault === 'wrong-outbound-reference' ? 'O' : 'NDP', totalDelta: -Q, availableDelta: -Q }]
|
|
if (completeCount && !(fault === 'missing-movement' && sku === 'SKU-B')) rows.push(incoming)
|
|
if (fault === 'duplicate-incoming' && completeCount) rows.push(incoming)
|
|
if (fault === 'replay-adds-movement' && completeCount >= 2) rows.push({ ...incoming, occurredAt: '2026-10-18T16:02:00' })
|
|
return page(u, rows)
|
|
}
|
|
if (path.includes('/return-inventory/cartons/')) return ok({ cartonMark: 'BOX', status: completeCount ? 'AVAILABLE' : 'OUTBOUNDED',
|
|
customerId: 'C', overseasWarehouseId: 'W', palletCode: fault === 'wrong-target' ? 'WRONG-TARGET' : 'CURRENT-TARGET', locationCode: 'LOCATION', putawayAt: completeCount ? '2026-10-18T16:01:00' : null,
|
|
items: [{ skuCode: 'SKU-A', quantity: 3, availableQuantity: completeCount ? 3 : 0 }, { skuCode: 'SKU-B', quantity: 2, availableQuantity: completeCount ? 2 : 0 }] })
|
|
if (path.includes('/return-inbound-options')) return ok([{ lastMileOrderId: fault === 'wrong-master' ? '92' : '91', customerId: 'C', inboundOrderId: null, inboundCode: null, deliveryNo: 'D', outboundNo: 'O',
|
|
returnSources: [{ returnCartonId: '81', inventoryPositionId: null, outboundPalletItemId: '71', cartonMark: 'BOX', maxReturnPackages: 1 }] }])
|
|
if (path.includes('/mark-return')) {
|
|
assert.equal(body.lastMileOrderId, '91'); assert.equal(Object.hasOwn(body, 'inboundOrderId'), false)
|
|
assert.deepEqual(body.items, [{ outboundPalletItemId: '71', returnCartonId: '81', packageCount: 1 }])
|
|
const replayed = marked; marked = true; return ok({ exceptionNo: 'EX', version: 0, replayed })
|
|
}
|
|
if (path.includes('/start-processing')) return ok({ version: 1 })
|
|
if (path.includes('/complete')) { assert.equal(body.receipts[0].inventoryPositionId, null); assert.equal(body.receipts[0].returnCartonId, '81'); return ok({ status: 'COMPLETED', restoredPackages: 1, replayed: completeCount++ > 0 }) }
|
|
if (method === 'GET' && path.includes('/exception-orders/')) return ok({ returnReceipts: [{ receiptNo: 'NATIVE-RECEIPT', receivedPackages: 1, receivedAt: '2026-10-18T16:01:00' }] })
|
|
if (method === 'GET' && path.includes('/delivery-orders/')) return ok({ order: { status: 'RETURNED', outboundNo: 'O', packageCount: 1, version: 2 } })
|
|
assert.fail(path)
|
|
})
|
|
ctx.requestMultipart = async (method, path, file) => { assert.match(new TextDecoder().decode(file.content), /SYNTHETIC TEST/); return ok({ fileId: '101' }) }
|
|
return { ctx, fixture: fresh({ cartonMark: 'BOX', returnCartonId: '81', lastMileOrderId: '91', deliveryNo: 'D', outboundNo: 'O',
|
|
target: { targetPalletId: '111', targetPalletNo: 'CURRENT-TARGET', targetLocationId: '112', targetLocationCode: 'LOCATION' } }) }
|
|
}
|
|
test('native recovery reads exact paged SKU stock between first completion and replay', async () => {
|
|
const f = nativeFixture(), result = await execute(f.ctx, { nativeCartonReturn: f.fixture })
|
|
const evidence = result.cases.nativeCartonReturn.evidence
|
|
assert.equal(result.cases.nativeCartonReturn.status, 'PASS')
|
|
assert.deepEqual(evidence.S.stock.map(x => x.total), [0, 0]); assert.deepEqual(evidence.R.stock.map(x => x.total), [3, 2]); assert.deepEqual(evidence.Rprime, evidence.R)
|
|
assert.deepEqual(evidence.R.stock.map(x => x.availableView), [3, 2]); assert.deepEqual(evidence.R.stock.map(x => x.cartons), [1, 1])
|
|
const completes = f.ctx.calls.map((row, i) => row.path.includes('/complete') ? i : -1).filter(i => i >= 0)
|
|
assert.equal(completes.length, 2)
|
|
assert.ok(f.ctx.calls.slice(completes[0] + 1, completes[1]).some(row => row.path.includes('/return-inventory?')))
|
|
assert.ok(f.ctx.calls.some(row => row.path.includes('page=3')))
|
|
})
|
|
for (const fault of ['wrong-master', 'missing-sku', 'availability-not-restored', 'cartons-not-restored', 'missing-movement', 'duplicate-incoming', 'replay-adds-stock', 'replay-adds-movement', 'replay-updates-time', 'wrong-target', 'wrong-outbound-reference']) test(`native recovery ${fault} cannot pass`, async () => {
|
|
const f = nativeFixture(fault)
|
|
await assert.rejects(execute(f.ctx, { nativeCartonReturn: f.fixture }), /Actual native|Every original|Native replay|exact validated|Original single-carton/)
|
|
if (fault === 'wrong-master') assert.ok(f.ctx.calls.every(row => row.method === 'GET'))
|
|
})
|
|
test('an old target pallet cannot be authorized by a valid native carton fixture', async () => {
|
|
const f = nativeFixture(); f.fixture.target.targetPalletId = 'OLD-PALLET'
|
|
await assert.rejects(execute(f.ctx, { nativeCartonReturn: f.fixture }), /Unowned pallet/)
|
|
assert.equal(f.ctx.calls.length, 0)
|
|
})
|
|
|
|
test('read-only list permission allows detail but rejects cancellation with unchanged outbound', async () => {
|
|
const outbound = { status: 'WAIT_OUTBOUND', version: 0, packageCount: 1 }
|
|
const ctx = context(() => ok({ outbound }))
|
|
ctx.requestReadonly = async (method) => method === 'GET' ? ok({ outbound }) : reject('A0403', 403)
|
|
assert.equal((await execute(ctx, { permissions: fresh({ outboundNo: 'O' }) })).cases.permissions.status, 'PASS')
|
|
})
|
|
|
|
test('unknown command response is checkpointed and never automatically retried', async () => {
|
|
const ctx = context((method) => method === 'GET' ? ok({ outbound: { status: 'OUTBOUNDED' } }) : { http: 0, body: null })
|
|
// The uncorrelated read itself stops before any command or automatic recovery.
|
|
ctx.request = async function (method, path) { this.calls.push({ method, path }); return { http: 0, body: null } }
|
|
await assert.rejects(execute(ctx, { departedGate: fresh({ outboundNo: 'O', deliveryNo: 'D' }) }), /Unknown transport/)
|
|
assert.equal(ctx.calls.length, 1); assert.equal(ctx.checkpoints.at(-1).requests.at(-1).status, 'UNKNOWN_RESULT')
|
|
})
|