import test from 'node:test' import assert from 'node:assert/strict' import { mkdtempSync, mkdirSync, readFileSync, writeFileSync, rmSync } from 'node:fs' import { join, resolve, dirname } from 'node:path' import { tmpdir } from 'node:os' import { createHash } from 'node:crypto' import { selectReturnSources, verifyFleetReturnRedispatch, waitForRedispatchSignatureAudit } from './fleet-return-redispatch-acceptance.mjs' function fixture(fault = '') { const trip = { tripNo: 'TR-ORIGINAL' }, requests = [], checkpoints = [] let returned = false, originalSigned = false, newSigned = false, departed = false const relations = ['A', 'B'].map((name, index) => ({ lastMileOrderId: name, inboundOrderId: String(index + 10), inboundCode: `IN-${name}`, deliveryNo: 'DL-ORIGINAL', outboundNo: 'OUT-ORIGINAL', returnSources: [{ outboundPalletItemId: String(index + 20), inventoryPositionId: String(index + 30), maxReturnPackages: index ? 3 : 4 }] })) const tail = name => ({ orderType: 'FIRST_LEG', sourceInboundId: `DOMESTIC-${name}`, customerId: 'C', overseasWarehouseId: 'W', inboundCode: `IN-${name}`, packageCount: name === 'A' ? 4 : 3, subOrderStatus: name === 'A' ? newSigned || fault === 'premature-A' && originalSigned ? 'DELIVERY:DELIVERED' : 'DELIVERY:WAIT_DELIVERY' : originalSigned && fault !== 'blocked-B' ? 'DELIVERY:DELIVERED' : 'DELIVERY:WAIT_DELIVERY' }) const order = name => name === 'DL-NEW' ? { deliveryNo: name, dispatchNo: 'DP-NEW', packageCount: 2, signedPackages: newSigned ? 2 : null, signedAt: newSigned ? 'new-signature' : null } : { deliveryNo: 'DL-ORIGINAL', dispatchNo: 'DP-ORIGINAL', outboundNo: 'OUT-ORIGINAL', status: 'DELIVERING', packageCount: 7, signedPackages: originalSigned ? 5 : null, signedAt: originalSigned ? newSigned && fault === 'rewritten-original' ? 'changed' : 'original-signature' : null } const envelope = data => ({ http: 200, body: { code: '200', data: structuredClone(data) }, requestId: 'request-id', traceId: 'trace-id' }) const context = { marker: 'FRESH', createdResources: { fleetTrips: [trip], fleetAcceptance: { marker: 'FRESH', customerId: 'C', warehouseId: 'W', tripNo: trip.tripNo, departure: { status: 'IN_TRANSIT', dispatches: [{ dispatchNo: 'DP-ORIGINAL' }] } } }, assert() {}, async checkpoint() { checkpoints.push(structuredClone(trip.returnRedispatch)) }, async requestMultipart(method, path, file) { requests.push({ method, path, file: file.name }); return envelope({ fileId: '99' }) }, async request(method, path, body, token) { requests.push({ method, path, body: structuredClone(body), token }) if (path.startsWith('/api/platform/inbounds?')) { const code = new URLSearchParams(path.split('?')[1]).get('inboundCode'), name = code.slice(-1) const delivered = name === 'A' ? newSigned || fault === 'domestic-premature-A' && originalSigned : originalSigned return envelope({ records: [{ inboundCode: code, processStatus: delivered ? 'DELIVERED' : 'LOADED' }] }) } if (path.includes('/orders/')) return envelope({ order: tail(path.split('/orders/')[1].split('?')[0]) }) if (path.includes('/return-inbound-options')) return envelope(fault === 'missing-B' ? relations.slice(0, 1) : relations) if (path.includes('/mark-return')) return envelope({ exceptionNo: 'EX-NEW', version: 0 }) if (path.includes('/start-processing')) return envelope({ status: 'PROCESSING', version: 1 }) if (path.includes('/exception-orders/') && path.includes('/complete')) { const replayed = returned; returned = true if (fault === 'unknown-return') throw Error('connection lost after commit') return envelope({ status: 'COMPLETED', restoredPackages: fault === 'wrong-return' ? 3 : 2, inventoryAdjustmentId: '88', replayed }) } if (path.endsWith('/pallets') && method === 'POST') { context.palletCode = body.palletCode; return envelope('100') } if (path.endsWith('/pallets/100')) return envelope({ warehouseId: 'W', locationId: '200', palletCode: context.palletCode }) if (path.includes('/locations?')) return envelope({ records: [{ id: '200', locationCode: 'L-1' }] }) if (path.includes('/dispatches/candidates')) return envelope([{ sourceType: 'RETURN_RECEIPT', returnReceiptId: '300', returnReceiptNo: 'RSR-300', warehousePalletId: '100', orderId: 'A', packageCount: fault === 'wrong-candidate' ? 4 : 2 }]) if (path.endsWith('/drafts/containers')) return envelope({ id: '400' }) if (path.endsWith('/draft-locks/lock')) { assert.equal(body.sourceType, 'RETURN_RECEIPT'); assert.equal(body.planPalletId, undefined); return envelope(null) } if (path.endsWith('/pre-arrange')) { assert.deepEqual(body.planPalletIds, []); assert.deepEqual(body.returnReceiptIds, ['300']); return envelope({ dispatchNo: 'DP-NEW', packageCount: 2 }) } if (path.endsWith('/fleet/trips/TR-NEW')) return envelope({ trip: { vehiclePlate: 'NEW', driverName: 'driver', driverPhone: 'phone' } }) if (path.endsWith('/transfer-delivery')) return envelope({ deliveries: [{ deliveryNo: 'DL-NEW' }] }) if (path.endsWith('/depart')) { assert.equal(token, 'private-pda-token'); const replayed = departed; departed = true; return envelope({ tripNo: 'TR-NEW', shippedPackages: 2, replayed }) } if (path.includes('/delivery-orders/')) return envelope({ order: order(path.split('/delivery-orders/')[1].split('?')[0]) }) throw Error(`Unexpected ${method} ${path}`) } } const dependencies = { async onBeforeRedispatchSignature(proof) { assert.equal(newSigned, false); assert.equal(originalSigned, true); assert.equal(proof.preSignatureGate.status, 'WAITING') if (fault === 'audit-gate') throw Error('Audit gate not approved') return { sourceAEventCount: 0, sourceBEventCount: 1, auditEvidenceSha256: 'a'.repeat(64) } }, async prepareFleetTrip() { return { tripNo: 'TR-NEW' } }, async confirmFleetLoading() { return 1 }, async pickAll() { return true }, async finishFleetTrip() { return { status: 'CARGO_COMPLETED_AND_RESOURCES_RELEASED' } }, async saveReceiptAndComplete(_ctx, delivery) { if (delivery.deliveryNo === 'DL-NEW') newSigned = true; else originalSigned = true; return { order: order(delivery.deliveryNo) } }, } const input = { customerId: 'C', warehouseId: 'W', pickerToken: 'private-pda-token', dispatchNos: ['DP-ORIGINAL'], deliveryNos: ['DL-ORIGINAL'] } return { context, trip, input, dependencies, requests, checkpoints } } const run = f => verifyFleetReturnRedispatch(f.context, 'test', f.trip, f.input, f.dependencies) for (const signedPackages of [undefined, null, '5', -1, 1.5]) test(`unknown or invalid original signed quantity ${signedPackages} stops before redispatch`, async () => { const f = fixture(), save = f.dependencies.saveReceiptAndComplete f.dependencies.saveReceiptAndComplete = async (...args) => { const result = await save(...args) result.order.signedPackages = signedPackages return result } f.dependencies.prepareFleetTrip = () => assert.fail('invalid signed quantity must stop before preparing redispatch') await assert.rejects(run(f), /stored quantity/) assert.equal(f.trip.returnRedispatch.originalSignatures.length, 0) }) for (const signedPackages of [undefined, null, '2', 0, 3]) test(`redispatch requires actual numeric two signed boxes: ${signedPackages}`, async () => { const f = fixture(), save = f.dependencies.saveReceiptAndComplete f.dependencies.saveReceiptAndComplete = async (...args) => { const result = await save(...args) if (args[1].deliveryNo === 'DL-NEW') result.order.signedPackages = signedPackages return result } f.dependencies.finishFleetTrip = () => assert.fail('invalid new signature must not release trip resources') await assert.rejects(run(f), /two boxes actually signed/) }) test('reviewed candidate recovery retains DELIVERING and never repeats original delivery start', async () => { const f = fixture(); f.input.sourceSelectionRecovery = true assert.equal((await run(f)).status, 'HTTP_CHAIN_VERIFIED_SOURCE_EVENT_EVIDENCE_REQUIRED') assert.ok(!f.requests.some(row => row.path?.endsWith('/status?customerId=C'))) }) test('reviewed source recovery refuses WAIT_DELIVERY before any delivery-start or return command', async () => { const f = fixture(); f.input.sourceSelectionRecovery = true const request = f.context.request f.context.request = async (...args) => { const r = await request(...args) if (r.body?.data?.order?.deliveryNo === 'DL-ORIGINAL') r.body.data.order.status = 'WAIT_DELIVERY' return r } await assert.rejects(run(f), /never start it again/) assert.ok(f.requests.every(row => row.method === 'GET')) }) test('fresh return/redispatch chain proves A pending, B complete, then A complete without claiming unique source events', async () => { const f = fixture(), result = await run(f) assert.equal(result.status, 'HTTP_CHAIN_VERIFIED_SOURCE_EVENT_EVIDENCE_REQUIRED') assert.equal(result.sourceEventsVerified, false) assert.equal(result.readOnlyEvidenceRequired.length, 3) assert.equal(result.beforeRedispatch.A.order.subOrderStatus, 'DELIVERY:WAIT_DELIVERY') assert.equal(result.beforeRedispatch.B.order.subOrderStatus, 'DELIVERY:DELIVERED') assert.equal(result.afterRedispatch.A.order.subOrderStatus, 'DELIVERY:DELIVERED') assert.equal(result.afterRedispatch.departed.shippedPackages, 2) assert.ok(!JSON.stringify(f.checkpoints).includes('private-pda-token')) assert.ok(f.checkpoints.some(row => row.requests.at(-1)?.status === 'SENDING' && row.requests.at(-1)?.file?.sha256)) const uploads = f.trip.returnRedispatch.requests.filter(row => row.file) assert.ok(uploads.length > 0) assert.ok(uploads.every(row => Number.isSafeInteger(row.file.sizeBytes) && row.file.sizeBytes > 0 && !Object.hasOwn(row.file, 'bytes'))) }) for (const missing of [false, true]) test(`unapproved or missing pre-signature gate cannot sign or close new trip; missing=${missing}`, async () => { const f = fixture('audit-gate') if (missing) delete f.dependencies.onBeforeRedispatchSignature const original = f.dependencies.saveReceiptAndComplete f.dependencies.saveReceiptAndComplete = async (...args) => { assert.notEqual(args[1].deliveryNo, 'DL-NEW'); return original(...args) } f.dependencies.finishFleetTrip = () => assert.fail('new trip must not close before audited signature') await assert.rejects(run(f), missing ? /audit gate is required/ : /Audit gate not approved/) assert.equal(f.trip.returnRedispatch.preSignatureGate.status, 'WAITING') assert.equal(f.trip.returnRedispatch.afterRedispatch, undefined) }) for (const fault of ['none', 'nonce', 'counts', 'hash', 'trip', 'timeout']) test(`file gate requires exact checkpoint and hashed source audit: ${fault}`, async () => { const root = mkdtempSync(join(tmpdir(), 'fleet-signature-gate-')), output = join(root, 'reports', 'run') mkdirSync(output, { recursive: true }) const context = { marker: 'CURRENT', context: { fleetRunId: 'FRESH' }, createdResources: { fleetAcceptance: { tripNo: 'TR-ORIGINAL', customerId: 'C' } } } const proof = { newTripNo: 'TR-NEW', sources: { A: { tail: { sourceInboundId: 'A' } }, B: { tail: { sourceInboundId: 'B' } } } } try { const wait = async () => { const checkpoint = JSON.parse(readFileSync(join(output, 'before-redispatch-signature.json'), 'utf8')) assert.equal(checkpoint.proof.newTripNo, 'TR-NEW'); assert.equal(checkpoint.sourceAInboundId, 'A') const evidence = Buffer.from('{"A":0,"B":1}') writeFileSync(join(root, 'reports', 'audit.json'), evidence) writeFileSync(join(output, 'approve-before-redispatch-signature.json'), JSON.stringify({ gateNonce: fault === 'nonce' ? 'OTHER' : checkpoint.gateNonce, marker: checkpoint.marker, newTripNo: fault === 'trip' ? 'OTHER' : checkpoint.newTripNo, sourceAEventCount: fault === 'counts' ? 1 : 0, sourceBEventCount: 1, auditEvidencePath: 'reports/audit.json', auditEvidenceSha256: fault === 'hash' ? 'a'.repeat(64) : createHash('sha256').update(evidence).digest('hex') })) } const promise = waitForRedispatchSignatureAudit(context, proof, { root, output, wait, timeoutMs: fault === 'timeout' ? 0 : 5000 }) if (fault === 'none') { const result = await promise; assert.equal(result.sourceAEventCount, 0); assert.equal(result.sourceBEventCount, 1) } else await assert.rejects(promise, /gate|approval|SHA256/) } finally { assert.equal(dirname(resolve(root)), resolve(tmpdir())); assert.ok(resolve(root).startsWith(join(resolve(tmpdir()), 'fleet-signature-gate-'))) rmSync(root, { recursive: true, force: true }) } }) for (const fault of ['missing-B', 'wrong-return', 'premature-A', 'domestic-premature-A', 'blocked-B', 'wrong-candidate', 'rewritten-original']) { test(`rejects ${fault} instead of declaring redispatch complete`, async () => { const f = fixture(fault); await assert.rejects(run(f)); assert.equal(f.trip.returnRedispatch.status, 'FAILED') if (fault === 'missing-B') assert.ok(f.requests.every(row => row.method === 'GET')) }) } test('unknown return outcome persists the exact original key and cannot automatically restart', async () => { const f = fixture('unknown-return'); await assert.rejects(run(f), /connection lost/) const last = f.checkpoints.at(-1).requests.at(-1) assert.equal(last.status, 'UNKNOWN_RESULT'); assert.ok(last.body.idempotencyKey) const count = f.requests.length; await assert.rejects(run(f), /no automatic restart/); assert.equal(f.requests.length, count) }) test('pre-send checkpoint failure cannot send even the first HTTP request', async () => { const f = fixture(); f.context.checkpoint = async () => { throw Error('disk full') } await assert.rejects(run(f), /disk full/); assert.equal(f.requests.length, 0) }) test('fixture selection refuses partial coverage or borrowed source shipments', () => { const row = name => ({ tail: { orderType: 'FIRST_LEG', sourceInboundId: name, inboundCode: name, packageCount: 4 }, relation: { inboundCode: name, returnSources: [{ maxReturnPackages: 3 }] } }) assert.throws(() => selectReturnSources([row('A'), row('B')]), /cover each selected source completely/) }) test('WAIT_DELIVERY start submits exactly the version read from the current delivery', async () => { const f = fixture(), original = f.context.request.bind(f.context) let started = false f.context.request = async (method, path, body, ...args) => { if (method === 'PUT' && path.includes('/delivery-orders/DL-ORIGINAL/status')) { assert.equal(body.expectedVersion, 17); assert.equal(body.status, 'DELIVERING'); started = true f.requests.push({ method, path, body }) return { http: 200, body: { code: '200', data: { deliveryStatus: 'DELIVERING' } }, requestId: 'r', traceId: 't' } } const response = await original(method, path, body, ...args) if (!started && response.body?.data?.order?.deliveryNo === 'DL-ORIGINAL') Object.assign(response.body.data.order, { status: 'WAIT_DELIVERY', version: '17' }) if (path.includes('/return-inbound-options')) assert.equal(started, true) return response } await run(f) assert.equal(f.requests.filter(row => row.method === 'PUT' && row.path.includes('/status')).length, 1) }) for (const multipart of [false, true]) test(`http:0 records UNKNOWN_RESULT and stops without retry; multipart=${multipart}`, async () => { const f = fixture(), original = f.context.request.bind(f.context) if (multipart) f.context.requestMultipart = async (method, path, file) => { f.requests.push({ method, path, file: file.name }); return { http: 0, body: null, requestId: null, traceId: null } } else f.context.request = async (method, path, body, ...args) => { if (path.includes('/exception-orders/') && path.includes('/complete')) { f.requests.push({ method, path, body: structuredClone(body) }); return { http: 0, body: null, requestId: null, traceId: null } } return original(method, path, body, ...args) } await assert.rejects(run(f), /transport outcome unknown/) const last = f.checkpoints.at(-1).requests.at(-1) assert.equal(last.status, 'UNKNOWN_RESULT'); assert.equal(last.http, 0) if (multipart) assert.match(last.file.sha256, /^[a-f0-9]{64}$/) else assert.ok(last.body.idempotencyKey) assert.equal(f.requests.filter(row => row.path === last.path && row.method === last.method).length, 1) const count = f.requests.length await assert.rejects(run(f), /no automatic restart/); assert.equal(f.requests.length, count) }) for (const [name, response] of Object.entries({ server500: { http: 500, body: { code: 'B0001', message: 'Internal error' }, requestId: 'r', traceId: 't' }, proxy502: { http: 502, body: null, requestId: null, traceId: null }, malformed200: { http: 200, body: { unexpected: true }, requestId: 'r', traceId: 't' }, htmlFailure: { http: 403, body: 'proxy blocked', requestId: 'r', traceId: 't' }, uncorrelatedSuccess: { http: 200, body: { code: '200', data: { status: 'COMPLETED' } }, requestId: null, traceId: null }, })) test(`ambiguous ${name} persists UNKNOWN_RESULT without replay or further requests`, async () => { const f = fixture(), original = f.context.request.bind(f.context) f.context.request = async (method, path, body, ...args) => { if (path.includes('/mark-return')) { f.requests.push({ method, path, body: structuredClone(body) }); return response } return original(method, path, body, ...args) } await assert.rejects(run(f), /outcome unknown/) const last = f.checkpoints.at(-1).requests.at(-1) assert.equal(last.status, 'UNKNOWN_RESULT'); assert.equal(last.http, response.http) assert.ok(last.body.idempotencyKey) assert.equal(f.requests.filter(row => row.path.includes('/mark-return')).length, 1) assert.equal(f.requests.at(-1).path, last.path) const count = f.requests.length; await assert.rejects(run(f), /no automatic restart/); assert.equal(f.requests.length, count) }) test('correlated explicit business rejection remains RESPONDED and also stops without replay', async () => { const f = fixture(), original = f.context.request.bind(f.context) f.context.request = async (method, path, body, ...args) => { if (path.includes('/mark-return')) { f.requests.push({ method, path, body }); return { http: 409, body: { code: 'A0409', message: 'version conflict' }, requestId: 'r', traceId: 't' } } return original(method, path, body, ...args) } await assert.rejects(run(f), /HTTP failed/) assert.equal(f.checkpoints.at(-1).requests.at(-1).status, 'RESPONDED') assert.equal(f.requests.filter(row => row.path.includes('/mark-return')).length, 1) })