"""TDD — HttpTransport.push_mpack_presign: client-side Step 1 of the mpack push protocol. PP-1 Happy path: POST returns {upload_url, mpack_key} — both echoed back correctly. PP-2 The POST body contains mpack_key == blob_id(mpack_bytes) and correct size_bytes. PP-3 The POST is sent to /{owner}/{slug}/push/mpack-presign. PP-4 Non-200 response raises TransportError. PP-5 Response missing upload_url raises TransportError. """ from __future__ import annotations from unittest.mock import patch import msgpack import pytest from muse.core.transport import HttpTransport, TransportError from muse.core.types import blob_id _URL = "https://staging.musehub.ai/gabriel/muse" _UPLOAD_URL = "https://minio.example.com/mpacks/sha256:abc?sig=fake" def _presign_response(mpack_key: str, upload_url: str = _UPLOAD_URL) -> bytes: return msgpack.packb( {"upload_url": upload_url, "mpack_key": mpack_key}, 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 # ── PP-1 ────────────────────────────────────────────────────────────────────── def test_pp1_happy_path_returns_upload_url_and_mpack_key() -> None: mpack_bytes = b"fake mpack content" mpack_key = blob_id(mpack_bytes) do = _mock_urllib_do(_presign_response(mpack_key)) with patch("muse.core.transport._urllib_do", side_effect=do): result = HttpTransport().push_mpack_presign(_URL, None, mpack_bytes) assert result["upload_url"] == _UPLOAD_URL assert result["mpack_key"] == mpack_key # ── PP-2 ────────────────────────────────────────────────────────────────────── def test_pp2_post_body_contains_correct_key_and_size() -> None: mpack_bytes = b"x" * 512 mpack_key = blob_id(mpack_bytes) do = _mock_urllib_do(_presign_response(mpack_key)) with patch("muse.core.transport._urllib_do", side_effect=do): HttpTransport().push_mpack_presign(_URL, None, mpack_bytes) _, _, _, sent_data = do.calls[0] payload = msgpack.unpackb(sent_data, raw=False) assert payload["mpack_key"] == mpack_key assert payload["size_bytes"] == 512 # ── PP-3 ────────────────────────────────────────────────────────────────────── def test_pp3_posts_to_correct_endpoint() -> None: mpack_bytes = b"data" mpack_key = blob_id(mpack_bytes) do = _mock_urllib_do(_presign_response(mpack_key)) with patch("muse.core.transport._urllib_do", side_effect=do): HttpTransport().push_mpack_presign(_URL, None, mpack_bytes) _, url, _, _ = do.calls[0] assert url.endswith("/push/mpack-presign") # ── PP-4 ────────────────────────────────────────────────────────────────────── def test_pp4_non_200_raises_transport_error() -> None: do = _mock_urllib_do(b"quota exceeded", status=429) with patch("muse.core.transport._urllib_do", side_effect=do): with pytest.raises(TransportError): HttpTransport().push_mpack_presign(_URL, None, b"bytes") # ── PP-5 ────────────────────────────────────────────────────────────────────── def test_pp5_missing_upload_url_raises_transport_error() -> None: mpack_bytes = b"data" mpack_key = blob_id(mpack_bytes) bad_resp = msgpack.packb({"mpack_key": mpack_key}, use_bin_type=True) do = _mock_urllib_do(bad_resp) with patch("muse.core.transport._urllib_do", side_effect=do): with pytest.raises(TransportError, match="upload_url"): HttpTransport().push_mpack_presign(_URL, None, mpack_bytes)