gabriel / muse public
test_transport_push_mpack_put.py python
120 lines 5.1 KB
Raw
sha256:79ffe87f5fe2ec146e35f05521218bbf54dffdb0440c07f970bad05f16efb89f chore: merge main — carry all urllib/typing/test fixes from dev Sonnet 4.6 minor ⚠ breaking 19 days ago
1 """TDD — HttpTransport.push_mpack_put: client-side Step 2 of the mpack push protocol.
2
3 PUT mpack_bytes directly to a presigned MinIO URL. No MuseHub API. No auth
4 header. No encoding. The presigned URL is the credential.
5
6 MP-1 PUT is sent to upload_url, not any MuseHub endpoint.
7 MP-2 Request body is raw mpack_bytes — no msgpack encoding, no wrapping.
8 MP-3 No Authorization header is added.
9 MP-4 2xx response → returns None (no error).
10 MP-5 Non-2xx response → raises TransportError.
11 MP-6 MinIO 204 (no content) is also treated as success.
12 """
13 from __future__ import annotations
14
15 from unittest.mock import MagicMock, patch
16
17 import pytest
18
19 from muse.core.transport import HttpTransport, TransportError
20
21 _UPLOAD_URL = "https://minio.example.com/mpacks/sha256:abc?X-Amz-Signature=fake"
22 _MPACK_BYTES = b"fake-mpack-content" * 50
23 _Headers = dict[str, str]
24
25
26 class _Resp:
27 def __init__(self, status: int = 200, body: bytes = b"") -> None:
28 self.status_code = status
29 self.content = body
30
31
32 def _mock_client(resp: _Resp) -> MagicMock:
33 mock = MagicMock()
34 mock.__enter__ = MagicMock(return_value=mock)
35 mock.__exit__ = MagicMock(return_value=False)
36 mock.put = MagicMock(return_value=resp)
37 return mock
38
39
40 # ── MP-1 ──────────────────────────────────────────────────────────────────────
41
42 def test_mp1_put_sent_to_upload_url() -> None:
43 """PUT must go to upload_url exactly — not a MuseHub endpoint."""
44 mock_client = _mock_client(_Resp(200))
45 transport = HttpTransport()
46
47 with patch("muse.core.transport._httpx_mod.Client", return_value=mock_client):
48 transport.push_mpack_put(_UPLOAD_URL, _MPACK_BYTES)
49
50 put_url: str = mock_client.put.call_args[0][0]
51 assert put_url == _UPLOAD_URL
52
53
54 # ── MP-2 ──────────────────────────────────────────────────────────────────────
55
56 def test_mp2_body_is_raw_bytes() -> None:
57 """Body must be the raw mpack bytes — no encoding, no wrapping."""
58 mock_client = _mock_client(_Resp(200))
59 transport = HttpTransport()
60
61 with patch("muse.core.transport._httpx_mod.Client", return_value=mock_client):
62 transport.push_mpack_put(_UPLOAD_URL, _MPACK_BYTES)
63
64 call_kwargs = mock_client.put.call_args
65 sent_body = call_kwargs[1].get("content") or call_kwargs[0][1]
66 assert sent_body == _MPACK_BYTES
67
68
69 # ── MP-3 ──────────────────────────────────────────────────────────────────────
70
71 def test_mp3_no_authorization_header() -> None:
72 """No Authorization header — the presigned URL is the credential."""
73 mock_client = _mock_client(_Resp(200))
74 transport = HttpTransport()
75
76 with patch("muse.core.transport._httpx_mod.Client", return_value=mock_client):
77 transport.push_mpack_put(_UPLOAD_URL, _MPACK_BYTES)
78
79 call_kwargs = mock_client.put.call_args
80 headers: _Headers = call_kwargs[1].get("headers") or {}
81 assert "Authorization" not in headers
82 assert "authorization" not in {k.lower() for k in headers}
83
84
85 # ── MP-4 ──────────────────────────────────────────────────────────────────────
86
87 def test_mp4_200_returns_none() -> None:
88 """Successful PUT returns None — no payload to parse."""
89 mock_client = _mock_client(_Resp(200))
90 transport = HttpTransport()
91
92 with patch("muse.core.transport._httpx_mod.Client", return_value=mock_client):
93 result = transport.push_mpack_put(_UPLOAD_URL, _MPACK_BYTES)
94
95 assert result is None
96
97
98 # ── MP-5 ──────────────────────────────────────────────────────────────────────
99
100 def test_mp5_non_2xx_raises_transport_error() -> None:
101 """Non-2xx (e.g. 403 expired presign) raises TransportError."""
102 mock_client = _mock_client(_Resp(403, b"Request has expired"))
103 transport = HttpTransport()
104
105 with patch("muse.core.transport._httpx_mod.Client", return_value=mock_client):
106 with pytest.raises(TransportError):
107 transport.push_mpack_put(_UPLOAD_URL, _MPACK_BYTES)
108
109
110 # ── MP-6 ──────────────────────────────────────────────────────────────────────
111
112 def test_mp6_204_no_content_is_success() -> None:
113 """MinIO may respond 204 No Content on PUT — treat as success."""
114 mock_client = _mock_client(_Resp(204))
115 transport = HttpTransport()
116
117 with patch("muse.core.transport._httpx_mod.Client", return_value=mock_client):
118 result = transport.push_mpack_put(_UPLOAD_URL, _MPACK_BYTES)
119
120 assert result is None
File History 2 commits
sha256:79ffe87f5fe2ec146e35f05521218bbf54dffdb0440c07f970bad05f16efb89f chore: merge main — carry all urllib/typing/test fixes from dev Sonnet 4.6 minor 19 days ago
sha256:0bea7600d1eee83e87950be49933b1006fa9dc2c71e7c4ee748d324f61138156 chore: bump version to 0.2.0rc11; fix typing audit violatio… Sonnet 4.6 minor 19 days ago