import time from fam_edge.model_adapters.circuit_breaker import CircuitBreaker def test_closed_stays_closed_below_threshold(): cb = CircuitBreaker(threshold=3, cooldown=1) cb.record_failure() cb.record_failure() assert not cb.is_open() assert cb.state == 'CLOSED' def test_opens_at_threshold(): cb = CircuitBreaker(threshold=3, cooldown=1) for _ in range(3): cb.record_failure() assert cb.is_open() assert cb.state == 'OPEN' def test_half_open_after_cooldown_and_clears_stale_failures(): """进入 HALF_OPEN 时应清空旧失败计数,探测调用失败一次不该立刻把它"凑数"重新 OPEN。""" cb = CircuitBreaker(threshold=3, cooldown=0.05) for _ in range(3): cb.record_failure() assert cb.is_open() time.sleep(0.1) # 冷却期已过,第一次 is_open() 调用把状态转为 HALF_OPEN 并放行一次探测 assert cb.is_open() is False assert cb.state == 'HALF_OPEN' assert len(cb.failures) == 0 # 探测失败一次:由于 deque 已清空,不应该只凭这一次失败就再次 OPEN # (state 仍是 HALF_OPEN,不是 OPEN,is_open() 只在 state=='OPEN' 时为 True) cb.record_failure() assert cb.state != 'OPEN' assert not cb.is_open() def test_half_open_probe_success_closes(): cb = CircuitBreaker(threshold=2, cooldown=0.05) cb.record_failure() cb.record_failure() time.sleep(0.1) assert cb.is_open() is False # 触发 HALF_OPEN 转换 cb.record_success() assert cb.state == 'CLOSED' assert len(cb.failures) == 0 def test_disabled_never_opens(): cb = CircuitBreaker(threshold=1, cooldown=1, enabled=False) cb.record_failure() cb.record_failure() assert not cb.is_open()