Files
Video-Tree-TRM5/tests/unit/test_breaker.py
T
iomgaa 14bb60e918 feat(adapters): 实现 CircuitBreaker 内存级熔断器
注入 now 纯确定性,force_open 支持 401/403 直接熔断。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-07-06 22:39:57 -04:00

70 lines
2.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""CircuitBreaker 单元测试。
验证内存级熔断器的核心行为:阈值触发、冷却半开、成功重置、强制熔断。
"""
from __future__ import annotations
import pytest
from adapters.breaker import CircuitBreaker
@pytest.fixture()
def breaker() -> CircuitBreaker:
"""创建 threshold=3, cooldown=10s 的熔断器。"""
return CircuitBreaker(fail_threshold=3, cooldown_s=10.0)
class TestCircuitBreaker:
"""CircuitBreaker 行为验证。"""
def test_closed_by_default(self, breaker: CircuitBreaker) -> None:
"""初始状态为关闭(放行)。"""
assert breaker.is_open("llm", now=0.0) is False
def test_opens_after_threshold_failures(self, breaker: CircuitBreaker) -> None:
"""连续失败未达阈值不开路;达到阈值后开路。"""
# 2 次失败 < threshold=3,仍关闭
breaker.record_failure("llm", now=1.0)
breaker.record_failure("llm", now=2.0)
assert breaker.is_open("llm", now=2.5) is False
# 第 3 次失败 >= threshold=3,开路
breaker.record_failure("llm", now=3.0)
assert breaker.is_open("llm", now=3.5) is True
def test_half_open_after_cooldown(self, breaker: CircuitBreaker) -> None:
"""冷却期过后自动半开(放行试探)。"""
for t in range(3):
breaker.record_failure("llm", now=float(t))
# 开路期间
assert breaker.is_open("llm", now=5.0) is True
# cooldown=10s,第 3 次失败 now=2.0open_until=12.0
# now=12.0 时冷却到期 → 半开
assert breaker.is_open("llm", now=12.0) is False
def test_success_resets(self, breaker: CircuitBreaker) -> None:
"""record_success 重置计数并关闭熔断器。"""
for t in range(3):
breaker.record_failure("llm", now=float(t))
assert breaker.is_open("llm", now=3.0) is True
breaker.record_success("llm")
assert breaker.is_open("llm", now=3.0) is False
def test_force_open_immediate(self, breaker: CircuitBreaker) -> None:
"""force_open 无需积累失败即可直接熔断。"""
breaker.force_open("llm", now=5.0)
assert breaker.is_open("llm", now=5.5) is True
def test_force_open_probe_failure_reopens(self, breaker: CircuitBreaker) -> None:
"""半开探针失败后重新熔断(因 fails 已置为 threshold)。"""
breaker.force_open("llm", now=0.0)
# cooldown=10s → open_until=10.0now=10.0 半开
assert breaker.is_open("llm", now=10.0) is False
# 半开探针失败:一次 record_failure 即达阈值(fails 已=3),重新熔断
breaker.record_failure("llm", now=10.0)
assert breaker.is_open("llm", now=10.5) is True