Files
pda-overseas-uniapp/tests/storage-rent-preflight.test.mjs
T

84 lines
5.4 KiB
JavaScript

import test from 'node:test'
import assert from 'node:assert/strict'
import { pickDispatchItem, pickOutboundTask, pickOutboundPallet, confirmCargoDiscard } from '../src/api/fulfillment.js'
import { session } from '../src/stores/session.js'
function transport(reply) {
const calls = []
session.apiBase = '/api'; session.token = 'overseas-token'
globalThis.uni = { request(options) {
calls.push({ url: options.url, data: options.data })
if (reply instanceof Error) return options.fail({ errMsg: reply.message })
options.success({ statusCode: 200, data: { code: '200', data: options.url.endsWith('/preparations') ? reply : { success: true } }, header: {} })
} }
return calls
}
test('all stock scan modes require ready preparation with the original command key and source identifiers', async () => {
const calls = transport({ status: 'READY', ready: true })
const request = { recordVersion: '9007199254740993', positionId: '9007199254740994', packages: 2, cartonMark: 'RC-1', productQuantity: 7, idempotencyKey: 'original-dispatch-key' }
await pickDispatchItem('DSP-1', '9007199254740995', request)
assert.deepEqual(calls[0].data, { mode: 'DISPATCH', businessNo: 'DSP-1', itemId: '9007199254740995', positionId: request.positionId, packages: 2, cartonMark: 'RC-1', productQuantity: 7, idempotencyKey: request.idempotencyKey })
assert.deepEqual(calls[1].data, request)
await pickOutboundTask('TASK-1', { outboundPalletItemId: '9007199254740996', inventoryPositionId: null, packages: 1, cartonMarks: [], skuCode: 'SKU-A', productQuantity: 5, idempotencyKey: 'original-sku-key' })
assert.deepEqual(calls[2].data, { mode: 'OUTBOUND', businessNo: 'TASK-1', itemId: '9007199254740996', positionId: null, packages: 1, cartonMarks: [], skuCode: 'SKU-A', productQuantity: 5, idempotencyKey: 'original-sku-key' })
await pickOutboundPallet('TASK-1', 'DP-1', { idempotencyKey: 'original-pallet-key' })
assert.deepEqual(calls[4].data, { mode: 'OUTBOUND_PALLET', businessNo: 'TASK-1', palletNo: 'DP-1', idempotencyKey: 'original-pallet-key' })
assert.equal(calls.length, 6)
for (let i = 0; i < calls.length; i += 2) assert.ok(calls[i].url.endsWith('/preparations'))
})
test('carton outbound preparation uses the same identity as the backend stock command', async () => {
const calls = transport({ status: 'READY', ready: true })
const request = { outboundPalletItemId: '21', inventoryPositionId: null, packages: 1, cartonMarks: ['RETURN-BOX-1'], idempotencyKey: 'carton-key' }
await pickOutboundTask('TASK-2', request)
assert.deepEqual(calls[0].data.cartonMarks, request.cartonMarks)
// 出库后台消费只使用 cartonMarks,不能额外派生 cartonMark 导致幂等摘要不一致。
assert.equal(Object.hasOwn(calls[0].data, 'cartonMark'), false)
assert.deepEqual(calls[1].data, request)
})
test('pending, failed, expired, malformed and network responses never send a stock mutation', async () => {
for (const response of [{ status: 'FREEZE_PENDING', ready: false }, { status: 'FAILED', message: '余额及授信不足' }, { status: 'EXPIRED', ready: false }, { status: 'READY', ready: false }, { status: 'UNKNOWN', ready: true }, {}, new Error('network down')]) {
const calls = transport(response)
await assert.rejects(pickOutboundPallet('TASK-1', 'DP-1', { idempotencyKey: 'stable-key' }), error => error.storageRentPreparation === true)
assert.equal(calls.length, 1)
}
})
test('retry reuses the preparation key; exempt and already consumed commands can recover the stock result', async () => {
const request = { idempotencyKey: 'stable-key' }
let calls = transport({ status: 'FREEZE_PENDING', ready: false })
await assert.rejects(pickOutboundPallet('TASK-1', 'DP-1', request))
const original = calls[0].data
for (const status of ['NOT_REQUIRED', 'READY', 'CONSUMED']) {
calls = transport({ status, ready: true })
await pickOutboundPallet('TASK-1', 'DP-1', request)
assert.deepEqual(calls[0].data, original)
assert.equal(calls.length, 2)
}
})
test('invalid dispatch version or missing command identity cannot start a reservation', async () => {
const calls = transport({ status: 'READY', ready: true })
await assert.rejects(pickDispatchItem('DSP-1', '11', { idempotencyKey: 'key' }), /版本/)
await assert.rejects(pickOutboundPallet('TASK-1', 'DP-1', {}), /标识/)
assert.equal(calls.length, 0)
})
test('discard requires funds before stock confirmation and preserves the complete source payload on retry', async () => {
const lines = [{ itemId: '9007199254740993', actualPackages: 2 }]
const submit = () => confirmCargoDiscard('CD-1', 'P-1', lines, ['101'], ['BOX-1'], false, { idempotencyKey: 'discard-stable-key' })
let calls = transport({ status: 'FREEZE_PENDING', ready: false })
await assert.rejects(submit(), error => error.storageRentPreparation === true)
assert.equal(calls.length, 1)
const original = calls[0].data
assert.deepEqual(original, { mode: 'DISCARD', businessNo: 'CD-1', palletNo: 'P-1', lines, photoFileIds: ['101'], scannedCartonMarks: ['BOX-1'], confirmAllCartons: false, idempotencyKey: 'discard-stable-key' })
calls = transport({ status: 'READY', ready: true })
await submit()
assert.deepEqual(calls[0].data, original)
assert.equal(calls.length, 2)
assert.ok(calls[1].url.endsWith('/cargo-discards/CD-1/pallets/P-1/confirm'))
assert.deepEqual(calls[1].data, { lines, photoFileIds: ['101'], scannedCartonMarks: ['BOX-1'], confirmAllCartons: false, idempotencyKey: 'discard-stable-key' })
})