"""计价测试(T-P3):峰谷边界(假时钟 ±1 分钟)/ 毫元取整不漂移 / margin 恒等式。""" import time import pytest from gateway.proxy.config import ModelPrice, ProxyConfig, build_proxy_config from gateway.proxy.pricing import compute, is_offpeak from gateway.settings import DEFAULTS @pytest.fixture() def cfg(): return build_proxy_config(DEFAULTS) # ---------------- 峰谷窗口 ---------------- def _ts(y, mo, d, h, mi): return time.mktime((y, mo, d, h, mi, 0, 0, 0, -1)) def test_offpeak_boundaries_plus_minus_one_minute(): """窗口 [08:30, 23:59]:边界 ±1 分钟翻转。""" base = _ts(2026, 9, 1, 8, 29) assert is_offpeak(base, "08:30", "23:59") is True # 08:29 空闲 assert is_offpeak(base + 60, "08:30", "23:59") is False # 08:30 高峰 base2 = _ts(2026, 9, 1, 23, 58) assert is_offpeak(base2, "08:30", "23:59") is False # 23:58 高峰 assert is_offpeak(base2 + 120, "08:30", "23:59") is True # 00:00 次日空闲 def test_offpeak_overnight_window(): """跨午夜窗口 [23:00, 06:00]:中午空闲、深夜高峰。""" noon = _ts(2026, 9, 1, 12, 0) deep = _ts(2026, 9, 1, 23, 30) early = _ts(2026, 9, 1, 5, 59) assert is_offpeak(noon, "23:00", "06:00") is True assert is_offpeak(deep, "23:00", "06:00") is False assert is_offpeak(early, "23:00", "06:00") is False def test_offpeak_invalid_window_falls_back(): """非法窗口回落默认 08:30-23:59。""" noon = _ts(2026, 9, 1, 12, 0) assert is_offpeak(noon, "garbage", "!!") is False assert is_offpeak(_ts(2026, 9, 1, 7, 0), "garbage", "!!") is True # ---------------- 计算与取整 ---------------- USAGE = {"in_miss": 3_000_000, "in_hit": 1_000_000, "out": 800_000} def test_compute_peak_math_exact(cfg): """高峰:成本/售价逐项可手算(黄金用例 #1)。 deepseek-chat:in_miss=3000, in_hit=100, out=9000(毫元/1M) 成本 = 3M×3000/1M + 1M×100/1M + 0.8M×9000/1M = 9000+100+7200 = 16300 毫元 售价 = 9000×0.5 + 100×0.5 + 7200×0.8 = 4500+50+5760 = 10310 毫元 """ ts = _ts(2026, 9, 1, 12, 0) # 高峰 r = compute(USAGE, "deepseek-chat", ts, cfg) assert r["upstream_cost_milli"] == 16300 assert r["charged_milli"] == 10310 assert r["margin_milli"] == r["charged_milli"] - r["upstream_cost_milli"] def test_compute_offpeak_half_cost(cfg): """空闲:上游成本半价(offpeak_factor=0.5),售价不变(黄金用例 #2)。""" ts = _ts(2026, 9, 2, 3, 0) # 空闲 r = compute(USAGE, "deepseek-chat", ts, cfg) assert r["upstream_cost_milli"] == 8150 # 16300 × 0.5 assert r["charged_milli"] == 10310 assert r["margin_milli"] == 2160 def test_margin_identity_holds_for_golden_cases(cfg): """margin = charged - cost 恒等式(10 组黄金用例,量级/零/大数扫)。""" ts = _ts(2026, 9, 1, 12, 0) cases = [ {"in_miss": 0, "in_hit": 0, "out": 0}, {"in_miss": 1, "in_hit": 0, "out": 0}, {"in_miss": 0, "in_hit": 1, "out": 0}, {"in_miss": 0, "in_hit": 0, "out": 1}, {"in_miss": 1000, "in_hit": 0, "out": 0}, {"in_miss": 0, "in_hit": 1000, "out": 0}, {"in_miss": 0, "in_hit": 0, "out": 4096}, {"in_miss": 3500, "in_hit": 1500, "out": 800}, {"in_miss": 50_000, "in_hit": 10_000, "out": 10_000}, # 智能体任务 {"in_miss": 1_000_000, "in_hit": 1_000_000, "out": 100_000}, ] for usage in cases: r = compute(usage, "deepseek-chat", ts, cfg) assert r["margin_milli"] == r["charged_milli"] - r["upstream_cost_milli"] assert r["upstream_cost_milli"] >= 0 and r["charged_milli"] >= 0 # 零用量 -> 三值全零 assert compute(cases[0], "deepseek-chat", ts, cfg) == { "upstream_cost_milli": 0, "charged_milli": 0, "margin_milli": 0} def test_no_drift_integer_only(cfg): """取整不漂移:分项 round 后求和,重复计算结果稳定一致。""" ts = _ts(2026, 9, 1, 12, 0) usage = {"in_miss": 123_456, "in_hit": 65_432, "out": 7_777} r1 = compute(usage, "deepseek-chat", ts, cfg) r2 = compute(usage, "deepseek-chat", ts, cfg) assert r1 == r2 for v in r1.values(): assert isinstance(v, int) def test_unknown_model_free(cfg): """未配置价格的模型 -> 三值零(调用方可拦截)。""" ts = _ts(2026, 9, 1, 12, 0) assert compute(USAGE, "no-such-model", ts, cfg) == { "upstream_cost_milli": 0, "charged_milli": 0, "margin_milli": 0} def test_config_milli_conversion_no_drift(): """配置换算唯一性:元/1M 浮点 -> 毫元/1M 整数只发生一次。""" c = build_proxy_config({"proxy": {"pricing": { "m1": {"in_miss": 0.003, "in_hit": 0.0001, "out": 0.009}, "m2": {"in_miss": 12.3456, "out": 1.5}}}}) assert c.price("m1").in_miss == 3 assert c.price("m1").in_hit == 1 # 0.0001 元 -> 0.1 毫元 -> 取整 0 -> 触发缺省 max(1, miss//30) # in_hit 缺省 = in_miss//30(D-P3):0.003 元 -> 3 毫元 -> 0(太小),显式补齐后重验 c2 = build_proxy_config({"proxy": {"pricing": { "m1": {"in_miss": 3.0, "in_hit": 0.1, "out": 9.0}, "m2": {"in_miss": 12.3456, "out": 1.5}}}}) assert c2.price("m2").in_miss == 12346 assert c2.price("m2").in_hit == 12346 // 30 # 411