gabriel / muse public
test_reset_supercharge.py python
311 lines 13.6 KB
Raw
sha256:51ce277f663e01a43eaffbe77509b1de7ac2d4251b55d23306304bcdeb92c90d feat(pack): delta-encode snapshots in MPackBundle wire format Sonnet 4.6 minor ⚠ breaking 121 days ago
1 """Supercharge tests for ``muse reset``.
2
3 Coverage tiers
4 --------------
5 Unit — TypedDict shape, alias registration, docstring completeness.
6 Integration — ``-j`` alias, ``-n`` alias, ``exit_code``/``duration_ms`` in JSON,
7 ``schema_version`` field, dry-run JSON envelope, applied JSON envelope.
8 Security — null byte / ANSI injection in ref and format args.
9 """
10
11 from __future__ import annotations
12
13 import argparse
14 import json
15 import os
16 import pathlib
17
18 import pytest
19
20 from tests.cli_test_helper import CliRunner, InvokeResult
21
22 runner = CliRunner()
23
24
25 # ──────────────────────────────────────────────────────────────────────────────
26 # Helpers
27 # ──────────────────────────────────────────────────────────────────────────────
28
29
30 def _invoke(repo: pathlib.Path, args: list[str]) -> InvokeResult:
31 saved = os.getcwd()
32 try:
33 os.chdir(repo)
34 return runner.invoke(None, args)
35 finally:
36 os.chdir(saved)
37
38
39 def _reset(repo: pathlib.Path, *extra: str) -> InvokeResult:
40 return _invoke(repo, ["reset", *extra])
41
42
43 def _commit(repo: pathlib.Path, message: str) -> str:
44 import re
45
46 result = _invoke(repo, ["commit", "-m", message])
47 m = re.search(r"sha256:[0-9a-f]{64}", result.output + (result.stderr or ""))
48 return m.group(0) if m else ""
49
50
51 @pytest.fixture()
52 def repo(tmp_path: pathlib.Path) -> pathlib.Path:
53 saved = os.getcwd()
54 try:
55 os.chdir(tmp_path)
56 runner.invoke(None, ["init"])
57 finally:
58 os.chdir(saved)
59 (tmp_path / "a.py").write_text("x = 1\n")
60 _commit(tmp_path, "initial")
61 (tmp_path / "b.py").write_text("y = 2\n")
62 _commit(tmp_path, "add b")
63 return tmp_path
64
65
66 @pytest.fixture()
67 def c1_id(repo: pathlib.Path) -> str:
68 """Full commit ID of the first commit (HEAD~1)."""
69 from muse.core.store import get_head_commit_id, read_commit
70
71 head_id = get_head_commit_id(repo, "main") or ""
72 head = read_commit(repo, head_id)
73 return (head.parent_commit_id or "") if head else ""
74
75
76 # ──────────────────────────────────────────────────────────────────────────────
77 # Unit — TypedDict
78 # ──────────────────────────────────────────────────────────────────────────────
79
80
81 class TestTypedDict:
82 def test_reset_json_typed_dict_exists(self) -> None:
83 from muse.cli.commands.reset import _ResetJson # noqa: F401
84
85 def test_reset_json_typed_dict_has_exit_code(self) -> None:
86 from muse.cli.commands.reset import _ResetJson
87 import typing
88
89 hints = typing.get_type_hints(_ResetJson)
90 assert "exit_code" in hints, "exit_code missing from _ResetJson"
91
92 def test_reset_json_typed_dict_has_duration_ms(self) -> None:
93 from muse.cli.commands.reset import _ResetJson
94 import typing
95
96 hints = typing.get_type_hints(_ResetJson)
97 assert "duration_ms" in hints, "duration_ms missing from _ResetJson"
98
99 def test_reset_json_typed_dict_has_schema_version(self) -> None:
100 from muse.cli.commands.reset import _ResetJson
101 import typing
102
103 hints = typing.get_type_hints(_ResetJson)
104 assert "schema" in hints, "schema_version missing from _ResetJson"
105
106 def test_reset_json_typed_dict_has_all_core_fields(self) -> None:
107 from muse.cli.commands.reset import _ResetJson
108 import typing
109
110 hints = typing.get_type_hints(_ResetJson)
111 required = {"branch", "ref", "old_commit_id", "new_commit_id", "snapshot_id", "mode", "dry_run"}
112 missing = required - set(hints)
113 assert not missing, f"Missing fields in _ResetJson: {missing}"
114
115
116 # ──────────────────────────────────────────────────────────────────────────────
117 # Unit — alias registration
118 # ──────────────────────────────────────────────────────────────────────────────
119
120
121 class TestAliasRegistration:
122 def _make_parser(self) -> "argparse.ArgumentParser":
123 import argparse
124 from muse.cli.commands.reset import register
125
126 p = argparse.ArgumentParser()
127 sub = p.add_subparsers()
128 register(sub)
129 return p
130
131 def test_j_alias_sets_json_fmt(self) -> None:
132 p = self._make_parser()
133 ns = p.parse_args(["reset", "HEAD~1", "-j"])
134 assert ns.json_out is True
135
136 def test_n_alias_sets_dry_run(self) -> None:
137 p = self._make_parser()
138 ns = p.parse_args(["reset", "HEAD~1", "-n"])
139 assert ns.dry_run is True
140
141 def test_j_and_n_together(self) -> None:
142 p = self._make_parser()
143 ns = p.parse_args(["reset", "HEAD~1", "-j", "-n"])
144 assert ns.json_out is True
145 assert ns.dry_run is True
146
147
148 # ──────────────────────────────────────────────────────────────────────────────
149 # Integration — -j alias produces identical output to --json
150 # ──────────────────────────────────────────────────────────────────────────────
151
152
153 class TestJsonAlias:
154 def test_j_alias_exit_code_zero(self, repo: pathlib.Path, c1_id: str) -> None:
155 result = _reset(repo, c1_id, "-j")
156 assert result.exit_code == 0
157
158 def test_j_alias_output_is_valid_json(self, repo: pathlib.Path, c1_id: str) -> None:
159 result = _reset(repo, c1_id, "-j")
160 data = json.loads(result.output)
161 assert isinstance(data, dict)
162
163 def test_j_alias_same_keys_as_json_flag(self, repo: pathlib.Path, c1_id: str) -> None:
164 # Use dry-run so neither call actually moves HEAD; both see same state.
165 r_json = _reset(repo, c1_id, "--json", "--dry-run")
166 r_j = _reset(repo, c1_id, "-j", "--dry-run")
167 assert set(json.loads(r_json.output)) == set(json.loads(r_j.output))
168
169
170 # ──────────────────────────────────────────────────────────────────────────────
171 # Integration — -n alias for --dry-run
172 # ──────────────────────────────────────────────────────────────────────────────
173
174
175 class TestDryRunAlias:
176 def test_n_alias_no_write(self, repo: pathlib.Path, c1_id: str) -> None:
177 from muse.core.store import get_head_commit_id
178
179 before = get_head_commit_id(repo, "main")
180 _reset(repo, c1_id, "-n")
181 after = get_head_commit_id(repo, "main")
182 assert before == after, "-n should not advance HEAD"
183
184 def test_n_alias_json_dry_run_true(self, repo: pathlib.Path, c1_id: str) -> None:
185 result = _reset(repo, c1_id, "-n", "-j")
186 data = json.loads(result.output)
187 assert data["dry_run"] is True
188
189 def test_n_alias_exit_code_zero(self, repo: pathlib.Path, c1_id: str) -> None:
190 result = _reset(repo, c1_id, "-n")
191 assert result.exit_code == 0
192
193
194 # ──────────────────────────────────────────────────────────────────────────────
195 # Integration — JSON envelope completeness
196 # ──────────────────────────────────────────────────────────────────────────────
197
198
199 class TestJsonEnvelope:
200 def test_applied_json_has_exit_code(self, repo: pathlib.Path, c1_id: str) -> None:
201 result = _reset(repo, c1_id, "--json")
202 data = json.loads(result.output)
203 assert "exit_code" in data
204
205 def test_applied_json_exit_code_is_zero(self, repo: pathlib.Path, c1_id: str) -> None:
206 result = _reset(repo, c1_id, "--json")
207 data = json.loads(result.output)
208 assert data["exit_code"] == 0
209
210 def test_applied_json_has_duration_ms(self, repo: pathlib.Path, c1_id: str) -> None:
211 result = _reset(repo, c1_id, "--json")
212 data = json.loads(result.output)
213 assert "duration_ms" in data
214
215 def test_applied_json_duration_ms_is_float(self, repo: pathlib.Path, c1_id: str) -> None:
216 result = _reset(repo, c1_id, "--json")
217 data = json.loads(result.output)
218 assert isinstance(data["duration_ms"], float)
219 assert data["duration_ms"] >= 0.0
220
221 def test_applied_json_has_schema_version(self, repo: pathlib.Path, c1_id: str) -> None:
222 result = _reset(repo, c1_id, "--json")
223 data = json.loads(result.output)
224 assert "schema" in data
225
226 def test_applied_json_schema_version_is_string(self, repo: pathlib.Path, c1_id: str) -> None:
227 result = _reset(repo, c1_id, "--json")
228 data = json.loads(result.output)
229 assert isinstance(data["schema"], int)
230 assert data["schema"] > 0
231
232 def test_dry_run_json_has_exit_code(self, repo: pathlib.Path, c1_id: str) -> None:
233 result = _reset(repo, c1_id, "--json", "--dry-run")
234 data = json.loads(result.output)
235 assert "exit_code" in data
236
237 def test_dry_run_json_exit_code_is_zero(self, repo: pathlib.Path, c1_id: str) -> None:
238 result = _reset(repo, c1_id, "--json", "--dry-run")
239 data = json.loads(result.output)
240 assert data["exit_code"] == 0
241
242 def test_dry_run_json_has_duration_ms(self, repo: pathlib.Path, c1_id: str) -> None:
243 result = _reset(repo, c1_id, "--json", "--dry-run")
244 data = json.loads(result.output)
245 assert "duration_ms" in data
246
247 def test_dry_run_json_duration_ms_is_float(self, repo: pathlib.Path, c1_id: str) -> None:
248 result = _reset(repo, c1_id, "--json", "--dry-run")
249 data = json.loads(result.output)
250 assert isinstance(data["duration_ms"], float)
251 assert data["duration_ms"] >= 0.0
252
253 def test_dry_run_json_has_schema_version(self, repo: pathlib.Path, c1_id: str) -> None:
254 result = _reset(repo, c1_id, "--json", "--dry-run")
255 data = json.loads(result.output)
256 assert "schema" in data
257
258
259 # ──────────────────────────────────────────────────────────────────────────────
260 # Security — input sanitization
261 # ──────────────────────────────────────────────────────────────────────────────
262
263
264 class TestSecurity:
265 def test_null_byte_in_ref_does_not_crash(self, repo: pathlib.Path) -> None:
266 result = _reset(repo, "HEAD\x00malicious")
267 assert result.exit_code != 0
268
269 def test_ansi_in_ref_not_echoed_raw(self, repo: pathlib.Path) -> None:
270 result = _reset(repo, "\x1b[31mred\x1b[0m")
271 combined = result.output + (result.stderr or "")
272 assert "\x1b[31m" not in combined
273
274 def test_null_byte_in_format_does_not_crash(self, repo: pathlib.Path) -> None:
275 result = _reset(repo, "HEAD~1", "--format", "json\x00malicious")
276 assert result.exit_code != 0
277
278 def test_ansi_in_format_not_echoed_raw(self, repo: pathlib.Path) -> None:
279 result = _reset(repo, "HEAD~1", "--format", "\x1b[31mred\x1b[0m")
280 combined = result.output + (result.stderr or "")
281 assert "\x1b[31m" not in combined
282
283
284 # ──────────────────────────────────────────────────────────────────────────────
285 # Unit — docstrings
286 # ──────────────────────────────────────────────────────────────────────────────
287
288
289 class TestDocstrings:
290 def test_register_has_docstring(self) -> None:
291 from muse.cli.commands.reset import register
292
293 assert register.__doc__ and len(register.__doc__.strip()) > 20
294
295 def test_register_docstring_mentions_flags(self) -> None:
296 from muse.cli.commands.reset import register
297
298 doc = register.__doc__ or ""
299 assert "--hard" in doc or "hard" in doc.lower()
300 assert "--dry-run" in doc or "dry_run" in doc or "dry-run" in doc.lower()
301
302 def test_run_has_docstring(self) -> None:
303 from muse.cli.commands.reset import run
304
305 assert run.__doc__ and len(run.__doc__.strip()) > 20
306
307 def test_run_docstring_mentions_schema_version(self) -> None:
308 from muse.cli.commands.reset import run
309
310 doc = run.__doc__ or ""
311 assert "json" in doc.lower() or "exit_code" in doc
File History 1 commit
sha256:51ce277f663e01a43eaffbe77509b1de7ac2d4251b55d23306304bcdeb92c90d feat(pack): delta-encode snapshots in MPackBundle wire format Sonnet 4.6 minor 121 days ago