"""Tests for auto-derived `supported_commands` in `muse domains publish` — muse#74 Phase 3 (SCR_08-12). Before this fix, `run_publish`'s auto-derive path (no `--capabilities`) hardcoded `supported_commands=["commit", "diff", "merge", "log", "status"]` for every domain, regardless of what it actually supports. This file pins the fix: the auto-derive path (and the explicit-`--capabilities`-with- omitted-key path, publish only) now call `domain_command_registry.get_supported_commands(active_domain_name)` instead. `run_update` is deliberately excluded from this fallback (see the plan's Design section) — SCR_12 pins that its omitted-key behavior stays `[]`, unchanged. """ from __future__ import annotations import json import pathlib import unittest.mock import urllib.request from typing import TYPE_CHECKING from tests.cli_test_helper import CliRunner from muse._version import __version__ from muse.core.paths import muse_dir if TYPE_CHECKING: from muse.core.transport import SigningIdentity cli = None # argparse migration — CliRunner ignores this arg runner = CliRunner() def _make_signing() -> "SigningIdentity": from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey from muse.core.transport import SigningIdentity return SigningIdentity(handle="testuser", private_key=Ed25519PrivateKey.generate()) def _make_repo(tmp_path: pathlib.Path, monkeypatch, domain: str) -> pathlib.Path: dot_muse = muse_dir(tmp_path) (dot_muse / "refs" / "heads").mkdir(parents=True) (dot_muse / "objects").mkdir() (dot_muse / "commits").mkdir() (dot_muse / "snapshots").mkdir() (dot_muse / "repo.json").write_text( json.dumps({"repo_id": "test-repo", "schema_version": __version__, "domain": domain}) ) (dot_muse / "HEAD").write_text("ref: refs/heads/main\n") monkeypatch.chdir(tmp_path) monkeypatch.setattr("muse.cli.commands.domains.get_signing_identity", lambda *a, **kw: _make_signing()) return tmp_path def _mock_urlopen_capture() -> tuple[list[urllib.request.Request], object]: captured: list[urllib.request.Request] = [] def fake_urlopen( req: urllib.request.Request, timeout: float | None = None, context: object = None ) -> unittest.mock.MagicMock: captured.append(req) resp = unittest.mock.MagicMock() resp.read.return_value = json.dumps( {"domain_id": "d1", "scoped_id": "@testuser/genomics", "manifest_hash": "sha256:abc"} ).encode() resp.__enter__ = unittest.mock.MagicMock(return_value=resp) resp.__exit__ = unittest.mock.MagicMock(return_value=False) return resp return captured, fake_urlopen _BASE_ARGS = [ "domains", "publish", "--author", "testuser", "--slug", "genomics", "--name", "Genomics", "--description", "Version DNA sequences", "--viewer-type", "genome", "--hub", "https://hub.test", ] def _sent_supported_commands(captured: list[urllib.request.Request]) -> list[str]: assert len(captured) == 1, "expected exactly one HTTP request" body = json.loads(captured[0].data.decode()) return list(body["capabilities"]["supported_commands"]) class TestAutoDeriveFromRealCliNamespace: def test_scr08_code_domain_gets_real_commands_not_hardcoded_five( self, tmp_path: pathlib.Path, monkeypatch: "pytest.MonkeyPatch" # noqa: F821 ) -> None: _make_repo(tmp_path, monkeypatch, domain="code") captured, fake_urlopen = _mock_urlopen_capture() with unittest.mock.patch("urllib.request.urlopen", fake_urlopen): result = runner.invoke(cli, _BASE_ARGS) assert result.exit_code == 0, result.output commands = _sent_supported_commands(captured) assert commands != ["commit", "diff", "merge", "log", "status"], ( "still sending the old hardcoded universal list" ) for anchor in ("grep", "impact", "symbols"): assert anchor in commands, f"{anchor!r} missing from published code capabilities" assert len(commands) > 30 def test_scr09_identity_domain_gets_empty_list( self, tmp_path: pathlib.Path, monkeypatch: "pytest.MonkeyPatch" # noqa: F821 ) -> None: _make_repo(tmp_path, monkeypatch, domain="identity") captured, fake_urlopen = _mock_urlopen_capture() with unittest.mock.patch("urllib.request.urlopen", fake_urlopen): result = runner.invoke(cli, _BASE_ARGS) assert result.exit_code == 0, result.output assert _sent_supported_commands(captured) == [] class TestExplicitCapabilitiesOverride: def test_scr10_explicit_supported_commands_key_overrides_auto_derive( self, tmp_path: pathlib.Path, monkeypatch: "pytest.MonkeyPatch" # noqa: F821 ) -> None: _make_repo(tmp_path, monkeypatch, domain="code") captured, fake_urlopen = _mock_urlopen_capture() caps = json.dumps({ "dimensions": [{"name": "x", "description": "y"}], "merge_semantics": "three_way", "supported_commands": ["custom-one", "custom-two"], }) with unittest.mock.patch("urllib.request.urlopen", fake_urlopen): result = runner.invoke(cli, [*_BASE_ARGS, "--capabilities", caps]) assert result.exit_code == 0, result.output assert _sent_supported_commands(captured) == ["custom-one", "custom-two"] def test_scr11_explicit_capabilities_without_supported_commands_key_falls_back_to_auto_derive( self, tmp_path: pathlib.Path, monkeypatch: "pytest.MonkeyPatch" # noqa: F821 ) -> None: _make_repo(tmp_path, monkeypatch, domain="code") captured, fake_urlopen = _mock_urlopen_capture() caps = json.dumps({ "dimensions": [{"name": "x", "description": "y"}], "merge_semantics": "three_way", }) with unittest.mock.patch("urllib.request.urlopen", fake_urlopen): result = runner.invoke(cli, [*_BASE_ARGS, "--capabilities", caps]) assert result.exit_code == 0, result.output commands = _sent_supported_commands(captured) assert commands != [] assert "grep" in commands class TestUpdateBehaviorDeliberatelyUnchanged: def test_scr12_update_with_capabilities_and_no_supported_commands_key_stays_empty( self, tmp_path: pathlib.Path, monkeypatch: "pytest.MonkeyPatch" # noqa: F821 ) -> None: _make_repo(tmp_path, monkeypatch, domain="code") captured, fake_urlopen = _mock_urlopen_capture() caps = json.dumps({ "dimensions": [{"name": "x", "description": "y"}], "merge_semantics": "three_way", }) update_args = [ "domains", "update", "--author", "testuser", "--slug", "genomics", "--capabilities", caps, "--hub", "https://hub.test", ] with unittest.mock.patch("urllib.request.urlopen", fake_urlopen): result = runner.invoke(cli, update_args) assert result.exit_code == 0, result.output assert len(captured) == 1 body = json.loads(captured[0].data.decode()) # `update` sends the whole capabilities object only when provided; # this pins that it is NOT auto-derived — omitted key stays []. assert body["capabilities"]["supported_commands"] == []