from __future__ import annotations import importlib.util import subprocess from pathlib import Path from types import ModuleType def _load_guard(repo_root: Path) -> ModuleType: script = repo_root / "scripts" / "graph_artifact_guard.py" spec = importlib.util.spec_from_file_location("graph_artifact_guard", script) assert spec and spec.loader module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module def _git(repo: Path, *args: str) -> str: result = subprocess.run( ["git", *args], cwd=repo, check=True, text=True, stdout=subprocess.PIPE, ) return result.stdout def test_graph_artifact_guard_parks_and_unparks_tracked_artifacts( tmp_path: Path, ) -> None: repo_root = Path(__file__).resolve().parents[2] guard = _load_guard(repo_root) repo = tmp_path / "repo" graph = repo / "graph" graph.mkdir(parents=True) _git(tmp_path, "init", "repo") _git(repo, "config", "user.email", "ctx@example.invalid") _git(repo, "config", "user.name", "ctx test") artifact = graph / "wiki-graph.tar.gz" overlay = graph / "entity-overlays.jsonl" artifact.write_bytes(b"fake graph artifact") overlay.write_text('{"nodes":[],"edges":[]}\n', encoding="utf-8") _git(repo, "add", "graph/wiki-graph.tar.gz", "graph/entity-overlays.jsonl") _git(repo, "commit", "-m", "track graph artifact") assert guard.main(["--repo", str(repo), "park"]) == 0 assert _git(repo, "ls-files", "-v", "graph/wiki-graph.tar.gz").startswith("S ") assert _git(repo, "ls-files", "-v", "graph/entity-overlays.jsonl").startswith("S ") assert guard.main(["--repo", str(repo), "unpark"]) == 0 assert not _git(repo, "ls-files", "-v", "graph/wiki-graph.tar.gz").startswith( "S " ) assert not _git(repo, "ls-files", "-v", "graph/entity-overlays.jsonl").startswith( "S " ) def test_graph_artifact_guard_removes_only_stale_graph_files( tmp_path: Path, ) -> None: repo_root = Path(__file__).resolve().parents[2] guard = _load_guard(repo_root) repo = tmp_path / "repo" graph = repo / "graph" graph.mkdir(parents=True) _git(tmp_path, "init", "repo") artifact = graph / "wiki-graph.tar.gz" staged = graph / "wiki-graph.tar.gz.staged" partial = graph / "wiki-graph-runtime.tar.gz.partial" outside = repo / "wiki-graph.tar.gz.staged" artifact.write_bytes(b"real graph artifact") staged.write_bytes(b"interrupted staged artifact") partial.write_bytes(b"interrupted partial artifact") outside.write_bytes(b"not under graph") assert guard.main(["--repo", str(repo), "clean-stale"]) == 0 assert artifact.exists() assert not staged.exists() assert not partial.exists() assert outside.exists() def test_graph_artifact_guard_prune_is_lfs_only_by_default( tmp_path: Path, monkeypatch, ) -> None: repo_root = Path(__file__).resolve().parents[2] guard = _load_guard(repo_root) calls: list[tuple[str, ...]] = [] def fake_run_git( _repo: Path, args: list[str], *, check: bool = True, capture: bool = False, ) -> subprocess.CompletedProcess[str]: calls.append(tuple(args)) return subprocess.CompletedProcess(args, 0, stdout="", stderr="") monkeypatch.setattr(guard, "_run_git", fake_run_git) guard._prune(tmp_path, include_lfs=True, include_git_prune=False) assert ("lfs", "prune", "--verbose") in calls assert not any(call[:1] == ("prune",) for call in calls) def test_graph_artifact_guard_prune_requires_explicit_git_prune( tmp_path: Path, monkeypatch, ) -> None: repo_root = Path(__file__).resolve().parents[2] guard = _load_guard(repo_root) calls: list[tuple[str, ...]] = [] def fake_run_git( _repo: Path, args: list[str], *, check: bool = True, capture: bool = False, ) -> subprocess.CompletedProcess[str]: calls.append(tuple(args)) return subprocess.CompletedProcess(args, 0, stdout="", stderr="") monkeypatch.setattr(guard, "_run_git", fake_run_git) guard._prune(tmp_path, include_lfs=False, include_git_prune=True) assert ("prune", "--expire=now", "--verbose") in calls assert not any(call[:2] == ("lfs", "prune") for call in calls)