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

Release 0.31.1 #153

Merged
merged 5 commits into from
Sep 22, 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
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.31.1] - 2024-09-22
### Fixed
- It is now possible to match on content provided as async iterable by the client.

## [0.31.0] - 2024-09-20
### Changed
- Tests will now fail at teardown by default if some requests were issued but were not matched.
Expand Down Expand Up @@ -337,7 +341,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- First release, should be considered as unstable for now as design might change.

[Unreleased]: https://github.com/Colin-b/pytest_httpx/compare/v0.31.0...HEAD
[Unreleased]: https://github.com/Colin-b/pytest_httpx/compare/v0.31.1...HEAD
[0.31.1]: https://github.com/Colin-b/pytest_httpx/compare/v0.31.0...v0.31.1
[0.31.0]: https://github.com/Colin-b/pytest_httpx/compare/v0.30.0...v0.31.0
[0.30.0]: https://github.com/Colin-b/pytest_httpx/compare/v0.29.0...v0.30.0
[0.29.0]: https://github.com/Colin-b/pytest_httpx/compare/v0.28.0...v0.29.0
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<a href="https://github.com/Colin-b/pytest_httpx/actions"><img alt="Build status" src="https://github.com/Colin-b/pytest_httpx/workflows/Release/badge.svg"></a>
<a href="https://github.com/Colin-b/pytest_httpx/actions"><img alt="Coverage" src="https://img.shields.io/badge/coverage-100%25-brightgreen"></a>
<a href="https://github.com/psf/black"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
<a href="https://github.com/Colin-b/pytest_httpx/actions"><img alt="Number of tests" src="https://img.shields.io/badge/tests-211 passed-blue"></a>
<a href="https://github.com/Colin-b/pytest_httpx/actions"><img alt="Number of tests" src="https://img.shields.io/badge/tests-215 passed-blue"></a>
<a href="https://pypi.org/project/pytest-httpx/"><img alt="Number of downloads" src="https://img.shields.io/pypi/dm/pytest_httpx"></a>
</p>

Expand Down
8 changes: 6 additions & 2 deletions pytest_httpx/_httpx_mock.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import copy
import inspect
from operator import methodcaller
from typing import Union, Optional, Callable, Any, NoReturn
from collections.abc import Awaitable
from typing import Union, Optional, Callable, Any, NoReturn, AsyncIterable
from collections.abc import Awaitable, Iterable

import httpx
from pytest import Mark
Expand Down Expand Up @@ -157,6 +157,8 @@ def _handle_request(
real_transport: httpx.HTTPTransport,
request: httpx.Request,
) -> httpx.Response:
# Store the content in request for future matching
request.read()
self._requests.append((real_transport, request))

callback = self._get_callback(real_transport, request)
Expand All @@ -173,6 +175,8 @@ async def _handle_async_request(
real_transport: httpx.AsyncHTTPTransport,
request: httpx.Request,
) -> httpx.Response:
# Store the content in request for future matching
await request.aread()
self._requests.append((real_transport, request))

callback = self._get_callback(real_transport, request)
Expand Down
5 changes: 3 additions & 2 deletions pytest_httpx/_request_matcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,12 @@ def _headers_match(self, request: httpx.Request) -> bool:
def _content_match(self, request: httpx.Request) -> bool:
if self.content is None and self.json is None:
return True

if self.content is not None:
return request.read() == self.content
return request.content == self.content
try:
# httpx._content.encode_json hard codes utf-8 encoding.
return json.loads(request.read().decode("utf-8")) == self.json
return json.loads(request.content.decode("utf-8")) == self.json
except json.decoder.JSONDecodeError:
return False

Expand Down
2 changes: 1 addition & 1 deletion pytest_httpx/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
# Major should be incremented in case there is a breaking change. (eg: 2.5.8 -> 3.0.0)
# Minor should be incremented in case there is an enhancement. (eg: 2.5.8 -> 2.6.0)
# Patch should be incremented in case there is a bug fix. (eg: 2.5.8 -> 2.5.9)
__version__ = "0.31.0"
__version__ = "0.31.1"
61 changes: 60 additions & 1 deletion tests/test_httpx_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import math
import re
import time
from collections.abc import AsyncIterable

