"""Regression tests for MSign raw-path extraction in _verify_msign. Uvicorn sets scope["path"] to the *decoded* URL path — e.g. a DELETE to /gabriel/muse/branches/feat%2Ffix-foo arrives in scope["path"] as /gabriel/muse/branches/feat/fix-foo. The client signs the encoded form, so using scope["path"] for the canonical message causes a signature mismatch and an HTTP 401 for any branch name that contains a slash (feat/*, task/*). The fix uses scope["raw_path"] (the encoded bytes off the wire) when available. These tests import and exercise the real ``_extract_signed_path`` helper used by ``_verify_msign`` — not a hand-copied mirror — so a future edit to the production function cannot silently drift from what's tested here again. """ from __future__ import annotations from unittest.mock import MagicMock from musehub.auth.request_signing import _extract_signed_path as _extract_path def _mock_request(raw_path: bytes, decoded_path: str, query: str = "") -> MagicMock: req = MagicMock() req.scope = {"raw_path": raw_path} req.url.path = decoded_path req.url.query = query return req # --------------------------------------------------------------------------- # Tests # --------------------------------------------------------------------------- class TestMSignRawPathExtraction: def test_encoded_slash_preserved_for_feat_branch(self) -> None: """Core regression: feat/* branch delete must use encoded path.""" req = _mock_request( raw_path=b"/gabriel/muse/branches/feat%2Ffix-agent-config-adapters", decoded_path="/gabriel/muse/branches/feat/fix-agent-config-adapters", ) path = _extract_path(req) assert path == "/gabriel/muse/branches/feat%2Ffix-agent-config-adapters" def test_encoded_slash_preserved_for_task_branch(self) -> None: req = _mock_request( raw_path=b"/gabriel/muse/branches/task%2Ffix-ops-commute", decoded_path="/gabriel/muse/branches/task/fix-ops-commute", ) path = _extract_path(req) assert path == "/gabriel/muse/branches/task%2Ffix-ops-commute" def test_decoded_form_absent_from_result(self) -> None: """The decoded slash must never appear in the canonical path.""" req = _mock_request( raw_path=b"/gabriel/muse/branches/feat%2Fmy-feature", decoded_path="/gabriel/muse/branches/feat/my-feature", ) path = _extract_path(req) assert "feat/my-feature" not in path def test_simple_path_without_encoding_unchanged(self) -> None: """Paths with no percent-encoding pass through unchanged.""" req = _mock_request( raw_path=b"/gabriel/muse/refs", decoded_path="/gabriel/muse/refs", ) assert _extract_path(req) == "/gabriel/muse/refs" def test_fallback_to_url_path_when_raw_path_absent(self) -> None: """ASGI servers that omit raw_path fall back to request.url.path.""" req = MagicMock() req.scope = {} req.url.path = "/gabriel/muse/refs" req.url.query = "" assert _extract_path(req) == "/gabriel/muse/refs" def test_fallback_to_url_path_when_raw_path_empty(self) -> None: """Empty raw_path bytes also trigger the fallback.""" req = _mock_request(raw_path=b"", decoded_path="/gabriel/muse/push/mpack-presign") assert _extract_path(req) == "/gabriel/muse/push/mpack-presign" def test_query_string_appended_with_encoded_path(self) -> None: req = _mock_request( raw_path=b"/search", decoded_path="/search", query="q=feat%2Fx", ) assert _extract_path(req) == "/search?q=feat%2Fx" def test_multiple_encoded_segments(self) -> None: """Deeply nested encoded paths are preserved in full.""" req = _mock_request( raw_path=b"/gabriel/muse/branches/feat%2Fauth%2Fv2", decoded_path="/gabriel/muse/branches/feat/auth/v2", ) assert _extract_path(req) == "/gabriel/muse/branches/feat%2Fauth%2Fv2"