/** * 401 状态机与轮次结果合并的单测(同步引擎设计 §3.4 + 实施计划「实现假设」#15)。 */ import { describe, expect, test } from 'bun:test' import { combineRoundResult, MAX_CONSECUTIVE_401S, nextKeyCounter, type KeyCounterNext, } from './policy' const active = (consecutive401s = 0): KeyCounterNext => ({ consecutive401s, status: 'active', disabledSource: null, removed: false, }) describe('combineRoundResult(两个调用合并为「本轮结果」)', () => { test('任一 401 → unauthorized(401 优先于成功)', () => { expect(combineRoundResult(['ok', 'unauthorized'])).toBe('unauthorized') expect(combineRoundResult(['unauthorized', 'failed'])).toBe('unauthorized') }) test('无 401 且任一成功 → ok', () => { expect(combineRoundResult(['ok', 'failed'])).toBe('ok') expect(combineRoundResult(['ok', 'ok'])).toBe('ok') }) test('全失败 → failed(不动计数)', () => { expect(combineRoundResult(['failed', 'failed'])).toBe('failed') }) }) describe('nextKeyCounter(401 计数 / 摘除 / 清零)', () => { test('成功 → 计数清零(不摘除)', () => { const next = nextKeyCounter(active(2), 'ok') expect(next.consecutive401s).toBe(0) expect(next.status).toBe('active') expect(next.removed).toBe(false) }) test('其他失败(403/500/网络)→ 计数不动', () => { const next = nextKeyCounter(active(1), 'failed') expect(next.consecutive401s).toBe(1) expect(next.removed).toBe(false) }) test('连续 401 累加;未达阈值不摘除', () => { let state = active(0) for (let i = 1; i < MAX_CONSECUTIVE_401S; i++) { const next = nextKeyCounter(state, 'unauthorized') expect(next.consecutive401s).toBe(i) expect(next.removed).toBe(false) expect(next.status).toBe('active') state = next } }) test('第 N 次(N=3)连续 401 → 摘除且 disabled_source = auto_401', () => { let state: KeyCounterNext = active(0) for (let i = 1; i <= MAX_CONSECUTIVE_401S; i++) { state = nextKeyCounter(state, 'unauthorized') } expect(state.consecutive401s).toBe(MAX_CONSECUTIVE_401S) expect(state.status).toBe('disabled') expect(state.disabledSource).toBe('auto_401') expect(state.removed).toBe(true) }) test('中途成功会打断连续性(计数回零)', () => { let state: KeyCounterNext = active(2) state = nextKeyCounter(state, 'ok') state = nextKeyCounter(state, 'unauthorized') expect(state.consecutive401s).toBe(1) expect(state.removed).toBe(false) }) })