gabriel / muse public
test_cmd_show.py python
218 lines 9.1 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 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 typer.testing import CliRunner
20
21 from muse.cli.app import cli
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", {})