"""Auto-inibizione dei layer ML quando il modello non è reperibile. Regola: un layer ML è disponibile solo se ENABLE_ML è attivo **e** il suo modello è reperibile (cache HF, MODELS_DIR, o online). Altrimenti `available()` è False → il provider non lo costruisce e la UI lo segnala (vedi layer_catalog). `zero_shot` è un caso speciale: ha GLiNER **con fallback NLI**, quindi è disponibile se è reperibile almeno uno dei due modelli. """ from __future__ import annotations from config.runtime.settings import Settings from layers.embedding import main as embedding_main from layers.ner import main as ner_main from layers.ner_comuni import main as ner_comuni_main from layers.zero_shot import main as zero_shot_main ML_ON = Settings(enable_ml=True) ML_OFF = Settings(enable_ml=False) def test_ml_layer_unavailable_when_ml_disabled(): # ENABLE_ML=False → ogni layer ML è inibito, modello o no. assert ner_main.PLUGIN.available(ML_OFF) is False assert zero_shot_main.PLUGIN.available(ML_OFF) is False def test_ner_available_tracks_model(monkeypatch): monkeypatch.setattr(ner_main, "model_available", lambda repo: True) assert ner_main.PLUGIN.available(ML_ON) is True monkeypatch.setattr(ner_main, "model_available", lambda repo: False) assert ner_main.PLUGIN.available(ML_ON) is False def test_ner_comuni_available_tracks_model(monkeypatch): monkeypatch.setattr(ner_comuni_main, "model_available", lambda repo: False) assert ner_comuni_main.PLUGIN.available(ML_ON) is False def test_embedding_available_tracks_model(monkeypatch): monkeypatch.setattr(embedding_main, "model_available", lambda repo: False) assert embedding_main.PLUGIN.available(ML_ON) is False def test_zero_shot_available_if_gliner_present(monkeypatch): # GLiNER reperibile, NLI no → layer disponibile (usa GLiNER). def avail(repo): return repo == ML_ON.gliner_model monkeypatch.setattr(zero_shot_main, "model_available", avail) assert zero_shot_main.PLUGIN.available(ML_ON) is True def test_zero_shot_available_if_only_nli_present(monkeypatch): # GLiNER assente ma fallback NLI reperibile → ancora disponibile. def avail(repo): return repo == ML_ON.zero_shot_model monkeypatch.setattr(zero_shot_main, "model_available", avail) assert zero_shot_main.PLUGIN.available(ML_ON) is True def test_zero_shot_unavailable_when_both_models_absent(monkeypatch): monkeypatch.setattr(zero_shot_main, "model_available", lambda repo: False) assert zero_shot_main.PLUGIN.available(ML_ON) is False # --- layer_catalog: la UI riceve disponibilità reale + motivo ----------------- def test_layer_catalog_reports_reason_for_missing_model(monkeypatch): from web.services.registry import ServiceRegistry monkeypatch.setattr(ner_main, "model_available", lambda repo: False) reg = ServiceRegistry(Settings(enable_ml=True)) cat = {layer["key"]: layer for layer in reg.layer_catalog()["layers"]} assert cat["ner"]["available"] is False assert "models/" in cat["ner"]["unavailable_reason"] # 'rules' non è ML: sempre disponibile, nessun motivo. assert cat["rules"]["available"] is True assert cat["rules"]["unavailable_reason"] is None def test_layer_catalog_reason_when_ml_disabled(): from web.services.registry import ServiceRegistry reg = ServiceRegistry(Settings(enable_ml=False)) cat = {layer["key"]: layer for layer in reg.layer_catalog()["layers"]} assert cat["ner"]["available"] is False assert cat["ner"]["unavailable_reason"] == "richiede ENABLE_ML=true"