91 lines
5.9 KiB
JavaScript
91 lines
5.9 KiB
JavaScript
import test from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { verifyClientRelabelFulfillment } from './client-relabel-fulfillment.mjs'
|
|
|
|
function harness(fault = '') {
|
|
const calls = [], saved = [], commands = new Set()
|
|
const proof = { marker: 'RELABEL-TEST', relabelNo: 'TB202610220001', customerId: '101', warehouseId: '201',
|
|
sourcePositionId: '301', sourcePalletNo: 'SOURCE', sourceOrderNo: 'WC202610220001' }
|
|
let status = 'PENDING_REVIEW', moved = false, scanned = false, version = 0, taskStatus = 'AVAILABLE'
|
|
const item = { itemId: '401', expectedPackages: 1, cartonMark: 'BOX-TEST', fbaCartonMark: 'FBA-TEST',
|
|
operationType: 'CARTON_AND_SKU', skuItems: [{ skuCode: 'SKU', newSkuCode: 'NEW', quantity: 1 }] }
|
|
const detail = () => ({ application: { relabelNo: proof.relabelNo, customerOrderNo: proof.sourceOrderNo,
|
|
overseasWarehouseId: fault === 'wrong warehouse' ? '999' : '201', status,
|
|
walletStatus: fault === 'no funds' ? 'PENDING' : 'FROZEN', fulfillmentStatus: status === 'READY_FOR_OUTBOUND' ? 'WAIT_PRE_DISPATCH' : null },
|
|
items: [{ sourceInventoryPositionId: '301', cartonMark: 'BOX-TEST' }] })
|
|
const task = () => ({ relabelNo: proof.relabelNo, taskStatus, version, applicationStatus: status,
|
|
relabelledPackages: scanned ? 1 : 0, skippedPackages: 0, exceptionPackages: 0, items: [item] })
|
|
const context = { marker: proof.marker, token: 'admin-token', context: { merchantQualificationFixture: { tenantId: '101' } },
|
|
assert(_module, name, _response, passed) { assert.ok(passed, name) },
|
|
async request(method, path, body, token) {
|
|
calls.push({ method, path, body, token })
|
|
assert.equal(saved.at(-1)?.requests.at(-1)?.status, 'SENDING')
|
|
const repeated = body?.idempotencyKey && commands.has(body.idempotencyKey)
|
|
if (body?.idempotencyKey) commands.add(body.idempotencyKey)
|
|
let data
|
|
if (path.endsWith('/tenant/profile')) data = { id: fault === 'wrong customer' ? '999' : '101', status: 'ACTIVE' }
|
|
else if (path.endsWith('/status')) { status = 'PENDING_PROCESSING'; data = detail() }
|
|
else if (path.endsWith('/claim')) { taskStatus = 'PROCESSING'; if (!repeated) version++; data = task() }
|
|
else if (path.endsWith('/cartons/scan')) { if (!repeated) version++; data = task() }
|
|
else if (path.endsWith('/units')) {
|
|
if (fault === 'unknown scan') throw new Error('unknown scan response')
|
|
scanned = true; if (!repeated) version++; data = task()
|
|
if (fault === 'scan replay drift' && repeated) data.relabelledPackages = 2
|
|
} else if (path.endsWith('/relabel-pallet-label')) data = { palletId: '501', palletCode: 'TARGET' }
|
|
else if (path.endsWith('/pallets/resolve')) {
|
|
const source = body.code === 'SOURCE'
|
|
data = { palletCode: body.code, palletVersion: moved ? 1 : 0,
|
|
totalPackages: source ? moved ? fault === 'no source deduction' ? 3 : 2 : 3 : moved ? 1 : 0 }
|
|
} else if (path.endsWith('/relabel-disassemble')) { moved = true; data = { status: 'COMPLETED', operationId: '601', replayed: Boolean(repeated) } }
|
|
else if (path.endsWith('/confirm-completion')) { status = 'READY_FOR_OUTBOUND'; data = detail() }
|
|
else if (path.endsWith('/complete')) { status = 'CUSTOMER_CONFIRM_PENDING'; taskStatus = 'WAIT_REVIEW'; data = task() }
|
|
else if (path.includes('/relabel-tasks/')) data = task()
|
|
else data = detail()
|
|
return { http: 200, body: { code: '200', data } }
|
|
},
|
|
async requestMultipart(method, path, body, token) {
|
|
calls.push({ method, path, token })
|
|
assert.equal(saved.at(-1)?.requests.at(-1)?.status, 'SENDING')
|
|
return { http: 200, body: { code: '200', data: { fileId: '701' } } }
|
|
} }
|
|
const inputs = { proof, merchantToken: 'client-secret', pdaToken: 'pda-secret', delay: async () => {},
|
|
checkpoint: async () => { if (fault === 'disk') throw new Error('disk full'); saved.push(structuredClone(proof.execution)) } }
|
|
return { context, inputs, calls, saved }
|
|
}
|
|
|
|
test('original customer relabel reaches ready for dispatch through real command sequence and exact replays', async () => {
|
|
const h = harness()
|
|
const result = await verifyClientRelabelFulfillment(h.context, h.inputs)
|
|
assert.equal(result.status, 'READY_FOR_OUTBOUND')
|
|
assert.deepEqual(result.targetPalletIds, ['501'])
|
|
assert.equal(result.execution.status, 'HTTP_VERIFIED_READY_FOR_DISPATCH')
|
|
assert.equal(h.calls.filter(call => call.path.endsWith('/units')).length, 2)
|
|
assert.equal(h.calls.filter(call => call.path.endsWith('/relabel-disassemble')).length, 2)
|
|
assert.equal(h.calls.filter(call => call.path.endsWith('/confirm-completion')).length, 2)
|
|
assert.ok(!JSON.stringify(h.saved).includes('client-secret'))
|
|
assert.ok(!JSON.stringify(h.saved).includes('pda-secret'))
|
|
await assert.rejects(verifyClientRelabelFulfillment(h.context, h.inputs), /不自动重跑/)
|
|
})
|
|
|
|
for (const fault of ['wrong customer', 'wrong warehouse', 'no funds', 'unknown scan', 'scan replay drift', 'no source deduction', 'disk']) {
|
|
test(`relabel stops before completion on ${fault}`, async () => {
|
|
const h = harness(fault)
|
|
await assert.rejects(verifyClientRelabelFulfillment(h.context, h.inputs))
|
|
assert.equal(h.inputs.proof.execution.status, 'FAILED')
|
|
assert.equal(h.calls.some(call => call.path.endsWith('/confirm-completion')), false)
|
|
if (fault === 'no funds') assert.equal(h.calls.some(call => call.path.endsWith('/claim')), false)
|
|
if (fault === 'unknown scan') {
|
|
assert.equal(h.calls.filter(call => call.path.endsWith('/units')).length, 1)
|
|
assert.equal(h.inputs.proof.execution.requests.at(-1).status, 'UNKNOWN_RESULT')
|
|
}
|
|
if (fault === 'disk') assert.equal(h.calls.length, 0)
|
|
})
|
|
}
|
|
|
|
test('foreign-batch relabel is rejected before any HTTP', async () => {
|
|
const h = harness()
|
|
h.inputs.proof.marker = 'OLD'
|
|
await assert.rejects(verifyClientRelabelFulfillment(h.context, h.inputs), /本轮客户申请/)
|
|
assert.equal(h.calls.length, 0)
|
|
})
|