"""SUPERCHARGE tests for ``muse switch``. Gaps addressed beyond the existing test_cmd_switch.py: Unit U1 duration_ms present and float in all JSON success paths U2 exit_code present and 0 in all JSON success paths U3 JSON error emitted to stdout when --json + branch not found U4 JSON error emitted to stdout when --json + no PREV_BRANCH U5 JSON error emitted to stdout when --json + mutual-exclusion violation U6 commit_id is sha256:-prefixed in JSON output U7 from_branch always present and correct in JSON Integration I1 switch - + --json emits valid JSON with correct action I2 switch -C existing branch + --json → action == "reset" I3 switch -C new branch + --json → action == "created" I4 switch -c + --intent stores intent on new branch I5 switch -c + --resumable marks branch resumable I6 --autoshelf + --json emits valid JSON I7 --merge + --json emits valid JSON (smoke — merge may yield conflict) I8 dry-run -C + --json emits JSON without creating branch I9 --json + --dry-run switch - emits JSON (prev branch preview) I10 switch -c on existing branch + --json → JSON error (not text traceback) Security S1 null byte in branch name rejected (exit non-zero) S2 path traversal (../) in branch name rejected S3 JSON error output contains no raw ANSI bytes S4 branch name sanitized in JSON output values Data integrity D1 PREV_BRANCH correct after 10 alternating switches D2 commit_id in JSON matches actual HEAD after switch D3 duration_ms is float not int D4 exit_code is int not bool D5 force-create action field: "reset" when branch existed, "created" when new Stress P1 50 rapid switches among 3 branches — final state consistent P2 switch with 30 branches in the repo completes P3 duration_ms present in all 10 rapid JSON calls Concurrent C1 4 threads switching in separate repos — all succeed """ from __future__ import annotations from collections.abc import Mapping import json import os import pathlib import threading import pytest from tests.cli_test_helper import CliRunner, InvokeResult from muse.core.refs import ( get_head_commit_id, read_current_branch, ) from muse.core.paths import muse_dir runner = CliRunner() # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- _CHDIR_LOCK = threading.Lock() def _env(repo: pathlib.Path) -> Mapping[str, str]: return {"MUSE_REPO_ROOT": str(repo)} def _invoke(repo: pathlib.Path, *args: str) -> InvokeResult: return runner.invoke(None, ["switch", *args], env=_env(repo)) def _run(repo: pathlib.Path, *args: str) -> InvokeResult: return runner.invoke(None, list(args), env=_env(repo)) def _init_repo(tmp: pathlib.Path) -> pathlib.Path: tmp.mkdir(parents=True, exist_ok=True) with _CHDIR_LOCK: saved = os.getcwd() try: os.chdir(tmp) runner.invoke(None, ["init"]) finally: os.chdir(saved) (tmp / "a.py").write_text("x = 1\n") _run(tmp, "commit", "-m", "initial") return tmp def _add_branch(repo: pathlib.Path, name: str) -> None: _run(repo, "branch", name) def _switch(repo: pathlib.Path, *args: str) -> InvokeResult: return _invoke(repo, *args) def _json_switch(repo: pathlib.Path, *args: str) -> Mapping[str, object]: result = _invoke(repo, "--json", *args) return result, json.loads(result.stdout) if result.stdout.strip() else {} @pytest.fixture() def repo(tmp_path: pathlib.Path) -> pathlib.Path: return _init_repo(tmp_path) @pytest.fixture() def two_branch_repo(repo: pathlib.Path) -> pathlib.Path: _add_branch(repo, "feat") _run(repo, "checkout", "feat") (repo / "feat.py").write_text("f = 1\n") _run(repo, "commit", "-m", "feat commit") _run(repo, "checkout", "main") return repo # --------------------------------------------------------------------------- # U1–U2 duration_ms and exit_code in all JSON success paths # --------------------------------------------------------------------------- class TestElapsedAndExitCode: def test_U1_duration_ms_switched(self, two_branch_repo: pathlib.Path) -> None: result, data = _json_switch(two_branch_repo, "feat") assert result.exit_code == 0 assert "duration_ms" in data, f"duration_ms missing; got keys: {list(data)}" def test_U1_duration_ms_created(self, repo: pathlib.Path) -> None: result, data = _json_switch(repo, "-c", "new-feat") assert result.exit_code == 0 assert "duration_ms" in data def test_U1_duration_ms_force_create_new(self, repo: pathlib.Path) -> None: result, data = _json_switch(repo, "-C", "brand-new") assert result.exit_code == 0 assert "duration_ms" in data def test_U1_duration_ms_force_create_existing( self, two_branch_repo: pathlib.Path ) -> None: result, data = _json_switch(two_branch_repo, "-C", "feat") assert result.exit_code == 0 assert "duration_ms" in data def test_U1_duration_ms_dry_run(self, two_branch_repo: pathlib.Path) -> None: result, data = _json_switch(two_branch_repo, "--dry-run", "feat") assert result.exit_code == 0 assert "duration_ms" in data def test_U2_exit_code_in_json(self, two_branch_repo: pathlib.Path) -> None: result, data = _json_switch(two_branch_repo, "feat") assert result.exit_code == 0 assert "exit_code" in data assert data["exit_code"] == 0 def test_D3_duration_ms_is_float(self, two_branch_repo: pathlib.Path) -> None: _, data = _json_switch(two_branch_repo, "feat") assert isinstance(data["duration_ms"], float) def test_D4_exit_code_is_int_not_bool(self, two_branch_repo: pathlib.Path) -> None: _, data = _json_switch(two_branch_repo, "feat") assert isinstance(data["exit_code"], int) assert not isinstance(data["exit_code"], bool) # --------------------------------------------------------------------------- # U3–U5 JSON error output on failure # --------------------------------------------------------------------------- class TestJsonErrors: def test_U3_branch_not_found_json_error(self, repo: pathlib.Path) -> None: result = _invoke(repo, "--json", "no-such-branch") assert result.exit_code != 0 data = json.loads(result.stdout) assert "error" in data assert data["exit_code"] != 0 def test_U4_no_prev_branch_json_error(self, repo: pathlib.Path) -> None: result = _invoke(repo, "--json", "-") assert result.exit_code != 0 data = json.loads(result.stdout) assert "error" in data def test_U5_mutual_exclusion_c_and_C_json_error(self, repo: pathlib.Path) -> None: result = _invoke(repo, "--json", "-c", "-C", "foo") assert result.exit_code != 0 data = json.loads(result.stdout) assert "error" in data def test_U5_mutual_exclusion_discard_merge_json_error( self, repo: pathlib.Path ) -> None: result = _invoke(repo, "--json", "--discard-changes", "--merge", "feat") assert result.exit_code != 0 data = json.loads(result.stdout) assert "error" in data def test_U5_mutual_exclusion_discard_autoshelf_json_error( self, repo: pathlib.Path ) -> None: result = _invoke(repo, "--json", "--discard-changes", "--autoshelf", "feat") assert result.exit_code != 0 data = json.loads(result.stdout) assert "error" in data def test_U5_mutual_exclusion_merge_autoshelf_json_error( self, repo: pathlib.Path ) -> None: result = _invoke(repo, "--json", "--merge", "--autoshelf", "feat") assert result.exit_code != 0 data = json.loads(result.stdout) assert "error" in data def test_json_error_has_duration_ms(self, repo: pathlib.Path) -> None: result = _invoke(repo, "--json", "no-such-branch") data = json.loads(result.stdout) assert "duration_ms" in data def test_json_error_has_exit_code(self, repo: pathlib.Path) -> None: result = _invoke(repo, "--json", "no-such-branch") data = json.loads(result.stdout) assert "exit_code" in data assert data["exit_code"] != 0 def test_create_existing_branch_json_error( self, two_branch_repo: pathlib.Path ) -> None: """switch -c on an existing branch must emit a JSON error, not a traceback.""" result = _invoke(two_branch_repo, "--json", "-c", "feat") assert result.exit_code != 0 data = json.loads(result.stdout) assert "error" in data assert "Traceback" not in result.stdout # --------------------------------------------------------------------------- # U6–U7 commit_id and from_branch # --------------------------------------------------------------------------- class TestCommitIdAndFromBranch: def test_U6_commit_id_sha256_prefixed(self, two_branch_repo: pathlib.Path) -> None: _, data = _json_switch(two_branch_repo, "feat") assert data["commit_id"].startswith("sha256:") def test_U7_from_branch_correct(self, two_branch_repo: pathlib.Path) -> None: _, data = _json_switch(two_branch_repo, "feat") assert data["from_branch"] == "main" def test_U7_from_branch_after_create(self, repo: pathlib.Path) -> None: _, data = _json_switch(repo, "-c", "new-feat") assert data["from_branch"] == "main" def test_D2_commit_id_matches_head(self, two_branch_repo: pathlib.Path) -> None: result, data = _json_switch(two_branch_repo, "feat") assert result.exit_code == 0 actual = get_head_commit_id(two_branch_repo, "feat") assert data["commit_id"] == actual # --------------------------------------------------------------------------- # I1–I10 Integration — uncovered paths # --------------------------------------------------------------------------- class TestIntegration: def test_I1_switch_dash_json(self, two_branch_repo: pathlib.Path) -> None: """switch - + --json must emit valid JSON.""" _switch(two_branch_repo, "feat") # go to feat first result = _invoke(two_branch_repo, "--json", "-") assert result.exit_code == 0 data = json.loads(result.stdout) assert "action" in data assert data["branch"] == "main" assert "duration_ms" in data def test_I2_force_create_existing_action_reset( self, two_branch_repo: pathlib.Path ) -> None: result, data = _json_switch(two_branch_repo, "-C", "feat") assert result.exit_code == 0 assert data["action"] == "reset" def test_I3_force_create_new_action_created(self, repo: pathlib.Path) -> None: result, data = _json_switch(repo, "-C", "brand-new") assert result.exit_code == 0 assert data["action"] == "created" def test_I4_create_with_intent(self, repo: pathlib.Path) -> None: """switch -c + --intent stores intent metadata on the new branch.""" result = _invoke(repo, "-c", "task/foo", "--intent", "implement foo feature") assert result.exit_code == 0 # Verify branch was created assert read_current_branch(repo) == "task/foo" # Verify intent is queryable branch_data = json.loads( _run(repo, "branch", "--json").stdout ) foo = next((b for b in branch_data if b["name"] == "task/foo"), None) assert foo is not None assert foo.get("intent") == "implement foo feature" def test_I5_create_with_resumable(self, repo: pathlib.Path) -> None: """switch -c + --resumable marks the branch as a resumable checkpoint.""" result = _invoke(repo, "-c", "task/bar", "--resumable") assert result.exit_code == 0 branch_data = json.loads(_run(repo, "branch", "--json").stdout) bar = next((b for b in branch_data if b["name"] == "task/bar"), None) assert bar is not None assert bar.get("resumable") is True def test_I6_autoshelf_json(self, two_branch_repo: pathlib.Path) -> None: result, data = _json_switch(two_branch_repo, "--autoshelf", "feat") assert result.exit_code == 0 assert "action" in data assert "duration_ms" in data def test_I8_dry_run_force_create_json(self, two_branch_repo: pathlib.Path) -> None: result, data = _json_switch(two_branch_repo, "--dry-run", "-C", "feat") assert result.exit_code == 0 assert data["dry_run"] is True assert "action" in data assert "duration_ms" in data # Branch ref must not be modified orig_tip = get_head_commit_id(two_branch_repo, "feat") main_tip = get_head_commit_id(two_branch_repo, "main") assert get_head_commit_id(two_branch_repo, "feat") == orig_tip def test_I9_dry_run_switch_dash_json(self, two_branch_repo: pathlib.Path) -> None: _switch(two_branch_repo, "feat") # record prev result, data = _json_switch(two_branch_repo, "--dry-run", "-") assert result.exit_code == 0 assert data["dry_run"] is True assert "duration_ms" in data def test_I10_create_existing_json_error_not_traceback( self, two_branch_repo: pathlib.Path ) -> None: result = _invoke(two_branch_repo, "--json", "-c", "feat") assert result.exit_code != 0 assert "Traceback" not in result.stdout data = json.loads(result.stdout) assert "error" in data # --------------------------------------------------------------------------- # Security # --------------------------------------------------------------------------- class TestSecurity: def test_S1_null_byte_in_branch_name_rejected(self, repo: pathlib.Path) -> None: result = _invoke(repo, "feat\x00malicious") assert result.exit_code != 0 def test_S2_path_traversal_in_branch_name_rejected( self, repo: pathlib.Path ) -> None: result = _invoke(repo, "../traversal") assert result.exit_code != 0 def test_S3_json_error_no_ansi(self, repo: pathlib.Path) -> None: result = _invoke(repo, "--json", "no-such-branch") assert "\x1b" not in result.stdout def test_S4_branch_name_sanitized_in_json( self, two_branch_repo: pathlib.Path ) -> None: _, data = _json_switch(two_branch_repo, "feat") assert "\x1b" not in json.dumps(data) def test_ansi_in_branch_name_rejected_with_json( self, repo: pathlib.Path ) -> None: result = _invoke(repo, "--json", "\x1b[31mbad\x1b[0m") assert result.exit_code != 0 # Must be parseable JSON, not raw error text data = json.loads(result.stdout) assert "error" in data # --------------------------------------------------------------------------- # Data integrity # --------------------------------------------------------------------------- class TestDataIntegrity: def test_D1_prev_branch_correct_after_10_toggles( self, two_branch_repo: pathlib.Path ) -> None: branches = ["main", "feat"] for i in range(10): _switch(two_branch_repo, branches[i % 2]) # After 10 switches index 9 → branches[1] = feat assert read_current_branch(two_branch_repo) == "feat" # PREV_BRANCH should be main (the branch we came from) prev = (muse_dir(two_branch_repo) / "PREV_BRANCH").read_text().strip() assert prev == "main" def test_D5_force_create_action_reset_when_existed( self, two_branch_repo: pathlib.Path ) -> None: _, data = _json_switch(two_branch_repo, "-C", "feat") assert data["action"] == "reset" def test_D5_force_create_action_created_when_new( self, repo: pathlib.Path ) -> None: _, data = _json_switch(repo, "-C", "new-branch") assert data["action"] == "created" def test_all_success_json_keys_present( self, two_branch_repo: pathlib.Path ) -> None: """Every successful switch --json must have the full schema.""" _, data = _json_switch(two_branch_repo, "feat") required = {"action", "branch", "from_branch", "commit_id", "dry_run", "duration_ms", "exit_code"} missing = required - set(data.keys()) assert not missing, f"Missing JSON keys: {missing}" def test_all_error_json_keys_present(self, repo: pathlib.Path) -> None: result = _invoke(repo, "--json", "ghost") data = json.loads(result.stdout) required = {"error", "duration_ms", "exit_code"} missing = required - set(data.keys()) assert not missing, f"Missing error JSON keys: {missing}" # --------------------------------------------------------------------------- # Stress # --------------------------------------------------------------------------- class TestStress: def test_P1_50_rapid_switches_three_branches( self, tmp_path: pathlib.Path ) -> None: repo = _init_repo(tmp_path) for name in ("feat-a", "feat-b"): _add_branch(repo, name) branches = ["main", "feat-a", "feat-b"] for i in range(50): result = _switch(repo, branches[i % 3]) assert result.exit_code == 0, f"Switch {i} failed" expected = branches[49 % 3] assert read_current_branch(repo) == expected def test_P2_switch_with_many_branches(self, tmp_path: pathlib.Path) -> None: """30 branches in the repo; switching still completes.""" repo = _init_repo(tmp_path) for i in range(30): _run(repo, "branch", f"branch-{i:02d}") result = _switch(repo, "branch-00") assert result.exit_code == 0 assert read_current_branch(repo) == "branch-00" def test_P3_duration_ms_in_all_rapid_json_calls( self, two_branch_repo: pathlib.Path ) -> None: branches = ["main", "feat"] for i in range(10): result = _invoke(two_branch_repo, "--json", branches[i % 2]) data = json.loads(result.stdout) assert "duration_ms" in data, f"Missing duration_ms on call {i}" assert isinstance(data["duration_ms"], float) # --------------------------------------------------------------------------- # Concurrent # --------------------------------------------------------------------------- _INIT_LOCK = threading.Lock() class TestConcurrent: def test_C1_four_concurrent_switches(self, tmp_path: pathlib.Path) -> None: results = [None] * 4 def _work(idx: int) -> None: repo = tmp_path / f"repo_{idx}" with _INIT_LOCK: r = _init_repo(repo) _add_branch(r, "feat") try: res = _switch(r, "feat") results[idx] = res.exit_code except Exception as exc: results[idx] = exc threads = [threading.Thread(target=_work, args=(i,)) for i in range(4)] for t in threads: t.start() for t in threads: t.join() for i, result in enumerate(results): assert not isinstance(result, Exception), f"Thread {i}: {result}" assert result == 0, f"Thread {i} exit code: {result}"