133 lines
7.6 KiB
JavaScript
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'))
|
|
})
|