122 lines
3.9 KiB
Python
122 lines
3.9 KiB
Python
import pytest
|
|
|
|
from app.library.router import ROUTES, Route, RouteType, add_route, get_route, get_routes, make_route_name, route
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def reset_routes():
|
|
# Ensure ROUTES is clean before each test
|
|
ROUTES.clear()
|
|
yield
|
|
ROUTES.clear()
|
|
|
|
|
|
class TestMakeRouteName:
|
|
def test_trailing_slash_and_root(self) -> None:
|
|
# Current behavior converts empty part to 'part'
|
|
assert make_route_name("post", "/") == "post:part"
|
|
assert make_route_name("post", "") == "post:part"
|
|
|
|
def test_invalid_chars_and_numbers(self) -> None:
|
|
# invalid chars become underscores, leading digits prefixed with p_
|
|
assert make_route_name("GET", "/a-b/c@d/123/0x-ff") == "get:a_b.c_d.p_123.p_0x_ff"
|
|
|
|
|
|
class TestRouteDecorator:
|
|
@pytest.mark.asyncio
|
|
async def test_registers_http_and_no_slash_alias(self) -> None:
|
|
# Define an async handler and decorate it
|
|
result_bucket: dict[str, int] = {"called": 0}
|
|
|
|
@route("GET", "/api/test/")
|
|
async def handler() -> str:
|
|
result_bucket["called"] += 1
|
|
return "ok"
|
|
|
|
# Two routes should be registered: with slash and _no_slash alias
|
|
http_routes = get_routes(RouteType.HTTP)
|
|
assert "get:api.test" in http_routes
|
|
assert "get:api.test_no_slash" in http_routes
|
|
|
|
# Verify stored Route objects
|
|
r1: Route = http_routes["get:api.test"]
|
|
r2: Route = http_routes["get:api.test_no_slash"]
|
|
assert r1.method == "GET"
|
|
assert r1.path == "/api/test/"
|
|
assert r2.method == "GET"
|
|
assert r2.path == "/api/test"
|
|
|
|
# The wrapper should call the original function
|
|
res = await r1.handler()
|
|
assert res == "ok"
|
|
assert result_bucket["called"] == 1
|
|
|
|
# Check that metadata is preserved by wraps
|
|
assert r1.handler.__name__ == handler.__name__
|
|
|
|
def test_decorator_no_slash_disabled(self) -> None:
|
|
@route("GET", "/api/one/", no_slash=True)
|
|
async def h1():
|
|
return "one"
|
|
|
|
http_routes = get_routes(RouteType.HTTP)
|
|
assert "get:api.one" in http_routes
|
|
assert "get:api.one_no_slash" not in http_routes
|
|
|
|
def test_socket_route_registration(self) -> None:
|
|
@route(RouteType.SOCKET, "/ws/conn")
|
|
async def ws():
|
|
return "socket"
|
|
|
|
socket_routes = get_routes(RouteType.SOCKET)
|
|
assert "socket:ws.conn" in socket_routes
|
|
# No no_slash alias for socket routes
|
|
assert "socket:ws.conn_no_slash" not in socket_routes
|
|
|
|
|
|
class TestAddRoute:
|
|
def test_add_route_http_with_alias(self) -> None:
|
|
async def handler():
|
|
return "ok"
|
|
|
|
add_route("POST", "/api/create/", handler)
|
|
|
|
http_routes = get_routes(RouteType.HTTP)
|
|
assert "post:api.create" in http_routes
|
|
assert "post:api.create_no_slash" in http_routes
|
|
|
|
r = get_route(RouteType.HTTP, "post:api.create")
|
|
assert isinstance(r, Route)
|
|
assert r.method == "POST"
|
|
assert r.path == "/api/create/"
|
|
|
|
def test_add_route_socket_no_alias(self) -> None:
|
|
async def s():
|
|
return "s"
|
|
|
|
add_route(RouteType.SOCKET, "/sock/path/", s)
|
|
|
|
socket_routes = get_routes(RouteType.SOCKET)
|
|
assert "socket:sock.path" in socket_routes
|
|
assert "socket:sock.path_no_slash" not in socket_routes
|
|
|
|
def test_add_route_custom_name(self) -> None:
|
|
async def h():
|
|
return "x"
|
|
|
|
add_route("GET", "/v1/x", h, name="get:v1.custom")
|
|
assert get_route(RouteType.HTTP, "get:v1.custom") is not None
|
|
|
|
|
|
class TestGetters:
|
|
def test_get_routes_copy(self) -> None:
|
|
async def h():
|
|
return "x"
|
|
|
|
add_route("GET", "/x", h)
|
|
routes = get_routes(RouteType.HTTP)
|
|
assert isinstance(routes, dict)
|
|
assert "get:x" in routes
|
|
|
|
def test_get_route_not_found(self) -> None:
|
|
assert get_route(RouteType.HTTP, "nonexistent") is None
|