feat(savings): 省钱战绩卡比价口径 + 关停 savings demo 兜底
- 新增 GET /api/v1/compare/stats(comparison_record: status=success 计数 + saved_amount_cents 求和),供「我的」页省钱战绩卡比价口径(完成比价 + 累计发现可省),不要求下单 - savings.py 停用 demo seeder(ensure_seeded 等移除),只计真实 source='compare';简化 beat_percent 为 compare-only - 测试: compare/stats 用例 + savings/welfare 改纯真实语义 - 文档: 新增 compare-stats.md;更新 savings-battle.md / savings_record.md / comparison_record.md / API README Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -193,11 +193,38 @@ def test_detail_cross_user_404(client) -> None:
|
||||
assert r.status_code == 404
|
||||
|
||||
|
||||
def test_stats_compare_count_and_saved(client) -> None:
|
||||
"""比价口径战绩:完成比价数 + 累计发现可省(各成功比价 saved 之和)。"""
|
||||
token = _login(client, "13800002011")
|
||||
# 新用户:无记录 → 全 0(配合「我的」页未比价锁定态)
|
||||
s0 = client.get("/api/v1/compare/stats", headers=_auth(token)).json()
|
||||
assert s0["compare_count"] == 0
|
||||
assert s0["discovered_saved_cents"] == 0
|
||||
|
||||
# 报 2 条成功比价(各省 128.50−123.50=5.00 元=500 分)
|
||||
client.post("/api/v1/compare/record", json=_food_payload("stat-1"), headers=_auth(token))
|
||||
client.post("/api/v1/compare/record", json=_food_payload("stat-2"), headers=_auth(token))
|
||||
s = client.get("/api/v1/compare/stats", headers=_auth(token)).json()
|
||||
assert s["compare_count"] == 2
|
||||
assert s["discovered_saved_cents"] == 1000
|
||||
|
||||
# 失败帧(只有源平台)不计入成功比价数
|
||||
fail = _food_payload("stat-fail")
|
||||
fail["comparison_results"] = [
|
||||
{"platform_id": "taobao_flash", "platform_name": "淘宝闪购",
|
||||
"package": "com.taobao.taobao", "price": 128.50, "is_source": True, "rank": 1},
|
||||
]
|
||||
client.post("/api/v1/compare/record", json=fail, headers=_auth(token))
|
||||
s2 = client.get("/api/v1/compare/stats", headers=_auth(token)).json()
|
||||
assert s2["compare_count"] == 2 # 仍 2(failed 不计)
|
||||
|
||||
|
||||
def test_requires_auth(client) -> None:
|
||||
"""不带 token 统一 401。"""
|
||||
assert client.post("/api/v1/compare/record", json={"trace_id": "t"}).status_code == 401
|
||||
assert client.get("/api/v1/compare/records").status_code == 401
|
||||
assert client.get("/api/v1/compare/records/1").status_code == 401
|
||||
assert client.get("/api/v1/compare/stats").status_code == 401
|
||||
|
||||
|
||||
def test_trace_id_required(client) -> None:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""记账测试:比价后下单上报 → 写 savings_record(source='compare')。
|
||||
|
||||
覆盖:上报落库 + 门店/菜品/原价随之入库、(user,client_event_id) 幂等、
|
||||
真实(compare)记录优先于 demo seeder、省额 = 源平台原价 − 实付。
|
||||
纯真实口径(demo 兜底已停用:新用户空、上报后才有数)、省额 = 源平台原价 − 实付。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -78,12 +78,21 @@ def test_order_report_idempotent(client) -> None:
|
||||
assert s["order_count"] == 1
|
||||
|
||||
|
||||
def test_real_overrides_demo_seed(client) -> None:
|
||||
"""先触发 demo seeder(多笔),再上报一笔真实 → 统计切换为只算真实那笔。"""
|
||||
def test_fresh_user_empty_until_report(client) -> None:
|
||||
"""纯真实(demo 兜底已停用):新用户 summary/battle/records 全空,上报后才有数。"""
|
||||
token = _login(client, "13800002003")
|
||||
demo = client.get("/api/v1/savings/summary", headers=_auth(token)).json()
|
||||
assert demo["order_count"] > 1 # demo 是多笔
|
||||
|
||||
# 未上报:无任何 demo,统计全 0、明细空
|
||||
s0 = client.get("/api/v1/savings/summary", headers=_auth(token)).json()
|
||||
assert s0["order_count"] == 0
|
||||
assert s0["total_saved_cents"] == 0
|
||||
b0 = client.get("/api/v1/savings/battle", headers=_auth(token)).json()
|
||||
assert b0["compare_count"] == 0
|
||||
assert b0["streak_days"] == 0
|
||||
p0 = client.get("/api/v1/savings/records", headers=_auth(token)).json()
|
||||
assert p0["items"] == []
|
||||
|
||||
# 上报一笔真实 → 统计只算这一笔
|
||||
client.post(
|
||||
"/api/v1/order/report",
|
||||
json=_report_body(
|
||||
@@ -92,7 +101,7 @@ def test_real_overrides_demo_seed(client) -> None:
|
||||
headers=_auth(token),
|
||||
)
|
||||
s = client.get("/api/v1/savings/summary", headers=_auth(token)).json()
|
||||
assert s["order_count"] == 1 # demo 被忽略
|
||||
assert s["order_count"] == 1
|
||||
assert s["total_saved_cents"] == 800 # 5000 − 4200
|
||||
|
||||
page = client.get("/api/v1/savings/records", headers=_auth(token)).json()
|
||||
|
||||
+31
-7
@@ -253,38 +253,62 @@ def test_exchange_insufficient_and_invalid(client) -> None:
|
||||
assert r.status_code == 400
|
||||
|
||||
|
||||
def _report_savings(client, token: str, n: int, *, original=5000, paid=4200) -> None:
|
||||
"""上报 n 笔真实比价记录(source='compare'),省额 = original − paid。"""
|
||||
for i in range(n):
|
||||
client.post(
|
||||
"/api/v1/order/report",
|
||||
json={
|
||||
"client_event_id": f"evt-{token[-6:]}-{i}",
|
||||
"platform": "美团外卖",
|
||||
"pay_channel": "wechat",
|
||||
"compared_price_cents": paid,
|
||||
"paid_amount_cents": paid,
|
||||
"original_price_cents": original,
|
||||
"shop_name": f"测试门店{i}",
|
||||
"dishes": ["招牌菜"],
|
||||
},
|
||||
headers=_auth(token),
|
||||
)
|
||||
|
||||
|
||||
def test_savings_summary_and_battle(client) -> None:
|
||||
"""首次访问触发 demo seeder,聚合接口返回真实算出的数字。"""
|
||||
"""聚合接口对真实上报记录返回真实算出的数字(demo 兜底已停用)。"""
|
||||
token = _login(client, "13800001007")
|
||||
_report_savings(client, token, 2) # 2 笔,各省 5000−4200=800
|
||||
|
||||
r = client.get("/api/v1/savings/summary", headers=_auth(token))
|
||||
assert r.status_code == 200, r.text
|
||||
s = r.json()
|
||||
assert s["order_count"] > 0
|
||||
assert s["total_saved_cents"] > 0
|
||||
assert s["order_count"] == 2
|
||||
assert s["total_saved_cents"] == 1600
|
||||
# 平均每单 = 总额 // 单数
|
||||
assert s["avg_saved_cents"] == s["total_saved_cents"] // s["order_count"]
|
||||
|
||||
r = client.get("/api/v1/savings/battle", headers=_auth(token))
|
||||
assert r.status_code == 200, r.text
|
||||
b = r.json()
|
||||
assert b["streak_days"] >= 1
|
||||
assert b["streak_days"] >= 1 # 今日上报 → 至少连续 1 天
|
||||
assert b["week_saved_cents"] >= 0
|
||||
assert 0 <= b["beat_percent"] <= 100
|
||||
# 完成比价次数与 summary 的 order_count 同源(均 = 有效记录数)
|
||||
# 完成比价次数与 summary 的 order_count 同源(均 = 真实记录数)
|
||||
assert b["compare_count"] == s["order_count"]
|
||||
|
||||
|
||||
def test_savings_seeder_idempotent(client) -> None:
|
||||
"""重复访问不应重复灌数据:两次 summary 完全一致。"""
|
||||
def test_savings_empty_for_new_user(client) -> None:
|
||||
"""新用户无真实上报时全 0(demo 兜底已停用),两次读一致。"""
|
||||
token = _login(client, "13800001008")
|
||||
first = client.get("/api/v1/savings/summary", headers=_auth(token)).json()
|
||||
second = client.get("/api/v1/savings/summary", headers=_auth(token)).json()
|
||||
assert first == second
|
||||
assert first["order_count"] == 0
|
||||
assert first["total_saved_cents"] == 0
|
||||
|
||||
|
||||
def test_savings_records_pagination(client) -> None:
|
||||
token = _login(client, "13800001009")
|
||||
_report_savings(client, token, 12) # 12 笔真实记录撑起分页
|
||||
|
||||
# 第一页
|
||||
r = client.get("/api/v1/savings/records?limit=5", headers=_auth(token))
|
||||
assert r.status_code == 200, r.text
|
||||
|
||||
Reference in New Issue
Block a user