diff --git a/CHANGELOG.md b/CHANGELOG.md index dfe5816..80ae883 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,8 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Fixed -- Fix regression causing `params` pattern to stop working under some conditions, - by doing a strict detection of `ANY` in multi items patterns (#313) +- Fix regression causing `params` pattern to stop working under some conditions, by + doing a strict detection of `ANY` in multi items patterns (#313) ### CI diff --git a/respx/patterns.py b/respx/patterns.py index 0bd6199..0854302 100644 --- a/respx/patterns.py +++ b/respx/patterns.py @@ -535,10 +535,7 @@ def _contains(self, value: Union[bytes, str]) -> Match: class JSON(ContentMixin, PathPattern): lookups = (Lookup.EQUAL,) key = "json" - value: str - - def clean(self, value: Union[str, List, Dict]) -> str: - return self.hash(value) + value: Union[str, List, Dict, Any] def parse(self, request: httpx.Request) -> str: content = super().parse(request) @@ -557,10 +554,23 @@ def parse(self, request: httpx.Request) -> str: else: value = json - return self.hash(value) + return value - def hash(self, value: Union[str, List, Dict]) -> str: - return jsonlib.dumps(value, sort_keys=True) + def __hash__(self): + return hash( + ( + self.__class__, + self.lookup, + jsonlib.dumps(self.value, sort_keys=True, default=self._encode_any), + ) + ) + + def _encode_any(self, o: Any) -> Any: + if o is ANY: + return str(ANY) + raise TypeError( # pragma: no cover + f"Object of type {type(o)} is not JSON serializable" + ) class Data(MultiItemsMixin, Pattern): diff --git a/tests/test_patterns.py b/tests/test_patterns.py index 178a0e5..b64ea14 100644 --- a/tests/test_patterns.py +++ b/tests/test_patterns.py @@ -567,6 +567,9 @@ def test_files_pattern(lookup, files, request_files, expected): False, ), (Lookup.EQUAL, "json-string", "json-string", True), + (Lookup.EQUAL, {"foo": ANY}, {"foo": "bar"}, True), + (Lookup.EQUAL, {"foo": ANY}, {"ham": "spam"}, False), + (Lookup.EQUAL, ANY, "any-value", True), ], ) def test_json_pattern(lookup, value, json, expected): @@ -585,6 +588,11 @@ def test_json_pattern(lookup, value, json, expected): ({"pk": 123}, "pk", 123, True), ({"foo": {"bar": "baz"}}, "foo__ham", "spam", False), ([{"name": "lundberg"}], "1__name", "lundberg", False), + ([{"name": "lundberg"}], "0__name", ANY, True), + ([{"name": "lundberg"}], "1__name", ANY, False), + ({"ham": [{"spam": "spam"}, {"egg": "yolk"}]}, "ham__1", ANY, True), + ({"ham": [{"spam": "spam"}, {"egg": "yolk"}]}, "ham__1__egg", ANY, True), + ({"ham": [{"spam": "spam"}, {"egg": "yolk"}]}, "ham__1__foo", ANY, False), ], ) def test_json_pattern_path(json, path, value, expected): @@ -594,6 +602,21 @@ def test_json_pattern_path(json, path, value, expected): assert bool(match) is expected +@pytest.mark.parametrize( + ("value", "other", "expected"), + [ + ({"foo": "bar"}, {"foo": "bar"}, True), + ({"foo": "bar", "ham": "spam"}, {"ham": "spam", "foo": "bar"}, True), + (["foobar", ANY], ["foobar", ANY], True), + (["foobar", "hamspam"], ["foobar", ANY], False), + (ANY, ANY, True), + ("foobar", ANY, False), + ], +) +def test_json_pattern_hash(value, other, expected): + assert (JSON(value) == JSON(other)) is expected + + def test_invalid_pattern(): with pytest.raises(KeyError, match="is not a valid Pattern"): M(foo="baz")