Repository: getsentry/responses
Pull request #390 (2021-07-24 16:04:46)

rfc compliant url logic
This addresses the fact that currently URLs are not matched using [RFCC-3986](https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2) compliant host-names. When adding a mock URL such as `http://some-URL-HERE`, then we should expect that both `http://some-URL-HERE` and `http://some-url-here` should pass when submitting requests. Per the RFC:
> The host sub-component is case-insensitive.


Patch:
diff --git a/responses/__init__.py b/responses/__init__.py
index 3eb9399..29cf442 100644
--- a/responses/__init__.py
+++ b/responses/__init__.py
@@ -29,6 +29,10 @@ try:
     from requests.packages.urllib3.connection import HTTPHeaderDict
 except ImportError:
     from urllib3.response import HTTPHeaderDict
+try:
+    from requests.packages.urllib3.util.url import parse_url
+except ImportError:
+    from urllib3.util.url import parse_url
 
 if six.PY2:
     from urlparse import urlparse, parse_qsl, urlsplit, urlunsplit
@@ -323,13 +327,17 @@ class BaseResponse(object):
                 url = _clean_unicode(url)
                 if not isinstance(other, six.text_type):
                     other = other.encode("ascii").decode("utf8")
+
             if match_querystring:
-                return self._url_matches_strict(url, other)
+                rfc_compliant_url = parse_url(url).url
+                return self._url_matches_strict(rfc_compliant_url, other)
 
             else:
                 url_without_qs = url.split("?", 1)[0]
                 other_without_qs = other.split("?", 1)[0]
-                return url_without_qs == other_without_qs
+                rfc_compliant_url_without_qs = parse_url(url_without_qs).url
+
+                return rfc_compliant_url_without_qs == other_without_qs
 
         elif isinstance(url, Pattern) and url.match(other):
             return True
diff --git a/responses/__init__.pyi b/responses/__init__.pyi
index d55537f..14e101c 100644
--- a/responses/__init__.pyi
+++ b/responses/__init__.pyi
@@ -37,7 +37,7 @@ def get_wrapped(
     func: Callable[..., Any], responses: RequestsMock
 ) -> Callable[..., Any]: ...
 def json_params_matcher(
-    params: Optional[Dict[str, Dict[str, str]]]
+    params: Optional[Dict]
 ) -> Callable[..., Any]: ...
 def urlencoded_params_matcher(
     params: Optional[Dict[str, str]]


Test patch:
diff --git a/responses/test_responses.py b/responses/test_responses.py
index 6ab8606..4821f56 100644
--- a/responses/test_responses.py
+++ b/responses/test_responses.py
@@ -1472,3 +1472,23 @@ def test_mocked_responses_list_registered():
 
     run()
     assert_reset()
+
+
+@pytest.mark.parametrize(
+    "url,other_url",
+    [
+        ("http://service-A/foo?q=fizz", "http://service-a/foo?q=fizz"),
+        ("http://service-a/foo", "http://service-A/foo"),
+        ("http://someHost-AwAy/", "http://somehost-away/"),
+        ("http://fizzbuzz/foo", "http://fizzbuzz/foo"),
+    ],
+)
+def test_rfc_compliance(url, other_url):
+    @responses.activate
+    def run():
+        responses.add(method=responses.GET, url=url)
+        resp = requests.request("GET", other_url)
+        assert_response(resp, "")
+
+    run()
+    assert_reset()