Skip to content

Commit

Permalink
Concentrate retry tests and complete coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
Gallaecio committed May 10, 2024
1 parent 2f533a7 commit f9a8c26
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 174 deletions.
3 changes: 3 additions & 0 deletions tests/mockserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ def render_POST(self, request):
request.setResponseCode(429)
response_data = {"status": 429, "type": "/limits/over-user-limit"}
return json.dumps(response_data).encode()
if domain == "e500.example":
request.setResponseCode(500)
return ""
if domain == "e520.example":
request.setResponseCode(520)
response_data = {"status": 520, "type": "/download/temporary-error"}
Expand Down
170 changes: 0 additions & 170 deletions tests/test_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,13 @@
from unittest.mock import AsyncMock

import pytest
from tenacity import AsyncRetrying

from zyte_api import AggressiveRetryFactory, AsyncZyteAPI, RequestError
from zyte_api._retry import RetryFactory
from zyte_api.aio.client import AsyncClient
from zyte_api.apikey import NoApiKey
from zyte_api.errors import ParsedError
from zyte_api.utils import USER_AGENT

from .mockserver import DropResource, MockServer


@pytest.mark.parametrize(
("client_cls",),
Expand Down Expand Up @@ -72,46 +68,6 @@ async def test_get(client_cls, get_method, mockserver):
assert actual_result == expected_result


UNSET = object()


class OutlierException(RuntimeError):
pass


@pytest.mark.parametrize(
("client_cls", "get_method"),
(
(AsyncZyteAPI, "get"),
(AsyncClient, "request_raw"),
),
)
@pytest.mark.parametrize(
("value", "exception"),
(
(UNSET, OutlierException),
(True, OutlierException),
(False, RequestError),
),
)
@pytest.mark.asyncio
async def test_get_handle_retries(client_cls, get_method, value, exception, mockserver):
kwargs = {}
if value is not UNSET:
kwargs["handle_retries"] = value

def broken_stop(_):
raise OutlierException

retrying = AsyncRetrying(stop=broken_stop)
client = client_cls(api_key="a", api_url=mockserver.urljoin("/"), retrying=retrying)
with pytest.raises(exception):
await getattr(client, get_method)(
{"url": "https://exception.example", "browserHtml": True},
**kwargs,
)


@pytest.mark.parametrize(
("client_cls", "get_method"),
(
Expand Down Expand Up @@ -234,132 +190,6 @@ async def test_iter(client_cls, iter_method, mockserver):
assert actual_result in expected_results


@pytest.mark.parametrize(
("client_cls", "get_method"),
(
(AsyncZyteAPI, "get"),
(AsyncClient, "request_raw"),
),
)
@pytest.mark.parametrize(
("subdomain", "waiter"),
(
("e429", "throttling"),
("e520", "temporary_download_error"),
),
)
@pytest.mark.asyncio
async def test_retry_wait(client_cls, get_method, subdomain, waiter, mockserver):
def broken_wait(self, retry_state):
raise OutlierException

class CustomRetryFactory(RetryFactory):
pass

setattr(CustomRetryFactory, f"{waiter}_wait", broken_wait)

retrying = CustomRetryFactory().build()
client = client_cls(api_key="a", api_url=mockserver.urljoin("/"), retrying=retrying)
with pytest.raises(OutlierException):
await getattr(client, get_method)(
{"url": f"https://{subdomain}.example", "browserHtml": True},
)


@pytest.mark.parametrize(
("client_cls", "get_method"),
(
(AsyncZyteAPI, "get"),
(AsyncClient, "request_raw"),
),
)
@pytest.mark.asyncio
async def test_retry_wait_network_error(client_cls, get_method):
waiter = "network_error"

def broken_wait(self, retry_state):
raise OutlierException

class CustomRetryFactory(RetryFactory):
pass

setattr(CustomRetryFactory, f"{waiter}_wait", broken_wait)

retrying = CustomRetryFactory().build()
with MockServer(resource=DropResource) as mockserver:
client = client_cls(
api_key="a", api_url=mockserver.urljoin("/"), retrying=retrying
)
with pytest.raises(OutlierException):
await getattr(client, get_method)(
{"url": "https://example.com", "browserHtml": True},
)


@pytest.mark.parametrize(
("client_cls", "get_method"),
(
(AsyncZyteAPI, "get"),
(AsyncClient, "request_raw"),
),
)
@pytest.mark.parametrize(
("subdomain", "stopper"),
(
("e429", "throttling"),
("e520", "temporary_download_error"),
),
)
@pytest.mark.asyncio
async def test_retry_stop(client_cls, get_method, subdomain, stopper, mockserver):
def broken_stop(self, retry_state):
raise OutlierException

class CustomRetryFactory(RetryFactory):
def wait(self, retry_state):
return None

setattr(CustomRetryFactory, f"{stopper}_stop", broken_stop)

retrying = CustomRetryFactory().build()
client = client_cls(api_key="a", api_url=mockserver.urljoin("/"), retrying=retrying)
with pytest.raises(OutlierException):
await getattr(client, get_method)(
{"url": f"https://{subdomain}.example", "browserHtml": True},
)


@pytest.mark.parametrize(
("client_cls", "get_method"),
(
(AsyncZyteAPI, "get"),
(AsyncClient, "request_raw"),
),
)
@pytest.mark.asyncio
async def test_retry_stop_network_error(client_cls, get_method):
stopper = "network_error"

def broken_stop(self, retry_state):
raise OutlierException

class CustomRetryFactory(RetryFactory):
def wait(self, retry_state):
return None

setattr(CustomRetryFactory, f"{stopper}_stop", broken_stop)

retrying = CustomRetryFactory().build()
with MockServer(resource=DropResource) as mockserver:
client = client_cls(
api_key="a", api_url=mockserver.urljoin("/"), retrying=retrying
)
with pytest.raises(OutlierException):
await getattr(client, get_method)(
{"url": "https://example.com", "browserHtml": True},
)


@pytest.mark.parametrize(
("client_cls", "get_method", "iter_method"),
(
Expand Down
110 changes: 106 additions & 4 deletions tests/test_retry.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,18 @@

import pytest
from aiohttp.client_exceptions import ServerConnectionError
from tenacity import AsyncRetrying

from zyte_api import RequestError, aggressive_retrying, zyte_api_retrying
from zyte_api import (
AggressiveRetryFactory,
AsyncZyteAPI,
RequestError,
RetryFactory,
aggressive_retrying,
zyte_api_retrying,
)

from .mockserver import DropResource, MockServer


def test_deprecated_imports():
Expand All @@ -17,6 +27,100 @@ def test_deprecated_imports():
assert zyte_api_retrying is deprecated_zyte_api_retrying


UNSET = object()


class OutlierException(RuntimeError):
pass


@pytest.mark.parametrize(
("value", "exception"),
(
(UNSET, OutlierException),
(True, OutlierException),
(False, RequestError),
),
)
@pytest.mark.asyncio
async def test_get_handle_retries(value, exception, mockserver):
kwargs = {}
if value is not UNSET:
kwargs["handle_retries"] = value

def broken_stop(_):
raise OutlierException

retrying = AsyncRetrying(stop=broken_stop)
client = AsyncZyteAPI(
api_key="a", api_url=mockserver.urljoin("/"), retrying=retrying
)
with pytest.raises(exception):
await client.get(
{"url": "https://exception.example", "browserHtml": True},
**kwargs,
)


@pytest.mark.parametrize(
("retry_factory", "status", "waiter"),
(
(RetryFactory, 429, "throttling"),
(RetryFactory, 520, "temporary_download_error"),
(AggressiveRetryFactory, 429, "throttling"),
(AggressiveRetryFactory, 500, "undocumented_error"),
(AggressiveRetryFactory, 520, "download_error"),
),
)
@pytest.mark.asyncio
async def test_retry_wait(retry_factory, status, waiter, mockserver):
def broken_wait(self, retry_state):
raise OutlierException

class CustomRetryFactory(retry_factory):
pass

setattr(CustomRetryFactory, f"{waiter}_wait", broken_wait)
retrying = CustomRetryFactory().build()
client = AsyncZyteAPI(
api_key="a", api_url=mockserver.urljoin("/"), retrying=retrying
)
with pytest.raises(OutlierException):
await client.get(
{"url": f"https://e{status}.example", "browserHtml": True},
)


@pytest.mark.parametrize(
("retry_factory",),
(
(RetryFactory,),
(AggressiveRetryFactory,),
),
)
@pytest.mark.asyncio
async def test_retry_wait_network_error(retry_factory):
waiter = "network_error"

def broken_wait(self, retry_state):
raise OutlierException

class CustomRetryFactory(retry_factory):
pass

setattr(CustomRetryFactory, f"{waiter}_wait", broken_wait)

retrying = CustomRetryFactory().build()
with MockServer(resource=DropResource) as mockserver:
client = AsyncZyteAPI(
api_key="a", api_url=mockserver.urljoin("/"), retrying=retrying
)
with pytest.raises(OutlierException):
await client.get(
{"url": "https://example.com", "browserHtml": True},
)


def mock_request_error(*, status=200):
return RequestError(
history=None,
Expand Down Expand Up @@ -335,9 +439,7 @@ def __init__(self, time):
)
@pytest.mark.asyncio
@patch("time.monotonic")
async def test_retrying(monotonic_mock, retrying, outcomes, exhausted):
"""Test retry stops based on a number of attempts (as opposed to those
based on time passed)."""
async def test_retry_stop(monotonic_mock, retrying, outcomes, exhausted):
monotonic_mock.return_value = 0
last_outcome = outcomes[-1]
outcomes = deque(outcomes)
Expand Down

0 comments on commit f9a8c26

Please sign in to comment.