Merge branch 'main' into codex/coupon-point-score
This commit is contained in:
@@ -356,3 +356,272 @@ def test_callback_disabled_returns_503(client, monkeypatch) -> None:
|
||||
# 503 发生在验签/发奖之前,不需要真实用户
|
||||
r = _callback(client, _signed(1, "trans_disabled"))
|
||||
assert r.status_code == 503, r.text
|
||||
|
||||
|
||||
# ===== 按 ad_session_id 查权威发奖结果(GET /reward-result/{ad_session_id})=====
|
||||
# 客户端看完广告轮询它拿弹窗金额:只认 status='granted' 且 coin>0,其余一律不弹。
|
||||
|
||||
|
||||
def _reward_result(client, token: str, session_id: str):
|
||||
return client.get(f"/api/v1/ad/reward-result/{session_id}", headers=_auth(token))
|
||||
|
||||
|
||||
def _session_extra(session_id: str, **kv: str) -> str:
|
||||
return json.dumps({"ad_session_id": session_id, **kv})
|
||||
|
||||
|
||||
def test_reward_result_pending_when_s2s_not_arrived(client) -> None:
|
||||
"""S2S 还没回调 → 200 + pending(**不是 404**),客户端据此继续轮询。"""
|
||||
token = _login(client, "13800003601")
|
||||
|
||||
r = _reward_result(client, token, "sess-not-yet-arrived")
|
||||
assert r.status_code == 200, r.text
|
||||
assert r.json() == {
|
||||
"ad_session_id": "sess-not-yet-arrived",
|
||||
"status": "pending",
|
||||
"coin": None,
|
||||
# 没记录 → 连属于哪一轮都不知道,累计值一并为 null(不是 0,0 会被读成"本轮没赚到")
|
||||
"round_coin": None,
|
||||
}
|
||||
|
||||
|
||||
def test_reward_result_returns_granted_coin(client) -> None:
|
||||
"""S2S 发奖后按会话查 → granted + 本次真实到账额(与钱包入账一致)。"""
|
||||
phone = "13800003602"
|
||||
token = _login(client, phone)
|
||||
uid = _user_id(phone)
|
||||
session_id = "sess-granted-1"
|
||||
|
||||
r = _callback(
|
||||
client,
|
||||
_signed(uid, "trans_rr_1", ecpm="200", extra=_session_extra(session_id)),
|
||||
)
|
||||
assert r.json() == {"is_verify": True, "reason": 0}
|
||||
|
||||
expected = calculate_ad_reward_coin("200", 1)
|
||||
body = _reward_result(client, token, session_id).json()
|
||||
assert body["status"] == "granted"
|
||||
assert body["coin"] == expected
|
||||
# 弹窗金额必须等于真实入账,这正是本接口存在的意义(不用余额差估算)
|
||||
assert _coin_balance(client, token) == expected
|
||||
|
||||
|
||||
def test_reward_result_prefers_granted_over_earlier_noshow(client) -> None:
|
||||
"""竞态:客户端先报 closed_early、S2S 随后才到 → 同一会话两条记录,必须返回 granted 那条。
|
||||
|
||||
只按 created_at 取最近一条是不够的(SQLite 下两条可能同一时间戳),故仓储层显式优先 granted。
|
||||
"""
|
||||
phone = "13800003603"
|
||||
token = _login(client, phone)
|
||||
uid = _user_id(phone)
|
||||
session_id = "sess-race-noshow"
|
||||
|
||||
# 1) 客户端以为没发奖,先留痕
|
||||
r = client.post(
|
||||
"/api/v1/ad/reward-noshow",
|
||||
json={"ad_session_id": session_id, "watched_seconds": 3},
|
||||
headers=_auth(token),
|
||||
)
|
||||
assert r.status_code == 200, r.text
|
||||
assert r.json()["status"] == "closed_early"
|
||||
assert _reward_result(client, token, session_id).json()["status"] == "closed_early"
|
||||
|
||||
# 2) S2S 姗姗来迟,真发了钱
|
||||
_callback(client, _signed(uid, "trans_rr_race", ecpm="200", extra=_session_extra(session_id)))
|
||||
|
||||
body = _reward_result(client, token, session_id).json()
|
||||
assert body["status"] == "granted"
|
||||
assert body["coin"] == calculate_ad_reward_coin("200", 1)
|
||||
|
||||
|
||||
def test_reward_result_capped_reports_zero_not_popup(client) -> None:
|
||||
"""达每日上限 → capped + coin=0;客户端不弹「获得 0 金币」。"""
|
||||
phone = "13800003604"
|
||||
token = _login(client, phone)
|
||||
uid = _user_id(phone)
|
||||
session_id = "sess-capped-1"
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
db.add(
|
||||
AdRewardRecord(
|
||||
trans_id="trans_rr_capped", user_id=_user_id(phone), coin=0, status="capped",
|
||||
reward_scene="reward_video", ad_session_id=session_id, reward_date="2026-07-17",
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
finally:
|
||||
db.close()
|
||||
assert uid # 记录挂在该用户名下
|
||||
|
||||
body = _reward_result(client, token, session_id).json()
|
||||
assert body["status"] == "capped"
|
||||
assert body["coin"] == 0
|
||||
|
||||
|
||||
def test_reward_result_scoped_to_owner(client) -> None:
|
||||
"""别人的会话查不到(按 user_id 收窄)→ pending,不泄漏他人发奖结果。"""
|
||||
phone_a = "13800003605"
|
||||
token_a = _login(client, phone_a)
|
||||
uid_a = _user_id(phone_a)
|
||||
token_b = _login(client, "13800003606")
|
||||
session_id = "sess-owner-only"
|
||||
|
||||
_callback(client, _signed(uid_a, "trans_rr_owner", ecpm="200", extra=_session_extra(session_id)))
|
||||
|
||||
assert _reward_result(client, token_a, session_id).json()["status"] == "granted"
|
||||
assert _reward_result(client, token_b, session_id).json()["status"] == "pending"
|
||||
|
||||
|
||||
def test_reward_result_requires_auth(client) -> None:
|
||||
"""无 Bearer → 401,不裸奔。"""
|
||||
assert client.get("/api/v1/ad/reward-result/sess-anon-1").status_code == 401
|
||||
|
||||
|
||||
# ===== 膨胀轮累计(boost_round_id → reward-result.round_coin)=====
|
||||
# 不变量:弹窗数字 == 本轮实际到账之和 == 余额涨幅。三者对不上用户就认为少发了钱。
|
||||
|
||||
|
||||
def _round_extra(session_id: str, round_id: str | None = None, **kv: str) -> str:
|
||||
data = {"ad_session_id": session_id, **kv}
|
||||
if round_id is not None:
|
||||
data["boost_round_id"] = round_id
|
||||
return json.dumps(data)
|
||||
|
||||
|
||||
def test_round_coin_accumulates_across_ads_in_same_round(client) -> None:
|
||||
"""一轮连看两条 → round_coin 逐条累计,且等于余额涨幅(第七节验收 1、2 步)。"""
|
||||
phone = "13800003701"
|
||||
token = _login(client, phone)
|
||||
uid = _user_id(phone)
|
||||
round_id = "b7e1c93a4f6d802b"
|
||||
|
||||
_callback(client, _signed(
|
||||
uid, "trans_round_1", ecpm="200", extra=_round_extra("sess-r1-a", round_id)))
|
||||
first = calculate_ad_reward_coin("200", 1)
|
||||
body = _reward_result(client, token, "sess-r1-a").json()
|
||||
assert body["coin"] == first
|
||||
assert body["round_coin"] == first # 第 1 条:本轮累计 == 本条
|
||||
|
||||
_callback(client, _signed(
|
||||
uid, "trans_round_2", ecpm="200", extra=_round_extra("sess-r1-b", round_id)))
|
||||
second = calculate_ad_reward_coin("200", 2) # LT 因子递减,第 2 条比第 1 条少
|
||||
body = _reward_result(client, token, "sess-r1-b").json()
|
||||
assert body["coin"] == second
|
||||
assert body["round_coin"] == first + second # 累计 = 两条之和
|
||||
|
||||
# 弹窗数字必须等于真实余额涨幅 —— 这条不变量是整个方案的目的
|
||||
assert _coin_balance(client, token) == first + second
|
||||
|
||||
|
||||
def test_new_round_restarts_accumulation(client) -> None:
|
||||
"""换新轮 id → round_coin 从头累计,不接着上一轮往上加(第七节最后一句验收)。"""
|
||||
phone = "13800003702"
|
||||
token = _login(client, phone)
|
||||
uid = _user_id(phone)
|
||||
|
||||
_callback(client, _signed(uid, "trans_r2_old", ecpm="200", extra=_round_extra("sess-r2-a", "round-old")))
|
||||
old = _reward_result(client, token, "sess-r2-a").json()["round_coin"]
|
||||
assert old > 0
|
||||
|
||||
_callback(client, _signed(uid, "trans_r2_new", ecpm="200", extra=_round_extra("sess-r2-b", "round-new")))
|
||||
body = _reward_result(client, token, "sess-r2-b").json()
|
||||
assert body["round_coin"] == body["coin"] # 新轮 = 只有本条
|
||||
assert body["round_coin"] != old + body["coin"]
|
||||
|
||||
|
||||
def test_round_coin_null_without_round_id(client) -> None:
|
||||
"""extra 没带 boost_round_id(老客户端 / GroMore 丢字段)→ round_coin=null,客户端退回显示单条。"""
|
||||
phone = "13800003703"
|
||||
token = _login(client, phone)
|
||||
uid = _user_id(phone)
|
||||
|
||||
_callback(client, _signed(uid, "trans_r3", ecpm="200", extra=_round_extra("sess-r3-noround", None)))
|
||||
body = _reward_result(client, token, "sess-r3-noround").json()
|
||||
assert body["coin"] == calculate_ad_reward_coin("200", 1)
|
||||
assert body["round_coin"] is None
|
||||
|
||||
|
||||
def test_round_coin_null_when_pending(client) -> None:
|
||||
"""S2S 未到账 → 没有记录 → 连轮 id 都不知道,round_coin 也是 null(不是 0)。"""
|
||||
token = _login(client, "13800003704")
|
||||
body = _reward_result(client, token, "sess-r4-pending").json()
|
||||
assert body == {
|
||||
"ad_session_id": "sess-r4-pending",
|
||||
"status": "pending",
|
||||
"coin": None,
|
||||
"round_coin": None,
|
||||
}
|
||||
|
||||
|
||||
def test_round_coin_returned_on_capped(client) -> None:
|
||||
"""撞每日上限那条不是 granted,但 round_coin **仍返本轮累计**(该条按 0 计)。
|
||||
|
||||
客户端的限额 toast 要显示前面几条已到账的总额,不能是空。
|
||||
"""
|
||||
phone = "13800003705"
|
||||
token = _login(client, phone)
|
||||
uid = _user_id(phone)
|
||||
round_id = "round-capped"
|
||||
|
||||
_callback(client, _signed(uid, "trans_cap_ok", ecpm="200", extra=_round_extra("sess-cap-a", round_id)))
|
||||
earned = _reward_result(client, token, "sess-cap-a").json()["round_coin"]
|
||||
assert earned > 0
|
||||
|
||||
# 手插一条同轮的 capped 记录(跑满 500 次太慢),模拟第 N 条撞上限
|
||||
db = SessionLocal()
|
||||
try:
|
||||
db.add(AdRewardRecord(
|
||||
trans_id="trans_cap_hit", user_id=uid, coin=0, status="capped",
|
||||
reward_scene="reward_video", ad_session_id="sess-cap-b",
|
||||
reward_date="2026-07-20", boost_round_id=round_id,
|
||||
))
|
||||
db.commit()
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
body = _reward_result(client, token, "sess-cap-b").json()
|
||||
assert body["status"] == "capped"
|
||||
assert body["coin"] == 0 # 这条没发钱
|
||||
assert body["round_coin"] == earned # 但本轮累计照常返回
|
||||
|
||||
|
||||
def test_round_coin_scoped_to_owner(client) -> None:
|
||||
"""轮 id 是客户端生成的,不能跨用户信任:拿别人的轮 id 查不到别人的金币。"""
|
||||
phone_a = "13800003706"
|
||||
token_a = _login(client, phone_a)
|
||||
uid_a = _user_id(phone_a)
|
||||
phone_b = "13800003707"
|
||||
token_b = _login(client, phone_b)
|
||||
uid_b = _user_id(phone_b)
|
||||
shared_round = "round-collision"
|
||||
|
||||
_callback(client, _signed(uid_a, "trans_own_a", ecpm="200", extra=_round_extra("sess-own-a", shared_round)))
|
||||
a_total = _reward_result(client, token_a, "sess-own-a").json()["round_coin"]
|
||||
|
||||
# B 用同一个轮 id(伪造或碰撞)看一条:B 的累计里不能混进 A 的钱
|
||||
_callback(client, _signed(uid_b, "trans_own_b", ecpm="200", extra=_round_extra("sess-own-b", shared_round)))
|
||||
b_body = _reward_result(client, token_b, "sess-own-b").json()
|
||||
assert b_body["round_coin"] == b_body["coin"]
|
||||
assert b_body["round_coin"] < a_total + b_body["coin"]
|
||||
|
||||
|
||||
def test_test_grant_accepts_boost_round_id(client, monkeypatch) -> None:
|
||||
"""debug 的 test-grant 不经 S2S、拿不到 mediaExtra,轮 id 由 body 补 → 本地也能验累计。"""
|
||||
monkeypatch.setattr(settings, "AD_REWARD_TEST_GRANT_ENABLED", True)
|
||||
token = _login(client, "13800003708")
|
||||
round_id = "round-testgrant"
|
||||
|
||||
coins = []
|
||||
for i in range(2):
|
||||
r = client.post(
|
||||
"/api/v1/ad/test-grant",
|
||||
json={"reward_scene": "reward_video", "boost_round_id": round_id,
|
||||
"ad_session_id": f"sess-tg-{i}-padding"},
|
||||
headers=_auth(token),
|
||||
)
|
||||
assert r.status_code == 200, r.text
|
||||
coins.append(r.json()["coin"])
|
||||
|
||||
body = _reward_result(client, token, "sess-tg-1-padding").json()
|
||||
assert body["round_coin"] == sum(coins)
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
"""广告收益报表的环境与业务代码位口径。"""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from sqlalchemy import delete
|
||||
|
||||
from app.admin.repositories import ad_revenue
|
||||
from app.db.session import SessionLocal
|
||||
from app.models.ad_ecpm import AdEcpmRecord
|
||||
from app.models.ad_pangle_revenue import AdPangleDailyRevenue
|
||||
from app.models.user import User
|
||||
|
||||
REPORT_DATE = "2040-02-03"
|
||||
|
||||
|
||||
def test_business_scope_filters_client_and_pangle_by_env_and_code(monkeypatch) -> None:
|
||||
db = SessionLocal()
|
||||
try:
|
||||
user = User(
|
||||
phone="18800009991",
|
||||
username="29999999991",
|
||||
register_channel="sms",
|
||||
)
|
||||
db.add(user)
|
||||
db.flush()
|
||||
db.add_all([
|
||||
AdEcpmRecord(
|
||||
user_id=user.id, ad_type="reward_video", ad_session_id="scope-prod-business",
|
||||
app_env="prod", our_code_id="prod-reward", ecpm_raw="10000",
|
||||
report_date=REPORT_DATE, created_at=datetime(2040, 2, 3, tzinfo=UTC),
|
||||
),
|
||||
AdEcpmRecord(
|
||||
user_id=user.id, ad_type="draw", ad_session_id="scope-prod-demo",
|
||||
app_env="prod", our_code_id="prod-demo", ecpm_raw="20000",
|
||||
report_date=REPORT_DATE, created_at=datetime(2040, 2, 3, tzinfo=UTC),
|
||||
),
|
||||
AdEcpmRecord(
|
||||
user_id=user.id, ad_type="draw", ad_session_id="scope-prod-known-business",
|
||||
app_env="prod", our_code_id="104098712", ecpm_raw="40000",
|
||||
report_date=REPORT_DATE, created_at=datetime(2040, 2, 3, tzinfo=UTC),
|
||||
),
|
||||
AdEcpmRecord(
|
||||
user_id=user.id, ad_type="reward_video", ad_session_id="scope-test-business",
|
||||
app_env="test", our_code_id="104127529", ecpm_raw="30000",
|
||||
report_date=REPORT_DATE, created_at=datetime(2040, 2, 3, tzinfo=UTC),
|
||||
),
|
||||
AdPangleDailyRevenue(
|
||||
report_date=REPORT_DATE, app_env="prod", our_code_id="prod-reward",
|
||||
adn="", revenue_yuan=1.5, api_revenue_yuan=1.2, impressions=10,
|
||||
),
|
||||
AdPangleDailyRevenue(
|
||||
report_date=REPORT_DATE, app_env="prod", our_code_id="prod-demo",
|
||||
adn="", revenue_yuan=8.0, api_revenue_yuan=7.0, impressions=40,
|
||||
),
|
||||
AdPangleDailyRevenue(
|
||||
report_date=REPORT_DATE, app_env="prod", our_code_id="104098712",
|
||||
adn="", revenue_yuan=2.5, api_revenue_yuan=2.0, impressions=20,
|
||||
),
|
||||
AdPangleDailyRevenue(
|
||||
report_date=REPORT_DATE, app_env="test", our_code_id="104127529",
|
||||
adn="", revenue_yuan=9.0, api_revenue_yuan=8.0, impressions=50,
|
||||
),
|
||||
])
|
||||
db.commit()
|
||||
monkeypatch.setattr(
|
||||
ad_revenue.app_config,
|
||||
"get_ad_config",
|
||||
lambda _db: {
|
||||
"reward_code_id": "prod-reward",
|
||||
"compare_draw_code_id": "prod-draw",
|
||||
"coupon_draw_code_id": "prod-draw",
|
||||
},
|
||||
)
|
||||
|
||||
business = ad_revenue.ad_revenue_report(
|
||||
db,
|
||||
date_from=REPORT_DATE,
|
||||
date_to=REPORT_DATE,
|
||||
app_env="prod",
|
||||
revenue_scope="business",
|
||||
)
|
||||
assert business["total_impressions"] == 2
|
||||
assert business["total_revenue_yuan"] == 0.5
|
||||
assert business["total_pangle_revenue_yuan"] == 4.0
|
||||
assert business["total_pangle_api_revenue_yuan"] == 3.2
|
||||
|
||||
all_codes = ad_revenue.ad_revenue_report(
|
||||
db,
|
||||
date_from=REPORT_DATE,
|
||||
date_to=REPORT_DATE,
|
||||
app_env="prod",
|
||||
revenue_scope="all",
|
||||
)
|
||||
assert all_codes["total_impressions"] == 3
|
||||
assert all_codes["total_revenue_yuan"] == 0.7
|
||||
assert all_codes["total_pangle_revenue_yuan"] == 12.0
|
||||
assert all_codes["total_pangle_api_revenue_yuan"] == 10.2
|
||||
|
||||
test_business = ad_revenue.ad_revenue_report(
|
||||
db,
|
||||
date_from=REPORT_DATE,
|
||||
date_to=REPORT_DATE,
|
||||
app_env="test",
|
||||
revenue_scope="business",
|
||||
)
|
||||
assert test_business["total_impressions"] == 1
|
||||
assert test_business["total_revenue_yuan"] == 0.3
|
||||
assert test_business["total_pangle_revenue_yuan"] == 9.0
|
||||
assert test_business["total_pangle_api_revenue_yuan"] == 8.0
|
||||
|
||||
all_env_business = ad_revenue.ad_revenue_report(
|
||||
db,
|
||||
date_from=REPORT_DATE,
|
||||
date_to=REPORT_DATE,
|
||||
app_env=None,
|
||||
revenue_scope="business",
|
||||
)
|
||||
assert all_env_business["total_impressions"] == 3
|
||||
assert all_env_business["total_revenue_yuan"] == 0.8
|
||||
assert all_env_business["total_pangle_revenue_yuan"] == 13.0
|
||||
assert all_env_business["total_pangle_api_revenue_yuan"] == 11.2
|
||||
finally:
|
||||
db.rollback()
|
||||
db.execute(delete(AdPangleDailyRevenue).where(AdPangleDailyRevenue.report_date == REPORT_DATE))
|
||||
db.execute(delete(AdEcpmRecord).where(AdEcpmRecord.report_date == REPORT_DATE))
|
||||
db.execute(delete(User).where(User.phone == "18800009991"))
|
||||
db.commit()
|
||||
db.close()
|
||||
@@ -0,0 +1,65 @@
|
||||
"""比价记录页后端分页与概览聚合。"""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, date, datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from app.admin.repositories import queries
|
||||
from app.db.session import SessionLocal
|
||||
from app.models.comparison import ComparisonRecord
|
||||
|
||||
|
||||
def test_summary_uses_only_success_durations_and_filters_beijing_date() -> None:
|
||||
db = SessionLocal()
|
||||
try:
|
||||
rows = [
|
||||
("summary-success-a", "success", 1000, 1.0, 100),
|
||||
("summary-success-b", "success", 3000, 2.0, 0),
|
||||
("summary-failed", "failed", 100_000, 3.0, 0),
|
||||
("summary-cancelled", "cancelled", 5000, 4.0, 0),
|
||||
]
|
||||
for trace_id, status, total_ms, cost, saved in rows:
|
||||
db.add(ComparisonRecord(
|
||||
trace_id=trace_id,
|
||||
status=status,
|
||||
total_ms=total_ms,
|
||||
llm_cost_yuan=cost,
|
||||
saved_amount_cents=saved,
|
||||
created_at=datetime(2038, 1, 15, 12, tzinfo=UTC),
|
||||
))
|
||||
db.add(ComparisonRecord(
|
||||
trace_id="summary-outside-day",
|
||||
status="success",
|
||||
total_ms=999_999,
|
||||
created_at=datetime(2038, 1, 16, 16, tzinfo=UTC),
|
||||
))
|
||||
db.flush()
|
||||
|
||||
summary = queries.comparison_records_summary(
|
||||
db, date_from=date(2038, 1, 15), date_to=date(2038, 1, 15)
|
||||
)
|
||||
|
||||
assert summary["started"] == 4
|
||||
assert summary["completed"] == 3
|
||||
assert summary["success"] == 2
|
||||
assert summary["success_rate"] == pytest.approx(2 / 3)
|
||||
assert summary["avg_token_cost"] == pytest.approx(2.5)
|
||||
assert summary["lower_price_rate"] == 0.5
|
||||
assert summary["avg_duration_ms"] == 2000
|
||||
assert summary["p5_duration_ms"] == 1100
|
||||
assert summary["p50_duration_ms"] == 2000
|
||||
assert summary["p95_duration_ms"] == 2900
|
||||
assert summary["p99_duration_ms"] == 2980
|
||||
assert summary["cancelled"] == 1
|
||||
assert summary["cancelled_rate"] == 0.25
|
||||
assert summary["cancelled_p50_ms"] == 5000
|
||||
|
||||
items, _next_cursor, total = queries.list_comparison_records(
|
||||
db, date_from=date(2038, 1, 15), date_to=date(2038, 1, 15), limit=20
|
||||
)
|
||||
assert total == 4
|
||||
assert {item.trace_id for item in items} == {row[0] for row in rows}
|
||||
finally:
|
||||
db.rollback()
|
||||
db.close()
|
||||
@@ -11,7 +11,6 @@ from app.core.rewards import (
|
||||
COIN_PER_CENT,
|
||||
COIN_PER_YUAN,
|
||||
MIN_EXCHANGE_COIN,
|
||||
SIGNIN_BOOST_COIN,
|
||||
SIGNIN_REWARDS,
|
||||
TASK_ENABLE_NOTIFICATION,
|
||||
TASK_REWARDS,
|
||||
@@ -106,48 +105,6 @@ def test_signin_flow(client) -> None:
|
||||
assert txn["balance_after"] == SIGNIN_REWARDS[0]
|
||||
|
||||
|
||||
def test_signin_boost_flow(client) -> None:
|
||||
"""签到后看广告膨胀 → S2S 固定补发 2000 金币,每天只能膨胀一次。"""
|
||||
phone = "13800001011"
|
||||
token = _login(client, phone)
|
||||
|
||||
r = client.post("/api/v1/signin/boost", json={}, headers=_auth(token))
|
||||
assert r.status_code == 409
|
||||
|
||||
r = client.post("/api/v1/signin", headers=_auth(token))
|
||||
assert r.status_code == 200, r.text
|
||||
first_coin = r.json()["coin_awarded"]
|
||||
|
||||
with SessionLocal() as db:
|
||||
user = get_user_by_phone(db, phone)
|
||||
assert user is not None
|
||||
uid = user.id
|
||||
|
||||
extra = json.dumps({"reward_scene": "signin_boost", "ad_session_id": "signin-session-1"})
|
||||
r = client.get(
|
||||
"/api/v1/ad/pangle-callback",
|
||||
params=_signed_ad_callback(uid, "signin-boost-trans-1", extra=extra, ecpm="200"),
|
||||
)
|
||||
assert r.status_code == 200, r.text
|
||||
assert r.json() == {"is_verify": True, "reason": 0}
|
||||
|
||||
r = client.post("/api/v1/signin/boost", json={"ad_ref_id": "signin-boost-trans-1"}, headers=_auth(token))
|
||||
assert r.status_code == 200, r.text
|
||||
body = r.json()
|
||||
assert body["coin_awarded"] == SIGNIN_BOOST_COIN
|
||||
assert body["coin_balance"] == first_coin + SIGNIN_BOOST_COIN
|
||||
|
||||
r = client.get(
|
||||
"/api/v1/ad/pangle-callback",
|
||||
params=_signed_ad_callback(uid, "signin-boost-trans-2", extra=extra, ecpm="200"),
|
||||
)
|
||||
assert r.status_code == 200, r.text
|
||||
|
||||
r = client.get("/api/v1/wallet/coin-transactions", headers=_auth(token))
|
||||
types = [t["biz_type"] for t in r.json()["items"]]
|
||||
assert "signin" in types
|
||||
assert "signin_boost" in types
|
||||
|
||||
|
||||
def test_task_claim_flow(client) -> None:
|
||||
"""打开消息提醒=可重复任务:每次领取金额减半(750/375/188),claimed 恒 False,余额累加。"""
|
||||
|
||||
Reference in New Issue
Block a user