From 745e3fe2c4917351982adc20cc6517edd720e296 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E8=87=AA=E8=BE=BE?= <980324341@qq.com> Date: Mon, 31 Aug 2026 20:35:10 +0800 Subject: [PATCH] =?UTF-8?q?task-35:=20=E6=9C=AA=E7=9F=A5=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E4=B8=8D=E5=88=A4=E7=BB=88=E6=80=81=E5=9B=BA=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 8 个测试固化未知状态语义:PENDING/SYNCING/空串/undefined 均为 非终态(FSM 层走重试语义、循环层不触发 onTerminal、任务保留在 轮询集合);同批终态+未知分开处理;自定义 isTerminal 生效; 小写值不误判。 --- .../tests/polling-unknown-status.test.ts | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 frontend-vue/tests/polling-unknown-status.test.ts diff --git a/frontend-vue/tests/polling-unknown-status.test.ts b/frontend-vue/tests/polling-unknown-status.test.ts new file mode 100644 index 00000000..57584b6e --- /dev/null +++ b/frontend-vue/tests/polling-unknown-status.test.ts @@ -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() + ;(globalThis as Record).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((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() + ;(globalThis as Record).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((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, '') +})