Add unit tests for gateway configuration and proxy request handling

This commit is contained in:
2026-05-31 21:58:07 +08:00
parent 8ad465bebd
commit 151b604a75
3 changed files with 245 additions and 0 deletions
+82
View File
@@ -0,0 +1,82 @@
from __future__ import annotations
import json
import httpx
import pytest
from gateway_framework.app import create_app
from gateway_framework.config import load_gateway_config
@pytest.mark.anyio
async def test_management_endpoints_and_external_openapi(tmp_path) -> None:
external_openapi = tmp_path / "external-openapi.json"
external_openapi.write_text(
json.dumps({"openapi": "3.1.0", "info": {"title": "Upstream", "version": "1.0.0"}}),
encoding="utf-8",
)
config_file = tmp_path / "gateway.yaml"
config_file.write_text(
f"""
settings:
title: Test Gateway
version: 9.9.9
description: test gateway
external_openapi_file: {external_openapi}
upstreams:
demo:
base_url: https://example.com/
routes:
- path: /api/v1/demo
methods: [GET]
upstream: demo
upstream_path: /api/v1/demo
""".strip(),
encoding="utf-8",
)
app = create_app(str(config_file))
app.state.gateway_config = load_gateway_config(config_file)
transport = httpx.ASGITransport(app=app)
async with httpx.AsyncClient(transport=transport, base_url="http://testserver") as client:
health = await client.get("/healthz")
ready = await client.get("/readyz")
routes = await client.get("/admin/routes")
external = await client.get("/openapi/external.json")
assert health.status_code == 200
assert health.json() == {"status": "ok"}
assert ready.status_code == 200
assert ready.json() == {"status": "ready"}
assert routes.status_code == 200
assert routes.json()[0]["path"] == "/api/v1/demo"
assert external.status_code == 200
assert external.json()["info"]["title"] == "Upstream"
@pytest.mark.anyio
async def test_external_openapi_not_configured(tmp_path) -> None:
config_file = tmp_path / "gateway.yaml"
config_file.write_text(
"""
upstreams:
demo:
base_url: https://example.com/
routes:
- path: /api/v1/demo
methods: [GET]
upstream: demo
""".strip(),
encoding="utf-8",
)
app = create_app(str(config_file))
app.state.gateway_config = load_gateway_config(config_file)
transport = httpx.ASGITransport(app=app)
async with httpx.AsyncClient(transport=transport, base_url="http://testserver") as client:
response = await client.get("/openapi/external.json")
assert response.status_code == 404
assert response.json()["error"] == "external_openapi_not_configured"
+67
View File
@@ -0,0 +1,67 @@
from __future__ import annotations
import pytest
from gateway_framework.config import load_gateway_config
def test_load_gateway_config_success(tmp_path) -> None:
config_file = tmp_path / "gateway.yaml"
config_file.write_text(
"""
settings:
title: Test Gateway
upstreams:
demo:
base_url: https://example.com/
routes:
- path: /api/v1/demo
methods: [GET]
upstream: demo
""".strip(),
encoding="utf-8",
)
config = load_gateway_config(config_file)
assert config.settings.title == "Test Gateway"
assert "demo" in config.upstreams
assert config.routes[0].path == "/api/v1/demo"
def test_load_gateway_config_unknown_upstream(tmp_path) -> None:
config_file = tmp_path / "gateway.yaml"
config_file.write_text(
"""
upstreams:
known:
base_url: https://example.com/
routes:
- path: /api/v1/demo
methods: [GET]
upstream: unknown
""".strip(),
encoding="utf-8",
)
with pytest.raises(ValueError, match="unknown upstreams"):
load_gateway_config(config_file)
def test_load_gateway_config_invalid_route_path(tmp_path) -> None:
config_file = tmp_path / "gateway.yaml"
config_file.write_text(
"""
upstreams:
demo:
base_url: https://example.com/
routes:
- path: api/v1/demo
methods: [GET]
upstream: demo
""".strip(),
encoding="utf-8",
)
with pytest.raises(ValueError, match="route path must start"):
load_gateway_config(config_file)
+96
View File
@@ -0,0 +1,96 @@
from __future__ import annotations
import httpx
import pytest
from starlette.requests import Request
from gateway_framework.config import GatewayConfig, RouteConfig, UpstreamConfig
from gateway_framework.proxy import build_target_path, proxy_request
def _make_request(path: str, query: str = "", headers: list[tuple[str, str]] | None = None) -> Request:
encoded_headers = [
(key.lower().encode("utf-8"), value.encode("utf-8")) for key, value in (headers or [])
]
scope = {
"type": "http",
"http_version": "1.1",
"method": "GET",
"scheme": "http",
"path": path,
"query_string": query.encode("utf-8"),
"headers": encoded_headers,
"client": ("127.0.0.1", 51234),
"server": ("gateway.local", 8000),
}
async def receive() -> dict[str, object]:
return {"type": "http.request", "body": b"", "more_body": False}
return Request(scope, receive)
def test_build_target_path_uses_upstream_path_override() -> None:
route = RouteConfig(path="/api/v1/bus", methods=["GET"], upstream="demo", upstream_path="/v2/bus")
request = _make_request("/api/v1/bus")
assert build_target_path(request, route) == "/v2/bus"
def test_build_target_path_applies_strip_prefix() -> None:
route = RouteConfig(path="/api/v1/bus", methods=["GET"], upstream="demo", strip_prefix="/api")
request = _make_request("/api/v1/bus")
assert build_target_path(request, route) == "/v1/bus"
@pytest.mark.anyio
async def test_proxy_request_forwards_to_upstream() -> None:
seen: dict[str, object] = {}
def handler(req: httpx.Request) -> httpx.Response:
seen["url"] = str(req.url)
seen["x_forwarded_proto"] = req.headers.get("x-forwarded-proto")
seen["x_forwarded_host"] = req.headers.get("x-forwarded-host")
seen["connection"] = req.headers.get("connection")
return httpx.Response(
status_code=200,
content=b'{"ok":true}',
headers={"content-type": "application/json", "connection": "close"},
)
config = GatewayConfig(
upstreams={"demo": UpstreamConfig(base_url="https://example.com/", timeout_seconds=5)},
routes=[],
)
route = RouteConfig(path="/api/v1/bus", methods=["GET"], upstream="demo")
request = _make_request("/api/v1/bus", query="a=1", headers=[("Connection", "keep-alive")])
async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client:
response = await proxy_request(request=request, client=client, config=config, route=route)
assert response.status_code == 200
assert response.body == b'{"ok":true}'
assert response.headers.get("connection") is None
assert seen["url"] == "https://example.com/api/v1/bus?a=1"
assert seen["x_forwarded_proto"] == "http"
assert seen["x_forwarded_host"] == "gateway.local"
@pytest.mark.anyio
async def test_proxy_request_returns_502_when_upstream_unreachable() -> None:
def handler(req: httpx.Request) -> httpx.Response:
raise httpx.ConnectError("connection failed", request=req)
config = GatewayConfig(
upstreams={"demo": UpstreamConfig(base_url="https://example.com/")},
routes=[],
)
route = RouteConfig(path="/api/v1/bus", methods=["GET"], upstream="demo")
request = _make_request("/api/v1/bus")
async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client:
response = await proxy_request(request=request, client=client, config=config, route=route)
assert response.status_code == 502
assert b"upstream_unreachable" in response.body