Skip to content

Commit d165710

Browse files
committed
minor
1 parent ed02f1b commit d165710

File tree

2 files changed

+88
-25
lines changed

2 files changed

+88
-25
lines changed

packages/service-library/src/servicelib/aiohttp/rest_middlewares.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,7 @@
2727
from .typing_extension import Handler, Middleware
2828

2929
_DEFAULT_API_VERSION = "v0"
30-
MSG_INTERNAL_ERROR_USER_FRIENDLY_TEMPLATE = (
31-
"Ups, something went wrong! But we took good note [{}]"
32-
)
30+
MSG_INTERNAL_ERROR_USER_FRIENDLY_TEMPLATE = "Oops! Something went wrong, but we've noted it down and we'll sort it out ASAP. Thanks for your patience! [{}]"
3331

3432

3533
_logger = logging.getLogger(__name__)

packages/service-library/tests/aiohttp/test_rest_middlewares.py

+87-22
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import json
88
import logging
99
from dataclasses import dataclass
10+
from http import HTTPStatus
1011
from typing import Any
1112

1213
import pytest
@@ -33,6 +34,7 @@
3334
from servicelib.status_codes_utils import (
3435
get_http_status_codes,
3536
is_client_error,
37+
is_error,
3638
is_server_error,
3739
is_success,
3840
)
@@ -107,12 +109,12 @@ def returns_value(cls, suffix):
107109
returned_value = loop.run_until_complete(coro(None))
108110
return json.loads(json_dumps(returned_value))
109111

110-
HTTP_RESPONSE_REASON = "Response with code={}"
112+
EXPECTED_HTTP_RESPONSE_REASON = "custom reason for code {}"
111113

112114
@classmethod
113-
async def raise_http_response(cls, request: web.Request):
115+
async def get_http_response(cls, request: web.Request):
114116
status_code = int(request.query["code"])
115-
reason = cls.HTTP_RESPONSE_REASON.format(status_code)
117+
reason = cls.EXPECTED_HTTP_RESPONSE_REASON.format(status_code)
116118

117119
match status_code:
118120
case status.HTTP_405_METHOD_NOT_ALLOWED:
@@ -129,14 +131,14 @@ async def raise_http_response(cls, request: web.Request):
129131
)
130132
case _:
131133
http_response_cls = all_aiohttp_http_exceptions[status_code]
132-
raise http_response_cls(reason=reason)
134+
if is_error(status_code):
135+
# 4XX and 5XX are raised
136+
raise http_response_cls(reason=reason)
137+
else:
138+
# otherwise returned
139+
return http_response_cls(reason=reason)
133140

134-
RAISE_UNEXPECTED_REASON = "Unexpected error"
135-
136-
@classmethod
137-
async def raise_unexpected(cls, request: web.Request):
138-
assert request
139-
raise SomeUnexpectedError(cls.RAISE_UNEXPECTED_REASON)
141+
EXPECTED_RAISE_UNEXPECTED_REASON = "Unexpected error"
140142

141143
@classmethod
142144
async def raise_exception(cls, request: web.Request):
@@ -146,6 +148,14 @@ async def raise_exception(cls, request: web.Request):
146148
raise NotImplementedError
147149
case asyncio.TimeoutError.__name__:
148150
raise asyncio.TimeoutError
151+
case web.HTTPOk.__name__:
152+
raise web.HTTPOk # 2XX
153+
case web.HTTPUnauthorized.__name__:
154+
raise web.HTTPUnauthorized # 4XX
155+
case web.HTTPServiceUnavailable.__name__:
156+
raise web.HTTPServiceUnavailable # 5XX
157+
case _: # unexpected
158+
raise SomeUnexpectedError(cls.EXPECTED_RAISE_UNEXPECTED_REASON)
149159

150160

151161
@pytest.fixture
@@ -165,13 +175,22 @@ def client(event_loop, aiohttp_client):
165175
("/v1/string", Handlers.get_string),
166176
("/v1/number", Handlers.get_number),
167177
("/v1/mixed", Handlers.get_mixed),
168-
("/v1/raise_http_code", Handlers.raise_http_response),
169-
("/v1/raise_unexpected", Handlers.raise_unexpected),
178+
("/v1/get_http_response", Handlers.get_http_response),
170179
("/v1/raise_exception", Handlers.raise_exception),
171180
]
172181
]
173182
)
174183

184+
app.router.add_routes(
185+
[
186+
web.get(
187+
"/free/raise_exception",
188+
Handlers.raise_exception,
189+
name="raise_exception_without_middleware",
190+
)
191+
]
192+
)
193+
175194
# middlewares
176195
app.middlewares.append(error_middleware_factory(api_version="/v1"))
177196
app.middlewares.append(envelope_middleware_factory(api_version="/v1"))
@@ -227,13 +246,15 @@ def _is_server_error(code):
227246

228247
@pytest.mark.parametrize("status_code", get_http_status_codes(status, _is_server_error))
229248
async def test_fails_with_http_server_error(client: TestClient, status_code: int):
230-
response = await client.get("/v1/raise_http_code", params={"code": status_code})
249+
response = await client.get("/v1/get_http_response", params={"code": status_code})
231250
assert response.status == status_code
232251

233252
data, error = unwrap_envelope(await response.json())
234253
assert not data
235254
assert error
236-
assert error["message"] == Handlers.HTTP_RESPONSE_REASON.format(status_code)
255+
assert error["message"] == Handlers.EXPECTED_HTTP_RESPONSE_REASON.format(
256+
status_code
257+
)
237258

238259

