gabriel / muse public
test_cmd_trust_json.py python
245 lines 9.4 KB
Raw
1 """TDD tests for ``muse trust --json`` output.
2
3 All read subcommands (list, hub-list) must support --json for agent ergonomics.
4 Mutation subcommands (add, remove, hub-reset) emit a JSON confirmation when
5 --json is passed.
6
7 Coverage:
8 - trust list --json → {"trusted_dirs": [...]}
9 - trust list --json with no dirs → {"trusted_dirs": []}
10 - trust list --json with multiple dirs → all present
11 - trust hub-list --json → {"fingerprints": {hostname: {...}}}
12 - trust hub-list --json with no fingerprints → {"fingerprints": {}}
13 - trust add <path> --json → {"added": true, "path": "..."}
14 - trust remove <path> --json → {"removed": true/false, "path": "..."}
15 - trust hub-reset <hostname> --json → {"reset": true/false, "hostname": "..."}
16 - trust list (text) still works (no regression)
17 - trust hub-list (text) still works (no regression)
18 """
19
20 from __future__ import annotations
21
22 import json
23 import pathlib
24 import unittest.mock
25
26 import pytest
27
28 from tests.cli_test_helper import CliRunner
29
30 runner = CliRunner()
31
32
33 # ---------------------------------------------------------------------------
34 # Helpers
35 # ---------------------------------------------------------------------------
36
37
38 def _invoke(args: list[str], muse_home: pathlib.Path | None = None) -> tuple[int, str, str]:
39 env: dict[str, str] = {}
40 result = runner.invoke(None, args, env=env)
41 return result.exit_code, result.stdout, result.stderr
42
43
44 def _invoke_with_home(args: list[str], muse_home: pathlib.Path) -> tuple[int, str, str]:
45 """Invoke with a patched MUSE_HOME so config is isolated."""
46 import muse.cli.config as cfg_mod
47 import muse.core.hub_trust as ht_mod
48
49 config_file = muse_home / "config.toml"
50 hub_trust_file = muse_home / "hub_trust.toml"
51
52 with (
53 unittest.mock.patch.object(cfg_mod, "_GLOBAL_CONFIG_FILE", config_file),
54 unittest.mock.patch.object(ht_mod, "_HUB_TRUST_FILE", hub_trust_file),
55 ):
56 result = runner.invoke(None, args, env={})
57 return result.exit_code, result.stdout, result.stderr
58
59
60 # ---------------------------------------------------------------------------
61 # trust list --json
62 # ---------------------------------------------------------------------------
63
64
65 class TestTrustListJson:
66 def test_json_is_object(self, tmp_path: pathlib.Path) -> None:
67 rc, out, err = _invoke_with_home(["trust", "list", "--json"], tmp_path)
68 assert rc == 0
69 data = json.loads(out.strip())
70 assert isinstance(data, dict)
71
72 def test_json_has_trusted_dirs_key(self, tmp_path: pathlib.Path) -> None:
73 rc, out, err = _invoke_with_home(["trust", "list", "--json"], tmp_path)
74 assert rc == 0
75 data = json.loads(out.strip())
76 assert "trusted_dirs" in data
77
78 def test_json_empty_when_no_dirs(self, tmp_path: pathlib.Path) -> None:
79 rc, out, err = _invoke_with_home(["trust", "list", "--json"], tmp_path)
80 assert rc == 0
81 data = json.loads(out.strip())
82 assert data["trusted_dirs"] == []
83
84 def test_json_contains_added_dirs(self, tmp_path: pathlib.Path) -> None:
85 _invoke_with_home(["trust", "add", "/some/path"], tmp_path)
86 rc, out, err = _invoke_with_home(["trust", "list", "--json"], tmp_path)
87 assert rc == 0
88 data = json.loads(out.strip())
89 assert any("/some/path" in d for d in data["trusted_dirs"])
90
91 def test_json_multiple_dirs(self, tmp_path: pathlib.Path) -> None:
92 _invoke_with_home(["trust", "add", "/path/a"], tmp_path)
93 _invoke_with_home(["trust", "add", "/path/b"], tmp_path)
94 rc, out, err = _invoke_with_home(["trust", "list", "--json"], tmp_path)
95 data = json.loads(out.strip())
96 dirs = data["trusted_dirs"]
97 assert len(dirs) == 2
98
99 def test_text_list_still_works(self, tmp_path: pathlib.Path) -> None:
100 rc, out, err = _invoke_with_home(["trust", "list"], tmp_path)
101 assert rc == 0
102 # text output — should not start with "{"
103 stripped = out.strip()
104 assert not stripped.startswith("{") or stripped == ""
105
106
107 # ---------------------------------------------------------------------------
108 # trust hub-list --json
109 # ---------------------------------------------------------------------------
110
111
112 class TestTrustHubListJson:
113 def test_json_is_object(self, tmp_path: pathlib.Path) -> None:
114 rc, out, err = _invoke_with_home(["trust", "hub-list", "--json"], tmp_path)
115 assert rc == 0
116 data = json.loads(out.strip())
117 assert isinstance(data, dict)
118
119 def test_json_has_fingerprints_key(self, tmp_path: pathlib.Path) -> None:
120 rc, out, err = _invoke_with_home(["trust", "hub-list", "--json"], tmp_path)
121 assert rc == 0
122 data = json.loads(out.strip())
123 assert "fingerprints" in data
124
125 def test_json_empty_when_no_fingerprints(self, tmp_path: pathlib.Path) -> None:
126 rc, out, err = _invoke_with_home(["trust", "hub-list", "--json"], tmp_path)
127 assert rc == 0
128 data = json.loads(out.strip())
129 assert data["fingerprints"] == {}
130
131 def test_json_fingerprints_is_dict(self, tmp_path: pathlib.Path) -> None:
132 rc, out, err = _invoke_with_home(["trust", "hub-list", "--json"], tmp_path)
133 data = json.loads(out.strip())
134 assert isinstance(data["fingerprints"], dict)
135
136 def test_text_hub_list_still_works(self, tmp_path: pathlib.Path) -> None:
137 rc, out, err = _invoke_with_home(["trust", "hub-list"], tmp_path)
138 assert rc == 0
139 stripped = out.strip()
140 assert not stripped.startswith("{") or stripped == ""
141
142
143 # ---------------------------------------------------------------------------
144 # trust add --json
145 # ---------------------------------------------------------------------------
146
147
148 class TestTrustAddJson:
149 def test_json_exit_zero(self, tmp_path: pathlib.Path) -> None:
150 rc, out, err = _invoke_with_home(["trust", "add", "/test/repo", "--json"], tmp_path)
151 assert rc == 0
152
153 def test_json_is_object(self, tmp_path: pathlib.Path) -> None:
154 rc, out, err = _invoke_with_home(["trust", "add", "/test/repo", "--json"], tmp_path)
155 data = json.loads(out.strip())
156 assert isinstance(data, dict)
157
158 def test_json_has_added_and_path(self, tmp_path: pathlib.Path) -> None:
159 rc, out, err = _invoke_with_home(["trust", "add", "/test/repo", "--json"], tmp_path)
160 data = json.loads(out.strip())
161 assert "added" in data
162 assert "path" in data
163
164 def test_json_added_is_true(self, tmp_path: pathlib.Path) -> None:
165 rc, out, err = _invoke_with_home(["trust", "add", "/test/repo", "--json"], tmp_path)
166 data = json.loads(out.strip())
167 assert data["added"] is True
168
169
170 # ---------------------------------------------------------------------------
171 # trust remove --json
172 # ---------------------------------------------------------------------------
173
174
175 class TestTrustRemoveJson:
176 def test_json_removed_true_when_present(self, tmp_path: pathlib.Path) -> None:
177 _invoke_with_home(["trust", "add", "/rm/this"], tmp_path)
178 rc, out, err = _invoke_with_home(["trust", "remove", "/rm/this", "--json"], tmp_path)
179 assert rc == 0
180 data = json.loads(out.strip())
181 assert data["removed"] is True
182 assert "/rm/this" in data["path"]
183
184 def test_json_removed_false_when_absent(self, tmp_path: pathlib.Path) -> None:
185 rc, out, err = _invoke_with_home(["trust", "remove", "/does/not/exist", "--json"], tmp_path)
186 assert rc == 0
187 data = json.loads(out.strip())
188 assert data["removed"] is False
189
190 def test_json_has_path_key(self, tmp_path: pathlib.Path) -> None:
191 rc, out, err = _invoke_with_home(["trust", "remove", "/some/path", "--json"], tmp_path)
192 data = json.loads(out.strip())
193 assert "path" in data
194
195
196 # ---------------------------------------------------------------------------
197 # trust hub-reset --json
198 # ---------------------------------------------------------------------------
199
200
201 class TestTrustHubResetJson:
202 def test_json_reset_false_when_not_pinned(self, tmp_path: pathlib.Path) -> None:
203 rc, out, err = _invoke_with_home(
204 ["trust", "hub-reset", "notahost.example", "--json"], tmp_path
205 )
206 assert rc == 0
207 data = json.loads(out.strip())
208 assert data["reset"] is False
209 assert "hostname" in data
210
211 def test_json_has_hostname_key(self, tmp_path: pathlib.Path) -> None:
212 rc, out, err = _invoke_with_home(
213 ["trust", "hub-reset", "somehost:10003", "--json"], tmp_path
214 )
215 data = json.loads(out.strip())
216 assert "hostname" in data
217
218 def test_json_is_object(self, tmp_path: pathlib.Path) -> None:
219 rc, out, err = _invoke_with_home(
220 ["trust", "hub-reset", "h.test", "--json"], tmp_path
221 )
222 data = json.loads(out.strip())
223 assert isinstance(data, dict)
224
225
226 class TestRegisterFlags:
227 def _parse(self, *args: str) -> "argparse.Namespace":
228 import argparse
229 from muse.cli.commands.trust import register
230 p = argparse.ArgumentParser()
231 sub = p.add_subparsers()
232 register(sub)
233 return p.parse_args(["trust", *args])
234
235 def test_default_json_out_is_false(self) -> None:
236 ns = self._parse("list")
237 assert ns.json_out is False
238
239 def test_json_flag_sets_json_out(self) -> None:
240 ns = self._parse("list", "--json")
241 assert ns.json_out is True
242
243 def test_j_shorthand_sets_json_out(self) -> None:
244 ns = self._parse("list", "-j")
245 assert ns.json_out is True
File History 1 commit