import { test } from 'node:test' import assert from 'node:assert/strict' import { TASK_POLL_VISIBLE_INTERVAL_MS, TASK_POLL_HIDDEN_INTERVAL_MS, TASK_POLL_BACKOFF_BASE_MS, TASK_POLL_BACKOFF_MAX_MS, getTaskPollIntervalMs, getTaskProgressCacheTtlMs, getTaskForegroundRefreshEnabled, getTaskForegroundRefreshDelayMs, getTaskPollBackoffMs, configureTaskPolling, resetTaskPollingConfig, } from '../src/shared/task-progress-config.ts' function withVisibility(visible: boolean, fn: () => void) { const original = (globalThis as Record).document const fakeDocument = { visibilityState: visible ? 'visible' : 'hidden', hidden: !visible } Object.defineProperty(globalThis, 'document', { value: fakeDocument, configurable: true, writable: true, }) try { fn() } finally { Object.defineProperty(globalThis, 'document', { value: original, configurable: true, writable: true, }) } } test('test_task_086_polling_normal_default_path', () => { assert.equal(getTaskPollBackoffMs(0), TASK_POLL_BACKOFF_BASE_MS) assert.equal(getTaskPollBackoffMs(1), 1000) assert.equal(getTaskForegroundRefreshEnabled(), true) assert.equal(getTaskForegroundRefreshDelayMs(), 0) assert.equal(getTaskPollIntervalMs(), TASK_POLL_VISIBLE_INTERVAL_MS) assert.equal(getTaskProgressCacheTtlMs(), 5000) // hidden 分支:可见性变化切换间隔 withVisibility(false, () => { assert.equal(getTaskPollIntervalMs(), TASK_POLL_HIDDEN_INTERVAL_MS) assert.equal(getTaskProgressCacheTtlMs(), 30000) }) withVisibility(true, () => { assert.equal(getTaskPollIntervalMs(), TASK_POLL_VISIBLE_INTERVAL_MS) assert.equal(getTaskProgressCacheTtlMs(), 5000) }) }) test('test_task_086_polling_normal_multiple_items', () => { resetTaskPollingConfig() configureTaskPolling({ backoffBaseMs: 1000, backoffMaxMs: 10_000 }) assert.deepEqual([0, 1, 2, 3].map((n) => getTaskPollBackoffMs(n)), [1000, 2000, 4000, 8000]) configureTaskPolling({ backoffBaseMs: 200, backoffMaxMs: 1600 }) assert.deepEqual([0, 1, 2, 3, 4].map((n) => getTaskPollBackoffMs(n)), [200, 400, 800, 1600, 1600]) configureTaskPolling({ foregroundRefreshEnabled: false }) assert.equal(getTaskForegroundRefreshEnabled(), false) configureTaskPolling({ foregroundRefreshDelayMs: 250 }) assert.equal(getTaskForegroundRefreshDelayMs(), 250) assert.equal(getTaskForegroundRefreshEnabled(), false, '未覆盖的字段保持原值') assert.equal(getTaskPollBackoffMs(0), 200, '未覆盖的字段保持原值') resetTaskPollingConfig() }) test('test_task_086_polling_normal_repeated_operation_is_idempotent', () => { resetTaskPollingConfig() configureTaskPolling({ backoffBaseMs: 300, backoffMaxMs: 2400 }) configureTaskPolling({ backoffBaseMs: 300, backoffMaxMs: 2400 }) configureTaskPolling({ backoffBaseMs: 300, backoffMaxMs: 2400 }) assert.deepEqual([0, 1, 2, 3].map((n) => getTaskPollBackoffMs(n)), [300, 600, 1200, 2400]) configureTaskPolling({ foregroundRefreshEnabled: true }) configureTaskPolling({ foregroundRefreshEnabled: true }) assert.equal(getTaskForegroundRefreshEnabled(), true) resetTaskPollingConfig() }) test('test_task_086_polling_boundary_empty_input', () => { resetTaskPollingConfig() configureTaskPolling({}) assert.equal(getTaskPollBackoffMs(0), TASK_POLL_BACKOFF_BASE_MS) assert.equal(getTaskForegroundRefreshEnabled(), true) assert.equal(getTaskForegroundRefreshDelayMs(), 0) resetTaskPollingConfig() }) test('test_task_086_polling_boundary_single_item', () => { resetTaskPollingConfig() configureTaskPolling({ backoffMaxMs: 999 }) assert.equal(getTaskPollBackoffMs(0), TASK_POLL_BACKOFF_BASE_MS, '未配置字段不受影响') assert.equal(getTaskPollBackoffMs(5), 999, '超过上限封顶') configureTaskPolling({ foregroundRefreshEnabled: false }) assert.equal(getTaskForegroundRefreshEnabled(), false) assert.equal(getTaskPollBackoffMs(5), 999, '配置之间互不影响') resetTaskPollingConfig() }) test('test_task_086_polling_boundary_limit_and_overflow', () => { resetTaskPollingConfig() // attempt 超过最大重试次数后封顶,不发生无界增长 assert.equal(getTaskPollBackoffMs(0), 500) assert.equal(getTaskPollBackoffMs(1), 1000) assert.equal(getTaskPollBackoffMs(2), 2000) assert.equal(getTaskPollBackoffMs(3), 4000) assert.equal(getTaskPollBackoffMs(4), 5000) assert.equal(getTaskPollBackoffMs(10), 5000) assert.equal(getTaskPollBackoffMs(100), 5000) // attempt 为 0 或负数按首次退避处理 assert.equal(getTaskPollBackoffMs(-1), 500) assert.equal(getTaskPollBackoffMs(NaN), 500) assert.equal(getTaskPollBackoffMs(0.5), 500) resetTaskPollingConfig() }) test('test_task_086_polling_invalid_input_rejected', () => { resetTaskPollingConfig() assert.throws(() => configureTaskPolling({ backoffBaseMs: 0 }), /退避基数必须为正数/) assert.throws(() => configureTaskPolling({ backoffBaseMs: -1 }), /退避基数必须为正数/) assert.throws(() => configureTaskPolling({ backoffMaxMs: 0 }), /退避上限必须为正数/) assert.throws(() => configureTaskPolling({ backoffMaxMs: -100 }), /退避上限必须为正数/) assert.throws(() => configureTaskPolling({ foregroundRefreshDelayMs: -1 }), /恢复延迟不能为负数/) // 拒绝后配置保持原值(零状态变更) assert.equal(getTaskPollBackoffMs(0), TASK_POLL_BACKOFF_BASE_MS) assert.equal(getTaskForegroundRefreshEnabled(), true) resetTaskPollingConfig() }) test('test_task_086_polling_dependency_failure_releases_resources', () => { resetTaskPollingConfig() // document 不可用(Node 环境/SSR):按可见间隔降级,不抛错 const original = (globalThis as Record).document Object.defineProperty(globalThis, 'document', { value: undefined, configurable: true, writable: true }) try { assert.equal(getTaskPollIntervalMs(), TASK_POLL_VISIBLE_INTERVAL_MS) assert.equal(getTaskProgressCacheTtlMs(), 5000) } finally { Object.defineProperty(globalThis, 'document', { value: original, configurable: true, writable: true }) } // 配置校验失败后错误可恢复:修正后同一配置系统继续工作 assert.throws(() => configureTaskPolling({ backoffBaseMs: -5 }), /退避基数必须为正数/) configureTaskPolling({ backoffBaseMs: 700, backoffMaxMs: 2800 }) assert.equal(getTaskPollBackoffMs(0), 700) assert.equal(getTaskPollBackoffMs(2), 2800) resetTaskPollingConfig() })