"""Couche 'capabilities moteurs' : vérité testable sur les moteurs disponibles. Un moteur n'est *disponible* que si ses dépendances (et son modèle, pour CamemBERT) chargent réellement dans l'environnement courant. Cette couche est consommée par la GUI (afficher/désactiver) et le CLI (`--engines`) pour que l'application ne promette jamais un moteur qu'elle n'embarque pas. Sondes injectables → aucun modèle, aucun réseau dans les tests. """ from __future__ import annotations import engine_capabilities as ec def _probes(camembert=True, eds=False, gliner=False): return { "camembert": lambda: (camembert, "ok" if camembert else "modèle absent"), "eds": lambda: (eds, "ok" if eds else "edsnlp non embarqué"), "gliner": lambda: (gliner, "ok" if gliner else "gliner non embarqué"), } def test_capabilities_map_reads_injected_probes(): caps = ec.capabilities_map(probes=_probes(camembert=True, eds=False, gliner=False)) assert set(caps) == {"camembert", "eds", "gliner"} assert caps["camembert"].available is True assert caps["eds"].available is False assert caps["gliner"].available is False def test_camembert_required_others_optional(): caps = ec.capabilities_map(probes=_probes()) assert caps["camembert"].required is True assert caps["eds"].required is False assert caps["gliner"].required is False def test_reason_surfaced_when_unavailable(): caps = ec.capabilities_map(probes=_probes(eds=False)) assert "edsnlp" in caps["eds"].reason # explication présentable à l'utilisateur # un moteur disponible expose aussi une raison non vide assert caps["camembert"].reason def test_available_engines_filters_unavailable(): avail = ec.available_engines(probes=_probes(camembert=True, eds=True, gliner=False)) keys = {c.key for c in avail} assert keys == {"camembert", "eds"} def test_labels_are_human_readable(): caps = ec.capabilities_map(probes=_probes()) assert "CamemBERT" in caps["camembert"].label assert "EDS" in caps["eds"].label assert "GLiNER" in caps["gliner"].label def test_default_probes_run_without_crash_and_are_consistent(): """Les sondes par défaut (find_spec + fichier modèle) ne crashent pas et renvoient un booléen + une raison non vide pour chaque moteur.""" caps = ec.capabilities_map() # sondes réelles de l'environnement assert set(caps) == {"camembert", "eds", "gliner"} for cap in caps.values(): assert isinstance(cap.available, bool) assert isinstance(cap.reason, str) and cap.reason