"""回测参数校验(L1 静态规则 + L2 数据最新日)单测 + 2029 回归。 背景:2026-08-15 用户实况——组合回测结束时间选 2029 也能提交 (portfolio_ef8b655a),零校验跑出垃圾结果占任务位。 """ import pytest from fastapi import HTTPException from sanguo_api import validation as V def _ok_range(): return "2024-01-01", "2024-06-30" # ===== L1 静态规则 ===== def test_future_end_rejected(): """用户报的 2029 场景:结束日期在未来必须 400。""" with pytest.raises(HTTPException) as e: V.validate_backtest_range("2024-01-01", "2029-12-31") assert e.value.status_code == 400 assert "未来" in e.value.detail def test_bad_format_rejected(): with pytest.raises(HTTPException) as e: V.validate_backtest_range("2024/01/01", "2024-06-30") assert e.value.status_code == 400 assert "格式" in e.value.detail def test_start_after_end_rejected(): with pytest.raises(HTTPException) as e: V.validate_backtest_range("2024-06-30", "2024-01-01") assert "早于" in e.value.detail def test_span_too_short_rejected(): with pytest.raises(HTTPException) as e: V.validate_backtest_range("2024-01-01", "2024-01-15") assert "过短" in e.value.detail def test_valid_range_passes(): s, t = _ok_range() assert V.validate_backtest_range(s, t) is None def test_capital_and_rate_checks(): with pytest.raises(HTTPException): V.validate_capital("初始资金", 0) with pytest.raises(HTTPException): V.validate_capital("初始资金", -100) assert V.validate_capital("初始资金", 1_000_000) is None # 费率应填小数(万3=0.0003);填成 3 明显是百分数/万分位口径错 with pytest.raises(HTTPException): V.validate_rate("佣金率", 3.0) assert V.validate_rate("佣金率", 0.0003) is None # ===== L2 数据最新日 ===== def test_end_beyond_latest_data_rejected(monkeypatch): # end 是过去日期(过 L1)但超过 mock 的数据最新日 → L2 拦 monkeypatch.setattr(V, "get_latest_daily_date", lambda: "2026-07-31") with pytest.raises(HTTPException) as e: V.validate_backtest_range("2026-01-01", "2026-08-05") assert "数据最新到" in e.value.detail def test_latest_query_failure_degrades_to_l1(monkeypatch): """查库失败返回 None → 只做 L1,数据层抖动不挡提交。""" monkeypatch.setattr(V, "get_latest_daily_date", lambda: None) assert V.validate_backtest_range("2026-01-01", "2026-08-05") is None def test_latest_date_cached(monkeypatch): """24h 缓存:第二次调用不重复查库。""" calls = [] monkeypatch.setattr(V, "_query_latest_daily_date", lambda: calls.append(1) or "2026-08-14") V._latest_cache = None # 清模块缓存 assert V.get_latest_daily_date() == "2026-08-14" assert V.get_latest_daily_date() == "2026-08-14" assert len(calls) == 1 V._latest_cache = None # 还原,避免污染其他测试 # ===== 入口集成:组合回测 2029 必须进不了队列 ===== def test_portfolio_submit_2029_rejected(monkeypatch): """回归:POST /portfolio/backtest end=2029 → 400,不产生任务。""" monkeypatch.setattr(V, "get_latest_daily_date", lambda: "2026-08-14") from sanguo_api.routes_portfolio import PortfolioBacktestRequest from sanguo_api.validation import validate_portfolio_request req = PortfolioBacktestRequest(start_date="2024-01-01", end_date="2029-12-31") with pytest.raises(HTTPException) as e: validate_portfolio_request(req) assert e.value.status_code == 400