gabriel / muse public
test_cmd_cherry_pick.py python
164 lines 6.9 KB
95b86799 feat: add --format json to all porcelain commands for agent-first output Gabriel Cardona <gabriel@tellurstori.com> 2d ago
1 """Comprehensive tests for ``muse cherry-pick``.
2
3 Covers:
4 - E2E: cherry-pick a specific commit onto current branch
5 - Integration: commit is replayed, creates new commit
6 - Security: sanitized output for conflict paths
7 - Stress: cherry-pick many commits
8 """
9
10 from __future__ import annotations
11
12 import datetime
13 import json
14 import pathlib
15 import uuid
16
17 import pytest
18 from typer.testing import CliRunner
19
20 from muse.cli.app import cli
21
22 runner = CliRunner()
23
24
25 # ---------------------------------------------------------------------------
26 # Shared helpers
27 # ---------------------------------------------------------------------------
28
29 def _env(root: pathlib.Path) -> dict[str, str]:
30 return {"MUSE_REPO_ROOT": str(root)}
31
32
33 def _init_repo(tmp_path: pathlib.Path) -> tuple[pathlib.Path, str]:
34 muse_dir = tmp_path / ".muse"
35 muse_dir.mkdir()
36 repo_id = str(uuid.uuid4())
37 (muse_dir / "repo.json").write_text(json.dumps({
38 "repo_id": repo_id,
39 "domain": "midi",
40 "default_branch": "main",
41 "created_at": "2025-01-01T00:00:00+00:00",
42 }), encoding="utf-8")
43 (muse_dir / "HEAD").write_text("ref: refs/heads/main", encoding="utf-8")
44 (muse_dir / "refs" / "heads").mkdir(parents=True)
45 (muse_dir / "snapshots").mkdir()
46 (muse_dir / "commits").mkdir()
47 (muse_dir / "objects").mkdir()
48 return tmp_path, repo_id
49
50
51 def _make_commit(root: pathlib.Path, repo_id: str, branch: str = "main",
52 message: str = "test",
53 manifest: dict[str, str] | None = None) -> str:
54 from muse.core.store import CommitRecord, SnapshotRecord, write_commit, write_snapshot
55 from muse.core.snapshot import compute_snapshot_id, compute_commit_id
56
57 ref_file = root / ".muse" / "refs" / "heads" / branch
58 parent_id = ref_file.read_text().strip() if ref_file.exists() else None
59 m = manifest or {}
60 snap_id = compute_snapshot_id(m)
61 committed_at = datetime.datetime.now(datetime.timezone.utc)
62 commit_id = compute_commit_id(
63 parent_ids=[parent_id] if parent_id else [],
64 snapshot_id=snap_id, message=message,
65 committed_at_iso=committed_at.isoformat(),
66 )
67 write_snapshot(root, SnapshotRecord(snapshot_id=snap_id, manifest=m))
68 write_commit(root, CommitRecord(
69 commit_id=commit_id, repo_id=repo_id, branch=branch,
70 snapshot_id=snap_id, message=message, committed_at=committed_at,
71 parent_commit_id=parent_id,
72 ))
73 ref_file.parent.mkdir(parents=True, exist_ok=True)
74 ref_file.write_text(commit_id, encoding="utf-8")
75 return commit_id
76
77
78 def _write_object(root: pathlib.Path, content: bytes) -> str:
79 import hashlib
80 obj_id = hashlib.sha256(content).hexdigest()
81 obj_path = root / ".muse" / "objects" / obj_id[:2] / obj_id[2:]
82 obj_path.parent.mkdir(parents=True, exist_ok=True)
83 obj_path.write_bytes(content)
84 return obj_id
85
86
87 # ---------------------------------------------------------------------------
88 # Tests
89 # ---------------------------------------------------------------------------
90
91 class TestCherryPickCLI:
92 def test_cherry_pick_commit_from_another_branch(self, tmp_path: pathlib.Path) -> None:
93 root, repo_id = _init_repo(tmp_path)
94 base = _make_commit(root, repo_id, branch="main", message="base")
95 (root / ".muse" / "refs" / "heads" / "feature").write_text(base)
96 obj = _write_object(root, b"feature content")
97 feature_commit = _make_commit(root, repo_id, branch="feature",
98 message="feature work",
99 manifest={"new.mid": obj})
100 result = runner.invoke(
101 cli, ["cherry-pick", feature_commit], env=_env(root), catch_exceptions=False
102 )
103 assert result.exit_code == 0
104
105 def test_cherry_pick_invalid_commit_fails(self, tmp_path: pathlib.Path) -> None:
106 root, repo_id = _init_repo(tmp_path)
107 _make_commit(root, repo_id)
108 result = runner.invoke(cli, ["cherry-pick", "deadbeef" * 8], env=_env(root))
109 assert result.exit_code != 0
110
111 def test_cherry_pick_creates_new_commit(self, tmp_path: pathlib.Path) -> None:
112 root, repo_id = _init_repo(tmp_path)
113 base = _make_commit(root, repo_id, branch="main", message="base")
114 (root / ".muse" / "refs" / "heads" / "feature").write_text(base)
115 obj = _write_object(root, b"cherry content")
116 feature_commit = _make_commit(root, repo_id, branch="feature",
117 message="cherry", manifest={"c.mid": obj})
118 original_head = (root / ".muse" / "refs" / "heads" / "main").read_text().strip()
119 runner.invoke(cli, ["cherry-pick", feature_commit], env=_env(root), catch_exceptions=False)
120 new_head = (root / ".muse" / "refs" / "heads" / "main").read_text().strip()
121 assert new_head != original_head
122
123 def test_cherry_pick_format_json(self, tmp_path: pathlib.Path) -> None:
124 root, repo_id = _init_repo(tmp_path)
125 base = _make_commit(root, repo_id, branch="main", message="base")
126 (root / ".muse" / "refs" / "heads" / "feature").write_text(base)
127 obj = _write_object(root, b"json pick")
128 feature_commit = _make_commit(root, repo_id, branch="feature",
129 message="json", manifest={"j.mid": obj})
130 result = runner.invoke(
131 cli, ["cherry-pick", "--format", "json", feature_commit],
132 env=_env(root), catch_exceptions=False
133 )
134 assert result.exit_code == 0
135 data = json.loads(result.output)
136 assert isinstance(data, dict)
137
138 def test_cherry_pick_output_sanitized(self, tmp_path: pathlib.Path) -> None:
139 root, repo_id = _init_repo(tmp_path)
140 base = _make_commit(root, repo_id, branch="main", message="base")
141 (root / ".muse" / "refs" / "heads" / "feature").write_text(base)
142 obj = _write_object(root, b"safe content")
143 feature_commit = _make_commit(root, repo_id, branch="feature",
144 message="safe", manifest={"s.mid": obj})
145 result = runner.invoke(cli, ["cherry-pick", feature_commit], env=_env(root), catch_exceptions=False)
146 assert "\x1b" not in result.output
147
148
149 class TestCherryPickStress:
150 def test_cherry_pick_sequence(self, tmp_path: pathlib.Path) -> None:
151 root, repo_id = _init_repo(tmp_path)
152 base = _make_commit(root, repo_id, branch="main", message="base")
153 (root / ".muse" / "refs" / "heads" / "feature").write_text(base)
154 commits = []
155 for i in range(5):
156 obj = _write_object(root, f"content {i}".encode())
157 c = _make_commit(root, repo_id, branch="feature",
158 message=f"commit {i}", manifest={f"f{i}.mid": obj})
159 commits.append(c)
160 for commit_id in commits:
161 result = runner.invoke(
162 cli, ["cherry-pick", commit_id], env=_env(root), catch_exceptions=False
163 )
164 assert result.exit_code == 0