- claim_task_bonus 在幂等快捷路径前做完成凭据校验:report_feedback 查当日 rp_feedback、first_report 查 succeeded rp_report,无凭据 40311 且不打流水;sign_in/share_report 不校验(产品决策:签到行为即领取,分享内测期接受端侧自报,hard_cap 锁死刷量收益) - task_bonus_rules 默认值与 .env.example 同步产品定稿(产品方案 §3.2,2026-09-11):sign_in 对话+10/day、report_feedback 发型+2/day、share_report 换装+3/day、first_report 重生成+1/lifetime;下架 invite_friend/profile_complete - billing/repo 新增只读凭据探测 has_feedback_between/has_completed_report(import-linter 禁 billing→report,直查 shared.models 的 rp_* 表,永远只读) - 测试:test_task_bonus_eligibility 5 用例(40311 两分支/凭据后 200/40910/40410/零流水) 偏差说明:test_task_bonus 与 test_task_bonus_rules 断言数值随定稿更新(发放/幂等/封顶语义未动);quota.py docstring「占位值」说法顺手更正。
66 lines
2.8 KiB
Python
66 lines
2.8 KiB
Python
"""settings.task_bonus_rules 的形状与 env 通路(FR-BILL-03/04).
|
||
|
||
协调提交已把 ``task_bonus_rules`` 纳入 ``_parse_json_object`` 共享校验器,
|
||
解析逻辑不再单独测;这里测默认值形状(每条配置必须能被 claim 路径安全消费)
|
||
以及集成测试要依赖的 ``TASK_BONUS_RULES_JSON`` 覆盖通路。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
|
||
import pytest
|
||
|
||
from packages.domain.billing.quota import PERIOD_TYPES
|
||
from packages.shared.config import Settings
|
||
|
||
#: 产品方案 §3.2 定稿目录(2026-09-11):签到、反馈、分享、首次速测;
|
||
#: invite_friend/profile_complete 无可信完成信号,已下架(不在默认值里)。
|
||
_FINAL_CATALOG_TASKS = (
|
||
"sign_in",
|
||
"report_feedback",
|
||
"share_report",
|
||
"first_report",
|
||
)
|
||
|
||
|
||
def _rules_without_env() -> dict[str, dict[str, object]]:
|
||
# _env_file=None:不读 .env,只看进程环境,保证断言的是代码内默认值。
|
||
return dict(Settings(_env_file=None).task_bonus_rules)
|
||
|
||
|
||
def test_default_rules_cover_the_final_catalog() -> None:
|
||
rules = _rules_without_env()
|
||
for task_key in _FINAL_CATALOG_TASKS:
|
||
assert task_key in rules, task_key
|
||
# 下架任务不得出现在默认值里(不下发即下架,claim 返回 40410)。
|
||
assert "invite_friend" not in rules
|
||
assert "profile_complete" not in rules
|
||
|
||
|
||
def test_every_default_rule_entry_has_a_valid_shape() -> None:
|
||
for task_key, conf in _rules_without_env().items():
|
||
assert isinstance(conf, dict), task_key
|
||
quota_key = conf.get("quota_key")
|
||
assert isinstance(quota_key, str) and quota_key, task_key
|
||
amount = conf.get("amount")
|
||
assert isinstance(amount, int) and not isinstance(amount, bool), task_key
|
||
assert amount > 0, task_key # 定稿数值(§3.2),改 env 即生效
|
||
assert conf.get("claim_period") in PERIOD_TYPES, task_key
|
||
|
||
|
||
def test_rules_json_env_reaches_settings_via_alias(monkeypatch: pytest.MonkeyPatch) -> None:
|
||
# 集成测试用同一通路把 amount 覆盖成超限值验证封顶,这里先证明通路本身可用。
|
||
raw = json.dumps({"sign_in": {"quota_key": "agent_chat", "amount": 11, "claim_period": "day"}})
|
||
monkeypatch.setenv("TASK_BONUS_RULES_JSON", raw)
|
||
settings = Settings(_env_file=None)
|
||
assert settings.task_bonus_rules["sign_in"]["amount"] == 11
|
||
|
||
|
||
def test_empty_rules_object_env_means_no_tasks(monkeypatch: pytest.MonkeyPatch) -> None:
|
||
# 关闭任务奖励的受支持形态是 "{}":解析为空注册表,所有 claim 返回 40410,
|
||
# 服务照常启动。(空白字符串不是合法 JSON,pydantic-settings 在 Settings
|
||
# 构造时就地失败——那是共享校验器的行为,不归本任务改。)
|
||
monkeypatch.setenv("TASK_BONUS_RULES_JSON", "{}")
|
||
assert Settings(_env_file=None).task_bonus_rules == {}
|