133 lines
4.7 KiB
TypeScript
133 lines
4.7 KiB
TypeScript
import { test } from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { createPollingStateMachine } from '../src/shared/polling-state-machine.ts'
|
|
|
|
test('test_task_095_task_normal_default_path', () => {
|
|
const fsm = createPollingStateMachine({ maxAttempts: 3 })
|
|
assert.equal(fsm.status, 'idle')
|
|
fsm.start()
|
|
assert.equal(fsm.status, 'polling')
|
|
fsm.succeed('SUCCESS')
|
|
assert.equal(fsm.status, 'done')
|
|
assert.equal(fsm.terminalStatus, 'SUCCESS')
|
|
assert.equal(fsm.attempts, 1)
|
|
assert.equal(fsm.errorCount, 0)
|
|
assert.deepEqual(fsm.retries(), [])
|
|
// 终态刷新:刷新计数
|
|
fsm.markRefreshed()
|
|
assert.equal(fsm.refreshed, true)
|
|
assert.equal(fsm.refreshCount, 1)
|
|
})
|
|
|
|
test('test_task_095_task_normal_multiple_items', () => {
|
|
// 多任务独立状态机:互不串扰
|
|
const fsmA = createPollingStateMachine({ maxAttempts: 3 })
|
|
const fsmB = createPollingStateMachine({ maxAttempts: 3 })
|
|
fsmA.start()
|
|
fsmB.start()
|
|
fsmA.fail('Network Error')
|
|
fsmB.succeed('SUCCESS')
|
|
assert.equal(fsmA.status, 'retrying')
|
|
assert.equal(fsmB.status, 'done')
|
|
assert.equal(fsmA.errorCount, 1)
|
|
assert.equal(fsmB.errorCount, 0)
|
|
assert.equal(fsmB.terminalStatus, 'SUCCESS')
|
|
assert.deepEqual(fsmA.retries(), [1])
|
|
})
|
|
|
|
test('test_task_095_task_normal_repeated_operation_is_idempotent', () => {
|
|
const fsm = createPollingStateMachine({ maxAttempts: 5 })
|
|
fsm.start()
|
|
fsm.fail('e1')
|
|
fsm.fail('e2')
|
|
fsm.succeed('SUCCESS')
|
|
// 重复进入终态幂等:不重复计数、不覆盖状态
|
|
fsm.succeed('SUCCESS')
|
|
fsm.succeed('SUCCESS')
|
|
assert.equal(fsm.status, 'done')
|
|
assert.equal(fsm.attempts, 3)
|
|
assert.equal(fsm.errorCount, 2)
|
|
// 重复刷新幂等:refreshCount 只计一次
|
|
fsm.markRefreshed()
|
|
fsm.markRefreshed()
|
|
fsm.markRefreshed()
|
|
assert.equal(fsm.refreshed, true)
|
|
assert.equal(fsm.refreshCount, 1)
|
|
// 终态后 start 无效
|
|
fsm.start()
|
|
assert.equal(fsm.status, 'done')
|
|
})
|
|
|
|
test('test_task_095_task_boundary_empty_input', () => {
|
|
const fsm = createPollingStateMachine({ maxAttempts: 3 })
|
|
assert.equal(fsm.status, 'idle')
|
|
assert.equal(fsm.attempts, 0)
|
|
assert.equal(fsm.errorCount, 0)
|
|
assert.equal(fsm.retries().length, 0)
|
|
assert.equal(fsm.refreshed, false)
|
|
// idle 状态不触发任何刷新
|
|
fsm.markRefreshed()
|
|
assert.equal(fsm.refreshCount, 0)
|
|
})
|
|
|
|
test('test_task_095_task_boundary_single_item', () => {
|
|
const fsm = createPollingStateMachine({ maxAttempts: 1 })
|
|
fsm.start()
|
|
fsm.succeed('FAILED')
|
|
assert.equal(fsm.status, 'done')
|
|
assert.equal(fsm.terminalStatus, 'FAILED', '终态单次成功')
|
|
assert.equal(fsm.attempts, 1)
|
|
})
|
|
|
|
test('test_task_095_task_boundary_limit_and_overflow', () => {
|
|
// 重试有界:超过 maxAttempts 后失败进入 failed,不再重试
|
|
const fsm = createPollingStateMachine({ maxAttempts: 2 })
|
|
fsm.start()
|
|
fsm.fail('e1')
|
|
assert.equal(fsm.status, 'retrying')
|
|
assert.equal(fsm.retrying, true)
|
|
fsm.fail('e2')
|
|
assert.equal(fsm.status, 'failed', '达到上限后失败不再重试')
|
|
assert.equal(fsm.retrying, false)
|
|
assert.deepEqual(fsm.retries(), [1], '仅成功重试过 1 次,第二次失败直接进入 failed')
|
|
// 失败状态下再次 fail:不越界
|
|
fsm.fail('e3')
|
|
assert.equal(fsm.attempts, 2)
|
|
assert.equal(fsm.errorCount, 2)
|
|
})
|
|
|
|
test('test_task_095_task_invalid_input_rejected', () => {
|
|
assert.throws(() => createPollingStateMachine({} as never), /maxAttempts 必须为正数/)
|
|
assert.throws(() => createPollingStateMachine({ maxAttempts: 0 }), /maxAttempts 必须为正数/)
|
|
assert.throws(() => createPollingStateMachine({ maxAttempts: -1 }), /maxAttempts 必须为正数/)
|
|
assert.throws(() => createPollingStateMachine({ maxAttempts: 1.5 }), /maxAttempts 必须为整数/)
|
|
assert.throws(
|
|
() => createPollingStateMachine({ maxAttempts: 2, isTerminal: 'x' as never }),
|
|
/isTerminal 必须是函数/,
|
|
)
|
|
// 未 start 时 fail/succeed 拒绝
|
|
const fsm = createPollingStateMachine({ maxAttempts: 3 })
|
|
assert.throws(() => fsm.fail('e'), /未开始轮询/)
|
|
assert.throws(() => fsm.succeed('SUCCESS'), /未开始轮询/)
|
|
})
|
|
|
|
test('test_task_095_task_dependency_failure_releases_resources', () => {
|
|
// isTerminal 依赖抛错:fail 调用失败但状态不污染,恢复后可用
|
|
let broken = true
|
|
const fsm = createPollingStateMachine({
|
|
maxAttempts: 3,
|
|
isTerminal: (status: string) => {
|
|
if (broken) throw new Error('isTerminal down')
|
|
return status === 'SUCCESS'
|
|
},
|
|
})
|
|
fsm.start()
|
|
assert.throws(() => fsm.succeed('SUCCESS'), /isTerminal down/)
|
|
assert.equal(fsm.status, 'polling', '失败不产生状态变更')
|
|
broken = false
|
|
fsm.succeed('SUCCESS')
|
|
assert.equal(fsm.status, 'done')
|
|
assert.equal(fsm.terminalStatus, 'SUCCESS')
|
|
assert.equal(fsm.attempts, 1)
|
|
})
|