"""TDD supercharge tests for ``muse code semantic-cherry-pick``. Gaps being closed ----------------- - ``-j`` alias for ``--json`` - ``-n`` alias for ``--dry-run`` - ``exit_code`` and ``duration_ms`` in JSON envelope - ``_CherryPickResultJson`` top-level TypedDict - Security: null byte / ANSI in address in JSON output - ``schema_version`` is a non-empty string - ``branch`` field present and correct in JSON - Docstring completeness for ``register()`` and ``run()`` Test classes ------------ TestJsonAlias -j alias works identically to --json TestJsonEnvelope exit_code, duration_ms, schema_version in all JSON TestTypedDict _CherryPickResultJson importable and typed TestCLIAliases -n alias for dry-run TestCLISecurity null byte / ANSI in address in JSON TestDocstrings register() and run() docstring completeness """ from __future__ import annotations import json import pathlib import textwrap import typing import pytest from collections.abc import Callable from tests.cli_test_helper import CliRunner, InvokeResult cli = None runner = CliRunner() # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _run(root: pathlib.Path, *args: str) -> InvokeResult: return runner.invoke(cli, list(args), env={"MUSE_REPO_ROOT": str(root)}) def _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 # --------------------------------------------------------------------------- # Fixture — two-commit repo with an evolving function # --------------------------------------------------------------------------- @pytest.fixture def two_commit_repo(tmp_path: pathlib.Path, monkeypatch: pytest.MonkeyPatch) -> pathlib.Path: """Code repo: commit 1 has v1 of compute_total; commit 2 has v2.""" monkeypatch.chdir(tmp_path) monkeypatch.setenv("MUSE_REPO_ROOT", str(tmp_path)) r = _run(tmp_path, "init", "--domain", "code") assert r.exit_code == 0, r.output (tmp_path / "billing.py").write_text(textwrap.dedent("""\ def compute_total(items: list[int]) -> int: return sum(items) def helper() -> None: pass """)) _commit(tmp_path, "v1") (tmp_path / "billing.py").write_text(textwrap.dedent("""\ def compute_total(items: list[int]) -> int: return sum(items) * 2 # v2 — different body def helper() -> None: pass """)) _commit(tmp_path, "v2") return tmp_path def _first_commit_id(root: pathlib.Path, runner_obj: CliRunner, cli_obj: Callable[..., None]) -> str: """Return the commit_id of the first (oldest) commit.""" r = runner_obj.invoke(cli_obj, ["log", "--json"], env={"MUSE_REPO_ROOT": str(root)}) commits = json.loads(r.output)["commits"] return commits[-1]["commit_id"] # oldest # --------------------------------------------------------------------------- # 1. -j alias # --------------------------------------------------------------------------- class TestJsonAlias: def test_j_alias_exits_zero(self, two_commit_repo: pathlib.Path) -> None: first = _first_commit_id(two_commit_repo, runner, cli) r = _run(two_commit_repo, "code", "semantic-cherry-pick", "billing.py::compute_total", "--from", first, "-j") assert r.exit_code == 0, r.output def test_j_alias_emits_valid_json(self, two_commit_repo: pathlib.Path) -> None: first = _first_commit_id(two_commit_repo, runner, cli) r = _run(two_commit_repo, "code", "semantic-cherry-pick", "billing.py::compute_total", "--from", first, "-j") data = json.loads(r.output.strip()) assert isinstance(data, dict) def test_j_alias_has_results(self, two_commit_repo: pathlib.Path) -> None: first = _first_commit_id(two_commit_repo, runner, cli) r = _run(two_commit_repo, "code", "semantic-cherry-pick", "billing.py::compute_total", "--from", first, "-j") data = json.loads(r.output) assert "results" in data def test_j_alias_same_keys_as_json_flag(self, two_commit_repo: pathlib.Path) -> None: first = _first_commit_id(two_commit_repo, runner, cli) # Use dry-run so both calls see the same state r1 = _run(two_commit_repo, "code", "semantic-cherry-pick", "billing.py::compute_total", "--from", first, "--json", "--dry-run") r2 = _run(two_commit_repo, "code", "semantic-cherry-pick", "billing.py::compute_total", "--from", first, "-j", "--dry-run") d1, d2 = json.loads(r1.output), 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_same_applied_count(self, two_commit_repo: pathlib.Path) -> None: first = _first_commit_id(two_commit_repo, runner, cli) r1 = _run(two_commit_repo, "code", "semantic-cherry-pick", "billing.py::compute_total", "--from", first, "--json", "--dry-run") r2 = _run(two_commit_repo, "code", "semantic-cherry-pick", "billing.py::compute_total", "--from", first, "-j", "--dry-run") assert json.loads(r1.output)["applied"] == json.loads(r2.output)["applied"] def test_j_with_dry_run(self, two_commit_repo: pathlib.Path) -> None: first = _first_commit_id(two_commit_repo, runner, cli) r = _run(two_commit_repo, "code", "semantic-cherry-pick", "billing.py::compute_total", "--from", first, "-j", "--dry-run") data = json.loads(r.output) assert data["dry_run"] is True def test_j_alias_dry_run_does_not_write(self, two_commit_repo: pathlib.Path) -> None: first = _first_commit_id(two_commit_repo, runner, cli) original = (two_commit_repo / "billing.py").read_text() _run(two_commit_repo, "code", "semantic-cherry-pick", "billing.py::compute_total", "--from", first, "-j", "--dry-run") assert (two_commit_repo / "billing.py").read_text() == original # --------------------------------------------------------------------------- # 2. JSON envelope: exit_code + duration_ms # --------------------------------------------------------------------------- class TestJsonEnvelope: def test_has_exit_code(self, two_commit_repo: pathlib.Path) -> None: first = _first_commit_id(two_commit_repo, runner, cli) r = _run(two_commit_repo, "code", "semantic-cherry-pick", "billing.py::compute_total", "--from", first, "--json", "--dry-run") data = json.loads(r.output) assert "exit_code" in data def test_exit_code_is_zero(self, two_commit_repo: pathlib.Path) -> None: first = _first_commit_id(two_commit_repo, runner, cli) r = _run(two_commit_repo, "code", "semantic-cherry-pick", "billing.py::compute_total", "--from", first, "--json", "--dry-run") data = json.loads(r.output) assert data["exit_code"] == 0 def test_has_duration_ms(self, two_commit_repo: pathlib.Path) -> None: first = _first_commit_id(two_commit_repo, runner, cli) r = _run(two_commit_repo, "code", "semantic-cherry-pick", "billing.py::compute_total", "--from", first, "--json", "--dry-run") data = json.loads(r.output) assert "duration_ms" in data def test_duration_ms_is_positive_float(self, two_commit_repo: pathlib.Path) -> None: first = _first_commit_id(two_commit_repo, runner, cli) r = _run(two_commit_repo, "code", "semantic-cherry-pick", "billing.py::compute_total", "--from", first, "--json", "--dry-run") data = json.loads(r.output) assert isinstance(data["duration_ms"], float) assert data["duration_ms"] > 0 def test_schema_version_is_nonempty_string(self, two_commit_repo: pathlib.Path) -> None: first = _first_commit_id(two_commit_repo, runner, cli) r = _run(two_commit_repo, "code", "semantic-cherry-pick", "billing.py::compute_total", "--from", first, "--json", "--dry-run") data = json.loads(r.output) assert isinstance(data["schema"], int) assert data["schema"] > 0 def test_branch_field_present(self, two_commit_repo: pathlib.Path) -> None: first = _first_commit_id(two_commit_repo, runner, cli) r = _run(two_commit_repo, "code", "semantic-cherry-pick", "billing.py::compute_total", "--from", first, "--json", "--dry-run") data = json.loads(r.output) assert "branch" in data assert isinstance(data["branch"], str) def test_exit_code_present_after_apply(self, two_commit_repo: pathlib.Path) -> None: first = _first_commit_id(two_commit_repo, runner, cli) r = _run(two_commit_repo, "code", "semantic-cherry-pick", "billing.py::compute_total", "--from", first, "--json") data = json.loads(r.output) assert data["exit_code"] == 0 def test_duration_ms_present_after_apply(self, two_commit_repo: pathlib.Path) -> None: first = _first_commit_id(two_commit_repo, runner, cli) r = _run(two_commit_repo, "code", "semantic-cherry-pick", "billing.py::compute_total", "--from", first, "--json") data = json.loads(r.output) assert "duration_ms" in data def test_not_found_still_has_exit_code(self, two_commit_repo: pathlib.Path) -> None: first = _first_commit_id(two_commit_repo, runner, cli) r = _run(two_commit_repo, "code", "semantic-cherry-pick", "billing.py::nonexistent_fn", "--from", first, "--json", "--dry-run") data = json.loads(r.output) assert "exit_code" in data assert data["exit_code"] == 0 def test_not_found_still_has_duration_ms(self, two_commit_repo: pathlib.Path) -> None: first = _first_commit_id(two_commit_repo, runner, cli) r = _run(two_commit_repo, "code", "semantic-cherry-pick", "billing.py::nonexistent_fn", "--from", first, "--json", "--dry-run") data = json.loads(r.output) assert "duration_ms" in data # --------------------------------------------------------------------------- # 3. _CherryPickResultJson TypedDict # --------------------------------------------------------------------------- class TestTypedDict: def test_cherry_pick_result_json_importable(self) -> None: from muse.cli.commands.semantic_cherry_pick import _CherryPickResultJson assert _CherryPickResultJson is not None def test_has_exit_code_field(self) -> None: from muse.cli.commands.semantic_cherry_pick import _CherryPickResultJson hints = typing.get_type_hints(_CherryPickResultJson) assert "exit_code" in hints def test_has_duration_ms_field(self) -> None: from muse.cli.commands.semantic_cherry_pick import _CherryPickResultJson hints = typing.get_type_hints(_CherryPickResultJson) assert "duration_ms" in hints def test_has_schema_version_field(self) -> None: from muse.cli.commands.semantic_cherry_pick import _CherryPickResultJson hints = typing.get_type_hints(_CherryPickResultJson) assert "schema" in hints def test_has_results_field(self) -> None: from muse.cli.commands.semantic_cherry_pick import _CherryPickResultJson hints = typing.get_type_hints(_CherryPickResultJson) assert "results" in hints def test_has_branch_field(self) -> None: from muse.cli.commands.semantic_cherry_pick import _CherryPickResultJson hints = typing.get_type_hints(_CherryPickResultJson) assert "branch" in hints def test_has_dry_run_field(self) -> None: from muse.cli.commands.semantic_cherry_pick import _CherryPickResultJson hints = typing.get_type_hints(_CherryPickResultJson) assert "dry_run" in hints def test_has_applied_field(self) -> None: from muse.cli.commands.semantic_cherry_pick import _CherryPickResultJson hints = typing.get_type_hints(_CherryPickResultJson) assert "applied" in hints def test_has_failed_field(self) -> None: from muse.cli.commands.semantic_cherry_pick import _CherryPickResultJson hints = typing.get_type_hints(_CherryPickResultJson) assert "failed" in hints def test_has_unverified_field(self) -> None: from muse.cli.commands.semantic_cherry_pick import _CherryPickResultJson hints = typing.get_type_hints(_CherryPickResultJson) assert "unverified" in hints # --------------------------------------------------------------------------- # 4. -n alias for --dry-run # --------------------------------------------------------------------------- class TestCLIAliases: def test_n_alias_exits_zero(self, two_commit_repo: pathlib.Path) -> None: first = _first_commit_id(two_commit_repo, runner, cli) r = _run(two_commit_repo, "code", "semantic-cherry-pick", "billing.py::compute_total", "--from", first, "-n", "--json") assert r.exit_code == 0, r.output def test_n_alias_dry_run_flag_true(self, two_commit_repo: pathlib.Path) -> None: first = _first_commit_id(two_commit_repo, runner, cli) r = _run(two_commit_repo, "code", "semantic-cherry-pick", "billing.py::compute_total", "--from", first, "-n", "--json") data = json.loads(r.output) assert data["dry_run"] is True def test_n_alias_does_not_write(self, two_commit_repo: pathlib.Path) -> None: first = _first_commit_id(two_commit_repo, runner, cli) original = (two_commit_repo / "billing.py").read_text() _run(two_commit_repo, "code", "semantic-cherry-pick", "billing.py::compute_total", "--from", first, "-n") assert (two_commit_repo / "billing.py").read_text() == original def test_n_alias_same_output_as_dry_run(self, two_commit_repo: pathlib.Path) -> None: first = _first_commit_id(two_commit_repo, runner, cli) r1 = _run(two_commit_repo, "code", "semantic-cherry-pick", "billing.py::compute_total", "--from", first, "--dry-run", "--json") r2 = _run(two_commit_repo, "code", "semantic-cherry-pick", "billing.py::compute_total", "--from", first, "-n", "--json") d1, d2 = json.loads(r1.output), json.loads(r2.output) for k in ("duration_ms", "timestamp"): d1.pop(k, None) d2.pop(k, None) assert d1 == d2 # --------------------------------------------------------------------------- # 5. Security # --------------------------------------------------------------------------- class TestCLISecurity: def test_null_byte_in_address_not_in_raw_stdout(self, two_commit_repo: pathlib.Path) -> None: first = _first_commit_id(two_commit_repo, runner, cli) r = _run(two_commit_repo, "code", "semantic-cherry-pick", "billing.py::compute\x00total", "--from", first, "--json") # json.dumps encodes null as \u0000 — raw \x00 must not appear assert "\x00" not in r.output def test_ansi_escape_not_in_json_stdout(self, two_commit_repo: pathlib.Path) -> None: first = _first_commit_id(two_commit_repo, runner, cli) r = _run(two_commit_repo, "code", "semantic-cherry-pick", "billing.py::compute_total", "--from", first, "--json", "--dry-run") assert "\x1b" not in r.output def test_path_traversal_in_address_is_not_found(self, two_commit_repo: pathlib.Path) -> None: first = _first_commit_id(two_commit_repo, runner, cli) r = _run(two_commit_repo, "code", "semantic-cherry-pick", "../../etc/passwd::compute_total", "--from", first, "--json", "--dry-run") data = json.loads(r.output) assert data["results"][0]["status"] == "not_found" def test_path_traversal_exit_code_zero(self, two_commit_repo: pathlib.Path) -> None: # not_found is a graceful result — command still exits 0 first = _first_commit_id(two_commit_repo, runner, cli) r = _run(two_commit_repo, "code", "semantic-cherry-pick", "../../etc/passwd::compute_total", "--from", first, "--json", "--dry-run") assert r.exit_code == 0 # --------------------------------------------------------------------------- # 6. Docstrings # --------------------------------------------------------------------------- class TestDocstrings: def test_run_docstring_exists(self) -> None: from muse.cli.commands.semantic_cherry_pick import run assert run.__doc__ is not None assert len(run.__doc__) > 80 def test_run_docstring_mentions_json(self) -> None: from muse.cli.commands.semantic_cherry_pick import run assert "json" in (run.__doc__ or "").lower() def test_register_docstring_exists(self) -> None: from muse.cli.commands.semantic_cherry_pick import register assert register.__doc__ is not None assert len(register.__doc__) > 80 def test_register_docstring_mentions_from(self) -> None: from muse.cli.commands.semantic_cherry_pick import register assert "--from" in (register.__doc__ or "") def test_register_docstring_mentions_dry_run(self) -> None: from muse.cli.commands.semantic_cherry_pick import register assert "dry-run" in (register.__doc__ or "") or "dry_run" in (register.__doc__ or "") def test_register_docstring_mentions_json(self) -> None: from muse.cli.commands.semantic_cherry_pick import register assert "json" in (register.__doc__ or "").lower() class TestRegisterFlags: def test_default_json_out_is_false(self) -> None: import argparse from muse.cli.commands.semantic_cherry_pick import register p = argparse.ArgumentParser() subs = p.add_subparsers() register(subs) args = p.parse_args(["semantic-cherry-pick", "src/billing.py::compute_total", "--from", "HEAD~1"]) assert args.json_out is False def test_json_flag_sets_json_out(self) -> None: import argparse from muse.cli.commands.semantic_cherry_pick import register p = argparse.ArgumentParser() subs = p.add_subparsers() register(subs) args = p.parse_args(["semantic-cherry-pick", "src/billing.py::compute_total", "--from", "HEAD~1", "--json"]) assert args.json_out is True def test_j_shorthand_sets_json_out(self) -> None: import argparse from muse.cli.commands.semantic_cherry_pick import register p = argparse.ArgumentParser() subs = p.add_subparsers() register(subs) args = p.parse_args(["semantic-cherry-pick", "src/billing.py::compute_total", "--from", "HEAD~1", "-j"]) assert args.json_out is True