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() ;(globalThis as Record).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((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() })