Merge branch 'main' into zhiwei/route-overwrite

This commit is contained in:
Zhiwei
2023-03-29 00:12:04 -04:00
committed by GitHub
53 changed files with 1774 additions and 1014 deletions

View File

@@ -49,6 +49,6 @@ def create_app_with_args(args):
try:
logger.info(f"foo={args.foo}")
except AttributeError:
logger.info(f"module={args.module}")
logger.info(f"target={args.target}")
return app

View File

@@ -11,7 +11,7 @@ from aioquic.quic.events import ProtocolNegotiated
from sanic import Request, Sanic
from sanic.compat import Header
from sanic.config import DEFAULT_CONFIG
from sanic.exceptions import PayloadTooLarge
from sanic.exceptions import BadRequest, PayloadTooLarge
from sanic.http.constants import Stage
from sanic.http.http3 import Http3, HTTPReceiver
from sanic.models.server_types import ConnInfo
@@ -292,3 +292,48 @@ def test_request_conn_info(app):
receiver = http3.get_receiver_by_stream_id(1)
assert isinstance(receiver.request.conn_info, ConnInfo)
def test_request_header_encoding(app):
protocol = generate_protocol(app)
http3 = Http3(protocol, protocol.transmit)
with pytest.raises(BadRequest) as exc_info:
http3.http_event_received(
HeadersReceived(
[
(b":method", b"GET"),
(b":path", b"/location"),
(b":scheme", b"https"),
(b":authority", b"localhost:8443"),
("foo\u00A0".encode(), b"bar"),
],
1,
False,
)
)
assert exc_info.value.status_code == 400
assert (
str(exc_info.value)
== "Header names may only contain US-ASCII characters."
)
def test_request_url_encoding(app):
protocol = generate_protocol(app)
http3 = Http3(protocol, protocol.transmit)
with pytest.raises(BadRequest) as exc_info:
http3.http_event_received(
HeadersReceived(
[
(b":method", b"GET"),
(b":path", b"/location\xA0"),
(b":scheme", b"https"),
(b":authority", b"localhost:8443"),
(b"foo", b"bar"),
],
1,
False,
)
)
assert exc_info.value.status_code == 400
assert str(exc_info.value) == "URL may only contain US-ASCII characters."

View File

