Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cfac7b64ef | |||
| 1c61f231a0 | |||
| dd3129116c | |||
| 78cd4c2696 |
+3
-1
@@ -53,8 +53,10 @@ WITHDRAW_MAX_CENTS: int = 5_000_000 # 5 万元
|
|||||||
TASK_ENABLE_NOTIFICATION = "enable_notification"
|
TASK_ENABLE_NOTIFICATION = "enable_notification"
|
||||||
|
|
||||||
# task_key -> 奖励金币
|
# task_key -> 奖励金币
|
||||||
|
# 打开消息提醒: 1000 金币(=¥0.1, 客户端原型展示口径; 量级与签到/里程碑相称)。
|
||||||
|
# 注意: 已不再 = 兑换下限(MIN_EXCHANGE_COIN=10000), test_exchange_flow 改走 grant_coins 直接供款。
|
||||||
TASK_REWARDS: dict[str, int] = {
|
TASK_REWARDS: dict[str, int] = {
|
||||||
TASK_ENABLE_NOTIFICATION: 10000,
|
TASK_ENABLE_NOTIFICATION: 1000,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ class ComparisonRecord(Base):
|
|||||||
# ===== 明细(JSON,越详细越好)=====
|
# ===== 明细(JSON,越详细越好)=====
|
||||||
# 下单菜品 [{name, qty, specs?}]
|
# 下单菜品 [{name, qty, specs?}]
|
||||||
items: Mapped[list] = mapped_column(_JSON, nullable=False, default=list)
|
items: Mapped[list] = mapped_column(_JSON, nullable=False, default=list)
|
||||||
# 逐平台对比 [{platform_id, platform_name, package, price, is_source, rank}](price 单位:元,原样存)
|
# 逐平台对比 [{platform_id, platform_name, package, price, is_source, rank, coupon_saved}](price/coupon_saved 单位:元,原样存)
|
||||||
comparison_results: Mapped[list] = mapped_column(_JSON, nullable=False, default=list)
|
comparison_results: Mapped[list] = mapped_column(_JSON, nullable=False, default=list)
|
||||||
# 目标平台未找到、跳过的菜名
|
# 目标平台未找到、跳过的菜名
|
||||||
skipped_dish_names: Mapped[list] = mapped_column(_JSON, nullable=False, default=list)
|
skipped_dish_names: Mapped[list] = mapped_column(_JSON, nullable=False, default=list)
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ def grant_ad_reward(
|
|||||||
# 发金币 + 记一笔,同事务
|
# 发金币 + 记一笔,同事务
|
||||||
crud_wallet.grant_coins(
|
crud_wallet.grant_coins(
|
||||||
db, user_id, coin,
|
db, user_id, coin,
|
||||||
biz_type="ad_reward", ref_id=trans_id, remark="看广告奖励",
|
biz_type="ad_reward", ref_id=trans_id, remark="看视频奖励金币",
|
||||||
)
|
)
|
||||||
rec = AdRewardRecord(
|
rec = AdRewardRecord(
|
||||||
trans_id=trans_id, user_id=user_id, coin=coin, status="granted",
|
trans_id=trans_id, user_id=user_id, coin=coin, status="granted",
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
"""比价战绩里程碑 CRUD:进度查询 + 逐档领奖。
|
"""比价战绩里程碑 CRUD:进度查询 + 逐档领奖。
|
||||||
|
|
||||||
进度 = comparison_record 里 status='success' 的条数(crud_compare.count_success)。
|
进度 = comparison_record 里 status='success' 的条数(crud_compare.count_success)。
|
||||||
第 N 档在"成功次数 >= N"时解锁;每档领一次,写 comparison_milestone_claim 去重 +
|
第 N 档在"成功次数 >= N"时解锁;每档领一次,写 comparison_milestone_claim 去重(标记已领)。
|
||||||
grant_coins,同一事务 commit(领奖记录与加金币原子化)。仿一次性任务 (task.py)。
|
⚠️ 当前产品定暂不真发金币(后续整体删除该功能):claim 只写领取记录,不调 grant_coins、
|
||||||
|
不写 coin_transaction,coin_awarded 恒为 0,余额不变。仿一次性任务 (task.py)。
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -14,7 +15,6 @@ from sqlalchemy.orm import Session
|
|||||||
from app.core.rewards import (
|
from app.core.rewards import (
|
||||||
RECORD_MILESTONE_COUNT,
|
RECORD_MILESTONE_COUNT,
|
||||||
RECORD_MILESTONES,
|
RECORD_MILESTONES,
|
||||||
record_milestone_reward,
|
|
||||||
)
|
)
|
||||||
from app.models.comparison_milestone import ComparisonMilestoneClaim
|
from app.models.comparison_milestone import ComparisonMilestoneClaim
|
||||||
from app.repositories import comparison as crud_compare
|
from app.repositories import comparison as crud_compare
|
||||||
@@ -103,14 +103,13 @@ def claim(db: Session, user_id: int, milestone: int) -> tuple[int, int]:
|
|||||||
if crud_compare.count_success(db, user_id) < milestone:
|
if crud_compare.count_success(db, user_id) < milestone:
|
||||||
raise MilestoneLockedError
|
raise MilestoneLockedError
|
||||||
|
|
||||||
coin = record_milestone_reward(milestone)
|
# ⚠️ 比价战绩里程碑暂不真发金币(产品定,后续整体删除该功能):仍记一条领取(去重/标记已领),
|
||||||
|
# 但不调 grant_coins、不写 coin_transaction,余额不变,返回发放 0 金币 + 当前余额。
|
||||||
db.add(
|
db.add(
|
||||||
ComparisonMilestoneClaim(
|
ComparisonMilestoneClaim(
|
||||||
user_id=user_id, milestone=milestone, coin_awarded=coin
|
user_id=user_id, milestone=milestone, coin_awarded=0
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
acc, _ = crud_wallet.grant_coins(
|
acc = crud_wallet.get_or_create_account(db, user_id, commit=False)
|
||||||
db, user_id, coin, biz_type="compare_milestone", ref_id=str(milestone)
|
|
||||||
)
|
|
||||||
db.commit()
|
db.commit()
|
||||||
return coin, acc.coin_balance
|
return 0, acc.coin_balance
|
||||||
|
|||||||
@@ -119,7 +119,8 @@ def do_signin(db: Session, user_id: int) -> tuple[SigninRecord, int]:
|
|||||||
)
|
)
|
||||||
db.add(record)
|
db.add(record)
|
||||||
acc, _ = crud_wallet.grant_coins(
|
acc, _ = crud_wallet.grant_coins(
|
||||||
db, user_id, coin, biz_type="signin", ref_id=today.isoformat()
|
db, user_id, coin, biz_type="signin", ref_id=today.isoformat(),
|
||||||
|
remark=f"每日签到 第{cycle_day}天",
|
||||||
)
|
)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(record)
|
db.refresh(record)
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ class ComparisonResultIn(BaseModel):
|
|||||||
price: float | None = None
|
price: float | None = None
|
||||||
is_source: bool = False
|
is_source: bool = False
|
||||||
rank: int | None = None
|
rank: int | None = None
|
||||||
|
# 该平台本单用红包省的**纯红包优惠额**(元, 不含配送费/代金券/满减)。None=没用/没抠到。
|
||||||
|
# 必须显式声明: 落库走 model_dump(), pydantic 默认丢未知字段, 不声明这行会被悄悄吞掉。
|
||||||
|
# 各平台抠到红包即带值(2026-06 起源平台 Phase1 意图识别也抠, 当前仅淘宝源)。见 pricebot 侧 比价红包额留痕-实现方案.md。
|
||||||
|
coupon_saved: float | None = None
|
||||||
|
|
||||||
|
|
||||||
class ComparisonRecordIn(BaseModel):
|
class ComparisonRecordIn(BaseModel):
|
||||||
@@ -109,7 +113,7 @@ class ComparisonRecordCreatedOut(BaseModel):
|
|||||||
|
|
||||||
class MilestoneStateOut(BaseModel):
|
class MilestoneStateOut(BaseModel):
|
||||||
milestone: int = Field(..., description="档位序号 1-based(= 解锁所需的成功比价次数)")
|
milestone: int = Field(..., description="档位序号 1-based(= 解锁所需的成功比价次数)")
|
||||||
coin: int = Field(..., description="该档奖励金币")
|
coin: int = Field(..., description="该档应发金币额(产品规则值);当前领取暂不真发, 见 compare-milestone-claim 文档")
|
||||||
state: str = Field(..., description="claimed(已领) / active(可领) / locked(未解锁)")
|
state: str = Field(..., description="claimed(已领) / active(可领) / locked(未解锁)")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
> 所属:比价记录组(前缀 `/api/v1/compare`) | 鉴权:Bearer | [← 返回 API 索引](./README.md)
|
> 所属:比价记录组(前缀 `/api/v1/compare`) | 鉴权:Bearer | [← 返回 API 索引](./README.md)
|
||||||
|
|
||||||
领取某一档(第 `milestone` 次)的金币奖励,真实入账金币钱包。领取记录写
|
领取某一档(第 `milestone` 次)。⚠️ **当前不真发金币**(产品定,后续整体删除该功能):仍写
|
||||||
`comparison_milestone_claim`((user_id, milestone) 唯一)+ `grant_coins` 同一事务原子提交,
|
`comparison_milestone_claim`((user_id, milestone) 唯一)标记该档已领、**每档只能领一次**,但不调
|
||||||
**每档只能领一次**。进度口径见 [milestones 进度接口](./compare-milestones.md)。
|
`grant_coins`、不写 `coin_transaction`,余额不变,`coin_awarded` 恒为 0。进度口径见
|
||||||
|
[milestones 进度接口](./compare-milestones.md)。
|
||||||
|
|
||||||
## 入参
|
## 入参
|
||||||
- 路径参数 `milestone`(int):档位序号,1..6(= `RECORD_MILESTONES` 长度)。
|
- 路径参数 `milestone`(int):档位序号,1..6(= `RECORD_MILESTONES` 长度)。
|
||||||
@@ -15,11 +16,11 @@
|
|||||||
| 字段 | 类型 | 说明 |
|
| 字段 | 类型 | 说明 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `milestone` | int | 本次领取的档位序号 |
|
| `milestone` | int | 本次领取的档位序号 |
|
||||||
| `coin_awarded` | int | 本次发放金币 |
|
| `coin_awarded` | int | 本次发放金币(当前恒为 `0`,暂不真发) |
|
||||||
| `coin_balance` | int | 领奖后金币余额 |
|
| `coin_balance` | int | 领奖后金币余额(当前不变) |
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{ "milestone": 1, "coin_awarded": 120, "coin_balance": 29087 }
|
{ "milestone": 1, "coin_awarded": 0, "coin_balance": 29087 }
|
||||||
```
|
```
|
||||||
|
|
||||||
## 错误
|
## 错误
|
||||||
@@ -29,5 +30,5 @@
|
|||||||
- `409` 该档已领过(`milestone already claimed`)
|
- `409` 该档已领过(`milestone already claimed`)
|
||||||
|
|
||||||
## 说明
|
## 说明
|
||||||
- 领取金币后,客户端应刷新钱包余额([`GET /api/v1/wallet/account`](./wallet-account.md))与本档状态。
|
- 当前不发金币,客户端领取后只需把本档状态刷成 claimed 即可;若后续恢复发奖,再按 `coin_awarded` 刷新钱包余额([`GET /api/v1/wallet/account`](./wallet-account.md))。
|
||||||
- 幂等:重复领同一档返回 409 且**不会重复发金币**(唯一约束 + 领取前查重)。
|
- 幂等:重复领同一档返回 409(唯一约束 + 领取前查重)。
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
档位金额是**产品规则**,定义在后端 `app/core/rewards.py` 的 `RECORD_MILESTONES`(当前 `120/180/300/500/800/1200`),客户端**不要写死**,以本接口返回为准。
|
档位金额是**产品规则**,定义在后端 `app/core/rewards.py` 的 `RECORD_MILESTONES`(当前 `120/180/300/500/800/1200`),客户端**不要写死**,以本接口返回为准。
|
||||||
|
|
||||||
|
> ⚠️ **当前领取暂不真发金币**(产品定,后续整体删除该功能):`coin` 仍返回产品规则值仅供展示,但 [claim 接口](./compare-milestone-claim.md) 实际 `coin_awarded` 恒为 0、余额不变。前端展示须与之对齐,勿让用户误以为领取可到账。
|
||||||
|
|
||||||
## 入参
|
## 入参
|
||||||
无(用户身份取自 Bearer token)。
|
无(用户身份取自 Bearer token)。
|
||||||
|
|
||||||
@@ -25,7 +27,7 @@
|
|||||||
| 字段 | 类型 | 说明 |
|
| 字段 | 类型 | 说明 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `milestone` | int | 档位序号(1-based),= 解锁所需的成功比价次数 |
|
| `milestone` | int | 档位序号(1-based),= 解锁所需的成功比价次数 |
|
||||||
| `coin` | int | 该档奖励金币 |
|
| `coin` | int | 该档**应发**金币额(产品规则值);⚠️ 当前领取不真发,见上方说明 |
|
||||||
| `state` | string | `claimed`(已领) / `active`(已解锁可领) / `locked`(未解锁) |
|
| `state` | string | `claimed`(已领) / `active`(已解锁可领) / `locked`(未解锁) |
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -31,7 +31,9 @@
|
|||||||
|
|
||||||
**Item**:`{ name: string, qty: int=1, specs: string[]\|null }`
|
**Item**:`{ name: string, qty: int=1, specs: string[]\|null }`
|
||||||
|
|
||||||
**Result(comparison_results 元素)**:`{ platform_id, platform_name, package, price(元,float\|null), is_source(bool), rank(int\|null) }`
|
**Result(comparison_results 元素)**:`{ platform_id, platform_name, package, price(元,float\|null), is_source(bool), rank(int\|null), coupon_saved(元,float\|null) }`
|
||||||
|
|
||||||
|
- `coupon_saved`:该平台本单**平台主优惠额**(元)——美团红包 / 淘宝平台红包 / 京东优惠券·百亿补贴,**只取那一笔**,不含配送费减免/共减总额。仅外卖目标平台带值,源平台/没用为 `null`,前端记录页据此展示**「已优惠 ¥X」**(null 不展示)。
|
||||||
|
|
||||||
## 服务端派生(客户端不用算)
|
## 服务端派生(客户端不用算)
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
| `id` | Integer | PK, autoincrement | |
|
| `id` | Integer | PK, autoincrement | |
|
||||||
| `user_id` | Integer | FK→user.id, index, NOT NULL | 归属用户 |
|
| `user_id` | Integer | FK→user.id, index, NOT NULL | 归属用户 |
|
||||||
| `milestone` | Integer | NOT NULL | 档位序号(1-based),见 `rewards.RECORD_MILESTONES` |
|
| `milestone` | Integer | NOT NULL | 档位序号(1-based),见 `rewards.RECORD_MILESTONES` |
|
||||||
| `coin_awarded` | Integer | NOT NULL, default 0 | 该档发放金币 |
|
| `coin_awarded` | Integer | NOT NULL, default 0 | 该档应发金币额;⚠️ 当前产品定暂不真发,实际恒为 0 |
|
||||||
| `claimed_at` | DateTime(tz) | server_default now() | 领取时间 |
|
| `claimed_at` | DateTime(tz) | server_default now() | 领取时间 |
|
||||||
|
|
||||||
## 索引与约束
|
## 索引与约束
|
||||||
@@ -22,5 +22,5 @@
|
|||||||
- 解锁进度依赖 `comparison_record`(`status='success'` 计数),本表不存进度本身。
|
- 解锁进度依赖 `comparison_record`(`status='success'` 计数),本表不存进度本身。
|
||||||
|
|
||||||
## 说明
|
## 说明
|
||||||
- 发金币走 `wallet.grant_coins(biz_type='compare_milestone', ref_id=str(milestone))`,与本表写入同事务。
|
- ⚠️ 当前 claim 仅写本表标记已领(去重),**不调 `grant_coins`、不写 `coin_transaction`,`coin_awarded` 恒为 0、余额不变**(产品定暂不真发金币,后续整体删除该功能)。
|
||||||
- 领取校验:档位越界 404、未解锁(成功数 < milestone)409、已领 409。
|
- 领取校验:档位越界 404、未解锁(成功数 < milestone)409、已领 409。
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
| `status` | String(16) | NOT NULL, default `success` | `success`(拿到有效对比)/ `failed`(出错/没采到目标价) |
|
| `status` | String(16) | NOT NULL, default `success` | `success`(拿到有效对比)/ `failed`(出错/没采到目标价) |
|
||||||
| `information` | String(256) | nullable | done 帧文案;成功=摘要,失败=具体原因(前端失败时当原因展示) |
|
| `information` | String(256) | nullable | done 帧文案;成功=摘要,失败=具体原因(前端失败时当原因展示) |
|
||||||
| `items` | JSON(PG: JSONB) | NOT NULL, default [] | 下单菜品 `[{name, qty, specs?}]` |
|
| `items` | JSON(PG: JSONB) | NOT NULL, default [] | 下单菜品 `[{name, qty, specs?}]` |
|
||||||
| `comparison_results` | JSON(PG: JSONB) | NOT NULL, default [] | 逐平台对比 `[{platform_id,platform_name,package,price(元),is_source,rank}]` |
|
| `comparison_results` | JSON(PG: JSONB) | NOT NULL, default [] | 逐平台对比 `[{platform_id,platform_name,package,price(元),is_source,rank,coupon_saved}]`;`coupon_saved`=该平台主优惠额(元,美团红包/淘宝平台红包/京东优惠券·百亿补贴,只取那一笔,不含配送费/共减总额),各平台抠到红包即带值(2026-06 起源平台 Phase1 意图识别也抠,当前仅淘宝源),没用/没抠到为 null,前端展示「已优惠 ¥X」 |
|
||||||
| `skipped_dish_names` | JSON(PG: JSONB) | NOT NULL, default [] | 被跳过的菜名 |
|
| `skipped_dish_names` | JSON(PG: JSONB) | NOT NULL, default [] | 被跳过的菜名 |
|
||||||
| `raw_payload` | JSON(PG: JSONB) | nullable | 客户端原始上报(calibration + done.params 全量) |
|
| `raw_payload` | JSON(PG: JSONB) | nullable | 客户端原始上报(calibration + done.params 全量) |
|
||||||
| `created_at` | DateTime(tz) | server_default now(), index | 时间 |
|
| `created_at` | DateTime(tz) | server_default now(), index | 时间 |
|
||||||
|
|||||||
@@ -82,8 +82,8 @@ def test_unlock_progresses_with_success(client) -> None:
|
|||||||
assert states[2] == "locked"
|
assert states[2] == "locked"
|
||||||
|
|
||||||
|
|
||||||
def test_claim_grants_coin_and_becomes_claimed(client) -> None:
|
def test_claim_marks_claimed_without_coin(client) -> None:
|
||||||
"""领第 1 档:发对应金币,进金币余额,该档变 claimed。"""
|
"""领第 1 档:暂不真发金币(产品定,后续删该功能),余额不变,但该档变 claimed。"""
|
||||||
token = _login(client, "13800003003")
|
token = _login(client, "13800003003")
|
||||||
_report_success(client, token, "s-1")
|
_report_success(client, token, "s-1")
|
||||||
|
|
||||||
@@ -93,8 +93,8 @@ def test_claim_grants_coin_and_becomes_claimed(client) -> None:
|
|||||||
assert r.status_code == 200, r.text
|
assert r.status_code == 200, r.text
|
||||||
res = r.json()
|
res = r.json()
|
||||||
assert res["milestone"] == 1
|
assert res["milestone"] == 1
|
||||||
assert res["coin_awarded"] == RECORD_MILESTONES[0]
|
assert res["coin_awarded"] == 0 # 不再发金币
|
||||||
assert res["coin_balance"] == bal_before + RECORD_MILESTONES[0]
|
assert res["coin_balance"] == bal_before # 余额不变
|
||||||
|
|
||||||
d = client.get("/api/v1/compare/milestones", headers=_auth(token)).json()
|
d = client.get("/api/v1/compare/milestones", headers=_auth(token)).json()
|
||||||
assert d["milestones"][0]["state"] == "claimed"
|
assert d["milestones"][0]["state"] == "claimed"
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ def _food_payload(trace_id: str) -> dict:
|
|||||||
],
|
],
|
||||||
"comparison_results": [
|
"comparison_results": [
|
||||||
{"platform_id": "meituan", "platform_name": "美团", "package": "com.sankuai.meituan",
|
{"platform_id": "meituan", "platform_name": "美团", "package": "com.sankuai.meituan",
|
||||||
"price": 123.50, "is_source": False, "rank": 1},
|
"price": 123.50, "is_source": False, "rank": 1, "coupon_saved": 7.0},
|
||||||
{"platform_id": "taobao_flash", "platform_name": "淘宝闪购", "package": "com.taobao.taobao",
|
{"platform_id": "taobao_flash", "platform_name": "淘宝闪购", "package": "com.taobao.taobao",
|
||||||
"price": 128.50, "is_source": True, "rank": 2},
|
"price": 128.50, "is_source": True, "rank": 2},
|
||||||
{"platform_id": "jd_waimai", "platform_name": "京东外卖", "package": "com.jingdong.app.mall",
|
{"platform_id": "jd_waimai", "platform_name": "京东外卖", "package": "com.jingdong.app.mall",
|
||||||
@@ -78,6 +78,18 @@ def test_report_and_derive(client) -> None:
|
|||||||
assert d["raw_payload"]["trace_id"] == "trace-1"
|
assert d["raw_payload"]["trace_id"] == "trace-1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_coupon_saved_passthrough(client) -> None:
|
||||||
|
"""红包优惠额 coupon_saved 上报→落库→读出;仅目标平台带值,源平台不带(null)。"""
|
||||||
|
token = _login(client, "13800002010")
|
||||||
|
rid = client.post(
|
||||||
|
"/api/v1/compare/record", json=_food_payload("trace-coupon"), headers=_auth(token)
|
||||||
|
).json()["id"]
|
||||||
|
d = client.get(f"/api/v1/compare/records/{rid}", headers=_auth(token)).json()
|
||||||
|
by_pid = {x["platform_id"]: x for x in d["comparison_results"]}
|
||||||
|
assert by_pid["meituan"]["coupon_saved"] == 7.0 # 目标平台带值
|
||||||
|
assert by_pid["taobao_flash"].get("coupon_saved") is None # 源平台不带/为 null
|
||||||
|
|
||||||
|
|
||||||
def test_source_is_cheapest_no_saving(client) -> None:
|
def test_source_is_cheapest_no_saving(client) -> None:
|
||||||
"""源平台本来就最便宜:saved<=0,is_source_best=True,仍记一条 success。"""
|
"""源平台本来就最便宜:saved<=0,is_source_best=True,仍记一条 success。"""
|
||||||
token = _login(client, "13800002002")
|
token = _login(client, "13800002002")
|
||||||
|
|||||||
+16
-4
@@ -12,6 +12,9 @@ from app.core.rewards import (
|
|||||||
TASK_ENABLE_NOTIFICATION,
|
TASK_ENABLE_NOTIFICATION,
|
||||||
TASK_REWARDS,
|
TASK_REWARDS,
|
||||||
)
|
)
|
||||||
|
from app.db.session import SessionLocal
|
||||||
|
from app.repositories import wallet as crud_wallet
|
||||||
|
from app.repositories.user import get_user_by_phone
|
||||||
|
|
||||||
|
|
||||||
def _login(client, phone: str) -> str:
|
def _login(client, phone: str) -> str:
|
||||||
@@ -130,10 +133,19 @@ def test_exchange_info(client) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_exchange_flow(client) -> None:
|
def test_exchange_flow(client) -> None:
|
||||||
"""先用任务领够金币 → 兑换 1 元 → 金币扣、现金加 → 现金流水有记录。"""
|
"""先供款够兑换下限的金币 → 兑换 1 元 → 金币扣、现金加 → 现金流水有记录。
|
||||||
token = _login(client, "13800001005")
|
|
||||||
# 领"打开消息提醒"得 10000 金币(= 1 元额度)
|
打开消息提醒任务已降到 1000 金币(不再 = 兑换下限), 不能再靠领任务供款;
|
||||||
client.post(f"/api/v1/tasks/{TASK_ENABLE_NOTIFICATION}/claim", headers=_auth(token))
|
直接 grant_coins 注入 MIN_EXCHANGE_COIN(= COIN_PER_YUAN = 10000)当种子。
|
||||||
|
"""
|
||||||
|
phone = "13800001005"
|
||||||
|
token = _login(client, phone)
|
||||||
|
# 供款: 直接注入 1 元额度金币(替代原先靠 notification=10000 领任务供款)
|
||||||
|
with SessionLocal() as db:
|
||||||
|
user = get_user_by_phone(db, phone)
|
||||||
|
assert user is not None
|
||||||
|
crud_wallet.grant_coins(db, user.id, MIN_EXCHANGE_COIN, biz_type="test_seed", remark="测试供款")
|
||||||
|
db.commit()
|
||||||
|
|
||||||
# 兑换 10000 金币 → 100 分
|
# 兑换 10000 金币 → 100 分
|
||||||
r = client.post(
|
r = client.post(
|
||||||
|
|||||||
Reference in New Issue
Block a user