290 lines
24 KiB
JavaScript
290 lines
24 KiB
JavaScript
import test from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { readFileSync } from 'node:fs'
|
|
import { installRelabelJournal, selectExplicitRelabelSource, nativeRelabelCreateBody, nativeRelabelGroups, relabelGroupMeasurements, runRelabelProducer } from './overseas-relabel-real-acceptance.mjs'
|
|
import { retainedRelabelSource } from './overseas-relabel-fulfillment-acceptance.mjs'
|
|
const ok = data => ({ http: 200, body: { code: '200', data }, requestId: 'REQ', traceId: 'TRACE' })
|
|
const config = () => ({ customerId: '100', warehouseId: '80', sourceBalanceId: '1', sourcePositionId: '2', merchantToken: 'MERCHANT-MEMORY', pdaToken: 'PDA-MEMORY', chargeAmount: 0 })
|
|
const sourceCandidate = () => ({ inventoryBalanceId: '1', inboundOrderId: '3', inboundOrderNo: 'DS-TEST', inboundCode: 'IN-TEST', overseasWarehouseId: '80',
|
|
availablePackages: '5', skuCode: 'SKU', destinationFbaWarehouseCode: 'ABE4', positions: [{ positionId: '2', palletNo: 'SOURCE', availablePackages: '5' }] })
|
|
|
|
test('explicit original balance/position selection never borrows history or a different customer warehouse', () => {
|
|
const candidate = sourceCandidate()
|
|
assert.equal(selectExplicitRelabelSource([candidate], config()).source, candidate)
|
|
for (const mutate of [value => { value.sourceBalanceId = '' }, value => { value.sourcePositionId = '99' },
|
|
value => { value.warehouseId = '81' }, value => { value.inboundCode = 'OTHER' }]) {
|
|
const value = config(); mutate(value); assert.throws(() => selectExplicitRelabelSource([candidate], value))
|
|
}
|
|
assert.throws(() => selectExplicitRelabelSource([candidate, candidate], config()), /ambiguous/)
|
|
const unavailable = sourceCandidate(); unavailable.positions[0].availablePackages = '4'
|
|
assert.throws(() => selectExplicitRelabelSource([unavailable], config()), /five real/)
|
|
})
|
|
|
|
test('empty successful relabel response is UNKNOWN, keeps the original version/key, and stops replacement writes', async () => {
|
|
const report = { marker: 'RUN-1', requests: [] }, snapshots = [], calls = []
|
|
const context = installRelabelJournal({ context: {}, createdResources: {},
|
|
async request(method, path, body) { calls.push({ method, path, body }); assert.equal(snapshots.at(-1).requests.at(-1).status, 'SENDING'); return ok(null) },
|
|
async requestMultipart() { throw Error('Unexpected upload') },
|
|
}, report, async () => snapshots.push(structuredClone(report)))
|
|
const original = { expectedVersion: '0', idempotencyKey: 'ORIGINAL', oldLabel: 'OLD' }
|
|
await assert.rejects(context.request('POST', '/api/pda/v1/last-leg/relabel-tasks/RL-1/units', original, 'PDA-MEMORY'), /business response missing/)
|
|
assert.equal(report.requests[0].status, 'UNKNOWN'); assert.deepEqual(report.requests[0].body, original)
|
|
assert.equal(report.requests[0].requestId, 'REQ')
|
|
await assert.rejects(context.request('POST', '/api/pda/v1/last-leg/relabel-tasks/RL-1/units', { ...original, idempotencyKey: 'NEW' }), /stop with original/)
|
|
assert.equal(calls.length, 1)
|
|
})
|
|
for (const data of [null, {}, { dispatchNo: 'DS-UNKNOWN', action: 'CREATE', recordVersion: '0', result: null }]) test(`native CREATE without its original business result stops with its original key: ${JSON.stringify(data)}`, async () => {
|
|
const report = { marker: 'RUN-1', requests: [] }, calls = []
|
|
const context = installRelabelJournal({ context: {}, createdResources: {}, request: async (...args) => { calls.push(args); return ok(data) }, requestMultipart: async () => { throw Error('Unexpected upload') } }, report, async () => {})
|
|
const body = { idempotencyKey: 'ORIGINAL-CREATE', cargo: [{ sourceType: 'RELABEL', relabelNo: 'RL-1', warehousePalletId: '9007199254740993', sourceVersion: 'a'.repeat(64) }] }
|
|
await assert.rejects(context.request('POST', '/api/platform/v1/last-leg/dispatches', body), /Unknown/)
|
|
assert.equal(report.requests[0].status, 'UNKNOWN'); assert.deepEqual(report.requests[0].body, body)
|
|
await assert.rejects(context.request('POST', '/api/platform/v1/last-leg/dispatches', { ...body, idempotencyKey: 'NEW' }), /Unknown/)
|
|
assert.equal(calls.length, 1)
|
|
})
|
|
|
|
function producerFixture(corruptCartonStage = false, mutateCandidates = value => value, mutateDetail = value => value) {
|
|
const report = { environment: 'TEST', marker: 'RUN-1', requests: [], scenarios: [], blockers: [], passed: false }, calls = [], snapshots = []
|
|
const applications = new Map(), replay = new Map(), pallets = new Map([['SOURCE', { palletId: '4', palletCode: 'SOURCE', totalPackages: 5, palletVersion: 0 }]])
|
|
let appSequence = 0, palletSequence = 10, photoSequence = 20
|
|
const dispatches = new Map()
|
|
const clone = value => structuredClone(value)
|
|
const detail = app => ({ relabelNo: app.no, version: app.version, items: clone(app.items), applicationStatus: app.status })
|
|
const context = { token: 'PLATFORM-MEMORY', context: {}, createdResources: {}, steps: [],
|
|
apiSuccess(_m, _l, response) { return response.body?.data }, addCoverageGap() {},
|
|
addStep(_m, _l, _r, passed) { this.steps.push({ passed }) },
|
|
async requestMultipart(method, path, file) {
|
|
calls.push({ method, path, file: { name: file.name, type: file.type, size: file.content.length } })
|
|
assert.equal(file.content.subarray(0, 8).toString('hex'), '89504e470d0a1a0a')
|
|
return ok({ fileId: String(photoSequence++) })
|
|
},
|
|
async request(method, path, body, token) {
|
|
calls.push({ method, path, body: clone(body), token })
|
|
if (path.includes('/inventory-candidates')) { assert.equal(token, 'MERCHANT-MEMORY'); return ok([sourceCandidate()]) }
|
|
if (path.startsWith('/api/platform/v1/last-leg/relabel-applications?') && method === 'POST') {
|
|
const no = `RL-${++appSequence}`, items = body.items.map((item, index) => ({ ...clone(item), id: `90071992547410${appSequence}${index}`,
|
|
itemId: `90071992547410${appSequence}${index}`, cartonProcessedPackages: 0, skuProcessedPackages: 0 }))
|
|
const app = { no, items, version: 0, body: clone(body), status: 'PROCESSING' }; applications.set(no, app)
|
|
return ok({ application: { relabelNo: no, overseasWarehouseId: '80' }, items: clone(items) })
|
|
}
|
|
if (path.includes('/relabel-tasks/')) {
|
|
assert.equal(token, 'PDA-MEMORY')
|
|
const no = path.split('/relabel-tasks/')[1].split('/')[0], app = applications.get(no)
|
|
if (replay.has(body.idempotencyKey)) return ok(clone(replay.get(body.idempotencyKey)))
|
|
if (path.endsWith('/claim')) return ok(detail(app))
|
|
if (path.endsWith('/cartons/scan')) {
|
|
assert.equal(body.expectedVersion, app.version)
|
|
const item = app.items.find(item => item.cartonMark === body.cartonMark); item.cartonProcessedPackages = 1
|
|
if (corruptCartonStage) item.skuProcessedPackages = 1
|
|
app.version++
|
|
} else if (path.endsWith('/units')) {
|
|
assert.equal(body.expectedVersion, app.version)
|
|
const item = app.items.find(item => item.itemId === body.itemId)
|
|
assert.equal(body.skuVerifications[0].quantity, 1); item.skuProcessedPackages = 1; app.version++
|
|
} else if (path.endsWith('/complete')) {
|
|
assert.equal(body.expectedVersion, app.version); assert.equal(body.photoFileIds.length, 1)
|
|
app.status = 'CUSTOMER_CONFIRM_PENDING'; app.version++
|
|
} else throw Error(`Unexpected relabel task ${path}`)
|
|
const result = detail(app); replay.set(body.idempotencyKey, clone(result)); return ok(result)
|
|
}
|
|
if (path.includes('/confirm-completion')) {
|
|
assert.equal(token, 'MERCHANT-MEMORY')
|
|
const no = path.split('/relabel-applications/')[1].split('/')[0], app = applications.get(no)
|
|
return ok({ application: { relabelNo: no, overseasWarehouseId: '80', status: app.body.items[0].operationType === 'SKU_LABEL' ? 'COMPLETED' : 'READY_FOR_OUTBOUND' }, items: clone(app.items) })
|
|
}
|
|
if (path.endsWith('/pallets/resolve')) return ok(clone(pallets.get(body.code)))
|
|
if (path.endsWith('/relabel-pallet-label')) {
|
|
if (replay.has(body.idempotencyKey)) return ok(clone(replay.get(body.idempotencyKey)))
|
|
const palletId = String(palletSequence++), palletCode = `P-${palletId}`
|
|
const value = { palletId, palletCode, totalPackages: 0, palletVersion: 0 }; pallets.set(palletCode, value)
|
|
replay.set(body.idempotencyKey, value); return ok(clone(value))
|
|
}
|
|
if (path.endsWith('/relabel-disassemble')) {
|
|
pallets.get(body.source.palletCode).totalPackages--; pallets.get(body.source.palletCode).palletVersion++
|
|
pallets.get(body.target.palletCode).totalPackages++; pallets.get(body.target.palletCode).palletVersion++
|
|
return ok({ operationType: 'RELABEL_PALLET_DISASSEMBLE' })
|
|
}
|
|
if (path.includes('/inventory/ledger?')) return ok({ records: [{ remark: '换标拆托 RUN-1', deltaPackages: -1 }, { remark: '换标拆托 RUN-1', deltaPackages: 1 }] })
|
|
if (path.endsWith('/relabel-pallet-measurements')) return ok([])
|
|
if (path.includes('/dispatches/candidates?')) {
|
|
const query = new URL(path, 'https://offline.invalid').searchParams
|
|
assert.equal(query.get('warehouseId'), '80'); assert.equal(query.get('sourceStage'), 'RELABEL')
|
|
assert.equal(query.has('orderNo'), false)
|
|
const rows = report.scenarios.filter(scenario => scenario.operationType === 'CARTON_AND_SKU').flatMap(scenario => scenario.targets.map(target => ({
|
|
selection: { sourceType: 'RELABEL', relabelNo: scenario.relabelNo, planPalletId: null, returnReceiptId: null, warehousePalletId: target.palletId, sourceVersion: 'a'.repeat(64) },
|
|
sourceKey: `RELABEL:${scenario.relabelNo}:${target.palletId}`, sourceVersion: 'a'.repeat(64), customerId: '100', warehouseId: '80',
|
|
palletNo: target.palletCode, availablePackages: '1', destination: { type: 'FBA', warehouseCode: 'ABE4', key: 'FBA:ABE4' }, blockReasons: [], appointmentBatches: [] })))
|
|
for (const subset of [rows.filter(row => row.selection.relabelNo !== 'RL-4'), rows.filter(row => row.selection.relabelNo === 'RL-4')]) attachGroup(subset)
|
|
return ok({ complete: true, candidates: mutateCandidates(rows) })
|
|
}
|
|
if (path === '/api/platform/v1/last-leg/dispatches' && method === 'POST') {
|
|
assert.equal(calls.filter(call => call.path.includes('/dispatches/candidates?')).length, 1)
|
|
assert.equal(body.cargo.length, 2)
|
|
const old = dispatches.get(body.idempotencyKey)
|
|
if (old) { assert.deepEqual(body, old.body); return ok({ ...clone(old.result), replayed: true }) }
|
|
const dispatchNo = `DS-NATIVE-${dispatches.size + 1}`
|
|
const result = { dispatchNo, action: 'CREATE', status: 'ACTIVE', stage: 'BEFORE_LOADING', recordVersion: '0', result: { dispatchNo, changedItemIds: ['9007199254740993'] } }
|
|
dispatches.set(body.idempotencyKey, { body: clone(body), result }); return ok(result)
|
|
}
|
|
if (path.includes('/dispatches/DS-NATIVE-') && method === 'GET') {
|
|
const dispatch = [...dispatches.values()].find(value => path.endsWith(value.result.dispatchNo))
|
|
return ok(mutateDetail({ dispatchNo: dispatch.result.dispatchNo, status: 'ACTIVE', stage: 'BEFORE_LOADING', recordVersion: '0',
|
|
counts: { packageCount: '2', palletCount: String(new Set(dispatch.body.cargo.map(row => row.warehousePalletId)).size) },
|
|
orderDetails: dispatch.body.cargo.map(row => ({ customerId: '100', inboundCode: 'IN-TEST', sourceBusinessType: 'RELABEL', sourceBusinessNo: row.relabelNo, sourceKey: `RELABEL:${row.relabelNo}:${row.warehousePalletId}` })) }))
|
|
}
|
|
throw Error(`Unexpected offline producer ${method} ${path}`)
|
|
},
|
|
}
|
|
installRelabelJournal(context, report, async () => { snapshots.push(clone(report)) })
|
|
return { context, report, calls, snapshots }
|
|
}
|
|
|
|
function attachGroup(rows) {
|
|
const sources = rows.map(row => ({ selection: structuredClone(row.selection), sourceKey: row.sourceKey, customerId: row.customerId, customerName: 'TEST', sourceBusinessType: 'RELABEL', sourceBusinessNo: row.selection.relabelNo, packages: row.availablePackages }))
|
|
const pallets = [...new Set(rows.map(row => row.selection.warehousePalletId))].map(warehousePalletId => ({ warehousePalletId, palletNo: rows.find(row => row.selection.warehousePalletId === warehousePalletId).palletNo,
|
|
sourceKeys: sources.filter(source => source.selection.warehousePalletId === warehousePalletId).map(source => source.sourceKey), packageCount: String(sources.filter(source => source.selection.warehousePalletId === warehousePalletId).reduce((sum, source) => sum + Number(source.packages), 0)) }))
|
|
const group = { groupKey: `GROUP-${sources[0].sourceBusinessNo}`, groupVersion: 'b'.repeat(64), sources, pallets, complete: true, blockReasons: [] }
|
|
for (const row of rows) row.selectionGroup = structuredClone(group)
|
|
return group
|
|
}
|
|
|
|
test('four relabel scenes retain scans/photos/counts; shared NEW/MERGE create one group once and split retains its application closure', async () => {
|
|
const f = producerFixture()
|
|
await runRelabelProducer(f.context, config(), f.report)
|
|
assert.deepEqual(f.report.scenarios.map(scenario => scenario.name), ['SKU-ONLY', 'NEW-PALLET', 'MERGE-EXISTING', 'SPLIT-TWO-TARGETS'])
|
|
assert.equal(f.report.relabelScenariosVerified, true)
|
|
assert.equal(f.report.scenarios[0].sourceBefore, f.report.scenarios[0].sourceAfter)
|
|
assert.deepEqual(f.report.sharedMerge.sourceScenarios.map(row => row.cartons), [1, 1])
|
|
assert.equal(f.report.sharedMerge.beforePackages, 1); assert.equal(f.report.sharedMerge.afterPackages, 2)
|
|
const fresh = f.report.scenarios[1], merged = f.report.scenarios[2]
|
|
assert.equal(fresh.nativeDispatch, merged.nativeDispatch); assert.equal(fresh.dispatchNo, 'DS-NATIVE-1'); assert.equal(merged.dispatchNo, fresh.dispatchNo)
|
|
assert.equal(fresh.nativeDispatch.createBody.cargo.length, 2); assert.equal(new Set(fresh.nativeDispatch.createBody.cargo.map(row => row.warehousePalletId)).size, 1)
|
|
assert.deepEqual([fresh, merged].map(scenario => [scenario.sourceContributions[0].sourceBusinessNo, scenario.sourceContributions[0].packages]), [['RL-2', '1'], ['RL-3', '1']])
|
|
assert.deepEqual(fresh.nativeDispatch.detail.orderDetails.map(row => row.sourceBusinessNo), ['RL-2', 'RL-3'])
|
|
assert.equal(f.calls.filter(call => call.path === '/api/platform/v1/last-leg/dispatches').length, 4)
|
|
const split = f.report.scenarios[3]
|
|
assert.equal(split.nativeDispatch.createBody.cargo.length, 2); assert.equal(split.nativeDispatch.createResult.recordVersion, '0')
|
|
assert.equal(split.nativeDispatch.createReplay.replayed, true)
|
|
assert.equal(split.items.length, 2); assert.equal(split.targets.length, 2)
|
|
assert.equal(split.sourceCartonMarks.length, 2); assert.equal(split.relabelCartonMarks.length, 2)
|
|
assert.equal(split.nativeDispatch.loadingMeasurements, undefined)
|
|
assert.equal(split.nativeDispatchBlocker.code, 'ACTUAL_LOADING_MEASUREMENT_REQUIRED')
|
|
assert.equal(f.report.passed, false)
|
|
assert.equal(retainedRelabelSource(f.report, '100', 'DS-NATIVE-2').scope.expectedPackages, 2)
|
|
assert.equal(f.calls.filter(call => call.path.endsWith('/photos/files')).length, 4)
|
|
assert.equal(f.calls.some(call => /pre-arrange/.test(call.path)), false)
|
|
const scans = f.calls.filter(call => call.path.endsWith('/units'))
|
|
assert.equal(scans.length, 10)
|
|
for (let index = 0; index < scans.length; index += 2) assert.deepEqual(scans[index].body, scans[index + 1].body)
|
|
})
|
|
|
|
test('carton stage may not complete the SKU stage and a failure keeps original scene items/source request proof', async () => {
|
|
const f = producerFixture(true)
|
|
await assert.rejects(runRelabelProducer(f.context, config(), f.report), /carton stage incorrectly/)
|
|
assert.equal(f.report.scenarios[0].relabelVerified, true)
|
|
assert.equal(f.report.scenarios[1].relabelVerified, false)
|
|
assert.equal(f.report.scenarios[1].requestItems.length, 1)
|
|
assert.equal(f.report.scenarios[1].sourceCartonMarks.length, 1)
|
|
assert.equal(f.calls.some(call => call.path === '/api/platform/v1/last-leg/dispatches'), false)
|
|
})
|
|
|
|
test('native producer selectors cannot replace customer, warehouse, destination, original source hash or physical quantity', () => {
|
|
const scenario = { name: 'SPLIT-TWO-TARGETS', relabelVerified: true, relabelNo: 'RL-1', targets: [{ palletId: '9007199254740993' }], items: [{ notifiedPackageCount: 1 }], destinationFbaWarehouseCode: 'ABE4' }
|
|
const candidate = { selection: { sourceType: 'RELABEL', relabelNo: 'RL-1', warehousePalletId: '9007199254740993', sourceVersion: 'a'.repeat(64) },
|
|
sourceKey: 'RELABEL:RL-1:9007199254740993', sourceVersion: 'a'.repeat(64), customerId: '100', warehouseId: '80', availablePackages: '1', palletNo: 'P-ORIGINAL',
|
|
destination: { type: 'FBA', warehouseCode: 'ABE4' }, blockReasons: [], appointmentBatches: [] }
|
|
attachGroup([candidate])
|
|
assert.equal(nativeRelabelCreateBody('RUN-1', [scenario], [candidate], '100', '80').cargo[0].warehousePalletId, '9007199254740993')
|
|
for (const mutate of [value => { value.customerId = '200' }, value => { value.warehouseId = '81' },
|
|
value => { value.destination.warehouseCode = 'OTHER' }, value => { value.availablePackages = '2' },
|
|
value => { value.selection.sourceVersion = 'stale' }, value => { value.cartonMarks = 'OLD' }]) {
|
|
const value = structuredClone(candidate); mutate(value); assert.throws(() => nativeRelabelCreateBody('RUN-1', [scenario], [value], '100', '80'))
|
|
}
|
|
})
|
|
|
|
for (const fault of ['missing group', 'incomplete group', 'group blocker', 'missing version', 'version drift', 'member selector version', 'unknown business', 'missing member',
|
|
'duplicate member', 'source packages', 'physical packages', 'missing physical source', 'duplicate physical', 'split application closure']) test(`authority group preflight blocks every CREATE: ${fault}`, async () => {
|
|
const f = producerFixture(false, rows => {
|
|
if (fault === 'missing group') delete rows[0].selectionGroup
|
|
if (fault === 'incomplete group') rows[0].selectionGroup.complete = false
|
|
if (fault === 'group blocker') rows[0].selectionGroup.blockReasons = ['真实组尚未核平']
|
|
if (fault === 'missing version') rows[0].selectionGroup.groupVersion = ''
|
|
if (fault === 'version drift') rows[0].selectionGroup.groupVersion = 'CHANGED'
|
|
if (fault === 'member selector version') rows[0].selectionGroup.sources[0].selection.sourceVersion = 'c'.repeat(64)
|
|
if (fault === 'unknown business') rows[0].selectionGroup.sources[0].sourceBusinessNo = 'RL-HISTORY'
|
|
if (fault === 'missing member') rows.splice(1, 1)
|
|
if (fault === 'duplicate member') rows.push(structuredClone(rows[0]))
|
|
if (fault === 'source packages') rows[0].selectionGroup.sources[0].packages = '2'
|
|
if (fault === 'physical packages') rows[0].selectionGroup.pallets[0].packageCount = '3'
|
|
if (fault === 'missing physical source') rows[0].selectionGroup.pallets[0].sourceKeys.pop()
|
|
if (fault === 'duplicate physical') rows[0].selectionGroup.pallets.push(structuredClone(rows[0].selectionGroup.pallets[0]))
|
|
if (fault === 'split application closure') rows.at(-1).selectionGroup.groupKey = 'OTHER-PHYSICAL-GROUP'
|
|
if (['incomplete group', 'group blocker', 'missing version', 'member selector version', 'unknown business', 'source packages', 'physical packages', 'missing physical source', 'duplicate physical'].includes(fault))
|
|
for (const row of rows.slice(1).filter(row => row.selectionGroup.groupKey === rows[0].selectionGroup.groupKey)) row.selectionGroup = structuredClone(rows[0].selectionGroup)
|
|
return rows
|
|
})
|
|
await assert.rejects(runRelabelProducer(f.context, config(), f.report))
|
|
assert.equal(f.report.relabelScenariosVerified, true)
|
|
assert.equal(f.calls.some(call => call.method === 'POST' && call.path === '/api/platform/v1/last-leg/dispatches'), false)
|
|
assert.ok(f.report.nativeSelectionFailure.message); assert.equal(f.report.passed, false)
|
|
})
|
|
|
|
test('native source business number is required in actual Detail and cannot fall back to inboundCode', async () => {
|
|
const f = producerFixture(false, rows => rows, detail => { delete detail.orderDetails[0].sourceBusinessNo; return detail })
|
|
await runRelabelProducer(f.context, config(), f.report)
|
|
assert.equal(f.report.passed, false)
|
|
assert.equal(f.report.scenarios[1].nativeDispatchBlocker.code, 'NATIVE_DISPATCH_NOT_VERIFIED')
|
|
assert.equal(f.report.scenarios[1].nativeDispatch.createResult.recordVersion, '0')
|
|
assert.equal(f.report.scenarios[1].nativeDispatch.createReplay.replayed, true)
|
|
})
|
|
|
|
function loadingReceipt(dispatchNo, palletIds) {
|
|
return { environment: 'TEST', marker: 'RUN-1', dispatchNo, pallets: palletIds.map(warehousePalletId => ({ warehousePalletId, palletSpaces: 2, weightKg: '15.5', volumeCbm: '0.75', deliveryIndex: 1,
|
|
evidence: ['palletSpaces', 'weightKg', 'volumeCbm'].map((field, index) => ({ field, value: [2, '15.5', '0.75'][index], actual: true, sourceType: 'OFFLINE_TEST_RECEIPT', sourceBusinessNo: 'OFFLINE-ONLY', observedAt: '2026-10-19T00:00:00Z' })) })) }
|
|
}
|
|
test('consistent shared scenario loading receipts publish one physical envelope and one dispatch command pair', async () => {
|
|
const f = producerFixture(), c = config()
|
|
c.loadingEvidenceByScenario = { 'NEW-PALLET': loadingReceipt('DS-NATIVE-1', ['10']), 'MERGE-EXISTING': loadingReceipt('DS-NATIVE-1', ['10']), 'SPLIT-TWO-TARGETS': loadingReceipt('DS-NATIVE-2', ['11', '12']) }
|
|
await runRelabelProducer(f.context, c, f.report)
|
|
assert.equal(f.report.passed, true) // Offline script contract only; no environment claim.
|
|
const fresh = f.report.scenarios[1], merged = f.report.scenarios[2]
|
|
assert.equal(fresh.nativeDispatch, merged.nativeDispatch)
|
|
assert.equal(fresh.nativeDispatch.loadingMeasurements.pallets.length, 1)
|
|
assert.equal(fresh.nativeDispatch.loadingMeasurements.pallets[0].weightKg, '15.5')
|
|
assert.equal(f.calls.filter(call => call.path === '/api/platform/v1/last-leg/dispatches').length, 4)
|
|
assert.equal(f.calls.some(call => /loading-complete|\/pick$|\/depart$/.test(call.path)), false)
|
|
})
|
|
for (const fault of ['dispatch', 'weight', 'space', 'stop', 'pallet', 'duplicate']) test(`shared loading receipts conflict before any CREATE: ${fault}`, async () => {
|
|
const f = producerFixture(), c = config(), fresh = loadingReceipt('DS-NATIVE-1', ['10']), merged = structuredClone(fresh)
|
|
if (fault === 'dispatch') merged.dispatchNo = 'DS-OTHER'
|
|
if (fault === 'weight') { merged.pallets[0].weightKg = '16'; merged.pallets[0].evidence[1].value = '16' }
|
|
if (fault === 'space') { merged.pallets[0].palletSpaces = 3; merged.pallets[0].evidence[0].value = 3 }
|
|
if (fault === 'stop') merged.pallets[0].deliveryIndex = 2
|
|
if (fault === 'pallet') merged.pallets[0].warehousePalletId = '9007199254740993'
|
|
if (fault === 'duplicate') merged.pallets.push(structuredClone(merged.pallets[0]))
|
|
c.loadingEvidenceByScenario = { 'NEW-PALLET': fresh, 'MERGE-EXISTING': merged }
|
|
await assert.rejects(runRelabelProducer(f.context, c, f.report))
|
|
assert.equal(f.calls.some(call => call.path === '/api/platform/v1/last-leg/dispatches'), false)
|
|
})
|
|
test('shared physical measurements deduplicate whole-pallet mass and reject composition or value drift', () => {
|
|
const measured = { palletId: '9007199254740993', palletCode: 'P-SHARED', packages: '2', stockVersion: 'STOCK-ORIGINAL', lengthCm: '100', widthCm: '100', heightCm: '100', grossWeightKg: '15.5', volumeCbm: '1' },
|
|
group = { pallets: [{ warehousePalletId: '9007199254740993', palletNo: 'P-SHARED', packageCount: '2' }] },
|
|
scenarios = [{ physicalMeasurements: [measured] }, { physicalMeasurements: [structuredClone(measured)] }]
|
|
const result = relabelGroupMeasurements(scenarios, group)
|
|
assert.equal(result.length, 1); assert.equal(result[0].grossWeightKg, '15.5')
|
|
scenarios[1].physicalMeasurements[0].stockVersion = 'NEW-STOCK'
|
|
assert.throws(() => relabelGroupMeasurements(scenarios, group), /conflict/)
|
|
scenarios[1].physicalMeasurements[0] = { ...measured, packages: '1' }
|
|
assert.throws(() => relabelGroupMeasurements(scenarios, group), /complete shared pallet/)
|
|
})
|
|
|
|
test('producer import is inert, current TEST Auth is unified, and evidence is retained instead of deleting scenario inputs', () => {
|
|
const source = readFileSync(new URL('./overseas-relabel-real-acceptance.mjs', import.meta.url), 'utf8')
|
|
assert.match(source, /fileURLToPath\(import.meta.url\)\) await mainRelabelProducer\(\)/)
|
|
assert.doesNotMatch(source, /overseasAccessToken|ADMIN_OVERSEAS_GATEWAY|ADMIN_OVERSEAS_ACCESS_TOKEN|delete scenario.items|delete scenario.targets|\/dispatches\/pre-arrange/)
|
|
assert.match(source, /options.environment === 'test'/)
|
|
assert.match(source, /profile.id\) === customerId/)
|
|
assert.match(source, /SYNTHETIC_PNG_NOT_PHYSICAL_WORK/)
|
|
assert.match(source, /Existing producer checkpoint must be reconciled/)
|
|
})
|