"""muse#20 Phase 1 — domains.py must resolve signing identity against the target hub URL, not the repo's default [hub] url. Regression for the same bug class as ls-remote's HTTP 401 (muse#20 background): `run_publish`/`run_update`/`run_delete` all computed `resolved_hub` (from `--hub`, or the repo's `[hub] url`, or the musehub.ai default) but then called `get_signing_identity(repo_root)` without forwarding it — so the identity lookup used the repo's default hub URL instead of the actual target, producing a wrong-hostname lookup whenever `--hub` pointed somewhere else (e.g. staging while the repo's default config points at localhost). Mirrors the `TestSigningIdentityForwarding` pattern from tests/test_cmd_ls_remote.py. """ from __future__ import annotations import pathlib from unittest.mock import patch from tests.cli_test_helper import CliRunner cli = None runner = CliRunner() _OTHER_HUB = "https://staging.musehub.ai" def _env(root: pathlib.Path) -> dict[str, str]: return {"MUSE_REPO_ROOT": str(root)} def _init_repo(tmp_path: pathlib.Path) -> pathlib.Path: """A minimal repo whose default [hub] url deliberately differs from the --hub target used in each test, so a wrong-hostname lookup would be caught.""" import json from muse.core.paths import muse_dir dot_muse = muse_dir(tmp_path) dot_muse.mkdir(parents=True, exist_ok=True) (dot_muse / "repo.json").write_text(json.dumps({"repo_id": "test-repo", "domain": "code"})) (dot_muse / "config.toml").write_text('[hub]\nurl = "https://localhost:1337"\n') return tmp_path class TestPublishSigningIdentityForwarding: def test_resolved_hub_forwarded_to_signing_identity(self, tmp_path: pathlib.Path) -> None: repo = _init_repo(tmp_path) with patch("muse.cli.commands.domains.get_signing_identity") as mock_gsi: mock_gsi.return_value = None runner.invoke( cli, [ "domains", "publish", "--author", "gabriel", "--slug", "genomics", "--name", "Genomics", "--description", "desc", "--viewer-type", "generic", "--hub", _OTHER_HUB, ], env=_env(repo), ) mock_gsi.assert_called_once() _, kwargs = mock_gsi.call_args assert kwargs.get("remote_url") == _OTHER_HUB, ( f"get_signing_identity must be called with remote_url={_OTHER_HUB!r}; " f"got {kwargs.get('remote_url')!r}. Without this, publish uses the " "wrong signing key whenever --hub differs from the repo's default." ) class TestUpdateSigningIdentityForwarding: def test_resolved_hub_forwarded_to_signing_identity(self, tmp_path: pathlib.Path) -> None: repo = _init_repo(tmp_path) with patch("muse.cli.commands.domains.get_signing_identity") as mock_gsi: mock_gsi.return_value = None runner.invoke( cli, [ "domains", "update", "--author", "gabriel", "--slug", "genomics", "--name", "Genomics v2", "--hub", _OTHER_HUB, ], env=_env(repo), ) mock_gsi.assert_called_once() _, kwargs = mock_gsi.call_args assert kwargs.get("remote_url") == _OTHER_HUB, ( f"get_signing_identity must be called with remote_url={_OTHER_HUB!r}; " f"got {kwargs.get('remote_url')!r}." ) class TestDeleteSigningIdentityForwarding: def test_resolved_hub_forwarded_to_signing_identity(self, tmp_path: pathlib.Path) -> None: repo = _init_repo(tmp_path) with patch("muse.cli.commands.domains.get_signing_identity") as mock_gsi: mock_gsi.return_value = None runner.invoke( cli, [ "domains", "delete", "--author", "gabriel", "--slug", "genomics", "--hub", _OTHER_HUB, ], env=_env(repo), ) mock_gsi.assert_called_once() _, kwargs = mock_gsi.call_args assert kwargs.get("remote_url") == _OTHER_HUB, ( f"get_signing_identity must be called with remote_url={_OTHER_HUB!r}; " f"got {kwargs.get('remote_url')!r}." ) class TestReleaseSigningIdentityForwarding: """muse#20 Phase 2 — release.py's get_signing_identity calls, already behaviorally correct (positional remote_url), are now explicit keyword style. These tests pin behavior so a future positional-arg drift (e.g. an accidental new positional parameter inserted before remote_url in get_signing_identity's signature) cannot silently break identity resolution without a loud test failure. """ def test_release_list_forwards_resolved_remote_url(self, tmp_path: pathlib.Path) -> None: import json from muse.core.paths import muse_dir repo = tmp_path dot_muse = muse_dir(repo) dot_muse.mkdir(parents=True, exist_ok=True) (dot_muse / "repo.json").write_text(json.dumps({"repo_id": "test-repo", "domain": "code"})) (dot_muse / "config.toml").write_text( f'[hub]\nurl = "https://localhost:1337"\n' f'[remotes.staging]\nurl = "{_OTHER_HUB}"\n' ) with patch("muse.cli.commands.release.get_signing_identity") as mock_gsi: mock_gsi.return_value = None with patch("muse.cli.commands.release.make_transport") as mt: mt.return_value.list_releases_remote.return_value = [] runner.invoke( cli, ["release", "list", "--remote", "staging", "--json"], env=_env(repo), ) mock_gsi.assert_called_once() _, kwargs = mock_gsi.call_args assert kwargs.get("remote_url") == _OTHER_HUB, ( f"get_signing_identity must be called with remote_url={_OTHER_HUB!r}; " f"got {kwargs.get('remote_url')!r}." )