Files
group_xinghuo_jinrong/app/api/simulate.py
T
GaoYiYuan_0626 a78e9d4d31 fix: B6 复审收口——测试 Redis 隔离/网关审计口径 platform/死常量清理/回归断言补强
- P2: test_risk_api env 注入 FakePublisher,aml 推送不触真 Redis
- P3: deny/_authz_audit 增 agent_type 参数,simulate 越权审计传 platform
  (与网关放行审计同口径);删 HANDLE_RESULTS 死常量(Literal 单处定义)
- P3: 补 start_date/end_date 过滤+分页 422 边界+suitability api 层审计断言;
  补 app/service/risk/__init__.py
- 挂账: B7 行新增④项(启动期拒绝/引擎工厂覆盖三实例化点/handle 原子性/
  input_guard_log);B9b 行核查单(disclaimer/scan 幂等/Swagger 手测/analyst);
  TODO.md T-30/T-31/T-32 进度同步
2026-09-06 19:21:15 +08:00

53 lines
2.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""模拟交易网关路由(PRD FR-1 · 薄路由,不含业务)。
鉴权:`Depends(get_auth_context)`(B6 回挂,评审 P2-2)——一期接受
risk_demo 演示账号或客户本人(auth.customer_id == 请求 customer_id,
PRD FR-1 §鉴权);越权经 deps.deny 审计后 403。T-01 后工厂内部换 JWT。
trace:B7 中间件贯通;B7 前由 service 层 ensure_trace 兜底。
挂载:B7 集成 main.py(当前仅 TestClient 独立挂 router 验证)。
统一响应外壳:utils/response.py 为占位(P1 任务),落地点挂账 B7(届时
simulate/risk 一并包裹,本路由返回体不变)。
"""
from __future__ import annotations
from decimal import Decimal
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
from app.api.deps import AuthContext, deny, get_auth_context
from app.gateway.trade_gateway import UnsupportedTradeType, submit_trade
from app.repository.risk_repository import RiskRepository
router = APIRouter(prefix="/api/simulate", tags=["simulate"])
def _repo() -> RiskRepository:
"""审计仓储(deny 留痕用;测试 monkeypatch 点)。"""
return RiskRepository()
class TradeRequest(BaseModel):
customer_id: str = Field(..., min_length=1)
product_id: str = Field(..., min_length=1)
trade_type: str = Field(..., max_length=16, description="subscribe | redeem;convert 显式拒绝")
amount: Decimal = Field(..., gt=0, description="交易金额(元),必须为正数")
@router.post("/trade")
def submit_trade_api(req: TradeRequest, auth: AuthContext = Depends(get_auth_context)) -> dict:
"""模拟交易(FR-1):适当性阻断或放行+引擎判定,返回 blocked + trade_id。"""
if not (auth.has_role("risk_demo") or (auth.is_customer() and auth.customer_id == req.customer_id)):
deny(
auth, "AUTH_403_ROLE", _repo(),
customer_id=req.customer_id, message="risk_demo or owner customer only",
agent_type="platform", # 网关越权与放行审计同口径(复审 P3)
)
try:
return submit_trade(req.model_dump())
except UnsupportedTradeType as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
except LookupError as exc: # NotFoundError 亦为其子类;已统一(B6 评审 P3-5)
raise HTTPException(status_code=404, detail=str(exc)) from exc