gabriel / muse public
test_transport_push_mpack_unpack.py python
125 lines 5.3 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_unpack: client-side Step 3 of the mpack push protocol.
2
3 MU-1 Happy path: POST returns head, branch, blobs_in_mpack, commits_in_mpack.
4 MU-2 POST body encodes all unpack fields correctly.
5 MU-3 POST is sent to /{owner}/{slug}/push/unpack-mpack.
6 MU-4 Non-2xx response raises TransportError.
7 MU-6 MSign Authorization header is present (unlike step 2).
8 """
9 from __future__ import annotations
10
11 from unittest.mock import MagicMock, patch
12
13 import msgpack
14 import pytest
15
16 from muse.core.transport import HttpTransport, TransportError
17 from muse.core.types import blob_id, fake_id
18
19 _Headers = dict[str, str]
20 _URL = "https://staging.musehub.ai/gabriel/muse"
21 _MPACK_KEY = blob_id(b"mpack bytes")
22 _HEAD = fake_id("tip-commit")
23
24
25 def _unpack_response(
26 head: str = _HEAD,
27 branch: str = "main",
28 blobs_in_mpack: int = 5,
29 commits_in_mpack: int = 2,
30 ) -> bytes:
31 return msgpack.packb({
32 "head": head,
33 "branch": branch,
34 "blobs_in_mpack": blobs_in_mpack,
35 "commits_in_mpack": commits_in_mpack,
36 }, use_bin_type=True)
37
38
39 def _mock_urllib_do(body: bytes, status: int = 200) -> "Callable[..., bytes]":
40 calls: list[tuple[str, str, dict, bytes | None]] = []
41
42 def _side_effect(method: str, url: str, headers: "dict[str, str]", data: "bytes | None" = None, **kwargs: "str | int | bool") -> bytes:
43 calls.append((method, url, headers, data))
44 if status >= 400:
45 raise TransportError(f"HTTP {status}", status)
46 return body
47
48 _side_effect.calls = calls
49 return _side_effect
50
51
52 # ── MU-1 ──────────────────────────────────────────────────────────────────────
53
54 def test_mu1_happy_path_returns_all_fields() -> None:
55 do = _mock_urllib_do(_unpack_response())
56 with patch("muse.core.transport._urllib_do", side_effect=do):
57 result = HttpTransport().push_mpack_unpack(
58 _URL, None, _MPACK_KEY, branch="main", head=_HEAD,
59 commits_count=2, blobs_count=5,
60 )
61 assert result["head"] == _HEAD
62 assert result["branch"] == "main"
63 assert result["blobs_in_mpack"] == 5
64 assert result["commits_in_mpack"] == 2
65
66
67 # ── MU-2 ──────────────────────────────────────────────────────────────────────
68
69 def test_mu2_post_body_encodes_all_fields() -> None:
70 do = _mock_urllib_do(_unpack_response())
71 with patch("muse.core.transport._urllib_do", side_effect=do):
72 HttpTransport().push_mpack_unpack(
73 _URL, None, _MPACK_KEY, branch="dev", head=_HEAD,
74 commits_count=3, blobs_count=10,
75 )
76 _, _, _, sent_data = do.calls[0]
77 payload = msgpack.unpackb(sent_data, raw=False)
78 assert payload["mpack_key"] == _MPACK_KEY
79 assert payload["branch"] == "dev"
80 assert payload["head"] == _HEAD
81 assert payload["commits_count"] == 3
82 assert payload["blobs_count"] == 10
83
84
85 # ── MU-3 ──────────────────────────────────────────────────────────────────────
86
87 def test_mu3_posts_to_correct_endpoint() -> None:
88 do = _mock_urllib_do(_unpack_response())
89 with patch("muse.core.transport._urllib_do", side_effect=do):
90 HttpTransport().push_mpack_unpack(_URL, None, _MPACK_KEY, head=_HEAD)
91 _, url, _, _ = do.calls[0]
92 assert url.endswith("/push/unpack-mpack")
93
94
95 # ── MU-4 ──────────────────────────────────────────────────────────────────────
96
97 def test_mu4_non_200_raises_transport_error() -> None:
98 do = _mock_urllib_do(b"integrity failure", status=422)
99 with patch("muse.core.transport._urllib_do", side_effect=do):
100 with pytest.raises(TransportError):
101 HttpTransport().push_mpack_unpack(_URL, None, _MPACK_KEY, head=_HEAD)
102
103
104 # ── MU-6 ──────────────────────────────────────────────────────────────────────
105
106 def test_mu6_authorization_header_present() -> None:
107 """Step 3 is a MuseHub API call — MSign auth header required."""
108 from muse.core.transport import SigningIdentity
109
110 mock_key = MagicMock()
111 mock_key.sign = MagicMock(return_value=b"\x00" * 64)
112 mock_key.public_key = MagicMock(return_value=MagicMock(
113 public_bytes=MagicMock(return_value=b"\x01" * 32)
114 ))
115 signing = SigningIdentity(handle="gabriel", private_key=mock_key)
116
117 do = _mock_urllib_do(_unpack_response())
118 with patch("muse.core.transport._urllib_do", side_effect=do):
119 with patch("muse.core.msign.build_msign_header", return_value="MSign fake") as mock_sign:
120 with patch("muse.core.hub_trust.check_and_pin"):
121 HttpTransport().push_mpack_unpack(_URL, signing, _MPACK_KEY, head=_HEAD)
122
123 mock_sign.assert_called_once()
124 _, _, headers, _ = do.calls[0]
125 assert any(k.lower() == "authorization" for k in headers)
File History 3 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
sha256:36c3cb3e76619d4c30a6d9bf81b5ec4ff148e30dcfed913e3114ca7b43b81c7e fix: rename objects→blobs in push client and all stale test… Sonnet 4.6 patch 22 days ago