task-47: 轮询 baseline 统计补强测试

This commit is contained in:
2026-08-31 22:47:47 +08:00
parent 969c5a2a67
commit 0cc7380205
3 changed files with 188 additions and 2 deletions
@@ -1,6 +1,10 @@
import { test } from 'node:test'
import assert from 'node:assert/strict'
import { createTaskPollingBaseline } from '../src/shared/task-polling-baseline.ts'
import {
createTaskPollingBaseline,
type TaskPollingBaseline,
} from '../src/shared/task-polling-baseline.ts'
import { useTaskProgressLoop } from '../src/shared/composables/useTaskProgressLoop.ts'
interface FakeDetail {
id: number
@@ -15,6 +19,43 @@ function detail(id: number): FakeDetail {
const byId = (d: FakeDetail) => d.id
const enc = (v: unknown) => new TextEncoder().encode(JSON.stringify(v)).byteLength
const tick = (ms = 0) => new Promise<void>((r) => globalThis.setTimeout(r, ms))
let scopeCounter = 0
type Item = { taskId?: number; status?: string }
interface BaselineLoopHarness {
loop: ReturnType<typeof useTaskProgressLoop<Item>>
}
function makeLoop(
options: Pick<Parameters<typeof useTaskProgressLoop<Item>>[0], 'onBaseline'>,
): BaselineLoopHarness {
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 loop = useTaskProgressLoop<Item>({
scope: `baseline-loop-${scopeCounter++}`,
fetchProgress: async (ids) => ({
items: ids.map((id) => ({ taskId: id, status: 'RUNNING' })),
}),
extractTaskId: (d) => d?.taskId,
extractStatus: (d) => d?.status,
getIntervalMs: () => 60000,
...options,
})
return { loop }
}
test('test_task_081_polling_frontend_normal_default_path', () => {
const baseline = createTaskPollingBaseline()
baseline.recordRequest()
@@ -154,8 +195,141 @@ test('test_task_081_polling_frontend_dependency_failure_releases_resources', ()
cachedBytes: 0,
droppedCount: 0,
entryKeys: [],
failureCount: 0,
})
// 错误可恢复:修复后同一实例继续工作
baseline.recordResponseItems([detail(1)], byId)
assert.equal(baseline.stats().entryCount, 1)
})
test('test_baseline_request_count', async () => {
const calls: TaskPollingBaseline[] = []
const { loop } = makeLoop({ onBaseline: (b) => { calls.push(b) } })
loop.reset([1])
await tick(10)
assert.ok(calls.length >= 1, '每轮请求后回传基线')
assert.equal(calls.at(-1)!.stats().requestCount, 1, '请求计数为 1reset 触发一轮)')
await loop.refreshOnce()
assert.equal(calls.at(-1)!.stats().requestCount, 2, '手动 refreshOnce 再记一次')
loop.dispose()
})
test('test_baseline_response_size', async () => {
const calls: TaskPollingBaseline[] = []
const { loop } = makeLoop({ onBaseline: (b) => { calls.push(b) } })
loop.reset([1, 2])
await tick(10)
const stats = calls.at(-1)!.stats()
loop.dispose()
assert.ok(stats.totalResponseBytes > 0, '响应体大小累计')
assert.equal(
stats.totalResponseBytes,
enc([
{ taskId: 1, status: 'RUNNING' },
{ taskId: 2, status: 'RUNNING' },
]),
'总字节 = 整轮响应体序列化字节数',
)
assert.equal(stats.largestResponseBytes, stats.totalResponseBytes, '单次最大 = 当前唯一一轮')
})
test('test_baseline_cache_usage', async () => {
const calls: TaskPollingBaseline[] = []
const { loop } = makeLoop({ onBaseline: (b) => { calls.push(b) } })
loop.reset([1, 2])
await tick(10)
const stats = calls.at(-1)!.stats()
loop.dispose()
assert.equal(stats.entryCount, 2, '每条进度一个缓存条目')
assert.deepEqual(stats.entryKeys, [1, 2])
assert.ok(stats.cachedBytes >= enc({ taskId: 1, status: 'RUNNING' }), '缓存字节含条目编码')
})
test('test_baseline_failure_count', async () => {
const calls: TaskPollingBaseline[] = []
let fails = 0
const loop = useTaskProgressLoop<Item>({
scope: `baseline-fail-${scopeCounter++}`,
fetchProgress: async () => {
if (fails < 2) {
fails += 1
throw new Error('network down')
}
return { items: [{ taskId: 1, status: 'RUNNING' }] }
},
extractTaskId: (d) => d?.taskId,
extractStatus: (d) => d?.status,
getIntervalMs: () => 60000,
onBaseline: (b) => { calls.push(b) },
})
loop.reset([1])
await tick(10)
const first = calls.at(-1)!.stats()
assert.equal(first.failureCount, 1, '首轮失败计数 1')
await loop.refreshOnce()
const second = calls.at(-1)!.stats()
assert.equal(second.failureCount, 2, '连续失败累计到 2')
await loop.refreshOnce()
const third = calls.at(-1)!.stats()
loop.dispose()
assert.equal(third.failureCount, 2, '失败计数为累计值(清零仅经 reset;连续计数走 onError attempt')
})
test('test_baseline_callback_after_round', async () => {
const calls: TaskPollingBaseline[] = []
const { loop } = makeLoop({ onBaseline: (b) => { calls.push(b) } })
loop.reset([1])
await tick(10)
await loop.refreshOnce()
assert.equal(calls.length, 2, '每轮 refreshOnce 结束后各回传一次基线')
assert.equal(calls[1].stats().requestCount, 2, '回传的是同一实例(累计统计)')
assert.strictEqual(calls[0], calls[1], '回传的是同一基线实例')
loop.dispose()
})
test('test_baseline_round_accuracy', async () => {
const calls: TaskPollingBaseline[] = []
const { loop } = makeLoop({ onBaseline: (b) => { calls.push(b) } })
loop.reset([1])
await tick(10)
// 重置后:请求量、条目数与请求一一对应,无跨轮串账
assert.equal(calls.at(-1)!.stats().requestCount, 1)
loop.add(2)
await tick(10)
const second = calls.at(-1)!.stats()
loop.dispose()
assert.equal(second.requestCount, 2, 'add 触发新一轮请求')
assert.equal(second.entryCount, 2, '第二轮响应写入新条目')
})
test('test_baseline_reset', async () => {
const calls: TaskPollingBaseline[] = []
const { loop } = makeLoop({ onBaseline: (b) => { calls.push(b) } })
loop.reset([1])
await tick(10)
const baseline = calls.at(-1)!
assert.equal(baseline.stats().requestCount, 1)
baseline.reset()
assert.deepEqual(baseline.stats(), {
requestCount: 0,
totalResponseBytes: 0,
largestResponseBytes: 0,
entryCount: 0,
cachedBytes: 0,
droppedCount: 0,
entryKeys: [],
failureCount: 0,
}, '重置后基线清零')
loop.dispose()
})
test('test_baseline_optional_off', async () => {
const { loop } = makeLoop({})
loop.reset([1])
await tick(10)
// 不传 onBaseline:不创建基线,不抛错
loop.refreshOnce()
await tick(10)
assert.equal(loop.taskStatuses.value[1], 'RUNNING', '无基线时轮询照常工作')
loop.dispose()
})