diff --git a/CHANGES b/CHANGES index b286c134..dcdce261 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,11 @@ +0.26.3 +------ + +* Fixed `query_string_matcher` (and the query matching auto-applied to a + registered URL's own query string) discarding blank-valued query params + (``b=``), which caused requests with an extra or missing blank param to + match incorrectly. See #804 + 0.26.2 ------ diff --git a/responses/matchers.py b/responses/matchers.py index 4b9d1f1b..d61decc1 100644 --- a/responses/matchers.py +++ b/responses/matchers.py @@ -259,8 +259,12 @@ def match(request: PreparedRequest) -> Tuple[bool, str]: data = parse_url(request.url or "") request_query = data.query - request_qsl = sorted(parse_qsl(request_query)) if request_query else {} - matcher_qsl = sorted(parse_qsl(query)) if query else {} + request_qsl = ( + sorted(parse_qsl(request_query, keep_blank_values=True)) + if request_query + else {} + ) + matcher_qsl = sorted(parse_qsl(query, keep_blank_values=True)) if query else {} valid = not query if request_query is None else request_qsl == matcher_qsl diff --git a/responses/tests/test_matchers.py b/responses/tests/test_matchers.py index 688e51d1..fc4fbc6f 100644 --- a/responses/tests/test_matchers.py +++ b/responses/tests/test_matchers.py @@ -69,6 +69,40 @@ def run(): assert_reset() +def test_query_string_matcher_blank_values(): + """A blank-valued query param (``foo=``) is significant: it must be present + to match, and an extra blank param must not be ignored.""" + + def run(): + with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: + rsps.add( + responses.GET, + "http://example.com", + body=b"test", + match=[matchers.query_string_matcher("test=1&foo=")], + ) + # Exact match, including the blank param, succeeds. + resp = requests.get("http://example.com?test=1&foo=") + assert_response(resp, "test") + # Omitting the required blank param must not match. + with pytest.raises(ConnectionError): + requests.get("http://example.com?test=1") + + with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: + rsps.add( + responses.GET, + "http://example.com", + body=b"test", + match=[matchers.query_string_matcher("test=1")], + ) + # An extra blank param must not be silently ignored. + with pytest.raises(ConnectionError): + requests.get("http://example.com?test=1&foo=") + + run() + assert_reset() + + def test_request_matches_post_params(): @responses.activate def run(deprecated):