"""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 typing import Protocol from unittest.mock import patch import pytest from muse.core.transport import HttpTransport, TransportError class _SideEffectFn(Protocol): calls: list[tuple[str, str, "dict[str, str]", "bytes | None"]] def __call__(self, method: str, url: str, headers: "dict[str, str]", data: "bytes | None" = ..., **kwargs: "str | int | bool") -> bytes: ... _UPLOAD_URL = "https://minio.example.com/mpacks/sha256:abc?X-Amz-Signature=fake" _MPACK_BYTES = b"fake-mpack-content" * 50 _Headers = dict[str, str] def _mock_urllib_do(status: int = 200, body: bytes = b"") -> "_SideEffectFn": calls: list[tuple[str, str, dict[str, str], 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 # ── MP-1 ────────────────────────────────────────────────────────────────────── def test_mp1_put_sent_to_upload_url() -> None: do = _mock_urllib_do(200) with patch("muse.core.transport._urllib_do", side_effect=do): HttpTransport().push_mpack_put(_UPLOAD_URL, _MPACK_BYTES) _, url, _, _ = do.calls[0] assert url == _UPLOAD_URL # ── MP-2 ────────────────────────────────────────────────────────────────────── def test_mp2_body_is_raw_bytes() -> None: do = _mock_urllib_do(200) with patch("muse.core.transport._urllib_do", side_effect=do): HttpTransport().push_mpack_put(_UPLOAD_URL, _MPACK_BYTES) _, _, _, sent_data = do.calls[0] assert sent_data == _MPACK_BYTES # ── MP-3 ────────────────────────────────────────────────────────────────────── def test_mp3_no_authorization_header() -> None: do = _mock_urllib_do(200) with patch("muse.core.transport._urllib_do", side_effect=do): HttpTransport().push_mpack_put(_UPLOAD_URL, _MPACK_BYTES) _, _, headers, _ = do.calls[0] assert "Authorization" not in headers assert "authorization" not in {k.lower() for k in headers} # ── MP-4 ────────────────────────────────────────────────────────────────────── def test_mp4_200_returns_none() -> None: do = _mock_urllib_do(200) with patch("muse.core.transport._urllib_do", side_effect=do): result = HttpTransport().push_mpack_put(_UPLOAD_URL, _MPACK_BYTES) assert result is None # ── MP-5 ────────────────────────────────────────────────────────────────────── def test_mp5_non_2xx_raises_transport_error() -> None: do = _mock_urllib_do(403, b"Request has expired") with patch("muse.core.transport._urllib_do", side_effect=do): with pytest.raises(TransportError): HttpTransport().push_mpack_put(_UPLOAD_URL, _MPACK_BYTES) # ── MP-6 ────────────────────────────────────────────────────────────────────── def test_mp6_204_no_content_is_success() -> None: do = _mock_urllib_do(204) with patch("muse.core.transport._urllib_do", side_effect=do): result = HttpTransport().push_mpack_put(_UPLOAD_URL, _MPACK_BYTES) assert result is None