From ef3ef3529491bb0332d872437304ceeb80d14ffd Mon Sep 17 00:00:00 2001 From: sarayourfriend <24264157+sarayourfriend@users.noreply.github.com> Date: Wed, 22 Nov 2023 20:06:59 +1100 Subject: [PATCH] Update tests for async implementation --- api/test/conftest.py | 29 +++++++++- api/test/unit/utils/test_image_proxy.py | 74 ++++++++++++++++++------- 2 files changed, 79 insertions(+), 24 deletions(-) diff --git a/api/test/conftest.py b/api/test/conftest.py index e06e86b862b..5277a134a29 100644 --- a/api/test/conftest.py +++ b/api/test/conftest.py @@ -1,11 +1,34 @@ +import asyncio + import pytest -from asgiref.sync import async_to_sync from conf.asgi import application +@pytest.fixture +def get_new_loop(): + loops: list[asyncio.AbstractEventLoop] = [] + + def _get_new_loop() -> asyncio.AbstractEventLoop: + loop = asyncio.new_event_loop() + loops.append(loop) + return loop + + yield _get_new_loop + + for loop in loops: + loop.close() + + +@pytest.fixture(scope="session") +def session_loop() -> asyncio.AbstractEventLoop: + loop = asyncio.new_event_loop() + yield loop + loop.close() + + @pytest.fixture(scope="session", autouse=True) -def ensure_asgi_lifecycle(): +def ensure_asgi_lifecycle(session_loop: asyncio.AbstractEventLoop): """ Call application shutdown lifecycle event. @@ -27,4 +50,4 @@ async def shutdown(): return {"type": "lifespan.shutdown"} yield - async_to_sync(application)(scope, shutdown, noop) + session_loop.run_until_complete(application(scope, shutdown, noop)) diff --git a/api/test/unit/utils/test_image_proxy.py b/api/test/unit/utils/test_image_proxy.py index 5f2aa58f15a..657dbe211b4 100644 --- a/api/test/unit/utils/test_image_proxy.py +++ b/api/test/unit/utils/test_image_proxy.py @@ -1,3 +1,4 @@ +import asyncio from dataclasses import replace from test.factory.models.image import ImageFactory from unittest.mock import MagicMock @@ -6,6 +7,7 @@ from django.conf import settings from rest_framework.exceptions import UnsupportedMediaType +import aiohttp import pook import pytest import requests @@ -17,7 +19,7 @@ UpstreamThumbnailException, extension, ) -from api.utils.image_proxy import get as photon_get +from api.utils.image_proxy import get as _photon_get from api.utils.tallies import get_monthly_timestamp @@ -56,8 +58,26 @@ def auth_key(): settings.PHOTON_AUTH_KEY = None +@pytest.fixture +def photon_get(session_loop): + """ + Run ``image_proxy.get`` and wait for all tasks to finish. + """ + + def do(*args, **kwargs): + try: + res = session_loop.run_until_complete(_photon_get(*args, **kwargs)) + return res + finally: + tasks = asyncio.all_tasks(session_loop) + for task in tasks: + session_loop.run_until_complete(task) + + yield do + + @pook.on -def test_get_successful_no_auth_key_default_args(mock_image_data): +def test_get_successful_no_auth_key_default_args(photon_get, mock_image_data): mock_get: pook.Mock = ( pook.get(PHOTON_URL_FOR_TEST_IMAGE) .params( @@ -81,7 +101,9 @@ def test_get_successful_no_auth_key_default_args(mock_image_data): @pook.on -def test_get_successful_original_svg_no_auth_key_default_args(mock_image_data): +def test_get_successful_original_svg_no_auth_key_default_args( + photon_get, mock_image_data +): mock_get: pook.Mock = ( pook.get(TEST_IMAGE_URL.replace(".jpg", ".svg")) .header("User-Agent", UA_HEADER) @@ -103,7 +125,9 @@ def test_get_successful_original_svg_no_auth_key_default_args(mock_image_data): @pook.on -def test_get_successful_with_auth_key_default_args(mock_image_data, auth_key): +def test_get_successful_with_auth_key_default_args( + photon_get, mock_image_data, auth_key +): mock_get: pook.Mock = ( pook.get(PHOTON_URL_FOR_TEST_IMAGE) .params( @@ -128,7 +152,7 @@ def test_get_successful_with_auth_key_default_args(mock_image_data, auth_key): @pook.on -def test_get_successful_no_auth_key_not_compressed(mock_image_data): +def test_get_successful_no_auth_key_not_compressed(photon_get, mock_image_data): mock_get: pook.Mock = ( pook.get(PHOTON_URL_FOR_TEST_IMAGE) .params( @@ -151,7 +175,7 @@ def test_get_successful_no_auth_key_not_compressed(mock_image_data): @pook.on -def test_get_successful_no_auth_key_full_size(mock_image_data): +def test_get_successful_no_auth_key_full_size(photon_get, mock_image_data): mock_get: pook.Mock = ( pook.get(PHOTON_URL_FOR_TEST_IMAGE) .params( @@ -174,7 +198,9 @@ def test_get_successful_no_auth_key_full_size(mock_image_data): @pook.on -def test_get_successful_no_auth_key_full_size_not_compressed(mock_image_data): +def test_get_successful_no_auth_key_full_size_not_compressed( + photon_get, mock_image_data +): mock_get: pook.Mock = ( pook.get(PHOTON_URL_FOR_TEST_IMAGE) .header("User-Agent", UA_HEADER) @@ -195,7 +221,7 @@ def test_get_successful_no_auth_key_full_size_not_compressed(mock_image_data): @pook.on -def test_get_successful_no_auth_key_png_only(mock_image_data): +def test_get_successful_no_auth_key_png_only(photon_get, mock_image_data): mock_get: pook.Mock = ( pook.get(PHOTON_URL_FOR_TEST_IMAGE) .params( @@ -219,7 +245,7 @@ def test_get_successful_no_auth_key_png_only(mock_image_data): @pook.on -def test_get_successful_forward_query_params(mock_image_data): +def test_get_successful_forward_query_params(photon_get, mock_image_data): params = urlencode({"hello": "world", 1: 2, "beep": "boop"}) mock_get: pook.Mock = ( pook.get(PHOTON_URL_FOR_TEST_IMAGE) @@ -249,18 +275,18 @@ def test_get_successful_forward_query_params(mock_image_data): @pytest.fixture -def setup_requests_get_exception(monkeypatch): +def setup_request_exception(monkeypatch): def do(exc): - def raise_exc(*args, **kwargs): + async def raise_exc(*args, **kwargs): raise exc - monkeypatch.setattr("requests.get", raise_exc) + monkeypatch.setattr(aiohttp.ClientSession, "get", raise_exc) yield do @pook.on -def test_get_successful_records_response_code(mock_image_data, redis): +def test_get_successful_records_response_code(photon_get, mock_image_data, redis): ( pook.get(PHOTON_URL_FOR_TEST_IMAGE) .params( @@ -317,15 +343,16 @@ def test_get_successful_records_response_code(mock_image_data, redis): ) @alert_count_params def test_get_exception_handles_error( + photon_get, exc, exc_name, count_start, should_alert, capture_exception, - setup_requests_get_exception, + setup_request_exception, redis, ): - setup_requests_get_exception(exc) + setup_request_exception(exc) month = get_monthly_timestamp() key = f"thumbnail_error:{exc_name}:subdomain.example.com:{month}" redis.set(key, count_start) @@ -353,19 +380,20 @@ def test_get_exception_handles_error( ], ) def test_get_http_exception_handles_error( + photon_get, status_code, text, count_start, should_alert, capture_exception, - setup_requests_get_exception, + setup_request_exception, redis, ): mock_response = MagicMock(spec=requests.Response) mock_response.status_code = status_code mock_response.text = text exc = requests.HTTPError(response=mock_response) - setup_requests_get_exception(exc) + setup_request_exception(exc) month = get_monthly_timestamp() key = f"thumbnail_error:requests.exceptions.HTTPError:subdomain.example.com:{month}" @@ -392,7 +420,9 @@ def test_get_http_exception_handles_error( @pook.on -def test_get_successful_https_image_url_sends_ssl_parameter(mock_image_data): +def test_get_successful_https_image_url_sends_ssl_parameter( + photon_get, mock_image_data +): https_url = TEST_IMAGE_URL.replace("http://", "https://") mock_get: pook.Mock = ( pook.get(PHOTON_URL_FOR_TEST_IMAGE) @@ -420,7 +450,7 @@ def test_get_successful_https_image_url_sends_ssl_parameter(mock_image_data): @pook.on -def test_get_unsuccessful_request_raises_custom_exception(): +def test_get_unsuccessful_request_raises_custom_exception(photon_get): mock_get: pook.Mock = pook.get(PHOTON_URL_FOR_TEST_IMAGE).reply(404).mock with pytest.raises( @@ -452,7 +482,7 @@ def test__get_extension_from_url(image_url, expected_ext): @pytest.mark.django_db @pytest.mark.parametrize("image_type", ["apng", "tiff", "bmp"]) -def test_photon_get_raises_by_not_allowed_types(image_type): +def test_photon_get_raises_by_not_allowed_types(photon_get, image_type): image_url = TEST_IMAGE_URL.replace(".jpg", f".{image_type}") image = ImageFactory.create(url=image_url) media_info = MediaInfo( @@ -473,7 +503,9 @@ def test_photon_get_raises_by_not_allowed_types(image_type): ({"Content-Type": "unknown"}, b"unknown"), ], ) -def test_photon_get_saves_image_type_to_cache(redis, headers, expected_cache_val): +def test_photon_get_saves_image_type_to_cache( + photon_get, redis, headers, expected_cache_val +): image_url = TEST_IMAGE_URL.replace(".jpg", "") image = ImageFactory.create(url=image_url) media_info = MediaInfo(