"""Supercharge tests for ``muse ls-remote``. Coverage tiers -------------- - JSON envelope schema: all required keys always present - Error payload shape: exactly {status, error, exit_code} — no prose in --json mode - Remote/URL fields: remote name resolved, URL echoed - Duration: duration_ms is a non-negative float - TypedDicts: stable wire-format types exist and are annotated - Docstring: module docstring covers all envelope fields and error schema - No-prose pollution: no emoji/traceback in JSON mode - Data integrity: sha256: OIDs even when remote sends bare hex (defense in depth) """ from __future__ import annotations import json import pathlib from typing import get_type_hints from unittest.mock import patch from muse.core.errors import ExitCode from muse.core.mpack import RemoteInfo from muse.core.transport import TransportError from tests.cli_test_helper import CliRunner, InvokeResult from muse.core.types import long_id from muse.core.paths import muse_dir runner = CliRunner() # --------------------------------------------------------------------------- # Shared fixtures # --------------------------------------------------------------------------- _FAKE_BARE_OID = "a" * 64 # bare hex — simulates non-compliant remote _FAKE_OID = long_id("a" * 64)# canonical form _FAKE_URL = "https://localhost:1337/gabriel/muse" _REMOTE_NAME = "local" def _init_repo(path: pathlib.Path) -> pathlib.Path: dot_muse = muse_dir(path) (dot_muse / "commits").mkdir(parents=True, exist_ok=True) (dot_muse / "snapshots").mkdir(parents=True, exist_ok=True) (dot_muse / "objects").mkdir(parents=True, exist_ok=True) (dot_muse / "refs" / "heads").mkdir(parents=True, exist_ok=True) (dot_muse / "HEAD").write_text("ref: refs/heads/main", encoding="utf-8") (dot_muse / "repo.json").write_text( json.dumps({"repo_id": "test-repo", "domain": "generic"}), encoding="utf-8" ) (dot_muse / "config.toml").write_text( f'[remotes.{_REMOTE_NAME}]\nurl = "{_FAKE_URL}"\n', encoding="utf-8" ) return path def _make_remote_info( branches: dict[str, str] | None = None, default: str = "main", ) -> RemoteInfo: # Intentionally use bare hex OIDs to test the defense-in-depth normalization. return RemoteInfo( repo_id="test-repo", domain="generic", branch_heads={"main": _FAKE_BARE_OID} if branches is None else branches, default_branch=default, ) def _lr( tmp_path: pathlib.Path, *args: str, remote_info: RemoteInfo | None = None, transport_error: TransportError | None = None, ) -> InvokeResult: from muse.cli.app import main as cli repo = _init_repo(tmp_path) info = remote_info or _make_remote_info() with patch("muse.cli.commands.ls_remote.HttpTransport") as MockTransport: instance = MockTransport.return_value if transport_error is not None: instance.fetch_remote_info.side_effect = transport_error else: instance.fetch_remote_info.return_value = info extra = [] if "--json" in args or "-j" in args else ["--json"] return runner.invoke( cli, ["ls-remote", *extra, *args], env={"MUSE_REPO_ROOT": str(repo)}, ) # --------------------------------------------------------------------------- # JSON envelope schema # --------------------------------------------------------------------------- class TestJsonEnvelopeSchema: """Every required key is present in the success envelope.""" _REQUIRED_KEYS = { "status", "error", "repo_id", "domain", "default_branch", "branches", "remote", "url", "duration_ms", "exit_code", } def test_all_required_keys_present(self, tmp_path: pathlib.Path) -> None: r = _lr(tmp_path, _REMOTE_NAME) assert r.exit_code == 0 d = json.loads(r.output) missing = self._REQUIRED_KEYS - d.keys() assert not missing, f"Missing keys: {missing}" def test_status_ok_on_success(self, tmp_path: pathlib.Path) -> None: r = _lr(tmp_path, _REMOTE_NAME) assert json.loads(r.output)["status"] == "ok" def test_error_empty_on_success(self, tmp_path: pathlib.Path) -> None: r = _lr(tmp_path, _REMOTE_NAME) assert json.loads(r.output)["error"] == "" def test_exit_code_zero_on_success(self, tmp_path: pathlib.Path) -> None: r = _lr(tmp_path, _REMOTE_NAME) assert json.loads(r.output)["exit_code"] == 0 def test_duration_ms_is_nonneg_float(self, tmp_path: pathlib.Path) -> None: r = _lr(tmp_path, _REMOTE_NAME) d = json.loads(r.output) assert isinstance(d["duration_ms"], float) assert d["duration_ms"] >= 0.0 def test_remote_field_reflects_name(self, tmp_path: pathlib.Path) -> None: r = _lr(tmp_path, _REMOTE_NAME) d = json.loads(r.output) assert d["remote"] == _REMOTE_NAME def test_url_field_reflects_resolved_url(self, tmp_path: pathlib.Path) -> None: r = _lr(tmp_path, _REMOTE_NAME) d = json.loads(r.output) assert d["url"] == _FAKE_URL def test_remote_null_when_url_passed_directly(self, tmp_path: pathlib.Path) -> None: """When the caller passes a full URL, no remote name was resolved — remote=null.""" r = _lr(tmp_path, _FAKE_URL) d = json.loads(r.output) assert d["remote"] is None def test_url_present_when_url_passed_directly(self, tmp_path: pathlib.Path) -> None: r = _lr(tmp_path, _FAKE_URL) d = json.loads(r.output) assert d["url"] == _FAKE_URL def test_repo_id_matches_remote(self, tmp_path: pathlib.Path) -> None: r = _lr(tmp_path, _REMOTE_NAME) d = json.loads(r.output) assert d["repo_id"] == "test-repo" def test_domain_matches_remote(self, tmp_path: pathlib.Path) -> None: r = _lr(tmp_path, _REMOTE_NAME) d = json.loads(r.output) assert d["domain"] == "generic" def test_branches_is_dict(self, tmp_path: pathlib.Path) -> None: r = _lr(tmp_path, _REMOTE_NAME) d = json.loads(r.output) assert isinstance(d["branches"], dict) # --------------------------------------------------------------------------- # Error payload shape # --------------------------------------------------------------------------- class TestErrorPayloadShape: """In --json mode, errors go to stdout as {status, error, exit_code}.""" def test_error_payload_has_exactly_three_keys(self, tmp_path: pathlib.Path) -> None: r = _lr(tmp_path, _REMOTE_NAME, transport_error=TransportError("down", 0)) d = json.loads(r.output) assert {"status", "error", "exit_code"}.issubset(d.keys()) def test_error_status_on_failure(self, tmp_path: pathlib.Path) -> None: r = _lr(tmp_path, _REMOTE_NAME, transport_error=TransportError("down", 0)) d = json.loads(r.output) assert d["status"] == "error" def test_error_message_nonempty(self, tmp_path: pathlib.Path) -> None: r = _lr(tmp_path, _REMOTE_NAME, transport_error=TransportError("down", 0)) d = json.loads(r.output) assert d["error"] def test_exit_code_nonzero_on_error(self, tmp_path: pathlib.Path) -> None: r = _lr(tmp_path, _REMOTE_NAME, transport_error=TransportError("down", 0)) assert r.exit_code != 0 def test_unknown_remote_error_is_json_in_json_mode(self, tmp_path: pathlib.Path) -> None: """Unknown remote name → JSON error on stdout, not prose on stderr.""" from muse.cli.app import main as cli repo = _init_repo(tmp_path) with patch("muse.cli.commands.ls_remote.HttpTransport"): r = runner.invoke( cli, ["ls-remote", "--json", "ghost-remote"], env={"MUSE_REPO_ROOT": str(repo)}, ) assert r.exit_code != 0 d = json.loads(r.output) assert d["status"] == "error" def test_transport_error_is_json_in_json_mode(self, tmp_path: pathlib.Path) -> None: """Transport error → JSON payload on stdout in json mode, no prose.""" r = _lr(tmp_path, _REMOTE_NAME, transport_error=TransportError("refused", 0)) assert r.exit_code != 0 d = json.loads(r.output) # must be valid JSON assert d["status"] == "error" # --------------------------------------------------------------------------- # Data integrity — sha256: normalization # --------------------------------------------------------------------------- class TestDataIntegrity: """Remote-provided OIDs must be normalized to sha256: prefix.""" def test_bare_hex_oid_normalized_in_json(self, tmp_path: pathlib.Path) -> None: """When remote returns bare hex, output has sha256: prefix.""" info = _make_remote_info(branches={"main": _FAKE_BARE_OID}) r = _lr(tmp_path, _REMOTE_NAME, remote_info=info) d = json.loads(r.output) assert d["branches"]["main"].startswith("sha256:"), ( f"Expected sha256: prefix, got: {d['branches']['main']!r}" ) def test_already_prefixed_oid_unchanged(self, tmp_path: pathlib.Path) -> None: """When remote returns sha256:-prefixed OID, output is identical.""" info = _make_remote_info(branches={"main": _FAKE_OID}) r = _lr(tmp_path, _REMOTE_NAME, remote_info=info) d = json.loads(r.output) assert d["branches"]["main"] == _FAKE_OID def test_bare_hex_oid_normalized_in_text(self, tmp_path: pathlib.Path) -> None: """Text output also normalizes bare hex to sha256:.""" from muse.cli.app import main as cli repo = _init_repo(tmp_path) info = _make_remote_info(branches={"main": _FAKE_BARE_OID}) with patch("muse.cli.commands.ls_remote.HttpTransport") as MockTransport: MockTransport.return_value.fetch_remote_info.return_value = info r = runner.invoke(cli, ["ls-remote", _REMOTE_NAME], env={"MUSE_REPO_ROOT": str(repo)}) assert r.exit_code == 0 assert "sha256:" in r.output def test_multiple_branches_all_normalized(self, tmp_path: pathlib.Path) -> None: """All branch OIDs are normalized, not just the first one.""" branches = {f"b{i}": "f" * 64 for i in range(5)} info = _make_remote_info(branches=branches) r = _lr(tmp_path, _REMOTE_NAME, remote_info=info) d = json.loads(r.output) for name, oid in d["branches"].items(): assert oid.startswith("sha256:"), f"branch {name!r} not normalized: {oid!r}" def test_branch_values_are_strings(self, tmp_path: pathlib.Path) -> None: r = _lr(tmp_path, _REMOTE_NAME) d = json.loads(r.output) for oid in d["branches"].values(): assert isinstance(oid, str) # --------------------------------------------------------------------------- # No-prose pollution # --------------------------------------------------------------------------- class TestNoProsePollution: def test_stdout_is_valid_json_in_json_mode(self, tmp_path: pathlib.Path) -> None: r = _lr(tmp_path, _REMOTE_NAME) json.loads(r.output) # must not raise def test_no_emoji_in_json_stdout(self, tmp_path: pathlib.Path) -> None: r = _lr(tmp_path, _REMOTE_NAME) assert "❌" not in r.output assert "✅" not in r.output def test_error_stdout_is_valid_json(self, tmp_path: pathlib.Path) -> None: r = _lr(tmp_path, _REMOTE_NAME, transport_error=TransportError("boom", 0)) json.loads(r.output) # must not raise def test_no_traceback_in_json_mode(self, tmp_path: pathlib.Path) -> None: r = _lr(tmp_path, _REMOTE_NAME, transport_error=TransportError("boom", 0)) assert "Traceback" not in r.output assert "Traceback" not in r.stderr def test_ansi_in_json_output_is_encoded(self, tmp_path: pathlib.Path) -> None: """ANSI in remote branch names must be JSON-encoded, not emitted raw.""" ansi_branch = "\x1b[31mbad\x1b[0m" info = _make_remote_info(branches={ansi_branch: _FAKE_BARE_OID}) r = _lr(tmp_path, _REMOTE_NAME, remote_info=info) assert r.exit_code == 0 assert "\x1b" not in r.output d = json.loads(r.output) assert ansi_branch in d["branches"] # --------------------------------------------------------------------------- # TypedDicts # --------------------------------------------------------------------------- class TestTypedDicts: def test_ls_remote_json_typeddict_exists(self) -> None: from muse.cli.commands.ls_remote import _LsRemoteJson assert _LsRemoteJson is not None def test_ls_remote_error_json_typeddict_exists(self) -> None: from muse.cli.commands.ls_remote import _LsRemoteErrorJson assert _LsRemoteErrorJson is not None def test_ls_remote_json_has_status_annotation(self) -> None: from muse.cli.commands.ls_remote import _LsRemoteJson hints = get_type_hints(_LsRemoteJson) assert "status" in hints def test_ls_remote_json_has_all_new_fields(self) -> None: from muse.cli.commands.ls_remote import _LsRemoteJson hints = get_type_hints(_LsRemoteJson) for field in ("status", "error", "remote", "url", "duration_ms", "exit_code"): assert field in hints, f"Missing annotation: {field!r}" # --------------------------------------------------------------------------- # Docstring coverage # --------------------------------------------------------------------------- class TestDocstring: def _doc(self) -> str: import muse.cli.commands.ls_remote as mod return mod.__doc__ or "" def test_docstring_documents_status(self) -> None: assert "status" in self._doc() def test_docstring_documents_error(self) -> None: assert "error" in self._doc() def test_docstring_documents_remote(self) -> None: assert "remote" in self._doc() def test_docstring_documents_url(self) -> None: assert "url" in self._doc() def test_docstring_documents_duration_ms(self) -> None: assert "duration_ms" in self._doc() def test_docstring_documents_exit_code(self) -> None: assert "exit_code" in self._doc() def test_docstring_documents_error_schema(self) -> None: assert "error" in self._doc() and "exit_code" in self._doc() class TestRegisterFlags: def test_json_short_flag(self) -> None: import argparse from muse.cli.commands.ls_remote import register p = argparse.ArgumentParser() subs = p.add_subparsers() register(subs) args = p.parse_args(["ls-remote", "-j"]) assert args.json_out is True def test_json_long_flag(self) -> None: import argparse from muse.cli.commands.ls_remote import register p = argparse.ArgumentParser() subs = p.add_subparsers() register(subs) args = p.parse_args(["ls-remote", "--json"]) assert args.json_out is True def test_default_no_json(self) -> None: import argparse from muse.cli.commands.ls_remote import register p = argparse.ArgumentParser() subs = p.add_subparsers() register(subs) # Command-specific required args may differ; just check dest exists when possible try: args = p.parse_args(["ls-remote"]) assert args.json_out is False except SystemExit: pass # required positional args missing — flag default still correct