Skip to content
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
24 changes: 20 additions & 4 deletions pytest_httpx/_request_matcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ def _url_match(
received: httpx.URL,
params: Optional[dict[str, Union[str | list[str]]]],
) -> bool:
# TODO Allow to provide a regex in URL and params as a dict
if isinstance(url_to_match, re.Pattern):
return url_to_match.match(str(received)) is not None

Expand Down Expand Up @@ -90,14 +89,23 @@ def __init__(
"If you want to match against the multipart representation, use match_files (and match_data). "
"Otherwise, use match_content."
)
if self.params and not self.url:
raise ValueError(
"URL must be provided when match_params is used."
)
if self.params and isinstance(self.url, re.Pattern):
raise ValueError(
"match_params cannot be used in addition to regex URL. Request this feature via https://github.com/Colin-b/pytest_httpx/issues/new?title=Regex%20URL%20should%20allow%20match_params&body=Hi,%20I%20need%20a%20regex%20to%20match%20the%20non%20query%20part%20of%20the%20URL%20only"
)
if self._is_matching_params_more_than_one_way():
raise ValueError(
"Provided URL must not contain any query parameter when match_params is used."
)
if self.data and not self.files:
raise ValueError(
"match_data is meant to be used for multipart matching (in conjunction with match_files)."
"Use match_content to match url encoded data."
)
# TODO Prevent match_params and params in URL
# TODO Prevent match_params with non str values / keys
# TODO Prevent match_params with list values of size < 2

def expect_body(self) -> bool:
matching_ways = [
Expand All @@ -115,6 +123,14 @@ def _is_matching_body_more_than_one_way(self) -> bool:
]
return sum(matching_ways) > 1

def _is_matching_params_more_than_one_way(self) -> bool:
url_has_params = bool(self.url.params) if (self.url and isinstance(self.url, httpx.URL)) else False
matching_ways = [
self.params is not None,
url_has_params,
]
return sum(matching_ways) > 1

