gabriel / muse public
test_code_query_supercharge.py python
298 lines 12.3 KB
Raw
sha256:88ac91129873e6a496e9189515aa690eb893ae25d69c8f72af141a2be5068eb3 docs: docstring sprint contract→find-symbol — idiomatic run… Sonnet 4.6 patch 142 days ago
1 """Supercharge tests for ``muse code code-query`` — agent-usability gaps.
2
3 The existing test_cmd_code_query.py covers parser correctness, evaluator
4 match logic, walk_history integration, CLI E2E, and stress. This file
5 targets only the gaps those tests leave open:
6
7 Coverage matrix
8 ---------------
9 - --json / -j: -j alias works identically to --json
10 - exit_code: JSON output includes exit_code = 0 on success
11 - duration_ms: JSON output includes non-negative float duration_ms
12 - TypedDicts: _CodeQueryOutputJson gains exit_code/duration_ms annotations
13 - Docstrings: run() docstring mentions exit_code and duration_ms
14 - ANSI: JSON output never contains terminal escape sequences
15 - Performance: duration_ms stays under 2000 ms for a small repo
16 """
17
18 from __future__ import annotations
19
20 import json
21 import pathlib
22
23 import pytest
24
25 from tests.cli_test_helper import CliRunner
26
27 runner = CliRunner()
28
29
30 # ---------------------------------------------------------------------------
31 # Helpers
32 # ---------------------------------------------------------------------------
33
34
35 def _env(root: pathlib.Path) -> dict[str, str]:
36 return {"MUSE_REPO_ROOT": str(root)}
37
38
39 def _run(root: pathlib.Path, *args: str):
40 return runner.invoke(None, list(args), env=_env(root))
41
42
43 def _stage_commit(root: pathlib.Path, msg: str = "commit") -> None:
44 r = _run(root, "code", "add", ".")
45 assert r.exit_code == 0, r.output
46 r2 = _run(root, "commit", "-m", msg, "--agent-id", "test-agent", "--model-id", "test-model")
47 assert r2.exit_code == 0, r2.output
48
49
50 # ---------------------------------------------------------------------------
51 # Fixture
52 # ---------------------------------------------------------------------------
53
54
55 @pytest.fixture()
56 def query_repo(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path:
57 """Code-domain repo with a few committed files for querying."""
58 monkeypatch.chdir(tmp_path)
59 r = _run(tmp_path, "init", "--domain", "code")
60 assert r.exit_code == 0, r.output
61 (tmp_path / "alpha.py").write_text("def alpha_fn():\n return 1\n")
62 _stage_commit(tmp_path, "add alpha")
63 (tmp_path / "beta.py").write_text("def beta_fn():\n return 2\n")
64 _stage_commit(tmp_path, "add beta")
65 return tmp_path
66
67
68 # ---------------------------------------------------------------------------
69 # TestJsonAlias — -j works identically to --json
70 # ---------------------------------------------------------------------------
71
72
73 class TestJsonAlias:
74 """-j shorthand must behave identically to --json."""
75
76 def test_j_alias_exits_zero(self, query_repo: pathlib.Path) -> None:
77 r = _run(query_repo, "code", "code-query", "author == test-agent", "-j")
78 assert r.exit_code == 0, r.output
79
80 def test_j_alias_valid_json(self, query_repo: pathlib.Path) -> None:
81 r = _run(query_repo, "code", "code-query", "author == test-agent", "-j")
82 json.loads(r.output) # must not raise
83
84 def test_j_alias_has_total_key(self, query_repo: pathlib.Path) -> None:
85 r = _run(query_repo, "code", "code-query", "author == test-agent", "-j")
86 data = json.loads(r.output)
87 assert "total" in data
88
89 def test_j_alias_has_results_key(self, query_repo: pathlib.Path) -> None:
90 r = _run(query_repo, "code", "code-query", "author == test-agent", "-j")
91 data = json.loads(r.output)
92 assert "results" in data
93
94 def test_j_alias_same_top_level_keys_as_json_flag(self, query_repo: pathlib.Path) -> None:
95 r1 = _run(query_repo, "code", "code-query", "author == test-agent", "--json")
96 r2 = _run(query_repo, "code", "code-query", "author == test-agent", "-j")
97 d1 = json.loads(r1.output)
98 d2 = json.loads(r2.output)
99 d1.pop("duration_ms", None)
100 d2.pop("duration_ms", None)
101 assert set(d1.keys()) == set(d2.keys())
102
103 def test_j_alias_total_matches_json_flag(self, query_repo: pathlib.Path) -> None:
104 r1 = _run(query_repo, "code", "code-query", "author == test-agent", "--json")
105 r2 = _run(query_repo, "code", "code-query", "author == test-agent", "-j")
106 assert json.loads(r1.output)["total"] == json.loads(r2.output)["total"]
107
108 def test_j_alias_no_matches_empty_results(self, query_repo: pathlib.Path) -> None:
109 r = _run(query_repo, "code", "code-query", "author == nobody", "-j")
110 data = json.loads(r.output)
111 assert data["results"] == []
112 assert data["total"] == 0
113
114 def test_j_alias_with_limit(self, query_repo: pathlib.Path) -> None:
115 r = _run(query_repo, "code", "code-query", "author == test-agent", "-j", "--limit", "1")
116 assert r.exit_code == 0, r.output
117 json.loads(r.output)
118
119
120 # ---------------------------------------------------------------------------
121 # TestDurationMs — JSON output must include duration_ms
122 # ---------------------------------------------------------------------------
123
124
125 class TestDurationMs:
126 """JSON output must include a non-negative float duration_ms."""
127
128 def test_json_has_duration_ms(self, query_repo: pathlib.Path) -> None:
129 r = _run(query_repo, "code", "code-query", "author == test-agent", "--json")
130 data = json.loads(r.output)
131 assert "duration_ms" in data
132
133 def test_json_duration_ms_nonnegative(self, query_repo: pathlib.Path) -> None:
134 r = _run(query_repo, "code", "code-query", "author == test-agent", "--json")
135 data = json.loads(r.output)
136 assert data["duration_ms"] >= 0
137
138 def test_json_duration_ms_is_float(self, query_repo: pathlib.Path) -> None:
139 r = _run(query_repo, "code", "code-query", "author == test-agent", "--json")
140 data = json.loads(r.output)
141 assert isinstance(data["duration_ms"], float)
142
143 def test_j_alias_duration_ms_present(self, query_repo: pathlib.Path) -> None:
144 r = _run(query_repo, "code", "code-query", "author == test-agent", "-j")
145 data = json.loads(r.output)
146 assert "duration_ms" in data
147
148 def test_duration_ms_no_matches(self, query_repo: pathlib.Path) -> None:
149 r = _run(query_repo, "code", "code-query", "author == nobody", "--json")
150 data = json.loads(r.output)
151 assert "duration_ms" in data
152 assert data["duration_ms"] >= 0
153
154 def test_duration_ms_with_since(self, query_repo: pathlib.Path) -> None:
155 r = _run(query_repo, "code", "code-query", "author == test-agent",
156 "--json", "--since", "2020-01-01")
157 data = json.loads(r.output)
158 assert "duration_ms" in data
159 assert data["duration_ms"] >= 0
160
161
162 # ---------------------------------------------------------------------------
163 # TestExitCode — JSON includes exit_code = 0 on success
164 # ---------------------------------------------------------------------------
165
166
167 class TestExitCode:
168 """JSON exit_code must be 0 on success (all errors raise SystemExit before JSON emits)."""
169
170 def test_json_has_exit_code(self, query_repo: pathlib.Path) -> None:
171 r = _run(query_repo, "code", "code-query", "author == test-agent", "--json")
172 data = json.loads(r.output)
173 assert "exit_code" in data
174
175 def test_json_exit_code_zero_with_matches(self, query_repo: pathlib.Path) -> None:
176 r = _run(query_repo, "code", "code-query", "author == test-agent", "--json")
177 assert r.exit_code == 0
178 data = json.loads(r.output)
179 assert data["exit_code"] == 0
180
181 def test_json_exit_code_zero_no_matches(self, query_repo: pathlib.Path) -> None:
182 r = _run(query_repo, "code", "code-query", "author == nobody", "--json")
183 assert r.exit_code == 0
184 data = json.loads(r.output)
185 assert data["exit_code"] == 0
186
187 def test_json_exit_code_is_int(self, query_repo: pathlib.Path) -> None:
188 r = _run(query_repo, "code", "code-query", "author == test-agent", "--json")
189 data = json.loads(r.output)
190 assert isinstance(data["exit_code"], int)
191
192 def test_j_alias_exit_code_present(self, query_repo: pathlib.Path) -> None:
193 r = _run(query_repo, "code", "code-query", "author == test-agent", "-j")
194 data = json.loads(r.output)
195 assert "exit_code" in data
196
197 def test_exit_code_mirrors_process_exit(self, query_repo: pathlib.Path) -> None:
198 r = _run(query_repo, "code", "code-query", "author == test-agent", "--json")
199 data = json.loads(r.output)
200 assert data["exit_code"] == r.exit_code
201
202 def test_exit_code_zero_with_limit(self, query_repo: pathlib.Path) -> None:
203 r = _run(query_repo, "code", "code-query", "author == test-agent",
204 "--json", "--limit", "1")
205 data = json.loads(r.output)
206 assert data["exit_code"] == 0
207
208
209 # ---------------------------------------------------------------------------
210 # TestTypedDicts — _CodeQueryOutputJson carries the new fields
211 # ---------------------------------------------------------------------------
212
213
214 class TestTypedDicts:
215 """_CodeQueryOutputJson must carry exit_code and duration_ms annotations."""
216
217 def test_code_query_output_json_exists(self) -> None:
218 from muse.cli.commands.code_query import _CodeQueryOutputJson # noqa: F401
219
220 def test_has_exit_code_annotation(self) -> None:
221 from muse.cli.commands.code_query import _CodeQueryOutputJson
222 assert "exit_code" in _CodeQueryOutputJson.__annotations__
223
224 def test_has_duration_ms_annotation(self) -> None:
225 from muse.cli.commands.code_query import _CodeQueryOutputJson
226 assert "duration_ms" in _CodeQueryOutputJson.__annotations__
227
228 def test_has_total_annotation(self) -> None:
229 from muse.cli.commands.code_query import _CodeQueryOutputJson
230 assert "total" in _CodeQueryOutputJson.__annotations__
231
232 def test_has_results_annotation(self) -> None:
233 from muse.cli.commands.code_query import _CodeQueryOutputJson
234 assert "results" in _CodeQueryOutputJson.__annotations__
235
236
237 # ---------------------------------------------------------------------------
238 # TestDocstrings — run() docstring documents new fields
239 # ---------------------------------------------------------------------------
240
241
242 class TestDocstrings:
243 """run() must document exit_code and duration_ms."""
244
245 def test_run_docstring_mentions_exit_code(self) -> None:
246 from muse.cli.commands.code_query import run
247 assert run.__doc__ is not None
248 assert "exit_code" in run.__doc__
249
250 def test_run_docstring_mentions_duration_ms(self) -> None:
251 from muse.cli.commands.code_query import run
252 assert run.__doc__ is not None
253 assert "duration_ms" in run.__doc__
254
255
256 # ---------------------------------------------------------------------------
257 # TestAnsiSanitization — no escape codes in JSON output
258 # ---------------------------------------------------------------------------
259
260
261 class TestAnsiSanitization:
262 """No ANSI escape sequences anywhere in the JSON output."""
263
264 def test_json_output_no_ansi_with_matches(self, query_repo: pathlib.Path) -> None:
265 r = _run(query_repo, "code", "code-query", "author == test-agent", "--json")
266 assert "\x1b" not in r.output
267
268 def test_j_alias_output_no_ansi(self, query_repo: pathlib.Path) -> None:
269 r = _run(query_repo, "code", "code-query", "author == test-agent", "-j")
270 assert "\x1b" not in r.output
271
272 def test_json_output_no_ansi_no_matches(self, query_repo: pathlib.Path) -> None:
273 r = _run(query_repo, "code", "code-query", "author == nobody", "--json")
274 assert "\x1b" not in r.output
275
276
277 # ---------------------------------------------------------------------------
278 # TestPerformance — duration_ms under 2000 ms for a small repo
279 # ---------------------------------------------------------------------------
280
281
282 class TestPerformance:
283 """duration_ms must stay under 2000 ms for small repos."""
284
285 def test_json_duration_under_2000ms(self, query_repo: pathlib.Path) -> None:
286 r = _run(query_repo, "code", "code-query", "author == test-agent", "--json")
287 data = json.loads(r.output)
288 assert data["duration_ms"] < 2000
289
290 def test_j_alias_duration_under_2000ms(self, query_repo: pathlib.Path) -> None:
291 r = _run(query_repo, "code", "code-query", "author == test-agent", "-j")
292 data = json.loads(r.output)
293 assert data["duration_ms"] < 2000
294
295 def test_duration_ms_is_float_not_int(self, query_repo: pathlib.Path) -> None:
296 r = _run(query_repo, "code", "code-query", "author == test-agent", "--json")
297 data = json.loads(r.output)
298 assert isinstance(data["duration_ms"], float)
File History 1 commit
sha256:88ac91129873e6a496e9189515aa690eb893ae25d69c8f72af141a2be5068eb3 docs: docstring sprint contract→find-symbol — idiomatic run… Sonnet 4.6 patch 142 days ago