From 30d8bd96efcbcac2591f83a72282e842cd4359e4 Mon Sep 17 00:00:00 2001 From: karosis88 Date: Fri, 28 Jul 2023 14:13:04 +0300 Subject: [PATCH] Improve transports and connection pools tests, make coverage 100% --- hishel/_async/_pool.py | 11 +++++----- hishel/_async/_transports.py | 20 ++++++++--------- hishel/_controller.py | 4 ++-- hishel/_sync/_pool.py | 11 +++++----- hishel/_sync/_transports.py | 20 ++++++++--------- hishel/_utils.py | 1 - tests/_async/test_client.py | 25 +++++++++++++--------- tests/_async/test_pool.py | 39 ++++++++++++++++++++++++++++++++++ tests/_async/test_transport.py | 37 +++++++++++++++++++++++++++++++- tests/_sync/test_client.py | 25 +++++++++++++--------- tests/_sync/test_pool.py | 39 ++++++++++++++++++++++++++++++++++ tests/_sync/test_transport.py | 37 +++++++++++++++++++++++++++++++- 12 files changed, 214 insertions(+), 55 deletions(-) diff --git a/hishel/_async/_pool.py b/hishel/_async/_pool.py index 16ff73a..ba2cd88 100644 --- a/hishel/_async/_pool.py +++ b/hishel/_async/_pool.py @@ -49,15 +49,16 @@ async def handle_async_request(self, request: Request) -> Response: # Re-validating the response. response = await self._pool.handle_async_request(res) - await response.aread() # Merge headers with the stale response. - self._controller.handle_validation_response( + full_response = self._controller.handle_validation_response( old_response=stored_resposne, new_response=response ) - await self._storage.store(key, response) - response.extensions["from_cache"] = response.status == 304 # type: ignore[index] - return response + + await full_response.aread() + await self._storage.store(key, full_response) + full_response.extensions["from_cache"] = response.status == 304 # type: ignore[index] + return full_response response = await self._pool.handle_async_request(request) diff --git a/hishel/_async/_transports.py b/hishel/_async/_transports.py index c6813a6..db03a5d 100644 --- a/hishel/_async/_transports.py +++ b/hishel/_async/_transports.py @@ -12,7 +12,7 @@ from .._serializers import JSONSerializer from ._storages import AsyncBaseStorage, AsyncFileStorage -if tp.TYPE_CHECKING: +if tp.TYPE_CHECKING: # pragma: no cover from typing_extensions import Self __all__ = ("AsyncCacheTransport",) @@ -89,22 +89,22 @@ async def handle_async_request(self, request: Request) -> Response: ) # Merge headers with the stale response. - self._controller.handle_validation_response( + full_response = self._controller.handle_validation_response( old_response=stored_resposne, new_response=httpcore_response ) - await httpcore_response.aread() - await self._storage.store(key, httpcore_response) + await full_response.aread() + await self._storage.store(key, full_response) - assert isinstance(httpcore_response.stream, tp.AsyncIterable) - httpcore_response.extensions["from_cache"] = ( # type: ignore[index] + assert isinstance(full_response.stream, tp.AsyncIterable) + full_response.extensions["from_cache"] = ( # type: ignore[index] httpcore_response.status == 304 ) return Response( - status_code=httpcore_response.status, - headers=httpcore_response.headers, - stream=AsyncResponseStream(httpcore_response.stream), - extensions=httpcore_response.extensions, + status_code=full_response.status, + headers=full_response.headers, + stream=AsyncResponseStream(full_response.stream), + extensions=full_response.extensions, ) response = await self._transport.handle_async_request(request) diff --git a/hishel/_controller.py b/hishel/_controller.py index 71b228a..186ab8b 100644 --- a/hishel/_controller.py +++ b/hishel/_controller.py @@ -207,7 +207,7 @@ def _validate_vary(self, request: Request, response: Response) -> bool: vary = Vary.from_value(vary_values=vary_headers) for vary_header in vary._values: if vary_header == "*": - return False + return False # pragma: no cover if extract_header_values( request.headers, vary_header @@ -247,7 +247,7 @@ def construct_response_from_cache( # response (if any) match those presented (see Section 4.1) if not self._validate_vary(request=request, response=response): # If the vary headers does not match, then do not use the response - return None + return None # pragma: no cover # the stored response does not contain the # no-cache directive (Section 5.2.2.4), unless diff --git a/hishel/_sync/_pool.py b/hishel/_sync/_pool.py index 269215f..85921cd 100644 --- a/hishel/_sync/_pool.py +++ b/hishel/_sync/_pool.py @@ -49,15 +49,16 @@ def handle_request(self, request: Request) -> Response: # Re-validating the response. response = self._pool.handle_request(res) - response.read() # Merge headers with the stale response. - self._controller.handle_validation_response( + full_response = self._controller.handle_validation_response( old_response=stored_resposne, new_response=response ) - self._storage.store(key, response) - response.extensions["from_cache"] = response.status == 304 # type: ignore[index] - return response + + full_response.read() + self._storage.store(key, full_response) + full_response.extensions["from_cache"] = response.status == 304 # type: ignore[index] + return full_response response = self._pool.handle_request(request) diff --git a/hishel/_sync/_transports.py b/hishel/_sync/_transports.py index ae5995f..17b1dc6 100644 --- a/hishel/_sync/_transports.py +++ b/hishel/_sync/_transports.py @@ -12,7 +12,7 @@ from .._serializers import JSONSerializer from ._storages import BaseStorage, FileStorage -if tp.TYPE_CHECKING: +if tp.TYPE_CHECKING: # pragma: no cover from typing_extensions import Self __all__ = ("CacheTransport",) @@ -89,22 +89,22 @@ def handle_request(self, request: Request) -> Response: ) # Merge headers with the stale response. - self._controller.handle_validation_response( + full_response = self._controller.handle_validation_response( old_response=stored_resposne, new_response=httpcore_response ) - httpcore_response.read() - self._storage.store(key, httpcore_response) + full_response.read() + self._storage.store(key, full_response) - assert isinstance(httpcore_response.stream, tp.Iterable) - httpcore_response.extensions["from_cache"] = ( # type: ignore[index] + assert isinstance(full_response.stream, tp.Iterable) + full_response.extensions["from_cache"] = ( # type: ignore[index] httpcore_response.status == 304 ) return Response( - status_code=httpcore_response.status, - headers=httpcore_response.headers, - stream=ResponseStream(httpcore_response.stream), - extensions=httpcore_response.extensions, + status_code=full_response.status, + headers=full_response.headers, + stream=ResponseStream(full_response.stream), + extensions=full_response.extensions, ) response = self._transport.handle_request(request) diff --git a/hishel/_utils.py b/hishel/_utils.py index 6d4d842..c3966ac 100644 --- a/hishel/_utils.py +++ b/hishel/_utils.py @@ -55,7 +55,6 @@ def extract_header_values( if isinstance(header_key, str): header_key = header_key.encode(HEADERS_ENCODING) extracted_headers = [] - for key, value in headers: if key.lower() == header_key.lower(): extracted_headers.append(value) diff --git a/tests/_async/test_client.py b/tests/_async/test_client.py index 2147e69..209ccdb 100644 --- a/tests/_async/test_client.py +++ b/tests/_async/test_client.py @@ -1,17 +1,22 @@ +import httpx import pytest import hishel @pytest.mark.anyio -async def test_client(): - async with hishel.AsyncCacheClient() as client: - await client.request( - "GET", - "https://httpbun.org/redirect/?url=https%3A//httpbun.org&status_code=301", +async def test_client_301(): + async with hishel.MockAsyncTransport() as transport: + transport.add_responses( + [httpx.Response(301, headers=[(b"Location", b"https://example.com")])] ) - response = await client.request( - "GET", - "https://httpbun.org/redirect/?url=https%3A//httpbun.org&status_code=301", - ) - assert "network_stream" not in response.extensions # from cache + async with hishel.AsyncCacheClient(transport=transport) as client: + await client.request( + "GET", + "https://www.example.com", + ) + response = await client.request( + "GET", + "https://www.example.com", + ) + assert response.extensions["from_cache"] diff --git a/tests/_async/test_pool.py b/tests/_async/test_pool.py index 28fba6d..14520df 100644 --- a/tests/_async/test_pool.py +++ b/tests/_async/test_pool.py @@ -2,6 +2,7 @@ import pytest import hishel +from hishel._utils import extract_header_values, header_presents @pytest.mark.anyio @@ -14,3 +15,41 @@ async def test_pool_301(use_temp_dir): await cache_pool.request("GET", "https://www.example.com") response = await cache_pool.request("GET", "https://www.example.com") assert response.extensions["from_cache"] + + +@pytest.mark.anyio +async def test_pool_response_validation(use_temp_dir): + async with hishel.MockAsyncConnectionPool() as pool: + pool.add_responses( + [ + httpcore.Response( + 200, + headers=[ + (b"Cache-Control", b"max-age=3600"), + (b"Date", b"Mon, 25 Aug 2015 12:00:00 GMT"), + ], + content=b"test", + ), + httpcore.Response( + 304, + headers=[ + (b"Cache-Control", b"max-age=3600"), + (b"Date", b"Mon, 25 Aug 2015 12:00:00 GMT"), + (b"Content-Type", b"application/json"), + ], + ), + ] + ) + async with hishel.AsyncCacheConnectionPool(pool=pool) as cache_pool: + request = httpcore.Request("GET", "https://www.example.com") + + await cache_pool.handle_async_request(request) + response = await cache_pool.handle_async_request(request) + assert response.status == 200 + assert response.extensions["from_cache"] + assert header_presents(response.headers, b"Content-Type") + assert ( + extract_header_values(response.headers, b"Content-Type", single=True)[0] + == b"application/json" + ) + assert await response.aread() == b"test" diff --git a/tests/_async/test_transport.py b/tests/_async/test_transport.py index b01ea53..803c588 100644 --- a/tests/_async/test_transport.py +++ b/tests/_async/test_transport.py @@ -5,7 +5,7 @@ @pytest.mark.anyio -async def test_transport_301(): +async def test_transport_301(use_temp_dir): async with hishel.MockAsyncTransport() as transport: transport.add_responses( [httpx.Response(301, headers=[(b"Location", b"https://example.com")])] @@ -16,3 +16,38 @@ async def test_transport_301(): await cache_transport.handle_async_request(request) response = await cache_transport.handle_async_request(request) assert response.extensions["from_cache"] + + +@pytest.mark.anyio +async def test_transport_response_validation(use_temp_dir): + async with hishel.MockAsyncTransport() as transport: + transport.add_responses( + [ + httpx.Response( + 200, + headers=[ + (b"Cache-Control", b"max-age=3600"), + (b"Date", b"Mon, 25 Aug 2015 12:00:00 GMT"), + ], + content="test", + ), + httpx.Response( + 304, + headers=[ + (b"Cache-Control", b"max-age=3600"), + (b"Date", b"Mon, 25 Aug 2015 12:00:00 GMT"), + (b"Content-Type", b"application/json"), + ], + ), + ] + ) + async with hishel.AsyncCacheTransport(transport=transport) as cache_transport: + request = httpx.Request("GET", "https://www.example.com") + + await cache_transport.handle_async_request(request) + response = await cache_transport.handle_async_request(request) + assert response.status_code == 200 + assert response.extensions["from_cache"] + assert "Content-Type" in response.headers + assert response.headers["Content-Type"] == "application/json" + assert await response.aread() == b"test" diff --git a/tests/_sync/test_client.py b/tests/_sync/test_client.py index cf1c8e3..f4d642a 100644 --- a/tests/_sync/test_client.py +++ b/tests/_sync/test_client.py @@ -1,17 +1,22 @@ +import httpx import pytest import hishel -def test_client(): - with hishel.CacheClient() as client: - client.request( - "GET", - "https://httpbun.org/redirect/?url=https%3A//httpbun.org&status_code=301", +def test_client_301(): + with hishel.MockTransport() as transport: + transport.add_responses( + [httpx.Response(301, headers=[(b"Location", b"https://example.com")])] ) - response = client.request( - "GET", - "https://httpbun.org/redirect/?url=https%3A//httpbun.org&status_code=301", - ) - assert "network_stream" not in response.extensions # from cache + with hishel.CacheClient(transport=transport) as client: + client.request( + "GET", + "https://www.example.com", + ) + response = client.request( + "GET", + "https://www.example.com", + ) + assert response.extensions["from_cache"] diff --git a/tests/_sync/test_pool.py b/tests/_sync/test_pool.py index c9e9d8d..b5e7197 100644 --- a/tests/_sync/test_pool.py +++ b/tests/_sync/test_pool.py @@ -2,6 +2,7 @@ import pytest import hishel +from hishel._utils import extract_header_values, header_presents @@ -14,3 +15,41 @@ def test_pool_301(use_temp_dir): cache_pool.request("GET", "https://www.example.com") response = cache_pool.request("GET", "https://www.example.com") assert response.extensions["from_cache"] + + + +def test_pool_response_validation(use_temp_dir): + with hishel.MockConnectionPool() as pool: + pool.add_responses( + [ + httpcore.Response( + 200, + headers=[ + (b"Cache-Control", b"max-age=3600"), + (b"Date", b"Mon, 25 Aug 2015 12:00:00 GMT"), + ], + content=b"test", + ), + httpcore.Response( + 304, + headers=[ + (b"Cache-Control", b"max-age=3600"), + (b"Date", b"Mon, 25 Aug 2015 12:00:00 GMT"), + (b"Content-Type", b"application/json"), + ], + ), + ] + ) + with hishel.CacheConnectionPool(pool=pool) as cache_pool: + request = httpcore.Request("GET", "https://www.example.com") + + cache_pool.handle_request(request) + response = cache_pool.handle_request(request) + assert response.status == 200 + assert response.extensions["from_cache"] + assert header_presents(response.headers, b"Content-Type") + assert ( + extract_header_values(response.headers, b"Content-Type", single=True)[0] + == b"application/json" + ) + assert response.read() == b"test" diff --git a/tests/_sync/test_transport.py b/tests/_sync/test_transport.py index 0d5185d..c32b89d 100644 --- a/tests/_sync/test_transport.py +++ b/tests/_sync/test_transport.py @@ -5,7 +5,7 @@ -def test_transport_301(): +def test_transport_301(use_temp_dir): with hishel.MockTransport() as transport: transport.add_responses( [httpx.Response(301, headers=[(b"Location", b"https://example.com")])] @@ -16,3 +16,38 @@ def test_transport_301(): cache_transport.handle_request(request) response = cache_transport.handle_request(request) assert response.extensions["from_cache"] + + + +def test_transport_response_validation(use_temp_dir): + with hishel.MockTransport() as transport: + transport.add_responses( + [ + httpx.Response( + 200, + headers=[ + (b"Cache-Control", b"max-age=3600"), + (b"Date", b"Mon, 25 Aug 2015 12:00:00 GMT"), + ], + content="test", + ), + httpx.Response( + 304, + headers=[ + (b"Cache-Control", b"max-age=3600"), + (b"Date", b"Mon, 25 Aug 2015 12:00:00 GMT"), + (b"Content-Type", b"application/json"), + ], + ), + ] + ) + with hishel.CacheTransport(transport=transport) as cache_transport: + request = httpx.Request("GET", "https://www.example.com") + + cache_transport.handle_request(request) + response = cache_transport.handle_request(request) + assert response.status_code == 200 + assert response.extensions["from_cache"] + assert "Content-Type" in response.headers + assert response.headers["Content-Type"] == "application/json" + assert response.read() == b"test"