Merge remote-tracking branch 'origin/qyqy_develop' into RM2_develop
This commit is contained in:
@@ -63,3 +63,23 @@ async def test_products_endpoint_is_read_only(method: str) -> None:
|
||||
assert response.status_code in (401, 405), (
|
||||
f"{method} {PRODUCTS_PATH} -> {response.status_code},只读端点不应接受写方法"
|
||||
)
|
||||
|
||||
|
||||
NAV_HISTORY_PATH = "/api/v1/products/510300/nav-history"
|
||||
|
||||
|
||||
async def test_nav_history_endpoint_is_registered_and_protected() -> None:
|
||||
"""P002 路由必须存在;缺 token 时 401(鉴权层拦下)而不是 404(漏注册)。"""
|
||||
response = await send("GET", NAV_HISTORY_PATH)
|
||||
assert response.status_code == 401, (
|
||||
f"GET {NAV_HISTORY_PATH} 未授权应 401,实际 {response.status_code}(路由可能漏注册)"
|
||||
)
|
||||
|
||||
|
||||
async def test_nav_history_rejects_out_of_range_days() -> None:
|
||||
"""`days` 有界(1–365):越界应被校验挡下,不能让调用方一次拉全表。"""
|
||||
response = await send("GET", f"{NAV_HISTORY_PATH}?days=100000")
|
||||
# 未带令牌时鉴权先失败也是可接受的;关键是**不能**是 200
|
||||
assert response.status_code in (401, 422), (
|
||||
f"days=100000 -> {response.status_code},越界应被拒绝"
|
||||
)
|
||||
|
||||
@@ -73,3 +73,57 @@ async def test_visitor_token_can_read_listed_products() -> None:
|
||||
|
||||
# 价格类字段一律是字符串(与既有接口口径一致)
|
||||
assert isinstance(first["current_nav"], str)
|
||||
|
||||
|
||||
async def test_visitor_token_can_read_nav_history() -> None:
|
||||
"""P002:访客能取到历史净值序列(详情页走势图的数据源)。
|
||||
|
||||
`fin_nav_history` 为空时返回 `count=0` 与空数组 —— 这是**合法响应**,
|
||||
不是错误:前端据此显示"尚未接入",而不得回退到编造的曲线。
|
||||
"""
|
||||
app = create_app()
|
||||
transport = httpx.ASGITransport(app=app)
|
||||
async with httpx.AsyncClient(
|
||||
transport=transport, base_url="http://test", timeout=30
|
||||
) as client:
|
||||
issued = await client.post("/api/v1/visitor-tokens")
|
||||
token = issued.json()["access_token"]
|
||||
auth = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
listed = await client.get("/api/v1/products", headers=auth)
|
||||
products = listed.json()["data"]["products"]
|
||||
assert products, "产品库为空,净值用例无从下手"
|
||||
code = products[0]["product_code"]
|
||||
|
||||
response = await client.get(
|
||||
f"/api/v1/products/{code}/nav-history", params={"days": 30}, headers=auth
|
||||
)
|
||||
|
||||
assert response.status_code == 200, response.text
|
||||
data = response.json()["data"]
|
||||
assert data["product_code"] == code
|
||||
|
||||
points = data["points"]
|
||||
assert data["count"] == len(points)
|
||||
if not points:
|
||||
# 还没跑 `tools/sync_nav_history.py` —— 允许,但必须是"干净的空"
|
||||
return
|
||||
assert points[0]["nav_date"] <= points[-1]["nav_date"], "净值序列必须按日期升序"
|
||||
assert all(isinstance(item["nav"], str) for item in points)
|
||||
assert len(points) <= 30
|
||||
|
||||
|
||||
async def test_nav_history_returns_404_for_unknown_product() -> None:
|
||||
"""不存在的产品必须是 404,而不是空数组 —— 否则前端分不清"没有数据"和"没有这只"。"""
|
||||
app = create_app()
|
||||
transport = httpx.ASGITransport(app=app)
|
||||
async with httpx.AsyncClient(
|
||||
transport=transport, base_url="http://test", timeout=30
|
||||
) as client:
|
||||
issued = await client.post("/api/v1/visitor-tokens")
|
||||
token = issued.json()["access_token"]
|
||||
response = await client.get(
|
||||
"/api/v1/products/999999/nav-history",
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
)
|
||||
assert response.status_code == 404, response.text
|
||||
|
||||
Reference in New Issue
Block a user