"""Altreste neben dem Betrieb (24.09.2026): nur aus der festen Liste, nur was da ist, nur auf Knopfdruck — und die Löschfunktion nimmt eine Kennung, nie einen Pfad. Befehle (systemctl, git, sudo) laufen über eine Schicht, die hier ersetzt ist; kein Test fasst die Box an.""" import os import pytest from fastapi import FastAPI from fastapi.testclient import TestClient from routers import boxwart from services import aufraeumen from services.aufraeumen import Altrest class Befehle: """Ersatz für aufraeumen._befehl: schreibt mit, und Units laufen nur, wenn der Test es sagt.""" def __init__(self): self.aufrufe: list[list[str]] = [] self.laufend: set[str] = set() self.eingetragen: set[str] = set() self.sudo_ok = True def __call__(self, args: list[str]) -> tuple[bool, str]: self.aufrufe.append(args) if args[:3] == ["systemctl", "--user", "show"]: unit = args[3] return True, (f"ActiveState={'active' if unit in self.laufend else 'inactive'}\n" f"UnitFileState={'enabled' if unit in self.eingetragen else 'disabled'}") if args[0] == "sudo": return self.sudo_ok, "" if self.sudo_ok else "sudo: a password is required" return True, "" @pytest.fixture def heim(tmp_path, monkeypatch): """Ein Home-Ordner mit Resten, eine Liste darauf und daneben etwas, das bleiben muss.""" home = tmp_path / "home" for pfad, groesse in (("zonos/modell.bin", 3000), ("zonos/sub/x.txt", 500), (".venv-a/lib/a.py", 1000), ("skript.sh", 50), ("wt/datei", 10), ("bleibt/wichtig.txt", 5)): datei = home / pfad datei.parent.mkdir(parents=True, exist_ok=True) datei.write_bytes(b"x" * groesse) liste = ( Altrest("zonos", "TTS-Test", ("~/zonos",), "Test."), Altrest("venvs", "Alte venvs", ("~/.venv-a", "~/.venv-gibt-es-nicht"), "Alt."), Altrest("skript", "Altes Skript", ("~/skript.sh",), "Tot."), Altrest("fehlt", "Längst weg", ("~/gibt-es-nicht",), "Weg."), Altrest("wt", "Arbeitskopie", ("~/wt",), "Git.", art="worktree"), Altrest("mem", "mem0", ("~/.venv-a",), "Sidecar.", sperre=("mem0-service.service",)), ) befehle = Befehle() monkeypatch.setenv("MC_ALTRESTE_HOME", str(home)) monkeypatch.setattr(aufraeumen, "ALTRESTE", liste) monkeypatch.setattr(aufraeumen, "AUF_DER_BOX", True) monkeypatch.setattr(aufraeumen, "_befehl", befehle) monkeypatch.setitem(aufraeumen._altreste_cache, "liste", None) return home, befehle def test_liste_zeigt_nur_was_da_ist(heim): liste = aufraeumen.altreste(frisch=True) assert [a["id"] for a in liste] == ["zonos", "venvs", "mem", "skript", "wt"] # größte zuerst zonos = liste[0] assert zonos["groesse_bytes"] == 3500 and zonos["groesse"] == "4 KB" and zonos["pfade"] == ["~/zonos"] venvs = next(a for a in liste if a["id"] == "venvs") assert venvs["pfade"] == ["~/.venv-a"] # nur, was da ist assert next(a for a in liste if a["id"] == "skript")["groesse"] == "unter 1 KB" def test_was_ein_dienst_braucht_wird_nicht_angeboten(heim): _, befehle = heim befehle.laufend.add("mem0-service.service") assert "mem" not in {a["id"] for a in aufraeumen.altreste(frisch=True)} ergebnis = aufraeumen.altrest_loeschen("mem") assert not ergebnis["ok"] and "läuft gerade" in ergebnis["detail"] befehle.laufend.clear() befehle.eingetragen.add("mem0-service.service") assert "Autostart" in aufraeumen.altrest_loeschen("mem")["detail"] assert (heim[0] / ".venv-a").exists() def test_loeschen_nur_ueber_die_kennung(heim): home, _ = heim for kennung in ("../../etc", "~/bleibt", str(home / "bleibt"), ""): ergebnis = aufraeumen.altrest_loeschen(kennung) assert not ergebnis["ok"] and "kennt die Liste nicht" in ergebnis["detail"] ergebnis = aufraeumen.altrest_loeschen("zonos") assert ergebnis == {"ok": True, "text": "TTS-Test gelöscht, 4 KB frei."} assert not (home / "zonos").exists() and (home / "bleibt" / "wichtig.txt").exists() assert "zonos" not in {a["id"] for a in aufraeumen.altreste()} # die Liste ist danach frisch assert "schon weg" in aufraeumen.altrest_loeschen("zonos")["detail"] def test_nacharbeit_je_art(heim): home, befehle = heim assert aufraeumen.altrest_loeschen("wt")["ok"] and not (home / "wt").exists() assert ["git", "-C", str(aufraeumen.REPO), "worktree", "prune"] in befehle.aufrufe assert aufraeumen.altrest_loeschen("skript")["ok"] assert ["systemctl", "--user", "daemon-reload"] not in befehle.aufrufe # nur bei Units def test_units_werden_neu_eingelesen(heim, monkeypatch): home, befehle = heim unit = home / ".config" / "systemd" / "user" / "alt.timer" unit.parent.mkdir(parents=True) unit.write_text("[Timer]\n", encoding="utf-8") monkeypatch.setattr(aufraeumen, "ALTRESTE", (Altrest("units", "Alt-Units", ("~/.config/systemd/user/alt.timer",), "Aus.", art="unit", sperre=("alt.timer",)),)) befehle.eingetragen.add("alt.timer") assert not aufraeumen.altrest_loeschen("units")["ok"] and unit.exists() befehle.eingetragen.clear() assert aufraeumen.altrest_loeschen("units")["ok"] and not unit.exists() assert befehle.aufrufe[-1] == ["systemctl", "--user", "daemon-reload"] def test_verknuepfung_nimmt_nie_ihr_ziel_mit(heim, monkeypatch): home, _ = heim try: os.symlink(home / "bleibt", home / "link", target_is_directory=True) except (OSError, NotImplementedError): pytest.skip("Verknüpfungen gehen hier nicht (Windows ohne Rechte)") monkeypatch.setattr(aufraeumen, "ALTRESTE", (Altrest("link", "Verknüpfung", ("~/link",), "Test."),)) assert aufraeumen.altrest_loeschen("link")["ok"] assert not (home / "link").is_symlink() and (home / "bleibt" / "wichtig.txt").exists() def test_root_ordner_mit_sudo_nur_wo_erlaubt(heim, monkeypatch): home, befehle = heim def verweigert(pfad, *args, **kwargs): raise PermissionError(13, "Keine Berechtigung", str(pfad)) monkeypatch.setattr(aufraeumen.shutil, "rmtree", verweigert) monkeypatch.setattr(aufraeumen, "ALTRESTE", (Altrest("rocm", "ROCm", ("~/zonos",), "Alt.", sudo=True), Altrest("ohne", "Ohne sudo", ("~/.venv-a",), "Alt."))) assert aufraeumen.altrest_loeschen("rocm")["ok"] assert ["sudo", "-n", "rm", "-rf", "--", str(home / "zonos")] in befehle.aufrufe ergebnis = aufraeumen.altrest_loeschen("ohne") assert not ergebnis["ok"] and "Keine Berechtigung" in ergebnis["detail"] assert sum(1 for a in befehle.aufrufe if a[0] == "sudo") == 1 befehle.sudo_ok = False assert "sudo -n rm ging nicht" in aufraeumen.altrest_loeschen("rocm")["detail"] def test_am_pc_wird_nichts_geloescht(heim, monkeypatch): monkeypatch.setattr(aufraeumen, "AUF_DER_BOX", False) assert not aufraeumen.altrest_loeschen("zonos")["ok"] and (heim[0] / "zonos").exists() def test_schnittstelle_nimmt_nur_eine_kennung(heim): app = FastAPI() app.include_router(boxwart.router) c = TestClient(app) assert c.get("/api/aufraeumen/altreste").json()["altreste"][0]["id"] == "zonos" assert c.post("/api/aufraeumen/altreste/loeschen", json={"pfad": "/"}).status_code == 422 r = c.post("/api/aufraeumen/altreste/loeschen", json={"id": "../bleibt"}) assert r.status_code == 409 and "kennt die Liste nicht" in r.json()["detail"] assert c.post("/api/aufraeumen/altreste/loeschen", json={"id": "skript"}).json()["ok"] is True # --- Die echte Liste ---------------------------------------------------------------------------------- GESCHUETZT = ("~", "~/.hermes", "~/.hermes/hermes-agent", "~/.hermes/hermes-agent/hermes_cli/web_dist", "~/.hermes/config.yaml", "~/.hermes/.env", "~/.hermes/memories", "~/.hermes/skills", "~/.hermes/kanban.db", "~/.hermes/scripts/news-melden.sh", "~/.hermes/scripts/sonntags-update.sh", "~/.hermes/scripts/stack-radar.sh", "~/.hermes/plugins/mc2-web-lesen", "~/mission-control-v2", "~/mission-control-v2/frontend/dist/index.html", "~/.config/systemd/user", "~/.config/systemd/user/mc2-radar.timer", "~/.config/systemd/user/lucy-stimme.service", "~/.lucy-stimme", "~/.voice", "~/.cache", "~/.opencode", "~/projekte", "~/.local/bin", "/srv/models", "/opt", "/opt/llamacpp-vulkan", "/opt/llamacpp-vulkan.bak") def test_feste_liste_ist_eng(): kennungen = [a.id for a in aufraeumen.ALTRESTE] assert len(kennungen) == len(set(kennungen)) for a in aufraeumen.ALTRESTE: assert a.art in ("ordner", "worktree", "unit") and a.hinweis and a.name for pfad in a.pfade: assert pfad.startswith("~/") or pfad == "/opt/llamacpp", pfad assert not any(z in pfad for z in ("*", "?", "..", "//")) and not pfad.endswith("/"), pfad for heilig in GESCHUETZT: assert pfad != heilig and not heilig.startswith(pfad + "/"), (pfad, heilig) assert a.sudo == (a.pfade == ("/opt/llamacpp",)) # sudo nur für den ROCm-Ordner unter /opt def test_feste_liste_enthaelt_die_bekannten_reste(): alle = {p for a in aufraeumen.ALTRESTE for p in a.pfade} for pfad in ("~/zonos2-test", "/opt/llamacpp", "~/.mem0", "~/.hermes/kanban", "~/.venv-audit", "~/.hermes/worktrees/orch-graph-verknuepfer", "~/projekte/mc2-referenz", "~/.hermes/plugins/mc2-memory", "~/.hermes/PINNED_VERSION", "~/mission-control-v2/frontend/dist/avatar.vrm"): assert pfad in alle, pfad # Was noch jemand nutzt, steht nicht darin (Prüfung vom 24.09.2026). assert not alle & {"~/.hermes/kanban.db", "~/.voice", "~/.cache", "~/.opencode", "~/.hermes/scripts/ampel-waechter.sh", "~/.hermes/scripts/pc_path_lookup.sh"} assert "~/.hermes/scripts/curator-idle-watchdog.sh" not in alle # steht in der crontab