0fc8521c3b
一次比价/领券的 trace_id 改由后端签发,让前端 SLS 运行日志(trace_id 索引列)、 app-server 比价记录与领券流水、pricebot trace 目录/run.log/trace_url 全链共用同一个 id 查到底(此前前端各业务自己 randomUUID,虽同链但非后端签发、也无单一签发点)。 - POST /api/v1/compare/start(预占额度,任务第一个请求,签发与建 running 行合一): 请求 trace_id 改可选,缺省时服务端签发 uuid;响应新增 trace_id 字段返回(签发的或 回显客户端带来的)。客户端带值则沿用——老客户端兼容 + 同 trace 重试幂等。 - POST /api/v1/coupon/session:started 帧缺 trace_id 时签发并随响应返回(签发不依赖 写库成功);非 started 帧缺 trace_id 不签发、不写库(收尾没有 id 只能是异常调用, 签发新 id 只会造出查不到发起信息的孤儿行)。新增 CouponSessionOut 响应模型——原 dict[str,bool] 注解无法承载字符串 trace_id,FastAPI 响应校验会炸。 - 测试:更新 2 处旧断言(响应体多出 trace_id 字段),新增 compare 不带 id 签发用例 + coupon started 签发/回显、终尾缺 id 跳过写库 3 个用例。全量 30 passed + ruff clean。 配合 shaguabijia-app-android 同名分支 feat-unify-trace-id-backend-issued 的前端换源改动。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Reviewed-on: #210
66 lines
2.1 KiB
Python
66 lines
2.1 KiB
Python
"""POST /api/v1/coupon/session 的 trace_id 签发行为(统一 trace_id 由后端下发)。
|
|
|
|
- started 不带 trace_id → 服务端签发并返回,行以签发 id 建;
|
|
- started 带 trace_id → 回显沿用(老客户端兼容);
|
|
- 非 started 缺 trace_id → 不签发不写库,trace_id=null(防孤儿行)。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from sqlalchemy import func, select
|
|
|
|
from app.db.session import SessionLocal
|
|
from app.models.coupon_state import CouponSession
|
|
|
|
|
|
def _base_payload(**overrides) -> dict:
|
|
payload = {
|
|
"device_id": "cs-issue-device",
|
|
"status": "started",
|
|
"started_at_ms": 1_722_000_000_000,
|
|
"platforms": ["meituan-waimai"],
|
|
"app_env": "dev",
|
|
}
|
|
payload.update(overrides)
|
|
return payload
|
|
|
|
|
|
def test_session_started_issues_trace_id_when_absent(client) -> None:
|
|
response = client.post("/api/v1/coupon/session", json=_base_payload())
|
|
assert response.status_code == 200, response.text
|
|
body = response.json()
|
|
assert body["ok"] is True
|
|
issued = body["trace_id"]
|
|
assert issued
|
|
with SessionLocal() as db:
|
|
row = db.execute(
|
|
select(CouponSession).where(CouponSession.trace_id == issued)
|
|
).scalar_one()
|
|
assert row.device_id == "cs-issue-device"
|
|
assert row.status == "started"
|
|
|
|
|
|
def test_session_started_echoes_client_trace_id(client) -> None:
|
|
response = client.post(
|
|
"/api/v1/coupon/session", json=_base_payload(trace_id="cs-legacy-1")
|
|
)
|
|
assert response.status_code == 200, response.text
|
|
assert response.json()["trace_id"] == "cs-legacy-1"
|
|
with SessionLocal() as db:
|
|
count = db.scalar(
|
|
select(func.count(CouponSession.id)).where(
|
|
CouponSession.trace_id == "cs-legacy-1"
|
|
)
|
|
)
|
|
assert count == 1
|
|
|
|
|
|
def test_session_terminal_without_trace_id_skips_write(client) -> None:
|
|
response = client.post(
|
|
"/api/v1/coupon/session",
|
|
json=_base_payload(status="completed", elapsed_ms=1234, claimed_count=2),
|
|
)
|
|
assert response.status_code == 200, response.text
|
|
body = response.json()
|
|
assert body["ok"] is True
|
|
assert body["trace_id"] is None
|