"""TDD — HttpTransport.push_mpack_unpack: client-side Step 3 of the mpack push protocol. MU-1 Happy path: POST returns head, branch, blobs_in_mpack, commits_in_mpack. MU-2 POST body encodes all unpack fields correctly. MU-3 POST is sent to /{owner}/{slug}/push/unpack-mpack. MU-4 Non-2xx response raises TransportError. MU-6 MSign Authorization header is present (unlike step 2). """ from __future__ import annotations from unittest.mock import MagicMock, patch import msgpack import pytest from muse.core.transport import HttpTransport, TransportError from muse.core.types import blob_id, fake_id _Headers = dict[str, str] _URL = "https://staging.musehub.ai/gabriel/muse" _MPACK_KEY = blob_id(b"mpack bytes") _HEAD = fake_id("tip-commit") def _unpack_response( head: str = _HEAD, branch: str = "main", blobs_in_mpack: int = 5, commits_in_mpack: int = 2, ) -> bytes: return msgpack.packb({ "head": head, "branch": branch, "blobs_in_mpack": blobs_in_mpack, "commits_in_mpack": commits_in_mpack, }, use_bin_type=True) def _mock_urllib_do(body: bytes, status: int = 200) -> "Callable[..., bytes]": calls: list[tuple[str, str, dict, bytes | None]] = [] def _side_effect(method: str, url: str, headers: "dict[str, str]", data: "bytes | None" = None, **kwargs: "str | int | bool") -> bytes: calls.append((method, url, headers, data)) if status >= 400: raise TransportError(f"HTTP {status}", status) return body _side_effect.calls = calls return _side_effect # ── MU-1 ────────────────────────────────────────────────────────────────────── def test_mu1_happy_path_returns_all_fields() -> None: do = _mock_urllib_do(_unpack_response()) with patch("muse.core.transport._urllib_do", side_effect=do): result = HttpTransport().push_mpack_unpack( _URL, None, _MPACK_KEY, branch="main", head=_HEAD, commits_count=2, blobs_count=5, ) assert result["head"] == _HEAD assert result["branch"] == "main" assert result["blobs_in_mpack"] == 5 assert result["commits_in_mpack"] == 2 # ── MU-2 ────────────────────────────────────────────────────────────────────── def test_mu2_post_body_encodes_all_fields() -> None: do = _mock_urllib_do(_unpack_response()) with patch("muse.core.transport._urllib_do", side_effect=do): HttpTransport().push_mpack_unpack( _URL, None, _MPACK_KEY, branch="dev", head=_HEAD, commits_count=3, blobs_count=10, ) _, _, _, sent_data = do.calls[0] payload = msgpack.unpackb(sent_data, raw=False) assert payload["mpack_key"] == _MPACK_KEY assert payload["branch"] == "dev" assert payload["head"] == _HEAD assert payload["commits_count"] == 3 assert payload["blobs_count"] == 10 # ── MU-3 ────────────────────────────────────────────────────────────────────── def test_mu3_posts_to_correct_endpoint() -> None: do = _mock_urllib_do(_unpack_response()) with patch("muse.core.transport._urllib_do", side_effect=do): HttpTransport().push_mpack_unpack(_URL, None, _MPACK_KEY, head=_HEAD) _, url, _, _ = do.calls[0] assert url.endswith("/push/unpack-mpack") # ── MU-4 ────────────────────────────────────────────────────────────────────── def test_mu4_non_200_raises_transport_error() -> None: do = _mock_urllib_do(b"integrity failure", status=422) with patch("muse.core.transport._urllib_do", side_effect=do): with pytest.raises(TransportError): HttpTransport().push_mpack_unpack(_URL, None, _MPACK_KEY, head=_HEAD) # ── MU-6 ────────────────────────────────────────────────────────────────────── def test_mu6_authorization_header_present() -> None: """Step 3 is a MuseHub API call — MSign auth header required.""" from muse.core.transport import SigningIdentity mock_key = MagicMock() mock_key.sign = MagicMock(return_value=b"\x00" * 64) mock_key.public_key = MagicMock(return_value=MagicMock( public_bytes=MagicMock(return_value=b"\x01" * 32) )) signing = SigningIdentity(handle="gabriel", private_key=mock_key) do = _mock_urllib_do(_unpack_response()) with patch("muse.core.transport._urllib_do", side_effect=do): with patch("muse.core.msign.build_msign_header", return_value="MSign fake") as mock_sign: with patch("muse.core.hub_trust.check_and_pin"): HttpTransport().push_mpack_unpack(_URL, signing, _MPACK_KEY, head=_HEAD) mock_sign.assert_called_once() _, _, headers, _ = do.calls[0] assert any(k.lower() == "authorization" for k in headers)