测试:覆盖批次授权并发状态
This commit is contained in:
@@ -5,6 +5,7 @@ import { test } from 'node:test'
|
|||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from 'node:url'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import { promisify } from 'node:util'
|
import { promisify } from 'node:util'
|
||||||
|
import * as vm from 'node:vm'
|
||||||
import * as testBatchContext from '../src/utils/testBatchContext.js'
|
import * as testBatchContext from '../src/utils/testBatchContext.js'
|
||||||
|
|
||||||
const frontendRoot = fileURLToPath(new URL('..', import.meta.url))
|
const frontendRoot = fileURLToPath(new URL('..', import.meta.url))
|
||||||
@@ -778,6 +779,85 @@ function extractNamedFunction(source, functionName) {
|
|||||||
return source.slice(match.index, bodyEnd + 1)
|
return source.slice(match.index, bodyEnd + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractScriptSetup(source) {
|
||||||
|
const match = /<script\s+setup(?:\s+[^>]*)?>([\s\S]*?)<\/script>/.exec(source)
|
||||||
|
assert.ok(match, '缺少 script setup')
|
||||||
|
return match[1].replace(/^import\s+.*$/gm, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDeferred() {
|
||||||
|
let resolve
|
||||||
|
let reject
|
||||||
|
const promise = new Promise((resolvePromise, rejectPromise) => {
|
||||||
|
resolve = resolvePromise
|
||||||
|
reject = rejectPromise
|
||||||
|
})
|
||||||
|
return { promise, resolve, reject }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function flushPromises() {
|
||||||
|
await Promise.resolve()
|
||||||
|
await Promise.resolve()
|
||||||
|
await Promise.resolve()
|
||||||
|
await Promise.resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
function createTestBatchPageHarness(pageSource, api = {}) {
|
||||||
|
const noopRequest = () => Promise.reject(new Error('未配置的测试请求'))
|
||||||
|
const ref = value => ({ value })
|
||||||
|
const context = vm.createContext({
|
||||||
|
Array,
|
||||||
|
Boolean,
|
||||||
|
Error,
|
||||||
|
JSON,
|
||||||
|
Number,
|
||||||
|
Object,
|
||||||
|
Promise,
|
||||||
|
Set,
|
||||||
|
String,
|
||||||
|
console,
|
||||||
|
ref,
|
||||||
|
reactive: value => value,
|
||||||
|
computed: getter => ({ get value() { return getter() } }),
|
||||||
|
getCurrentInstance: () => ({ proxy: { $modal: { confirm: () => Promise.resolve(), msgError: () => {}, msgSuccess: () => {} } } }),
|
||||||
|
onBeforeUnmount: () => {},
|
||||||
|
onMounted: () => {},
|
||||||
|
window: { innerWidth: 1280, addEventListener: () => {}, removeEventListener: () => {} },
|
||||||
|
useUserStore: () => ({ admin: true }),
|
||||||
|
useTestBatchStore: () => ({ currentBatchId: undefined, leaveTestMode: () => {}, refreshOptions: () => Promise.resolve() }),
|
||||||
|
addTestBatch: api.addTestBatch || noopRequest,
|
||||||
|
assignTestBatchUsers: api.assignTestBatchUsers || noopRequest,
|
||||||
|
closeTestBatch: api.closeTestBatch || noopRequest,
|
||||||
|
getTestBatch: api.getTestBatch || noopRequest,
|
||||||
|
listTestBatches: api.listTestBatches || noopRequest,
|
||||||
|
previewTestBatchPurge: api.previewTestBatchPurge || noopRequest,
|
||||||
|
purgeTestBatch: api.purgeTestBatch || noopRequest,
|
||||||
|
reopenTestBatch: api.reopenTestBatch || noopRequest,
|
||||||
|
updateTestBatch: api.updateTestBatch || noopRequest
|
||||||
|
})
|
||||||
|
const exposure = `
|
||||||
|
globalThis.__testBatchHarness = {
|
||||||
|
openAssign,
|
||||||
|
submitAssign,
|
||||||
|
getList,
|
||||||
|
hydrateParticipantCounts,
|
||||||
|
state: {
|
||||||
|
assignBatch,
|
||||||
|
assignError,
|
||||||
|
assignLoading,
|
||||||
|
assignVisible,
|
||||||
|
assignedUserIds,
|
||||||
|
batchList,
|
||||||
|
detailLoaded,
|
||||||
|
loadError,
|
||||||
|
loading
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
vm.runInContext(`${extractScriptSetup(pageSource)}\n${exposure}`, context, { filename: 'src/views/system/testBatch/index.vue' })
|
||||||
|
return context.__testBatchHarness
|
||||||
|
}
|
||||||
|
|
||||||
function extractPromiseCallback(functionSource, methodName, occurrence = 0) {
|
function extractPromiseCallback(functionSource, methodName, occurrence = 0) {
|
||||||
const sanitized = sanitizeSource(functionSource, { allowTemplateInterpolation: true })
|
const sanitized = sanitizeSource(functionSource, { allowTemplateInterpolation: true })
|
||||||
const marker = `.${methodName}(`
|
const marker = `.${methodName}(`
|
||||||
@@ -2543,3 +2623,141 @@ test('测试批次授权、清理和对话框异步状态拒绝错误目标与
|
|||||||
assert.match(adoption, /max-height:\s*90vh/)
|
assert.match(adoption, /max-height:\s*90vh/)
|
||||||
assert.match(adoption, /overflow-y:\s*auto/)
|
assert.match(adoption, /overflow-y:\s*auto/)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('测试批次管理页真实授权详情 success、failure 与 finally 不得覆盖新会话', async () => {
|
||||||
|
const management = await source('src/views/system/testBatch/index.vue')
|
||||||
|
const detailRequests = []
|
||||||
|
const page = createTestBatchPageHarness(management, {
|
||||||
|
getTestBatch: batchId => {
|
||||||
|
const request = createDeferred()
|
||||||
|
detailRequests.push({ batchId, request })
|
||||||
|
return request.promise
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
page.openAssign({ batchId: 1 })
|
||||||
|
page.openAssign({ batchId: 2 })
|
||||||
|
detailRequests[0].request.resolve({ data: { batchId: 1, batchName: '批次 A', userIds: [101] } })
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
assert.equal(page.state.assignBatch.value, null, '过期 success 不得写入新批次详情')
|
||||||
|
assert.equal(page.state.assignedUserIds.value, '', '过期 success 不得写入新批次用户')
|
||||||
|
assert.equal(page.state.detailLoaded.value, false, '过期 success 不得解锁保存')
|
||||||
|
assert.equal(page.state.assignLoading.value, true, '过期 finally 不得结束新会话 loading')
|
||||||
|
|
||||||
|
detailRequests[1].request.resolve({ data: { batchId: 2, batchName: '批次 B', userIds: [202] } })
|
||||||
|
await flushPromises()
|
||||||
|
assert.deepEqual(page.state.assignBatch.value, { batchId: 2, batchName: '批次 B', userIds: [202] })
|
||||||
|
assert.equal(page.state.assignedUserIds.value, '202')
|
||||||
|
assert.equal(page.state.detailLoaded.value, true)
|
||||||
|
assert.equal(page.state.assignLoading.value, false)
|
||||||
|
|
||||||
|
const failedRequests = []
|
||||||
|
const failedPage = createTestBatchPageHarness(management, {
|
||||||
|
getTestBatch: batchId => {
|
||||||
|
const request = createDeferred()
|
||||||
|
failedRequests.push({ batchId, request })
|
||||||
|
return request.promise
|
||||||
|
}
|
||||||
|
})
|
||||||
|
failedPage.openAssign({ batchId: 3 })
|
||||||
|
failedPage.openAssign({ batchId: 4 })
|
||||||
|
failedRequests[1].request.resolve({ data: { batchId: 4, batchName: '批次 B', userIds: [404] } })
|
||||||
|
await flushPromises()
|
||||||
|
failedRequests[0].request.reject(new Error('A 详情失败'))
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
assert.deepEqual(failedPage.state.assignBatch.value, { batchId: 4, batchName: '批次 B', userIds: [404] }, '过期 failure 不得清空已加载的新会话详情')
|
||||||
|
assert.equal(failedPage.state.assignedUserIds.value, '404', '过期 failure 不得改写新批次输入')
|
||||||
|
assert.equal(failedPage.state.detailLoaded.value, true)
|
||||||
|
assert.equal(failedPage.state.assignLoading.value, false)
|
||||||
|
assert.equal(failedPage.state.assignError.value, '', '过期 failure 不得展示旧批次错误')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('测试批次管理页当前授权详情失败保持禁用并拒绝空数组提交', async () => {
|
||||||
|
const management = await source('src/views/system/testBatch/index.vue')
|
||||||
|
const detailRequest = createDeferred()
|
||||||
|
const assignments = []
|
||||||
|
const page = createTestBatchPageHarness(management, {
|
||||||
|
getTestBatch: () => detailRequest.promise,
|
||||||
|
assignTestBatchUsers: (batchId, payload) => {
|
||||||
|
assignments.push({ batchId, payload })
|
||||||
|
return Promise.resolve({ msg: '不应调用' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
page.openAssign({ batchId: 5 })
|
||||||
|
detailRequest.reject(new Error('详情失败'))
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
assert.equal(page.state.assignBatch.value, null)
|
||||||
|
assert.equal(page.state.assignedUserIds.value, '')
|
||||||
|
assert.equal(page.state.detailLoaded.value, false, '详情失败后不得解锁保存')
|
||||||
|
assert.equal(page.state.assignLoading.value, false)
|
||||||
|
assert.match(page.state.assignError.value, /详情加载失败/)
|
||||||
|
page.submitAssign()
|
||||||
|
assert.deepEqual(assignments, [], '详情未成功加载时不得提交移除全部用户的空数组')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('测试批次管理页真实 hydration 拒绝旧版本回写并保留当前失败状态', async () => {
|
||||||
|
const management = await source('src/views/system/testBatch/index.vue')
|
||||||
|
const listRequests = []
|
||||||
|
const detailRequests = []
|
||||||
|
const page = createTestBatchPageHarness(management, {
|
||||||
|
listTestBatches: () => {
|
||||||
|
const request = createDeferred()
|
||||||
|
listRequests.push(request)
|
||||||
|
return request.promise
|
||||||
|
},
|
||||||
|
getTestBatch: batchId => {
|
||||||
|
const request = createDeferred()
|
||||||
|
detailRequests.push({ batchId, request })
|
||||||
|
return request.promise
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
page.getList()
|
||||||
|
listRequests[0].resolve({ rows: [{ batchId: 7, batchName: '旧列表' }], total: 1 })
|
||||||
|
await flushPromises()
|
||||||
|
page.getList()
|
||||||
|
listRequests[1].resolve({ rows: [{ batchId: 7, batchName: '新列表' }], total: 1 })
|
||||||
|
await flushPromises()
|
||||||
|
detailRequests[0].request.resolve({ data: { batchId: 7, userIds: [1, 2] } })
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
assert.equal(page.state.batchList.value[0].batchName, '新列表')
|
||||||
|
assert.equal(page.state.batchList.value[0].participantState, 'loading', '旧 success 不得写入新列表同 ID 行')
|
||||||
|
assert.equal(page.state.batchList.value[0].participantCount, undefined)
|
||||||
|
|
||||||
|
const failedListRequests = []
|
||||||
|
const failedDetailRequests = []
|
||||||
|
const failedPage = createTestBatchPageHarness(management, {
|
||||||
|
listTestBatches: () => {
|
||||||
|
const request = createDeferred()
|
||||||
|
failedListRequests.push(request)
|
||||||
|
return request.promise
|
||||||
|
},
|
||||||
|
getTestBatch: batchId => {
|
||||||
|
const request = createDeferred()
|
||||||
|
failedDetailRequests.push({ batchId, request })
|
||||||
|
return request.promise
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
failedPage.getList()
|
||||||
|
failedListRequests[0].resolve({ rows: [{ batchId: 8, batchName: '旧列表' }], total: 1 })
|
||||||
|
await flushPromises()
|
||||||
|
failedPage.getList()
|
||||||
|
failedListRequests[1].resolve({ rows: [{ batchId: 8, batchName: '新列表' }], total: 1 })
|
||||||
|
await flushPromises()
|
||||||
|
failedDetailRequests[0].request.reject(new Error('旧详情失败'))
|
||||||
|
await flushPromises()
|
||||||
|
|
||||||
|
assert.equal(failedPage.state.batchList.value[0].participantState, 'loading', '旧 failure 不得把新列表同 ID 行标为失败')
|
||||||
|
failedDetailRequests[1].request.reject(new Error('当前详情失败'))
|
||||||
|
await flushPromises()
|
||||||
|
assert.equal(failedPage.state.batchList.value.length, 1, '当前详情失败不得清空整个列表')
|
||||||
|
assert.equal(failedPage.state.batchList.value[0].participantState, 'error')
|
||||||
|
assert.equal(failedPage.state.batchList.value[0].participantCount, undefined)
|
||||||
|
assert.equal(failedPage.state.loadError.value, '', '当前详情失败必须是单项状态,不得升级为列表失败')
|
||||||
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user