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

fix(matchers): Don't sort failed matches when printing error message #711

Merged
merged 1 commit into from
May 8, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
0.25.2
------

* Fixed error messages when matches fail: inputs are not sorted or reformatted. See #704

0.25.1
------

Expand Down
71 changes: 12 additions & 59 deletions responses/matchers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,45 +18,6 @@
from urllib3.util.url import parse_url


def _create_key_val_str(input_dict: Union[Mapping[Any, Any], Any]) -> str:
"""
Returns string of format {'key': val, 'key2': val2}
Function is called recursively for nested dictionaries

:param input_dict: dictionary to transform
:return: (str) reformatted string
"""

def list_to_str(input_list: List[str]) -> str:
"""
Convert all list items to string.
Function is called recursively for nested lists
"""
converted_list = []
for item in sorted(input_list, key=lambda x: str(x)):
if isinstance(item, dict):
item = _create_key_val_str(item)
elif isinstance(item, list):
item = list_to_str(item)

converted_list.append(str(item))
list_str = ", ".join(converted_list)
return "[" + list_str + "]"

items_list = []
for key in sorted(input_dict.keys(), key=lambda x: str(x)):
val = input_dict[key]
if isinstance(val, dict):
val = _create_key_val_str(val)
elif isinstance(val, list):
val = list_to_str(input_list=val)

items_list.append(f"{key}: {val}")

key_val_str = "{{{}}}".format(", ".join(items_list))
return key_val_str


def _filter_dict_recursively(
dict1: Mapping[Any, Any], dict2: Mapping[Any, Any]
) -> Mapping[Any, Any]:
Expand Down Expand Up @@ -91,8 +52,8 @@ def match(request: PreparedRequest) -> Tuple[bool, str]:
params_dict = params or {}
valid = params is None if request_body is None else params_dict == qsl_body
if not valid:
reason = "request.body doesn't match: {} doesn't match {}".format(
_create_key_val_str(qsl_body), _create_key_val_str(params_dict)
reason = (
f"request.body doesn't match: {qsl_body} doesn't match {params_dict}"
)

return valid, reason
Expand Down Expand Up @@ -146,13 +107,7 @@ def match(request: PreparedRequest) -> Tuple[bool, str]:
valid = params is None if request_body is None else json_params == json_body

if not valid:
if isinstance(json_body, dict) and isinstance(json_params, dict):
reason = "request.body doesn't match: {} doesn't match {}".format(
_create_key_val_str(json_body), _create_key_val_str(json_params)
)
else:
reason = f"request.body doesn't match: {json_body} doesn't match {json_params}"

reason = f"request.body doesn't match: {json_body} doesn't match {json_params}"
if not strict_match:
reason += (
"\nNote: You use non-strict parameters check, "
Expand Down Expand Up @@ -234,10 +189,7 @@ def match(request: PreparedRequest) -> Tuple[bool, str]:
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),
)
reason = f"Parameters do not match. {request_params_dict} doesn't match {params_dict}"
if not strict_match:
reason += (
"\nYou can use `strict_match=True` to do a strict parameters check."
Expand Down Expand Up @@ -267,9 +219,9 @@ def match(request: PreparedRequest) -> Tuple[bool, str]:
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)),
reason = (
"Query string doesn't match. "
f"{dict(request_qsl)} doesn't match {dict(matcher_qsl)}"
)

return valid, reason
Expand Down Expand Up @@ -299,8 +251,8 @@ def match(request: PreparedRequest) -> Tuple[bool, str]:
)

if not valid:
reason = "Arguments don't match: {} doesn't match {}".format(
_create_key_val_str(request_kwargs), _create_key_val_str(kwargs_dict)
reason = (
f"Arguments don't match: {request_kwargs} doesn't match {kwargs_dict}"
)

return valid, reason
Expand Down Expand Up @@ -436,8 +388,9 @@ def match(request: PreparedRequest) -> Tuple[bool, str]:
valid = _compare_with_regex(request_headers)

if not valid:
return False, "Headers do not match: {} doesn't match {}".format(
_create_key_val_str(request_headers), _create_key_val_str(headers)
return (
False,
f"Headers do not match: {request_headers} doesn't match {headers}",
)

return valid, ""
Expand Down
96 changes: 44 additions & 52 deletions responses/tests/test_matchers.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,27 @@ def run():
)
assert (
"- POST http://example.com/ request.body doesn't match: "
"{page: {type: json}} doesn't match {page: {diff: value, type: json}}"
"{'page': {'type': 'json'}} doesn't match {'page': {'type': 'json', 'diff': 'value'}}"
) in str(exc.value)

run()
assert_reset()


def test_failed_matchers_dont_modify_inputs_order_in_error_message():
json_a = {"array": ["C", "B", "A"]}
json_b = '{"array" : ["B", "A", "C"]}'
mock_request = Mock(body=json_b)
result = matchers.json_params_matcher(json_a)(mock_request)
assert result == (
False,
(
"request.body doesn't match: {'array': ['B', 'A', 'C']} "
"doesn't match {'array': ['C', 'B', 'A']}"
),
)


def test_json_params_matcher_json_list():
json_a = [{"a": "b"}]
json_b = '[{"a": "b", "c": "d"}]'
Expand Down Expand Up @@ -250,7 +264,7 @@ def run():

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

Expand Down Expand Up @@ -304,7 +318,7 @@ def run():
)

