""" 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