"""Risoluzione modelli: ordine **cache HF → filesystem (MODELS_DIR) → online**. Il vecchio gate `ENABLE_HF_CACHE` è stato rimosso. La regola ora è: - in cache HF → si usa la cache (ritorna il repo-id, offline-safe); - altrimenti in `MODELS_DIR/` → si usa la folder locale; - altrimenti, se è permessa la rete (`HF_HUB_OFFLINE` non attivo) → repo-id (download); - altrimenti il modello NON è disponibile → `ModelUnavailable` (nessun crash a monte: i layer lo intercettano e si auto-inibiscono). """ from __future__ import annotations from pathlib import Path import pytest import config.runtime.models as cfg_models # Riferimento alla funzione REALE (l'autouse fixture sotto la sostituisce): # serve al test che verifica la lettura di RUNTIME_MODE. _REAL_LOCAL_ONLY = cfg_models._local_only @pytest.fixture(autouse=True) def _default_non_local(monkeypatch): """Default dei test: NON local mode (l'ambiente può avere RUNTIME_MODE=local via .env caricato da python-dotenv). I test del local mode lo riattivano.""" monkeypatch.setattr(cfg_models, "_local_only", lambda: False) # --- local_path: cache → filesystem → online → ModelUnavailable -------------- def test_local_path_prefers_hf_cache(monkeypatch, tmp_path): # In cache → ritorna il repo-id (i loader caricano dalla cache, anche offline). monkeypatch.setattr(cfg_models, "MODELS_DIR", tmp_path) monkeypatch.setattr(cfg_models, "_in_hf_cache", lambda repo: True) repo = "DeepMount00/universal_ner_ita" # anche se esiste la folder locale, la cache ha precedenza (tmp_path / repo).mkdir(parents=True) assert cfg_models.local_path(repo) == repo def test_local_path_falls_back_to_filesystem(monkeypatch, tmp_path): # Non in cache, ma presente in MODELS_DIR → path locale. monkeypatch.setattr(cfg_models, "MODELS_DIR", tmp_path) monkeypatch.setattr(cfg_models, "_in_hf_cache", lambda repo: False) repo = "DeepMount00/Italian_NER_XXL_v2" (tmp_path / repo).mkdir(parents=True) assert cfg_models.local_path(repo) == str(tmp_path / repo) def test_local_path_uses_repo_id_when_online(monkeypatch, tmp_path): # Né cache né folder, ma rete permessa → repo-id (download dall'Hub). monkeypatch.setattr(cfg_models, "MODELS_DIR", tmp_path) monkeypatch.setattr(cfg_models, "_in_hf_cache", lambda repo: False) monkeypatch.setattr(cfg_models, "_online", lambda: True) repo = "DeepMount00/universal_ner_ita" assert cfg_models.local_path(repo) == repo def test_local_path_raises_when_absent_and_offline(monkeypatch, tmp_path): # Né cache né folder, niente rete → ModelUnavailable (non FileNotFoundError). monkeypatch.setattr(cfg_models, "MODELS_DIR", tmp_path) monkeypatch.setattr(cfg_models, "_in_hf_cache", lambda repo: False) monkeypatch.setattr(cfg_models, "_online", lambda: False) with pytest.raises(cfg_models.ModelUnavailable): cfg_models.local_path("DeepMount00/universal_ner_ita") # --- model_available: cache OR filesystem OR online -------------------------- def test_model_available_true_when_cached(monkeypatch, tmp_path): monkeypatch.setattr(cfg_models, "MODELS_DIR", tmp_path) monkeypatch.setattr(cfg_models, "_in_hf_cache", lambda repo: True) monkeypatch.setattr(cfg_models, "_online", lambda: False) assert cfg_models.model_available("x/y") is True def test_model_available_true_when_in_filesystem(monkeypatch, tmp_path): monkeypatch.setattr(cfg_models, "MODELS_DIR", tmp_path) monkeypatch.setattr(cfg_models, "_in_hf_cache", lambda repo: False) monkeypatch.setattr(cfg_models, "_online", lambda: False) (tmp_path / "x" / "y").mkdir(parents=True) assert cfg_models.model_available("x/y") is True def test_model_available_true_when_online(monkeypatch, tmp_path): monkeypatch.setattr(cfg_models, "MODELS_DIR", tmp_path) monkeypatch.setattr(cfg_models, "_in_hf_cache", lambda repo: False) monkeypatch.setattr(cfg_models, "_online", lambda: True) assert cfg_models.model_available("x/y") is True def test_model_available_false_when_absent_and_offline(monkeypatch, tmp_path): monkeypatch.setattr(cfg_models, "MODELS_DIR", tmp_path) monkeypatch.setattr(cfg_models, "_in_hf_cache", lambda repo: False) monkeypatch.setattr(cfg_models, "_online", lambda: False) assert cfg_models.model_available("x/y") is False # --- find_gguf: cache → filesystem → online → ModelUnavailable --------------- def test_find_gguf_prefers_filesystem(monkeypatch, tmp_path): monkeypatch.setattr(cfg_models, "MODELS_DIR", tmp_path) monkeypatch.setattr(cfg_models, "_cache_gguf", lambda repo, pat: None) repo = "unsloth/gemma-4-E2B-it-GGUF" base = tmp_path / repo base.mkdir(parents=True) (base / "gemma-4-E2B-it-Q4_K_M.gguf").write_bytes(b"x") (base / "gemma-4-E2B-it-Q8_0.gguf").write_bytes(b"x") assert Path(cfg_models.find_gguf(repo, "*Q4_K_M.gguf")) == ( base / "gemma-4-E2B-it-Q4_K_M.gguf" ) def test_find_gguf_prefers_cache_over_filesystem(monkeypatch, tmp_path): # In cache → usata prima della folder (coerente con local_path). monkeypatch.setattr(cfg_models, "MODELS_DIR", tmp_path) monkeypatch.setattr(cfg_models, "_cache_gguf", lambda repo, pat: "/cache/x.gguf") repo = "unsloth/gemma-4-E2B-it-GGUF" base = tmp_path / repo base.mkdir(parents=True) (base / "gemma-4-E2B-it-Q4_K_M.gguf").write_bytes(b"x") assert cfg_models.find_gguf(repo, "*Q4_K_M.gguf") == "/cache/x.gguf" def test_find_gguf_downloads_when_online(monkeypatch, tmp_path): monkeypatch.setattr(cfg_models, "MODELS_DIR", tmp_path) monkeypatch.setattr(cfg_models, "_cache_gguf", lambda repo, pat: None) monkeypatch.setattr(cfg_models, "_online", lambda: True) monkeypatch.setattr(cfg_models, "_download_gguf", lambda repo, pat: "/dl/x.gguf") assert cfg_models.find_gguf("unsloth/gemma-4-E2B-it-GGUF", "*Q4_K_M.gguf") == ( "/dl/x.gguf" ) def test_find_gguf_raises_when_absent_and_offline(monkeypatch, tmp_path): monkeypatch.setattr(cfg_models, "MODELS_DIR", tmp_path) monkeypatch.setattr(cfg_models, "_cache_gguf", lambda repo, pat: None) monkeypatch.setattr(cfg_models, "_online", lambda: False) with pytest.raises(cfg_models.ModelUnavailable): cfg_models.find_gguf("unsloth/gemma-4-E2B-it-GGUF", "*Q4_K_M.gguf") # --- RUNTIME_MODE=local: SOLO filesystem (niente cache HF, niente rete) ------- def test_local_only_reads_runtime_mode(monkeypatch): monkeypatch.setenv("RUNTIME_MODE", "local") assert _REAL_LOCAL_ONLY() is True monkeypatch.setenv("RUNTIME_MODE", "LOCAL") # case-insensitive assert _REAL_LOCAL_ONLY() is True monkeypatch.setenv("RUNTIME_MODE", "dedicated") assert _REAL_LOCAL_ONLY() is False monkeypatch.delenv("RUNTIME_MODE", raising=False) assert _REAL_LOCAL_ONLY() is False def test_local_mode_uses_only_filesystem(monkeypatch, tmp_path): # In local mode, cache "piena" e rete permessa NON contano: vale solo la folder. monkeypatch.setattr(cfg_models, "MODELS_DIR", tmp_path) monkeypatch.setattr(cfg_models, "_local_only", lambda: True) monkeypatch.setattr(cfg_models, "_in_hf_cache", lambda repo: True) monkeypatch.setattr(cfg_models, "_online", lambda: True) repo = "DeepMount00/universal_ner_ita" (tmp_path / repo).mkdir(parents=True) assert cfg_models.local_path(repo) == str(tmp_path / repo) def test_local_mode_ignores_cache_and_network(monkeypatch, tmp_path): # Modello in cache e online, ma NON in MODELS_DIR → indisponibile in local mode. monkeypatch.setattr(cfg_models, "MODELS_DIR", tmp_path) monkeypatch.setattr(cfg_models, "_local_only", lambda: True) monkeypatch.setattr(cfg_models, "_in_hf_cache", lambda repo: True) monkeypatch.setattr(cfg_models, "_online", lambda: True) with pytest.raises(cfg_models.ModelUnavailable): cfg_models.local_path("DeepMount00/universal_ner_ita") def test_model_available_local_mode_filesystem_only(monkeypatch, tmp_path): monkeypatch.setattr(cfg_models, "MODELS_DIR", tmp_path) monkeypatch.setattr(cfg_models, "_local_only", lambda: True) monkeypatch.setattr(cfg_models, "_in_hf_cache", lambda repo: True) monkeypatch.setattr(cfg_models, "_online", lambda: True) assert cfg_models.model_available("x/y") is False (tmp_path / "x" / "y").mkdir(parents=True) assert cfg_models.model_available("x/y") is True def test_find_gguf_local_mode_filesystem_only(monkeypatch, tmp_path): monkeypatch.setattr(cfg_models, "MODELS_DIR", tmp_path) monkeypatch.setattr(cfg_models, "_local_only", lambda: True) monkeypatch.setattr(cfg_models, "_cache_gguf", lambda repo, pat: "/cache/x.gguf") monkeypatch.setattr(cfg_models, "_online", lambda: True) repo = "unsloth/gemma-4-E2B-it-GGUF" with pytest.raises(cfg_models.ModelUnavailable): cfg_models.find_gguf(repo, "*Q4_K_M.gguf") # cache/online ignorati base = tmp_path / repo base.mkdir(parents=True) (base / "m-Q4_K_M.gguf").write_bytes(b"x") assert Path(cfg_models.find_gguf(repo, "*Q4_K_M.gguf")) == base / "m-Q4_K_M.gguf" def test_gguf_available_local_mode_filesystem_only(monkeypatch, tmp_path): monkeypatch.setattr(cfg_models, "MODELS_DIR", tmp_path) monkeypatch.setattr(cfg_models, "_local_only", lambda: True) monkeypatch.setattr(cfg_models, "_cache_gguf", lambda repo, pat: "/cache/x.gguf") monkeypatch.setattr(cfg_models, "_online", lambda: True) assert cfg_models.gguf_available("unsloth/gemma-4-E2B-it-GGUF", "*Q4_K_M.gguf") is False