Files
crawler-plugin/frontend-vue/tests/backoff-cap.test.ts
T
huangzd1997 3e45aa2a91 task-32: 退避封顶
8 个测试固化退避封顶语义:达到 maxMs 后保持恒定(含低 maxMs、
高 factor 提前封顶)、成功后从基数重新起算、恰等于 maxMs 不溢出、
循环内失败计数映射退避间隔的集成、非法 maxMs 配置拒绝。

行为由任务 29/30 的 factor 与 maxMs 配置已实现,本次为验证型测试。
2026-08-31 20:27:29 +08:00

111 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test } from 'node:test'
import assert from 'node:assert/strict'
import {
getTaskPollBackoffMs,
configureTaskPolling,
resetTaskPollingConfig,
} from '../src/shared/task-progress-config.ts'
import { useTaskProgressLoop } from '../src/shared/composables/useTaskProgressLoop.ts'
test('test_backoff_cap_reached', () => {
resetTaskPollingConfig()
assert.equal(getTaskPollBackoffMs(4), 8000)
assert.equal(getTaskPollBackoffMs(5), 10000, 'attempt 5 达到封顶')
})
test('test_backoff_stays_capped', () => {
resetTaskPollingConfig()
for (const attempt of [5, 6, 7, 10, 50]) {
assert.equal(getTaskPollBackoffMs(attempt), 10000, `attempt ${attempt} 保持 maxMs`)
}
})
test('test_cap_with_low_max', () => {
resetTaskPollingConfig()
configureTaskPolling({ backoffMaxMs: 1500 })
assert.equal(getTaskPollBackoffMs(0), 500)
assert.equal(getTaskPollBackoffMs(1), 1000)
assert.equal(getTaskPollBackoffMs(2), 1500, '低 maxMs 提前封顶')
assert.equal(getTaskPollBackoffMs(3), 1500)
resetTaskPollingConfig()
})
test('test_cap_with_high_factor', () => {
resetTaskPollingConfig()
configureTaskPolling({ backoffFactor: 5 })
assert.equal(getTaskPollBackoffMs(0), 500)
assert.equal(getTaskPollBackoffMs(1), 2500)
assert.equal(getTaskPollBackoffMs(2), 10000, '高 factor 快速封顶')
assert.equal(getTaskPollBackoffMs(3), 10000)
resetTaskPollingConfig()
})
test('test_cap_resets_after_success', () => {
resetTaskPollingConfig()
// 封顶只由 attempt 驱动:attempt 归 0 即从基数重新开始
assert.equal(getTaskPollBackoffMs(0), 500)
assert.equal(getTaskPollBackoffMs(50), 10000)
assert.equal(getTaskPollBackoffMs(0), 500, '成功后从 500 起')
assert.equal(getTaskPollBackoffMs(1), 1000)
})
test('test_cap_attempt_boundary', () => {
resetTaskPollingConfig()
configureTaskPolling({ backoffMaxMs: 2000 })
// 恰好等于封顶那一档:base × factor^1 = 1000 < 2000^2 = 2000 = 封顶
assert.equal(getTaskPollBackoffMs(1), 1000)
assert.equal(getTaskPollBackoffMs(2), 2000, '恰等于 maxMs 不溢出')
assert.equal(getTaskPollBackoffMs(3), 2000)
resetTaskPollingConfig()
})
test('test_cap_integration_loop', async () => {
resetTaskPollingConfig()
const storage = new Map<string, string>()
;(globalThis as Record<string, unknown>).window = {
localStorage: {
getItem: (k: string) => storage.get(k) ?? null,
setItem: (k: string, v: string) => { storage.set(k, v) },
removeItem: (k: string) => { storage.delete(k) },
},
setTimeout: (fn: () => void, ms: number) => globalThis.setTimeout(fn, ms),
clearTimeout: (id: unknown) => globalThis.clearTimeout(id as number),
setInterval: (fn: () => void, ms: number) => globalThis.setInterval(fn, ms),
clearInterval: (id: unknown) => globalThis.clearInterval(id as number),
}
const attempts: number[] = []
const loop = useTaskProgressLoop<{ taskId?: number; status?: string }>({
scope: 'cap-integration',
fetchProgress: async () => { throw new Error('net') },
extractTaskId: (d) => d?.taskId,
extractStatus: (d) => d?.status,
getIntervalMs: () => 60000,
onError: (_e, attempt) => attempts.push(attempt ?? -1),
})
loop.reset([1])
await new Promise<void>((r) => globalThis.setTimeout(r, 0))
for (let i = 0; i < 12; i++) {
await loop.refreshOnce()
}
const intervals = attempts.map((a) => getTaskPollBackoffMs(a))
assert.deepEqual(
intervals.slice(0, 7),
[500, 1000, 2000, 4000, 8000, 10000, 10000],
'失败计数映射到退避间隔,达到 maxMs 后保持',
)
const tail = intervals.slice(7)
assert.ok(tail.every((v) => v === 10000), '封顶后间隔恒等于 maxMs')
loop.dispose()
resetTaskPollingConfig()
})
test('test_cap_config_negative', () => {
resetTaskPollingConfig()
assert.throws(() => configureTaskPolling({ backoffMaxMs: 0 }), /退避上限必须为正数/)
assert.throws(() => configureTaskPolling({ backoffMaxMs: -5 }), /退避上限必须为正数/)
// 拒绝后退避仍按默认值工作,不产生非法间隔
assert.equal(getTaskPollBackoffMs(0), 500)
assert.equal(getTaskPollBackoffMs(50), 10000)
resetTaskPollingConfig()
})