"""Tests for muse check-ref-format. Coverage tiers -------------- Unit — _CheckResult schema, _CheckRefFormatResult schema, _RulesDict schema, _RULES content correctness Integration — valid names (simple, namespaced, hierarchical, edge-length), invalid names (each rule: leading-dot, trailing-dot, consecutive-dot, leading-slash, trailing-slash, consecutive-slash, null-byte, backslash, tab, CR, LF, empty, too-long), mixed validity, all-valid exit 0, any-invalid exit 1, --quiet mode, --format text, --json shorthand, --rules (json+text), --stdin (read, blanks/comments skipped, combined, empty errors), valid_count / invalid_count fields, error output to stderr Security — ANSI in name sanitized in text output, ANSI in --rules safe, format error to stderr, no traceback on bad format, null-byte name rejected cleanly Stress — 500 valid names, 500 invalid names, 200 sequential calls, 255-char max-length name, 256-char over-length name, 1000-name stdin batch """ from __future__ import annotations import json import pathlib import pytest from tests.cli_test_helper import CliRunner, InvokeResult from muse.cli.commands.check_ref_format import ( _RULES, _CheckRefFormatJson as _CheckRefFormatResult, _CheckResult, _RulesDict, ) cli = None # argparse-based CLI; CliRunner ignores this arg runner = CliRunner() # --------------------------------------------------------------------------- # Helper — check-ref-format needs no repo (pure CPU) # --------------------------------------------------------------------------- def _crf(*args: str, stdin: str | None = None) -> InvokeResult: """Invoke check-ref-format with no MUSE_REPO_ROOT constraint.""" return runner.invoke(cli, ["check-ref-format", *args], input=stdin) # --------------------------------------------------------------------------- # Unit — schemas and constants # --------------------------------------------------------------------------- class TestSchemas: def test_check_result_fields(self) -> None: keys = _CheckResult.__annotations__ assert "name" in keys assert "valid" in keys assert "error" in keys def test_check_ref_format_result_fields(self) -> None: keys = _CheckRefFormatResult.__annotations__ assert "results" in keys assert "all_valid" in keys assert "valid_count" in keys assert "invalid_count" in keys def test_rules_dict_fields(self) -> None: keys = _RulesDict.__annotations__ assert "max_length" in keys assert "forbidden_chars" in keys assert "forbidden_patterns" in keys assert "notes" in keys def test_check_ref_format_result_has_elapsed(self) -> None: assert "duration_ms" in _CheckRefFormatResult.__annotations__ def test_check_ref_format_result_has_exit_code(self) -> None: assert "exit_code" in _CheckRefFormatResult.__annotations__ def test_rules_max_length(self) -> None: assert _RULES["max_length"] == 255 def test_rules_forbidden_chars_includes_c0_controls(self) -> None: """Null byte is covered by the C0 controls group.""" forbidden_chars = _RULES["forbidden_chars"] # Either the literal null byte or a descriptive C0 group string is acceptable. has_null = "\x00" in forbidden_chars has_c0_group = any("C0" in s or "0x00" in s for s in forbidden_chars) assert has_null or has_c0_group, "null byte must be covered in forbidden_chars" def test_rules_forbidden_chars_includes_backslash(self) -> None: assert "\\" in _RULES["forbidden_chars"] def test_rules_forbidden_patterns_not_empty(self) -> None: assert len(_RULES["forbidden_patterns"]) >= 4 def test_rules_notes_mentions_slash_ok(self) -> None: assert "/" in _RULES["notes"] or "slash" in _RULES["notes"].lower() # --------------------------------------------------------------------------- # Integration — valid names # --------------------------------------------------------------------------- class TestValidNames: def test_simple_name(self) -> None: r = _crf("--json", "main") assert r.exit_code == 0 data = json.loads(r.output) assert data["all_valid"] is True assert data["results"][0]["valid"] is True assert data["results"][0]["error"] is None def test_namespaced_name(self) -> None: r = _crf("--json", "feat/my-branch") assert r.exit_code == 0 data = json.loads(r.output) assert data["all_valid"] is True def test_deeply_hierarchical_name(self) -> None: r = _crf("--json", "team/feat/PROJ-42/wip") assert r.exit_code == 0 assert json.loads(r.output)["all_valid"] is True def test_name_with_numbers(self) -> None: r = _crf("release-2026-03-27") assert r.exit_code == 0 def test_single_char_name(self) -> None: r = _crf("--json", "x") assert r.exit_code == 0 def test_255_char_name_valid(self) -> None: name = "a" * 255 r = _crf("--json", name) assert r.exit_code == 0 assert json.loads(r.output)["all_valid"] is True def test_all_valid_exits_zero(self) -> None: r = _crf("--json", "feat/a", "fix/b", "dev", "main") assert r.exit_code == 0 data = json.loads(r.output) assert data["all_valid"] is True assert data["valid_count"] == 4 assert data["invalid_count"] == 0 # --------------------------------------------------------------------------- # Integration — invalid names (each rule) # --------------------------------------------------------------------------- class TestInvalidNames: def test_consecutive_dots(self) -> None: r = _crf("--json", "bad..name") assert r.exit_code != 0 data = json.loads(r.output) assert data["all_valid"] is False assert data["results"][0]["valid"] is False assert data["results"][0]["error"] is not None def test_leading_dot(self) -> None: r = _crf("--json", ".hidden") assert r.exit_code != 0 assert json.loads(r.output)["all_valid"] is False def test_trailing_dot(self) -> None: r = _crf("--json", "trailing.") assert r.exit_code != 0 assert json.loads(r.output)["all_valid"] is False def test_leading_slash(self) -> None: r = _crf("/bad") assert r.exit_code != 0 def test_trailing_slash(self) -> None: r = _crf("bad/") assert r.exit_code != 0 def test_consecutive_slashes(self) -> None: r = _crf("bad//name") assert r.exit_code != 0 def test_null_byte(self) -> None: r = _crf("bad\x00name") assert r.exit_code != 0 def test_backslash(self) -> None: r = _crf("bad\\name") assert r.exit_code != 0 def test_tab_character(self) -> None: r = _crf("bad\tname") assert r.exit_code != 0 def test_carriage_return(self) -> None: r = _crf("bad\rname") assert r.exit_code != 0 def test_newline(self) -> None: r = _crf("bad\nname") assert r.exit_code != 0 def test_empty_name(self) -> None: r = _crf("--json", "") assert r.exit_code != 0 def test_256_char_name_too_long(self) -> None: name = "a" * 256 r = _crf("--json", name) assert r.exit_code != 0 assert json.loads(r.output)["all_valid"] is False def test_any_invalid_exits_nonzero(self) -> None: r = _crf("--json", "good", "bad..name") assert r.exit_code != 0 def test_invalid_count_correct(self) -> None: r = _crf("--json", "good", "bad..name", ".also-bad") data = json.loads(r.output) assert data["valid_count"] == 1 assert data["invalid_count"] == 2 # --------------------------------------------------------------------------- # Integration — valid_count / invalid_count fields # --------------------------------------------------------------------------- class TestCountFields: def test_all_valid_counts(self) -> None: r = _crf("--json", "a", "b", "c") data = json.loads(r.output) assert data["valid_count"] == 3 assert data["invalid_count"] == 0 def test_all_invalid_counts(self) -> None: r = _crf("--json", "..a", "..b") data = json.loads(r.output) assert data["valid_count"] == 0 assert data["invalid_count"] == 2 def test_mixed_counts(self) -> None: r = _crf("--json", "good", "..bad", "also-good", ".also-bad") data = json.loads(r.output) assert data["valid_count"] == 2 assert data["invalid_count"] == 2 # --------------------------------------------------------------------------- # Integration — --quiet mode # --------------------------------------------------------------------------- class TestQuietMode: def test_quiet_valid_exits_zero_no_output(self) -> None: r = _crf("--quiet", "main") assert r.exit_code == 0 assert r.output.strip() == "" def test_quiet_invalid_exits_nonzero_no_output(self) -> None: r = _crf("-q", "bad..name") assert r.exit_code != 0 assert r.output.strip() == "" def test_quiet_mixed_exits_nonzero(self) -> None: r = _crf("--quiet", "good", "bad..name") assert r.exit_code != 0 # --------------------------------------------------------------------------- # Integration — text output # --------------------------------------------------------------------------- class TestTextOutput: def test_valid_shows_ok(self) -> None: r = _crf("main") assert r.exit_code == 0 assert "ok" in r.output def test_invalid_shows_fail(self) -> None: r = _crf("bad..name") assert r.exit_code != 0 assert "FAIL" in r.output def test_mixed_shows_both(self) -> None: r = _crf("good", "bad..name") assert r.exit_code != 0 assert "ok" in r.output assert "FAIL" in r.output def test_json_shorthand_alias(self) -> None: r = _crf("--json", "main") assert r.exit_code == 0 data = json.loads(r.output) assert "results" in data # --------------------------------------------------------------------------- # Integration — --rules # --------------------------------------------------------------------------- class TestRules: def test_rules_json_output(self) -> None: r = _crf("--json", "--rules") assert r.exit_code == 0 data = json.loads(r.output) assert "max_length" in data assert "forbidden_chars" in data assert "forbidden_patterns" in data assert "notes" in data def test_rules_max_length_is_255(self) -> None: r = _crf("--json", "--rules") data = json.loads(r.output) assert data["max_length"] == 255 def test_rules_text_format(self) -> None: r = _crf("--rules") assert r.exit_code == 0 assert "max_length" in r.output assert "forbidden" in r.output.lower() def test_rules_needs_no_names(self) -> None: """--rules exits cleanly with no name arguments.""" r = _crf("--json", "--rules") assert r.exit_code == 0 def test_rules_json_is_parseable(self) -> None: r = _crf("--rules", "--json") assert r.exit_code == 0 data = json.loads(r.output) assert isinstance(data["forbidden_chars"], list) assert isinstance(data["forbidden_patterns"], list) # --------------------------------------------------------------------------- # Integration — --stdin # --------------------------------------------------------------------------- class TestStdinMode: def test_stdin_reads_names(self) -> None: r = _crf("--json", "--stdin", stdin="main\ndev\n") assert r.exit_code == 0 data = json.loads(r.output) assert data["valid_count"] == 2 def test_stdin_skips_blank_lines(self) -> None: r = _crf("--json", "--stdin", stdin="\nmain\n\ndev\n\n") assert r.exit_code == 0 data = json.loads(r.output) assert len(data["results"]) == 2 def test_stdin_skips_comments(self) -> None: r = _crf("--json", "--stdin", stdin="# a comment\nmain\n") assert r.exit_code == 0 data = json.loads(r.output) assert len(data["results"]) == 1 def test_stdin_combined_with_positional(self) -> None: r = _crf("--json", "main", "--stdin", stdin="dev\n") assert r.exit_code == 0 data = json.loads(r.output) assert len(data["results"]) == 2 def test_stdin_invalid_name_from_stdin(self) -> None: r = _crf("--json", "--stdin", stdin="bad..name\n") assert r.exit_code != 0 data = json.loads(r.output) assert data["all_valid"] is False def test_stdin_empty_with_no_positional_errors(self) -> None: r = _crf("--stdin", stdin="") assert r.exit_code != 0 assert r.stdout_bytes == b"" def test_stdin_only_comments_errors(self) -> None: r = _crf("--stdin", stdin="# comment only\n") assert r.exit_code != 0 assert r.stdout_bytes == b"" # --------------------------------------------------------------------------- # Security # --------------------------------------------------------------------------- class TestSecurity: def test_ansi_in_name_stripped_text_output(self) -> None: """ANSI escape in a branch name must not appear raw in text output.""" ansi_name = "\x1b[31mbadname\x1b[0m" r = _crf(ansi_name) assert "\x1b" not in r.output def test_ansi_in_error_message_stripped(self) -> None: """If the error message echoes the name, it must be sanitized.""" ansi_name = "\x1b[31m.leading\x1b[0m" r = _crf(ansi_name) assert "\x1b" not in r.output def test_no_args_no_traceback(self) -> None: r = _crf() assert "Traceback" not in r.output assert "Traceback" not in r.stderr def test_null_byte_name_rejected_cleanly(self) -> None: r = _crf("bad\x00name") assert r.exit_code != 0 assert "Traceback" not in r.output assert "Traceback" not in r.stderr def test_no_args_error_to_stderr(self) -> None: r = _crf() assert r.exit_code != 0 assert r.stdout_bytes == b"" def test_rules_json_no_ansi(self) -> None: r = _crf("--rules") assert "\x1b" not in r.output # --------------------------------------------------------------------------- # Stress # --------------------------------------------------------------------------- class TestStress: def test_500_valid_names(self) -> None: names = [f"feat/task-{i:04d}" for i in range(500)] r = _crf("--json", *names) assert r.exit_code == 0 data = json.loads(r.output) assert data["valid_count"] == 500 assert data["invalid_count"] == 0 def test_500_invalid_names(self) -> None: names = [f"bad..{i}" for i in range(500)] r = _crf("--json", *names) assert r.exit_code != 0 data = json.loads(r.output) assert data["invalid_count"] == 500 def test_200_sequential_calls(self) -> None: for _ in range(200): r = _crf("--json", "main") assert r.exit_code == 0 def test_max_length_boundary(self) -> None: valid = "a" * 255 invalid = "a" * 256 r = _crf("--json", valid, invalid) data = json.loads(r.output) assert data["valid_count"] == 1 assert data["invalid_count"] == 1 def test_1000_name_stdin_batch(self) -> None: stdin_input = "\n".join(f"feat/task-{i}" for i in range(1000)) + "\n" r = _crf("--json", "--stdin", stdin=stdin_input) assert r.exit_code == 0 data = json.loads(r.output) assert data["valid_count"] == 1000 # --------------------------------------------------------------------------- # duration_ms # --------------------------------------------------------------------------- class TestElapsed: def test_elapsed_in_default_json(self) -> None: r = _crf("--json", "main") data = json.loads(r.output) assert "duration_ms" in data assert isinstance(data["duration_ms"], float) assert data["duration_ms"] >= 0.0 def test_elapsed_in_rules_json(self) -> None: r = _crf("--json", "--rules") data = json.loads(r.output) assert "duration_ms" in data assert isinstance(data["duration_ms"], float) def test_elapsed_absent_from_text_output(self) -> None: r = _crf("main") assert "duration_ms" not in r.output # --------------------------------------------------------------------------- # exit_code in JSON # --------------------------------------------------------------------------- class TestExitCode: def test_exit_code_0_when_all_valid(self) -> None: data = json.loads(_crf("--json", "main", "feat/x").output) assert data["exit_code"] == 0 def test_exit_code_1_when_any_invalid(self) -> None: r = _crf("--json", "good", "bad..name") data = json.loads(r.output) assert data["exit_code"] == 1 assert r.exit_code == 1 def test_exit_code_matches_process_exit(self) -> None: """exit_code in JSON always matches the process exit code.""" for names, expected in [ (["main"], 0), (["bad..name"], 1), (["main", "bad..name"], 1), ]: r = _crf("--json", *names) data = json.loads(r.output) assert data["exit_code"] == expected assert r.exit_code == expected def test_exit_code_in_rules_json(self) -> None: data = json.loads(_crf("--json", "--rules").output) assert "exit_code" in data assert data["exit_code"] == 0 # --------------------------------------------------------------------------- # --invalid-only # --------------------------------------------------------------------------- class TestInvalidOnly: def test_filters_to_invalid_names(self) -> None: r = _crf("--json", "--invalid-only", "main", "bad..name", "feat/x", ".bad") assert r.exit_code != 0 data = json.loads(r.output) assert len(data["results"]) == 2 assert all(not res["valid"] for res in data["results"]) names = [res["name"] for res in data["results"]] assert "bad..name" in names assert ".bad" in names def test_empty_results_when_all_valid(self) -> None: r = _crf("--json", "--invalid-only", "main", "feat/x") assert r.exit_code == 0 data = json.loads(r.output) assert data["results"] == [] assert data["all_valid"] is True def test_all_results_when_all_invalid(self) -> None: r = _crf("--json", "--invalid-only", "bad..a", "bad..b") assert r.exit_code != 0 data = json.loads(r.output) assert len(data["results"]) == 2 def test_counts_reflect_full_set_not_filtered(self) -> None: """valid_count and invalid_count reflect the original full batch.""" r = _crf("--json", "--invalid-only", "main", "bad..name", "feat/x") data = json.loads(r.output) assert data["valid_count"] == 2 assert data["invalid_count"] == 1 def test_text_format(self) -> None: r = _crf("--invalid-only", "main", "bad..name") assert r.exit_code != 0 assert "main" not in r.output assert "FAIL" in r.output assert "bad..name" in r.output def test_stdin_compatible(self) -> None: r = _crf("--json", "--invalid-only", "--stdin", stdin="main\nbad..name\n") data = json.loads(r.output) assert len(data["results"]) == 1 assert data["results"][0]["name"] == "bad..name" def test_incompatible_with_quiet(self) -> None: r = _crf("--invalid-only", "--quiet", "main") assert r.exit_code != 0 def test_incompatible_with_rules(self) -> None: r = _crf("--invalid-only", "--rules") assert r.exit_code != 0 # --------------------------------------------------------------------------- # Regression — previously incorrect behavior # --------------------------------------------------------------------------- class TestLeadingDotFix: """Tests for the leading-dot rule that looked correct in tests but previously used monkeypatch.chdir unnecessarily.""" def test_leading_dot_invalid(self) -> None: r = _crf("--json", ".hidden") assert r.exit_code != 0 data = json.loads(r.output) assert data["results"][0]["valid"] is False def test_mid_segment_dot_is_valid(self) -> None: """feat/.hidden is valid — the leading-dot rule applies to the whole ref name, not each path segment (same behaviour as Git).""" r = _crf("--json", "feat/.hidden") assert r.exit_code == 0 assert json.loads(r.output)["all_valid"] is True # --------------------------------------------------------------------------- # Flag tests # --------------------------------------------------------------------------- import argparse as _argparse class TestRegisterFlags: def _parse(self, *args: str) -> _argparse.Namespace: from muse.cli.commands.check_ref_format import register p = _argparse.ArgumentParser() sub = p.add_subparsers() register(sub) return p.parse_args(["check-ref-format", *args]) def test_default_json_out_is_false(self) -> None: ns = self._parse("main") assert ns.json_out is False def test_json_flag_sets_json_out(self) -> None: ns = self._parse("--json", "main") assert ns.json_out is True def test_j_shorthand_sets_json_out(self) -> None: ns = self._parse("-j", "main") assert ns.json_out is True