rebuild(stage-2): establish runtime and persistence foundations
This commit is contained in:
+67
-14
@@ -1,36 +1,89 @@
|
||||
from fastapi.testclient import TestClient
|
||||
import asyncio
|
||||
|
||||
import httpx
|
||||
from fastapi import Query
|
||||
|
||||
from backend.bootstrap.application import create_application
|
||||
from backend.bootstrap.settings import Settings
|
||||
from backend.http.errors import AppError
|
||||
|
||||
|
||||
def request(application, path: str) -> httpx.Response:
|
||||
async def run() -> httpx.Response:
|
||||
transport = httpx.ASGITransport(app=application, raise_app_exceptions=False)
|
||||
async with application.router.lifespan_context(application):
|
||||
async with httpx.AsyncClient(
|
||||
transport=transport, base_url="http://testserver"
|
||||
) as client:
|
||||
return await client.get(path)
|
||||
|
||||
return asyncio.run(run())
|
||||
|
||||
|
||||
def test_health_reports_runtime_environment(tmp_path) -> None:
|
||||
application = create_application(
|
||||
Settings(environment="test", debug=False, data_directory=tmp_path)
|
||||
)
|
||||
|
||||
with TestClient(application) as client:
|
||||
response = client.get("/api/health")
|
||||
application = create_application(Settings.for_test(tmp_path))
|
||||
response = request(application, "/api/health")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"status": "ok", "environment": "test"}
|
||||
assert response.json() == {
|
||||
"status": "ok",
|
||||
"environment": "test",
|
||||
"components": {"process": "ok", "database": "ok"},
|
||||
}
|
||||
assert len(response.headers["X-Request-ID"]) == 32
|
||||
|
||||
|
||||
def test_unknown_failure_uses_safe_error_contract(tmp_path) -> None:
|
||||
application = create_application(
|
||||
Settings(environment="test", debug=False, data_directory=tmp_path)
|
||||
)
|
||||
application = create_application(Settings.for_test(tmp_path))
|
||||
|
||||
@application.get("/api/test/failure")
|
||||
def fail() -> None:
|
||||
raise RuntimeError("secret provider details")
|
||||
|
||||
with TestClient(application, raise_server_exceptions=False) as client:
|
||||
response = client.get("/api/test/failure")
|
||||
response = request(application, "/api/test/failure")
|
||||
|
||||
payload = response.json()["error"]
|
||||
assert response.status_code == 500
|
||||
assert payload["code"] == "internal_error"
|
||||
assert payload["message"] == "服务暂时不可用,请稍后重试。"
|
||||
assert "secret provider details" not in response.text
|
||||
assert len(payload["correlation_id"]) == 32
|
||||
assert len(payload["request_id"]) == 32
|
||||
assert response.headers["X-Request-ID"] == payload["request_id"]
|
||||
|
||||
|
||||
def test_application_error_uses_business_message(tmp_path) -> None:
|
||||
application = create_application(Settings.for_test(tmp_path))
|
||||
|
||||
@application.get("/api/test/locked")
|
||||
def locked() -> None:
|
||||
raise AppError("membership_required", "该功能仅对会员开放。", 403)
|
||||
|
||||
response = request(application, "/api/test/locked")
|
||||
|
||||
assert response.status_code == 403
|
||||
assert response.json()["error"]["code"] == "membership_required"
|
||||
assert response.json()["error"]["message"] == "该功能仅对会员开放。"
|
||||
|
||||
|
||||
def test_framework_404_uses_the_same_safe_contract(tmp_path) -> None:
|
||||
application = create_application(Settings.for_test(tmp_path))
|
||||
|
||||
response = request(application, "/api/does-not-exist")
|
||||
|
||||
assert response.status_code == 404
|
||||
assert response.json()["error"]["code"] == "not_found"
|
||||
assert response.json()["error"]["message"] == "请求的内容不存在。"
|
||||
|
||||
|
||||
def test_validation_error_does_not_expose_framework_details(tmp_path) -> None:
|
||||
application = create_application(Settings.for_test(tmp_path))
|
||||
|
||||
@application.get("/api/test/validated")
|
||||
def validated(value: int = Query(ge=1)) -> dict[str, int]:
|
||||
return {"value": value}
|
||||
|
||||
response = request(application, "/api/test/validated?value=wrong")
|
||||
|
||||
assert response.status_code == 422
|
||||
assert response.json()["error"]["code"] == "invalid_request"
|
||||
assert "integer" not in response.text.lower()
|
||||
|
||||
Reference in New Issue
Block a user