Files
group_fqcd_jr/tests/unit/api/test_trace_id_middleware.py
T
张胜宇 e239eb778b docs: 品牌全量口径统一为「南方基金」+ 作废文档清理
1) 客服 Agent 四份交付文档 + 构建脚手架:品牌由包装占位 XX科技 / 旧名 南方财富
   统一为南方基金(热线 400-889-8899 / 官网 nffund.com),系统名改为「智能服务系统」;
   同步追加 §0.4 修订记录行,工程记录行保留原占位字面以支撑硬编码扫描验收。
2) 开发文档:清理 28 份已作废/残留文档(14 份移出归档 + 14 份仓库副本),
   新增《文档规整方案与开发前待决事项-2026-09-17》。
3) 客服agent 四份交付文档首次纳入本分支。
2026-09-17 15:15:22 +08:00

61 lines
2.0 KiB
Python

"""`X-Trace-ID` 响应头契约测试。
背景:接口文档把它列为所有响应的必备头,但实现里**完全没有**——客户端拿到错误时无法把
请求与服务端日志对上。中间件独立成模块正是为了能这样直接测三条分支。
"""
from starlette.requests import Request
from starlette.responses import Response
from app.api.middleware import TRACE_ID_HEADER, attach_trace_id
from app.core.contracts import RequestContext
def build_request(
headers: dict[str, str] | None = None, *, context: RequestContext | None = None
) -> Request:
scope = {
"type": "http",
"method": "GET",
"path": "/",
"query_string": b"",
"headers": [
(key.lower().encode("latin-1"), value.encode("latin-1"))
for key, value in (headers or {}).items()
],
}
request = Request(scope)
if context is not None:
request.state.request_context = context
return request
async def passthrough(_request: Request) -> Response:
return Response()
async def test_request_context_trace_id_wins_over_header() -> None:
context = RequestContext(user_id="1", trace_id="server-trace")
request = build_request({"X-Trace-ID": "client-trace"}, context=context)
response = await attach_trace_id(request, passthrough)
assert response.headers[TRACE_ID_HEADER] == "server-trace"
async def test_falls_back_to_incoming_header_when_context_missing() -> None:
"""认证失败等场景还没建立上下文,此时透传客户端带来的 id 而不是凭空造一个。"""
request = build_request({"X-Trace-ID": "client-trace"})
response = await attach_trace_id(request, passthrough)
assert response.headers[TRACE_ID_HEADER] == "client-trace"
async def test_no_header_when_neither_context_nor_request_header() -> None:
request = build_request()
response = await attach_trace_id(request, passthrough)
assert TRACE_ID_HEADER not in response.headers