|
|
"""Unit tests for HFInferenceJudgeHandler Circuit Breaker.""" |
|
|
|
|
|
from unittest.mock import MagicMock, patch |
|
|
|
|
|
import pytest |
|
|
|
|
|
from src.agent_factory.judges import HFInferenceJudgeHandler |
|
|
from src.utils.models import Citation, Evidence |
|
|
|
|
|
|
|
|
@pytest.mark.unit |
|
|
class TestJudgeCircuitBreaker: |
|
|
"""Tests specifically for the circuit breaker logic.""" |
|
|
|
|
|
@pytest.fixture |
|
|
def handler(self): |
|
|
"""Create a handler with mocked dependencies.""" |
|
|
with patch("src.agent_factory.judges.InferenceClient"): |
|
|
return HFInferenceJudgeHandler() |
|
|
|
|
|
@pytest.mark.asyncio |
|
|
async def test_circuit_breaker_triggers_after_max_failures(self, handler): |
|
|
"""Verify it switches to 'synthesize' after 3 consecutive failures.""" |
|
|
|
|
|
|
|
|
with patch.object(handler, "_call_with_retry", side_effect=Exception("Model failed")): |
|
|
evidence = [ |
|
|
Evidence( |
|
|
content="test", |
|
|
citation=Citation(source="pubmed", title="t", url="u", date="2025"), |
|
|
) |
|
|
] |
|
|
|
|
|
|
|
|
result1 = await handler.assess("test", evidence) |
|
|
assert result1.recommendation == "continue" |
|
|
assert handler.consecutive_failures == 1 |
|
|
|
|
|
|
|
|
result2 = await handler.assess("test", evidence) |
|
|
assert result2.recommendation == "continue" |
|
|
assert handler.consecutive_failures == 2 |
|
|
|
|
|
|
|
|
result3 = await handler.assess("test", evidence) |
|
|
assert result3.recommendation == "continue" |
|
|
assert handler.consecutive_failures == 3 |
|
|
|
|
|
|
|
|
|
|
|
result4 = await handler.assess("test", evidence) |
|
|
|
|
|
assert result4.recommendation == "synthesize" |
|
|
assert result4.sufficient is True |
|
|
|
|
|
reasoning_lower = result4.reasoning.lower() |
|
|
assert "failed" in reasoning_lower or "unavailable" in reasoning_lower |
|
|
|
|
|
@pytest.mark.asyncio |
|
|
async def test_circuit_breaker_resets_on_success(self, handler): |
|
|
"""Verify failures reset if a call succeeds.""" |
|
|
|
|
|
evidence = [ |
|
|
Evidence( |
|
|
content="t", |
|
|
citation=Citation(source="pubmed", title="t", url="u", date="d"), |
|
|
) |
|
|
] |
|
|
|
|
|
|
|
|
with patch.object(handler, "_call_with_retry", side_effect=Exception("Fail")): |
|
|
await handler.assess("test", evidence) |
|
|
assert handler.consecutive_failures == 1 |
|
|
|
|
|
|
|
|
valid_assessment = MagicMock(recommendation="continue", sufficient=False) |
|
|
with patch.object(handler, "_call_with_retry", return_value=valid_assessment): |
|
|
await handler.assess("test", evidence) |
|
|
assert handler.consecutive_failures == 0 |
|
|
|
|
|
@pytest.mark.asyncio |
|
|
async def test_circuit_breaker_resets_on_new_question(self, handler): |
|
|
"""Verify failures reset if question changes.""" |
|
|
|
|
|
evidence = [] |
|
|
|
|
|
|
|
|
with patch.object(handler, "_call_with_retry", side_effect=Exception("Fail")): |
|
|
await handler.assess("Question A", evidence) |
|
|
assert handler.consecutive_failures == 1 |
|
|
|
|
|
|
|
|
await handler.assess("Question B", evidence) |
|
|
|
|
|
|
|
|
|
|
|
assert handler.consecutive_failures == 1 |
|
|
|