PayrollManagementSystem/backend/tests/unit/test_param_snapshot.py
liuwenxin 8ad3650dd3 feat(system): 计提参数版本管理、变更留痕与计算预览接口
- 9 个端点:目录快照(asOf)/单参数历史/变更批次提交与撤回/批次分页与明细/两日期对比/Excel 导出/计算预览
- submit_change 全量前置校验(注册表/维度岗位/取值域/同批去重)→ 建批次 → 逐项留痕(旧值取提交时生效值)
- 撤回:FOR UPDATE 锁行 + 已生效禁撤,删除后解析自然回退;同日多次变更取最新留痕
- 预览按岗位模板分派公式(经理聚合口径明确拒绝),出参含所用参数与生效时点;导出走 openpyxl
- ECharts 决策记录:前端统计图使用 echarts(用户指定)

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-12 20:24:17 +08:00

96 lines
4.0 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) 的最新值在前。"""
# 仓储实际返回 4 元组(含生效日期);单测关注覆盖链,统一补同一个日期
return ParamSnapshot.from_rows(
date(2026, 10, 1), [(code, dim, value, date(2026, 1, 1)) for code, dim, value in 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]