Skip to content

Commit

Permalink
mbeliaev/query_string (#415)
Browse files Browse the repository at this point in the history
* added matcher for query string
* remove coverage for failed imports since they are not tested any way
keep imports, see #390 and #354
* fix test_response_with_instance
* added docs

Co-authored-by: Mark Story <mark@sentry.io>
  • Loading branch information
beliaev-maksim and markstory authored Oct 22, 2021
1 parent 149eedd commit 8231646
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 6 deletions.
2 changes: 2 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
* Removed the unused ``_is_redirect()`` function from responses internals.
* Added `responses.matchers.request_kwargs_matcher`. This matcher allows you
to match additional request arguments like `stream`.
* Added `responses.matchers.query_string_matcher`. This matcher allows you
to match request query string, similar to `responses.matchers.query_param_matcher`.
* Changed all matchers output message in case of mismatch. Now message is aligned
between Python2 and Python3 versions
* Added Python 3.10 support
Expand Down
21 changes: 21 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,27 @@ Note, you must set ``match_querystring=False``
assert resp.request.params == params
As alternative, you can use query string value in ``matchers.query_string_matcher``

.. code-block:: python
import requests
import responses
from responses import matchers
@responses.activate
def my_func():
responses.add(
responses.GET,
"https://httpbin.org/get",
match=[matchers.query_string_matcher("didi=pro&test=1")],
)
resp = requests.get("https://httpbin.org/get", params={"test": 1, "didi": "pro"})
my_func()
To validate request arguments use the ``matchers.request_kwargs_matcher`` function to match
against the request kwargs.
Note, only arguments provided to ``matchers.request_kwargs_matcher`` will be validated
Expand Down
12 changes: 6 additions & 6 deletions responses/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,16 @@

try:
from requests.packages.urllib3.response import HTTPResponse
except ImportError:
from urllib3.response import HTTPResponse
except ImportError: # pragma: no cover
from urllib3.response import HTTPResponse # pragma: no cover
try:
from requests.packages.urllib3.connection import HTTPHeaderDict
except ImportError:
from urllib3.response import HTTPHeaderDict
except ImportError: # pragma: no cover
from urllib3.response import HTTPHeaderDict # pragma: no cover
try:
from requests.packages.urllib3.util.url import parse_url
except ImportError:
from urllib3.util.url import parse_url
except ImportError: # pragma: no cover
from urllib3.util.url import parse_url # pragma: no cover

if six.PY2:
from urlparse import urlparse, parse_qsl, urlsplit, urlunsplit
Expand Down
34 changes: 34 additions & 0 deletions responses/matchers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
else:
from urllib.parse import parse_qsl

try:
from requests.packages.urllib3.util.url import parse_url
except ImportError: # pragma: no cover
from urllib3.util.url import parse_url # pragma: no cover

try:
from json.decoder import JSONDecodeError
except ImportError:
Expand Down Expand Up @@ -140,6 +145,35 @@ def match(request):
return match


def query_string_matcher(query):
"""
Matcher to match query string part of request
:param query: (str), same as constructed by request
:return: (func) matcher
"""

def match(request):
reason = ""
data = parse_url(request.url)
request_query = data.query

request_qsl = sorted(parse_qsl(request_query))
matcher_qsl = sorted(parse_qsl(query))

valid = not query if request_query is None else request_qsl == matcher_qsl

if not valid:
reason = "Query string doesn't match. {} doesn't match {}".format(
_create_key_val_str(dict(request_qsl)),
_create_key_val_str(dict(matcher_qsl)),
)

return valid, reason

return match


def request_kwargs_matcher(kwargs):
"""
Matcher to match keyword arguments provided to request
Expand Down
4 changes: 4 additions & 0 deletions responses/matchers.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ def query_param_matcher(
params: Optional[Dict[str, str]]
) -> Callable[..., Any]: ...

def query_string_matcher(
query: Optional[str]
) -> Callable[..., Any]: ...

def request_kwargs_matcher(
kwargs: Optional[Dict[str, Any]]
) -> Callable[..., Any]: ...
Expand Down
50 changes: 50 additions & 0 deletions responses/test_responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ def run():
assert len(responses.calls) == 2
assert responses.calls[1].request.url == "http://example.com/?foo=bar"

run()
assert_reset()


@pytest.mark.parametrize(
"original,replacement",
Expand Down Expand Up @@ -339,6 +342,27 @@ def run():
assert_reset()


def test_query_string_matcher():
@responses.activate
def run():
url = "http://example.com?test=1&foo=bar"
responses.add(
responses.GET,
url,
body=b"test",
match=[matchers.query_string_matcher("test=1&foo=bar")],
)
resp = requests.get("http://example.com?test=1&foo=bar")
assert_response(resp, "test")
resp = requests.get("http://example.com?foo=bar&test=1")
assert_response(resp, "test")
resp = requests.get("http://example.com/?foo=bar&test=1")
assert_response(resp, "test")

run()
assert_reset()


def test_match_empty_querystring():
@responses.activate
def run():
Expand Down Expand Up @@ -1786,6 +1810,32 @@ def run():
assert_reset()


def test_query_string_matcher_raises():
"""
Validate that Exception is raised if request does not match responses.matchers
validate matchers.query_string_matcher
:return: None
"""

def run():

with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:
rsps.add(
"GET",
"http://111.com",
match=[matchers.query_string_matcher("didi=pro")],
)

with pytest.raises(ConnectionError) as excinfo:
requests.get("http://111.com", params={"test": "1", "didi": "pro"})

msg = str(excinfo.value)
assert (
"Query string doesn't match. {didi: pro, test: 1} doesn't match {didi: pro}"
in msg
)


def test_request_matches_headers():
@responses.activate
def run():
Expand Down

0 comments on commit 8231646

Please sign in to comment.