cgcardona / muse public
test_cli_show.py python
225 lines 8.0 KB
dfaf1b77 refactor: rename muse-work/ → state/ Gabriel Cardona <gabriel@tellurstori.com> 8h ago
1 """Tests for muse show — inspect a commit's metadata, diff, and files."""
2
3 import json
4 import pathlib
5
6 import pytest
7 from typer.testing import CliRunner
8
9 from muse.cli.app import cli
10
11 runner = CliRunner()
12
13
14 @pytest.fixture
15 def repo(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path:
16 monkeypatch.chdir(tmp_path)
17 monkeypatch.setenv("MUSE_REPO_ROOT", str(tmp_path))
18 result = runner.invoke(cli, ["init"])
19 assert result.exit_code == 0, result.output
20 return tmp_path
21
22
23 def _write(repo: pathlib.Path, filename: str, content: str = "data") -> None:
24 (repo / "state" / filename).write_text(content)
25
26
27 def _commit(msg: str = "initial", **flags: str) -> str:
28 args = ["commit", "-m", msg]
29 for k, v in flags.items():
30 args += [f"--{k}", v]
31 result = runner.invoke(cli, args)
32 assert result.exit_code == 0, result.output
33 # output: "[main abcd1234] msg" → strip trailing ] from token
34 return result.output.split()[1].rstrip("]")
35
36
37 class TestShowHead:
38 def test_shows_commit_id(self, repo: pathlib.Path) -> None:
39 _write(repo, "beat.mid")
40 _commit("initial commit")
41 result = runner.invoke(cli, ["show"])
42 assert result.exit_code == 0, result.output
43 assert "commit" in result.output
44
45 def test_shows_message(self, repo: pathlib.Path) -> None:
46 _write(repo, "beat.mid")
47 _commit("my special message")
48 result = runner.invoke(cli, ["show"])
49 assert result.exit_code == 0
50 assert "my special message" in result.output
51
52 def test_shows_date(self, repo: pathlib.Path) -> None:
53 _write(repo, "beat.mid")
54 _commit("dated commit")
55 result = runner.invoke(cli, ["show"])
56 assert result.exit_code == 0
57 assert "Date:" in result.output
58
59 def test_shows_author(self, repo: pathlib.Path) -> None:
60 _write(repo, "beat.mid")
61 runner.invoke(cli, ["commit", "-m", "authored", "--author", "Gabriel"])
62 result = runner.invoke(cli, ["show"])
63 assert result.exit_code == 0
64 assert "Gabriel" in result.output
65
66 def test_no_author_line_when_empty(self, repo: pathlib.Path) -> None:
67 _write(repo, "beat.mid")
68 _commit("no author")
69 result = runner.invoke(cli, ["show"])
70 assert result.exit_code == 0
71 assert "Author:" not in result.output
72
73
74 class TestShowStat:
75 def test_shows_added_file_by_default(self, repo: pathlib.Path) -> None:
76 _write(repo, "beat.mid")
77 _commit("add beat")
78 result = runner.invoke(cli, ["show"])
79 assert result.exit_code == 0
80 assert "beat.mid" in result.output
81 assert "+" in result.output
82
83 def test_no_stat_flag_hides_files(self, repo: pathlib.Path) -> None:
84 _write(repo, "beat.mid")
85 _commit("add beat")
86 result = runner.invoke(cli, ["show", "--no-stat"])
87 assert result.exit_code == 0
88 assert "beat.mid" not in result.output
89
90 def test_shows_modified_file(self, repo: pathlib.Path) -> None:
91 _write(repo, "beat.mid", "v1")
92 _commit("v1")
93 _write(repo, "beat.mid", "v2")
94 _commit("v2")
95 result = runner.invoke(cli, ["show"])
96 assert result.exit_code == 0
97 assert "beat.mid" in result.output
98
99 def test_file_change_count(self, repo: pathlib.Path) -> None:
100 _write(repo, "a.mid")
101 _write(repo, "b.mid")
102 _commit("two files")
103 result = runner.invoke(cli, ["show"])
104 assert result.exit_code == 0
105 assert "file(s) changed" in result.output
106
107 def test_no_files_changed_no_count_line(self, repo: pathlib.Path) -> None:
108 _write(repo, "beat.mid", "v1")
109 _commit("v1")
110 _write(repo, "beat.mid", "v1")
111 result = runner.invoke(cli, ["commit", "--allow-empty"])
112 # empty commit — stat block should show no files changed
113 result2 = runner.invoke(cli, ["show"])
114 assert result2.exit_code == 0
115
116
117 class TestShowMetadata:
118 def test_shows_section_metadata(self, repo: pathlib.Path) -> None:
119 _write(repo, "beat.mid")
120 runner.invoke(cli, ["commit", "-m", "verse", "--section", "verse"])
121 result = runner.invoke(cli, ["show"])
122 assert result.exit_code == 0
123 assert "section" in result.output
124 assert "verse" in result.output
125
126 def test_shows_track_and_emotion(self, repo: pathlib.Path) -> None:
127 _write(repo, "beat.mid")
128 runner.invoke(cli, ["commit", "-m", "drums", "--track", "drums", "--emotion", "joyful"])
129 result = runner.invoke(cli, ["show"])
130 assert result.exit_code == 0
131 assert "track" in result.output
132 assert "emotion" in result.output
133
134
135 class TestShowRef:
136 def test_show_specific_commit(self, repo: pathlib.Path) -> None:
137 _write(repo, "beat.mid")
138 short = _commit("first")
139 _write(repo, "lead.mid")
140 _commit("second")
141 # show the first commit by prefix
142 result = runner.invoke(cli, ["show", short])
143 assert result.exit_code == 0
144 assert "first" in result.output
145
146 def test_show_unknown_ref_errors(self, repo: pathlib.Path) -> None:
147 _write(repo, "beat.mid")
148 _commit("only")
149 result = runner.invoke(cli, ["show", "deadbeef"])
150 assert result.exit_code != 0
151 assert "not found" in result.output.lower() or "deadbeef" in result.output
152
153 def test_show_no_commits_errors(self, repo: pathlib.Path) -> None:
154 result = runner.invoke(cli, ["show"])
155 assert result.exit_code != 0
156
157
158 class TestShowParent:
159 def test_shows_parent_after_second_commit(self, repo: pathlib.Path) -> None:
160 _write(repo, "beat.mid")
161 _commit("first")
162 _write(repo, "lead.mid")
163 _commit("second")
164 result = runner.invoke(cli, ["show"])
165 assert result.exit_code == 0
166 assert "Parent:" in result.output
167
168 def test_root_commit_has_no_parent_line(self, repo: pathlib.Path) -> None:
169 _write(repo, "beat.mid")
170 short = _commit("root commit")
171 result = runner.invoke(cli, ["show", short])
172 assert result.exit_code == 0
173 assert "Parent:" not in result.output
174
175
176 class TestShowJson:
177 def test_json_output_is_valid(self, repo: pathlib.Path) -> None:
178 _write(repo, "beat.mid")
179 _commit("json test")
180 result = runner.invoke(cli, ["show", "--json"])
181 assert result.exit_code == 0
182 data = json.loads(result.output)
183 assert "commit_id" in data
184 assert "message" in data
185
186 def test_json_contains_message(self, repo: pathlib.Path) -> None:
187 _write(repo, "beat.mid")
188 _commit("the message")
189 result = runner.invoke(cli, ["show", "--json"])
190 data = json.loads(result.output)
191 assert data["message"] == "the message"
192
193 def test_json_with_stat_includes_file_lists(self, repo: pathlib.Path) -> None:
194 _write(repo, "beat.mid")
195 _commit("add beat")
196 result = runner.invoke(cli, ["show", "--json", "--stat"])
197 data = json.loads(result.output)
198 assert "files_added" in data
199 assert "beat.mid" in data["files_added"]
200
201 def test_json_no_stat_excludes_file_lists(self, repo: pathlib.Path) -> None:
202 _write(repo, "beat.mid")
203 _commit("add beat")
204 result = runner.invoke(cli, ["show", "--json", "--no-stat"])
205 data = json.loads(result.output)
206 assert "files_added" not in data
207
208 def test_json_stat_shows_removed_file(self, repo: pathlib.Path) -> None:
209 _write(repo, "beat.mid", "v1")
210 _commit("add")
211 (repo / "state" / "beat.mid").unlink()
212 _write(repo, "lead.mid", "new")
213 _commit("swap")
214 result = runner.invoke(cli, ["show", "--json", "--stat"])
215 data = json.loads(result.output)
216 assert "beat.mid" in data["files_removed"]
217
218 def test_json_stat_shows_modified_file(self, repo: pathlib.Path) -> None:
219 _write(repo, "beat.mid", "v1")
220 _commit("v1")
221 _write(repo, "beat.mid", "v2")
222 _commit("v2")
223 result = runner.invoke(cli, ["show", "--json", "--stat"])
224 data = json.loads(result.output)
225 assert "beat.mid" in data["files_modified"]