gabriel / muse public
test_plumbing_show_ref.py python
196 lines 7.1 KB
e88283c9 feat(plumbing): add 7 new interrogation commands to complete plumbing layer Gabriel Cardona <gabriel@tellurstori.com> 2d ago
1 """Tests for ``muse plumbing show-ref``.
2
3 Verifies enumeration of branch refs, HEAD resolution, glob-pattern filtering,
4 verify mode (silent existence check), and text-format output.
5 """
6
7 from __future__ import annotations
8
9 import datetime
10 import hashlib
11 import json
12 import pathlib
13
14 from typer.testing import CliRunner
15
16 from muse.cli.app import cli
17 from muse.core.errors import ExitCode
18 from muse.core.store import CommitRecord, SnapshotRecord, write_commit, write_snapshot
19
20 runner = CliRunner()
21
22
23 # ---------------------------------------------------------------------------
24 # Helpers
25 # ---------------------------------------------------------------------------
26
27
28 def _sha(tag: str) -> str:
29 return hashlib.sha256(tag.encode()).hexdigest()
30
31
32 def _init_repo(path: pathlib.Path) -> pathlib.Path:
33 muse = path / ".muse"
34 (muse / "commits").mkdir(parents=True)
35 (muse / "snapshots").mkdir(parents=True)
36 (muse / "objects").mkdir(parents=True)
37 (muse / "refs" / "heads").mkdir(parents=True)
38 (muse / "HEAD").write_text("ref: refs/heads/main", encoding="utf-8")
39 (muse / "repo.json").write_text(
40 json.dumps({"repo_id": "test-repo", "domain": "midi"}), encoding="utf-8"
41 )
42 return path
43
44
45 def _env(repo: pathlib.Path) -> dict[str, str]:
46 return {"MUSE_REPO_ROOT": str(repo)}
47
48
49 def _snap(repo: pathlib.Path) -> str:
50 sid = _sha("snap")
51 write_snapshot(
52 repo,
53 SnapshotRecord(
54 snapshot_id=sid,
55 manifest={},
56 created_at=datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc),
57 ),
58 )
59 return sid
60
61
62 def _make_branch(repo: pathlib.Path, branch: str, tag: str, snap_id: str) -> str:
63 cid = _sha(tag)
64 write_commit(
65 repo,
66 CommitRecord(
67 commit_id=cid,
68 repo_id="test-repo",
69 branch=branch,
70 snapshot_id=snap_id,
71 message=tag,
72 committed_at=datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc),
73 author="tester",
74 parent_commit_id=None,
75 ),
76 )
77 ref = repo / ".muse" / "refs" / "heads" / branch
78 ref.write_text(cid, encoding="utf-8")
79 return cid
80
81
82 # ---------------------------------------------------------------------------
83 # Tests
84 # ---------------------------------------------------------------------------
85
86
87 class TestShowRef:
88 def test_lists_all_branch_refs(self, tmp_path: pathlib.Path) -> None:
89 repo = _init_repo(tmp_path)
90 sid = _snap(repo)
91 _make_branch(repo, "main", "main-commit", sid)
92 _make_branch(repo, "dev", "dev-commit", sid)
93 result = runner.invoke(cli, ["plumbing", "show-ref"], env=_env(repo))
94 assert result.exit_code == 0, result.output
95 data = json.loads(result.stdout)
96 assert data["count"] == 2
97 ref_names = {r["ref"] for r in data["refs"]}
98 assert "refs/heads/main" in ref_names
99 assert "refs/heads/dev" in ref_names
100
101 def test_commit_ids_match_branch_heads(self, tmp_path: pathlib.Path) -> None:
102 repo = _init_repo(tmp_path)
103 sid = _snap(repo)
104 cid_main = _make_branch(repo, "main", "main-c", sid)
105 result = runner.invoke(cli, ["plumbing", "show-ref"], env=_env(repo))
106 assert result.exit_code == 0, result.output
107 data = json.loads(result.stdout)
108 main_ref = next(r for r in data["refs"] if r["ref"] == "refs/heads/main")
109 assert main_ref["commit_id"] == cid_main
110
111 def test_head_info_included(self, tmp_path: pathlib.Path) -> None:
112 repo = _init_repo(tmp_path)
113 sid = _snap(repo)
114 cid = _make_branch(repo, "main", "main-c", sid)
115 result = runner.invoke(cli, ["plumbing", "show-ref"], env=_env(repo))
116 assert result.exit_code == 0, result.output
117 data = json.loads(result.stdout)
118 assert data["head"]["branch"] == "main"
119 assert data["head"]["commit_id"] == cid
120
121 def test_empty_repo_returns_empty_list(self, tmp_path: pathlib.Path) -> None:
122 repo = _init_repo(tmp_path)
123 result = runner.invoke(cli, ["plumbing", "show-ref"], env=_env(repo))
124 assert result.exit_code == 0, result.output
125 data = json.loads(result.stdout)
126 assert data["count"] == 0
127 assert data["refs"] == []
128
129 def test_pattern_filter_restricts_output(self, tmp_path: pathlib.Path) -> None:
130 repo = _init_repo(tmp_path)
131 sid = _snap(repo)
132 _make_branch(repo, "main", "main-c", sid)
133 _make_branch(repo, "dev", "dev-c", sid)
134 result = runner.invoke(
135 cli, ["plumbing", "show-ref", "--pattern", "refs/heads/main"], env=_env(repo)
136 )
137 assert result.exit_code == 0, result.output
138 data = json.loads(result.stdout)
139 assert data["count"] == 1
140 assert data["refs"][0]["ref"] == "refs/heads/main"
141
142 def test_head_flag_returns_only_head(self, tmp_path: pathlib.Path) -> None:
143 repo = _init_repo(tmp_path)
144 sid = _snap(repo)
145 cid = _make_branch(repo, "main", "main-c", sid)
146 _make_branch(repo, "dev", "dev-c", sid)
147 result = runner.invoke(cli, ["plumbing", "show-ref", "--head"], env=_env(repo))
148 assert result.exit_code == 0, result.output
149 data = json.loads(result.stdout)
150 assert data["head"]["commit_id"] == cid
151
152 def test_verify_existing_ref_exits_zero(self, tmp_path: pathlib.Path) -> None:
153 repo = _init_repo(tmp_path)
154 sid = _snap(repo)
155 _make_branch(repo, "main", "main-c", sid)
156 result = runner.invoke(
157 cli, ["plumbing", "show-ref", "--verify", "refs/heads/main"], env=_env(repo)
158 )
159 assert result.exit_code == 0
160
161 def test_verify_missing_ref_exits_user_error(self, tmp_path: pathlib.Path) -> None:
162 repo = _init_repo(tmp_path)
163 result = runner.invoke(
164 cli, ["plumbing", "show-ref", "--verify", "refs/heads/nonexistent"], env=_env(repo)
165 )
166 assert result.exit_code == ExitCode.USER_ERROR
167
168 def test_verify_produces_no_stdout(self, tmp_path: pathlib.Path) -> None:
169 repo = _init_repo(tmp_path)
170 sid = _snap(repo)
171 _make_branch(repo, "main", "main-c", sid)
172 result = runner.invoke(
173 cli, ["plumbing", "show-ref", "--verify", "refs/heads/main"], env=_env(repo)
174 )
175 assert result.stdout.strip() == ""
176
177 def test_text_format_output(self, tmp_path: pathlib.Path) -> None:
178 repo = _init_repo(tmp_path)
179 sid = _snap(repo)
180 _make_branch(repo, "main", "main-c", sid)
181 result = runner.invoke(
182 cli, ["plumbing", "show-ref", "--format", "text"], env=_env(repo)
183 )
184 assert result.exit_code == 0, result.output
185 assert "refs/heads/main" in result.stdout
186
187 def test_refs_sorted_lexicographically(self, tmp_path: pathlib.Path) -> None:
188 repo = _init_repo(tmp_path)
189 sid = _snap(repo)
190 _make_branch(repo, "zzz", "z-c", sid)
191 _make_branch(repo, "aaa", "a-c", sid)
192 _make_branch(repo, "mmm", "m-c", sid)
193 result = runner.invoke(cli, ["plumbing", "show-ref"], env=_env(repo))
194 assert result.exit_code == 0, result.output
195 ref_names = [r["ref"] for r in json.loads(result.stdout)["refs"]]
196 assert ref_names == sorted(ref_names)