mock_server.py 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
  1. """
  2. 配置驱动的多端点 Mock(AIDOP 全模块双模式契约测试)。
  3. 契约与 mock_receipt_api.py 完全一致,严格对齐 MdpApiPullExecutor.cs:
  4. - GET,单次请求,不分页;
  5. - Bearer Token 鉴权(api_auth_type='TOKEN',token 见环境变量 MOCK_TOKEN);
  6. - 响应数组位于 data.list(mdp_entity.response_data_path='data.list');
  7. - 每行去重键 bizKey(mdp_entity.dedup_key_path='bizKey');
  8. - 可选 ?cursor=<上批最后一行 bizKey>:仅返回 bizKey 大于该游标的行。
  9. 端点由 endpoints.json 配置:{ "/api/<obj>": "samples/<file>.json" }。
  10. 新增对象只需在 endpoints.json 加一行 + 放一份样本 JSON(数组,每行含 bizKey),无需改本脚本。
  11. 启动:
  12. pip install -r requirements.txt
  13. set MOCK_TOKEN=uat-mock-token # bash: export MOCK_TOKEN=uat-mock-token
  14. uvicorn mock_server:app --host 127.0.0.1 --port 8018
  15. """
  16. import json
  17. import os
  18. from pathlib import Path
  19. from fastapi import FastAPI, Header, HTTPException, Query, Request
  20. app = FastAPI(title="AIDOP Mock Multi-Endpoint API", version="1.0.0")
  21. _BASE = Path(__file__).parent
  22. _TOKEN = os.environ.get("MOCK_TOKEN", "uat-mock-token")
  23. def _load_endpoints() -> dict[str, str]:
  24. with (_BASE / "endpoints.json").open("r", encoding="utf-8") as f:
  25. return json.load(f)
  26. def _load_rows(rel_path: str) -> list[dict]:
  27. p = _BASE / rel_path
  28. if not p.exists():
  29. raise HTTPException(status_code=404, detail=f"sample not found: {rel_path}")
  30. with p.open("r", encoding="utf-8") as f:
  31. return json.load(f)
  32. def _check_auth(authorization: str | None) -> None:
  33. if authorization != f"Bearer {_TOKEN}":
  34. raise HTTPException(status_code=401, detail="invalid or missing bearer token")
  35. @app.get("/__endpoints")
  36. def list_endpoints():
  37. return {"token_env": "MOCK_TOKEN", "endpoints": _load_endpoints()}
  38. @app.get("/health")
  39. def health():
  40. return {"status": "UP"}
  41. @app.get("/api/{obj:path}")
  42. def get_object(
  43. obj: str,
  44. request: Request,
  45. cursor: str | None = Query(default=None, description="上批最后一行 bizKey(增量游标)"),
  46. authorization: str | None = Header(default=None),
  47. ):
  48. _check_auth(authorization)
  49. endpoints = _load_endpoints()
  50. path = request.url.path
  51. if path not in endpoints:
  52. raise HTTPException(status_code=404, detail=f"no mock for {path}; add it to endpoints.json")
  53. rows = _load_rows(endpoints[path])
  54. rows.sort(key=lambda r: str(r.get("bizKey", "")))
  55. if cursor:
  56. rows = [r for r in rows if str(r.get("bizKey", "")) > cursor]
  57. return {"code": 0, "message": "ok", "data": {"list": rows}}