239260
def _is_client_error(code):
@@ -244,13 +265,15 @@ def _is_client_error(code):
244265

245266
@pytest.mark.parametrize("status_code", get_http_status_codes(status, _is_client_error))
246267
async def test_fails_with_http_client_error(client: TestClient, status_code: int):
247-
response = await client.get("/v1/raise_http_code", params={"code": status_code})
268+
response = await client.get("/v1/get_http_response", params={"code": status_code})
248269
assert response.status == status_code
249270

250271
data, error = unwrap_envelope(await response.json())
251272
assert not data
252273
assert error
253-
assert error["message"] == Handlers.HTTP_RESPONSE_REASON.format(status_code)
274+
assert error["message"] == Handlers.EXPECTED_HTTP_RESPONSE_REASON.format(
275+
status_code
276+
)
254277
assert error["errors"]
255278

256279

@@ -260,9 +283,11 @@ def _is_success(code):
260283

261284
@pytest.mark.parametrize("status_code", get_http_status_codes(status, _is_success))
262285
async def test_fails_with_http_successful(client: TestClient, status_code: int):
263-
response = await client.get("/v1/raise_http_code", params={"code": status_code})
286+
response = await client.get("/v1/get_http_response", params={"code": status_code})
264287
assert response.status == status_code
265288

289+
print(await response.text())
290+
266291
data, error = unwrap_envelope(await response.json())
267292
assert not error
268293
assert data
@@ -291,7 +316,7 @@ async def test_raised_unhandled_exception(
291316
client: TestClient, caplog: pytest.LogCaptureFixture
292317
):
293318
caplog.set_level(logging.ERROR)
294-
response = await client.get("/v1/raise_unexpected")
319+
response = await client.get("/v1/raise_exception")
295320

296321
# respond the client with 500
297322
assert response.status == status.HTTP_500_INTERNAL_SERVER_ERROR
@@ -314,23 +339,63 @@ async def test_raised_unhandled_exception(
314339

315340
# log sufficient information to diagnose the issue
316341
#
317-
# ERROR servicelib.aiohttp.rest_middlewares:rest_middlewares.py:96 Request 'GET /v1/raise_unexpected' raised 'SomeUnhandledError' [OEC:140555466658464]
342+
# ERROR servicelib.aiohttp.rest_middlewares:rest_middlewares.py:96 Request 'GET /v1/raise_exception' raised 'SomeUnhandledError' [OEC:140555466658464]
318343
# request.remote='127.0.0.1'
319344
# request.headers={b'Host': b'127.0.0.1:33461', b'Accept': b'*/*', b'Accept-Encoding': b'gzip, deflate', b'User-Agent': b'Python/3.10 aiohttp/3.8.6'}
320345
# Traceback (most recent call last):
321346
# File "osparc-simcore/packages/service-library/src/servicelib/aiohttp/rest_middlewares.py", line 120, in _middleware_handler
322347
# return await handler(request)
323348
# File "osparc-simcore/packages/service-library/src/servicelib/aiohttp/rest_middlewares.py", line 177, in _middleware_handler
324349
# resp_or_data = await handler(request)
325-
# File "osparc-simcore/packages/service-library/tests/aiohttp/test_rest_middlewares.py", line 107, in raise_unexpected
326-
# raise SomeUnhandledError(cls.raise_unexpected_REASON)
350+
# File "osparc-simcore/packages/service-library/tests/aiohttp/test_rest_middlewares.py", line 107, in raise_exception
351+
# raise SomeUnhandledError(cls.EXPECTED_RAISE_UNEXPECTED_REASON)
327352
# tests.aiohttp.test_rest_middlewares.SomeUnhandledError: Unexpected error
328353

329354
assert response.method in caplog.text
330355
assert response.url.path in caplog.text
331356
assert "request.headers=" in caplog.text
332357
assert "request.remote=" in caplog.text
333358
assert SomeUnexpectedError.__name__ in caplog.text
334-
assert Handlers.RAISE_UNEXPECTED_REASON in caplog.text
359+
assert Handlers.EXPECTED_RAISE_UNEXPECTED_REASON in caplog.text
335360
# log OEC
336361
assert "OEC:" in caplog.text
362+
363+
364+
async def test_aiohttp_exceptions_construction_policies(client: TestClient):
365+
366+
# using default constructor
367+
err = web.HTTPOk()
368+
assert err.status == status.HTTP_200_OK
369+
assert err.content_type == "text/plain"
370+
# reason is an exception property and is default to
371+
assert err.reason == HTTPStatus(status.HTTP_200_OK).phrase
372+
# default text if nothing set!
373+
assert err.text == f"{err.status}: {err.reason}"
374+
375+
# This is how it is transformed into a response
376+
#
377+
# NOTE: that the reqson is somehow transmitted in the header
378+
#
379+
# version = request.version
380+
# status_line = "HTTP/{}.{} {} {}".format(
381+
# version[0], version[1], self._status, self._reason
382+
# )
383+
# await writer.write_headers(status_line, self._headers)
384+
#
385+
#
386+
assert client.app
387+
assert (
388+
client.app.router["raise_exception_without_middleware"].url_for().path
389+
== "/free/raise_exception"
390+
)
391+
392+
response = await client.get(
393+
"/free/raise_exception", params={"exc": web.HTTPOk.__name__}
394+
)
395+
assert response.status == status.HTTP_200_OK
396+
assert response.reason == err.reason # I wonder how this is passed
397+
assert response.content_type == err.content_type
398+
399+
text = await response.text()
400+
assert err.text == f"{err.status}: {err.reason}"
401+
print(text)

0 commit comments

Comments
 (0)