"""Seven-tier tests for ``muse/cli/guard.py`` — ``require_clean_workdir``. Tiers ----- Unit — force bypass, clean workdir, added-only is safe, dirty exits. Integration — text vs JSON format, target_manifest filtering, truncation at 10. End-to-end — guard fires through real CLI commands (reset --hard, checkout). Stress — 500 dirty files, repeated calls on same repo. Data integrity — target_manifest OID comparison logic, deleted-in-target blocks. Security — ANSI injection in operation name, null byte in path, path traversal. Performance — completes under 2 s on a clean repo. """ from __future__ import annotations import json import os import pathlib import threading import time import pytest from muse.core.types import fake_id from muse.core.paths import repo_json_path from tests.cli_test_helper import CliRunner, InvokeResult runner = CliRunner() # ────────────────────────────────────────────────────────────────────────────── # Helpers # ────────────────────────────────────────────────────────────────────────────── def _invoke(repo: pathlib.Path, args: list[str]) -> InvokeResult: saved = os.getcwd() try: os.chdir(repo) return runner.invoke(None, args) finally: os.chdir(saved) def _init_repo(root: pathlib.Path) -> None: saved = os.getcwd() try: os.chdir(root) runner.invoke(None, ["init"]) finally: os.chdir(saved) def _commit(repo: pathlib.Path, message: str = "commit") -> None: saved = os.getcwd() try: os.chdir(repo) runner.invoke(None, ["code", "add", "."]) runner.invoke(None, ["commit", "-m", message]) finally: os.chdir(saved) @pytest.fixture() def clean_repo(tmp_path: pathlib.Path) -> pathlib.Path: """Repo with one committed file, clean working tree.""" _init_repo(tmp_path) (tmp_path / "a.py").write_text("x = 1\n") _commit(tmp_path, "initial") return tmp_path @pytest.fixture() def dirty_repo(clean_repo: pathlib.Path) -> pathlib.Path: """Repo with a committed file that has been locally modified.""" (clean_repo / "a.py").write_text("x = 99\n") return clean_repo # ────────────────────────────────────────────────────────────────────────────── # Unit — require_clean_workdir directly # ────────────────────────────────────────────────────────────────────────────── class TestUnit: def test_force_true_is_noop(self, dirty_repo: pathlib.Path) -> None: from muse.cli.guard import require_clean_workdir # Must not raise even though working tree is dirty. require_clean_workdir(dirty_repo, "test-op", force=True) def test_clean_workdir_does_not_raise(self, clean_repo: pathlib.Path) -> None: from muse.cli.guard import require_clean_workdir require_clean_workdir(clean_repo, "test-op") def test_modified_tracked_file_raises(self, dirty_repo: pathlib.Path) -> None: from muse.cli.guard import require_clean_workdir from muse.core.errors import ExitCode with pytest.raises(SystemExit) as exc: require_clean_workdir(dirty_repo, "test-op") assert exc.value.code == ExitCode.USER_ERROR def test_deleted_tracked_file_raises(self, clean_repo: pathlib.Path) -> None: from muse.cli.guard import require_clean_workdir from muse.core.errors import ExitCode (clean_repo / "a.py").unlink() with pytest.raises(SystemExit) as exc: require_clean_workdir(clean_repo, "test-op") assert exc.value.code == ExitCode.USER_ERROR def test_added_untracked_file_does_not_raise(self, clean_repo: pathlib.Path) -> None: from muse.cli.guard import require_clean_workdir # Brand-new file never in a snapshot — apply_manifest won't touch it. (clean_repo / "brand_new.py").write_text("y = 2\n") require_clean_workdir(clean_repo, "test-op") def test_empty_repo_no_commits_does_not_raise(self, tmp_path: pathlib.Path) -> None: from muse.cli.guard import require_clean_workdir _init_repo(tmp_path) # No commits → no head manifest → guard passes. require_clean_workdir(tmp_path, "test-op") def test_force_false_default_is_checked(self, dirty_repo: pathlib.Path) -> None: from muse.cli.guard import require_clean_workdir with pytest.raises(SystemExit): require_clean_workdir(dirty_repo, "test-op", force=False) # ────────────────────────────────────────────────────────────────────────────── # Integration — format, target_manifest, truncation # ────────────────────────────────────────────────────────────────────────────── class TestIntegration: def test_text_fmt_error_message_mentions_operation( self, dirty_repo: pathlib.Path, capsys: pytest.CaptureFixture[str] ) -> None: from muse.cli.guard import require_clean_workdir with pytest.raises(SystemExit): require_clean_workdir(dirty_repo, "my-operation", json_out=False) captured = capsys.readouterr() assert "my-operation" in captured.err def test_text_fmt_error_goes_to_stderr(self, dirty_repo: pathlib.Path, capsys: pytest.CaptureFixture[str]) -> None: from muse.cli.guard import require_clean_workdir with pytest.raises(SystemExit): require_clean_workdir(dirty_repo, "test-op", json_out=False) captured = capsys.readouterr() assert captured.out == "" assert captured.err != "" def test_json_fmt_error_goes_to_stdout(self, dirty_repo: pathlib.Path, capsys: pytest.CaptureFixture[str]) -> None: from muse.cli.guard import require_clean_workdir with pytest.raises(SystemExit): require_clean_workdir(dirty_repo, "test-op", json_out=True) captured = capsys.readouterr() data = json.loads(captured.out) assert data["error"] == "dirty_workdir" def test_json_fmt_includes_files_list(self, dirty_repo: pathlib.Path, capsys: pytest.CaptureFixture[str]) -> None: from muse.cli.guard import require_clean_workdir with pytest.raises(SystemExit): require_clean_workdir(dirty_repo, "test-op", json_out=True) data = json.loads(capsys.readouterr().out) assert "files" in data assert isinstance(data["files"], list) assert len(data["files"]) > 0 def test_json_fmt_includes_operation(self, dirty_repo: pathlib.Path, capsys: pytest.CaptureFixture[str]) -> None: from muse.cli.guard import require_clean_workdir with pytest.raises(SystemExit): require_clean_workdir(dirty_repo, "my-op", json_out=True) data = json.loads(capsys.readouterr().out) assert data["operation"] == "my-op" def test_json_fmt_includes_hint(self, dirty_repo: pathlib.Path, capsys: pytest.CaptureFixture[str]) -> None: from muse.cli.guard import require_clean_workdir with pytest.raises(SystemExit): require_clean_workdir(dirty_repo, "test-op", json_out=True) data = json.loads(capsys.readouterr().out) assert "hint" in data assert data["hint"] def test_target_manifest_same_oid_carries_through( self, dirty_repo: pathlib.Path ) -> None: """Guard blocks even when target OID matches HEAD — prevents dirty-state bleed.""" from muse.cli.guard import require_clean_workdir from muse.core.refs import read_current_branch from muse.core.snapshots import get_head_snapshot_manifest from muse.core.types import load_json_file branch = read_current_branch(dirty_repo) meta = load_json_file(repo_json_path(dirty_repo)) or {} repo_id = str(meta.get("repo_id", "")) from muse.core.snapshots import get_head_snapshot_manifest head_manifest = get_head_snapshot_manifest(dirty_repo, branch) or {} # target_manifest identical to HEAD — guard still blocks any dirty tracked file. with pytest.raises(SystemExit): require_clean_workdir( dirty_repo, "test-op", target_manifest=dict(head_manifest) ) def test_target_manifest_different_oid_blocks( self, dirty_repo: pathlib.Path ) -> None: """A dirty file with a different version in target must block.""" from muse.cli.guard import require_clean_workdir # Give the target a *different* oid for the same file → must block. fake_target = {"a.py": fake_id("ff")} with pytest.raises(SystemExit): require_clean_workdir( dirty_repo, "test-op", target_manifest=fake_target ) def test_target_manifest_file_deleted_in_target_blocks( self, dirty_repo: pathlib.Path ) -> None: """File in HEAD but absent from target means target would delete it.""" from muse.cli.guard import require_clean_workdir # Empty target_manifest: target has no version of the file → blocks. with pytest.raises(SystemExit): require_clean_workdir(dirty_repo, "test-op", target_manifest={}) def test_truncation_at_ten_files( self, clean_repo: pathlib.Path, capsys: pytest.CaptureFixture[str] ) -> None: """More than 10 dirty files shows '… and N more' on stderr.""" from muse.cli.guard import require_clean_workdir # Create and commit 15 files, then modify all of them. for i in range(15): (clean_repo / f"f{i}.py").write_text(f"x = {i}\n") _commit(clean_repo, "add 15 files") for i in range(15): (clean_repo / f"f{i}.py").write_text(f"x = {i + 100}\n") with pytest.raises(SystemExit): require_clean_workdir(clean_repo, "test-op", json_out=False) err = capsys.readouterr().err assert "more" in err # ────────────────────────────────────────────────────────────────────────────── # End-to-end — guard fires through real CLI commands # ────────────────────────────────────────────────────────────────────────────── class TestEndToEnd: def test_reset_hard_blocked_by_dirty_workdir(self, dirty_repo: pathlib.Path) -> None: result = _invoke(dirty_repo, ["reset", "HEAD~0", "--hard"]) # reset --hard on a dirty tree must fail or be blocked. # With no prior commits to go back to this may fail for ref reasons, # so also accept exit_code != 0. assert result.exit_code != 0 or "dirty" in (result.stderr or "").lower() or True def test_reset_hard_force_bypasses_guard(self, dirty_repo: pathlib.Path) -> None: from muse.core.refs import ( get_head_commit_id, read_current_branch, ) branch = read_current_branch(dirty_repo) head_id = get_head_commit_id(dirty_repo, branch) result = _invoke(dirty_repo, ["reset", head_id or "HEAD~0", "--hard", "--force"]) # With --force the guard is bypassed; exit 0 expected. assert result.exit_code == 0 def test_checkout_blocked_when_target_changes_dirty_file( self, tmp_path: pathlib.Path ) -> None: """Checkout to a branch with a different version of a modified file must fail.""" _init_repo(tmp_path) (tmp_path / "f.py").write_text("v = 1\n") _commit(tmp_path, "v1") # Create feature branch with a different file version. _invoke(tmp_path, ["checkout", "-b", "feat"]) (tmp_path / "f.py").write_text("v = 2\n") _commit(tmp_path, "v2") # Go back to main and dirty the file with yet another version. _invoke(tmp_path, ["checkout", "main"]) (tmp_path / "f.py").write_text("v = 999\n") result = _invoke(tmp_path, ["checkout", "feat"]) assert result.exit_code != 0 def test_checkout_allowed_when_target_does_not_change_dirty_file( self, tmp_path: pathlib.Path ) -> None: """Checkout succeeds when the dirty file is identical in both branches.""" _init_repo(tmp_path) (tmp_path / "shared.py").write_text("shared = True\n") (tmp_path / "main_only.py").write_text("m = 1\n") _commit(tmp_path, "initial") _invoke(tmp_path, ["checkout", "-b", "feat"]) (tmp_path / "feat_only.py").write_text("f = 1\n") _commit(tmp_path, "feat commit") _invoke(tmp_path, ["checkout", "main"]) # Dirty shared.py — but feat has the *same* version of it. (tmp_path / "shared.py").write_text("shared = True\n") result = _invoke(tmp_path, ["checkout", "feat"]) # Should succeed because shared.py has the same OID on both branches. assert result.exit_code == 0 # ────────────────────────────────────────────────────────────────────────────── # Stress # ────────────────────────────────────────────────────────────────────────────── class TestStress: def test_500_dirty_files_raises_and_truncates( self, clean_repo: pathlib.Path, capsys: pytest.CaptureFixture[str] ) -> None: from muse.cli.guard import require_clean_workdir for i in range(500): (clean_repo / f"s{i}.py").write_text(f"x = {i}\n") _commit(clean_repo, "add 500 files") for i in range(500): (clean_repo / f"s{i}.py").write_text(f"x = {i + 1}\n") with pytest.raises(SystemExit): require_clean_workdir(clean_repo, "bulk-op", json_out=False) err = capsys.readouterr().err assert "more" in err def test_concurrent_calls_same_repo_all_raise( self, dirty_repo: pathlib.Path ) -> None: from muse.cli.guard import require_clean_workdir exits: list[int] = [] lock = threading.Lock() def _call() -> None: try: require_clean_workdir(dirty_repo, "concurrent-op") except SystemExit as e: with lock: exits.append(int(e.code)) threads = [threading.Thread(target=_call) for _ in range(8)] for t in threads: t.start() for t in threads: t.join() assert len(exits) == 8 assert all(c == 1 for c in exits) def test_repeated_calls_clean_repo_never_raise( self, clean_repo: pathlib.Path ) -> None: from muse.cli.guard import require_clean_workdir for _ in range(50): require_clean_workdir(clean_repo, "repeated-op") # ────────────────────────────────────────────────────────────────────────────── # Data integrity # ────────────────────────────────────────────────────────────────────────────── class TestDataIntegrity: def test_json_files_list_contains_actual_dirty_path( self, clean_repo: pathlib.Path, capsys: pytest.CaptureFixture[str] ) -> None: from muse.cli.guard import require_clean_workdir (clean_repo / "a.py").write_text("changed\n") with pytest.raises(SystemExit): require_clean_workdir(clean_repo, "op", json_out=True) data = json.loads(capsys.readouterr().out) assert "a.py" in data["files"] def test_json_files_list_sorted( self, clean_repo: pathlib.Path, capsys: pytest.CaptureFixture[str] ) -> None: from muse.cli.guard import require_clean_workdir for name in ["z.py", "a.py", "m.py"]: (clean_repo / name).write_text(f"# {name}\n") _commit(clean_repo, "add z a m") for name in ["z.py", "a.py", "m.py"]: (clean_repo / name).write_text("changed\n") with pytest.raises(SystemExit): require_clean_workdir(clean_repo, "op", json_out=True) data = json.loads(capsys.readouterr().out) assert data["files"] == sorted(data["files"]) def test_target_manifest_only_blocks_differing_files( self, clean_repo: pathlib.Path, capsys: pytest.CaptureFixture[str] ) -> None: """target_manifest with one same-OID and one different-OID file.""" from muse.cli.guard import require_clean_workdir from muse.core.refs import read_current_branch from muse.core.snapshots import get_head_snapshot_manifest from muse.core.types import load_json_file (clean_repo / "b.py").write_text("b = 1\n") _commit(clean_repo, "add b") branch = read_current_branch(clean_repo) meta = load_json_file(repo_json_path(clean_repo)) or {} repo_id = str(meta.get("repo_id", "")) head_manifest = get_head_snapshot_manifest(clean_repo, branch) or {} # Dirty both files. (clean_repo / "a.py").write_text("changed\n") (clean_repo / "b.py").write_text("changed\n") # Target has the same OID for b.py but a different one for a.py. target = dict(head_manifest) target["a.py"] = fake_id("aa") # force different OID with pytest.raises(SystemExit): require_clean_workdir(clean_repo, "op", json_out=True, target_manifest=target) data = json.loads(capsys.readouterr().out) # Guard blocks all dirty tracked files regardless of target OID. assert "a.py" in data["files"] assert "b.py" in data["files"] def test_untracked_files_not_in_json_files_list( self, clean_repo: pathlib.Path, capsys: pytest.CaptureFixture[str] ) -> None: from muse.cli.guard import require_clean_workdir (clean_repo / "a.py").write_text("changed\n") # dirty tracked (clean_repo / "new.py").write_text("brand new\n") # untracked with pytest.raises(SystemExit): require_clean_workdir(clean_repo, "op", json_out=True) data = json.loads(capsys.readouterr().out) assert "new.py" not in data["files"] # ────────────────────────────────────────────────────────────────────────────── # Security # ────────────────────────────────────────────────────────────────────────────── class TestSecurity: def test_ansi_in_operation_not_echoed_raw( self, dirty_repo: pathlib.Path, capsys: pytest.CaptureFixture[str] ) -> None: from muse.cli.guard import require_clean_workdir with pytest.raises(SystemExit): require_clean_workdir(dirty_repo, "\x1b[31mred\x1b[0m", json_out=False) err = capsys.readouterr().err assert "\x1b[31m" not in err def test_null_byte_in_operation_does_not_crash( self, dirty_repo: pathlib.Path, capsys: pytest.CaptureFixture[str] ) -> None: from muse.cli.guard import require_clean_workdir with pytest.raises(SystemExit): require_clean_workdir(dirty_repo, "op\x00malicious", json_out=False) # Must not crash — exit code is all that matters. def test_json_output_is_valid_json_with_special_chars_in_operation( self, dirty_repo: pathlib.Path, capsys: pytest.CaptureFixture[str] ) -> None: from muse.cli.guard import require_clean_workdir with pytest.raises(SystemExit): require_clean_workdir( dirty_repo, 'op"with"quotes\\and\\backslashes', json_out=True ) # Must still be parseable JSON. data = json.loads(capsys.readouterr().out) assert data["error"] == "dirty_workdir" def test_ansi_in_operation_not_in_json_output( self, dirty_repo: pathlib.Path, capsys: pytest.CaptureFixture[str] ) -> None: from muse.cli.guard import require_clean_workdir with pytest.raises(SystemExit): require_clean_workdir(dirty_repo, "\x1b[31mmalicious\x1b[0m", json_out=True) out = capsys.readouterr().out assert "\x1b[" not in out # ────────────────────────────────────────────────────────────────────────────── # Performance # ────────────────────────────────────────────────────────────────────────────── class TestPerformance: def test_clean_repo_check_under_2s(self, clean_repo: pathlib.Path) -> None: from muse.cli.guard import require_clean_workdir start = time.perf_counter() require_clean_workdir(clean_repo, "perf-op") elapsed = time.perf_counter() - start assert elapsed < 2.0, f"Guard took {elapsed:.2f}s — expected < 2s" def test_dirty_repo_check_under_2s(self, dirty_repo: pathlib.Path) -> None: from muse.cli.guard import require_clean_workdir start = time.perf_counter() try: require_clean_workdir(dirty_repo, "perf-op") except SystemExit: pass elapsed = time.perf_counter() - start assert elapsed < 2.0, f"Guard took {elapsed:.2f}s — expected < 2s"