调整部分前端布局和后端增删改逻辑
This commit is contained in:
@@ -1076,14 +1076,65 @@ test('重复扫码仅在相同值和时间窗口内成立', async () => {
|
||||
assert.equal(isDuplicateScan('qr-a', 'qr-b', 1200, 1000, 500), false)
|
||||
})
|
||||
|
||||
test('幂等键由 Web Crypto randomUUID 生成且不降级为伪随机值', async () => {
|
||||
test('幂等键优先使用 Web Crypto randomUUID', async () => {
|
||||
const palletScanPath = path.join(frontendRoot, 'src/utils/palletScan.js')
|
||||
const source = await readFile(palletScanPath, 'utf8')
|
||||
const { createIdempotencyKey } = await import('../src/utils/palletScan.js')
|
||||
let getRandomValuesCalled = false
|
||||
const expectedKey = '12345678-1234-4123-8123-123456789abc'
|
||||
|
||||
assert.match(source, /globalThis\.crypto\.randomUUID\(\)/)
|
||||
const actualKey = createIdempotencyKey({
|
||||
randomUUID: () => expectedKey,
|
||||
getRandomValues: () => {
|
||||
getRandomValuesCalled = true
|
||||
throw new Error('存在 randomUUID 时不应调用 getRandomValues')
|
||||
}
|
||||
})
|
||||
|
||||
assert.equal(actualKey, expectedKey)
|
||||
assert.equal(getRandomValuesCalled, false)
|
||||
assert.doesNotMatch(source, /Date\.now|Math\.random|new Date/)
|
||||
assert.match(createIdempotencyKey(), /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i)
|
||||
})
|
||||
|
||||
test('局域网 HTTP 环境缺少 randomUUID 时使用安全随机数生成 UUID v4', async () => {
|
||||
const { createIdempotencyKey } = await import('../src/utils/palletScan.js')
|
||||
const cryptoProvider = {
|
||||
getRandomValues(bytes) {
|
||||
bytes.fill(0)
|
||||
return bytes
|
||||
}
|
||||
}
|
||||
|
||||
assert.equal(createIdempotencyKey(cryptoProvider), '00000000-0000-4000-8000-000000000000')
|
||||
})
|
||||
|
||||
test('首次入库确认由业务幂等键保护,不被通用短时重复提交规则拦截', async () => {
|
||||
const source = await readFile(path.join(frontendRoot, 'src/api/business/inbound.js'), 'utf8')
|
||||
const confirmInboundSource = extractNamedFunction(source, 'confirmInbound')
|
||||
|
||||
assert.match(confirmInboundSource, /headers\s*:\s*\{\s*repeatSubmit\s*:\s*false\s*\}/)
|
||||
})
|
||||
|
||||
test('首次入库确认重试前清理旧错误,并明确重试会再次请求服务端', async () => {
|
||||
const source = await readFile(path.join(frontendRoot, 'src/views/business/inbound/index.vue'), 'utf8')
|
||||
const confirmSource = extractNamedFunction(source, 'handleConfirm')
|
||||
const confirmCatch = extractPromiseCallback(confirmSource, 'catch')
|
||||
const clearErrorIndex = confirmSource.indexOf("loadError.value = ''")
|
||||
|
||||
assert.ok(clearErrorIndex >= 0, '入库确认缺少旧错误状态清理')
|
||||
assert.ok(
|
||||
clearErrorIndex < confirmSource.indexOf('proxy.$modal.confirm('),
|
||||
'打开确认框前必须清理上一次的失败状态'
|
||||
)
|
||||
assert.match(confirmCatch, /再次确认将重新请求服务端,并复用本次幂等键/)
|
||||
})
|
||||
|
||||
test('首次入库确认失败后取消重试不丢弃原幂等键', async () => {
|
||||
const source = await readFile(path.join(frontendRoot, 'src/views/business/inbound/index.vue'), 'utf8')
|
||||
const confirmSource = extractNamedFunction(source, 'handleConfirm')
|
||||
const confirmCatch = extractPromiseCallback(confirmSource, 'catch')
|
||||
|
||||
assert.doesNotMatch(confirmCatch, /clearConfirmIdempotency\(\)|handleConfirmCancellation\(/)
|
||||
})
|
||||
|
||||
test('17 个 API 文件逐一匹配硬编码请求契约', async (t) => {
|
||||
|
||||
Reference in New Issue
Block a user