import httpx
import pytest
Expand Down Expand Up @@ -2007,7 +2008,7 @@ async def test_streams_are_not_cascading_resulting_in_maximum_recursion(
) -> None:
httpx_mock.add_response(json={"abc": "def"})
async with httpx.AsyncClient() as client:
tasks = [client.get("https://example.com/") for _ in range(950)]
tasks = [client.get("https://test_url") for _ in range(950)]
await asyncio.gather(*tasks)
# No need to assert anything, this test case ensure that no error was raised by the gather

Expand All @@ -2033,3 +2034,61 @@ async def handle_async_request(
response = await client.post("https://test_url", content=b"This is the body")
assert response.read() == b""
assert response.headers["x-prefix"] == "test"


@pytest.mark.asyncio
async def test_response_selection_content_matching_with_async_iterable(
httpx_mock: HTTPXMock,
) -> None:
httpx_mock.add_response(match_content=b"full content 1", content=b"matched 1")
httpx_mock.add_response(match_content=b"full content 2", content=b"matched 2")

async def stream_content_1() -> AsyncIterable[bytes]:
yield b"full"
yield b" "
yield b"content"
yield b" 1"

async def stream_content_2() -> AsyncIterable[bytes]:
yield b"full"
yield b" "
yield b"content"
yield b" 2"

async with httpx.AsyncClient() as client:
response_2 = await client.put("https://test_url", content=stream_content_2())
response_1 = await client.put("https://test_url", content=stream_content_1())
assert response_1.content == b"matched 1"
assert response_2.content == b"matched 2"


@pytest.mark.asyncio
async def test_request_selection_content_matching_with_async_iterable(
httpx_mock: HTTPXMock,
) -> None:
httpx_mock.add_response(match_content=b"full content 1")
httpx_mock.add_response(match_content=b"full content 2")

async def stream_content_1() -> AsyncIterable[bytes]:
yield b"full"
yield b" "
yield b"content"
yield b" 1"

async def stream_content_2() -> AsyncIterable[bytes]:
yield b"full"
yield b" "
yield b"content"
yield b" 2"

async with httpx.AsyncClient() as client:
await client.put("https://test_url_2", content=stream_content_2())
await client.put("https://test_url_1", content=stream_content_1())
assert (
httpx_mock.get_request(match_content=b"full content 1").url
== "https://test_url_1"
)
assert (
httpx_mock.get_request(match_content=b"full content 2").url
== "https://test_url_2"
)
57 changes: 57 additions & 0 deletions tests/test_httpx_sync.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import re
from collections.abc import Iterable
from unittest.mock import ANY

import httpx
Expand Down Expand Up @@ -1718,3 +1719,59 @@ def handle_request(
response = client.post("https://test_url", content=b"This is the body")
assert response.read() == b""
assert response.headers["x-prefix"] == "test"


def test_response_selection_content_matching_with_iterable(
httpx_mock: HTTPXMock,
) -> None:
httpx_mock.add_response(match_content=b"full content 1", content=b"matched 1")
httpx_mock.add_response(match_content=b"full content 2", content=b"matched 2")

def stream_content_1() -> Iterable[bytes]:
yield b"full"
yield b" "
yield b"content"
yield b" 1"

def stream_content_2() -> Iterable[bytes]:
yield b"full"
yield b" "
yield b"content"
yield b" 2"

with httpx.Client() as client:
response_2 = client.put("https://test_url", content=stream_content_2())
response_1 = client.put("https://test_url", content=stream_content_1())
assert response_1.content == b"matched 1"
assert response_2.content == b"matched 2"


def test_request_selection_content_matching_with_iterable(
httpx_mock: HTTPXMock,
) -> None:
httpx_mock.add_response(match_content=b"full content 1")
httpx_mock.add_response(match_content=b"full content 2")

def stream_content_1() -> Iterable[bytes]:
yield b"full"
yield b" "
yield b"content"
yield b" 1"

def stream_content_2() -> Iterable[bytes]:
yield b"full"
yield b" "
yield b"content"
yield b" 2"

with httpx.Client() as client:
client.put("https://test_url_2", content=stream_content_2())
client.put("https://test_url_1", content=stream_content_1())
assert (
httpx_mock.get_request(match_content=b"full content 1").url
== "https://test_url_1"
)
assert (
httpx_mock.get_request(match_content=b"full content 2").url
== "https://test_url_2"
)