def match(
self,
real_transport: Union[httpx.HTTPTransport, httpx.AsyncHTTPTransport],
Expand Down
149 changes: 149 additions & 0 deletions tests/test_httpx_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,155 @@ async def test_url_query_params_partial_matching(httpx_mock: HTTPXMock) -> None:
assert response.content == b""


@pytest.mark.asyncio
@pytest.mark.httpx_mock(assert_all_requests_were_expected=False)
async def test_url_query_params_with_single_value_list(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_response(
url="https://test_url",
match_params={"a": ["1"]},
is_optional=True,
)

async with httpx.AsyncClient() as client:
with pytest.raises(httpx.TimeoutException) as exception_info:
await client.post("https://test_url?a=1")
assert (
str(exception_info.value)
== """No response can be found for POST request on https://test_url?a=1 amongst:
- Match any request on https://test_url with {'a': ['1']} query parameters"""
)


@pytest.mark.asyncio
@pytest.mark.httpx_mock(assert_all_requests_were_expected=False)
async def test_url_query_params_with_non_str_value(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_response(
url="https://test_url",
match_params={"a": 1},
is_optional=True,
)

async with httpx.AsyncClient() as client:
with pytest.raises(httpx.TimeoutException) as exception_info:
await client.post("https://test_url?a=1")
assert (
str(exception_info.value)
== """No response can be found for POST request on https://test_url?a=1 amongst:
- Match any request on https://test_url with {'a': 1} query parameters"""
)


@pytest.mark.asyncio
@pytest.mark.httpx_mock(assert_all_requests_were_expected=False)
async def test_url_query_params_with_non_str_list_value(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_response(
url="https://test_url",
match_params={"a": [1, "2"]},
is_optional=True,
)

async with httpx.AsyncClient() as client:
with pytest.raises(httpx.TimeoutException) as exception_info:
await client.post("https://test_url?a=1&a=2")
assert (
str(exception_info.value)
== """No response can be found for POST request on https://test_url?a=1&a=2 amongst:
- Match any request on https://test_url with {'a': [1, '2']} query parameters"""
)


@pytest.mark.asyncio
@pytest.mark.httpx_mock(assert_all_requests_were_expected=False)
async def test_url_query_params_with_non_str_name(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_response(
url="https://test_url",
match_params={1: "1"},
is_optional=True,
)

async with httpx.AsyncClient() as client:
with pytest.raises(httpx.TimeoutException) as exception_info:
await client.post("https://test_url?1=1")
assert (
str(exception_info.value)
== """No response can be found for POST request on https://test_url?1=1 amongst:
- Match any request on https://test_url with {1: '1'} query parameters"""
)


@pytest.mark.asyncio
@pytest.mark.httpx_mock(assert_all_requests_were_expected=False)
async def test_url_query_params_not_matching(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_response(
url="https://test_url",
match_params={"a": "1"},
is_optional=True,
)

async with httpx.AsyncClient() as client:
with pytest.raises(httpx.TimeoutException) as exception_info:
await client.post("https://test_url?a=2")
assert (
str(exception_info.value)
== """No response can be found for POST request on https://test_url?a=2 amongst:
- Match any request on https://test_url with {'a': '1'} query parameters"""
)


@pytest.mark.asyncio
async def test_url_matching_with_more_than_one_value_on_same_param(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_response(url="https://test_url?a=1&a=3", is_optional=True)

async with httpx.AsyncClient() as client:
response = await client.get("https://test_url", params={"a": [1, 3]})
assert response.content == b""


@pytest.mark.asyncio
@pytest.mark.httpx_mock(assert_all_requests_were_expected=False)
async def test_url_not_matching_with_more_than_one_value_on_same_param_and_diff_value(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_response(url="https://test_url?a=2&a=3", is_optional=True)

async with httpx.AsyncClient() as client:
with pytest.raises(httpx.TimeoutException) as exception_info:
await client.get("https://test_url", params={"a": [1, 3]})
assert (
str(exception_info.value)
== """No response can be found for GET request on https://test_url?a=1&a=3 amongst:
- Match any request on https://test_url?a=2&a=3"""
)


@pytest.mark.asyncio
@pytest.mark.httpx_mock(assert_all_requests_were_expected=False)
async def test_url_not_matching_with_more_than_one_value_on_same_param_and_more_values(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_response(url="https://test_url?a=1&a=3", is_optional=True)

async with httpx.AsyncClient() as client:
with pytest.raises(httpx.TimeoutException) as exception_info:
await client.get("https://test_url", params={"a": [1, 3, 4]})
assert (
str(exception_info.value)
== """No response can be found for GET request on https://test_url?a=1&a=3&a=4 amongst:
- Match any request on https://test_url?a=1&a=3"""
)


@pytest.mark.asyncio
@pytest.mark.httpx_mock(assert_all_requests_were_expected=False)
async def test_url_not_matching_with_more_than_one_value_on_same_param_and_less_values(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_response(url="https://test_url?a=1&a=3&a=4", is_optional=True)

async with httpx.AsyncClient() as client:
with pytest.raises(httpx.TimeoutException) as exception_info:
await client.get("https://test_url", params={"a": [1, 3]})
assert (
str(exception_info.value)
== """No response can be found for GET request on https://test_url?a=1&a=3 amongst:
- Match any request on https://test_url?a=1&a=3&a=4"""
)


@pytest.mark.asyncio
@pytest.mark.httpx_mock(assert_all_requests_were_expected=False)
async def test_url_not_matching(httpx_mock: HTTPXMock) -> None:
Expand Down
160 changes: 159 additions & 1 deletion tests/test_httpx_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,165 @@ def test_url_query_params_partial_matching(httpx_mock: HTTPXMock) -> None:
assert response.content == b""


# TODO Test URL matching with more than one value on the same param (sucess and failure)
@pytest.mark.httpx_mock(assert_all_requests_were_expected=False)
def test_url_query_params_with_single_value_list(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_response(
url="https://test_url",
match_params={"a": ["1"]},
is_optional=True,
)

with httpx.Client() as client:
with pytest.raises(httpx.TimeoutException) as exception_info:
client.post("https://test_url?a=1")
assert (
str(exception_info.value)
== """No response can be found for POST request on https://test_url?a=1 amongst:
- Match any request on https://test_url with {'a': ['1']} query parameters"""
)


@pytest.mark.httpx_mock(assert_all_requests_were_expected=False)
def test_url_query_params_with_non_str_value(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_response(
url="https://test_url",
match_params={"a": 1},
is_optional=True,
)

with httpx.Client() as client:
with pytest.raises(httpx.TimeoutException) as exception_info:
client.post("https://test_url?a=1")
assert (
str(exception_info.value)
== """No response can be found for POST request on https://test_url?a=1 amongst:
- Match any request on https://test_url with {'a': 1} query parameters"""
)


@pytest.mark.httpx_mock(assert_all_requests_were_expected=False)
def test_url_query_params_with_non_str_list_value(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_response(
url="https://test_url",
match_params={"a": [1, "2"]},
is_optional=True,
)

with httpx.Client() as client:
with pytest.raises(httpx.TimeoutException) as exception_info:
client.post("https://test_url?a=1&a=2")
assert (
str(exception_info.value)
== """No response can be found for POST request on https://test_url?a=1&a=2 amongst:
- Match any request on https://test_url with {'a': [1, '2']} query parameters"""
)


@pytest.mark.httpx_mock(assert_all_requests_were_expected=False)
def test_url_query_params_with_non_str_name(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_response(
url="https://test_url",
match_params={1: "1"},
is_optional=True,
)

with httpx.Client() as client:
with pytest.raises(httpx.TimeoutException) as exception_info:
client.post("https://test_url?1=1")
assert (
str(exception_info.value)
== """No response can be found for POST request on https://test_url?1=1 amongst:
- Match any request on https://test_url with {1: '1'} query parameters"""
)


def test_match_params_without_url(httpx_mock: HTTPXMock) -> None:
with pytest.raises(ValueError) as exception_info:
httpx_mock.add_response(match_params={"a": "1"})

assert str(exception_info.value) == "URL must be provided when match_params is used."


def test_query_params_in_both_url_and_match_params(httpx_mock: HTTPXMock) -> None:
with pytest.raises(ValueError) as exception_info:
httpx_mock.add_response(url="https://test_url?a=1", match_params={"a": "1"})

assert str(exception_info.value) == "Provided URL must not contain any query parameter when match_params is used."


def test_regex_url_and_match_params(httpx_mock: HTTPXMock) -> None:
with pytest.raises(ValueError) as exception_info:
httpx_mock.add_response(url=re.compile(".*test.*"), match_params={"a": "1"})

assert str(exception_info.value) == "match_params cannot be used in addition to regex URL. Request this feature via https://github.com/Colin-b/pytest_httpx/issues/new?title=Regex%20URL%20should%20allow%20match_params&body=Hi,%20I%20need%20a%20regex%20to%20match%20the%20non%20query%20part%20of%20the%20URL%20only"


@pytest.mark.httpx_mock(assert_all_requests_were_expected=False)
def test_url_query_params_not_matching(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_response(
url="https://test_url",
match_params={"a": "1"},
is_optional=True,
)

with httpx.Client() as client:
with pytest.raises(httpx.TimeoutException) as exception_info:
client.post("https://test_url?a=2")
assert (
str(exception_info.value)
== """No response can be found for POST request on https://test_url?a=2 amongst:
- Match any request on https://test_url with {'a': '1'} query parameters"""
)


def test_url_matching_with_more_than_one_value_on_same_param(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_response(url="https://test_url?a=1&a=3", is_optional=True)

with httpx.Client() as client:
response = client.get("https://test_url", params={"a": [1, 3]})
assert response.content == b""


@pytest.mark.httpx_mock(assert_all_requests_were_expected=False)
def test_url_not_matching_with_more_than_one_value_on_same_param_and_diff_value(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_response(url="https://test_url?a=2&a=3", is_optional=True)

with httpx.Client() as client:
with pytest.raises(httpx.TimeoutException) as exception_info:
client.get("https://test_url", params={"a": [1, 3]})
assert (
str(exception_info.value)
== """No response can be found for GET request on https://test_url?a=1&a=3 amongst:
- Match any request on https://test_url?a=2&a=3"""
)


@pytest.mark.httpx_mock(assert_all_requests_were_expected=False)
def test_url_not_matching_with_more_than_one_value_on_same_param_and_more_values(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_response(url="https://test_url?a=1&a=3", is_optional=True)

with httpx.Client() as client:
with pytest.raises(httpx.TimeoutException) as exception_info:
client.get("https://test_url", params={"a": [1, 3, 4]})
assert (
str(exception_info.value)
== """No response can be found for GET request on https://test_url?a=1&a=3&a=4 amongst:
- Match any request on https://test_url?a=1&a=3"""
)


@pytest.mark.httpx_mock(assert_all_requests_were_expected=False)
def test_url_not_matching_with_more_than_one_value_on_same_param_and_less_values(httpx_mock: HTTPXMock) -> None:
httpx_mock.add_response(url="https://test_url?a=1&a=3&a=4", is_optional=True)

with httpx.Client() as client:
with pytest.raises(httpx.TimeoutException) as exception_info:
client.get("https://test_url", params={"a": [1, 3]})
assert (
str(exception_info.value)
== """No response can be found for GET request on https://test_url?a=1&a=3 amongst:
- Match any request on https://test_url?a=1&a=3&a=4"""
)


@pytest.mark.httpx_mock(assert_all_requests_were_expected=False)
Expand Down