"""比价战绩里程碑测试:进度随成功比价数解锁 / 逐档领奖发金币 / 幂等 / 边界。 复用比价记录上报接口造成功记录(每条 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)。""" # trace_id 现全局唯一(prod 由 app-server 签发 UUID);测试共用 session DB,故按 token # (每用户不同)加前缀防跨用户/跨文件复用 "s-1" 撞车(见 comparison_record_trace_unique 迁移)。 payload = { "trace_id": f"{token[-16:]}:{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": f"{token[-16:]}:{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_marks_claimed_without_coin(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"] == 0 # 不再发金币 assert res["coin_balance"] == bal_before # 余额不变 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