99520c69d7
1.新增后端测试 2.增加了后端的加密 3.增加了i18n(国际化)
78 lines
2.4 KiB
Python
78 lines
2.4 KiB
Python
"""``api/__init__.py`` 全局异常兜底 handler 与 CORS 中间件。"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from fastapi.responses import JSONResponse
|
|
from httpx import AsyncClient, ASGITransport
|
|
|
|
|
|
@pytest.fixture
|
|
def app_with_global_handler():
|
|
"""构造一个最小 app,套用与生产相同的兜底 handler。"""
|
|
from kilostar.utils.logger import get_logger
|
|
|
|
_logger = get_logger("api")
|
|
|
|
app = FastAPI()
|
|
|
|
@app.exception_handler(Exception)
|
|
async def unhandled_exception_handler(request: Request, exc: Exception):
|
|
_logger.exception(
|
|
f"Unhandled exception on {request.method} {request.url.path}: {exc}"
|
|
)
|
|
return JSONResponse(
|
|
status_code=500,
|
|
content={"message": "服务内部错误,请稍后重试"},
|
|
)
|
|
|
|
@app.get("/boom")
|
|
async def boom():
|
|
raise RuntimeError("internal stack trace with secrets")
|
|
|
|
return app
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_unhandled_exception_returns_masked_500(app_with_global_handler):
|
|
transport = ASGITransport(app=app_with_global_handler, raise_app_exceptions=False)
|
|
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
|
resp = await client.get("/boom")
|
|
|
|
assert resp.status_code == 500
|
|
body = resp.json()
|
|
assert body == {"message": "服务内部错误,请稍后重试"}
|
|
# 关键:不能把内部 traceback 透出
|
|
assert "internal stack trace with secrets" not in resp.text
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_cors_preflight_with_explicit_origin():
|
|
app = FastAPI()
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["https://app.example.com"],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
@app.get("/x")
|
|
async def _x():
|
|
return {"ok": True}
|
|
|
|
transport = ASGITransport(app=app)
|
|
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
|
resp = await client.options(
|
|
"/x",
|
|
headers={
|
|
"Origin": "https://app.example.com",
|
|
"Access-Control-Request-Method": "GET",
|
|
},
|
|
)
|
|
|
|
assert resp.headers.get("access-control-allow-origin") == "https://app.example.com"
|
|
assert resp.headers.get("access-control-allow-credentials") == "true"
|