"""审批与方案版本化的纯函数单元测试(秒级,无需容器)。 守的是「方案内容是否真的变了」这一判定(``business_service._plan_changed``): 它决定要不要新增一个方案版本行、要不要重提审批单。判错的后果是双向的—— 判成「变了」会刷出版本与审批留痕噪音,判成「没变」会让改过的方案 沿用旧版本号,版本语义失真。 比较口径是**集合**(人员 ID + 其比例),与提交顺序无关:用户只是把行 拖了个序,不该被算成方案变了。 """ from decimal import Decimal import pytest from app.models.business import SecondaryAllocation from app.services.business_service import _plan_changed def _row(personnel_id: int, ratio: str) -> SecondaryAllocation: """构造一个未入库的方案行(ORM 实例化不触数据库)。""" return SecondaryAllocation( business_id=1, version_no=1, personnel_id=personnel_id, personnel_name=f"t_人员{personnel_id}", ratio=Decimal(ratio), is_active=1, ) class TestPlanChanged: """方案内容比较。""" def test_完全相同_判未变(self) -> None: old = [_row(1, "50.00"), _row(2, "30.00")] new = [_row(1, "50.00"), _row(2, "30.00")] assert _plan_changed(old, new) is False def test_顺序不同_判未变(self) -> None: """用户只是把行拖了个序 —— 送审快照按库内生效行顺序生成, 按顺序比较会把这种编辑误判成「方案变了」。""" old = [_row(1, "50.00"), _row(2, "30.00")] new = [_row(2, "30.00"), _row(1, "50.00")] assert _plan_changed(old, new) is False def test_比例变化_判已变(self) -> None: old = [_row(1, "50.00")] new = [_row(1, "80.00")] assert _plan_changed(old, new) is True def test_参与人增减_判已变(self) -> None: old = [_row(1, "50.00")] assert _plan_changed(old, [_row(1, "50.00"), _row(2, "30.00")]) is True assert _plan_changed([_row(1, "50.00"), _row(2, "30.00")], [_row(1, "50.00")]) is True def test_同一人的比例从一人换成另一人_判已变(self) -> None: """参与人不同但比例相同:人员 ID 是比较键的一部分,不能只看比例。""" old = [_row(1, "50.00")] new = [_row(2, "50.00")] assert _plan_changed(old, new) is True @pytest.mark.parametrize("written", ["50", "50.0", "50.00"]) def test_比例写法不同_判未变(self, written: str) -> None: """比例入参是字符串('50' 与 '50.00' 都合法),数值相同即未变。""" assert _plan_changed([_row(1, "50.00")], [_row(1, written)]) is False def test_空方案与空方案_判未变(self) -> None: assert _plan_changed([], []) is False def test_空方案与有方案_判已变(self) -> None: assert _plan_changed([], [_row(1, "50.00")]) is True