156 lines
5.8 KiB
Python
156 lines
5.8 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Tests — chargement du gazetteer médicaments edsnlp depuis data/ (torch-free).
|
|
|
|
Contexte : le build Windows torch-free (Plan 3) retire torch. Or edsnlp importe
|
|
torch en dur → en frozen, `import edsnlp` échoue et l'ancienne
|
|
`_load_edsnlp_drug_names()` retournait silencieusement set() → whitelist
|
|
médicaments amputée de ~4206 noms → sur-masquage de médicaments pris pour des
|
|
personnes.
|
|
|
|
Correctif (Option A+B) :
|
|
A. charger d'abord depuis data/edsnlp/drugs.json (versionné, 0 dépendance) ;
|
|
fallback sur le package edsnlp (dev).
|
|
B. log.warning explicite si NI le fichier data NI le package ne sont dispo.
|
|
|
|
Aucun mock du contenu du gazetteer : on utilise le VRAI fichier data extrait.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
import anonymizer_core_refactored_onnx as core
|
|
|
|
ROOT_DIR = Path(__file__).resolve().parents[2]
|
|
DATA_DRUGS = ROOT_DIR / "data" / "edsnlp" / "drugs.json"
|
|
|
|
# Nombre exact de noms mono-mot (len>=4, lowercase) issus de drugs.json 0.20.0.
|
|
# C'est aussi le compte historique produit par l'ancienne fonction en dev :
|
|
# la garantie de non-régression est que la whitelist n'est PAS réduite.
|
|
EXPECTED_COUNT = 4206
|
|
|
|
# Noms de médicaments réellement présents dans le gazetteer extrait et qui
|
|
# entrent en conflit avec des noms/prénoms INSEE (vérifiés, pas inventés).
|
|
CONFLICT_NAMES = ["elisor", "kessar", "panos", "muse", "sirop"]
|
|
|
|
|
|
def test_data_file_present_and_parses():
|
|
"""Le fichier data doit exister et contenir 1968 codes ATC."""
|
|
import json
|
|
|
|
assert DATA_DRUGS.exists(), f"fichier data manquant : {DATA_DRUGS}"
|
|
data = json.loads(DATA_DRUGS.read_text(encoding="utf-8"))
|
|
assert len(data) == 1968
|
|
|
|
|
|
def test_load_from_data_exact_count():
|
|
"""Chargement depuis data/edsnlp/drugs.json → set de 4206 noms exactement."""
|
|
result = core._load_edsnlp_drug_names()
|
|
assert isinstance(result, set)
|
|
assert len(result) == EXPECTED_COUNT
|
|
|
|
|
|
def test_load_contains_conflict_names():
|
|
"""Les noms-conflits INSEE vérifiés doivent être dans le set (anti-sur-masquage)."""
|
|
result = core._load_edsnlp_drug_names()
|
|
for name in CONFLICT_NAMES:
|
|
assert name in result, f"{name!r} absent du gazetteer médicaments"
|
|
|
|
|
|
def test_fallback_to_package_when_data_absent(monkeypatch, tmp_path):
|
|
"""Si le fichier data est absent mais edsnlp importable → fallback package,
|
|
même résultat (4206)."""
|
|
pytest.importorskip("edsnlp")
|
|
# Pointer la constante de chemin data vers un dossier vide → fichier absent.
|
|
missing = tmp_path / "drugs.json"
|
|
monkeypatch.setattr(core, "_EDSNLP_DRUGS_DATA_PATH", missing)
|
|
assert not missing.exists()
|
|
|
|
result = core._load_edsnlp_drug_names()
|
|
assert len(result) == EXPECTED_COUNT
|
|
for name in CONFLICT_NAMES:
|
|
assert name in result
|
|
|
|
|
|
def test_warning_when_both_sources_absent(monkeypatch, tmp_path, caplog):
|
|
"""Si le fichier data est absent ET edsnlp non importable → set() + log.warning."""
|
|
import builtins
|
|
|
|
missing = tmp_path / "drugs.json"
|
|
monkeypatch.setattr(core, "_EDSNLP_DRUGS_DATA_PATH", missing)
|
|
|
|
_real_import = builtins.__import__
|
|
|
|
def _fake_import(name, *args, **kwargs):
|
|
if name == "edsnlp" or name.startswith("edsnlp."):
|
|
raise ImportError("edsnlp indisponible (torch-free)")
|
|
return _real_import(name, *args, **kwargs)
|
|
|
|
monkeypatch.setattr(builtins, "__import__", _fake_import)
|
|
|
|
with caplog.at_level("WARNING", logger=core.log.name):
|
|
result = core._load_edsnlp_drug_names()
|
|
|
|
assert result == set()
|
|
assert any(
|
|
"edsnlp" in rec.message.lower() and rec.levelname == "WARNING"
|
|
for rec in caplog.records
|
|
), "aucun log.warning émis lors de l'échec total"
|
|
|
|
|
|
def test_data_source_matches_package_source(monkeypatch, tmp_path):
|
|
"""Le set chargé depuis data doit être IDENTIQUE à celui du fallback package
|
|
(garantie que l'extraction n'altère pas le gazetteer)."""
|
|
pytest.importorskip("edsnlp")
|
|
from_data = core._load_edsnlp_drug_names()
|
|
|
|
missing = tmp_path / "drugs.json"
|
|
monkeypatch.setattr(core, "_EDSNLP_DRUGS_DATA_PATH", missing)
|
|
from_package = core._load_edsnlp_drug_names()
|
|
|
|
assert from_data == from_package
|
|
|
|
|
|
def test_corrupted_data_falls_back_to_package(monkeypatch, tmp_path):
|
|
"""Fichier data PRÉSENT mais corrompu + edsnlp DISPONIBLE → fail-safe :
|
|
retombe sur le package et retourne 4206 (jamais un set partiel/tronqué)."""
|
|
pytest.importorskip("edsnlp")
|
|
corrupt = tmp_path / "drugs.json"
|
|
corrupt.write_text("{ invalide", encoding="utf-8")
|
|
monkeypatch.setattr(core, "_EDSNLP_DRUGS_DATA_PATH", corrupt)
|
|
|
|
result = core._load_edsnlp_drug_names()
|
|
assert len(result) == EXPECTED_COUNT
|
|
for name in CONFLICT_NAMES:
|
|
assert name in result
|
|
|
|
|
|
def test_corrupted_data_and_no_package_warns(monkeypatch, tmp_path, caplog):
|
|
"""Fichier data corrompu ET edsnlp INDISPONIBLE → set() vide + log.warning
|
|
(dégradation rendue visible, pas de silence)."""
|
|
import builtins
|
|
|
|
corrupt = tmp_path / "drugs.json"
|
|
corrupt.write_text("{ invalide", encoding="utf-8")
|
|
monkeypatch.setattr(core, "_EDSNLP_DRUGS_DATA_PATH", corrupt)
|
|
|
|
_real_import = builtins.__import__
|
|
|
|
def _fake_import(name, *args, **kwargs):
|
|
if name == "edsnlp" or name.startswith("edsnlp."):
|
|
raise ImportError("edsnlp indisponible (torch-free)")
|
|
return _real_import(name, *args, **kwargs)
|
|
|
|
monkeypatch.setattr(builtins, "__import__", _fake_import)
|
|
|
|
with caplog.at_level("WARNING", logger=core.log.name):
|
|
result = core._load_edsnlp_drug_names()
|
|
|
|
assert result == set()
|
|
assert any(
|
|
"edsnlp" in rec.message.lower() and rec.levelname == "WARNING"
|
|
for rec in caplog.records
|
|
), "aucun log.warning émis lors de l'échec total (data corrompu + pas de package)"
|