"""预算时间与金额、配置引用的确定性合同。""" from datetime import datetime, timedelta from decimal import Decimal from zoneinfo import ZoneInfo import pytest from muse.任务运行.配置版本 import 凭据引用, 提供方配置, 运行配置内容, 配置版本错误 from muse.任务运行.预算管理 import 任务预算计划, 归属窗口, 角色预算, 预算错误, 额度策略 @pytest.mark.case_id( "TC-2852b5b9a8d0", environment="离线纯规则", given="给定十个整点所在时刻", when="计算固定本地日窗口", then=["起点日期和小时一致,末窗到次日00"], contract="docs/系统架构/新版设计/模块设计/S02-任务运行.md", ) @pytest.mark.parametrize( "hour,expected", [(0, 0), (4, 0), (5, 5), (9, 5), (10, 10), (14, 10), (15, 15), (19, 15), (20, 20), (23, 20)], ids=["00", "04", "05", "09", "10", "14", "15", "19", "20", "23"], ) def test_五小时额度窗口承接每日边界__2852b5(hour: int, expected: int) -> None: 当前 = datetime(2026, 7, 16, hour, 30, tzinfo=ZoneInfo("Asia/Shanghai")) 窗口 = 归属窗口(当前) assert 窗口.起点.strftime("%Y-%m-%dT%H") == f"2026-07-16T{expected:02}" assert 窗口.终点 - 窗口.起点 == timedelta(hours=4 if hour >= 20 else 5) @pytest.mark.case_id( "TC-7790f820deda", environment="离线纯规则", given="给定四个旧窗口边界时刻", when="计算下一边界等待秒数", then=["分别为7200、1800、10800、60秒"], contract="docs/系统架构/新版设计/模块设计/S02-任务运行.md", ) @pytest.mark.parametrize( "hour,minute,seconds", [(3, 0, 7200), (19, 30, 1800), (21, 0, 10800), (23, 59, 60)], ids=["morning", "20-boundary", "midnight", "last-minute"], ) def test_下一窗口等待秒数按原日界线计算__7790f8(hour: int, minute: int, seconds: int) -> None: 当前 = datetime(2026, 9, 9, hour, minute, tzinfo=ZoneInfo("Asia/Shanghai")) assert (归属窗口(当前).终点 - 当前).total_seconds() == seconds @pytest.mark.case_id( "NC-budget-plan-reserve", environment="离线纯规则", given="各角色计划次数、容量和单次金额", when="登记任务预算计划", then=["保留计划与570最坏预留,金额不足拒绝"], contract="docs/系统架构/新版设计/模块设计/S02-任务运行.md", ) def test_预算计划保留调用计划并按单次上限预留__a60003() -> None: 角色 = ( 角色预算("writer", 45, 150, Decimal("5")), 角色预算("detector", 24, 150, Decimal("5")), 角色预算("judge", 45, 150, Decimal("5")), ) 计划 = 任务预算计划( Decimal("570"), 角色, "approval-1", datetime(2030, 1, 1, tzinfo=ZoneInfo("UTC")) ) assert 计划.最坏预留 == Decimal("570.000000") assert 计划.冻结()["角色"][0]["计划次数"] == 45 with pytest.raises(预算错误): 任务预算计划(Decimal("569.99"), 角色, "approval-1", 计划.截止时间) @pytest.mark.case_id( "NC-budget-amount-validation", environment="离线纯规则", given="NaN、负数与无穷金额", when="构造额度策略", then=["不能作为确定可用额度"], contract="docs/系统架构/新版设计/模块设计/S02-任务运行.md", ) @pytest.mark.parametrize( "amount", [Decimal("NaN"), Decimal("-1"), Decimal("Infinity")], ids=["nan", "negative", "infinite"], ) def test_预算拒绝不确定或负数金额__a60001(amount: Decimal) -> None: with pytest.raises(预算错误): 额度策略("quota", "1", amount, 6000) @pytest.mark.case_id( "NC-config-controlled-credentials", environment="离线纯规则", given="明确凭据引用或角色参数内明文凭据", when="冻结运行配置内容", then=["引用保留而不支持的凭据字段拒绝"], contract="docs/系统架构/新版设计/模块设计/S02-任务运行.md", ) def test_配置凭据只保留引用且调用参数不可藏入明文__a60002() -> None: 引用 = 凭据引用("api", "环境变量", "MUSE_PROVIDER_KEY") 内容 = 运行配置内容( "direct", "1", "role-policy-1", "release-1", "quota-1", {"writer": {"provider": "provider", "model": "model", "thinking": "high"}}, (引用,), (提供方配置("provider", "responses", "https://provider.invalid/v1/responses", "api"),), "price-1", ) assert 内容.冻结()["凭据"][0]["位置"] == "MUSE_PROVIDER_KEY" with pytest.raises(配置版本错误): 运行配置内容( "direct", "1", "policy", "release", "quota", {"writer": {"api_key": "synthetic-secret"}}, (), (), "price-1", )