"""Supercharge tests for ``muse code code-check`` — agent-usability gaps. The existing test_cmd_code_check.py covers correctness, JSON schema, filters, --diff, --rules, security, edge cases, and stress. This file targets only the gaps those tests leave open: Coverage matrix --------------- - --json / -j: -j alias works identically to --json - exit_code: JSON output includes exit_code mirroring process exit (0 normally; 1 when --strict and has_errors) - duration_ms: JSON output includes non-negative float duration_ms - TypedDicts: _CodeCheckOutputJson gains exit_code/duration_ms annotations - Docstrings: run() docstring mentions exit_code and duration_ms - ANSI: JSON output never contains terminal escape sequences - Performance: duration_ms stays under 2000 ms for a small repo Critical distinction: exit_code is NOT hardcoded to 0. When --strict is active and violations with severity=error are found, both the process and the JSON exit_code equal 1. """ from __future__ import annotations from collections.abc import Mapping import json import pathlib import textwrap import pytest from tests.cli_test_helper import CliRunner from muse.core.paths import muse_dir runner = CliRunner() # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _env(root: pathlib.Path) -> Mapping[str, str]: return {"MUSE_REPO_ROOT": str(root)} def _run(root: pathlib.Path, *args: str) -> "InvokeResult": return runner.invoke(None, list(args), env=_env(root)) def _stage_commit(root: pathlib.Path, msg: str = "commit") -> None: r = _run(root, "code", "add", ".") assert r.exit_code == 0, r.output r2 = _run(root, "commit", "-m", msg) assert r2.exit_code == 0, r2.output # --------------------------------------------------------------------------- # Fixtures # --------------------------------------------------------------------------- @pytest.fixture() def clean_repo(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path: """Code-domain repo with one clean Python file — no violations.""" monkeypatch.chdir(tmp_path) r = _run(tmp_path, "init", "--domain", "code") assert r.exit_code == 0, r.output (tmp_path / "clean.py").write_text("def hello():\n return 'hello'\n") _stage_commit(tmp_path, "clean") return tmp_path def _complex_func(n_branches: int = 12) -> str: """Python source with cyclomatic complexity > 10 (triggers max_complexity rule).""" lines = ["def heavy(x: int) -> int:", " if x == 1:", " return 1"] for i in range(2, n_branches + 1): lines.append(f" elif x == {i}:") lines.append(f" return {i}") lines.append(" return 0") return "\n".join(lines) + "\n" @pytest.fixture() def violation_repo(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path: """Repo whose HEAD commit has a high-complexity function → error violation. Writes a custom code_invariants.toml that raises max_complexity to severity=error so that --strict exits 1 and has_errors is True. """ monkeypatch.chdir(tmp_path) r = _run(tmp_path, "init", "--domain", "code") assert r.exit_code == 0, r.output dot_muse = muse_dir(tmp_path) dot_muse.mkdir(exist_ok=True) (dot_muse / "code_invariants.toml").write_text( '[[rule]]\nname = "complexity_gate"\nseverity = "error"\n' 'rule_type = "max_complexity"\n[rule.params]\nthreshold = 10\n' ) (tmp_path / "complex.py").write_text(_complex_func()) _stage_commit(tmp_path, "add complex function") return tmp_path # --------------------------------------------------------------------------- # TestJsonAlias — -j works identically to --json # --------------------------------------------------------------------------- class TestJsonAlias: """-j shorthand must behave identically to --json.""" def test_j_alias_exits_zero_clean(self, clean_repo: pathlib.Path) -> None: r = _run(clean_repo, "code", "code-check", "-j") assert r.exit_code == 0, r.output def test_j_alias_valid_json(self, clean_repo: pathlib.Path) -> None: r = _run(clean_repo, "code", "code-check", "-j") json.loads(r.output) # must not raise def test_j_alias_has_violations_key(self, clean_repo: pathlib.Path) -> None: r = _run(clean_repo, "code", "code-check", "-j") data = json.loads(r.output) assert "violations" in data def test_j_alias_has_has_errors_key(self, clean_repo: pathlib.Path) -> None: r = _run(clean_repo, "code", "code-check", "-j") data = json.loads(r.output) assert "has_errors" in data def test_j_alias_same_top_level_keys_as_json_flag(self, clean_repo: pathlib.Path) -> None: r1 = _run(clean_repo, "code", "code-check", "--json") r2 = _run(clean_repo, "code", "code-check", "-j") d1 = json.loads(r1.output) d2 = json.loads(r2.output) d1.pop("duration_ms", None) d2.pop("duration_ms", None) assert set(d1.keys()) == set(d2.keys()) def test_j_alias_violation_count_matches(self, violation_repo: pathlib.Path) -> None: r1 = _run(violation_repo, "code", "code-check", "--json") r2 = _run(violation_repo, "code", "code-check", "-j") d1 = json.loads(r1.output) d2 = json.loads(r2.output) assert len(d1["violations"]) == len(d2["violations"]) def test_j_alias_with_strict(self, clean_repo: pathlib.Path) -> None: r = _run(clean_repo, "code", "code-check", "-j", "--strict") assert r.exit_code == 0, r.output json.loads(r.output) # --------------------------------------------------------------------------- # TestDurationMs — JSON output must include duration_ms # --------------------------------------------------------------------------- class TestDurationMs: """JSON output must include a non-negative float duration_ms.""" def test_json_has_duration_ms(self, clean_repo: pathlib.Path) -> None: r = _run(clean_repo, "code", "code-check", "--json") data = json.loads(r.output) assert "duration_ms" in data def test_json_duration_ms_nonnegative(self, clean_repo: pathlib.Path) -> None: r = _run(clean_repo, "code", "code-check", "--json") data = json.loads(r.output) assert data["duration_ms"] >= 0 def test_json_duration_ms_is_float(self, clean_repo: pathlib.Path) -> None: r = _run(clean_repo, "code", "code-check", "--json") data = json.loads(r.output) assert isinstance(data["duration_ms"], float) def test_j_alias_duration_ms_present(self, clean_repo: pathlib.Path) -> None: r = _run(clean_repo, "code", "code-check", "-j") data = json.loads(r.output) assert "duration_ms" in data def test_duration_ms_with_violations(self, violation_repo: pathlib.Path) -> None: r = _run(violation_repo, "code", "code-check", "--json") data = json.loads(r.output) assert "duration_ms" in data assert data["duration_ms"] >= 0 def test_duration_ms_with_filter(self, clean_repo: pathlib.Path) -> None: r = _run(clean_repo, "code", "code-check", "--json", "--filter", "error") data = json.loads(r.output) assert "duration_ms" in data assert data["duration_ms"] >= 0 # --------------------------------------------------------------------------- # TestExitCode — JSON includes exit_code mirroring process exit # --------------------------------------------------------------------------- class TestExitCode: """JSON exit_code must mirror the actual process exit code. Without --strict: always 0, even if violations exist. With --strict and error-severity violations: 1. """ def test_json_has_exit_code(self, clean_repo: pathlib.Path) -> None: r = _run(clean_repo, "code", "code-check", "--json") data = json.loads(r.output) assert "exit_code" in data def test_json_exit_code_zero_clean_no_strict(self, clean_repo: pathlib.Path) -> None: r = _run(clean_repo, "code", "code-check", "--json") assert r.exit_code == 0 data = json.loads(r.output) assert data["exit_code"] == 0 def test_json_exit_code_is_int(self, clean_repo: pathlib.Path) -> None: r = _run(clean_repo, "code", "code-check", "--json") data = json.loads(r.output) assert isinstance(data["exit_code"], int) def test_j_alias_exit_code_present(self, clean_repo: pathlib.Path) -> None: r = _run(clean_repo, "code", "code-check", "-j") data = json.loads(r.output) assert "exit_code" in data def test_exit_code_zero_with_violations_no_strict( self, violation_repo: pathlib.Path ) -> None: """Violations without --strict → process exits 0, JSON exit_code == 0.""" r = _run(violation_repo, "code", "code-check", "--json") assert r.exit_code == 0 data = json.loads(r.output) assert data["exit_code"] == 0 def test_exit_code_one_strict_with_errors(self, violation_repo: pathlib.Path) -> None: """--strict + error violations → process exits 1, JSON exit_code == 1.""" r = _run(violation_repo, "code", "code-check", "--json", "--strict") assert r.exit_code == 1 data = json.loads(r.output) assert data["exit_code"] == 1 def test_exit_code_mirrors_process_exit_clean(self, clean_repo: pathlib.Path) -> None: r = _run(clean_repo, "code", "code-check", "--json") data = json.loads(r.output) assert data["exit_code"] == r.exit_code def test_exit_code_mirrors_process_exit_strict_violations( self, violation_repo: pathlib.Path ) -> None: r = _run(violation_repo, "code", "code-check", "--json", "--strict") data = json.loads(r.output) assert data["exit_code"] == r.exit_code def test_exit_code_not_hardcoded_zero(self, violation_repo: pathlib.Path) -> None: """Prove exit_code is computed, not a literal 0.""" r = _run(violation_repo, "code", "code-check", "--json", "--strict") data = json.loads(r.output) assert data["exit_code"] != 0 # --------------------------------------------------------------------------- # TestTypedDicts — _CodeCheckOutputJson carries the new fields # --------------------------------------------------------------------------- class TestTypedDicts: """_CodeCheckOutputJson must carry exit_code and duration_ms annotations.""" def test_code_check_output_json_exists(self) -> None: from muse.cli.commands.code_check import _CodeCheckOutputJson # noqa: F401 def test_has_exit_code_annotation(self) -> None: from muse.cli.commands.code_check import _CodeCheckOutputJson assert "exit_code" in _CodeCheckOutputJson.__annotations__ def test_has_duration_ms_annotation(self) -> None: from muse.cli.commands.code_check import _CodeCheckOutputJson assert "duration_ms" in _CodeCheckOutputJson.__annotations__ def test_retains_violations_annotation(self) -> None: from muse.cli.commands.code_check import _CodeCheckOutputJson assert "violations" in _CodeCheckOutputJson.__annotations__ def test_retains_has_errors_annotation(self) -> None: from muse.cli.commands.code_check import _CodeCheckOutputJson assert "has_errors" in _CodeCheckOutputJson.__annotations__ def test_retains_commit_id_annotation(self) -> None: from muse.cli.commands.code_check import _CodeCheckOutputJson assert "commit_id" in _CodeCheckOutputJson.__annotations__ def test_retains_rules_checked_annotation(self) -> None: from muse.cli.commands.code_check import _CodeCheckOutputJson assert "rules_checked" in _CodeCheckOutputJson.__annotations__ # --------------------------------------------------------------------------- # TestAnsiSanitization — no escape codes in JSON output # --------------------------------------------------------------------------- class TestAnsiSanitization: """No ANSI escape sequences anywhere in the JSON output.""" def test_json_output_no_ansi_clean(self, clean_repo: pathlib.Path) -> None: r = _run(clean_repo, "code", "code-check", "--json") assert "\x1b" not in r.output def test_j_alias_output_no_ansi(self, clean_repo: pathlib.Path) -> None: r = _run(clean_repo, "code", "code-check", "-j") assert "\x1b" not in r.output def test_json_output_no_ansi_violations(self, violation_repo: pathlib.Path) -> None: r = _run(violation_repo, "code", "code-check", "--json") assert "\x1b" not in r.output # --------------------------------------------------------------------------- # TestPerformance — duration_ms under 2000 ms for a small repo # --------------------------------------------------------------------------- class TestPerformance: """duration_ms must stay under 2000 ms for small repos.""" def test_json_duration_under_2000ms(self, clean_repo: pathlib.Path) -> None: r = _run(clean_repo, "code", "code-check", "--json") data = json.loads(r.output) assert data["duration_ms"] < 2000 def test_j_alias_duration_under_2000ms(self, clean_repo: pathlib.Path) -> None: r = _run(clean_repo, "code", "code-check", "-j") data = json.loads(r.output) assert data["duration_ms"] < 2000 def test_duration_ms_is_float_not_int(self, clean_repo: pathlib.Path) -> None: r = _run(clean_repo, "code", "code-check", "--json") data = json.loads(r.output) assert isinstance(data["duration_ms"], float)