"""``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"