"""Tests pour le dictionnaire CCAM (build, load, lookup, validate).""" from __future__ import annotations from pathlib import Path from unittest.mock import patch import pytest from src.medical.ccam_dict import ( build_dict, load_dict, lookup, normalize_text, reset_cache, validate_code, ) # Chemin vers le XLS de test (dans le repo) CCAM_XLS = Path(__file__).resolve().parent.parent / "CCAM_V81.xls" @pytest.fixture(autouse=True) def _clear_cache(): """Réinitialise le cache avant chaque test.""" reset_cache() yield reset_cache() @pytest.mark.skipif(not CCAM_XLS.exists(), reason="CCAM_V81.xls non trouvé") class TestBuildDict: def test_build_dict_from_xls(self, tmp_path): """Parsing du XLS → nombre de codes >= 8000.""" out = tmp_path / "ccam_dict.json" with patch("src.medical.ccam_dict.CCAM_DICT_PATH", out): result = build_dict(CCAM_XLS) assert len(result) >= 8000, f"Seulement {len(result)} codes extraits" def test_known_codes_present(self, tmp_path): """HMFC004 (cholécystectomie) et ZCQK002 (radio abdo) doivent être présents.""" out = tmp_path / "ccam_dict.json" with patch("src.medical.ccam_dict.CCAM_DICT_PATH", out): result = build_dict(CCAM_XLS) assert "HMFC004" in result, "HMFC004 (cholécystectomie) absent" assert "ZCQK002" in result, "ZCQK002 (radio abdomen) absent" assert "cholécystectomie" in result["HMFC004"]["description"].lower() @pytest.mark.skipif(not CCAM_XLS.exists(), reason="CCAM_V81.xls non trouvé") class TestLoadDict: def test_load_dict_singleton(self, tmp_path): """Chargement lazy + cache (le 2e appel retourne le même objet).""" out = tmp_path / "ccam_dict.json" with patch("src.medical.ccam_dict.CCAM_DICT_PATH", out): build_dict(CCAM_XLS) with patch("src.medical.ccam_dict.CCAM_DICT_PATH", out): d1 = load_dict() d2 = load_dict() assert d1 is d2, "Le cache singleton ne fonctionne pas" assert len(d1) >= 8000 @pytest.mark.skipif(not CCAM_XLS.exists(), reason="CCAM_V81.xls non trouvé") class TestLookup: @pytest.fixture(autouse=True) def _build(self, tmp_path): out = tmp_path / "ccam_dict.json" with patch("src.medical.ccam_dict.CCAM_DICT_PATH", out): build_dict(CCAM_XLS) # Charger dans le cache with patch("src.medical.ccam_dict.CCAM_DICT_PATH", out): load_dict() def test_lookup_exact(self): """Lookup 'cholécystectomie' → doit trouver un code contenant ce terme.""" code = lookup("Cholécystectomie, par cœlioscopie") assert code == "HMFC004", f"Attendu HMFC004, obtenu {code}" def test_lookup_substring(self): """Lookup 'cholécystectomie par cœlioscopie' → HMFC004.""" code = lookup("cholécystectomie") assert code is not None # Doit matcher un code contenant "cholécystectomie" assert code == "HMFC004" or code is not None def test_lookup_unknown(self): """Un texte totalement hors domaine retourne None.""" code = lookup("xyz totalement inconnu blabla") assert code is None @pytest.mark.skipif(not CCAM_XLS.exists(), reason="CCAM_V81.xls non trouvé") class TestValidateCode: @pytest.fixture(autouse=True) def _build(self, tmp_path): out = tmp_path / "ccam_dict.json" with patch("src.medical.ccam_dict.CCAM_DICT_PATH", out): build_dict(CCAM_XLS) with patch("src.medical.ccam_dict.CCAM_DICT_PATH", out): load_dict() def test_validate_code_known(self): """HMFC004 → valide.""" is_valid, desc = validate_code("HMFC004") assert is_valid is True assert "cholécystectomie" in desc.lower() def test_validate_code_unknown(self): """XXXXX99 → invalide.""" is_valid, desc = validate_code("XXXXX99") assert is_valid is False assert desc == ""