"""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 == {}