Files
pda-uniapp/tests/dropship-workspace.test.mjs
T

133 lines
7.6 KiB
JavaScript

import test from 'node:test'
import assert from 'node:assert/strict'
import { readFile } from 'node:fs/promises'
import vm from 'node:vm'
import { sanitizeUserMessage } from '../utils/user-facing.js'
const source = await readFile(new URL('../pages/dropship-palletization/dropship-palletization.vue', import.meta.url), 'utf8')
const script = source.match(/<script>([\s\S]*?)<\/script>/)[1].replace(/^\s*import .*$/gm, '').replace('export default', 'globalThis.page =')
function harness(overrides = {}) {
const calls = []
const target = { taskId: '9007199254740999', customerName: '客户B', taskStatus: 'PENDING', palletSetupReady: true, pallets: [] }
const pallet = { palletId: '9007199254740998', palletNo: 'DP-261019-0001', status: 'OPEN', cartonCount: 2 }
const context = {
PortalHeader: {}, PopupHeader: {}, ScanTargetInput: {}, session: { currentUser: { id: '7' } },
getDropshipPalletizationTask: async (id) => { calls.push(['get', id]); return { ...target, taskId: id } },
claimDropshipPalletizationTask: async (id) => { calls.push(['claim', id]); return { ...target, taskId: id, taskStatus: 'CLAIMED' } },
resolveDropshipWorkspaceCarton: async () => ({ task: target, carton: { boxCode: 'BOX-B', canCount: true, skuLines: [{ skuCode: 'S', inboundDetailId: '44', expectedQuantity: 3 }] } }),
resolveDropshipWorkspacePallet: async () => ({ pallet, members: [] }),
countDropshipCarton: async (id, payload) => { calls.push(['count', id, payload]); return { carton: {} } },
moveDropshipCarton: async (id, cartonId, payload) => { calls.push(['move', id, cartonId, payload]); return { ...target, taskId: id } },
saveDropshipPalletizingDraft: () => {}, clearDropshipPalletizingDraft: () => {}, stableOperationId: () => 'stable-key',
isOnline: () => true, sanitizeUserMessage, console: { error: (...args) => calls.push(['diagnostic', ...args]) }, uni: { redirectTo: (value) => calls.push(['redirect', value]) },
...overrides
}
vm.runInNewContext(script, context)
const state = context.page.data()
Object.assign(state, context.page.methods, { taskId: '10', warehouseId: '100', task: { taskStatus: 'CLAIMED', palletSetupReady: true, version: 1 }, loading: false })
for (const [key, value] of Object.entries(context.page.computed)) Object.defineProperty(state, key, { get: () => value.call(state), configurable: true })
return { state, calls, pallet, target }
}
test('scan failures show the business message and retain correlation evidence in diagnostics', async () => {
const failure = { message: '箱码不属于当前一件代发入仓单', code: 'A0404', requestId: 'request-test', traceId: 'trace-test' }
const { state, calls } = harness({ resolveDropshipWorkspaceCarton: async () => { throw failure } })
await state.resolveCarton('WRONG-BOX')
assert.equal(state.error, failure.message)
assert.equal(state.loading, false)
const evidence = calls.find(call => call[0] === 'diagnostic')[2]
assert.equal(evidence.code, failure.code)
assert.equal(evidence.requestId, failure.requestId)
assert.equal(evidence.traceId, failure.traceId)
assert.equal(state.formatOperationError({}, '箱码解析失败'), '箱码解析失败')
})
test('cross customer scan remains read only until confirmation and then claims original task', async () => {
const { state, calls, pallet, target } = harness()
state.workspace = { pallet, members: [] }; state.selectedPalletNo = pallet.palletNo
await state.resolveCarton('BOX-B')
assert.equal(state.taskId, '10')
assert.equal(state.scanTask.taskId, target.taskId)
assert.equal(calls.length, 0)
await state.confirmCarton()
assert.equal(calls[0][0], 'get')
assert.equal(calls[1][0], 'claim')
const counted = calls.find((call) => call[0] === 'count')
assert.equal(counted[1], target.taskId)
assert.equal(counted[2].palletNo, pallet.palletNo)
assert.equal(state.taskId, target.taskId)
})
test('failed pallet lookup removes stale active pallet and blocks counting', async () => {
const { state, pallet } = harness({ resolveDropshipWorkspacePallet: async () => { throw new Error('托盘不存在') } })
state.workspace = { pallet, members: [] }; state.selectedPalletNo = pallet.palletNo
state.carton = { canCount: true }; state.skuCounts = [{ actualQuantity: 1, expectedQuantity: 1 }]
await state.selectPallet('DP-261019-9999')
assert.equal(state.currentPallet, null)
assert.equal(state.canSubmitCarton, false)
})
test('moving another customers carton submits its own task id', async () => {
const { state, calls, pallet, target } = harness()
state.workspace = { pallet, members: [{ taskId: target.taskId, inboundId: '31', customerId: '2', customerName: '客户B' }] }
state.selectedPalletNo = pallet.palletNo
await state.moveCarton({ cartonId: '99', inboundId: '31' }, null)
assert.equal(calls.find((call) => call[0] === 'move')[1], target.taskId)
})
test('switching tickets carries only validated open pallet and scanner cannot race submission', async () => {
const { state, calls, pallet } = harness()
state.workspace = { pallet, members: [] }; state.selectedPalletNo = pallet.palletNo
state.continueOtherTask()
assert.match(calls[0][1].url, /palletNo=DP-261019-0001/)
state.submitting = true
await state.selectPallet('DP-261019-0002')
assert.equal(state.currentPallet.palletNo, pallet.palletNo)
})
test('reopening a task drops a pallet sealed since the draft was saved without mutating business facts', async () => {
const cleared = []
const { state, pallet, calls } = harness({ clearDropshipPalletizingDraft: (identity) => cleared.push(identity) })
pallet.status = 'SEALED'
state.selectedPalletNo = pallet.palletNo; state.palletCode = pallet.palletNo; state.selectedPalletId = pallet.palletId
await state.loadTask({ restorePallet: true })
assert.equal(state.currentPallet, null)
assert.equal(state.selectedPalletNo, '')
assert.equal(state.selectedPalletId, '')
assert.equal(state.palletCode, '')
assert.equal(cleared.length, 1)
assert.equal(cleared[0].taskId, '10')
assert.deepEqual(calls.map(call => call[0]), ['get'])
})
test('restoring open cross customer pallet still works and explicit sealed scan remains inspectable', async () => {
const { state, pallet } = harness()
state.selectedPalletNo = pallet.palletNo
await state.loadTask({ restorePallet: true })
assert.equal(state.currentPallet.palletNo, pallet.palletNo)
pallet.status = 'SEALED'
await state.selectPallet(pallet.palletNo)
assert.equal(state.currentPallet.status, 'SEALED')
assert.equal(state.canSubmitCarton, false)
})
test('task list stops carrying a pallet sealed while another ticket was being worked', async () => {
const taskSource = await readFile(new URL('../pages/dropship-tasks/dropship-tasks.vue', import.meta.url), 'utf8')
const taskScript = taskSource.match(/<script>([\s\S]*?)<\/script>/)[1].replace(/^\s*import .*$/gm, '').replace('export default', 'globalThis.page =')
let status = 'OPEN'
const navigations = []
const context = { PortalHeader: {}, ScanTargetInput: {}, session: { currentUser: { warehouseId: '100' } },
resolveDropshipWorkspacePallet: async () => ({ pallet: { status } }),
listDropshipPalletizationTasks: async () => [], formatApiError: error => error.message,
uni: { navigateTo: value => navigations.push(value.url) } }
vm.runInNewContext(taskScript, context)
const state = Object.assign(context.page.data(), context.page.methods, { palletNo: 'DP-261019-0014' })
const task = { taskId: '20', warehouseId: '100' }
await state.loadTasks(); state.openTask(task)
assert.match(navigations[0], /palletNo=DP-261019-0014/)
status = 'SEALED'
await state.loadTasks(); state.openTask(task)
assert.equal(state.palletNo, '')
assert.ok(!navigations[1].includes('DP-261019-0014'))
})