- 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 进度同步
53 lines
2.4 KiB
Python
53 lines
2.4 KiB
Python
"""模拟交易网关路由(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
|