Files
crawler-plugin/frontend-vue/tests/terminal-once.test.ts
T
huangzd1997 9e8ecb57e5 task-36: 终态任务仅一次 onTerminal
8 个测试固化 markTerminal 语义:同一任务终态只回调一次(幂等)、
终态任务从轮询集合移除、重复响应不回调、重新 add 后可再触发、
多任务各自回调、markTerminal 先于移除、终态后 localStorage 同步
清理、停止轮询不触发回调。
2026-08-31 20:36:18 +08:00

133 lines
5.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test } from 'node:test'
import assert from 'node:assert/strict'
import { createTaskPollingCoordinator } from '../src/shared/task-polling-coordinator.ts'
import { useTaskProgressLoop } from '../src/shared/composables/useTaskProgressLoop.ts'
test('test_terminal_called_once', () => {
const called: number[] = []
const coordinator = createTaskPollingCoordinator({ onTerminal: (id) => called.push(id) })
coordinator.add(5)
coordinator.markTerminal(5)
coordinator.markTerminal(5)
coordinator.markTerminal(5)
assert.deepEqual(called, [5], '同一任务终态只回调一次')
assert.equal(coordinator.stats().terminalCount, 1)
})
test('test_terminal_removed_from_poll', () => {
const coordinator = createTaskPollingCoordinator({})
coordinator.add(7)
coordinator.add(8)
coordinator.markTerminal(7)
assert.deepEqual(coordinator.ids(), [8], '终态任务从轮询集合移除')
})
test('test_terminal_repeat_response', async () => {
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 terminalCalls: number[] = []
const coordinator = createTaskPollingCoordinator({ onTerminal: (id) => terminalCalls.push(id) })
const loop = useTaskProgressLoop<{ taskId?: number; status?: string }>({
scope: 'repeat-terminal',
fetchProgress: async () => ({ items: [{ taskId: 9, status: 'SUCCESS' }] }),
extractTaskId: (d) => d?.taskId,
extractStatus: (d) => d?.status,
getIntervalMs: () => 60000,
onTerminal: (id) => { terminalCalls.push(id) },
coordinator,
})
loop.reset([9])
await new Promise<void>((r) => globalThis.setTimeout(r, 0))
await loop.refreshOnce()
const once = terminalCalls.filter((id) => id === 9).length
assert.equal(once, 1, '重复收到同任务终态不重复回调')
assert.equal(coordinator.has(9), false, '终态任务已从协调器移除')
loop.dispose()
})
test('test_terminal_after_readd', () => {
const called: number[] = []
const coordinator = createTaskPollingCoordinator({ onTerminal: (id) => called.push(id) })
coordinator.add(3)
coordinator.markTerminal(3)
coordinator.add(3)
assert.ok(coordinator.has(3), '重新 add 后任务回归集合')
coordinator.markTerminal(3)
assert.deepEqual(called, [3, 3], '重新 add 后可再次触发')
})
test('test_terminal_multiple_tasks', () => {
const called: number[] = []
const coordinator = createTaskPollingCoordinator({ onTerminal: (id) => called.push(id) })
coordinator.add(1)
coordinator.add(2)
coordinator.add(3)
coordinator.markTerminal(2)
coordinator.markTerminal(1)
coordinator.markTerminal(3)
assert.deepEqual(called, [2, 1, 3], '多任务各自回调一次')
assert.equal(coordinator.stats().terminalCount, 3)
assert.deepEqual(coordinator.ids(), [])
})
test('test_terminal_coordinator_order', () => {
const order: string[] = []
const coordinator = createTaskPollingCoordinator({
onTerminal: (id) => order.push(`coordinator:${id}`),
})
coordinator.add(4)
coordinator.markTerminal(4)
assert.deepEqual(order, ['coordinator:4'], 'markTerminal 先于移除(回调时任务已不在集合)')
assert.equal(coordinator.has(4), false)
})
test('test_terminal_storage_cleared', async () => {
const storage = new Map<string, string>()
storage.set('term:storage', JSON.stringify([11]))
;(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 coordinator = createTaskPollingCoordinator({})
const loop = useTaskProgressLoop<{ taskId?: number; status?: string }>({
scope: 'storage-clear',
storageKey: 'term:storage',
fetchProgress: async () => ({ items: [{ taskId: 11, status: 'SUCCESS' }] }),
extractTaskId: (d) => d?.taskId,
extractStatus: (d) => d?.status,
getIntervalMs: () => 60000,
onTerminal: () => {},
coordinator,
})
await new Promise<void>((r) => globalThis.setTimeout(r, 0))
await loop.refreshOnce()
assert.equal(storage.has('term:storage'), false, '终态后 localStorage 同步清理')
loop.dispose()
})
test('test_terminal_dispose_no_call', () => {
const called: number[] = []
const coordinator = createTaskPollingCoordinator({ onTerminal: (id) => called.push(id) })
coordinator.add(2)
// dispose 等价于停止轮询:此后不应再有任何终态回调(没有 markTerminal
assert.equal(called.length, 0)
assert.ok(coordinator.has(2), '任务仍在集合,仅停止轮询不清理')
})