Files
shaguabijia-app-server/tests/test_invite_compare_reward.py
T
no_gen_mu 5cdde8f6c6 feat(invite): 好友列表已比价状态(is_compared) + DEV 造虚拟好友接口; fix(deploy): 每日兑换定时器锁北京时区
邀请:
- get_invitees 返回增加 is_compared 字段(= compare_reward_granted),
  客户端据此区分好友列表"去提醒/邀请成功"、在途列表只取未比价、算在途好友数与收益。
- 新增 POST/DELETE /invite/seed-fake 开发接口(仅非生产, is_prod 挡):造未比价+已比价虚拟好友,
  已比价的走真实入账发 2 元邀请奖励金, 便于真机联调在途/好友列表 UI; 清理原路退回不留脏账。

兑换时区:
- daily-exchange.service/.timer 锁定 Asia/Shanghai, 避免按服务器本地时区在错误时刻触发每日兑换。
- 新增 test_daily_exchange_timezone 覆盖同一北京日内幂等。

测试: 邀请 + 兑换时区相关用例全过(28 passed)。
(仓库预存的 3 个 proxy 转发测试失败与本改动无关, 已在干净 HEAD 上复现确认。)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 22:24:40 +08:00

132 lines
5.3 KiB
Python

"""邀请 v3 比价发奖测试:好友完成成功比价 → 邀请人得邀请奖励金(独立账户),幂等只发一次,
账户隔离不串金币/现金。被邀请人绑定不得任何奖励由 test_invite.py 覆盖。
"""
from __future__ import annotations
from sqlalchemy import select
from app.core.rewards import INVITE_COMPARE_REWARD_CENTS
from app.db.session import SessionLocal
from app.models.wallet import CoinAccount, InviteCashTransaction
from app.repositories.user import get_user_by_phone
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 _my_code(client, token: str) -> str:
return client.get("/api/v1/invite/me", headers=_auth(token)).json()["invite_code"]
def _bind(client, token: str, code: str):
return client.post("/api/v1/invite/bind", json={"invite_code": code}, headers=_auth(token))
def _report_compare(client, token: str, trace_id: str, status: str = "success"):
return client.post(
"/api/v1/compare/record",
json={"trace_id": trace_id, "status": status},
headers=_auth(token),
)
def _invite_cash(phone: str) -> int:
"""被试用户的邀请奖励金余额(分)。专用查询端点见小步3,这里直接查 DB。"""
with SessionLocal() as db:
u = get_user_by_phone(db, phone)
acc = db.get(CoinAccount, u.id) if u else None
return acc.invite_cash_balance_cents if acc else 0
def test_compare_reward_granted_to_inviter(client) -> None:
"""B 被 A 邀请后完成一次成功比价 → A 得邀请奖励金 2 元(独立账户),B 不得该奖。"""
a = _login(client, "13800003001")
b = _login(client, "13800003002")
_bind(client, b, _my_code(client, a))
assert _invite_cash("13800003001") == 0 # 发奖前
r = _report_compare(client, b, "trace-reward-1")
assert r.status_code == 200, r.text
assert _invite_cash("13800003001") == INVITE_COMPARE_REWARD_CENTS # 邀请人到账
assert _invite_cash("13800003002") == 0 # 被邀请人不得此奖
def test_compare_reward_idempotent(client) -> None:
"""好友比价多次 → 只发一次(compare_reward_granted 幂等)。"""
a = _login(client, "13800003003")
b = _login(client, "13800003004")
_bind(client, b, _my_code(client, a))
_report_compare(client, b, "trace-idem-1")
_report_compare(client, b, "trace-idem-2") # 第二次比价(不同 trace)
_report_compare(client, b, "trace-idem-1") # 重复上报同 trace
assert _invite_cash("13800003003") == INVITE_COMPARE_REWARD_CENTS # 仍只发一次
def test_invitees_is_compared_flag_flips_after_compare(client) -> None:
"""好友列表 is_compared:好友比价前 False(在途/去提醒)、比价后 True(邀请成功)。"""
a = _login(client, "13800003021")
b = _login(client, "13800003022")
_bind(client, b, _my_code(client, a))
with SessionLocal() as db:
b_name = get_user_by_phone(db, "13800003022").nickname
def _b_item():
items = client.get("/api/v1/invite/invitees", headers=_auth(a)).json()["items"]
return next(it for it in items if it["display_name"] == b_name)
assert _b_item()["is_compared"] is False # 比价前:未比价(在途 / 去提醒)
_report_compare(client, b, "trace-flag-1") # B 完成一次成功比价
assert _b_item()["is_compared"] is True # 比价后:已比价(邀请成功)
def test_compare_no_relation_no_reward(client) -> None:
"""没有邀请关系的人比价 → 不发奖(没人是他的邀请人)。"""
x = _login(client, "13800003005")
r = _report_compare(client, x, "trace-norel-1")
assert r.status_code == 200, r.text
assert _invite_cash("13800003005") == 0
def test_compare_failed_no_reward(client) -> None:
"""失败的比价(status=failed)不触发发奖。"""
a = _login(client, "13800003006")
b = _login(client, "13800003007")
_bind(client, b, _my_code(client, a))
_report_compare(client, b, "trace-fail-1", status="failed")
assert _invite_cash("13800003006") == 0
def test_compare_reward_isolated_from_coin_cash(client) -> None:
"""账户隔离:邀请奖励金进 invite_cash,不串 cash_balance_cents;流水落 invite_cash_transaction。"""
a = _login(client, "13800003008")
b = _login(client, "13800003009")
_bind(client, b, _my_code(client, a))
_report_compare(client, b, "trace-iso-1")
with SessionLocal() as db:
ua = get_user_by_phone(db, "13800003008")
acc = db.get(CoinAccount, ua.id)
assert acc.invite_cash_balance_cents == INVITE_COMPARE_REWARD_CENTS # 奖励金到账
assert acc.cash_balance_cents == 0 # 没串进金币现金
txns = db.execute(
select(InviteCashTransaction).where(
InviteCashTransaction.user_id == ua.id,
InviteCashTransaction.biz_type == "invite_reward",
)
).scalars().all()
assert len(txns) == 1
assert txns[0].amount_cents == INVITE_COMPARE_REWARD_CENTS