202 lines
6.0 KiB
JavaScript
202 lines
6.0 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
import test from 'node:test'
|
|
|
|
import { useApprovalTaskQueue } from '../src/composables/useApprovalTaskQueue.js'
|
|
|
|
function queueItem(id, overrides = {}) {
|
|
return {
|
|
task: {
|
|
id,
|
|
claimId: `claim-${id}`,
|
|
status: 'pending',
|
|
version: 1,
|
|
canAct: true,
|
|
availableActions: ['approve'],
|
|
batchEligible: true,
|
|
claimStatusSnapshot: 'submitted',
|
|
claimStageSnapshot: '直属领导审批',
|
|
...overrides
|
|
},
|
|
claim: { id: `claim-${id}`, claim_no: `BX-${id}` }
|
|
}
|
|
}
|
|
|
|
function listResult(items, page = 1) {
|
|
return {
|
|
items,
|
|
total: items.length,
|
|
page,
|
|
pageSize: 20,
|
|
totalPages: items.length ? 1 : 0,
|
|
generatedAt: '2026-07-16T10:00:00Z'
|
|
}
|
|
}
|
|
|
|
function deferred() {
|
|
let resolve
|
|
let reject
|
|
const promise = new Promise((resolvePromise, rejectPromise) => {
|
|
resolve = resolvePromise
|
|
reject = rejectPromise
|
|
})
|
|
return { promise, reject, resolve }
|
|
}
|
|
|
|
test('列表请求代次阻止慢请求覆盖新页结果', async () => {
|
|
const requests = []
|
|
const queue = useApprovalTaskQueue({
|
|
services: {
|
|
fetchApprovalTasks(params) {
|
|
const request = deferred()
|
|
requests.push({ params, request })
|
|
return request.promise
|
|
}
|
|
}
|
|
})
|
|
|
|
const firstLoad = queue.loadQueue()
|
|
await queue.setPage(2, { reload: false })
|
|
const secondLoad = queue.loadQueue()
|
|
requests[1].request.resolve(listResult([queueItem('task-new')], 2))
|
|
await secondLoad
|
|
requests[0].request.resolve(listResult([queueItem('task-stale')], 1))
|
|
const staleResult = await firstLoad
|
|
|
|
assert.equal(requests[0].params.page, 1)
|
|
assert.equal(requests[1].params.page, 2)
|
|
assert.equal(staleResult.ignored, true)
|
|
assert.equal(queue.items.value[0].task.id, 'task-new')
|
|
assert.equal(queue.page.value, 2)
|
|
})
|
|
|
|
test('筛选变化清空选择并回到第一页', async () => {
|
|
const queue = useApprovalTaskQueue({
|
|
services: {
|
|
async fetchApprovalTasks() {
|
|
return listResult([queueItem('task-001')])
|
|
}
|
|
}
|
|
})
|
|
await queue.loadQueue()
|
|
queue.toggleTask(queue.items.value[0], true)
|
|
await queue.setPage(3, { reload: false })
|
|
assert.deepEqual(queue.selectedTaskIds.value, ['task-001'])
|
|
|
|
await queue.setFilters({ riskLevel: 'high' }, { reload: false })
|
|
assert.equal(queue.page.value, 1)
|
|
assert.equal(queue.filters.value.riskLevel, 'high')
|
|
assert.deepEqual(queue.selectedTaskIds.value, [])
|
|
})
|
|
|
|
test('批量部分成功保留失败选择并使用逐项任务快照', async () => {
|
|
const batchCalls = []
|
|
let listCallCount = 0
|
|
const rows = [queueItem('task-001'), queueItem('task-002')]
|
|
const queue = useApprovalTaskQueue({
|
|
services: {
|
|
async fetchApprovalTasks() {
|
|
listCallCount += 1
|
|
return listResult(rows)
|
|
},
|
|
async batchApproveApprovalTasks(payload) {
|
|
batchCalls.push(payload)
|
|
return {
|
|
batchRequestId: payload.batchRequestId,
|
|
status: 'partial',
|
|
succeededCount: 1,
|
|
replayedCount: 0,
|
|
conflictCount: 0,
|
|
blockedCount: 1,
|
|
forbiddenCount: 0,
|
|
failedCount: 0,
|
|
items: [
|
|
{ taskId: 'task-001', status: 'succeeded' },
|
|
{ taskId: 'task-002', status: 'blocked', message: '存在开放高风险' }
|
|
]
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
await queue.loadQueue()
|
|
queue.toggleCurrentPage(true)
|
|
const result = await queue.batchApprove({ opinion: '批量核对完成' })
|
|
|
|
assert.equal(listCallCount, 2)
|
|
assert.equal(batchCalls.length, 1)
|
|
assert.equal(batchCalls[0].items[0].task.id, 'task-001')
|
|
assert.equal(batchCalls[0].items[0].opinion, '批量核对完成')
|
|
assert.equal(result.status, 'partial')
|
|
assert.deepEqual(queue.selectedTaskIds.value, ['task-002'])
|
|
assert.equal(queue.lastBatchResult.value.items[1].status, 'blocked')
|
|
assert.equal(queue.pendingBatchRequestId.value, '')
|
|
})
|
|
|
|
test('单项刷新采用服务端最新权限并移除失效选择', async () => {
|
|
const queue = useApprovalTaskQueue({
|
|
services: {
|
|
async fetchApprovalTasks() {
|
|
return listResult([queueItem('task-001')])
|
|
},
|
|
async fetchApprovalTaskDetail() {
|
|
return queueItem('task-001', {
|
|
version: 2,
|
|
canAct: false,
|
|
availableActions: [],
|
|
batchEligible: false,
|
|
readOnlyReason: '任务已转交给其他审批人'
|
|
})
|
|
}
|
|
}
|
|
})
|
|
|
|
await queue.loadQueue()
|
|
queue.toggleTask(queue.items.value[0], true)
|
|
await queue.refreshTask('task-001')
|
|
|
|
assert.equal(queue.items.value[0].task.version, 2)
|
|
assert.equal(queue.items.value[0].task.readOnlyReason, '任务已转交给其他审批人')
|
|
assert.deepEqual(queue.selectedTaskIds.value, [])
|
|
})
|
|
|
|
test('批量选择在前端基础层限制为服务端允许的 20 项', async () => {
|
|
const rows = Array.from({ length: 21 }, (_, index) => queueItem(`task-${index + 1}`))
|
|
const queue = useApprovalTaskQueue({
|
|
services: {
|
|
async fetchApprovalTasks() {
|
|
return listResult(rows)
|
|
}
|
|
}
|
|
})
|
|
|
|
await queue.loadQueue()
|
|
queue.toggleCurrentPage(true)
|
|
|
|
assert.equal(queue.selectedTaskIds.value.length, 20)
|
|
assert.match(queue.errorMessage.value, /最多选择 20 项/)
|
|
})
|
|
|
|
test('批量失败按相同负载复用幂等键,编辑意见后生成新键', async () => {
|
|
const requestIds = []
|
|
const queue = useApprovalTaskQueue({
|
|
services: {
|
|
async fetchApprovalTasks() {
|
|
return listResult([queueItem('task-retry')])
|
|
},
|
|
async batchApproveApprovalTasks(payload) {
|
|
requestIds.push(payload.batchRequestId)
|
|
throw new Error('模拟网络中断')
|
|
}
|
|
}
|
|
})
|
|
|
|
await queue.loadQueue()
|
|
queue.toggleCurrentPage(true)
|
|
await assert.rejects(queue.batchApprove({ opinion: '核对通过' }), /模拟网络中断/)
|
|
await assert.rejects(queue.batchApprove({ opinion: '核对通过' }), /模拟网络中断/)
|
|
await assert.rejects(queue.batchApprove({ opinion: '补充核对后通过' }), /模拟网络中断/)
|
|
|
|
assert.equal(requestIds[1], requestIds[0])
|
|
assert.notEqual(requestIds[2], requestIds[0])
|
|
})
|