task-32: 退避封顶
8 个测试固化退避封顶语义:达到 maxMs 后保持恒定(含低 maxMs、 高 factor 提前封顶)、成功后从基数重新起算、恰等于 maxMs 不溢出、 循环内失败计数映射退避间隔的集成、非法 maxMs 配置拒绝。 行为由任务 29/30 的 factor 与 maxMs 配置已实现,本次为验证型测试。
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
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()
|
||||
})
|
||||
Reference in New Issue
Block a user