diff --git a/CHANGES b/CHANGES index dcdce261..1ec7c55a 100644 --- a/CHANGES +++ b/CHANGES @@ -5,6 +5,10 @@ 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 +* Fixed the default `header_matcher` (``strict_match=False``) matching header + field names case-sensitively, so a request whose header casing differed from + the matcher spec (for example lowercase names over HTTP/2) failed to match. + See #805 0.26.2 ------ diff --git a/responses/matchers.py b/responses/matchers.py index d61decc1..5c2e0fb3 100644 --- a/responses/matchers.py +++ b/responses/matchers.py @@ -432,8 +432,12 @@ def match(request: PreparedRequest) -> Tuple[bool, str]: request_headers: Union[Mapping[Any, Any], Any] = request.headers or {} if not strict_match: - # filter down to just the headers specified in the matcher - request_headers = {k: v for k, v in request_headers.items() if k in headers} + # Filter to the matcher's headers, keyed by the matcher's names, so + # the case-insensitive lookup on request.headers (a + # CaseInsensitiveDict) is not lost in the plain-dict rebuild. + request_headers = { + k: request_headers[k] for k in headers if k in request_headers + } valid = _compare_with_regex(request_headers) diff --git a/responses/tests/test_matchers.py b/responses/tests/test_matchers.py index fc4fbc6f..5b56838b 100644 --- a/responses/tests/test_matchers.py +++ b/responses/tests/test_matchers.py @@ -857,6 +857,26 @@ def run(): assert_reset() +def test_request_matches_headers_case_insensitive_field_names(): + # HTTP header names are case-insensitive (and HTTP/2 lower-cases them), so + # the default matcher must match regardless of the field-name casing. + @responses.activate + def run(): + url = "http://example.com/" + responses.add( + method=responses.GET, + url=url, + json={"success": True}, + match=[matchers.header_matcher({"X-Custom": "token"})], + ) + + resp = requests.get(url, headers={"x-custom": "token"}) + assert_response(resp, body='{"success": true}', content_type="application/json") + + run() + assert_reset() + + def test_request_header_value_mismatch_raises(): @responses.activate def run():