"""Comprehensive tests for ``muse code code-check``. Review findings addressed -------------------------- Bug fixes * Error messages (no commit, bad rules path) now write to stderr, not stdout. * Dead import ``CodeChecker`` removed from the CLI module. * Non-existent ``--rules`` file now produces an explicit error instead of silently falling back to built-in defaults. New capabilities * ``--filter error|warning|info``: show only violations of the given severity. * ``--diff ``: show only violations that are NEW since a reference commit (CI ratchet — fail only on regressions, not pre-existing noise). * ``--diff`` + ``--json``: emits ``diff_ref`` field in JSON payload. * ``make_report`` + ``diff_reports`` added to ``muse.core.invariants``. Test categories --------------- I Core behaviour — HEAD, specific commit, text output shape. II JSON output — required keys, has_errors, has_warnings, counts. III --filter flag — error/warning/info filtering, interaction with --strict. IV --diff flag — new-only violations, CI ratchet, JSON shape, bad ref. V --rules flag — custom file, path traversal, missing file. VI Security — path traversal, absolute paths, dot-dot escapes. VII Edge cases — no commits, fresh repo, header format, consistency. VIII Stress — 100 violation files, 50-file cycle chain, large rule sets. """ from __future__ import annotations from collections.abc import Mapping import argparse import json import pathlib import pytest from muse.core.types import Manifest, split_id from tests.cli_test_helper import CliRunner runner = CliRunner() cli = None # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _env(root: pathlib.Path) -> Manifest: return {"MUSE_REPO_ROOT": str(root)} def _run(root: pathlib.Path, *args: str) -> tuple[int, str]: result = runner.invoke(cli, list(args), env=_env(root), catch_exceptions=False) return result.exit_code, result.output + result.stderr def _run_unchecked(root: pathlib.Path, *args: str) -> tuple[int, str]: result = runner.invoke(cli, list(args), env=_env(root)) return result.exit_code, result.output + result.stderr def _stage_commit(root: pathlib.Path, msg: str = "commit") -> None: """Stage all modified files and commit.""" code, out = _run(root, "code", "add", ".") assert code == 0, f"add failed: {out}" code, out = _run(root, "commit", "-m", msg) assert code == 0, f"commit failed: {out}" def _complex_func(n_branches: int = 12) -> str: """Return Python source for a function with cyclomatic complexity > 10. Builds the string line-by-line to guarantee correct indentation — f-string multiline substitution does NOT propagate indentation to continuation lines, so the naive template approach produces broken Python. """ 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" # --------------------------------------------------------------------------- # Fixtures # --------------------------------------------------------------------------- @pytest.fixture() def clean_repo(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path: """Repo with one clean Python file (no violations).""" monkeypatch.chdir(tmp_path) r = runner.invoke(cli, ["init", "--domain", "code"], env=_env(tmp_path)) assert r.exit_code == 0, r.output (tmp_path / "clean.py").write_text("def greet(name: str) -> str:\n return f'hello {name}'\n") _stage_commit(tmp_path, "init clean") return tmp_path @pytest.fixture() def violation_repo(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path: """Repo with one clean commit then a second commit adding violations. Commit 1: clean.py only (no complexity violations). Commit 2: adds complex_mod.py with a function of complexity > 10. Both commits stage ALL files (muse code add .) so the snapshot contains the accumulated working-tree state, not just the delta. """ monkeypatch.chdir(tmp_path) r = runner.invoke(cli, ["init", "--domain", "code"], env=_env(tmp_path)) assert r.exit_code == 0, r.output # Commit 1 — simple function, no complexity violation (tmp_path / "clean.py").write_text("def _add(a: int, b: int) -> int:\n return a + b\n") _stage_commit(tmp_path, "init clean") # Commit 2 — add a high-complexity function (complexity violation) (tmp_path / "complex_mod.py").write_text(_complex_func(12)) _stage_commit(tmp_path, "add complex_mod") return tmp_path @pytest.fixture() def cycle_repo(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path: """Repo with two files that import each other (circular import → error).""" monkeypatch.chdir(tmp_path) r = runner.invoke(cli, ["init", "--domain", "code"], env=_env(tmp_path)) assert r.exit_code == 0, r.output (tmp_path / "alpha.py").write_text("import beta\n\ndef from_alpha() -> str:\n return 'alpha'\n") (tmp_path / "beta.py").write_text("import alpha\n\ndef from_beta() -> str:\n return 'beta'\n") _stage_commit(tmp_path, "add cycle") return tmp_path # =========================================================================== # I Core behaviour # =========================================================================== class TestCoreBehaviourI: def test_I1_clean_repo_exits_0(self, clean_repo: pathlib.Path) -> None: code, out = _run(clean_repo, "code", "code-check") assert code == 0, out def test_I2_clean_repo_reports_no_error_violations(self, clean_repo: pathlib.Path) -> None: """A simple single-file repo may have dead_export warnings (the exported function is never imported elsewhere), but it must have zero errors.""" _, out = _run(clean_repo, "code", "code-check", "--json") d = json.loads(out.strip()) assert d["has_errors"] is False def test_I3_complexity_violation_detected(self, violation_repo: pathlib.Path) -> None: _, out = _run(violation_repo, "code", "code-check") assert "complexity" in out.lower() or "heavy" in out.lower() def test_I4_specific_commit_id_accepted(self, violation_repo: pathlib.Path) -> None: # Get the current HEAD commit ID from JSON output code, out = _run(violation_repo, "code", "code-check", "--json") assert code == 0, out d = json.loads(out.strip().splitlines()[-1] if "\n" in out else out) commit_id = d["commit_id"] # Run with the explicit commit ID (short hex prefix, no algo: prefix) code2, out2 = _run(violation_repo, "code", "code-check", split_id(commit_id)[1][:8]) assert code2 == 0, out2 def test_I5_without_strict_exits_0_even_with_violations( self, violation_repo: pathlib.Path ) -> None: code, _ = _run(violation_repo, "code", "code-check") assert code == 0 def test_I6_strict_exits_1_on_error_severity(self, cycle_repo: pathlib.Path) -> None: """Circular imports produce error-severity violations; --strict must exit 1.""" code, out = _run_unchecked(cycle_repo, "code", "code-check", "--strict") assert code == 1, f"expected exit 1, got {code}:\n{out}" def test_I7_text_output_header_contains_commit_short( self, clean_repo: pathlib.Path ) -> None: code, out = _run(clean_repo, "code", "code-check", "--json") d = json.loads(out.strip().splitlines()[-1] if "\n" in out else out) commit_prefix = d["commit_id"][:8] _, text_out = _run(clean_repo, "code", "code-check") assert commit_prefix in text_out def test_I8_error_to_no_commit_goes_to_stderr( self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch ) -> None: """When there are no commits, error must go to stderr, not stdout.""" monkeypatch.chdir(tmp_path) r = runner.invoke(cli, ["init", "--domain", "code"], env=_env(tmp_path)) assert r.exit_code == 0, r.output result = runner.invoke(cli, ["code", "code-check"], env=_env(tmp_path)) assert result.exit_code != 0 assert "❌" in result.stderr or "no commit" in result.stderr.lower() # =========================================================================== # II JSON output # =========================================================================== class TestJsonOutputII: def test_II1_json_has_required_keys(self, clean_repo: pathlib.Path) -> None: code, out = _run(clean_repo, "code", "code-check", "--json") assert code == 0, out d = json.loads(out.strip()) for key in ("commit_id", "domain", "violations", "rules_checked", "has_errors", "has_warnings"): assert key in d, f"missing key: {key}" def test_II2_json_violations_is_list(self, clean_repo: pathlib.Path) -> None: _, out = _run(clean_repo, "code", "code-check", "--json") d = json.loads(out.strip()) assert isinstance(d["violations"], list) def test_II3_json_has_errors_true_when_circular(self, cycle_repo: pathlib.Path) -> None: _, out = _run(cycle_repo, "code", "code-check", "--json") d = json.loads(out.strip()) assert d["has_errors"] is True def test_II4_json_has_errors_false_for_clean(self, clean_repo: pathlib.Path) -> None: _, out = _run(clean_repo, "code", "code-check", "--json") d = json.loads(out.strip()) assert d["has_errors"] is False def test_II5_json_violation_entries_have_required_fields( self, violation_repo: pathlib.Path ) -> None: _, out = _run(violation_repo, "code", "code-check", "--json") d = json.loads(out.strip()) for v in d["violations"]: for field in ("rule_name", "severity", "address", "description"): assert field in v, f"violation missing field {field!r}: {v}" def test_II6_strict_json_exits_1_on_error(self, cycle_repo: pathlib.Path) -> None: code, out = _run_unchecked(cycle_repo, "code", "code-check", "--json", "--strict") assert code == 1, out # Output is still valid JSON d = json.loads(out.strip()) assert d["has_errors"] is True def test_II7_json_rules_checked_nonzero(self, clean_repo: pathlib.Path) -> None: _, out = _run(clean_repo, "code", "code-check", "--json") d = json.loads(out.strip()) assert d["rules_checked"] > 0 def test_II8_json_domain_is_code(self, clean_repo: pathlib.Path) -> None: _, out = _run(clean_repo, "code", "code-check", "--json") d = json.loads(out.strip()) assert d["domain"] == "code" # =========================================================================== # III --filter flag # =========================================================================== class TestFilterFlagIII: def test_III1_filter_error_hides_warnings(self, violation_repo: pathlib.Path) -> None: """complexity_gate produces warnings; --filter error should show none.""" _, out = _run(violation_repo, "code", "code-check", "--filter", "error", "--json") d = json.loads(out.strip()) for v in d["violations"]: assert v["severity"] == "error", f"non-error slipped through: {v}" def test_III2_filter_warning_hides_errors(self, cycle_repo: pathlib.Path) -> None: """Circular imports produce errors; --filter warning should hide them.""" _, out = _run(cycle_repo, "code", "code-check", "--filter", "warning", "--json") d = json.loads(out.strip()) for v in d["violations"]: assert v["severity"] == "warning", f"non-warning slipped through: {v}" def test_III3_filter_error_clean_repo_zero_violations( self, clean_repo: pathlib.Path ) -> None: code, out = _run(clean_repo, "code", "code-check", "--filter", "error", "--json") assert code == 0, out d = json.loads(out.strip()) assert d["violations"] == [] def test_III4_filter_error_strict_still_exits_1(self, cycle_repo: pathlib.Path) -> None: code, out = _run_unchecked( cycle_repo, "code", "code-check", "--filter", "error", "--strict" ) assert code == 1, out def test_III5_filter_warning_strict_exits_0_for_cycle_repo( self, cycle_repo: pathlib.Path ) -> None: """Filtering to warnings only means the error violations are hidden; --strict should NOT fire since the filtered report has no errors.""" code, _ = _run_unchecked( cycle_repo, "code", "code-check", "--filter", "warning", "--strict" ) assert code == 0 def test_III6_filter_appears_in_text_header(self, clean_repo: pathlib.Path) -> None: _, out = _run(clean_repo, "code", "code-check", "--filter", "error") assert "[error only]" in out def test_III7_filter_json_has_severity_filter_key(self, clean_repo: pathlib.Path) -> None: _, out = _run(clean_repo, "code", "code-check", "--filter", "error", "--json") d = json.loads(out.strip()) assert d.get("severity_filter") == "error" # =========================================================================== # IV --diff flag # =========================================================================== class TestDiffFlagIV: def test_IV1_diff_against_self_shows_zero_new_violations( self, violation_repo: pathlib.Path ) -> None: """Comparing HEAD vs HEAD should produce no new violations.""" # Get HEAD commit ID _, jout = _run(violation_repo, "code", "code-check", "--json") head_id = json.loads(jout.strip())["commit_id"] _, out = _run(violation_repo, "code", "code-check", "--diff", head_id, "--json") d = json.loads(out.strip()) assert d["violations"] == [], f"expected 0 new violations, got: {d['violations']}" def test_IV2_diff_shows_only_new_violations(self, violation_repo: pathlib.Path) -> None: """violation_repo has 2 commits: clean then complex_mod. --diff against the first commit should surface the new complexity violations.""" # Get the first (clean) commit ID via log code, log_out = _run(violation_repo, "log", "--json") assert code == 0, log_out commits = json.loads(log_out)["commits"] assert len(commits) >= 2 # commits is newest-first; the second entry is the clean commit clean_commit_id = commits[1]["commit_id"] code, out = _run( violation_repo, "code", "code-check", "--diff", clean_commit_id, "--json" ) assert code == 0, out d = json.loads(out.strip()) # There should be some new violations (complexity warnings from complex_mod.py) assert len(d["violations"]) > 0 def test_IV3_diff_json_has_diff_ref_key(self, violation_repo: pathlib.Path) -> None: _, jout = _run(violation_repo, "code", "code-check", "--json") head_id = json.loads(jout.strip())["commit_id"] _, out = _run(violation_repo, "code", "code-check", "--diff", head_id, "--json") d = json.loads(out.strip()) assert "diff_ref" in d assert d["diff_ref"] == head_id def test_IV4_diff_text_header_contains_diff_label( self, violation_repo: pathlib.Path ) -> None: _, jout = _run(violation_repo, "code", "code-check", "--json") head_id = json.loads(jout.strip())["commit_id"] _, out = _run(violation_repo, "code", "code-check", "--diff", head_id) assert "new since" in out.lower() def test_IV5_diff_with_bad_ref_exits_1(self, clean_repo: pathlib.Path) -> None: code, out = _run_unchecked( clean_repo, "code", "code-check", "--diff", "deadbeef00000000" ) assert code == 1, out def test_IV6_diff_strict_fires_only_on_new_errors( self, violation_repo: pathlib.Path ) -> None: """Diff vs self → 0 new violations → --strict must NOT exit 1.""" _, jout = _run(violation_repo, "code", "code-check", "--json") head_id = json.loads(jout.strip())["commit_id"] code, _ = _run_unchecked( violation_repo, "code", "code-check", "--diff", head_id, "--strict" ) assert code == 0 def test_IV7_diff_and_filter_combined(self, violation_repo: pathlib.Path) -> None: """--diff and --filter compose: only new + matching-severity violations.""" _, jout = _run(violation_repo, "code", "code-check", "--json") head_id = json.loads(jout.strip())["commit_id"] code, out = _run( violation_repo, "code", "code-check", "--diff", head_id, "--filter", "error", "--json" ) assert code == 0, out d = json.loads(out.strip()) assert d["violations"] == [] assert d.get("severity_filter") == "error" assert "diff_ref" in d # =========================================================================== # V --rules flag # =========================================================================== class TestRulesFlagV: def test_V1_custom_rules_file_used(self, clean_repo: pathlib.Path) -> None: """A custom rules file with zero rules produces zero violations and rules_checked=0. An explicit empty file must NOT fall back to built-in defaults. """ rules = clean_repo / "rules.toml" rules.write_text("# no rules\n") code, out = _run(clean_repo, "code", "code-check", "--rules", "rules.toml", "--json") assert code == 0, out d = json.loads(out.strip()) # Zero rules → zero violations regardless of file content assert d["violations"] == [] assert d["rules_checked"] == 0 def test_V2_custom_rules_complexity_threshold_0(self, clean_repo: pathlib.Path) -> None: """Threshold of 0 flags every function (complexity ≥ 1 > 0).""" rules = clean_repo / "tight.toml" rules.write_text( '[[rule]]\nname = "strict_complexity"\nseverity = "error"\n' 'rule_type = "max_complexity"\n[rule.params]\nthreshold = 0\n' ) _, out = _run(clean_repo, "code", "code-check", "--rules", "tight.toml", "--json") d = json.loads(out.strip()) assert len(d["violations"]) > 0 def test_V3_missing_rules_file_exits_1(self, clean_repo: pathlib.Path) -> None: code, out = _run_unchecked( clean_repo, "code", "code-check", "--rules", "nonexistent.toml" ) assert code == 1, out def test_V4_path_traversal_in_rules_exits_1(self, clean_repo: pathlib.Path) -> None: code, out = _run_unchecked( clean_repo, "code", "code-check", "--rules", "../../etc/passwd" ) assert code == 1, out assert "❌" in out or "traversal" in out.lower() or "escape" in out.lower() def test_V5_rules_zero_rules_rules_checked_is_0(self, clean_repo: pathlib.Path) -> None: rules = clean_repo / "empty.toml" rules.write_text("") _, out = _run(clean_repo, "code", "code-check", "--rules", "empty.toml", "--json") d = json.loads(out.strip()) assert d["rules_checked"] == 0 # =========================================================================== # VI Security # =========================================================================== class TestSecurityVI: def test_VI1_absolute_path_outside_repo_rejected(self, clean_repo: pathlib.Path) -> None: """An absolute path to /etc/hosts must be rejected by contain_path.""" code, out = _run_unchecked( clean_repo, "code", "code-check", "--rules", "/etc/hosts" ) assert code == 1, out assert "❌" in out or "traversal" in out.lower() or "escape" in out.lower() def test_VI2_dotdot_traversal_rejected(self, clean_repo: pathlib.Path) -> None: code, out = _run_unchecked( clean_repo, "code", "code-check", "--rules", "../../../etc/passwd" ) assert code == 1, out def test_VI3_null_byte_in_rules_path_rejected(self, clean_repo: pathlib.Path) -> None: """Null bytes in file paths must not silently succeed.""" code, _ = _run_unchecked( clean_repo, "code", "code-check", "--rules", "rules\x00.toml" ) assert code == 1 def test_VI4_error_output_goes_to_stderr(self, clean_repo: pathlib.Path) -> None: """Path traversal error must appear on stderr, not stdout.""" result = runner.invoke( cli, ["code", "code-check", "--rules", "../../etc/passwd"], env=_env(clean_repo), ) assert result.exit_code == 1 assert "❌" in result.stderr or "traversal" in result.stderr.lower() def test_VI5_repo_without_commits_does_not_panic( self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch ) -> None: monkeypatch.chdir(tmp_path) r = runner.invoke(cli, ["init", "--domain", "code"], env=_env(tmp_path)) assert r.exit_code == 0, r.output result = runner.invoke(cli, ["code", "code-check"], env=_env(tmp_path)) assert result.exit_code == 1 # Must not raise an unhandled exception assert result.exception is None # =========================================================================== # VII Edge cases # =========================================================================== class TestEdgeCasesVII: def test_VII1_single_file_dead_export_warning(self, clean_repo: pathlib.Path) -> None: """greet in clean.py is never imported — expect dead_exports warning.""" _, out = _run(clean_repo, "code", "code-check", "--json") d = json.loads(out.strip()) # dead_exports may or may not fire depending on how the rule counts imports; # the important invariant is that we get a valid report. assert isinstance(d["violations"], list) def test_VII2_text_and_json_violation_counts_agree( self, violation_repo: pathlib.Path ) -> None: code_t, text_out = _run(violation_repo, "code", "code-check") code_j, json_out = _run(violation_repo, "code", "code-check", "--json") assert code_t == 0 assert code_j == 0 d = json.loads(json_out.strip()) count = len(d["violations"]) # Text output summary line contains the violation count assert f"{count} violation" in text_out def test_VII3_cycle_repo_no_cycles_rule_fires(self, cycle_repo: pathlib.Path) -> None: _, out = _run(cycle_repo, "code", "code-check", "--json") d = json.loads(out.strip()) cycle_violations = [v for v in d["violations"] if v["rule_name"] == "no_cycles"] assert len(cycle_violations) > 0 def test_VII4_commit_id_in_json_is_full_sha(self, clean_repo: pathlib.Path) -> None: _, out = _run(clean_repo, "code", "code-check", "--json") d = json.loads(out.strip()) assert len(d["commit_id"]) >= 40 def test_VII5_filter_and_json_compose_cleanly(self, cycle_repo: pathlib.Path) -> None: """--filter warning + --json should produce valid JSON with only warnings.""" code, out = _run(cycle_repo, "code", "code-check", "--filter", "warning", "--json") assert code == 0, out d = json.loads(out.strip()) for v in d["violations"]: assert v["severity"] == "warning" def test_VII6_rules_checked_matches_builtin_default_count( self, clean_repo: pathlib.Path ) -> None: _, out = _run(clean_repo, "code", "code-check", "--json") d = json.loads(out.strip()) # Built-in defaults have 3 rules assert d["rules_checked"] == 3 def test_VII7_second_run_produces_identical_output( self, clean_repo: pathlib.Path ) -> None: """Output is deterministic: two back-to-back runs must be identical.""" _, out1 = _run(clean_repo, "code", "code-check", "--json") _, out2 = _run(clean_repo, "code", "code-check", "--json") d1 = json.loads(out1.strip()) d2 = json.loads(out2.strip()) assert d1["violations"] == d2["violations"] assert d1["rules_checked"] == d2["rules_checked"] # =========================================================================== # VIII Stress # =========================================================================== class TestStressVIII: def test_VIII1_100_complex_files_all_violations_reported( self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch ) -> None: """100 files each containing a high-complexity function — all should fire.""" monkeypatch.chdir(tmp_path) r = runner.invoke(cli, ["init", "--domain", "code"], env=_env(tmp_path)) assert r.exit_code == 0, r.output for i in range(100): # Each file has a uniquely-named complex function src = f"def heavy_{i}(x: int) -> int:\n" src += " if x == 0:\n return 0\n" for j in range(1, 13): src += f" elif x == {j}:\n return {j}\n" src += " return -1\n" (tmp_path / f"mod_{i:03d}.py").write_text(src) _stage_commit(tmp_path, "100 complex files") code, out = _run(tmp_path, "code", "code-check", "--json") assert code == 0, out d = json.loads(out.strip()) complexity_violations = [ v for v in d["violations"] if v["rule_name"] == "complexity_gate" ] # Every file should have at least one complexity violation assert len(complexity_violations) >= 100 def test_VIII2_large_custom_rule_set( self, clean_repo: pathlib.Path ) -> None: """A TOML file with 50 identical rules — all 50 should be evaluated.""" rules_toml = "" for i in range(50): rules_toml += ( f'[[rule]]\nname = "complexity_{i}"\nseverity = "warning"\n' f'rule_type = "max_complexity"\n[rule.params]\nthreshold = 100\n\n' ) (clean_repo / "big_rules.toml").write_text(rules_toml) code, out = _run( clean_repo, "code", "code-check", "--rules", "big_rules.toml", "--json" ) assert code == 0, out d = json.loads(out.strip()) assert d["rules_checked"] == 50 def test_VIII3_diff_on_100_file_repo( self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch ) -> None: """--diff on a 100-file repo must complete and return valid JSON.""" monkeypatch.chdir(tmp_path) r = runner.invoke(cli, ["init", "--domain", "code"], env=_env(tmp_path)) assert r.exit_code == 0, r.output # Commit 1: 100 clean files for i in range(100): (tmp_path / f"mod_{i:03d}.py").write_text(f"def fn_{i}() -> int:\n return {i}\n") _stage_commit(tmp_path, "100 clean files") # Save first commit ID code, jout = _run(tmp_path, "code", "code-check", "--json") assert code == 0 base_commit = json.loads(jout.strip())["commit_id"] # Commit 2: add one complex file (tmp_path / "complex_new.py").write_text(_complex_func(15)) _stage_commit(tmp_path, "add complex file") code, out = _run( tmp_path, "code", "code-check", "--diff", base_commit, "--json" ) assert code == 0, out d = json.loads(out.strip()) # Should show new violations only from complex_new.py addresses = [v["address"] for v in d["violations"]] assert any("complex_new" in a for a in addresses) # Should NOT include violations from other files (they existed in base) non_new = [a for a in addresses if "complex_new" not in a] assert non_new == [], f"unexpected non-new violations: {non_new}" def test_VIII4_stress_diff_vs_self_always_zero( self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch ) -> None: """50 iterations of diff-vs-self — always 0 new violations.""" monkeypatch.chdir(tmp_path) r = runner.invoke(cli, ["init", "--domain", "code"], env=_env(tmp_path)) assert r.exit_code == 0, r.output for i in range(20): (tmp_path / f"mod_{i}.py").write_text(_complex_func(12)) _stage_commit(tmp_path, "bulk commit") _, jout = _run(tmp_path, "code", "code-check", "--json") head = json.loads(jout.strip())["commit_id"] for _ in range(50): _, out = _run(tmp_path, "code", "code-check", "--diff", head, "--json") d = json.loads(out.strip()) assert d["violations"] == [], "diff vs self must always be empty" def test_VIII5_filter_stress_all_severities_preserved( self, tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch ) -> None: """Filter by each severity and union equals total violations.""" monkeypatch.chdir(tmp_path) r = runner.invoke(cli, ["init", "--domain", "code"], env=_env(tmp_path)) assert r.exit_code == 0, r.output (tmp_path / "alpha.py").write_text("import beta\n\ndef a() -> None: pass\n") (tmp_path / "beta.py").write_text("import alpha\n\ndef b() -> None: pass\n") _stage_commit(tmp_path, "cycle + warnings") _, total_out = _run(tmp_path, "code", "code-check", "--json") total = json.loads(total_out.strip()) total_count = len(total["violations"]) union: list[Mapping[str, str]] = [] for sev in ("error", "warning", "info"): _, sout = _run(tmp_path, "code", "code-check", "--filter", sev, "--json") d = json.loads(sout.strip()) for v in d["violations"]: assert v["severity"] == sev union.extend(d["violations"]) assert len(union) == total_count, ( f"union of filtered severities ({len(union)}) != total ({total_count})" ) # --------------------------------------------------------------------------- # TestRegisterFlags # --------------------------------------------------------------------------- class TestRegisterFlags: """register() wires --json / -j correctly.""" def _parse(self, *args: str) -> argparse.Namespace: from muse.cli.commands.code_check import register p = argparse.ArgumentParser() sub = p.add_subparsers() register(sub) return p.parse_args(["code-check", *args]) def test_default_json_out_is_false(self) -> None: ns = self._parse() assert ns.json_out is False def test_json_flag_sets_json_out(self) -> None: ns = self._parse("--json") assert ns.json_out is True def test_j_shorthand_sets_json_out(self) -> None: ns = self._parse("-j") assert ns.json_out is True