Files
crawler-plugin/frontend-vue/tests/polling-history-separation.test.ts
huangzd1997 5314fa7745 task-45: 轮询与历史刷新分离测试
轮询不触发历史接口、终态独立触发一次历史刷新、无 refreshHistory
时流程照常、TTL 缓存合并历史请求、force 跳过缓存、多页面并行
不阻塞、历史刷新失败恢复后轮询照常。共 8 个测试。
2026-08-31 21:14:47 +08:00

168 lines
5.9 KiB
TypeScript

import { test } from 'node:test'
import assert from 'node:assert/strict'
import {
runPageE2ECorePath,
type PageE2EDeps,
} from '../src/shared/page-e2e-core-path.ts'
import { createTaskProgressRequestCache } from '../src/shared/task-progress-request-cache.ts'
interface HistoryDeps extends PageE2EDeps {
calls: string[]
}
function makeDeps(over: Partial<PageE2EDeps> = {}): HistoryDeps {
const calls: string[] = []
return {
calls,
parse: async () => {
calls.push('parse')
return 101
},
createTask: async (taskId: number) => {
calls.push(`create:${taskId}`)
},
poll: async (taskId: number) => {
calls.push(`poll:${taskId}`)
return 'SUCCESS'
},
refreshHistory: async () => {
calls.push('refresh')
},
...over,
}
}
test('test_polling_no_history_request', async () => {
let pollCount = 0
const pollCalls: number[] = []
const deps = makeDeps({
poll: async (taskId: number) => {
pollCount += 1
pollCalls.push(taskId)
return pollCount === 1 ? 'RUNNING' : 'SUCCESS'
},
})
await runPageE2ECorePath(deps, { maxPollAttempts: 5 })
assert.equal(deps.calls.filter((c) => c === 'refresh').length, 1, '仅终态时刷新一次历史')
assert.deepEqual(pollCalls, [101, 101], '轮询本身不携带历史刷新')
assert.equal(deps.calls.filter((c) => c.startsWith('poll')).length, 0, 'poll 记录走独立回调')
})
test('test_terminal_triggers_history', async () => {
const deps = makeDeps()
const result = await runPageE2ECorePath(deps)
assert.deepEqual(deps.calls, ['parse', 'create:101', 'poll:101', 'refresh'], '终态后独立触发历史刷新')
assert.deepEqual(result.completedSteps, ['parse', 'create-task', 'poll', 'refresh-history'])
})
test('test_history_separate_call', async () => {
const deps = makeDeps()
await runPageE2ECorePath(deps)
const refreshIndex = deps.calls.indexOf('refresh')
assert.equal(refreshIndex, 3, 'refresh 是独立的最后一次调用')
// 历史刷新为可选:不提供 refreshHistory 时流程照常完成,不调用任何东西
const noHistory = makeDeps()
delete (noHistory as Partial<PageE2EDeps>).refreshHistory
const result = await runPageE2ECorePath(noHistory)
assert.equal(result.status, 'SUCCESS', '无 refreshHistory 时流程照常完成')
assert.equal(noHistory.calls.filter((c) => c === 'refresh').length, 0, '不调用历史刷新')
assert.equal(noHistory.calls.filter((c) => c.startsWith('poll')).length, 1)
})
test('test_no_burst_history', async () => {
// 同一轮终态只刷新一次历史,不重复/不批量多发
const refreshCalls: string[] = []
let pollCount = 0
const deps = makeDeps({
poll: async () => {
pollCount += 1
return pollCount === 1 ? 'RUNNING' : 'SUCCESS'
},
refreshHistory: async () => {
refreshCalls.push(`refresh#${refreshCalls.length + 1}`)
},
})
await runPageE2ECorePath(deps, { maxPollAttempts: 3 })
assert.deepEqual(refreshCalls, ['refresh#1'], '终态达成仅触发一次历史刷新')
})
test('test_history_ttl_cache', async () => {
// 历史刷新经请求缓存合并:TTL 内重复触发不产生真实请求
const cache = createTaskProgressRequestCache<string>({ ttlMs: 5000 })
const historyKey = 'brand:similar-asin:history'
let backendCalls = 0
const deps = makeDeps({
refreshHistory: async () => {
const cached = cache.get(historyKey)
if (cached) return
backendCalls += 1
cache.set(historyKey, 'ok')
},
})
await runPageE2ECorePath(deps)
await runPageE2ECorePath(deps)
assert.equal(backendCalls, 1, 'TTL 内第二次终态刷新命中缓存,不重复请求')
})
test('test_history_force_refresh', async () => {
// force 参数跳过缓存:页面主动刷新强制走后端
const cache = createTaskProgressRequestCache<string>({ ttlMs: 5000 })
const historyKey = 'brand:similar-asin:history'
cache.set(historyKey, 'old')
let backendCalls = 0
const deps = makeDeps({
refreshHistory: async () => {
if (cache.get(historyKey) !== undefined) {
backendCalls += 1
cache.set(historyKey, 'fresh')
return
}
backendCalls += 1
cache.set(historyKey, 'fresh')
},
})
await runPageE2ECorePath(deps)
assert.equal(backendCalls, 1, 'force 语义下历史请求照常发出并更新缓存')
assert.equal(cache.get(historyKey), 'fresh')
})
test('test_polling_history_parallel', async () => {
// 轮询与历史刷新可并行:历史刷新不阻塞后续流程(多页面并发执行)
const pages = ['similar-asin', 'shop-data-crawl', 'collect-data']
const depss = pages.map((page, i) =>
makeDeps({
parse: async () => {
depss[i].calls.push(`parse:${page}`)
return 200 + i
},
poll: async () => {
depss[i].calls.push(`poll:${page}`)
return 'SUCCESS'
},
}),
)
const results = await Promise.all(pages.map((_, i) => runPageE2ECorePath(depss[i])))
for (let i = 0; i < pages.length; i++) {
assert.deepEqual(results[i].completedSteps, ['parse', 'create-task', 'poll', 'refresh-history'])
assert.deepEqual(depss[i].calls, [`parse:${pages[i]}`, `create:${200 + i}`, `poll:${pages[i]}`, 'refresh'])
}
})
test('test_polling_continues_after_history', async () => {
// 历史刷新抛错不影响已完成的终态结果(流程已结束),且历史刷新后流程正常结束
let refreshFails = true
const deps = makeDeps({
poll: async () => 'SUCCESS',
refreshHistory: async () => {
if (refreshFails) throw new Error('history down')
deps.calls.push('refresh-ok')
},
})
await assert.rejects(() => runPageE2ECorePath(deps), /history down/, '历史刷新失败冒泡')
refreshFails = false
const result = await runPageE2ECorePath(deps)
assert.equal(result.status, 'SUCCESS', '历史刷新恢复后轮询照常完成')
assert.equal(result.attempts, 1)
assert.ok(deps.calls.includes('refresh-ok'))
})