Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mbeliaev/loose matcher #498

Merged
merged 4 commits into from
Feb 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
See `responses.registries.OrderedRegistry`.
* Expose `get_registry()` method of `RequestsMock` object. Replaces internal `_get_registry()`.
* `query_param_matcher` can now accept dictionaries with `int` and `float` values.
* Add support for the `loose` version of `query_param_matcher` via named argument `strict_match`.
* Added support for `async/await` functions.
* `response_callback` is no longer executed on exceptions raised by failed `Response`s
* An error is now raised when both `content_type` and `headers[content-type]` are provided as parameters.
Expand Down
3 changes: 3 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,9 @@ deprecated argument.
assert resp.request.url == constructed_url
assert resp.request.params == params

By default, matcher will validate that all parameters match strictly.
To validate that only parameters specified in the matcher are present in original request
use ``strict_match=False``.

Query Parameters as a String
""""""""""""""""""""""""""""
Expand Down
41 changes: 31 additions & 10 deletions responses/matchers.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,26 @@ def match(request: PreparedRequest) -> Tuple[bool, str]:
return match


def query_param_matcher(params: Optional[Dict[str, Any]]) -> Callable[..., Any]:
"""
Matcher to match 'params' argument in request
def query_param_matcher(
params: Optional[Dict[str, Any]], *, strict_match: bool = True
) -> Callable[..., Any]:
"""Matcher to match 'params' argument in request.

Parameters
----------
params : dict
The same as provided to request or a part of it if used in
conjunction with ``strict_match=False``.
strict_match : bool, default=True
If set to ``True``, validates that all parameters match.
If set to ``False``, original request may contain additional parameters.


Returns
-------
Callable
Matcher function.

:param params: (dict), same as provided to request
:return: (func) matcher
"""

params_dict = params or {}
Expand All @@ -152,17 +166,24 @@ def match(request: PreparedRequest) -> Tuple[bool, str]:
reason = ""
request_params = request.params # type: ignore[attr-defined]
request_params_dict = request_params or {}
valid = (
params is None
if request_params is None
else params_dict == request_params_dict
)

if not strict_match:
# filter down to just the params specified in the matcher
request_params_dict = {
k: v for k, v in request_params_dict.items() if k in params_dict
}

valid = sorted(params_dict.items()) == sorted(request_params_dict.items())

if not valid:
reason = "Parameters do not match. {} doesn't match {}".format(
_create_key_val_str(request_params_dict),
_create_key_val_str(params_dict),
)
if not strict_match:
reason += (
"\nYou can use `strict_match=True` to do a strict parameters check."
)

return valid, reason

Expand Down
46 changes: 46 additions & 0 deletions responses/test_matchers.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,52 @@ def run():
assert_reset()


def test_query_param_matcher_loose():
@responses.activate
def run():
expected_query_params = {"only_one_param": "test"}
responses.add(
responses.GET,
"https://example.com/",
match=[
matchers.query_param_matcher(expected_query_params, strict_match=False),
],
)
requests.get(
"https://example.com", params={"only_one_param": "test", "second": "param"}
)

run()
assert_reset()


def test_query_param_matcher_loose_fail():
@responses.activate
def run():
expected_query_params = {"does_not_exist": "test"}
responses.add(
responses.GET,
"https://example.com/",
match=[
matchers.query_param_matcher(expected_query_params, strict_match=False),
],
)
with pytest.raises(ConnectionError) as exc:
requests.get(
"https://example.com",
params={"only_one_param": "test", "second": "param"},
)

assert (
"- GET https://example.com/ Parameters do not match. {} doesn't"
" match {does_not_exist: test}\n"
"You can use `strict_match=True` to do a strict parameters check."
) in str(exc.value)

run()
assert_reset()


def test_request_matches_empty_body():
def run():
with responses.RequestsMock(assert_all_requests_are_fired=True) as rsps:
Expand Down