"""Aufräumen der Modell-Platte: nur Ungenutztes, nur auf Knopfdruck, geteilte Dateien bleiben.""" from pathlib import Path import pytest from services import aufraeumen, llamaswap @pytest.fixture def platte(tmp_path, monkeypatch): """Nachbau der Box: Hirn + Bild-Zwilling teilen Gewichte, der Projektor liegt im Original-Ordner.""" for ordner, datei, groesse in (("Hirn-GGUF", "hirn.gguf", 3000), ("Original-GGUF", "mmproj.gguf", 500), ("Coder-GGUF", "coder.gguf", 2000), ("Alt-GGUF", "alt.gguf", 4000), ("Muse-GGUF", "muse.gguf", 1500), ("Reserve-GGUF", "reserve.gguf", 2500), ("radar", "log.txt", 10), (".cache", "x", 10)): (tmp_path / ordner).mkdir() (tmp_path / ordner / datei).write_bytes(b"x" * groesse) m = str(tmp_path) cfg = {"models": { "hirn": {"cmd": f"llama-server -m {m}/Hirn-GGUF/hirn.gguf", "aliases": ["hermes", "fast"]}, "hirn-bild": {"cmd": f"llama-server -m {m}/Hirn-GGUF/hirn.gguf --mmproj {m}/Original-GGUF/mmproj.gguf", "aliases": ["vision"]}, "coder": {"cmd": f"llama-server -m {m}/Coder-GGUF/coder.gguf", "aliases": ["coder"]}, "alt": {"cmd": f"llama-server -m {m}/Alt-GGUF/alt.gguf"}, "muse": {"cmd": f"llama-server -m {m}/Muse-GGUF/muse.gguf", "aliases": ["debugger"]}, }, "groups": {"brains": {"persistent": True, "members": ["hirn"]}}} monkeypatch.setattr(aufraeumen, "MODELS_DIR", tmp_path) monkeypatch.setattr(aufraeumen, "AUF_DER_BOX", True) monkeypatch.setattr(llamaswap, "read_config", lambda: cfg) monkeypatch.setattr(llamaswap, "write_config", lambda c: None) return tmp_path, cfg def test_kandidaten_sind_rollenlose_eintraege_und_verwaiste_ordner(platte): namen = {(k["art"], k["name"]) for k in aufraeumen.kandidaten()} assert namen == {("eintrag", "alt"), ("eintrag", "muse"), ("ordner", "Reserve-GGUF")} # Der Original-Ordner ist KEIN Kandidat: der Bild-Zwilling nutzt seinen Projektor. muse = next(k for k in aufraeumen.kandidaten() if k["name"] == "muse") assert "debugger" in muse["hinweis"] def test_eintrag_loeschen_nimmt_ihn_aus_der_config_und_seinen_ordner_mit(platte): wurzel, cfg = platte ergebnis = aufraeumen.loeschen("eintrag", "alt") assert ergebnis["ok"] and "alt gelöscht" in ergebnis["text"] assert "alt" not in cfg["models"] and not (wurzel / "Alt-GGUF").exists() assert (wurzel / "Hirn-GGUF" / "hirn.gguf").exists() and (wurzel / "Coder-GGUF").exists() def test_nur_aktuelle_kandidaten_und_nie_systemordner(platte): wurzel, _ = platte assert not aufraeumen.loeschen("ordner", "Hirn-GGUF")["ok"] # wird genutzt assert not aufraeumen.loeschen("ordner", "radar")["ok"] # Systemordner assert not aufraeumen.loeschen("ordner", "../etwas")["ok"] assert (wurzel / "Hirn-GGUF").exists() and (wurzel / "radar").exists() assert aufraeumen.loeschen("ordner", "Reserve-GGUF")["ok"] and not (wurzel / "Reserve-GGUF").exists() def test_delete_model_laesst_geteilte_gewichte_liegen(platte): """Das Löschen des Bild-Zwillings darf die Gewichte des Hirns nicht mitnehmen (24.09.).""" wurzel, cfg = platte assert llamaswap.delete_model("hirn-bild") is True assert "hirn-bild" not in cfg["models"] assert (wurzel / "Hirn-GGUF" / "hirn.gguf").exists() # Im Zweifel liegen lassen: der Ordner taucht danach als verwaist im Aufräumen auf. assert Path(wurzel / "Original-GGUF" / "mmproj.gguf").exists() assert ("ordner", "Original-GGUF") in {(k["art"], k["name"]) for k in aufraeumen.kandidaten()}