Files

216 lines
5.8 KiB
Python

import threading
from types import SimpleNamespace
from unittest.mock import MagicMock
import pytest
from aiohttp import web
from core.routes.helpers import create_route_helpers, register_service_route
class _HashCancelled(Exception):
pass
class _Routes:
def __init__(self):
self.handlers = {}
def post(self, path):
def register(handler):
self.handlers[('POST', path)] = handler
return handler
return register
def get(self, path):
def register(handler):
self.handlers[('GET', path)] = handler
return handler
return register
def _build_helpers():
logger = MagicMock()
helpers = create_route_helpers(
web=web,
logger=logger,
load_settings=MagicMock(return_value={"download_backend": "python"}),
hash_calculation_cancelled=_HashCancelled,
)
return helpers, logger
@pytest.mark.asyncio
async def test_register_service_route_delegates_to_the_operation():
routes = _Routes()
requests = []
async def operation(request):
requests.append(request)
return 'ok'
register_service_route(
{
'json_api_endpoint': lambda _error_prefix: lambda func: func,
'routes': routes,
},
path='/model_resolver/example',
error_prefix='example',
operation=operation,
)
request = object()
response = await routes.handlers[('POST', '/model_resolver/example')](request)
assert response == 'ok'
assert requests == [request]
@pytest.mark.asyncio
async def test_register_service_route_supports_multiple_methods_and_error_flags():
routes = _Routes()
endpoint_options = []
def endpoint(error_prefix, return_success_on_error=False):
endpoint_options.append((error_prefix, return_success_on_error))
return lambda func: func
async def operation(_request):
return 'ok'
register_service_route(
{
'json_api_endpoint': endpoint,
'routes': routes,
},
path='/model_resolver/example',
methods=('get', 'post'),
error_prefix='example',
operation=operation,
return_success_on_error=True,
)
assert set(routes.handlers) == {
('GET', '/model_resolver/example'),
('POST', '/model_resolver/example'),
}
assert endpoint_options == [('example', True)]
@pytest.mark.asyncio
async def test_json_api_endpoint_returns_500_for_unexpected_exception():
(json_api_endpoint, *_), logger = _build_helpers()
@json_api_endpoint("test")
async def handler(request):
raise RuntimeError("boom")
response = await handler(SimpleNamespace())
assert response.status == 500
assert response.text == '{"error": "boom"}'
logger.error.assert_called_once()
@pytest.mark.asyncio
async def test_json_api_endpoint_preserves_http_exception_status():
(json_api_endpoint, *_), logger = _build_helpers()
@json_api_endpoint("test")
async def handler(request):
raise web.HTTPBadRequest(reason="invalid payload")
response = await handler(SimpleNamespace())
assert response.status == 400
assert response.text == '{"error": "invalid payload"}'
logger.error.assert_called_once()
@pytest.mark.asyncio
async def test_json_api_endpoint_can_include_failure_success_flag():
(json_api_endpoint, *_), _ = _build_helpers()
@json_api_endpoint("test", return_success_on_error=True)
async def handler(request):
raise RuntimeError("boom")
response = await handler(SimpleNamespace())
assert response.status == 500
assert response.text == '{"error": "boom", "success": false}'
def test_progress_helpers_validate_ids_and_wrap_results():
(
_,
get_progress_response,
cancel_progress_response,
*_rest,
), _ = _build_helpers()
tracker = MagicMock()
tracker.get.return_value = {"status": "running"}
tracker.mark_cancelled.return_value = False
request = SimpleNamespace(match_info={"progress_id": "job-1"})
progress = get_progress_response(
tracker,
request,
found_wrapper=lambda payload: {"exists": True, **payload},
)
cancelled = cancel_progress_response(tracker, request)
assert progress.text == '{"exists": true, "status": "running"}'
assert cancelled.text == (
'{"success": true, "cancelled": false, "progress_id": "job-1"}'
)
tracker.cleanup.assert_called_once_with()
tracker.mark_cancelled.assert_called_once_with("job-1", "Cancelled")
@pytest.mark.parametrize("helper_index", [1, 2])
def test_progress_helpers_reject_blank_ids(helper_index):
(
_,
get_progress_response,
cancel_progress_response,
*_rest,
), _ = _build_helpers()
tracker = MagicMock()
request = SimpleNamespace(match_info={"analysis_id": " "})
helper = (get_progress_response, cancel_progress_response)[helper_index - 1]
response = helper(tracker, request, param_name="analysis_id")
assert response.status == 400
assert response.text == '{"error": "analysis_id is required"}'
tracker.cleanup.assert_not_called()
tracker.get.assert_not_called()
tracker.mark_cancelled.assert_not_called()
def test_background_helper_reports_unexpected_task_errors():
(_, _, _, run_in_background_thread, *_rest), logger = _build_helpers()
tracker = MagicMock()
tracker.is_cancelled.return_value = False
on_error = MagicMock()
error_reported = threading.Event()
def report_error(error):
on_error(error)
error_reported.set()
run_in_background_thread(
tracker,
"job-1",
lambda: (_ for _ in ()).throw(RuntimeError("boom")),
MagicMock(),
on_error=report_error,
)
assert error_reported.wait(timeout=2)
on_error.assert_called_once()
logger.exception.assert_called_once()