Files
aivanov_CIM/tests/test_pipeline_error_handling.py
2026-03-05 01:20:14 +01:00

364 lines
10 KiB
Python

"""
Tests pour la gestion des erreurs du Pipeline.
Ce module teste le retry, le timeout et le mode résultats partiels.
"""
import pytest
from datetime import datetime, timedelta
from unittest.mock import Mock, patch
from pipeline_mco_pmsi.models.clinical import ClinicalDocument
from pipeline_mco_pmsi.models.metadata import StayMetadata
from pipeline_mco_pmsi.pipeline import Pipeline
def test_pipeline_retry_on_failure(db_session, rag_engine):
"""
Test le retry avec exponential backoff en cas d'échec.
Exigences: 14.1, 14.6
"""
from pipeline_mco_pmsi.database.models import StayDB
pipeline = Pipeline(
db_session=db_session,
rag_engine=rag_engine,
max_retries=3,
retry_delay=0.1, # Court délai pour les tests
)
# Créer le séjour dans la base de données
stay = StayDB(
stay_id="stay_retry_001",
admission_date=datetime.now() - timedelta(days=1),
discharge_date=datetime.now(),
specialty="gastro-enterologie",
unit="medecine",
age=45,
sex="M",
)
db_session.add(stay)
db_session.commit()
documents = [
ClinicalDocument(
document_id="doc_retry_001",
document_type="cr_medical",
content="Diagnostic: Gastrite aiguë.",
creation_date=datetime.now(),
author="Dr. Test",
priority=2,
)
]
stay_metadata = StayMetadata(
stay_id="stay_retry_001",
admission_date=datetime.now() - timedelta(days=1),
discharge_date=datetime.now(),
specialty="gastro-enterologie",
unit="medecine",
age=45,
sex="M",
)
# Mock pour simuler des échecs puis succès
call_count = 0
original_extract = pipeline.clinical_facts_extractor.extract_facts
def mock_extract_with_retry(structured_stay):
nonlocal call_count
call_count += 1
if call_count < 2: # Échoue la première fois
raise RuntimeError("Erreur temporaire")
return original_extract(structured_stay)
with patch.object(
pipeline.clinical_facts_extractor,
"extract_facts",
side_effect=mock_extract_with_retry,
):
result = pipeline.process_stay(documents, stay_metadata)
# Vérifications
assert call_count == 2 # 1 échec + 1 succès
assert result.success is True
def test_pipeline_timeout_with_partial_results(db_session, rag_engine):
"""
Test le timeout avec mode résultats partiels activé.
Exigences: 14.6
"""
from pipeline_mco_pmsi.database.models import StayDB
pipeline = Pipeline(
db_session=db_session,
rag_engine=rag_engine,
timeout=0.01, # Timeout très court pour forcer l'erreur
partial_results_mode=True,
)
# Créer le séjour dans la base de données
stay = StayDB(
stay_id="stay_timeout_001",
admission_date=datetime.now() - timedelta(days=1),
discharge_date=datetime.now(),
specialty="gastro-enterologie",
unit="medecine",
age=45,
sex="M",
)
db_session.add(stay)
db_session.commit()
documents = [
ClinicalDocument(
document_id="doc_timeout_001",
document_type="cr_medical",
content="Diagnostic: Gastrite aiguë.",
creation_date=datetime.now(),
author="Dr. Test",
priority=2,
)
]
stay_metadata = StayMetadata(
stay_id="stay_timeout_001",
admission_date=datetime.now() - timedelta(days=1),
discharge_date=datetime.now(),
specialty="gastro-enterologie",
unit="medecine",
age=45,
sex="M",
)
# Mock pour ralentir le traitement
import time
def slow_extract(structured_stay):
time.sleep(0.1) # Plus long que le timeout
return []
with patch.object(
pipeline.clinical_facts_extractor,
"extract_facts",
side_effect=slow_extract,
):
result = pipeline.process_stay(documents, stay_metadata)
# Vérifications
assert result.success is False
assert "timeout" in result.error_message.lower()
# Vérifier qu'un problème de validation timeout a été ajouté
timeout_issues = [
issue for issue in result.validation_issues
if "timeout" in issue.message.lower()
]
assert len(timeout_issues) > 0
def test_pipeline_timeout_without_partial_results(db_session, rag_engine):
"""
Test le timeout sans mode résultats partiels (doit lever une exception).
Exigences: 14.6
"""
from pipeline_mco_pmsi.database.models import StayDB
pipeline = Pipeline(
db_session=db_session,
rag_engine=rag_engine,
timeout=0.01, # Timeout très court
partial_results_mode=False, # Mode désactivé
)
# Créer le séjour dans la base de données
stay = StayDB(
stay_id="stay_timeout_002",
admission_date=datetime.now() - timedelta(days=1),
discharge_date=datetime.now(),
specialty="gastro-enterologie",
unit="medecine",
age=45,
sex="M",
)
db_session.add(stay)
db_session.commit()
documents = [
ClinicalDocument(
document_id="doc_timeout_002",
document_type="cr_medical",
content="Diagnostic: Gastrite aiguë.",
creation_date=datetime.now(),
author="Dr. Test",
priority=2,
)
]
stay_metadata = StayMetadata(
stay_id="stay_timeout_002",
admission_date=datetime.now() - timedelta(days=1),
discharge_date=datetime.now(),
specialty="gastro-enterologie",
unit="medecine",
age=45,
sex="M",
)
# Mock pour ralentir le traitement
import time
def slow_extract(structured_stay):
time.sleep(0.1)
return []
with patch.object(
pipeline.clinical_facts_extractor,
"extract_facts",
side_effect=slow_extract,
):
result = pipeline.process_stay(documents, stay_metadata)
# Vérifications - devrait retourner un résultat d'erreur
assert result.success is False
assert result.error_message is not None
def test_pipeline_max_retries_exceeded(db_session, rag_engine):
"""
Test que le pipeline échoue après avoir dépassé le nombre maximum de retries.
Exigences: 14.1
"""
from pipeline_mco_pmsi.database.models import StayDB
pipeline = Pipeline(
db_session=db_session,
rag_engine=rag_engine,
max_retries=2,
retry_delay=0.1,
)
# Créer le séjour dans la base de données
stay = StayDB(
stay_id="stay_max_retry_001",
admission_date=datetime.now() - timedelta(days=1),
discharge_date=datetime.now(),
specialty="gastro-enterologie",
unit="medecine",
age=45,
sex="M",
)
db_session.add(stay)
db_session.commit()
documents = [
ClinicalDocument(
document_id="doc_max_retry_001",
document_type="cr_medical",
content="Diagnostic: Gastrite aiguë.",
creation_date=datetime.now(),
author="Dr. Test",
priority=2,
)
]
stay_metadata = StayMetadata(
stay_id="stay_max_retry_001",
admission_date=datetime.now() - timedelta(days=1),
discharge_date=datetime.now(),
specialty="gastro-enterologie",
unit="medecine",
age=45,
sex="M",
)
# Mock pour toujours échouer
def always_fail(structured_stay):
raise RuntimeError("Erreur persistante")
with patch.object(
pipeline.clinical_facts_extractor,
"extract_facts",
side_effect=always_fail,
):
result = pipeline.process_stay(documents, stay_metadata)
# Vérifications
assert result.success is False
assert result.error_message is not None
assert "Erreur persistante" in result.error_message
def test_pipeline_error_handling_with_partial_data(db_session, rag_engine):
"""
Test que les données partielles sont préservées en cas d'erreur.
Exigences: 14.6
"""
from pipeline_mco_pmsi.database.models import StayDB
pipeline = Pipeline(
db_session=db_session,
rag_engine=rag_engine,
partial_results_mode=True,
)
# Créer le séjour dans la base de données
stay = StayDB(
stay_id="stay_partial_001",
admission_date=datetime.now() - timedelta(days=1),
discharge_date=datetime.now(),
specialty="gastro-enterologie",
unit="medecine",
age=45,
sex="M",
)
db_session.add(stay)
db_session.commit()
documents = [
ClinicalDocument(
document_id="doc_partial_001",
document_type="cr_medical",
content="Diagnostic: Gastrite aiguë.",
creation_date=datetime.now(),
author="Dr. Test",
priority=2,
)
]
stay_metadata = StayMetadata(
stay_id="stay_partial_001",
admission_date=datetime.now() - timedelta(days=1),
discharge_date=datetime.now(),
specialty="gastro-enterologie",
unit="medecine",
age=45,
sex="M",
)
# Mock pour échouer après l'extraction des faits
def fail_after_facts(proposal, facts, cim10_version, ccam_version):
raise RuntimeError("Erreur lors de la vérification")
with patch.object(
pipeline.verificateur,
"verify_proposal",
side_effect=fail_after_facts,
):
result = pipeline.process_stay(documents, stay_metadata)
# Vérifications
assert result.success is False
# Les données partielles doivent être présentes
assert result.structured_stay is not None
# Le coding_proposal peut être None si l'erreur survient avant ou pendant le codage
# On vérifie juste que le résultat est bien un échec avec un message d'erreur
assert result.error_message is not None