2026-10-12 04:27:56 -04:00
|
|
|
|
"""参数快照(时点化取值)测试。
|
|
|
|
|
|
|
|
|
|
|
|
守的是:覆盖链与兜底 —— 一旦取错维度(如企业户取到市政阈值行)
|
|
|
|
|
|
或漏兜底,核算会用错口径或直接崩溃;同日多次变更必须取最新留痕。
|
|
|
|
|
|
仓储 SQL 的排序语义(同日取 id 最大)在集成测试中覆盖。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from dataclasses import FrozenInstanceError
|
|
|
|
|
|
from datetime import date
|
|
|
|
|
|
from fractions import Fraction
|
|
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
|
|
from app.services.param_service import ParamSnapshot
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _snap(rows: list[tuple[str, str, str]]) -> ParamSnapshot:
|
|
|
|
|
|
"""构造快照。rows 须模拟仓储输出:同一 (code, dim) 的最新值在前。"""
|
2026-10-12 08:24:17 -04:00
|
|
|
|
# 仓储实际返回 4 元组(含生效日期);单测关注覆盖链,统一补同一个日期
|
|
|
|
|
|
return ParamSnapshot.from_rows(
|
|
|
|
|
|
date(2026, 10, 1), [(code, dim, value, date(2026, 1, 1)) for code, dim, value in rows]
|
|
|
|
|
|
)
|
2026-10-12 04:27:56 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestCoverChain:
|
|
|
|
|
|
"""覆盖链:角色维度 > 产品类别维度 > 默认行('')。"""
|
|
|
|
|
|
|
|
|
|
|
|
def test_产品类别维度优先于默认行(self) -> None:
|
|
|
|
|
|
snap = _snap([("enterprise.rate", "", "3"), ("enterprise.rate", "municipal_water", "2")])
|
|
|
|
|
|
assert snap.get("enterprise.rate", product_category="municipal_water") == "2"
|
|
|
|
|
|
|
|
|
|
|
|
def test_无匹配维度时落默认行(self) -> None:
|
|
|
|
|
|
snap = _snap([("enterprise.rate", "", "3"), ("enterprise.rate", "municipal_water", "2")])
|
|
|
|
|
|
assert snap.get("enterprise.rate", product_category="rail_transit") == "3"
|
|
|
|
|
|
|
|
|
|
|
|
def test_角色维度优先于产品类别维度(self) -> None:
|
|
|
|
|
|
snap = _snap([("some.param", "municipal_water", "A"), ("some.param", "salesperson", "B")])
|
|
|
|
|
|
got = snap.get("some.param", role_code="salesperson", product_category="municipal_water")
|
|
|
|
|
|
assert got == "B"
|
|
|
|
|
|
|
|
|
|
|
|
def test_无任何维度匹配且无默认行_回退注册表默认值(self) -> None:
|
|
|
|
|
|
snap = _snap([("enterprise.rate", "municipal_water", "2")])
|
|
|
|
|
|
# 轨交无行、无默认行 → 回退注册表 default_value(3)
|
|
|
|
|
|
assert snap.fraction("enterprise.rate", product_category="rail_transit") == Fraction(3)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestFallback:
|
|
|
|
|
|
"""注册表默认值兜底(种子缺失时的安全网)。"""
|
|
|
|
|
|
|
|
|
|
|
|
def test_未定义参数按注册表默认值兜底(self) -> None:
|
|
|
|
|
|
snap = _snap([])
|
|
|
|
|
|
assert snap.fraction("prepay.rate") == Fraction(10)
|
|
|
|
|
|
|
|
|
|
|
|
def test_未注册编码抛ValueError_属代码缺陷(self) -> None:
|
|
|
|
|
|
snap = _snap([])
|
|
|
|
|
|
with pytest.raises(ValueError):
|
|
|
|
|
|
snap.fraction("not.registered.code")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestSameDayChanges:
|
|
|
|
|
|
"""同日多次变更:仓储 SQL 已按 (effective_date, id) 倒序,快照取先出现者。"""
|
|
|
|
|
|
|
|
|
|
|
|
def test_同日多次变更取最新一条(self) -> None:
|
|
|
|
|
|
snap = _snap([("enterprise.rate", "", "3"), ("enterprise.rate", "", "2")])
|
|
|
|
|
|
assert snap.fraction("enterprise.rate") == Fraction(3)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestTypedGetters:
|
|
|
|
|
|
"""类型化取值:Fraction 精确解析、整数、布尔、文本。"""
|
|
|
|
|
|
|
|
|
|
|
|
def test_fraction精确解析分数式(self) -> None:
|
|
|
|
|
|
snap = _snap([("salesperson.rate.slope", "salesperson", "4/360")])
|
|
|
|
|
|
assert snap.fraction("salesperson.rate.slope", role_code="salesperson") == Fraction(1, 90)
|
|
|
|
|
|
|
|
|
|
|
|
def test_integer(self) -> None:
|
|
|
|
|
|
snap = _snap([("salesperson.days.boundary", "salesperson", "360")])
|
|
|
|
|
|
assert snap.integer("salesperson.days.boundary", role_code="salesperson") == 360
|
|
|
|
|
|
|
|
|
|
|
|
def test_text返回枚举原文(self) -> None:
|
|
|
|
|
|
snap = _snap([("payment.weight.mode", "", "FULL")])
|
|
|
|
|
|
assert snap.text("payment.weight.mode") == "FULL"
|
|
|
|
|
|
|
|
|
|
|
|
def test_boolean(self) -> None:
|
|
|
|
|
|
snap = _snap([("some.flag", "", "1")])
|
|
|
|
|
|
assert snap.boolean("some.flag") is True
|
|
|
|
|
|
assert snap.boolean("some.flag") is not False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestImmutability:
|
|
|
|
|
|
"""快照冻结:解析结果不得被调用方改写(核算期间一致性)。"""
|
|
|
|
|
|
|
|
|
|
|
|
def test_字段不可赋值(self) -> None:
|
|
|
|
|
|
snap = _snap([])
|
|
|
|
|
|
with pytest.raises(FrozenInstanceError):
|
|
|
|
|
|
snap.biz_date = date(2026, 1, 1) # type: ignore[misc]
|