cdss-backend / dataFusion /tests /test_fallback.py
shak3008's picture
Deploy CDSS Multi-Agent Biomedical System
82d7112
Raw History Blame Contribute Delete
5.37 kB
"""Dedicated unit tests for automatic rule-based fallback behavior under all LLM failure modes."""
from unittest.mock import MagicMock, patch
import httpx
import pytest
from app.config import settings
from app.fusion import fuse_clinical_data, preprocess_fusion_request
from app.schemas import FusionRequest
from tests.test_data import VALID_FUSION_REQUEST_DICT
def _run_fusion_with_mock_llm(mock_post):
request = FusionRequest.model_validate(VALID_FUSION_REQUEST_DICT)
preprocessed = preprocess_fusion_request(request)
context, conflicts, trace_origins, mode, stats, fallback_reason = fuse_clinical_data(preprocessed, correlation_id="test-fallback-suite")
return context, conflicts, trace_origins, mode, stats, fallback_reason
def test_fallback_on_api_timeout():
"""Verify that network timeout during LLM call falls back automatically to rule-based fusion."""
with patch.object(settings, "llm_api_key", "valid_key_123"), \
patch("httpx.Client.post", side_effect=httpx.TimeoutException("Connection timed out")):
context, conflicts, origins, mode, stats, reason = _run_fusion_with_mock_llm(None)
assert mode == "rule_based_fallback"
assert reason is not None
assert "Acute Myocardial Infarction" in context.normalized_medical_terms
assert len(conflicts) > 0
def test_fallback_on_network_error():
"""Verify that network failure during LLM call falls back automatically to rule-based fusion."""
with patch.object(settings, "llm_api_key", "valid_key_123"), \
patch("httpx.Client.post", side_effect=httpx.RequestError("DNS Resolution Error")):
context, conflicts, origins, mode, stats, reason = _run_fusion_with_mock_llm(None)
assert mode == "rule_based_fallback"
assert reason is not None
assert "Acute Myocardial Infarction" in context.normalized_medical_terms
def test_fallback_on_invalid_api_key():
"""Verify that unconfigured/invalid API key falls back automatically to rule-based fusion."""
with patch.object(settings, "llm_api_key", "mock_key_or_set_real_api_key"):
context, conflicts, origins, mode, stats, reason = _run_fusion_with_mock_llm(None)
assert mode == "rule_based_fallback"
assert reason is not None
assert "Acute Myocardial Infarction" in context.normalized_medical_terms
def test_fallback_on_rate_limit_429():
"""Verify that 429 rate limit response from LLM provider falls back automatically to rule-based fusion."""
mock_response = MagicMock()
mock_response.status_code = 429
with patch.object(settings, "llm_api_key", "valid_key_123"), \
patch("httpx.Client.post", return_value=mock_response):
context, conflicts, origins, mode, stats, reason = _run_fusion_with_mock_llm(None)
assert mode == "rule_based_fallback"
assert reason is not None
assert "Acute Myocardial Infarction" in context.normalized_medical_terms
def test_fallback_on_malformed_json():
"""Verify that malformed/invalid JSON returned by LLM falls back automatically to rule-based fusion."""
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {
"choices": [{"message": {"content": "This is raw text, not JSON"}}]
}
with patch.object(settings, "llm_api_key", "valid_key_123"), \
patch("httpx.Client.post", return_value=mock_response):
context, conflicts, origins, mode, stats, reason = _run_fusion_with_mock_llm(None)
assert mode == "rule_based_fallback"
assert reason is not None
assert "Acute Myocardial Infarction" in context.normalized_medical_terms
def test_fallback_on_empty_response():
"""Verify that empty response content from LLM falls back automatically to rule-based fusion."""
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {"choices": []}
with patch.object(settings, "llm_api_key", "valid_key_123"), \
patch("httpx.Client.post", return_value=mock_response):
context, conflicts, origins, mode, stats, reason = _run_fusion_with_mock_llm(None)
assert mode == "rule_based_fallback"
assert reason is not None
assert "Acute Myocardial Infarction" in context.normalized_medical_terms
def test_fallback_on_schema_validation_error():
"""Verify that LLM output failing Pydantic schema validation falls back automatically to rule-based fusion."""
invalid_schema_json = {
"unified_context": {
"invalid_field": ["Acute Myocardial Infarction"]
# Missing required fields like merged_findings, normalized_medical_terms, confidence_score, etc.
}
}
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {
"choices": [{"message": {"content": str(invalid_schema_json)}}]
}
with patch.object(settings, "llm_api_key", "valid_key_123"), \
patch("httpx.Client.post", return_value=mock_response):
context, conflicts, origins, mode, stats, reason = _run_fusion_with_mock_llm(None)
assert mode == "rule_based_fallback"
assert reason is not None
assert "Acute Myocardial Infarction" in context.normalized_medical_terms