task-43: progressCache TTL 惰性清理边界测试

命中返回、过期读取即清理、超上限淘汰最旧、空缓存 get undefined、
同 key 覆盖并刷新 TTL、TTL 边界立即过期、惰性清理不主动扫描、
轮询周期内缓存可用。共 8 个测试。
This commit is contained in:
2026-08-31 21:07:43 +08:00
parent 9af7a5170a
commit 6bf87aff13
@@ -207,3 +207,116 @@ test('test_task_082_progress_cleanup_dependency_failure_releases_resources', ()
cache.clear()
assert.equal(cache.size, 0)
})
test('test_cache_get_hit', () => {
const c = clock()
const cache = createProgressResponseCache<FakeDetail>({ ttlMs: 5000, maxEntries: 10, now: c.now })
cache.set(1, detail(1))
assert.deepEqual(cache.get(1), detail(1), 'TTL 内命中返回原值')
assert.equal(cache.has(1), true)
})
test('test_cache_get_expired_cleared', () => {
const c = clock()
const cache = createProgressResponseCache<FakeDetail>({ ttlMs: 5000, maxEntries: 10, now: c.now })
cache.set(1, detail(1))
c.tick(4999)
assert.deepEqual(cache.get(1), detail(1), '恰好未过期仍命中')
c.tick(2)
assert.equal(cache.get(1), undefined, '过期后读取返回 undefined 并清理')
assert.equal(cache.size, 0, '过期条目已从缓存移除')
})
test('test_cache_max_entries_evict', () => {
const c = clock()
const cache = createProgressResponseCache<FakeDetail>({ ttlMs: 60000, maxEntries: 2, now: c.now })
cache.set(1, detail(1))
cache.set(2, detail(2))
cache.set(3, detail(3))
assert.equal(cache.size, 2)
assert.equal(cache.get(1), undefined, '溢出淘汰最旧条目')
assert.equal(cache.get(3)?.id, 3, '最新条目保留')
})
test('test_cache_empty_get_undefined', () => {
const c = clock()
const cache = createProgressResponseCache<FakeDetail>({ ttlMs: 1000, maxEntries: 5, now: c.now })
assert.equal(cache.get(1), undefined, '空缓存 get 返回 undefined')
assert.equal(cache.has(1), false)
assert.deepEqual(cache.entries(), [])
})
test('test_cache_set_overwrites', () => {
const c = clock()
const cache = createProgressResponseCache<FakeDetail>({ ttlMs: 60000, maxEntries: 5, now: c.now })
cache.set(1, detail(1))
c.tick(100)
cache.set(1, { ...detail(1), status: 'SUCCESS' })
assert.equal(cache.get(1)?.status, 'SUCCESS', '同 key 覆盖新值')
assert.equal(cache.size, 1)
// 覆盖刷新 TTL:覆盖后再过一半 TTL 仍命中
c.tick(30000)
assert.deepEqual(cache.get(1), { ...detail(1), status: 'SUCCESS' }, '覆盖后 TTL 重新计时')
})
test('test_cache_ttl_zero', () => {
const c = clock()
const cache = createProgressResponseCache<FakeDetail>({ ttlMs: 1, maxEntries: 5, now: c.now })
cache.set(1, detail(1))
c.tick(1)
assert.equal(cache.get(1), undefined, 'TTL=1ms 过 1ms 即过期')
assert.equal(cache.size, 0)
})
test('test_cache_lazy_cleanup', () => {
const c = clock()
const cache = createProgressResponseCache<FakeDetail>({ ttlMs: 1000, maxEntries: 5, now: c.now })
cache.set(1, detail(1))
cache.set(2, detail(2))
c.tick(2000)
// 未读取前缓存条目仍在(惰性:无后台定时清理)
assert.equal(cache.size, 2)
// 读取触发惰性清理:读 1 只清 1,读 2 再清 2
assert.equal(cache.get(1), undefined)
assert.equal(cache.size, 1)
assert.equal(cache.get(2), undefined)
assert.equal(cache.size, 0)
})
test('test_cache_after_polling_cycle', async () => {
const c = clock()
const cache = createProgressResponseCache<{ taskId?: number; status?: string }>({
ttlMs: 5000,
maxEntries: 10,
now: c.now,
})
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 { useTaskProgressLoop } = await import('../src/shared/composables/useTaskProgressLoop.ts')
const loop = useTaskProgressLoop<{ taskId?: number; status?: string }>({
scope: 'cache-cycle',
fetchProgress: async (ids) => ({
items: ids.map((id) => ({ taskId: id, status: 'RUNNING' })),
}),
extractTaskId: (d) => d?.taskId,
extractStatus: (d) => d?.status,
getIntervalMs: () => 60000,
progressCache: cache,
})
loop.reset([1])
await new Promise<void>((r) => globalThis.setTimeout(r, 10))
assert.equal(cache.get(1)?.status, 'RUNNING', '轮询周期内缓存可用')
c.tick(6000)
assert.equal(cache.get(1), undefined, '超 TTL 后读取清理')
loop.dispose()
})