@@ -448,7 +448,7 @@ def test_custom_context():
@pytest.mark.parametrize("use", (False, True))
def test_uvloop_config(app: Sanic, monkeypatch, use):
@app.get("/test")
@app.get("/test", name="test")
def handler(request):
return text("ok")
@@ -571,21 +571,6 @@ def test_cannot_run_single_process_and_workers_or_auto_reload(
app.run(single_process=True, **extra)
def test_cannot_run_single_process_and_legacy(app: Sanic):
message = "Cannot run single process and legacy mode"
with pytest.raises(RuntimeError, match=message):
app.run(single_process=True, legacy=True)
def test_cannot_run_without_sys_signals_with_workers(app: Sanic):
message = (
"Cannot run Sanic.serve with register_sys_signals=False. "
"Use either Sanic.serve_single or Sanic.serve_legacy."
)
with pytest.raises(RuntimeError, match=message):
app.run(register_sys_signals=False, single_process=False, legacy=False)
def test_default_configure_logging():
with patch("sanic.app.logging") as mock:
Sanic("Test")

View File

@@ -652,3 +652,17 @@ async def test_asgi_headers_decoding(app: Sanic, monkeypatch: MonkeyPatch):
_, response = await app.asgi_client.get("/", headers={"Test-Header": "😅"})
assert response.status_code == 200
@pytest.mark.asyncio
async def test_asgi_url_decoding(app):
@app.get("/dir/<name>", unquote=True)
def _request(request: Request, name):
return text(name)
# 2F should not become a path separator (unquoted later)
_, response = await app.asgi_client.get("/dir/some%2Fpath")
assert response.text == "some/path"
_, response = await app.asgi_client.get("/dir/some%F0%9F%98%80path")
assert response.text == "some😀path"

View File

@@ -67,6 +67,14 @@ def test_bp_copy(app: Sanic):
_, response = app.test_client.get("/version6/page")
assert "Hello world!" in response.text
route_names = [route.name for route in app.router.routes]
assert "test_bp_copy.test_bp1.handle_request" in route_names
assert "test_bp_copy.test_bp2.handle_request" in route_names
assert "test_bp_copy.test_bp3.handle_request" in route_names
assert "test_bp_copy.test_bp4.handle_request" in route_names
assert "test_bp_copy.test_bp5.handle_request" in route_names
assert "test_bp_copy.test_bp6.handle_request" in route_names
def test_bp_copy_with_route_overwriting(app: Sanic):
bpv1 = Blueprint("bp_v1", version=1)

View File

@@ -303,6 +303,10 @@ def test_bp_with_host_list(app: Sanic):
assert response.text == "Hello subdomain!"
route_names = [r.name for r in app.router.routes]
assert "test_bp_with_host_list.test_bp_host.handler1" in route_names
assert "test_bp_with_host_list.test_bp_host.handler2" in route_names
def test_several_bp_with_host_list(app: Sanic):
bp = Blueprint(

View File

@@ -43,8 +43,10 @@ def read_app_info(lines: List[str]):
"appname,extra",
(
("fake.server.app", None),
("fake.server", None),
("fake.server:create_app", "--factory"),
("fake.server.create_app()", None),
("fake.server.create_app", None),
),
)
def test_server_run(
@@ -60,14 +62,17 @@ def test_server_run(
assert "Goin' Fast @ http://127.0.0.1:8000" in lines
def test_server_run_factory_with_args(caplog):
command = [
"fake.server.create_app_with_args",
"--factory",
]
@pytest.mark.parametrize(
"command",
(
["fake.server.create_app_with_args", "--factory"],
["fake.server.create_app_with_args"],
),
)
def test_server_run_factory_with_args(caplog, command):
lines = capture(command, caplog)
assert "module=fake.server.create_app_with_args" in lines
assert "target=fake.server.create_app_with_args" in lines
def test_server_run_factory_with_args_arbitrary(caplog):
@@ -81,25 +86,6 @@ def test_server_run_factory_with_args_arbitrary(caplog):
assert "foo=bar" in lines
def test_error_with_function_as_instance_without_factory_arg(caplog):
command = ["fake.server.create_app"]
lines = capture(command, caplog)
assert (
"Failed to run app: Module is not a Sanic app, it is a function\n "
"If this callable returns a Sanic instance try: \n"
"sanic fake.server.create_app --factory"
) in lines
def test_error_with_path_as_instance_without_simple_arg(caplog):
command = ["./fake/"]
lines = capture(command, caplog)
assert (
"Failed to run app: App not found.\n Please use --simple if you "
"are passing a directory to sanic.\n eg. sanic ./fake/ --simple"
) in lines
@pytest.mark.parametrize(
"cmd",
(

View File

@@ -1,11 +1,16 @@
from datetime import datetime, timedelta
from http.cookies import SimpleCookie
from unittest.mock import Mock
import pytest
from sanic import Sanic
from sanic.cookies import Cookie
from sanic import Request, Sanic
from sanic.compat import Header
from sanic.cookies import Cookie, CookieJar
from sanic.cookies.request import CookieRequestParameters
from sanic.exceptions import ServerError
from sanic.response import text
from sanic.response.convenience import json
# ------------------------------------------------------------ #
@@ -111,21 +116,23 @@ def test_cookie_options(app):
def test_cookie_deletion(app):
cookie_jar = None
@app.route("/")
def handler(request):
nonlocal cookie_jar
response = text("OK")
del response.cookies["i_want_to_die"]
response.cookies["i_never_existed"] = "testing"
del response.cookies["i_never_existed"]
del response.cookies["one"]
response.cookies["two"] = "testing"
del response.cookies["two"]
cookie_jar = response.cookies
return response
request, response = app.test_client.get("/")
response_cookies = SimpleCookie()
response_cookies.load(response.headers.get("Set-Cookie", {}))
_, response = app.test_client.get("/")
assert int(response_cookies["i_want_to_die"]["max-age"]) == 0
with pytest.raises(KeyError):
response.cookies["i_never_existed"]
assert cookie_jar.get_cookie("one").max_age == 0
assert cookie_jar.get_cookie("two").max_age == 0
assert len(response.cookies) == 0
def test_cookie_reserved_cookie():
@@ -252,3 +259,262 @@ def test_cookie_expires_illegal_instance_type(expires):
with pytest.raises(expected_exception=TypeError) as e:
c["expires"] = expires
assert e.message == "Cookie 'expires' property must be a datetime"
@pytest.mark.parametrize("value", ("foo=one; foo=two", "foo=one;foo=two"))
def test_request_with_duplicate_cookie_key(value):
headers = Header({"Cookie": value})
request = Request(b"/", headers, "1.1", "GET", Mock(), Mock())
assert request.cookies["foo"] == "one"
assert request.cookies.get("foo") == "one"
assert request.cookies.getlist("foo") == ["one", "two"]
assert request.cookies.get("bar") is None
def test_cookie_jar_cookies():
headers = Header()
jar = CookieJar(headers)
jar.add_cookie("foo", "one")
jar.add_cookie("foo", "two", domain="example.com")
assert len(jar.cookies) == 2
assert len(headers) == 2
def test_cookie_jar_has_cookie():
headers = Header()
jar = CookieJar(headers)
jar.add_cookie("foo", "one")
jar.add_cookie("foo", "two", domain="example.com")
assert jar.has_cookie("foo")
assert jar.has_cookie("foo", domain="example.com")
assert not jar.has_cookie("foo", path="/unknown")
assert not jar.has_cookie("bar")
def test_cookie_jar_get_cookie():
headers = Header()
jar = CookieJar(headers)
cookie1 = jar.add_cookie("foo", "one")
cookie2 = jar.add_cookie("foo", "two", domain="example.com")
assert jar.get_cookie("foo") is cookie1
assert jar.get_cookie("foo", domain="example.com") is cookie2
assert jar.get_cookie("foo", path="/unknown") is None
assert jar.get_cookie("bar") is None
def test_cookie_jar_add_cookie_encode():
headers = Header()
jar = CookieJar(headers)
jar.add_cookie("foo", "one")
jar.add_cookie(
"foo",
"two",
domain="example.com",
path="/something",
secure=True,
max_age=999,
httponly=True,
samesite="strict",
)
jar.add_cookie("foo", "three", secure_prefix=True)
jar.add_cookie("foo", "four", host_prefix=True)
jar.add_cookie("foo", "five", host_prefix=True, partitioned=True)
encoded = [cookie.encode("ascii") for cookie in jar.cookies]
assert encoded == [
b"foo=one; Path=/; SameSite=Lax; Secure",
b"foo=two; Path=/something; Domain=example.com; Max-Age=999; SameSite=Strict; Secure; HttpOnly", # noqa
b"__Secure-foo=three; Path=/; SameSite=Lax; Secure",
b"__Host-foo=four; Path=/; SameSite=Lax; Secure",
b"__Host-foo=five; Path=/; SameSite=Lax; Secure; Partitioned",
]
def test_cookie_jar_old_school_cookie_encode():
headers = Header()
jar = CookieJar(headers)
jar["foo"] = "one"
jar["bar"] = "two"
jar["bar"]["domain"] = "example.com"
jar["bar"]["path"] = "/something"
jar["bar"]["secure"] = True
jar["bar"]["max-age"] = 999
jar["bar"]["httponly"] = True
jar["bar"]["samesite"] = "strict"
encoded = [cookie.encode("ascii") for cookie in jar.cookies]
assert encoded == [
b"foo=one; Path=/",
b"bar=two; Path=/something; Domain=example.com; Max-Age=999; SameSite=Strict; Secure; HttpOnly", # noqa
]
def test_cookie_jar_delete_cookie_encode():
headers = Header()
jar = CookieJar(headers)
jar.delete_cookie("foo")
jar.delete_cookie("foo", domain="example.com")
encoded = [cookie.encode("ascii") for cookie in jar.cookies]
assert encoded == [
b'foo=""; Path=/; Max-Age=0; Secure',
b'foo=""; Path=/; Domain=example.com; Max-Age=0; Secure',
]
def test_cookie_jar_old_school_delete_encode():
headers = Header()
jar = CookieJar(headers)
del jar["foo"]
encoded = [cookie.encode("ascii") for cookie in jar.cookies]
assert encoded == [
b'foo=""; Path=/; Max-Age=0; Secure',
]
def test_bad_cookie_prarms():
headers = Header()
jar = CookieJar(headers)
with pytest.raises(
ServerError,
match=(
"Both host_prefix and secure_prefix were requested. "
"A cookie should have only one prefix."
),
):
jar.add_cookie("foo", "bar", host_prefix=True, secure_prefix=True)
with pytest.raises(
ServerError,
match="Cannot set host_prefix on a cookie without secure=True",
):
jar.add_cookie("foo", "bar", host_prefix=True, secure=False)
with pytest.raises(
ServerError,
match="Cannot set host_prefix on a cookie unless path='/'",
):
jar.add_cookie(
"foo", "bar", host_prefix=True, secure=True, path="/foo"
)
with pytest.raises(
ServerError,
match="Cannot set host_prefix on a cookie with a defined domain",
):
jar.add_cookie(
"foo", "bar", host_prefix=True, secure=True, domain="foo.bar"
)
with pytest.raises(
ServerError,
match="Cannot set secure_prefix on a cookie without secure=True",
):
jar.add_cookie("foo", "bar", secure_prefix=True, secure=False)
with pytest.raises(
ServerError,
match=(
"Cannot create a partitioned cookie without "
"also setting host_prefix=True"
),
):
jar.add_cookie("foo", "bar", partitioned=True)
def test_cookie_accessors(app: Sanic):
@app.get("/")
async def handler(request: Request):
return json(
{
"getitem": {
"one": request.cookies["one"],
"two": request.cookies["two"],
"three": request.cookies["three"],
},
"get": {
"one": request.cookies.get("one", "fallback"),
"two": request.cookies.get("two", "fallback"),
"three": request.cookies.get("three", "fallback"),
"four": request.cookies.get("four", "fallback"),
},
"getlist": {
"one": request.cookies.getlist("one", ["fallback"]),
"two": request.cookies.getlist("two", ["fallback"]),
"three": request.cookies.getlist("three", ["fallback"]),
"four": request.cookies.getlist("four", ["fallback"]),
},
"getattr": {
"one": request.cookies.one,
"two": request.cookies.two,
"three": request.cookies.three,
"four": request.cookies.four,
},
}
)
_, response = app.test_client.get(
"/",
cookies={
"__Host-one": "1",
"__Secure-two": "2",
"three": "3",
},
)
assert response.json == {
"getitem": {
"one": "1",
"two": "2",
"three": "3",
},
"get": {
"one": "1",
"two": "2",
"three": "3",
"four": "fallback",
},
"getlist": {
"one": ["1"],
"two": ["2"],
"three": ["3"],
"four": ["fallback"],
},
"getattr": {
"one": "1",
"two": "2",
"three": "3",
"four": "",
},
}
def test_cookie_accessor_hyphens():
cookies = CookieRequestParameters({"session-token": ["abc123"]})
assert cookies.get("session-token") == cookies.session_token
def test_cookie_passthru(app):
cookie_jar = None
@app.route("/")
def handler(request):
nonlocal cookie_jar
response = text("OK")
response.add_cookie("one", "1", host_prefix=True)
response.delete_cookie("two", secure_prefix=True)
cookie_jar = response.cookies
return response
_, response = app.test_client.get("/")
assert cookie_jar.get_cookie("two", secure_prefix=True).max_age == 0
assert len(response.cookies) == 1
assert response.cookies["__Host-one"] == "1"

View File

@@ -248,9 +248,9 @@ def test_fallback_with_content_type_mismatch_accept(app):
app.router.reset()
@app.route("/alt1")
@app.route("/alt2", error_format="text")
@app.route("/alt3", error_format="html")
@app.route("/alt1", name="alt1")
@app.route("/alt2", error_format="text", name="alt2")
@app.route("/alt3", error_format="html", name="alt3")
def handler(_):
raise Exception("problem here")
# Yes, we know this return value is unreachable. This is on purpose.

View File

@@ -285,9 +285,15 @@ def test_contextual_exception_context(debug):
def fail():
raise TeapotError(context={"foo": "bar"})
app.post("/coffee/json", error_format="json")(lambda _: fail())
app.post("/coffee/html", error_format="html")(lambda _: fail())
app.post("/coffee/text", error_format="text")(lambda _: fail())
app.post("/coffee/json", error_format="json", name="json")(
lambda _: fail()
)
app.post("/coffee/html", error_format="html", name="html")(
lambda _: fail()
)
app.post("/coffee/text", error_format="text", name="text")(
lambda _: fail()
)
_, response = app.test_client.post("/coffee/json", debug=debug)
assert response.status == 418
@@ -323,9 +329,15 @@ def test_contextual_exception_extra(debug):
def fail():
raise TeapotError(extra={"foo": "bar"})
app.post("/coffee/json", error_format="json")(lambda _: fail())
app.post("/coffee/html", error_format="html")(lambda _: fail())
app.post("/coffee/text", error_format="text")(lambda _: fail())
app.post("/coffee/json", error_format="json", name="json")(
lambda _: fail()
)
app.post("/coffee/html", error_format="html", name="html")(
lambda _: fail()
)
app.post("/coffee/text", error_format="text", name="text")(
lambda _: fail()
)
_, response = app.test_client.post("/coffee/json", debug=debug)
assert response.status == 418

View File

@@ -266,20 +266,17 @@ def test_exception_handler_response_was_sent(
assert "Error" in response.text
def test_warn_on_duplicate(
app: Sanic, caplog: LogCaptureFixture, recwarn: WarningsRecorder
):
def test_errir_on_duplicate(app: Sanic):
@app.exception(ServerError)
async def exception_handler_1(request, exception):
...
@app.exception(ServerError)
async def exception_handler_2(request, exception):
...
assert len(caplog.records) == 1
assert len(recwarn) == 1
assert caplog.records[0].message == (
message = (
"Duplicate exception handler definition on: route=__ALL_ROUTES__ and "
"exception=<class 'sanic.exceptions.ServerError'>"
)
with pytest.raises(ServerError, match=message):
@app.exception(ServerError)
async def exception_handler_2(request, exception):
...

View File

@@ -98,3 +98,17 @@ def test_transfer_chunked(client):
data = stdjson.loads(body)
assert data == ["foo", "bar"]
def test_url_encoding(client):
client.send(
"""
GET /invalid\xA0url HTTP/1.1
"""
)
response = client.recv()
headers, body = response.rsplit(b"\r\n\r\n", 1)
assert b"400 Bad Request" in headers
assert b"URL may only contain US-ASCII characters." in body

View File

@@ -49,96 +49,6 @@ def test_multiprocessing(app):
assert len(process_list) == num_workers + 1
@pytest.mark.skipif(
not hasattr(signal, "SIGALRM"),
reason="SIGALRM is not implemented for this platform, we have to come "
"up with another timeout strategy to test these",
)
def test_multiprocessing_legacy(app):
"""Tests that the number of children we produce is correct"""
# Selects a number at random so we can spot check
num_workers = random.choice(range(2, multiprocessing.cpu_count() * 2 + 1))
process_list = set()
@app.after_server_start
async def shutdown(app):
await sleep(2.1)
app.stop()
def stop_on_alarm(*args):
for process in multiprocessing.active_children():
process_list.add(process.pid)
signal.signal(signal.SIGALRM, stop_on_alarm)
signal.alarm(2)
app.run(HOST, 4121, workers=num_workers, debug=True, legacy=True)
assert len(process_list) == num_workers
@pytest.mark.skipif(
not hasattr(signal, "SIGALRM"),
reason="SIGALRM is not implemented for this platform, we have to come "
"up with another timeout strategy to test these",
)
def test_multiprocessing_legacy_sock(app):
"""Tests that the number of children we produce is correct"""
# Selects a number at random so we can spot check
num_workers = random.choice(range(2, multiprocessing.cpu_count() * 2 + 1))
process_list = set()
@app.after_server_start
async def shutdown(app):
await sleep(2.1)
app.stop()
def stop_on_alarm(*args):
for process in multiprocessing.active_children():
process_list.add(process.pid)
signal.signal(signal.SIGALRM, stop_on_alarm)
signal.alarm(2)
sock = configure_socket(
{
"host": HOST,
"port": 4121,
"unix": None,
"backlog": 100,
}
)
app.run(workers=num_workers, debug=True, legacy=True, sock=sock)
sock.close()
assert len(process_list) == num_workers
@pytest.mark.skipif(
not hasattr(signal, "SIGALRM"),
reason="SIGALRM is not implemented for this platform, we have to come "
"up with another timeout strategy to test these",
)
def test_multiprocessing_legacy_unix(app):
"""Tests that the number of children we produce is correct"""
# Selects a number at random so we can spot check
num_workers = random.choice(range(2, multiprocessing.cpu_count() * 2 + 1))
process_list = set()
@app.after_server_start
async def shutdown(app):
await sleep(2.1)
app.stop()
def stop_on_alarm(*args):
for process in multiprocessing.active_children():
process_list.add(process.pid)
signal.signal(signal.SIGALRM, stop_on_alarm)
signal.alarm(2)
app.run(workers=num_workers, debug=True, legacy=True, unix="./test.sock")
assert len(process_list) == num_workers
@pytest.mark.skipif(
not hasattr(signal, "SIGALRM"),
reason="SIGALRM is not implemented for this platform",

View File

@@ -1,3 +1,5 @@
import uuid
from unittest.mock import Mock
from uuid import UUID, uuid4
@@ -5,7 +7,7 @@ import pytest
from sanic import Sanic, response
from sanic.exceptions import BadURL, SanicException
from sanic.request import Request, uuid
from sanic.request import Request
from sanic.server import HttpProtocol

View File

@@ -16,8 +16,9 @@ from sanic_testing.testing import (
)
from sanic import Blueprint, Sanic
from sanic.constants import DEFAULT_HTTP_CONTENT_TYPE
from sanic.exceptions import ServerError
from sanic.request import DEFAULT_HTTP_CONTENT_TYPE, RequestParameters
from sanic.request import RequestParameters
from sanic.response import html, json, text
@@ -104,11 +105,11 @@ def test_html(app):
return html("<h1>Hello</h1>")
@app.route("/foo")
async def handler(request):
async def handler_foo(request):
return html(Foo())
@app.route("/bar")
async def handler(request):
async def handler_bar(request):
return html(Bar())
request, response = app.test_client.get("/")
@@ -1813,8 +1814,8 @@ def test_request_cookies(app):
request, response = app.test_client.get("/", cookies=cookies)
assert request.cookies == cookies
assert request.cookies == cookies # For request._cookies
assert len(request.cookies) == len(cookies)
assert request.cookies["test"] == cookies["test"]
@pytest.mark.asyncio
@@ -1827,8 +1828,8 @@ async def test_request_cookies_asgi(app):
request, response = await app.asgi_client.get("/", cookies=cookies)
assert request.cookies == cookies
assert request.cookies == cookies # For request._cookies
assert len(request.cookies) == len(cookies)
assert request.cookies["test"] == cookies["test"]
def test_request_cookies_without_cookies(app):
@@ -2198,10 +2199,25 @@ def test_safe_method_with_body(app):
assert response.body == b"OK"
def test_conflicting_body_methods_overload(app):
@pytest.mark.asyncio
async def test_conflicting_body_methods_overload_error(app: Sanic):
@app.put("/")
@app.put("/p/")
@app.put("/p/<foo>")
async def put(request, foo=None):
...
with pytest.raises(
ServerError,
match="Duplicate route names detected: test_conflicting_body_methods_overload_error\.put.*",
):
await app._startup()
def test_conflicting_body_methods_overload(app: Sanic):
@app.put("/", name="one")
@app.put("/p/", name="two")
@app.put("/p/<foo>", name="three")
async def put(request, foo=None):
return json(
{"name": request.route.name, "body": str(request.body), "foo": foo}
@@ -2219,21 +2235,21 @@ def test_conflicting_body_methods_overload(app):
_, response = app.test_client.put("/", json=payload)
assert response.status == 200
assert response.json == {
"name": "test_conflicting_body_methods_overload.put",
"name": "test_conflicting_body_methods_overload.one",
"foo": None,
"body": data,
}
_, response = app.test_client.put("/p", json=payload)
assert response.status == 200
assert response.json == {
"name": "test_conflicting_body_methods_overload.put",
"name": "test_conflicting_body_methods_overload.two",
"foo": None,
"body": data,
}
_, response = app.test_client.put("/p/test", json=payload)
assert response.status == 200
assert response.json == {
"name": "test_conflicting_body_methods_overload.put",
"name": "test_conflicting_body_methods_overload.three",
"foo": "test",
"body": data,
}
@@ -2246,9 +2262,26 @@ def test_conflicting_body_methods_overload(app):
}
def test_handler_overload(app):
@pytest.mark.asyncio
async def test_handler_overload_error(app: Sanic):
@app.get("/long/sub/route/param_a/<param_a:str>/param_b/<param_b:str>")
@app.post("/long/sub/route/")
def handler(request, **kwargs):
...
with pytest.raises(
ServerError,
match="Duplicate route names detected: test_handler_overload_error\.handler.*",
):
await app._startup()
def test_handler_overload(app: Sanic):
@app.get(
"/long/sub/route/param_a/<param_a:str>/param_b/<param_b:str>",
name="one",
)
@app.post("/long/sub/route/", name="two")
def handler(request, **kwargs):
return json(kwargs)

View File

@@ -12,7 +12,7 @@ from sanic_testing.testing import SanicTestClient
from sanic import Blueprint, Sanic
from sanic.constants import HTTP_METHODS
from sanic.exceptions import NotFound, SanicException
from sanic.exceptions import NotFound, SanicException, ServerError
from sanic.request import Request
from sanic.response import empty, json, text
@@ -744,8 +744,8 @@ def test_route_duplicate(app):
def test_double_stack_route(app):
@app.route("/test/1")
@app.route("/test/2")
@app.route("/test/1", name="test1")
@app.route("/test/2", name="test2")
async def handler1(request):
return text("OK")
@@ -759,8 +759,8 @@ def test_double_stack_route(app):
async def test_websocket_route_asgi(app):
ev = asyncio.Event()
@app.websocket("/test/1")
@app.websocket("/test/2")
@app.websocket("/test/1", name="test1")
@app.websocket("/test/2", name="test2")
async def handler(request, ws):
ev.set()
@@ -1279,7 +1279,7 @@ async def test_added_callable_route_ctx_kwargs(app):
@pytest.mark.asyncio
async def test_duplicate_route_deprecation(app):
async def test_duplicate_route_error(app):
@app.route("/foo", name="duped")
async def handler_foo(request):
return text("...")
@@ -1289,9 +1289,7 @@ async def test_duplicate_route_deprecation(app):
return text("...")
message = (
r"\[DEPRECATION v23\.3\] Duplicate route names detected: "
r"test_duplicate_route_deprecation\.duped\. In the future, "
r"Sanic will enforce uniqueness in route naming\."
"Duplicate route names detected: test_duplicate_route_error.duped."
)
with pytest.warns(DeprecationWarning, match=message):
with pytest.raises(ServerError, match=message):
await app._startup()

View File

@@ -66,8 +66,8 @@ def test_no_register_system_signals_fails(app):
app.listener("after_server_stop")(after)
message = (
"Cannot run Sanic.serve with register_sys_signals=False. Use "
"either Sanic.serve_single or Sanic.serve_legacy."
r"Cannot run Sanic\.serve with register_sys_signals=False\. Use "
r"Sanic.serve_single\."
)
with pytest.raises(RuntimeError, match=message):
app.prepare(HOST, PORT, register_sys_signals=False)

View File

@@ -9,7 +9,7 @@ from time import gmtime, strftime
import pytest
from sanic import Sanic, text
from sanic.exceptions import FileNotFound
from sanic.exceptions import FileNotFound, ServerError
@pytest.fixture(scope="module")
@@ -108,14 +108,9 @@ def test_static_file_pathlib(app, static_file_directory, file_name):
def test_static_file_bytes(app, static_file_directory, file_name):
bsep = os.path.sep.encode("utf-8")
file_path = static_file_directory.encode("utf-8") + bsep + file_name
message = (
"Serving a static directory with a bytes "
"string is deprecated and will be removed in v22.9."
)
with pytest.warns(DeprecationWarning, match=message):
message = "Static file or directory must be a path-like object or string"
with pytest.raises(TypeError, match=message):
app.static("/testing.file", file_path)
request, response = app.test_client.get("/testing.file")
assert response.status == 200
@pytest.mark.parametrize(
@@ -523,10 +518,26 @@ def test_no_stack_trace_on_not_found(app, static_file_directory, caplog):
assert response.text == "No file: /static/non_existing_file.file"
def test_multiple_statics(app, static_file_directory):
@pytest.mark.asyncio
async def test_multiple_statics_error(app, static_file_directory):
app.static("/file", get_file_path(static_file_directory, "test.file"))
app.static("/png", get_file_path(static_file_directory, "python.png"))
message = (
r"Duplicate route names detected: test_multiple_statics_error\.static"
)
with pytest.raises(ServerError, match=message):
await app._startup()
def test_multiple_statics(app, static_file_directory):
app.static(
"/file", get_file_path(static_file_directory, "test.file"), name="file"
)
app.static(
"/png", get_file_path(static_file_directory, "python.png"), name="png"
)
_, response = app.test_client.get("/file")
assert response.status == 200
assert response.body == get_file_content(
@@ -540,10 +551,22 @@ def test_multiple_statics(app, static_file_directory):
)
def test_resource_type_default(app, static_file_directory):
@pytest.mark.asyncio
async def test_resource_type_default_error(app, static_file_directory):
app.static("/static", static_file_directory)
app.static("/file", get_file_path(static_file_directory, "test.file"))
message = r"Duplicate route names detected: test_resource_type_default_error\.static"
with pytest.raises(ServerError, match=message):
await app._startup()
def test_resource_type_default(app, static_file_directory):
app.static("/static", static_file_directory, name="static")
app.static(
"/file", get_file_path(static_file_directory, "test.file"), name="file"
)
_, response = app.test_client.get("/static")
assert response.status == 404

View File

@@ -52,34 +52,23 @@ def test_cwd_in_path():
def test_input_is_dir():
loader = AppLoader(str(STATIC))
message = (
"App not found.\n Please use --simple if you are passing a "
f"directory to sanic.\n eg. sanic {str(STATIC)} --simple"
)
with pytest.raises(ValueError, match=message):
loader.load()
app = loader.load()
assert isinstance(app, Sanic)
def test_input_is_factory():
ns = SimpleNamespace(module="foo")
ns = SimpleNamespace(target="foo")
loader = AppLoader("tests.fake.server:create_app", args=ns)
message = (
"Module is not a Sanic app, it is a function\n If this callable "
"returns a Sanic instance try: \nsanic foo --factory"
)
with pytest.raises(ValueError, match=message):
loader.load()
app = loader.load()
assert isinstance(app, Sanic)
def test_input_is_module():
ns = SimpleNamespace(module="foo")
ns = SimpleNamespace(target="foo")
loader = AppLoader("tests.fake.server", args=ns)
message = (
"Module is not a Sanic app, it is a module\n "
"Perhaps you meant foo:app?"
)
with pytest.raises(ValueError, match=message):
loader.load()
app = loader.load()
assert isinstance(app, Sanic)
@pytest.mark.parametrize("creator", ("mkcert", "trustme"))

View File

@@ -72,24 +72,6 @@ def test_not_have_multiplexer_single(app: Sanic):
assert not event.is_set()
def test_not_have_multiplexer_legacy(app: Sanic):
event = Event()
@app.main_process_start
async def setup(app, _):
app.shared_ctx.event = event
@app.after_server_start
def stop(app):
if hasattr(app, "m") and isinstance(app.m, WorkerMultiplexer):
app.shared_ctx.event.set()
app.stop()
app.run(legacy=True)
assert not event.is_set()
def test_ack(worker_state: Dict[str, Any], m: WorkerMultiplexer):
worker_state["Test"] = {"foo": "bar"}
m.ack()