"""Regression tests for muse branch #21. ``muse branch --merged``/``--no-merged`` crashed with a ``ValueError`` from ``validate_branch_name`` when given a ``sha256:``-prefixed commit ID instead of a branch name — the argument was passed unconditionally to ``get_head_commit_id``, which validates its input as a branch name. A commit ID is a legitimate argument for both flags (e.g. checking against the tip of a remote-tracking ref by ID rather than by local branch name). """ from __future__ import annotations import json import os import pathlib from tests.cli_test_helper import CliRunner runner = CliRunner() def _invoke(repo: pathlib.Path, args: list[str]): saved = os.getcwd() try: os.chdir(repo) return runner.invoke(None, args) finally: os.chdir(saved) def _commit(repo: pathlib.Path, *extra: str): _invoke(repo, ["code", "add", "."]) return _invoke(repo, ["commit", *extra]) def _head_commit_id(repo: pathlib.Path) -> str: result = _invoke(repo, ["rev-parse", "HEAD", "--json"]) return json.loads(result.output.strip().splitlines()[-1])["commit_id"] def _init_repo(tmp_path: pathlib.Path) -> pathlib.Path: saved = os.getcwd() try: os.chdir(tmp_path) runner.invoke(None, ["init"]) finally: os.chdir(saved) (tmp_path / "a.py").write_text("x = 1\n") _commit(tmp_path, "-m", "initial") return tmp_path class TestBranchMergedAcceptsCommitId: def test_merged_accepts_sha256_commit_id(self, tmp_path: pathlib.Path) -> None: """--merged must not crash — MRG_01.""" repo = _init_repo(tmp_path) tip = _head_commit_id(repo) result = _invoke(repo, ["branch", "--merged", tip, "--json"]) assert result.exit_code == 0, result.output branches = json.loads(result.output.strip().splitlines()[-1]) names = {b["name"] for b in branches} assert "main" in names def test_no_merged_accepts_sha256_commit_id(self, tmp_path: pathlib.Path) -> None: """--no-merged must not crash — MRG_02.""" repo = _init_repo(tmp_path) tip = _head_commit_id(repo) result = _invoke(repo, ["branch", "--no-merged", tip, "--json"]) assert result.exit_code == 0, result.output # main is merged into its own tip, so it must be excluded. branches = json.loads(result.output.strip().splitlines()[-1]) names = {b["name"] for b in branches} assert "main" not in names def test_merged_still_accepts_branch_name(self, tmp_path: pathlib.Path) -> None: """Existing branch-name behaviour is unchanged — MRG_03.""" repo = _init_repo(tmp_path) result = _invoke(repo, ["branch", "--merged", "main", "--json"]) assert result.exit_code == 0, result.output branches = json.loads(result.output.strip().splitlines()[-1]) names = {b["name"] for b in branches} assert "main" in names def test_merged_with_diverged_branch_by_commit_id( self, tmp_path: pathlib.Path ) -> None: """A branch not yet merged into the given commit ID is excluded — MRG_04.""" repo = _init_repo(tmp_path) base_tip = _head_commit_id(repo) _invoke(repo, ["checkout", "-b", "feat/unmerged"]) (repo / "b.py").write_text("y = 2\n") _commit(repo, "-m", "unmerged work") result = _invoke(repo, ["branch", "--merged", base_tip, "--json"]) assert result.exit_code == 0, result.output branches = json.loads(result.output.strip().splitlines()[-1]) names = {b["name"] for b in branches} assert "feat/unmerged" not in names assert "main" in names