mirror of https://github.com/encode/starlette.git
356 lines
10 KiB
Python
356 lines
10 KiB
Python
import pytest
|
|
|
|
from starlette.responses import JSONResponse, PlainTextResponse, Response
|
|
from starlette.routing import Host, Mount, NoMatchFound, Route, Router, WebSocketRoute
|
|
from starlette.testclient import TestClient
|
|
from starlette.websockets import WebSocket, WebSocketDisconnect
|
|
|
|
|
|
def homepage(request):
|
|
return Response("Hello, world", media_type="text/plain")
|
|
|
|
|
|
def users(request):
|
|
return Response("All users", media_type="text/plain")
|
|
|
|
|
|
def user(request):
|
|
content = "User " + request.path_params["username"]
|
|
return Response(content, media_type="text/plain")
|
|
|
|
|
|
def user_me(request):
|
|
content = "User fixed me"
|
|
return Response(content, media_type="text/plain")
|
|
|
|
|
|
def user_no_match(request): # pragma: no cover
|
|
content = "User fixed no match"
|
|
return Response(content, media_type="text/plain")
|
|
|
|
|
|
app = Router(
|
|
[
|
|
Route("/", endpoint=homepage, methods=["GET"]),
|
|
Mount(
|
|
"/users",
|
|
routes=[
|
|
Route("/", endpoint=users),
|
|
Route("/me", endpoint=user_me),
|
|
Route("/{username}", endpoint=user),
|
|
Route("/nomatch", endpoint=user_no_match),
|
|
],
|
|
),
|
|
Mount("/static", app=Response("xxxxx", media_type="image/png")),
|
|
]
|
|
)
|
|
|
|
|
|
@app.route("/func")
|
|
def func_homepage(request):
|
|
return Response("Hello, world!", media_type="text/plain")
|
|
|
|
|
|
@app.route("/func", methods=["POST"])
|
|
def contact(request):
|
|
return Response("Hello, POST!", media_type="text/plain")
|
|
|
|
|
|
@app.route("/int/{param:int}", name="int-convertor")
|
|
def int_convertor(request):
|
|
number = request.path_params["param"]
|
|
return JSONResponse({"int": number})
|
|
|
|
|
|
@app.route("/float/{param:float}", name="float-convertor")
|
|
def float_convertor(request):
|
|
num = request.path_params["param"]
|
|
return JSONResponse({"float": num})
|
|
|
|
|
|
@app.route("/path/{param:path}", name="path-convertor")
|
|
def path_convertor(request):
|
|
path = request.path_params["param"]
|
|
return JSONResponse({"path": path})
|
|
|
|
|
|
@app.websocket_route("/ws")
|
|
async def websocket_endpoint(session):
|
|
await session.accept()
|
|
await session.send_text("Hello, world!")
|
|
await session.close()
|
|
|
|
|
|
@app.websocket_route("/ws/{room}")
|
|
async def websocket_params(session):
|
|
await session.accept()
|
|
await session.send_text(f"Hello, {session.path_params['room']}!")
|
|
await session.close()
|
|
|
|
|
|
client = TestClient(app)
|
|
|
|
|
|
def test_router():
|
|
response = client.get("/")
|
|
assert response.status_code == 200
|
|
assert response.text == "Hello, world"
|
|
|
|
response = client.post("/")
|
|
assert response.status_code == 405
|
|
assert response.text == "Method Not Allowed"
|
|
|
|
response = client.get("/foo")
|
|
assert response.status_code == 404
|
|
assert response.text == "Not Found"
|
|
|
|
response = client.get("/users")
|
|
assert response.status_code == 200
|
|
assert response.text == "All users"
|
|
|
|
response = client.get("/users/tomchristie")
|
|
assert response.status_code == 200
|
|
assert response.text == "User tomchristie"
|
|
|
|
response = client.get("/users/me")
|
|
assert response.status_code == 200
|
|
assert response.text == "User fixed me"
|
|
|
|
response = client.get("/users/nomatch")
|
|
assert response.status_code == 200
|
|
assert response.text == "User nomatch"
|
|
|
|
response = client.get("/static/123")
|
|
assert response.status_code == 200
|
|
assert response.text == "xxxxx"
|
|
|
|
|
|
def test_route_converters():
|
|
# Test integer conversion
|
|
response = client.get("/int/5")
|
|
assert response.status_code == 200
|
|
assert response.json() == {"int": 5}
|
|
assert app.url_path_for("int-convertor", param=5) == "/int/5"
|
|
|
|
# Test float conversion
|
|
response = client.get("/float/25.5")
|
|
assert response.status_code == 200
|
|
assert response.json() == {"float": 25.5}
|
|
assert app.url_path_for("float-convertor", param=25.5) == "/float/25.5"
|
|
|
|
# Test path conversion
|
|
response = client.get("/path/some/example")
|
|
assert response.status_code == 200
|
|
assert response.json() == {"path": "some/example"}
|
|
assert (
|
|
app.url_path_for("path-convertor", param="some/example") == "/path/some/example"
|
|
)
|
|
|
|
|
|
def test_url_path_for():
|
|
assert app.url_path_for("homepage") == "/"
|
|
assert app.url_path_for("user", username="tomchristie") == "/users/tomchristie"
|
|
assert app.url_path_for("websocket_endpoint") == "/ws"
|
|
with pytest.raises(NoMatchFound):
|
|
assert app.url_path_for("broken")
|
|
with pytest.raises(AssertionError):
|
|
app.url_path_for("user", username="tom/christie")
|
|
with pytest.raises(AssertionError):
|
|
app.url_path_for("user", username="")
|
|
|
|
|
|
def test_url_for():
|
|
assert (
|
|
app.url_path_for("homepage").make_absolute_url(base_url="https://example.org")
|
|
== "https://example.org/"
|
|
)
|
|
assert (
|
|
app.url_path_for("user", username="tomchristie").make_absolute_url(
|
|
base_url="https://example.org"
|
|
)
|
|
== "https://example.org/users/tomchristie"
|
|
)
|
|
assert (
|
|
app.url_path_for("websocket_endpoint").make_absolute_url(
|
|
base_url="https://example.org"
|
|
)
|
|
== "wss://example.org/ws"
|
|
)
|
|
|
|
|
|
def test_router_add_route():
|
|
response = client.get("/func")
|
|
assert response.status_code == 200
|
|
assert response.text == "Hello, world!"
|
|
|
|
|
|
def test_router_duplicate_path():
|
|
response = client.post("/func")
|
|
assert response.status_code == 200
|
|
assert response.text == "Hello, POST!"
|
|
|
|
|
|
def test_router_add_websocket_route():
|
|
with client.websocket_connect("/ws") as session:
|
|
text = session.receive_text()
|
|
assert text == "Hello, world!"
|
|
|
|
with client.websocket_connect("/ws/test") as session:
|
|
text = session.receive_text()
|
|
assert text == "Hello, test!"
|
|
|
|
|
|
def http_endpoint(request):
|
|
url = request.url_for("http_endpoint")
|
|
return Response(f"URL: {url}", media_type="text/plain")
|
|
|
|
|
|
class WebSocketEndpoint:
|
|
async def __call__(self, scope, receive, send):
|
|
websocket = WebSocket(scope=scope, receive=receive, send=send)
|
|
await websocket.accept()
|
|
await websocket.send_json({"URL": str(websocket.url_for("websocket_endpoint"))})
|
|
await websocket.close()
|
|
|
|
|
|
mixed_protocol_app = Router(
|
|
routes=[
|
|
Route("/", endpoint=http_endpoint),
|
|
WebSocketRoute("/", endpoint=WebSocketEndpoint(), name="websocket_endpoint"),
|
|
]
|
|
)
|
|
|
|
|
|
def test_protocol_switch():
|
|
client = TestClient(mixed_protocol_app)
|
|
|
|
response = client.get("/")
|
|
assert response.status_code == 200
|
|
assert response.text == "URL: http://testserver/"
|
|
|
|
with client.websocket_connect("/") as session:
|
|
assert session.receive_json() == {"URL": "ws://testserver/"}
|
|
|
|
with pytest.raises(WebSocketDisconnect):
|
|
client.websocket_connect("/404")
|
|
|
|
|
|
ok = PlainTextResponse("OK")
|
|
|
|
|
|
def test_mount_urls():
|
|
mounted = Router([Mount("/users", ok, name="users")])
|
|
client = TestClient(mounted)
|
|
assert client.get("/users").status_code == 200
|
|
assert client.get("/users").url == "http://testserver/users/"
|
|
assert client.get("/users/").status_code == 200
|
|
assert client.get("/users/a").status_code == 200
|
|
assert client.get("/usersa").status_code == 404
|
|
|
|
|
|
def test_reverse_mount_urls():
|
|
mounted = Router([Mount("/users", ok, name="users")])
|
|
assert mounted.url_path_for("users", path="/a") == "/users/a"
|
|
|
|
users = Router([Route("/{username}", ok, name="user")])
|
|
mounted = Router([Mount("/{subpath}/users", users, name="users")])
|
|
assert (
|
|
mounted.url_path_for("users:user", subpath="test", username="tom")
|
|
== "/test/users/tom"
|
|
)
|
|
assert (
|
|
mounted.url_path_for("users", subpath="test", path="/tom") == "/test/users/tom"
|
|
)
|
|
|
|
|
|
def test_mount_at_root():
|
|
mounted = Router([Mount("/", ok, name="users")])
|
|
client = TestClient(mounted)
|
|
assert client.get("/").status_code == 200
|
|
|
|
|
|
def users_api(request):
|
|
return JSONResponse({"users": [{"username": "tom"}]})
|
|
|
|
|
|
mixed_hosts_app = Router(
|
|
routes=[
|
|
Host(
|
|
"www.example.org",
|
|
app=Router(
|
|
[
|
|
Route("/", homepage, name="homepage"),
|
|
Route("/users", users, name="users"),
|
|
]
|
|
),
|
|
),
|
|
Host(
|
|
"api.example.org",
|
|
name="api",
|
|
app=Router([Route("/users", users_api, name="users")]),
|
|
),
|
|
]
|
|
)
|
|
|
|
|
|
def test_host_routing():
|
|
client = TestClient(mixed_hosts_app, base_url="https://api.example.org/")
|
|
|
|
response = client.get("/users")
|
|
assert response.status_code == 200
|
|
assert response.json() == {"users": [{"username": "tom"}]}
|
|
|
|
response = client.get("/")
|
|
assert response.status_code == 404
|
|
|
|
client = TestClient(mixed_hosts_app, base_url="https://www.example.org/")
|
|
|
|
response = client.get("/users")
|
|
assert response.status_code == 200
|
|
assert response.text == "All users"
|
|
|
|
response = client.get("/")
|
|
assert response.status_code == 200
|
|
|
|
|
|
def test_host_reverse_urls():
|
|
assert (
|
|
mixed_hosts_app.url_path_for("homepage").make_absolute_url("https://whatever")
|
|
== "https://www.example.org/"
|
|
)
|
|
assert (
|
|
mixed_hosts_app.url_path_for("users").make_absolute_url("https://whatever")
|
|
== "https://www.example.org/users"
|
|
)
|
|
assert (
|
|
mixed_hosts_app.url_path_for("api:users").make_absolute_url("https://whatever")
|
|
== "https://api.example.org/users"
|
|
)
|
|
|
|
|
|
async def subdomain_app(scope, receive, send):
|
|
response = JSONResponse({"subdomain": scope["path_params"]["subdomain"]})
|
|
await response(scope, receive, send)
|
|
|
|
|
|
subdomain_app = Router(
|
|
routes=[Host("{subdomain}.example.org", app=subdomain_app, name="subdomains")]
|
|
)
|
|
|
|
|
|
def test_subdomain_routing():
|
|
client = TestClient(subdomain_app, base_url="https://foo.example.org/")
|
|
|
|
response = client.get("/")
|
|
assert response.status_code == 200
|
|
assert response.json() == {"subdomain": "foo"}
|
|
|
|
|
|
def test_subdomain_reverse_urls():
|
|
assert (
|
|
subdomain_app.url_path_for(
|
|
"subdomains", subdomain="foo", path="/homepage"
|
|
).make_absolute_url("https://whatever")
|
|
== "https://foo.example.org/homepage"
|
|
)
|