PayrollManagementSystem/backend/tests/unit/test_param_snapshot.py
liuwenxin 1b3509b20f feat(system): 计提参数按业务日期时点化取值服务
- param_repository.resolve_values:窗口函数一条 SQL 取全量快照(同日多次变更取 effective_date DESC, id DESC 首条)
- param_service.ParamSnapshot:冻结快照 + 覆盖链(角色 > 产品类别 > 默认行)+ 注册表默认值兜底;fraction/integer/boolean/text 类型化取值
- 12 项单元用例:覆盖链优先级、兜底、同日取最新、冻结不可变

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-12 16:27:56 +08:00

93 lines
3.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""参数快照(时点化取值)测试。
守的是:覆盖链与兜底 —— 一旦取错维度(如企业户取到市政阈值行)
或漏兜底,核算会用错口径或直接崩溃;同日多次变更必须取最新留痕。
仓储 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) 的最新值在前。"""
return ParamSnapshot.from_rows(date(2026, 10, 1), rows)
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]