2018-12-13 17:50:50 +00:00
|
|
|
import asyncio
|
2018-12-22 15:21:45 +00:00
|
|
|
import logging
|
2019-01-15 15:27:41 +00:00
|
|
|
import sys
|
2020-09-28 22:40:24 +01:00
|
|
|
|
2019-01-15 12:52:53 +00:00
|
|
|
from inspect import isawaitable
|
2021-01-05 14:57:35 +00:00
|
|
|
from os import environ
|
2020-09-28 22:24:00 +01:00
|
|
|
from unittest.mock import patch
|
2019-04-23 22:44:42 +01:00
|
|
|
|
2018-12-13 17:50:50 +00:00
|
|
|
import pytest
|
|
|
|
|
2020-03-26 04:42:46 +00:00
|
|
|
from sanic import Sanic
|
2018-12-13 17:50:50 +00:00
|
|
|
from sanic.exceptions import SanicException
|
|
|
|
from sanic.response import text
|
|
|
|
|
|
|
|
|
2019-01-15 15:32:40 +00:00
|
|
|
def uvloop_installed():
|
|
|
|
try:
|
2019-04-23 22:44:42 +01:00
|
|
|
import uvloop # noqa
|
2019-02-06 18:29:33 +00:00
|
|
|
|
2019-01-15 15:32:40 +00:00
|
|
|
return True
|
|
|
|
except ImportError:
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
2018-12-13 17:50:50 +00:00
|
|
|
def test_app_loop_running(app):
|
2018-12-30 11:18:06 +00:00
|
|
|
@app.get("/test")
|
2018-12-13 17:50:50 +00:00
|
|
|
async def handler(request):
|
|
|
|
assert isinstance(app.loop, asyncio.AbstractEventLoop)
|
2018-12-30 11:18:06 +00:00
|
|
|
return text("pass")
|
2018-12-13 17:50:50 +00:00
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
request, response = app.test_client.get("/test")
|
|
|
|
assert response.text == "pass"
|
2018-12-13 17:50:50 +00:00
|
|
|
|
|
|
|
|
2020-09-28 22:40:24 +01:00
|
|
|
@pytest.mark.skipif(
|
|
|
|
sys.version_info < (3, 7), reason="requires python3.7 or higher"
|
|
|
|
)
|
2019-01-15 11:38:47 +00:00
|
|
|
def test_create_asyncio_server(app):
|
2019-01-15 15:45:47 +00:00
|
|
|
if not uvloop_installed():
|
|
|
|
loop = asyncio.get_event_loop()
|
2019-02-06 18:29:33 +00:00
|
|
|
asyncio_srv_coro = app.create_server(return_asyncio_server=True)
|
2019-01-15 15:45:47 +00:00
|
|
|
assert isawaitable(asyncio_srv_coro)
|
|
|
|
srv = loop.run_until_complete(asyncio_srv_coro)
|
|
|
|
assert srv.is_serving() is True
|
2019-01-15 12:52:53 +00:00
|
|
|
|
|
|
|
|
2020-09-28 22:40:24 +01:00
|
|
|
@pytest.mark.skipif(
|
|
|
|
sys.version_info < (3, 7), reason="requires python3.7 or higher"
|
|
|
|
)
|
2020-01-20 15:00:48 +00:00
|
|
|
def test_asyncio_server_no_start_serving(app):
|
2019-01-15 15:45:47 +00:00
|
|
|
if not uvloop_installed():
|
|
|
|
loop = asyncio.get_event_loop()
|
|
|
|
asyncio_srv_coro = app.create_server(
|
2020-03-26 04:42:46 +00:00
|
|
|
port=43123,
|
2019-01-15 15:45:47 +00:00
|
|
|
return_asyncio_server=True,
|
2019-02-06 18:29:33 +00:00
|
|
|
asyncio_server_kwargs=dict(start_serving=False),
|
|
|
|
)
|
2019-01-15 15:45:47 +00:00
|
|
|
srv = loop.run_until_complete(asyncio_srv_coro)
|
|
|
|
assert srv.is_serving() is False
|
2019-01-15 11:38:47 +00:00
|
|
|
|
2020-06-05 15:14:18 +01:00
|
|
|
|
2020-09-28 22:40:24 +01:00
|
|
|
@pytest.mark.skipif(
|
|
|
|
sys.version_info < (3, 7), reason="requires python3.7 or higher"
|
|
|
|
)
|
2020-01-20 15:00:48 +00:00
|
|
|
def test_asyncio_server_start_serving(app):
|
|
|
|
if not uvloop_installed():
|
|
|
|
loop = asyncio.get_event_loop()
|
|
|
|
asyncio_srv_coro = app.create_server(
|
2020-03-26 04:42:46 +00:00
|
|
|
port=43124,
|
2020-01-20 15:00:48 +00:00
|
|
|
return_asyncio_server=True,
|
|
|
|
asyncio_server_kwargs=dict(start_serving=False),
|
|
|
|
)
|
|
|
|
srv = loop.run_until_complete(asyncio_srv_coro)
|
|
|
|
assert srv.is_serving() is False
|
|
|
|
loop.run_until_complete(srv.start_serving())
|
|
|
|
assert srv.is_serving() is True
|
2020-03-28 18:43:14 +00:00
|
|
|
wait_close = srv.close()
|
|
|
|
loop.run_until_complete(wait_close)
|
2020-01-20 15:00:48 +00:00
|
|
|
# Looks like we can't easily test `serve_forever()`
|
2019-01-15 11:38:47 +00:00
|
|
|
|
2020-06-05 15:14:18 +01:00
|
|
|
|
2018-12-13 17:50:50 +00:00
|
|
|
def test_app_loop_not_running(app):
|
|
|
|
with pytest.raises(SanicException) as excinfo:
|
2019-06-04 08:58:00 +01:00
|
|
|
app.loop
|
2018-12-13 17:50:50 +00:00
|
|
|
|
|
|
|
assert str(excinfo.value) == (
|
2018-12-30 11:18:06 +00:00
|
|
|
"Loop can only be retrieved after the app has started "
|
|
|
|
"running. Not supported with `create_server` function"
|
2018-12-13 17:50:50 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def test_app_run_raise_type_error(app):
|
|
|
|
|
|
|
|
with pytest.raises(TypeError) as excinfo:
|
2018-12-30 11:18:06 +00:00
|
|
|
app.run(loop="loop")
|
2018-12-13 17:50:50 +00:00
|
|
|
|
|
|
|
assert str(excinfo.value) == (
|
2018-12-30 11:18:06 +00:00
|
|
|
"loop is not a valid argument. To use an existing loop, "
|
|
|
|
"change to create_server().\nSee more: "
|
|
|
|
"https://sanic.readthedocs.io/en/latest/sanic/deploying.html"
|
|
|
|
"#asynchronous-support"
|
2018-12-13 17:50:50 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def test_app_route_raise_value_error(app):
|
|
|
|
|
|
|
|
with pytest.raises(ValueError) as excinfo:
|
2018-12-30 11:18:06 +00:00
|
|
|
|
|
|
|
@app.route("/test")
|
2018-12-13 17:50:50 +00:00
|
|
|
async def handler():
|
2018-12-30 11:18:06 +00:00
|
|
|
return text("test")
|
2018-12-13 17:50:50 +00:00
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
assert (
|
|
|
|
str(excinfo.value)
|
|
|
|
== "Required parameter `request` missing in the handler() route?"
|
|
|
|
)
|
2018-12-13 17:50:50 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_app_handle_request_handler_is_none(app, monkeypatch):
|
|
|
|
def mockreturn(*args, **kwargs):
|
Streaming Server (#1876)
* Streaming request by async for.
* Make all requests streaming and preload body for non-streaming handlers.
* Cleanup of code and avoid mixing streaming responses.
* Async http protocol loop.
* Change of test: don't require early bad request error but only after CRLF-CRLF.
* Add back streaming requests.
* Rewritten request body parser.
* Misc. cleanup, down to 4 failing tests.
* All tests OK.
* Entirely remove request body queue.
* Let black f*ckup the layout
* Better testing error messages on protocol errors.
* Remove StreamBuffer tests because the type is about to be removed.
* Remove tests using the deprecated get_headers function that can no longer be supported. Chunked mode is now autodetected, so do not put content-length header if chunked mode is preferred.
* Major refactoring of HTTP protocol handling (new module http.py added), all requests made streaming. A few compatibility issues and a lot of cleanup to be done remain, 16 tests failing.
* Terminate check_timeouts once connection_task finishes.
* Code cleanup, 14 tests failing.
* Much cleanup, 12 failing...
* Even more cleanup and error checking, 8 failing tests.
* Remove keep-alive header from responses. First of all, it should say timeout=<value> which wasn't the case with existing implementation, and secondly none of the other web servers I tried include this header.
* Everything but CustomServer OK.
* Linter
* Disable custom protocol test
* Remove unnecessary variables, optimise performance.
* A test was missing that body_init/body_push/body_finish are never called. Rewritten using receive_body and case switching to make it fail if bypassed.
* Minor fixes.
* Remove unused code.
* Py 3.8 check for deprecated loop argument.
* Fix a middleware cancellation handling test with py38.
* Linter 'n fixes
* Typing
* Stricter handling of request header size
* More specific error messages on Payload Too Large.
* Init http.response = None
* Messages further tuned.
* Always try to consume request body, plus minor cleanup.
* Add a missing check in case of close_if_idle on a dead connection.
* Avoid error messages on PayloadTooLarge.
* Add test for new API.
* json takes str, not bytes
* Default to no maximum request size for streaming handlers.
* Fix chunked mode crash.
* Header values should be strictly ASCII but both UTF-8 and Latin-1 exist. Use UTF-8B to
cope with all.
* Refactoring and cleanup.
* Unify response header processing of ASGI and asyncio modes.
* Avoid special handling of StreamingHTTPResponse.
* 35 % speedup in HTTP/1.1 response formatting (not so much overall effect).
* Duplicate set-cookie headers were being produced.
* Cleanup processed_headers some more.
* Linting
* Import ordering
* Response middleware ran by async request.respond().
* Need to check if transport is closing to avoid getting stuck in sending loops after peer has disconnected.
* Middleware and error handling refactoring.
* Linter
* Fix tracking of HTTP stage when writing to transport fails.
* Add clarifying comment
* Add a check for request body functions and a test for NotImplementedError.
* Linter and typing
* These must be tuples + hack mypy warnings away.
* New streaming test and minor fixes.
* Constant receive buffer size.
* 256 KiB send and receive buffers.
* Revert "256 KiB send and receive buffers."
This reverts commit abc1e3edb21a5e6925fa4c856657559608a8d65b.
* app.handle_exception already sends the response.
* Improved handling of errors during request.
* An odd hack to avoid an httpx limitation that causes test failures.
* Limit request header size to 8 KiB at most.
* Remove unnecessary use of format string.
* Cleanup tests
* Remove artifact
* Fix type checking
* Mark test for skipping
* Cleanup some edge cases
* Add ignore_body flag to safe methods
* Add unit tests for timeout logic
* Add unit tests for timeout logic
* Fix Mock usage in timeout test
* Change logging test to only logger in handler
* Windows py3.8 logging issue with current testing client
* Add test_header_size_exceeded
* Resolve merge conflicts
* Add request middleware to hard exception handling
* Add request middleware to hard exception handling
* Request middleware on exception handlers
* Linting
* Cleanup deprecations
Co-authored-by: L. Kärkkäinen <tronic@users.noreply.github.com>
Co-authored-by: Adam Hopkins <admhpkns@gmail.com>
2021-01-10 22:45:36 +00:00
|
|
|
return None, [], {}, "", "", None, False
|
2018-12-13 17:50:50 +00:00
|
|
|
|
|
|
|
# Not sure how to make app.router.get() return None, so use mock here.
|
2018-12-30 11:18:06 +00:00
|
|
|
monkeypatch.setattr(app.router, "get", mockreturn)
|
2018-12-13 17:50:50 +00:00
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
@app.get("/test")
|
2018-12-13 17:50:50 +00:00
|
|
|
def handler(request):
|
2018-12-30 11:18:06 +00:00
|
|
|
return text("test")
|
2018-12-13 17:50:50 +00:00
|
|
|
|
2020-09-30 13:11:27 +01:00
|
|
|
_, response = app.test_client.get("/test")
|
2018-12-13 17:50:50 +00:00
|
|
|
|
2020-06-05 15:14:18 +01:00
|
|
|
assert (
|
|
|
|
"'None' was returned while requesting a handler from the router"
|
|
|
|
in response.text
|
|
|
|
)
|
2018-12-13 17:50:50 +00:00
|
|
|
|
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
@pytest.mark.parametrize("websocket_enabled", [True, False])
|
|
|
|
@pytest.mark.parametrize("enable", [True, False])
|
2018-12-22 15:21:45 +00:00
|
|
|
def test_app_enable_websocket(app, websocket_enabled, enable):
|
2018-12-13 17:50:50 +00:00
|
|
|
app.websocket_enabled = websocket_enabled
|
|
|
|
app.enable_websocket(enable=enable)
|
|
|
|
|
|
|
|
assert app.websocket_enabled == enable
|
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
@app.websocket("/ws")
|
2018-12-13 17:50:50 +00:00
|
|
|
async def handler(request, ws):
|
2018-12-30 11:18:06 +00:00
|
|
|
await ws.send("test")
|
2018-12-13 17:50:50 +00:00
|
|
|
|
|
|
|
assert app.websocket_enabled == True
|
2018-12-22 15:21:45 +00:00
|
|
|
|
|
|
|
|
2020-08-07 04:37:59 +01:00
|
|
|
@patch("sanic.app.WebSocketProtocol")
|
|
|
|
def test_app_websocket_parameters(websocket_protocol_mock, app):
|
|
|
|
app.config.WEBSOCKET_MAX_SIZE = 44
|
|
|
|
app.config.WEBSOCKET_MAX_QUEUE = 45
|
|
|
|
app.config.WEBSOCKET_READ_LIMIT = 46
|
|
|
|
app.config.WEBSOCKET_WRITE_LIMIT = 47
|
|
|
|
app.config.WEBSOCKET_PING_TIMEOUT = 48
|
|
|
|
app.config.WEBSOCKET_PING_INTERVAL = 50
|
|
|
|
|
|
|
|
@app.websocket("/ws")
|
|
|
|
async def handler(request, ws):
|
|
|
|
await ws.send("test")
|
|
|
|
|
|
|
|
try:
|
|
|
|
# This will fail because WebSocketProtocol is mocked and only the call kwargs matter
|
|
|
|
app.test_client.get("/ws")
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
|
|
|
|
websocket_protocol_call_args = websocket_protocol_mock.call_args
|
|
|
|
ws_kwargs = websocket_protocol_call_args[1]
|
2020-09-28 22:24:00 +01:00
|
|
|
assert ws_kwargs["websocket_max_size"] == app.config.WEBSOCKET_MAX_SIZE
|
|
|
|
assert ws_kwargs["websocket_max_queue"] == app.config.WEBSOCKET_MAX_QUEUE
|
|
|
|
assert ws_kwargs["websocket_read_limit"] == app.config.WEBSOCKET_READ_LIMIT
|
2020-09-28 22:40:24 +01:00
|
|
|
assert (
|
|
|
|
ws_kwargs["websocket_write_limit"] == app.config.WEBSOCKET_WRITE_LIMIT
|
|
|
|
)
|
|
|
|
assert (
|
|
|
|
ws_kwargs["websocket_ping_timeout"]
|
|
|
|
== app.config.WEBSOCKET_PING_TIMEOUT
|
|
|
|
)
|
|
|
|
assert (
|
|
|
|
ws_kwargs["websocket_ping_interval"]
|
|
|
|
== app.config.WEBSOCKET_PING_INTERVAL
|
|
|
|
)
|
2020-08-07 04:37:59 +01:00
|
|
|
|
|
|
|
|
2018-12-22 15:21:45 +00:00
|
|
|
def test_handle_request_with_nested_exception(app, monkeypatch):
|
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
err_msg = "Mock Exception"
|
2018-12-22 15:21:45 +00:00
|
|
|
|
|
|
|
# Not sure how to raise an exception in app.error_handler.response(), use mock here
|
|
|
|
def mock_error_handler_response(*args, **kwargs):
|
|
|
|
raise Exception(err_msg)
|
|
|
|
|
2020-09-28 22:40:24 +01:00
|
|
|
monkeypatch.setattr(
|
|
|
|
app.error_handler, "response", mock_error_handler_response
|
|
|
|
)
|
2018-12-22 15:21:45 +00:00
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
@app.get("/")
|
2018-12-22 15:21:45 +00:00
|
|
|
def handler(request):
|
|
|
|
raise Exception
|
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
request, response = app.test_client.get("/")
|
2018-12-22 15:21:45 +00:00
|
|
|
assert response.status == 500
|
2018-12-30 11:18:06 +00:00
|
|
|
assert response.text == "An error occurred while handling an error"
|
2018-12-22 15:21:45 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_handle_request_with_nested_exception_debug(app, monkeypatch):
|
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
err_msg = "Mock Exception"
|
2018-12-22 15:21:45 +00:00
|
|
|
|
|
|
|
# Not sure how to raise an exception in app.error_handler.response(), use mock here
|
|
|
|
def mock_error_handler_response(*args, **kwargs):
|
|
|
|
raise Exception(err_msg)
|
|
|
|
|
2020-09-28 22:40:24 +01:00
|
|
|
monkeypatch.setattr(
|
|
|
|
app.error_handler, "response", mock_error_handler_response
|
|
|
|
)
|
2018-12-22 15:21:45 +00:00
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
@app.get("/")
|
2018-12-22 15:21:45 +00:00
|
|
|
def handler(request):
|
|
|
|
raise Exception
|
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
request, response = app.test_client.get("/", debug=True)
|
2018-12-22 15:21:45 +00:00
|
|
|
assert response.status == 500
|
|
|
|
assert response.text.startswith(
|
2020-02-25 20:01:13 +00:00
|
|
|
f"Error while handling error: {err_msg}\nStack: Traceback (most recent call last):\n"
|
2018-12-22 15:21:45 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def test_handle_request_with_nested_sanic_exception(app, monkeypatch, caplog):
|
|
|
|
|
|
|
|
# Not sure how to raise an exception in app.error_handler.response(), use mock here
|
|
|
|
def mock_error_handler_response(*args, **kwargs):
|
2018-12-30 11:18:06 +00:00
|
|
|
raise SanicException("Mock SanicException")
|
2018-12-22 15:21:45 +00:00
|
|
|
|
2020-09-28 22:40:24 +01:00
|
|
|
monkeypatch.setattr(
|
|
|
|
app.error_handler, "response", mock_error_handler_response
|
|
|
|
)
|
2018-12-22 15:21:45 +00:00
|
|
|
|
2018-12-30 11:18:06 +00:00
|
|
|
@app.get("/")
|
2018-12-22 15:21:45 +00:00
|
|
|
def handler(request):
|
|
|
|
raise Exception
|
|
|
|
|
|
|
|
with caplog.at_level(logging.ERROR):
|
2018-12-30 11:18:06 +00:00
|
|
|
request, response = app.test_client.get("/")
|
2020-03-26 04:42:46 +00:00
|
|
|
port = request.server_port
|
|
|
|
assert port > 0
|
2018-12-22 15:21:45 +00:00
|
|
|
assert response.status == 500
|
2020-01-20 14:58:14 +00:00
|
|
|
assert "Mock SanicException" in response.text
|
2019-01-08 15:15:23 +00:00
|
|
|
assert (
|
2018-12-30 11:18:06 +00:00
|
|
|
"sanic.root",
|
2018-12-22 15:21:45 +00:00
|
|
|
logging.ERROR,
|
2020-03-26 04:42:46 +00:00
|
|
|
f"Exception occurred while handling uri: 'http://127.0.0.1:{port}/'",
|
2019-01-08 15:15:23 +00:00
|
|
|
) in caplog.record_tuples
|
2020-03-26 04:42:46 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_app_name_required():
|
2020-12-28 20:47:31 +00:00
|
|
|
with pytest.raises(SanicException):
|
2020-03-26 04:42:46 +00:00
|
|
|
Sanic()
|
2020-07-09 12:52:58 +01:00
|
|
|
|
|
|
|
|
2020-07-13 21:59:45 +01:00
|
|
|
def test_app_has_test_mode_sync():
|
|
|
|
app = Sanic("test")
|
|
|
|
|
2020-07-09 12:52:58 +01:00
|
|
|
@app.get("/")
|
|
|
|
def handler(request):
|
|
|
|
assert request.app.test_mode
|
|
|
|
return text("test")
|
|
|
|
|
|
|
|
_, response = app.test_client.get("/")
|
|
|
|
assert response.status == 200
|
|
|
|
|
|
|
|
|
2020-12-28 20:47:31 +00:00
|
|
|
def test_app_registry():
|
|
|
|
instance = Sanic("test")
|
|
|
|
assert Sanic._app_registry["test"] is instance
|
2020-07-09 12:52:58 +01:00
|
|
|
|
2020-07-13 21:59:45 +01:00
|
|
|
|
2020-12-28 20:47:31 +00:00
|
|
|
def test_app_registry_wrong_type():
|
|
|
|
with pytest.raises(SanicException):
|
|
|
|
Sanic.register_app(1)
|
|
|
|
|
|
|
|
|
|
|
|
def test_app_registry_name_reuse():
|
|
|
|
Sanic("test")
|
|
|
|
Sanic.test_mode = False
|
|
|
|
with pytest.raises(SanicException):
|
|
|
|
Sanic("test")
|
|
|
|
Sanic.test_mode = True
|
2021-01-05 14:57:35 +00:00
|
|
|
Sanic("test")
|
2020-12-28 20:47:31 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_app_registry_retrieval():
|
|
|
|
instance = Sanic("test")
|
|
|
|
assert Sanic.get_app("test") is instance
|
|
|
|
|
|
|
|
|
|
|
|
def test_get_app_does_not_exist():
|
|
|
|
with pytest.raises(SanicException):
|
|
|
|
Sanic.get_app("does-not-exist")
|
|
|
|
|
|
|
|
|
|
|
|
def test_get_app_does_not_exist_force_create():
|
|
|
|
assert isinstance(
|
|
|
|
Sanic.get_app("does-not-exist", force_create=True), Sanic
|
|
|
|
)
|
2021-01-05 14:57:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_app_no_registry():
|
|
|
|
Sanic("no-register", register=False)
|
|
|
|
with pytest.raises(SanicException):
|
|
|
|
Sanic.get_app("no-register")
|
|
|
|
|
|
|
|
|
|
|
|
def test_app_no_registry_env():
|
|
|
|
environ["SANIC_REGISTER"] = "False"
|
|
|
|
Sanic("no-register")
|
|
|
|
with pytest.raises(SanicException):
|
|
|
|
Sanic.get_app("no-register")
|
|
|
|
del environ["SANIC_REGISTER"]
|