"""TDD — HttpTransport.push_mpack_put: client-side Step 2 of the mpack push protocol. PUT mpack_bytes directly to a presigned MinIO URL. No MuseHub API. No auth header. No encoding. The presigned URL is the credential. MP-1 PUT is sent to upload_url, not any MuseHub endpoint. MP-2 Request body is raw mpack_bytes — no msgpack encoding, no wrapping. MP-3 No Authorization header is added. MP-4 2xx response → returns None (no error). MP-5 Non-2xx response → raises TransportError. MP-6 MinIO 204 (no content) is also treated as success. """ from __future__ import annotations from unittest.mock import MagicMock, patch import pytest from muse.core.transport import HttpTransport, TransportError _UPLOAD_URL = "https://minio.example.com/mpacks/sha256:abc?X-Amz-Signature=fake" _MPACK_BYTES = b"fake-mpack-content" * 50 _Headers = dict[str, str] class _Resp: def __init__(self, status: int = 200, body: bytes = b"") -> None: self.status_code = status self.content = body def _mock_client(resp: _Resp) -> MagicMock: mock = MagicMock() mock.__enter__ = MagicMock(return_value=mock) mock.__exit__ = MagicMock(return_value=False) mock.put = MagicMock(return_value=resp) return mock # ── MP-1 ────────────────────────────────────────────────────────────────────── def test_mp1_put_sent_to_upload_url() -> None: """PUT must go to upload_url exactly — not a MuseHub endpoint.""" mock_client = _mock_client(_Resp(200)) transport = HttpTransport() with patch("muse.core.transport._httpx_mod.Client", return_value=mock_client): transport.push_mpack_put(_UPLOAD_URL, _MPACK_BYTES) put_url: str = mock_client.put.call_args[0][0] assert put_url == _UPLOAD_URL # ── MP-2 ────────────────────────────────────────────────────────────────────── def test_mp2_body_is_raw_bytes() -> None: """Body must be the raw mpack bytes — no encoding, no wrapping.""" mock_client = _mock_client(_Resp(200)) transport = HttpTransport() with patch("muse.core.transport._httpx_mod.Client", return_value=mock_client): transport.push_mpack_put(_UPLOAD_URL, _MPACK_BYTES) call_kwargs = mock_client.put.call_args sent_body = call_kwargs[1].get("content") or call_kwargs[0][1] assert sent_body == _MPACK_BYTES # ── MP-3 ────────────────────────────────────────────────────────────────────── def test_mp3_no_authorization_header() -> None: """No Authorization header — the presigned URL is the credential.""" mock_client = _mock_client(_Resp(200)) transport = HttpTransport() with patch("muse.core.transport._httpx_mod.Client", return_value=mock_client): transport.push_mpack_put(_UPLOAD_URL, _MPACK_BYTES) call_kwargs = mock_client.put.call_args headers: _Headers = call_kwargs[1].get("headers") or {} assert "Authorization" not in headers assert "authorization" not in {k.lower() for k in headers} # ── MP-4 ────────────────────────────────────────────────────────────────────── def test_mp4_200_returns_none() -> None: """Successful PUT returns None — no payload to parse.""" mock_client = _mock_client(_Resp(200)) transport = HttpTransport() with patch("muse.core.transport._httpx_mod.Client", return_value=mock_client): result = transport.push_mpack_put(_UPLOAD_URL, _MPACK_BYTES) assert result is None # ── MP-5 ────────────────────────────────────────────────────────────────────── def test_mp5_non_2xx_raises_transport_error() -> None: """Non-2xx (e.g. 403 expired presign) raises TransportError.""" mock_client = _mock_client(_Resp(403, b"Request has expired")) transport = HttpTransport() with patch("muse.core.transport._httpx_mod.Client", return_value=mock_client): with pytest.raises(TransportError): transport.push_mpack_put(_UPLOAD_URL, _MPACK_BYTES) # ── MP-6 ────────────────────────────────────────────────────────────────────── def test_mp6_204_no_content_is_success() -> None: """MinIO may respond 204 No Content on PUT — treat as success.""" mock_client = _mock_client(_Resp(204)) transport = HttpTransport() with patch("muse.core.transport._httpx_mod.Client", return_value=mock_client): result = transport.push_mpack_put(_UPLOAD_URL, _MPACK_BYTES) assert result is None