gabriel / muse public

test_cmd_reflog.py file-level

at sha256:f · View file ↗ · Intel ↗

History
1 files
1 commits
0 hotspots
0 🧊 dead
0 💥 blast risk
sha256:b adding issues docs to bust staging mpack prebuild cache. · gabriel · Jun 20, 2026
1 """Comprehensive tests for ``muse reflog``.
2
3 Covers:
4 - Unit: _fmt_entry sanitizes operation field
5 - Integration: reflog populated by commits, --all flag
6 - E2E: full CLI via CliRunner
7 - Security: branch name validated before use as path, operation sanitized
8 - Stress: large reflog with limit
9 """
10
11 from __future__ import annotations
12
13 import datetime
14 import json
15 import pathlib
16
17 import pytest
18 from tests.cli_test_helper import CliRunner
19 from muse.core.types import NULL_COMMIT_ID, fake_id
20 from muse.core.paths import muse_dir, ref_path
21
22 cli = None # argparse migration — CliRunner ignores this arg
23
24 runner = CliRunner()
25
26
27 # ---------------------------------------------------------------------------
28 # Helpers
29 # ---------------------------------------------------------------------------
30
31 def _env(root: pathlib.Path) -> Manifest:
32 return {"MUSE_REPO_ROOT": str(root)}
33
34
35 def _init_repo(tmp_path: pathlib.Path) -> tuple[pathlib.Path, str]:
36 dot_muse = muse_dir(tmp_path)
37 dot_muse.mkdir()
38 repo_id = fake_id("repo")
39 (dot_muse / "repo.json").write_text(json.dumps({
40 "repo_id": repo_id,
41 "domain": "midi",
42 "default_branch": "main",
43 "created_at": "2025-01-01T00:00:00+00:00",
44 }), encoding="utf-8")
45 (dot_muse / "HEAD").write_text("ref: refs/heads/main", encoding="utf-8")
46 (dot_muse / "refs" / "heads").mkdir(parents=True)
47 (dot_muse / "snapshots").mkdir()
48 (dot_muse / "commits").mkdir()
49 (dot_muse / "objects").mkdir()
50 return tmp_path, repo_id
51
52
53 def _make_commit_with_reflog(
54 root: pathlib.Path, repo_id: str, message: str = "commit", branch: str = "main"
55 ) -> str:
56 from muse.core.commits import (
57 CommitRecord,
58 write_commit,
59 )
60 from muse.core.snapshots import (
61 SnapshotRecord,
62 write_snapshot,
63 )
64 from muse.core.ids import hash_snapshot, hash_commit
65 from muse.core.reflog import append_reflog
66
67 ref_file = ref_path(root, branch)
68 parent_id = ref_file.read_text().strip() if ref_file.exists() else None
69 manifest: Manifest = {}
70 snap_id = hash_snapshot(manifest)
71 committed_at = datetime.datetime.now(datetime.timezone.utc)
72 commit_id = hash_commit( parent_ids=[parent_id] if parent_id else [],
73 snapshot_id=snap_id, message=message,
74 committed_at_iso=committed_at.isoformat(),
75 )
76 write_snapshot(root, SnapshotRecord(snapshot_id=snap_id, manifest=manifest))
77 write_commit(root, CommitRecord(
78 commit_id=commit_id, branch=branch,
79 snapshot_id=snap_id, message=message, committed_at=committed_at,
80 parent_commit_id=parent_id,
81 ))
82 ref_file.parent.mkdir(parents=True, exist_ok=True)
83 ref_file.write_text(commit_id, encoding="utf-8")
84 append_reflog(root, branch, old_id=parent_id or NULL_COMMIT_ID, new_id=commit_id,
85 author="user", operation=f"commit: {message}")
86 return commit_id
87
88
89 # ---------------------------------------------------------------------------
90 # Unit tests
91 # ---------------------------------------------------------------------------
92
93
94 class TestRegisterFlags:
95 def _parse(self, *args: str) -> "argparse.Namespace":
96 import argparse
97 from muse.cli.commands.reflog import register
98 p = argparse.ArgumentParser()
99 sub = p.add_subparsers()
100 register(sub)
101 return p.parse_args(["reflog", *args])
102
103 def test_default_json_out_is_false(self) -> None:
104 ns = self._parse()
105 assert ns.json_out is False
106
107 def test_json_flag_sets_json_out(self) -> None:
108 ns = self._parse("--json")
109 assert ns.json_out is True
110
111 def test_j_shorthand_sets_json_out(self) -> None:
112 ns = self._parse("-j")
113 assert ns.json_out is True
114
115
116 class TestReflogUnit:
117 def test_fmt_entry_sanitizes_operation(self) -> None:
118 from muse.cli.commands.reflog import _fmt_entry
119 from muse.core.reflog import ReflogEntry
120
121 entry = ReflogEntry(
122 old_id=NULL_COMMIT_ID, new_id="a" * 64, author="user",
123 timestamp=datetime.datetime(2025, 1, 1, tzinfo=datetime.timezone.utc),
124 operation="commit: Hello\x1b[31mRED\x1b[0m",
125 )
126 result = _fmt_entry(0, entry)
127 assert "\x1b" not in result
128
129 def test_fmt_entry_initial_shown_as_initial(self) -> None:
130 from muse.cli.commands.reflog import _fmt_entry
131 from muse.core.reflog import ReflogEntry
132
133 entry = ReflogEntry(
134 old_id=NULL_COMMIT_ID, new_id="b" * 64, author="user",
135 timestamp=datetime.datetime(2025, 1, 1, tzinfo=datetime.timezone.utc),
136 operation="branch: created",
137 )
138 result = _fmt_entry(0, entry)
139 assert "initial" in result
140
141
142 # ---------------------------------------------------------------------------
143 # Integration tests
144 # ---------------------------------------------------------------------------
145
146 class TestReflogIntegration:
147 def test_reflog_empty_repo(self, tmp_path: pathlib.Path) -> None:
148 root, _ = _init_repo(tmp_path)
149 result = runner.invoke(cli, ["reflog"], env=_env(root), catch_exceptions=False)
150 assert result.exit_code == 0
151 assert "No reflog entries" in result.output
152
153 def test_reflog_after_commit(self, tmp_path: pathlib.Path) -> None:
154 root, repo_id = _init_repo(tmp_path)
155 _make_commit_with_reflog(root, repo_id, message="my first commit")
156 result = runner.invoke(cli, ["reflog"], env=_env(root), catch_exceptions=False)
157 assert result.exit_code == 0
158 assert "@{0" in result.output
159 assert "commit: my first commit" in result.output
160
161 def test_reflog_limit(self, tmp_path: pathlib.Path) -> None:
162 root, repo_id = _init_repo(tmp_path)
163 for i in range(10):
164 _make_commit_with_reflog(root, repo_id, message=f"commit {i}")
165 result = runner.invoke(cli, ["reflog", "--limit", "3"], env=_env(root), catch_exceptions=False)
166 assert result.exit_code == 0
167 lines = [l for l in result.output.splitlines() if "@{" in l]
168 assert len(lines) <= 3
169
170 def test_reflog_branch_flag(self, tmp_path: pathlib.Path) -> None:
171 root, repo_id = _init_repo(tmp_path)
172 _make_commit_with_reflog(root, repo_id, message="on main")
173 result = runner.invoke(cli, ["reflog", "--branch", "main"], env=_env(root), catch_exceptions=False)
174 assert result.exit_code == 0
175 assert "main" in result.output
176
177 def test_reflog_short_flags(self, tmp_path: pathlib.Path) -> None:
178 root, repo_id = _init_repo(tmp_path)
179 for i in range(5):
180 _make_commit_with_reflog(root, repo_id, message=f"commit {i}")
181 result = runner.invoke(cli, ["reflog", "--limit", "2", "-b", "main"], env=_env(root), catch_exceptions=False)
182 assert result.exit_code == 0
183 lines = [l for l in result.output.splitlines() if "@{" in l]
184 assert len(lines) <= 2
185
186 def test_reflog_all_flag_lists_refs(self, tmp_path: pathlib.Path) -> None:
187 root, repo_id = _init_repo(tmp_path)
188 _make_commit_with_reflog(root, repo_id, message="first")
189 result = runner.invoke(cli, ["reflog", "--all"], env=_env(root), catch_exceptions=False)
190 assert result.exit_code == 0
191
192
193 # ---------------------------------------------------------------------------
194 # Security tests
195 # ---------------------------------------------------------------------------
196
197 class TestReflogSecurity:
198 def test_invalid_branch_name_rejected(self, tmp_path: pathlib.Path) -> None:
199 root, repo_id = _init_repo(tmp_path)
200 _make_commit_with_reflog(root, repo_id)
201 result = runner.invoke(cli, ["reflog", "--branch", "../../../etc/passwd"], env=_env(root))
202 assert result.exit_code != 0
203
204 def test_operation_with_control_chars_sanitized(self, tmp_path: pathlib.Path) -> None:
205 root, repo_id = _init_repo(tmp_path)
206 from muse.core.reflog import append_reflog
207 _make_commit_with_reflog(root, repo_id, message="clean")
208 append_reflog(root, "main", old_id=NULL_COMMIT_ID, new_id="a" * 64,
209 author="user", operation="malicious\x1b[31mRED\x1b[0m op")
210 result = runner.invoke(cli, ["reflog"], env=_env(root), catch_exceptions=False)
211 assert result.exit_code == 0
212 assert "\x1b" not in result.output
213
214
215 # ---------------------------------------------------------------------------
216 # Stress tests
217 # ---------------------------------------------------------------------------
218
219 class TestReflogStress:
220 def test_large_reflog_with_limit(self, tmp_path: pathlib.Path) -> None:
221 root, repo_id = _init_repo(tmp_path)
222 for i in range(50):
223 _make_commit_with_reflog(root, repo_id, message=f"commit {i:03d}")
224 result = runner.invoke(cli, ["reflog", "--limit", "5"], env=_env(root), catch_exceptions=False)
225 assert result.exit_code == 0
226 lines = [l for l in result.output.splitlines() if "@{" in l]
227 assert len(lines) <= 5