gabriel / muse public
test_cmd_show.py python
218 lines 9.1 KB
86000da9 fix: replace typer CliRunner with argparse-compatible test helper Gabriel Cardona <gabriel@tellurstori.com> 1d ago
1 """Comprehensive tests for ``muse show``.
2
3 Covers:
4 - Unit: _format_op for each DomainOp type
5 - Integration: show a commit in text and JSON
6 - E2E: full CLI round-trip
7 - Security: sanitize_display applied to message/author/metadata
8 - Stress: commits with large metadata sets
9 """
10
11 from __future__ import annotations
12
13 import datetime
14 import json
15 import pathlib
16 import uuid
17
18 import pytest
19 from tests.cli_test_helper import CliRunner
20
21 cli = None # argparse migration — CliRunner ignores this arg
22
23 runner = CliRunner()
24
25
26 # ---------------------------------------------------------------------------
27 # Helpers
28 # ---------------------------------------------------------------------------
29
30 def _env(root: pathlib.Path) -> dict[str, str]:
31 return {"MUSE_REPO_ROOT": str(root)}
32
33
34 def _init_repo(tmp_path: pathlib.Path) -> tuple[pathlib.Path, str]:
35 muse_dir = tmp_path / ".muse"
36 muse_dir.mkdir()
37 repo_id = str(uuid.uuid4())
38 (muse_dir / "repo.json").write_text(json.dumps({
39 "repo_id": repo_id,
40 "domain": "midi",
41 "default_branch": "main",
42 "created_at": "2025-01-01T00:00:00+00:00",
43 }), encoding="utf-8")
44 (muse_dir / "HEAD").write_text("ref: refs/heads/main", encoding="utf-8")
45 (muse_dir / "refs" / "heads").mkdir(parents=True)
46 (muse_dir / "snapshots").mkdir()
47 (muse_dir / "commits").mkdir()
48 (muse_dir / "objects").mkdir()
49 return tmp_path, repo_id
50
51
52 def _make_commit(
53 root: pathlib.Path, repo_id: str, message: str = "initial commit",
54 author: str = "Alice", metadata: dict[str, str] | None = None,
55 ) -> str:
56 from muse.core.store import CommitRecord, SnapshotRecord, write_commit, write_snapshot
57 from muse.core.snapshot import compute_snapshot_id, compute_commit_id
58
59 ref_file = root / ".muse" / "refs" / "heads" / "main"
60 parent_id = ref_file.read_text().strip() if ref_file.exists() else None
61 manifest: dict[str, str] = {}
62 snap_id = compute_snapshot_id(manifest)
63 committed_at = datetime.datetime.now(datetime.timezone.utc)
64 commit_id = compute_commit_id(
65 parent_ids=[parent_id] if parent_id else [],
66 snapshot_id=snap_id, message=message,
67 committed_at_iso=committed_at.isoformat(),
68 )
69 write_snapshot(root, SnapshotRecord(snapshot_id=snap_id, manifest=manifest))
70 write_commit(root, CommitRecord(
71 commit_id=commit_id, repo_id=repo_id, branch="main",
72 snapshot_id=snap_id, message=message, committed_at=committed_at,
73 parent_commit_id=parent_id, author=author, metadata=metadata or {},
74 ))
75 ref_file.parent.mkdir(parents=True, exist_ok=True)
76 ref_file.write_text(commit_id, encoding="utf-8")
77 return commit_id
78
79
80 # ---------------------------------------------------------------------------
81 # Unit tests
82 # ---------------------------------------------------------------------------
83
84 class TestShowUnit:
85 def test_format_op_insert(self) -> None:
86 from muse.cli.commands.show import _format_op
87 from muse.domain import InsertOp
88 op: InsertOp = {"op": "insert", "address": "track/1",
89 "position": None, "content_id": "abc", "content_summary": "new"}
90 lines = _format_op(op)
91 assert any("A" in l and "track/1" in l for l in lines)
92
93 def test_format_op_delete(self) -> None:
94 from muse.cli.commands.show import _format_op
95 from muse.domain import DeleteOp
96 op: DeleteOp = {"op": "delete", "address": "track/2",
97 "position": None, "content_id": "abc", "content_summary": "old"}
98 lines = _format_op(op)
99 assert any("D" in l and "track/2" in l for l in lines)
100
101 def test_format_op_replace(self) -> None:
102 from muse.cli.commands.show import _format_op
103 from muse.domain import ReplaceOp
104 op: ReplaceOp = {"op": "replace", "address": "track/3",
105 "old_content_id": "abc", "new_content_id": "def",
106 "old_summary": "old", "new_summary": "new"}
107 lines = _format_op(op)
108 assert any("M" in l for l in lines)
109
110
111 # ---------------------------------------------------------------------------
112 # Integration tests
113 # ---------------------------------------------------------------------------
114
115 class TestShowIntegration:
116 def test_show_head_commit(self, tmp_path: pathlib.Path) -> None:
117 root, repo_id = _init_repo(tmp_path)
118 commit_id = _make_commit(root, repo_id, message="Hello show")
119 result = runner.invoke(cli, ["show"], env=_env(root), catch_exceptions=False)
120 assert result.exit_code == 0
121 assert commit_id in result.output
122 assert "Hello show" in result.output
123
124 def test_show_json_output(self, tmp_path: pathlib.Path) -> None:
125 root, repo_id = _init_repo(tmp_path)
126 commit_id = _make_commit(root, repo_id, message="json test")
127 result = runner.invoke(cli, ["show", "--json"], env=_env(root), catch_exceptions=False)
128 assert result.exit_code == 0
129 data = json.loads(result.output)
130 assert data["commit_id"] == commit_id
131 assert data["message"] == "json test"
132
133 def test_show_specific_commit_by_sha(self, tmp_path: pathlib.Path) -> None:
134 root, repo_id = _init_repo(tmp_path)
135 commit_id = _make_commit(root, repo_id, message="specific")
136 result = runner.invoke(cli, ["show", commit_id[:12]], env=_env(root), catch_exceptions=False)
137 assert result.exit_code == 0
138 assert "specific" in result.output
139
140 def test_show_nonexistent_commit_fails(self, tmp_path: pathlib.Path) -> None:
141 root, repo_id = _init_repo(tmp_path)
142 _make_commit(root, repo_id)
143 result = runner.invoke(cli, ["show", "deadbeef"], env=_env(root))
144 assert result.exit_code != 0
145 assert "not found" in result.output
146
147 def test_show_no_stat(self, tmp_path: pathlib.Path) -> None:
148 root, repo_id = _init_repo(tmp_path)
149 _make_commit(root, repo_id, message="no stat")
150 result = runner.invoke(cli, ["show", "--no-stat"], env=_env(root), catch_exceptions=False)
151 assert result.exit_code == 0
152 assert "no stat" in result.output
153
154 def test_show_author_in_output(self, tmp_path: pathlib.Path) -> None:
155 root, repo_id = _init_repo(tmp_path)
156 _make_commit(root, repo_id, author="Bob Smith")
157 result = runner.invoke(cli, ["show"], env=_env(root), catch_exceptions=False)
158 assert "Bob Smith" in result.output
159
160 def test_show_metadata_in_output(self, tmp_path: pathlib.Path) -> None:
161 root, repo_id = _init_repo(tmp_path)
162 _make_commit(root, repo_id, metadata={"section": "chorus", "key": "Am"})
163 result = runner.invoke(cli, ["show"], env=_env(root), catch_exceptions=False)
164 assert "section" in result.output
165 assert "chorus" in result.output
166
167
168 # ---------------------------------------------------------------------------
169 # Security tests
170 # ---------------------------------------------------------------------------
171
172 class TestShowSecurity:
173 def test_commit_message_with_ansi_escaped(self, tmp_path: pathlib.Path) -> None:
174 root, repo_id = _init_repo(tmp_path)
175 malicious = "Hello \x1b[31mRED\x1b[0m world"
176 _make_commit(root, repo_id, message=malicious)
177 result = runner.invoke(cli, ["show"], env=_env(root), catch_exceptions=False)
178 assert result.exit_code == 0
179 assert "\x1b" not in result.output
180
181 def test_author_with_control_chars_escaped(self, tmp_path: pathlib.Path) -> None:
182 root, repo_id = _init_repo(tmp_path)
183 _make_commit(root, repo_id, author="Alice\x1b[0m\x00Bob")
184 result = runner.invoke(cli, ["show"], env=_env(root), catch_exceptions=False)
185 assert result.exit_code == 0
186 assert "\x1b" not in result.output
187 assert "\x00" not in result.output
188
189 def test_metadata_with_control_chars_escaped(self, tmp_path: pathlib.Path) -> None:
190 root, repo_id = _init_repo(tmp_path)
191 _make_commit(root, repo_id, metadata={"key\x1b[31m": "val\x00ue"})
192 result = runner.invoke(cli, ["show"], env=_env(root), catch_exceptions=False)
193 assert result.exit_code == 0
194 assert "\x1b" not in result.output
195
196
197 # ---------------------------------------------------------------------------
198 # Stress tests
199 # ---------------------------------------------------------------------------
200
201 class TestShowStress:
202 def test_show_commit_with_large_metadata(self, tmp_path: pathlib.Path) -> None:
203 root, repo_id = _init_repo(tmp_path)
204 large_meta = {f"key_{i}": f"value_{i}" for i in range(200)}
205 _make_commit(root, repo_id, metadata=large_meta)
206 result = runner.invoke(cli, ["show"], env=_env(root), catch_exceptions=False)
207 assert result.exit_code == 0
208 assert "key_0" in result.output
209 assert "key_199" in result.output
210
211 def test_show_json_with_large_metadata(self, tmp_path: pathlib.Path) -> None:
212 root, repo_id = _init_repo(tmp_path)
213 large_meta = {f"k{i}": f"v{i}" for i in range(100)}
214 _make_commit(root, repo_id, metadata=large_meta)
215 result = runner.invoke(cli, ["show", "--json"], env=_env(root), catch_exceptions=False)
216 assert result.exit_code == 0
217 data = json.loads(result.output)
218 assert "k99" in data.get("metadata", {})