feat(compare): 比价记录落库 + 战绩里程碑(真发金币)
- comparison_record 表 + 上报(幂等 user+trace)/列表/详情 接口;服务端派生 best/saved/is_source_best/status + information(done 帧文案/失败具体原因) - 战绩里程碑:comparison_milestone_claim 表 + 进度/领取接口,按成功比价次数 解锁,复用 grant_coins 真发金币(RECORD_MILESTONES 120/180/300/500/800/1200) - 迁移 record/information/milestone 三个,单 head;rewards.py 集中档位配置 - 测试 test_compare_record(8)+test_compare_milestone(7) 全绿;docs/api 12a-12e + 索引 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
"""比价战绩里程碑测试:进度随成功比价数解锁 / 逐档领奖发金币 / 幂等 / 边界。
|
||||
|
||||
复用比价记录上报接口造成功记录(每条 trace 一次成功比价),再验里程碑。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from app.core.rewards import RECORD_MILESTONES
|
||||
|
||||
|
||||
def _login(client, phone: str) -> str:
|
||||
client.post("/api/v1/auth/sms/send", json={"phone": phone})
|
||||
r = client.post("/api/v1/auth/sms/login", json={"phone": phone, "code": "123456"})
|
||||
assert r.status_code == 200, r.text
|
||||
return r.json()["access_token"]
|
||||
|
||||
|
||||
def _auth(token: str) -> dict[str, str]:
|
||||
return {"Authorization": f"Bearer {token}"}
|
||||
|
||||
|
||||
def _report_success(client, token: str, trace_id: str) -> None:
|
||||
"""上报一条成功比价(有非源有效价 → status 派生 success)。"""
|
||||
payload = {
|
||||
"trace_id": trace_id,
|
||||
"business_type": "food",
|
||||
"store_name": "测试店",
|
||||
"source_platform_id": "taobao_flash",
|
||||
"source_price": 30.0,
|
||||
"comparison_results": [
|
||||
{"platform_id": "meituan", "platform_name": "美团", "price": 25.0,
|
||||
"is_source": False, "rank": 1},
|
||||
{"platform_id": "taobao_flash", "platform_name": "淘宝闪购", "price": 30.0,
|
||||
"is_source": True, "rank": 2},
|
||||
],
|
||||
}
|
||||
r = client.post("/api/v1/compare/record", json=payload, headers=_auth(token))
|
||||
assert r.status_code == 200, r.text
|
||||
|
||||
|
||||
def _report_failed(client, token: str, trace_id: str) -> None:
|
||||
"""上报一条失败比价(只有源 → status 派生 failed,不计入解锁)。"""
|
||||
payload = {
|
||||
"trace_id": trace_id,
|
||||
"business_type": "food",
|
||||
"comparison_results": [
|
||||
{"platform_id": "taobao_flash", "platform_name": "淘宝闪购", "price": 30.0,
|
||||
"is_source": True, "rank": 1},
|
||||
],
|
||||
}
|
||||
r = client.post("/api/v1/compare/record", json=payload, headers=_auth(token))
|
||||
assert r.status_code == 200, r.text
|
||||
|
||||
|
||||
def test_status_empty(client) -> None:
|
||||
"""没比价过:成功数 0,所有档 locked,无可领。"""
|
||||
token = _login(client, "13800003001")
|
||||
r = client.get("/api/v1/compare/milestones", headers=_auth(token))
|
||||
assert r.status_code == 200, r.text
|
||||
d = r.json()
|
||||
assert d["success_count"] == 0
|
||||
assert d["claimable_count"] == 0
|
||||
assert len(d["milestones"]) == len(RECORD_MILESTONES)
|
||||
assert all(m["state"] == "locked" for m in d["milestones"])
|
||||
# 第 1 档金币对齐配置
|
||||
assert d["milestones"][0]["coin"] == RECORD_MILESTONES[0]
|
||||
assert d["milestones"][0]["milestone"] == 1
|
||||
|
||||
|
||||
def test_unlock_progresses_with_success(client) -> None:
|
||||
"""成功比价 2 次:前 2 档 active 可领,其余 locked。失败记录不计入。"""
|
||||
token = _login(client, "13800003002")
|
||||
_report_success(client, token, "s-1")
|
||||
_report_success(client, token, "s-2")
|
||||
_report_failed(client, token, "f-1") # 失败不计
|
||||
|
||||
d = client.get("/api/v1/compare/milestones", headers=_auth(token)).json()
|
||||
assert d["success_count"] == 2
|
||||
assert d["claimable_count"] == 2
|
||||
states = [m["state"] for m in d["milestones"]]
|
||||
assert states[0] == "active"
|
||||
assert states[1] == "active"
|
||||
assert states[2] == "locked"
|
||||
|
||||
|
||||
def test_claim_grants_coin_and_becomes_claimed(client) -> None:
|
||||
"""领第 1 档:发对应金币,进金币余额,该档变 claimed。"""
|
||||
token = _login(client, "13800003003")
|
||||
_report_success(client, token, "s-1")
|
||||
|
||||
bal_before = client.get("/api/v1/wallet/account", headers=_auth(token)).json()["coin_balance"]
|
||||
|
||||
r = client.post("/api/v1/compare/milestones/1/claim", headers=_auth(token))
|
||||
assert r.status_code == 200, r.text
|
||||
res = r.json()
|
||||
assert res["milestone"] == 1
|
||||
assert res["coin_awarded"] == RECORD_MILESTONES[0]
|
||||
assert res["coin_balance"] == bal_before + RECORD_MILESTONES[0]
|
||||
|
||||
d = client.get("/api/v1/compare/milestones", headers=_auth(token)).json()
|
||||
assert d["milestones"][0]["state"] == "claimed"
|
||||
assert d["claimable_count"] == 0 # 第 1 档领掉了,成功数 1 没解锁第 2 档
|
||||
|
||||
|
||||
def test_claim_idempotent_409(client) -> None:
|
||||
"""同档重复领 → 409,且不重复发金币。"""
|
||||
token = _login(client, "13800003004")
|
||||
_report_success(client, token, "s-1")
|
||||
assert client.post("/api/v1/compare/milestones/1/claim", headers=_auth(token)).status_code == 200
|
||||
bal = client.get("/api/v1/wallet/account", headers=_auth(token)).json()["coin_balance"]
|
||||
|
||||
r = client.post("/api/v1/compare/milestones/1/claim", headers=_auth(token))
|
||||
assert r.status_code == 409
|
||||
bal2 = client.get("/api/v1/wallet/account", headers=_auth(token)).json()["coin_balance"]
|
||||
assert bal2 == bal # 没重复发
|
||||
|
||||
|
||||
def test_claim_locked_409(client) -> None:
|
||||
"""领还没解锁的档(成功数不够)→ 409 locked。"""
|
||||
token = _login(client, "13800003005")
|
||||
_report_success(client, token, "s-1") # 只解锁第 1 档
|
||||
r = client.post("/api/v1/compare/milestones/2/claim", headers=_auth(token))
|
||||
assert r.status_code == 409
|
||||
|
||||
|
||||
def test_claim_unknown_milestone_404(client) -> None:
|
||||
"""档位越界 → 404。"""
|
||||
token = _login(client, "13800003006")
|
||||
assert client.post("/api/v1/compare/milestones/0/claim", headers=_auth(token)).status_code == 404
|
||||
big = len(RECORD_MILESTONES) + 1
|
||||
assert client.post(f"/api/v1/compare/milestones/{big}/claim", headers=_auth(token)).status_code == 404
|
||||
|
||||
|
||||
def test_requires_auth(client) -> None:
|
||||
"""不带 token → 401。"""
|
||||
assert client.get("/api/v1/compare/milestones").status_code == 401
|
||||
assert client.post("/api/v1/compare/milestones/1/claim").status_code == 401
|
||||
Reference in New Issue
Block a user