"""Phase 6 of #185 (musehub staging): full dry-run acceptance test. Ties every prior phase together in one flow, against a REAL scratch muse repo under tmp_path (never ~/ecosystem/muse): refresh a sandbox (which itself takes a snapshot backup first), corrupt the sandbox on purpose, confirm canonical `muse verify` stays green throughout, then exercise both restore paths (snapshot and bundle) successfully against a separately corrupted canonical. """ import json import shutil import subprocess import sys from pathlib import Path import pytest sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts" / "dev")) from backup import create_bundle_backup, list_snapshots, restore_from_bundle, restore_from_snapshot # noqa: E402 from sandbox import refresh_sandbox # noqa: E402 MUSE = shutil.which("muse") def _run(args: list[str], cwd: Path) -> subprocess.CompletedProcess: return subprocess.run([MUSE, *args], cwd=cwd, capture_output=True, text=True, check=True) def _make_real_repo(root: Path, *, commits: int = 3) -> None: root.mkdir(parents=True, exist_ok=True) _run(["init"], cwd=root) for i in range(commits): (root / f"file{i}.txt").write_text(f"content {i}\n") _run(["code", "add", "."], cwd=root) _run(["commit", "-m", f"commit {i}"], cwd=root) def _rev_parse(root: Path, ref: str) -> str: return json.loads(_run(["rev-parse", ref, "--json"], cwd=root).stdout)["commit_id"] def _verify_all_ok(root: Path) -> bool: proc = subprocess.run([MUSE, "verify", "--json"], cwd=root, capture_output=True, text=True) return json.loads(proc.stdout).get("all_ok", False) def _corrupt_an_object(root: Path) -> None: objects_root = root / ".muse" / "objects" / "sha256" for shard in objects_root.iterdir(): for obj in shard.iterdir(): if obj.is_file(): obj.chmod(0o644) obj.write_text("CORRUPTED-FOR-TEST") return raise AssertionError("no object found to corrupt") @pytest.fixture(autouse=True) def _require_muse(): if MUSE is None: pytest.skip("muse not installed on this machine yet (Phase 2 not applied)") class TestFullPipelineAcceptance: def test_sandbox_corruption_never_reaches_canonical_and_both_restores_work( self, tmp_path: Path, ) -> None: canonical = tmp_path / "ecosystem" / "muse" sandbox_base = tmp_path / "sandboxes" backup_base = tmp_path / "backups" _make_real_repo(canonical) good_main = _rev_parse(canonical, "main") # ── Step 1: refresh a sandbox — this must also produce a snapshot backup. sandbox_path = refresh_sandbox( "muse", canonical_root=canonical, sandbox_base=sandbox_base, backup_base=backup_base, ) snaps_after_refresh = list_snapshots("muse", backup_base=backup_base) assert len(snaps_after_refresh) == 1, "sandbox-refresh must snapshot canonical first" # ── Step 2: corrupt the sandbox on purpose. _corrupt_an_object(sandbox_path) # ── Step 3: canonical must stay green throughout. assert _verify_all_ok(canonical) is True assert _rev_parse(canonical, "main") == good_main # ── Step 4: take a real bundle backup of (still-healthy) canonical. bundle_path = create_bundle_backup("muse", canonical_root=canonical, backup_base=backup_base) # ── Step 5: corrupt canonical itself now, and restore via snapshot. _corrupt_an_object(canonical) assert _verify_all_ok(canonical) is False snap_name = snaps_after_refresh[0].name restore_from_snapshot("muse", snap_name, canonical_root=canonical, backup_base=backup_base, force=True) assert _verify_all_ok(canonical) is True assert _rev_parse(canonical, "main") == good_main # ── Step 6: corrupt canonical again, restore via bundle this time. _corrupt_an_object(canonical) assert _verify_all_ok(canonical) is False restore_from_bundle( "muse", bundle_path.name, canonical_root=canonical, backup_base=backup_base, force=True, ) assert _verify_all_ok(canonical) is True assert _rev_parse(canonical, "main") == good_main # ── Step 7: the sandbox's earlier corruption was never visible to # canonical at any point in this whole flow — re-confirm explicitly. assert (sandbox_path / ".muse").exists() sandbox_verify = subprocess.run( [MUSE, "verify", "--json"], cwd=sandbox_path, capture_output=True, text=True, ) # The sandbox itself may or may not still fail verify (it was # deliberately corrupted and never repaired) — what matters is # canonical was independent of it throughout. assert _verify_all_ok(canonical) is True