task-35: 未知状态不判终态固化
8 个测试固化未知状态语义:PENDING/SYNCING/空串/undefined 均为 非终态(FSM 层走重试语义、循环层不触发 onTerminal、任务保留在 轮询集合);同批终态+未知分开处理;自定义 isTerminal 生效; 小写值不误判。
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
import { test } from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { createPollingStateMachine } from '../src/shared/polling-state-machine.ts'
|
||||
import { useTaskProgressLoop } from '../src/shared/composables/useTaskProgressLoop.ts'
|
||||
|
||||
test('test_pending_not_terminal', () => {
|
||||
const fsm = createPollingStateMachine({ maxAttempts: 3 })
|
||||
fsm.start()
|
||||
fsm.succeed('PENDING')
|
||||
assert.equal(fsm.status, 'retrying', 'PENDING 不判终态,沿用重试语义')
|
||||
assert.equal(fsm.terminalStatus, '')
|
||||
})
|
||||
|
||||
test('test_syncing_not_terminal', () => {
|
||||
const fsm = createPollingStateMachine({ maxAttempts: 3 })
|
||||
fsm.start()
|
||||
fsm.succeed('SYNCING')
|
||||
assert.equal(fsm.status, 'retrying', 'SYNCING 不判终态')
|
||||
assert.equal(fsm.terminalStatus, '')
|
||||
})
|
||||
|
||||
test('test_empty_status_not_terminal', () => {
|
||||
const fsm = createPollingStateMachine({ maxAttempts: 3 })
|
||||
fsm.start()
|
||||
fsm.succeed('')
|
||||
assert.equal(fsm.status, 'retrying', '空串不判终态')
|
||||
assert.equal(fsm.terminalStatus, '')
|
||||
})
|
||||
|
||||
test('test_undefined_status_not_terminal', () => {
|
||||
const fsm = createPollingStateMachine({ maxAttempts: 3 })
|
||||
fsm.start()
|
||||
fsm.succeed(undefined as never)
|
||||
assert.equal(fsm.status, 'retrying', 'undefined 不判终态')
|
||||
assert.equal(fsm.terminalStatus, '')
|
||||
})
|
||||
|
||||
test('test_unknown_status_continues_polling', 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 loop = useTaskProgressLoop<{ taskId?: number; status?: string }>({
|
||||
scope: 'unknown-status',
|
||||
fetchProgress: async () => ({ items: [{ taskId: 1, status: 'MYSTERY' }] }),
|
||||
extractTaskId: (d) => d?.taskId,
|
||||
extractStatus: (d) => d?.status,
|
||||
getIntervalMs: () => 60000,
|
||||
onTerminal: (taskId) => { terminalCalls.push(taskId) },
|
||||
})
|
||||
loop.reset([1])
|
||||
await new Promise<void>((r) => globalThis.setTimeout(r, 0))
|
||||
await loop.refreshOnce()
|
||||
assert.deepEqual(loop.taskIds.value, [1], '未知状态任务仍在轮询集合')
|
||||
assert.equal(terminalCalls.length, 0, '未知状态不触发终态回调')
|
||||
loop.dispose()
|
||||
})
|
||||
|
||||
test('test_mixed_terminal_unknown', 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 all = [
|
||||
{ taskId: 1, status: 'SUCCESS' },
|
||||
{ taskId: 2, status: 'QUEUED' },
|
||||
{ taskId: 3, status: 'FAILED' },
|
||||
]
|
||||
const loop = useTaskProgressLoop<{ taskId?: number; status?: string }>({
|
||||
scope: 'mixed-status',
|
||||
fetchProgress: async (ids) => ({
|
||||
items: all.filter((item) => ids.includes(item.taskId ?? -1)),
|
||||
}),
|
||||
extractTaskId: (d) => d?.taskId,
|
||||
extractStatus: (d) => d?.status,
|
||||
getIntervalMs: () => 60000,
|
||||
onTerminal: (taskId) => { terminalCalls.push(taskId) },
|
||||
})
|
||||
loop.reset([1, 2, 3])
|
||||
await new Promise<void>((r) => globalThis.setTimeout(r, 0))
|
||||
await loop.refreshOnce()
|
||||
assert.deepEqual(terminalCalls, [1, 3], '同批终态+未知分开处理')
|
||||
assert.deepEqual(loop.taskIds.value, [2], '未知状态任务保留')
|
||||
loop.dispose()
|
||||
})
|
||||
|
||||
test('test_custom_is_terminal', () => {
|
||||
const fsm = createPollingStateMachine({
|
||||
maxAttempts: 3,
|
||||
isTerminal: (status) => status === 'COMPLETED',
|
||||
})
|
||||
fsm.start()
|
||||
fsm.succeed('SUCCESS')
|
||||
assert.equal(fsm.status, 'retrying', '自定义判定下 SUCCESS 非终态')
|
||||
fsm.succeed('COMPLETED')
|
||||
assert.equal(fsm.status, 'done', '自定义判定生效')
|
||||
assert.equal(fsm.terminalStatus, 'COMPLETED')
|
||||
})
|
||||
|
||||
test('test_lowercase_unknown', () => {
|
||||
const fsm = createPollingStateMachine({ maxAttempts: 3 })
|
||||
fsm.start()
|
||||
fsm.succeed('success')
|
||||
assert.equal(fsm.status, 'retrying', '小写 success 不误判终态')
|
||||
assert.equal(fsm.terminalStatus, '')
|
||||
})
|
||||
Reference in New Issue
Block a user