diff --git a/mocket/mocks/mockhttp.py b/mocket/mocks/mockhttp.py index d8a83cf..5524ddd 100644 --- a/mocket/mocks/mockhttp.py +++ b/mocket/mocks/mockhttp.py @@ -7,7 +7,7 @@ from functools import cached_property from http.server import BaseHTTPRequestHandler from typing import Any, Callable -from urllib.parse import parse_qs, unquote, urlsplit +from urllib.parse import parse_qs, urlsplit from h11 import SERVER, Connection, Data from h11 import Request as H11Request @@ -90,11 +90,7 @@ def querystring(self) -> dict: Dictionary of query parameter names to lists of values """ parts = self.path.split("?", 1) - return ( - parse_qs(unquote(parts[1]), keep_blank_values=True) - if len(parts) == 2 - else {} - ) + return parse_qs(parts[1], keep_blank_values=True) if len(parts) == 2 else {} @cached_property def body(self) -> str: diff --git a/tests/test_http.py b/tests/test_http.py index 91fa795..fbb0557 100644 --- a/tests/test_http.py +++ b/tests/test_http.py @@ -15,6 +15,49 @@ from mocket.mocks.mockhttp import Entry, Response +@pytest.mark.parametrize( + ("query", "expected"), + [ + ("q=one%26two", {"q": ["one&two"]}), + ("q=one%2Btwo", {"q": ["one+two"]}), + ("q=%2526", {"q": ["%26"]}), + ("field%26name=value", {"field&name": ["value"]}), + ("field%3Dname=value", {"field=name": ["value"]}), + ("q=%25E2%2582%25AC", {"q": ["%E2%82%AC"]}), + ("q=one+two", {"q": ["one two"]}), + ("q=one%20two", {"q": ["one two"]}), + ("q=%E2%82%AC", {"q": ["\u20ac"]}), + ("q=&q=next&bare&empty=", {"q": ["", "next"], "bare": [""], "empty": [""]}), + ("", {}), + (None, {}), + ], +) +def test_request_querystring_uses_form_urlencoded_semantics(query, expected): + path = "/" if query is None else f"/?{query}" + url = f"http://testme.org{path}" + with Mocketizer(strict_mode=True): + Entry.single_register(Entry.GET, url, body="ok") + with requests.get(url, timeout=5) as response: + assert response.content == b"ok" + assert Mocket.last_request().querystring == expected + + +@pytest.mark.parametrize("scheme", ["http", "https"]) +def test_recorded_querystring(scheme): + parameters = [("field&name", "a+b&c=%26"), ("q", ""), ("q", "\u20ac")] + url = f"{scheme}://testme.org/?{urlencode(parameters)}" + + with Mocketizer(strict_mode=True): + Entry.single_register(Entry.GET, url, body="ok") + with requests.get(url, timeout=5) as response: + assert response.content == b"ok" + + assert Mocket.last_request().querystring == { + "field&name": ["a+b&c=%26"], + "q": ["", "\u20ac"], + } + + class HttpTestCase(TestCase): def assertEqualHeaders(self, first, second, msg=None): first = {k.lower(): v for k, v in first.items()}