|
6 | 6 | # pylint: disable=unused-variable |
7 | 7 |
|
8 | 8 |
|
| 9 | +import logging |
| 10 | + |
9 | 11 | import pytest |
10 | 12 | from aiohttp import web |
11 | 13 | from aiohttp.test_utils import make_mocked_request |
|
14 | 16 | from simcore_service_webserver.errors import WebServerBaseError |
15 | 17 | from simcore_service_webserver.exceptions_handlers_base import ( |
16 | 18 | AsyncDynamicTryExceptContext, |
| 19 | + async_try_except_decorator, |
17 | 20 | ) |
18 | 21 | from simcore_service_webserver.exceptions_handlers_http_error_map import ( |
19 | 22 | ExceptionToHttpErrorMap, |
@@ -59,120 +62,93 @@ async def test_factory__create_exception_handler_from_http_error( |
59 | 62 | assert response.content_type == MIMETYPE_APPLICATION_JSON |
60 | 63 |
|
61 | 64 |
|
62 | | -async def test_factory__create_exception_handler_from_http_error_map( |
| 65 | +async def test_handling_different_exceptions_with_context( |
63 | 66 | fake_request: web.Request, |
| 67 | + caplog: pytest.LogCaptureFixture, |
64 | 68 | ): |
65 | 69 | exc_to_http_error_map: ExceptionToHttpErrorMap = { |
66 | | - OneError: HttpErrorInfo(status.HTTP_400_BAD_REQUEST, "Error One mapped to 400") |
| 70 | + OneError: HttpErrorInfo(status.HTTP_400_BAD_REQUEST, "Error {code} to 400"), |
| 71 | + OtherError: HttpErrorInfo(status.HTTP_500_INTERNAL_SERVER_ERROR, "{code}"), |
67 | 72 | } |
68 | | - |
69 | 73 | cm = AsyncDynamicTryExceptContext( |
70 | 74 | to_exceptions_handlers_map(exc_to_http_error_map), request=fake_request |
71 | 75 | ) |
72 | | - async with cm: |
73 | | - raise OneError |
74 | | - |
75 | | - response = cm.get_response() |
76 | | - assert response is not None |
77 | | - assert response.status == status.HTTP_400_BAD_REQUEST |
78 | | - assert response.reason == "Error One mapped to 400" |
79 | 76 |
|
80 | | - # By-passes exceptions not listed |
81 | | - err = RuntimeError() |
82 | | - with pytest.raises(RuntimeError) as err_info: |
| 77 | + with caplog.at_level(logging.ERROR): |
| 78 | + # handles as 4XX |
83 | 79 | async with cm: |
84 | | - raise err |
85 | | - |
86 | | - assert cm.get_response() is None |
87 | | - assert err_info.value == err |
88 | | - |
89 | | - |
90 | | -# async def test__handled_exception_context_manager(fake_request: web.Request): |
91 | | - |
92 | | -# expected_response = web.json_response({"error": {"msg": "Foo"}}) |
93 | | - |
94 | | -# async def _custom_handler(request, exception): |
95 | | -# assert request == fake_request |
96 | | -# assert isinstance( |
97 | | -# exception, BaseError |
98 | | -# ), "only BasePluginError exceptions should call this handler" |
99 | | -# return expected_response |
100 | | - |
101 | | -# exc_handling_ctx = _handled_exception_context_manager( |
102 | | -# BaseError, _custom_handler, request=fake_request |
103 | | -# ) |
104 | | - |
105 | | -# # handles any BaseError returning a response |
106 | | -# async with exc_handling_ctx as ctx: |
107 | | -# raise OneError |
108 | | -# assert ctx.response == expected_response |
109 | | - |
110 | | -# async with exc_handling_ctx as ctx: |
111 | | -# raise OtherError |
112 | | -# assert ctx.response == expected_response |
113 | | - |
114 | | -# # otherwise thru |
115 | | -# with pytest.raises(ArithmeticError): |
116 | | -# async with exc_handling_ctx: |
117 | | -# raise ArithmeticError |
118 | | - |
119 | | - |
120 | | -# async def test_create_decorator_from_exception_handler( |
121 | | -# caplog: pytest.LogCaptureFixture, |
122 | | -# ): |
123 | | -# # Create an SINGLE exception handler that acts as umbrella for all these exceptions |
124 | | -# http_error_map: ExceptionToHttpErrorMap = { |
125 | | -# OneError: HttpErrorInfo( |
126 | | -# status.HTTP_503_SERVICE_UNAVAILABLE, |
127 | | -# "Human readable error transmitted to the front-end", |
128 | | -# ) |
129 | | -# } |
130 | | - |
131 | | -# exc_handler = create_exception_handler_from_http_error_map(http_error_map) |
132 | | -# _exc_handling_ctx = create_decorator_from_exception_handler( |
133 | | -# exception_types=BaseError, # <--- FIXME" this is redundant because exception has been already passed in exc_handler! |
134 | | -# exception_handler=exc_handler, |
135 | | -# ) |
| 80 | + raise OneError |
| 81 | + |
| 82 | + response = cm.get_response() |
| 83 | + assert response is not None |
| 84 | + assert response.status == status.HTTP_400_BAD_REQUEST |
| 85 | + assert response.reason == exc_to_http_error_map[OneError].msg_template.format( |
| 86 | + code="WebServerBaseError.BaseError.OneError" |
| 87 | + ) |
| 88 | + assert not caplog.records |
| 89 | + |
| 90 | + # unhandled -> reraises |
| 91 | + err = RuntimeError() |
| 92 | + with pytest.raises(RuntimeError) as err_info: |
| 93 | + async with cm: |
| 94 | + raise err |
| 95 | + |
| 96 | + assert cm.get_response() is None |
| 97 | + assert err_info.value == err |
| 98 | + |
| 99 | + # handles as 5XX and logs |
| 100 | + async with cm: |
| 101 | + raise OtherError |
136 | 102 |
|
137 | | -# @_exc_handling_ctx |
138 | | -# async def _rest_handler(request: web.Request) -> web.Response: |
139 | | -# if request.query.get("raise") == "OneError": |
140 | | -# raise OneError |
141 | | -# if request.query.get("raise") == "ArithmeticError": |
142 | | -# raise ArithmeticError |
| 103 | + response = cm.get_response() |
| 104 | + assert response is not None |
| 105 | + assert response.status == status.HTTP_500_INTERNAL_SERVER_ERROR |
| 106 | + assert response.reason == exc_to_http_error_map[OtherError].msg_template.format( |
| 107 | + code="WebServerBaseError.BaseError.OtherError" |
| 108 | + ) |
| 109 | + assert caplog.records, "Expected 5XX troubleshooting logged as error" |
| 110 | + assert caplog.records[0].levelno == logging.ERROR |
143 | 111 |
|
144 | | -# return web.Response(reason="all good") |
145 | 112 |
|
146 | | -# with caplog.at_level(logging.ERROR): |
| 113 | +async def test_handling_different_exceptions_with_decorator( |
| 114 | + fake_request: web.Request, |
| 115 | + caplog: pytest.LogCaptureFixture, |
| 116 | +): |
| 117 | + exc_to_http_error_map: ExceptionToHttpErrorMap = { |
| 118 | + OneError: HttpErrorInfo(status.HTTP_503_SERVICE_UNAVAILABLE, "{code}"), |
| 119 | + } |
147 | 120 |
|
148 | | -# # emulates successful call |
149 | | -# resp = await _rest_handler(make_mocked_request("GET", "/foo")) |
150 | | -# assert resp.status == status.HTTP_200_OK |
151 | | -# assert resp.reason == "all good" |
| 121 | + exc_handling_decorator = async_try_except_decorator( |
| 122 | + to_exceptions_handlers_map(exc_to_http_error_map) |
| 123 | + ) |
152 | 124 |
|
153 | | -# assert not caplog.records |
| 125 | + @exc_handling_decorator |
| 126 | + async def _rest_handler(request: web.Request) -> web.Response: |
| 127 | + if request.query.get("raise") == "OneError": |
| 128 | + raise OneError |
| 129 | + if request.query.get("raise") == "ArithmeticError": |
| 130 | + raise ArithmeticError |
| 131 | + return web.json_response(reason="all good") |
154 | 132 |
|
155 | | -# # this will be passed and catched by the outermost error middleware |
156 | | -# with pytest.raises(ArithmeticError): |
157 | | -# await _rest_handler( |
158 | | -# make_mocked_request("GET", "/foo?raise=ArithmeticError") |
159 | | -# ) |
| 133 | + with caplog.at_level(logging.ERROR): |
160 | 134 |
|
161 | | -# assert not caplog.records |
| 135 | + # emulates successful call |
| 136 | + resp = await _rest_handler(make_mocked_request("GET", "/foo")) |
| 137 | + assert resp.status == status.HTTP_200_OK |
| 138 | + assert resp.reason == "all good" |
162 | 139 |
|
163 | | -# # this is a 5XX will be converted to response but is logged as error as well |
164 | | -# with pytest.raises(web.HTTPException) as exc_info: |
165 | | -# await _rest_handler(make_mocked_request("GET", "/foo?raise=OneError")) |
| 140 | + assert not caplog.records |
166 | 141 |
|
167 | | -# resp = exc_info.value |
168 | | -# assert resp.status == status.HTTP_503_SERVICE_UNAVAILABLE |
169 | | -# assert "front-end" in resp.reason |
| 142 | + # reraised |
| 143 | + with pytest.raises(ArithmeticError): |
| 144 | + await _rest_handler( |
| 145 | + make_mocked_request("GET", "/foo?raise=ArithmeticError") |
| 146 | + ) |
170 | 147 |
|
171 | | -# assert caplog.records, "Expected 5XX troubleshooting logged as error" |
172 | | -# assert caplog.records[0].levelno == logging.ERROR |
| 148 | + assert not caplog.records |
173 | 149 |
|
174 | | -# # typically capture by last |
175 | | -# with pytest.raises(ArithmeticError): |
176 | | -# resp = await _rest_handler( |
177 | | -# make_mocked_request("GET", "/foo?raise=ArithmeticError") |
178 | | -# ) |
| 150 | + # handles as 5XX and logs |
| 151 | + resp = await _rest_handler(make_mocked_request("GET", "/foo?raise=OneError")) |
| 152 | + assert resp.status == status.HTTP_503_SERVICE_UNAVAILABLE |
| 153 | + assert caplog.records, "Expected 5XX troubleshooting logged as error" |
| 154 | + assert caplog.records[0].levelno == logging.ERROR |
0 commit comments