gabriel / musehub public
test_request_signing_raw_path.py python
96 lines 4.0 KB
Raw
sha256:c748a46461e554a9560edea4edf4c3dffd2a7f81a1948ebdba60f5ec940c6e25 fix: use raw ASGI path for MSign canonical message Sonnet 4.6 patch 40 days ago
1 """Regression tests for MSign raw-path extraction in _verify_msign.
2
3 Uvicorn sets scope["path"] to the *decoded* URL path — e.g. a DELETE to
4 /gabriel/muse/branches/feat%2Ffix-foo arrives in scope["path"] as
5 /gabriel/muse/branches/feat/fix-foo. The client signs the encoded form, so
6 using scope["path"] for the canonical message causes a signature mismatch and
7 an HTTP 401 for any branch name that contains a slash (feat/*, task/*).
8
9 The fix uses scope["raw_path"] (the encoded bytes off the wire) when available.
10 These tests import and exercise the real ``_extract_signed_path`` helper used
11 by ``_verify_msign`` — not a hand-copied mirror — so a future edit to the
12 production function cannot silently drift from what's tested here again.
13 """
14 from __future__ import annotations
15
16 from unittest.mock import MagicMock
17
18 from musehub.auth.request_signing import _extract_signed_path as _extract_path
19
20
21 def _mock_request(raw_path: bytes, decoded_path: str, query: str = "") -> MagicMock:
22 req = MagicMock()
23 req.scope = {"raw_path": raw_path}
24 req.url.path = decoded_path
25 req.url.query = query
26 return req
27
28
29 # ---------------------------------------------------------------------------
30 # Tests
31 # ---------------------------------------------------------------------------
32
33 class TestMSignRawPathExtraction:
34
35 def test_encoded_slash_preserved_for_feat_branch(self) -> None:
36 """Core regression: feat/* branch delete must use encoded path."""
37 req = _mock_request(
38 raw_path=b"/gabriel/muse/branches/feat%2Ffix-agent-config-adapters",
39 decoded_path="/gabriel/muse/branches/feat/fix-agent-config-adapters",
40 )
41 path = _extract_path(req)
42 assert path == "/gabriel/muse/branches/feat%2Ffix-agent-config-adapters"
43
44 def test_encoded_slash_preserved_for_task_branch(self) -> None:
45 req = _mock_request(
46 raw_path=b"/gabriel/muse/branches/task%2Ffix-ops-commute",
47 decoded_path="/gabriel/muse/branches/task/fix-ops-commute",
48 )
49 path = _extract_path(req)
50 assert path == "/gabriel/muse/branches/task%2Ffix-ops-commute"
51
52 def test_decoded_form_absent_from_result(self) -> None:
53 """The decoded slash must never appear in the canonical path."""
54 req = _mock_request(
55 raw_path=b"/gabriel/muse/branches/feat%2Fmy-feature",
56 decoded_path="/gabriel/muse/branches/feat/my-feature",
57 )
58 path = _extract_path(req)
59 assert "feat/my-feature" not in path
60
61 def test_simple_path_without_encoding_unchanged(self) -> None:
62 """Paths with no percent-encoding pass through unchanged."""
63 req = _mock_request(
64 raw_path=b"/gabriel/muse/refs",
65 decoded_path="/gabriel/muse/refs",
66 )
67 assert _extract_path(req) == "/gabriel/muse/refs"
68
69 def test_fallback_to_url_path_when_raw_path_absent(self) -> None:
70 """ASGI servers that omit raw_path fall back to request.url.path."""
71 req = MagicMock()
72 req.scope = {}
73 req.url.path = "/gabriel/muse/refs"
74 req.url.query = ""
75 assert _extract_path(req) == "/gabriel/muse/refs"
76
77 def test_fallback_to_url_path_when_raw_path_empty(self) -> None:
78 """Empty raw_path bytes also trigger the fallback."""
79 req = _mock_request(raw_path=b"", decoded_path="/gabriel/muse/push/mpack-presign")
80 assert _extract_path(req) == "/gabriel/muse/push/mpack-presign"
81
82 def test_query_string_appended_with_encoded_path(self) -> None:
83 req = _mock_request(
84 raw_path=b"/search",
85 decoded_path="/search",
86 query="q=feat%2Fx",
87 )
88 assert _extract_path(req) == "/search?q=feat%2Fx"
89
90 def test_multiple_encoded_segments(self) -> None:
91 """Deeply nested encoded paths are preserved in full."""
92 req = _mock_request(
93 raw_path=b"/gabriel/muse/branches/feat%2Fauth%2Fv2",
94 decoded_path="/gabriel/muse/branches/feat/auth/v2",
95 )
96 assert _extract_path(req) == "/gabriel/muse/branches/feat%2Fauth%2Fv2"
File History 1 commit
sha256:c748a46461e554a9560edea4edf4c3dffd2a7f81a1948ebdba60f5ec940c6e25 fix: use raw ASGI path for MSign canonical message Sonnet 4.6 patch 40 days ago