msg = str(excinfo.value)
assert "request.body doesn't match: {my: data} doesn't match {}" in msg
assert "request.body doesn't match: {'my': 'data'} doesn't match {}" in msg

with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:
rsps.add(
Expand All @@ -322,9 +336,9 @@ def run():
)
msg = str(excinfo.value)
assert (
"request.body doesn't match: {page: second, type: urlencoded} doesn't match {}"
in msg
)
"request.body doesn't match: {'page': 'second', "
"'type': 'urlencoded'} doesn't match {}"
) in msg

run()
assert_reset()
Expand Down Expand Up @@ -389,7 +403,8 @@ def run():

msg = str(excinfo.value)
assert (
"request.body doesn't match: {id: bad} doesn't match {foo: bar}" in msg
"request.body doesn't match: {'id': 'bad'} doesn't match {'foo': 'bar'}"
in msg
)

assert (
Expand Down Expand Up @@ -418,10 +433,11 @@ def run():

msg = str(excinfo.value)
assert (
"Parameters do not match. {id: bad} doesn't match {my: params}" in msg
"Parameters do not match. {'id': 'bad'} doesn't match {'my': 'params'}"
in msg
)
assert (
"request.body doesn't match: {page: two} doesn't match {page: one}"
"request.body doesn't match: {'page': 'two'} doesn't match {'page': 'one'}"
in msg
)

Expand All @@ -442,7 +458,7 @@ def run():
msg = str(excinfo.value)
assert (
"Arguments don't match: "
"{stream: True, verify: True} doesn't match {stream: True, verify: False}"
"{'stream': True, 'verify': True} doesn't match {'stream': True, 'verify': False}"
) in msg

run()
Expand Down Expand Up @@ -587,9 +603,9 @@ def run():

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

run()
assert_reset()
Expand Down Expand Up @@ -642,8 +658,8 @@ def run():

msg = str(excinfo.value)
assert (
"Headers do not match: {Accept: application/xml} doesn't match "
"{Accept: application/json}"
"Headers do not match: {'Accept': 'application/xml'} doesn't match "
"{'Accept': 'application/json'}"
) in msg

run()
Expand All @@ -665,7 +681,9 @@ def run():
requests.get(url, headers={})

msg = str(excinfo.value)
assert ("Headers do not match: {} doesn't match {x-custom-header: foo}") in msg
assert (
"Headers do not match: {} doesn't match {'x-custom-header': 'foo'}"
) in msg

run()
assert_reset()
Expand Down Expand Up @@ -716,8 +734,8 @@ def run():

msg = str(excinfo.value)
assert (
"Headers do not match: {Accept: text/plain, Accept-Charset: utf-8} "
"doesn't match {Accept: text/plain}"
"Headers do not match: {'Accept': 'text/plain', 'Accept-Charset': 'utf-8'} "
"doesn't match {'Accept': 'text/plain'}"
) in msg

run()
Expand Down Expand Up @@ -792,31 +810,6 @@ def run():
assert_reset()


def test_matchers_create_key_val_str():
"""
Test that matchers._create_key_val_str does recursive conversion
"""
data = {
"my_list": [
1,
2,
"a",
{"key1": "val1", "key2": 2, 3: "test"},
"!",
[["list", "nested"], {"nested": "dict"}],
],
1: 4,
"test": "val",
"high": {"nested": "nested_dict"},
}
conv_str = matchers._create_key_val_str(data)
reference = (
"{1: 4, high: {nested: nested_dict}, my_list: [!, 1, 2, [[list, nested], {nested: dict}], "
"a, {3: test, key1: val1, key2: 2}], test: val}"
)
assert conv_str == reference


class TestHeaderWithRegex:
@property
def url(self): # type: ignore[misc]
Expand Down Expand Up @@ -892,9 +885,9 @@ def run():
session.send(prepped)
msg = str(excinfo.value)
assert (
"Headers do not match: {Accept: text/plain, Message-Signature: "
'signature="123",created=abc} '
"doesn't match {Accept: text/plain, Message-Signature: "
"Headers do not match: {'Accept': 'text/plain', 'Message-Signature': "
"""'signature="123",created=abc'} """
"doesn't match {'Accept': 'text/plain', 'Message-Signature': "
"re.compile('signature=\"\\\\S+\",created=\\\\d+')}"
) in msg

Expand All @@ -921,8 +914,8 @@ def run():
session.send(prepped)
msg = str(excinfo.value)
assert (
"Headers do not match: {Accept: text/plain, Accept-Charset: utf-8} "
"doesn't match {Accept: text/plain, Message-Signature: "
"Headers do not match: {'Accept': 'text/plain', 'Accept-Charset': 'utf-8'} "
"doesn't match {'Accept': 'text/plain', 'Message-Signature': "
"re.compile('signature=\"\\\\S+\",created=\\\\d+')}"
) in msg

Expand Down Expand Up @@ -950,10 +943,9 @@ def run():
session.send(prepped)
msg = str(excinfo.value)
assert (
"Headers do not match: {Accept: text/plain, Accept-Charset: utf-8, "
'Message-Signature: signature="abc",'
"created=1243} "
"doesn't match {Accept: text/plain, Message-Signature: "
"Headers do not match: {'Accept': 'text/plain', 'Accept-Charset': 'utf-8', "
"""'Message-Signature': 'signature="abc",created=1243'} """
"doesn't match {'Accept': 'text/plain', 'Message-Signature': "
"re.compile('signature=\"\\\\S+\",created=\\\\d+')}"
) in msg

Expand Down
Loading