2021-08-07 21:24:48 +01:00
|
|
|
import logging
|
2021-04-05 16:01:48 +01:00
|
|
|
import warnings
|
|
|
|
|
2016-12-30 19:50:12 +00:00
|
|
|
import pytest
|
2019-04-23 22:44:42 +01:00
|
|
|
|
2017-01-13 00:54:34 +00:00
|
|
|
from bs4 import BeautifulSoup
|
2016-12-30 19:50:12 +00:00
|
|
|
|
2016-10-14 11:37:40 +01:00
|
|
|
from sanic import Sanic
|
2019-04-23 22:44:42 +01:00
|
|
|
from sanic.exceptions import (
|
|
|
|
Forbidden,
|
|
|
|
InvalidUsage,
|
|
|
|
NotFound,
|
2021-04-06 20:20:25 +01:00
|
|
|
SanicException,
|
2019-04-23 22:44:42 +01:00
|
|
|
ServerError,
|
|
|
|
Unauthorized,
|
2021-04-06 20:20:25 +01:00
|
|
|
abort,
|
2019-04-23 22:44:42 +01:00
|
|
|
)
|
2016-10-14 23:36:58 +01:00
|
|
|
from sanic.response import text
|
2021-09-29 11:09:23 +01:00
|
|
|
from websockets.version import version as websockets_version
|
2016-10-14 11:37:40 +01:00
|
|
|
|
|
|
|
|
2016-12-30 19:50:12 +00:00
|
|
|
class SanicExceptionTestException(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
@pytest.fixture(scope="module")
|
2016-12-30 19:50:12 +00:00
|
|
|
def exception_app():
|
2018-12-30 11:18:06 +00:00
|
|
|
app = Sanic("test_exceptions")
|
2016-10-14 11:37:40 +01:00
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
@app.route("/")
|
2016-12-30 19:50:12 +00:00
|
|
|
def handler(request):
|
2018-12-30 11:18:06 +00:00
|
|
|
return text("OK")
|
2016-10-14 11:37:40 +01:00
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
@app.route("/error")
|
2016-12-30 19:50:12 +00:00
|
|
|
def handler_error(request):
|
|
|
|
raise ServerError("OK")
|
2016-10-14 11:37:40 +01:00
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
@app.route("/404")
|
2016-12-30 19:50:12 +00:00
|
|
|
def handler_404(request):
|
|
|
|
raise NotFound("OK")
|
2016-10-14 11:37:40 +01:00
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
@app.route("/403")
|
2017-06-23 15:44:57 +01:00
|
|
|
def handler_403(request):
|
|
|
|
raise Forbidden("Forbidden")
|
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
@app.route("/401")
|
2017-08-24 15:46:39 +01:00
|
|
|
def handler_401(request):
|
|
|
|
raise Unauthorized("Unauthorized")
|
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
@app.route("/401/basic")
|
2017-06-23 15:12:15 +01:00
|
|
|
def handler_401_basic(request):
|
2017-08-24 15:46:39 +01:00
|
|
|
raise Unauthorized("Unauthorized", scheme="Basic", realm="Sanic")
|
2017-06-23 15:12:15 +01:00
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
@app.route("/401/digest")
|
2017-06-23 15:12:15 +01:00
|
|
|
def handler_401_digest(request):
|
2018-12-30 11:18:06 +00:00
|
|
|
raise Unauthorized(
|
|
|
|
"Unauthorized",
|
|
|
|
scheme="Digest",
|
|
|
|
realm="Sanic",
|
|
|
|
qop="auth, auth-int",
|
|
|
|
algorithm="MD5",
|
|
|
|
nonce="abcdef",
|
|
|
|
opaque="zyxwvu",
|
|
|
|
)
|
|
|
|
|
|
|
|
@app.route("/401/bearer")
|
2017-06-29 11:34:52 +01:00
|
|
|
def handler_401_bearer(request):
|
2017-08-24 15:46:39 +01:00
|
|
|
raise Unauthorized("Unauthorized", scheme="Bearer")
|
2017-06-23 15:12:15 +01:00
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
@app.route("/invalid")
|
2016-12-30 19:50:12 +00:00
|
|
|
def handler_invalid(request):
|
|
|
|
raise InvalidUsage("OK")
|
2016-10-14 11:37:40 +01:00
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
@app.route("/abort/401")
|
2017-11-08 10:44:57 +00:00
|
|
|
def handler_401_error(request):
|
2021-04-05 16:01:48 +01:00
|
|
|
raise SanicException(status_code=401)
|
2017-08-24 15:46:39 +01:00
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
@app.route("/abort")
|
2017-11-08 10:44:57 +00:00
|
|
|
def handler_500_error(request):
|
2021-04-05 16:01:48 +01:00
|
|
|
raise SanicException(status_code=500)
|
|
|
|
|
|
|
|
@app.route("/old_abort")
|
|
|
|
def handler_old_abort_error(request):
|
2017-05-20 22:43:57 +01:00
|
|
|
abort(500)
|
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
@app.route("/abort/message")
|
2018-12-13 17:50:50 +00:00
|
|
|
def handler_abort_message(request):
|
2021-04-05 16:01:48 +01:00
|
|
|
raise SanicException(message="Custom Message", status_code=500)
|
2018-12-13 17:50:50 +00:00
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
@app.route("/divide_by_zero")
|
2016-12-30 19:50:12 +00:00
|
|
|
def handle_unhandled_exception(request):
|
2019-02-28 14:56:41 +00:00
|
|
|
_ = 1 / 0
|
2016-10-14 11:37:40 +01:00
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
@app.route("/error_in_error_handler_handler")
|
2016-12-30 19:50:12 +00:00
|
|
|
def custom_error_handler(request):
|
2018-12-30 11:18:06 +00:00
|
|
|
raise SanicExceptionTestException("Dummy message!")
|
2016-10-14 11:37:40 +01:00
|
|
|
|
2016-12-30 19:50:12 +00:00
|
|
|
@app.exception(SanicExceptionTestException)
|
|
|
|
def error_in_error_handler_handler(request, exception):
|
2019-02-28 14:56:41 +00:00
|
|
|
_ = 1 / 0
|
2016-10-14 11:37:40 +01:00
|
|
|
|
2016-12-30 19:50:12 +00:00
|
|
|
return app
|
2016-10-14 11:37:40 +01:00
|
|
|
|
2017-06-23 15:12:15 +01:00
|
|
|
|
2018-08-26 15:43:14 +01:00
|
|
|
def test_catch_exception_list(app):
|
2017-03-08 00:22:23 +00:00
|
|
|
@app.exception([SanicExceptionTestException, NotFound])
|
|
|
|
def exception_list(request, exception):
|
|
|
|
return text("ok")
|
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
@app.route("/")
|
2017-03-08 00:22:23 +00:00
|
|
|
def exception(request):
|
|
|
|
raise SanicExceptionTestException("You won't see me")
|
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
request, response = app.test_client.get("/random")
|
|
|
|
assert response.text == "ok"
|
2017-03-08 00:22:23 +00:00
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
request, response = app.test_client.get("/")
|
|
|
|
assert response.text == "ok"
|
2016-10-14 11:37:40 +01:00
|
|
|
|
2017-05-21 03:30:08 +01:00
|
|
|
|
2016-12-30 19:50:12 +00:00
|
|
|
def test_no_exception(exception_app):
|
|
|
|
"""Test that a route works without an exception"""
|
2018-12-30 11:18:06 +00:00
|
|
|
request, response = exception_app.test_client.get("/")
|
2016-10-14 23:36:58 +01:00
|
|
|
assert response.status == 200
|
2018-12-30 11:18:06 +00:00
|
|
|
assert response.text == "OK"
|
2016-10-14 11:37:40 +01:00
|
|
|
|
|
|
|
|
2016-12-30 19:50:12 +00:00
|
|
|
def test_server_error_exception(exception_app):
|
|
|
|
"""Test the built-in ServerError exception works"""
|
2018-12-30 11:18:06 +00:00
|
|
|
request, response = exception_app.test_client.get("/error")
|
2016-10-14 23:36:58 +01:00
|
|
|
assert response.status == 500
|
2016-10-14 11:37:40 +01:00
|
|
|
|
|
|
|
|
2017-05-21 03:30:08 +01:00
|
|
|
def test_invalid_usage_exception(exception_app):
|
2016-12-30 19:50:12 +00:00
|
|
|
"""Test the built-in InvalidUsage exception works"""
|
2018-12-30 11:18:06 +00:00
|
|
|
request, response = exception_app.test_client.get("/invalid")
|
2017-05-21 03:30:08 +01:00
|
|
|
assert response.status == 400
|
2016-10-14 23:36:58 +01:00
|
|
|
|
|
|
|
|
2016-12-30 19:50:12 +00:00
|
|
|
def test_not_found_exception(exception_app):
|
|
|
|
"""Test the built-in NotFound exception works"""
|
2018-12-30 11:18:06 +00:00
|
|
|
request, response = exception_app.test_client.get("/404")
|
2016-10-14 23:36:58 +01:00
|
|
|
assert response.status == 404
|
2016-12-30 19:50:12 +00:00
|
|
|
|
|
|
|
|
2017-06-23 15:44:57 +01:00
|
|
|
def test_forbidden_exception(exception_app):
|
|
|
|
"""Test the built-in Forbidden exception"""
|
2018-12-30 11:18:06 +00:00
|
|
|
request, response = exception_app.test_client.get("/403")
|
2017-06-23 15:44:57 +01:00
|
|
|
assert response.status == 403
|
|
|
|
|
2017-07-27 22:00:27 +01:00
|
|
|
|
2017-06-23 15:12:15 +01:00
|
|
|
def test_unauthorized_exception(exception_app):
|
|
|
|
"""Test the built-in Unauthorized exception"""
|
2018-12-30 11:18:06 +00:00
|
|
|
request, response = exception_app.test_client.get("/401")
|
2017-08-24 15:46:39 +01:00
|
|
|
assert response.status == 401
|
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
request, response = exception_app.test_client.get("/401/basic")
|
2017-06-23 15:12:15 +01:00
|
|
|
assert response.status == 401
|
2018-12-30 11:18:06 +00:00
|
|
|
assert response.headers.get("WWW-Authenticate") is not None
|
|
|
|
assert response.headers.get("WWW-Authenticate") == 'Basic realm="Sanic"'
|
2017-06-23 15:12:15 +01:00
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
request, response = exception_app.test_client.get("/401/digest")
|
2017-06-23 15:12:15 +01:00
|
|
|
assert response.status == 401
|
2017-07-27 22:00:27 +01:00
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
auth_header = response.headers.get("WWW-Authenticate")
|
2017-06-23 15:12:15 +01:00
|
|
|
assert auth_header is not None
|
2018-12-30 11:18:06 +00:00
|
|
|
assert auth_header.startswith("Digest")
|
2017-12-22 01:54:57 +00:00
|
|
|
assert 'qop="auth, auth-int"' in auth_header
|
|
|
|
assert 'algorithm="MD5"' in auth_header
|
|
|
|
assert 'nonce="abcdef"' in auth_header
|
|
|
|
assert 'opaque="zyxwvu"' in auth_header
|
2017-06-23 15:12:15 +01:00
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
request, response = exception_app.test_client.get("/401/bearer")
|
2017-06-29 11:34:52 +01:00
|
|
|
assert response.status == 401
|
2018-12-30 11:18:06 +00:00
|
|
|
assert response.headers.get("WWW-Authenticate") == "Bearer"
|
2017-06-29 11:34:52 +01:00
|
|
|
|
2017-06-23 15:12:15 +01:00
|
|
|
|
2016-12-30 19:50:12 +00:00
|
|
|
def test_handled_unhandled_exception(exception_app):
|
|
|
|
"""Test that an exception not built into sanic is handled"""
|
2018-12-30 11:18:06 +00:00
|
|
|
request, response = exception_app.test_client.get("/divide_by_zero")
|
2016-12-30 19:50:12 +00:00
|
|
|
assert response.status == 500
|
2018-12-30 11:18:06 +00:00
|
|
|
soup = BeautifulSoup(response.body, "html.parser")
|
2020-01-20 14:58:14 +00:00
|
|
|
assert "Internal Server Error" in soup.h1.text
|
2017-01-13 00:54:34 +00:00
|
|
|
|
|
|
|
message = " ".join(soup.p.text.split())
|
|
|
|
assert message == (
|
|
|
|
"The server encountered an internal error and "
|
2018-12-30 11:18:06 +00:00
|
|
|
"cannot complete your request."
|
|
|
|
)
|
2016-12-30 19:50:12 +00:00
|
|
|
|
2017-05-21 03:30:08 +01:00
|
|
|
|
2016-12-30 19:50:12 +00:00
|
|
|
def test_exception_in_exception_handler(exception_app):
|
|
|
|
"""Test that an exception thrown in an error handler is handled"""
|
2017-02-14 19:51:20 +00:00
|
|
|
request, response = exception_app.test_client.get(
|
2018-12-30 11:18:06 +00:00
|
|
|
"/error_in_error_handler_handler"
|
|
|
|
)
|
2016-12-30 19:50:12 +00:00
|
|
|
assert response.status == 500
|
2018-12-30 11:18:06 +00:00
|
|
|
assert response.body == b"An error occurred while handling an error"
|
2017-01-25 01:18:58 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_exception_in_exception_handler_debug_off(exception_app):
|
|
|
|
"""Test that an exception thrown in an error handler is handled"""
|
2017-02-14 19:51:20 +00:00
|
|
|
request, response = exception_app.test_client.get(
|
2018-12-30 11:18:06 +00:00
|
|
|
"/error_in_error_handler_handler", debug=False
|
|
|
|
)
|
2017-01-25 01:18:58 +00:00
|
|
|
assert response.status == 500
|
2018-12-30 11:18:06 +00:00
|
|
|
assert response.body == b"An error occurred while handling an error"
|
2017-01-25 01:18:58 +00:00
|
|
|
|
|
|
|
|
2017-11-08 10:44:57 +00:00
|
|
|
def test_exception_in_exception_handler_debug_on(exception_app):
|
2017-01-25 01:18:58 +00:00
|
|
|
"""Test that an exception thrown in an error handler is handled"""
|
2017-02-14 19:51:20 +00:00
|
|
|
request, response = exception_app.test_client.get(
|
2018-12-30 11:18:06 +00:00
|
|
|
"/error_in_error_handler_handler", debug=True
|
|
|
|
)
|
2017-01-25 01:18:58 +00:00
|
|
|
assert response.status == 500
|
2018-12-30 11:18:06 +00:00
|
|
|
assert response.body.startswith(b"Exception raised in exception ")
|
2017-05-21 03:30:08 +01:00
|
|
|
|
|
|
|
|
2021-04-05 16:01:48 +01:00
|
|
|
def test_sanic_exception(exception_app):
|
|
|
|
"""Test sanic exceptions are handled"""
|
2018-12-30 11:18:06 +00:00
|
|
|
request, response = exception_app.test_client.get("/abort/401")
|
2017-08-24 15:46:39 +01:00
|
|
|
assert response.status == 401
|
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
request, response = exception_app.test_client.get("/abort")
|
2017-05-21 03:30:08 +01:00
|
|
|
assert response.status == 500
|
2021-04-05 16:01:48 +01:00
|
|
|
# check fallback message
|
|
|
|
assert "Internal Server Error" in response.text
|
2018-12-13 17:50:50 +00:00
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
request, response = exception_app.test_client.get("/abort/message")
|
2018-12-13 17:50:50 +00:00
|
|
|
assert response.status == 500
|
2021-04-05 16:01:48 +01:00
|
|
|
assert "Custom Message" in response.text
|
|
|
|
|
|
|
|
with warnings.catch_warnings(record=True) as w:
|
|
|
|
request, response = exception_app.test_client.get("/old_abort")
|
|
|
|
assert response.status == 500
|
2021-04-06 20:20:25 +01:00
|
|
|
assert len(w) == 1 and "deprecated" in w[0].message.args[0]
|
2021-08-07 21:24:48 +01:00
|
|
|
|
|
|
|
|
2021-08-09 19:14:15 +01:00
|
|
|
def test_custom_exception_default_message(exception_app):
|
|
|
|
class TeaError(SanicException):
|
|
|
|
message = "Tempest in a teapot"
|
|
|
|
status_code = 418
|
|
|
|
|
|
|
|
exception_app.router.reset()
|
|
|
|
|
|
|
|
@exception_app.get("/tempest")
|
|
|
|
def tempest(_):
|
|
|
|
raise TeaError
|
|
|
|
|
|
|
|
_, response = exception_app.test_client.get("/tempest", debug=True)
|
|
|
|
assert response.status == 418
|
|
|
|
assert b"Tempest in a teapot" in response.body
|
|
|
|
|
|
|
|
|
2021-08-07 21:24:48 +01:00
|
|
|
def test_exception_in_ws_logged(caplog):
|
|
|
|
app = Sanic(__file__)
|
|
|
|
|
|
|
|
@app.websocket("/feed")
|
|
|
|
async def feed(request, ws):
|
|
|
|
raise Exception("...")
|
|
|
|
|
|
|
|
with caplog.at_level(logging.INFO):
|
|
|
|
app.test_client.websocket("/feed")
|
2021-09-29 11:09:23 +01:00
|
|
|
# Websockets v10.0 and above output an additional
|
|
|
|
# INFO message when a ws connection is accepted
|
|
|
|
ws_version_parts = websockets_version.split(".")
|
|
|
|
ws_major = int(ws_version_parts[0])
|
|
|
|
record_index = 2 if ws_major >= 10 else 1
|
|
|
|
assert caplog.record_tuples[record_index][0] == "sanic.error"
|
|
|
|
assert caplog.record_tuples[record_index][1] == logging.ERROR
|
2021-08-07 21:24:48 +01:00
|
|
|
assert (
|
2021-09-29 11:09:23 +01:00
|
|
|
"Exception occurred while handling uri:"
|
|
|
|
in caplog.record_tuples[record_index][2]
|
2021-08-07 21:24:48 +01:00
|
|
|
)
|