"""Tests du pont moteur G3-A : kwargs corrects, managers lazy, engine injecté. Aucun vrai manager, aucun modèle, aucun réseau : tout est injecté via factories. """ from __future__ import annotations from pathlib import Path import pytest from gui_v6.engine_bridge import ( EngineSettings, ManagerState, NerManagers, build_engine_kwargs, make_process_fn, ) class FakeManager: def __init__(self, name): self.name = name def _counting_factories(counter, fail_camembert=False): def camembert(): counter["camembert"] += 1 if fail_camembert: raise RuntimeError("modèle absent") return FakeManager("camembert") def eds(): counter["eds"] += 1 return FakeManager("eds") def gliner(): counter["gliner"] += 1 return FakeManager("gliner") return {"camembert": camembert, "eds": eds, "gliner": gliner} # -- kwargs ---------------------------------------------------------------- def test_kwargs_defaults_v5_like(): settings = EngineSettings(config_path=Path("/tmp/cfg.yml"), ogc_label="OCG") kwargs = build_engine_kwargs(settings, managers=None) assert kwargs["make_vector_redaction"] is False assert kwargs["also_make_raster_burn"] is True assert kwargs["config_path"] == Path("/tmp/cfg.yml") assert kwargs["ogc_label"] == "OCG" # Sans managers : pas de NER. assert kwargs["use_hf"] is False def test_kwargs_with_loaded_managers(): settings = EngineSettings(enable_eds=True, enable_gliner=True) counter = {"camembert": 0, "eds": 0, "gliner": 0} managers = NerManagers(settings, factories=_counting_factories(counter)) managers.ensure_loaded() kwargs = build_engine_kwargs(settings, managers) assert kwargs["use_hf"] is True assert kwargs["camembert_manager"].name == "camembert" assert kwargs["ner_manager"].name == "eds" assert kwargs["gliner_manager"].name == "gliner" def test_kwargs_ner_disabled(): settings = EngineSettings(use_local_ner=False) counter = {"camembert": 0, "eds": 0, "gliner": 0} managers = NerManagers(settings, factories=_counting_factories(counter)) managers.ensure_loaded() kwargs = build_engine_kwargs(settings, managers) assert kwargs["use_hf"] is False assert counter["camembert"] == 0 # NER désactivé : rien chargé # -- lazy loading ---------------------------------------------------------- def test_managers_not_loaded_on_init(): settings = EngineSettings() counter = {"camembert": 0, "eds": 0, "gliner": 0} NerManagers(settings, factories=_counting_factories(counter)) # Aucune factory appelée à la construction. assert counter == {"camembert": 0, "eds": 0, "gliner": 0} def test_managers_load_once_and_state(): settings = EngineSettings(enable_eds=True) counter = {"camembert": 0, "eds": 0, "gliner": 0} managers = NerManagers(settings, factories=_counting_factories(counter)) assert managers.state == ManagerState.NOT_LOADED assert managers.ensure_loaded() == ManagerState.READY assert managers.ensure_loaded() == ManagerState.READY # idempotent assert counter["camembert"] == 1 # chargé une seule fois assert counter["eds"] == 1 assert counter["gliner"] == 0 # non activé def test_managers_unavailable_when_camembert_fails(): settings = EngineSettings() counter = {"camembert": 0, "eds": 0, "gliner": 0} managers = NerManagers(settings, factories=_counting_factories(counter, fail_camembert=True)) assert managers.ensure_loaded() == ManagerState.UNAVAILABLE assert managers.use_hf is False def test_optional_manager_failure_is_tolerated(): settings = EngineSettings(enable_gliner=True) def factories(): def camembert(): return FakeManager("camembert") def gliner(): raise RuntimeError("gliner ko") def eds(): return FakeManager("eds") return {"camembert": camembert, "eds": eds, "gliner": gliner} managers = NerManagers(settings, factories=factories()) assert managers.ensure_loaded() == ManagerState.READY # gliner ko ne bloque pas assert managers.use_hf is True # -- garde-fou capabilities runtime ---------------------------------------- class _FakeCap: """Capability minimale pour injecter une sonde dans les tests.""" def __init__(self, available, reason="(test)"): self.available = available self.reason = reason def _caps_provider(eds_ok, gliner_ok): def provider(): return { "camembert": _FakeCap(True), "eds": _FakeCap(eds_ok), "gliner": _FakeCap(gliner_ok), } return provider def test_guard_disables_unavailable_eds_before_load(): # Profil/config forçant EDS alors que le moteur n'est pas embarqué. settings = EngineSettings(enable_eds=True) counter = {"camembert": 0, "eds": 0, "gliner": 0} managers = NerManagers( settings, factories=_counting_factories(counter), caps_provider=_caps_provider(eds_ok=False, gliner_ok=True), ) assert managers.ensure_loaded() == ManagerState.READY assert settings.enable_eds is False # désactivation forcée assert counter["eds"] == 0 # jamais tenté de charger assert managers.as_kwargs()["ner_manager"] is None def test_guard_disables_unavailable_gliner_before_load(): settings = EngineSettings(enable_gliner=True) counter = {"camembert": 0, "eds": 0, "gliner": 0} managers = NerManagers( settings, factories=_counting_factories(counter), caps_provider=_caps_provider(eds_ok=True, gliner_ok=False), ) assert managers.ensure_loaded() == ManagerState.READY assert settings.enable_gliner is False assert counter["gliner"] == 0 assert managers.as_kwargs()["gliner_manager"] is None def test_guard_keeps_available_engine_enabled(): settings = EngineSettings(enable_eds=True, enable_gliner=True) counter = {"camembert": 0, "eds": 0, "gliner": 0} managers = NerManagers( settings, factories=_counting_factories(counter), caps_provider=_caps_provider(eds_ok=True, gliner_ok=True), ) assert managers.ensure_loaded() == ManagerState.READY assert settings.enable_eds is True assert settings.enable_gliner is True assert counter["eds"] == 1 assert counter["gliner"] == 1 def test_guard_failsafe_when_probe_raises(): settings = EngineSettings(enable_eds=True) counter = {"camembert": 0, "eds": 0, "gliner": 0} def boom(): raise RuntimeError("probe ko") managers = NerManagers( settings, factories=_counting_factories(counter), caps_provider=boom ) # Best-effort : une sonde en échec ne bloque pas le chargement et ne # modifie pas les réglages (les try/except de load protègent déjà). assert managers.ensure_loaded() == ManagerState.READY assert settings.enable_eds is True assert counter["eds"] == 1 # -- make_process_fn ------------------------------------------------------- def test_process_fn_calls_engine_with_kwargs(tmp_path): settings = EngineSettings() counter = {"camembert": 0, "eds": 0, "gliner": 0} managers = NerManagers(settings, factories=_counting_factories(counter)) captured = {} def fake_engine(doc_path, out_dir, **kwargs): captured["doc"] = doc_path captured["out"] = out_dir captured["kwargs"] = kwargs return {"status": "ok"} fn = make_process_fn(settings, managers=managers, engine=fake_engine) # Avant tout traitement, aucun manager chargé. assert counter["camembert"] == 0 result = fn(tmp_path / "doc.pdf", tmp_path / "out") assert result == {"status": "ok"} assert captured["doc"] == tmp_path / "doc.pdf" assert captured["kwargs"]["make_vector_redaction"] is False assert captured["kwargs"]["also_make_raster_burn"] is True assert captured["kwargs"]["use_hf"] is True assert captured["kwargs"]["camembert_manager"].name == "camembert" # Le chargement n'a eu lieu qu'à l'appel de process_fn. assert counter["camembert"] == 1