适配PDA稳定柜次身份契约
This commit is contained in:
@@ -2,6 +2,7 @@ import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
|
||||
const { stableOperationId } = await import(new URL('../utils/idempotency.js', import.meta.url).href)
|
||||
const { stableTaskIdentityKey, stableTaskOperationIdentity } = await import(new URL('../utils/task-identity.js', import.meta.url).href)
|
||||
|
||||
test('PDA operation id is stable ASCII and within the backend 128 character limit', () => {
|
||||
const payload = { itemStates: Object.fromEntries(Array.from({ length: 500 }, (_, index) => [`明细-${index}`, 'PICKED'])), reason: '中文异常原因'.repeat(100) }
|
||||
@@ -17,3 +18,18 @@ test('PDA operation id changes when the draft changes', () => {
|
||||
const second = stableOperationId('submit', 'operator-task-container', { item: 'SKIPPED' })
|
||||
assert.notEqual(first, second)
|
||||
})
|
||||
|
||||
test('real container number changes do not move the draft or idempotency identity', () => {
|
||||
const before = { operatorId: 7, taskId: 11, loadingPlanId: 13, containerBookingId: 17, currentContainerNo: 'REAL-001' }
|
||||
const after = { ...before, currentContainerNo: 'REAL-002' }
|
||||
const payload = { item: 'PICKED' }
|
||||
assert.equal(stableTaskIdentityKey(before), stableTaskIdentityKey(after))
|
||||
assert.equal(stableOperationId('submit', stableTaskOperationIdentity(before), payload), stableOperationId('submit', stableTaskOperationIdentity(after), payload))
|
||||
})
|
||||
|
||||
test('container booking mismatch is rejected by stable task binding', () => {
|
||||
const original = { operatorId: 7, taskId: 11, loadingPlanId: 13, containerBookingId: 17 }
|
||||
const differentBooking = { ...original, containerBookingId: 18 }
|
||||
assert.equal(stableTaskIdentityKey(original), stableTaskIdentityKey(differentBooking))
|
||||
assert.notEqual(stableTaskOperationIdentity(original), stableTaskOperationIdentity(differentBooking))
|
||||
})
|
||||
|
||||
@@ -68,7 +68,15 @@ test('PDA cabinet picking adapter claims online and submits one atomic batch', (
|
||||
assert.match(page, /claimContainerPickingTask\(/)
|
||||
assert.match(page, /submitContainerPickingBatch\(/)
|
||||
assert.match(page, /任务版本已变化,请刷新后重试/)
|
||||
assert.match(source, /data: \{ warehouseId, containerNo: task\.containerNo, expectedVersion, inventorySnapshotToken, items \}/)
|
||||
assert.match(source, /data: \{ warehouseId, loadingPlanId: task\.loadingPlanId, containerBookingId: task\.containerBookingId, expectedVersion, inventorySnapshotToken, items \}/)
|
||||
assert.match(source, /cabinetNo=/)
|
||||
assert.match(source, /loadingPlanId=/)
|
||||
assert.match(source, /tasks\/\$\{encodeURIComponent\(taskId\)\}\?warehouseId=/)
|
||||
assert.match(source, /currentContainerNo/)
|
||||
assert.match(source, /getPickingInventorySnapshot[\s\S]*?\.then\(normalizeInventorySnapshot\)/)
|
||||
assert.doesNotMatch(source.match(/export async function submitContainerPickingBatch[\s\S]*?\n\}/)?.[0] || '', /containerNo/)
|
||||
assert.doesNotMatch(page, /claim-[^`]*currentBox\.boxNo/)
|
||||
assert.doesNotMatch(page.match(/stableOperationId\('submit'[\s\S]*?\n/)?.[0] || '', /boxNo|containerNo|currentContainerNo/)
|
||||
assert.match(page, /await getPickingInventorySnapshot\([\s\S]*await submitContainerPickingBatch/)
|
||||
assert.match(page, /库存已变化,请刷新后重试/)
|
||||
assert.doesNotMatch(source.match(/export async function submitContainerPickingBatch[\s\S]*?\n\}/)?.[0] || '', /for \(/)
|
||||
|
||||
@@ -3,19 +3,29 @@ import assert from 'node:assert/strict'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
|
||||
const source = await readFile(new URL('../stores/picking.js', import.meta.url), 'utf8')
|
||||
const taskIdentitySource = await readFile(new URL('../utils/task-identity.js', import.meta.url), 'utf8')
|
||||
|
||||
test('picking drafts are persisted under operator, task and cabinet identity', () => {
|
||||
test('picking drafts are persisted under stable operator, task and loading plan identity', () => {
|
||||
assert.match(source, /pda-picking-draft/)
|
||||
assert.match(source, /operatorId/)
|
||||
assert.match(source, /taskId/)
|
||||
assert.match(source, /containerNo/)
|
||||
assert.match(source, /loadingPlanId/)
|
||||
assert.match(source, /containerBookingId/)
|
||||
assert.match(source, /uni\.setStorageSync\(key/)
|
||||
assert.match(source, /uni\.getStorageSync\(key/)
|
||||
assert.match(source, /uni\.removeStorageSync\(key/)
|
||||
})
|
||||
|
||||
test('draft restore rejects a different operator or task', () => {
|
||||
assert.match(source, /saved\.operatorId !== identityValue\(identity\)\.operatorId/)
|
||||
assert.match(source, /saved\.taskId !== identityValue\(identity\)\.taskId/)
|
||||
assert.match(source, /saved\.containerNo !== identityValue\(identity\)\.containerNo/)
|
||||
assert.match(source, /saved\.operatorId !== expected\.operatorId/)
|
||||
assert.match(source, /saved\.taskId !== expected\.taskId/)
|
||||
assert.match(source, /saved\.loadingPlanId !== expected\.loadingPlanId/)
|
||||
assert.match(source, /saved\.containerBookingId !== expected\.containerBookingId/)
|
||||
assert.doesNotMatch(taskIdentitySource, /containerNo|currentContainerNo|boxNo/)
|
||||
})
|
||||
|
||||
test('mutable real container number is not part of draft identity or restore validation', () => {
|
||||
const restoreGuard = source.match(/const expected = stableTaskIdentity[\s\S]*?return null/)?.[0] || ''
|
||||
assert.doesNotMatch(taskIdentitySource, /containerNo|currentContainerNo|boxNo/)
|
||||
assert.doesNotMatch(restoreGuard, /containerNo|currentContainerNo|boxNo/)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user