Files
tick-stock-panel/backend/tests/test_ext_pull_timeout_config.py
T
shy3130 72cbf67070 feat(ext-data): 拉取超时进配置, 默认 30 秒与历史行为一致
大响应接口 (如全量集合竞价 /day, 实测 ~77s) 在硬编码 30s 超时下必然失败。

- PullConfig.timeout_seconds: 5~300, 默认 30; config.json 缺字段或手改
  非法值时归一 30 (fail-closed), 旧配置零迁移
- _request_json (定时拉取/手动拉取/测试/回填共用) 与 detect-url 探测
  均读该配置; detect-url 请求体新增同名字段, 创建对话框可随测试识别传入
- 编辑面板 (ExtDataPullPanel) 与创建对话框 (CreateExtDialog) 增加
  拉取超时输入框, 创建时随拉取配置一起落盘
2026-09-14 15:38:45 +08:00

112 lines
3.6 KiB
Python

"""扩展数据拉取的每配置超时 (PullConfig.timeout_seconds)。
历史行为是三处硬编码 30s: 大响应接口 (如全量集合竞价 /day, 实测 ~77s) 会超时。
现在超时进配置, 默认 30 与旧行为完全一致; 拉取 (_request_json) 与
URL 探测 (detect-url) 都读它。老 config.json 无该字段 → 默认 30, 手改非法值 → 归一 30。
"""
from __future__ import annotations
from types import SimpleNamespace
import pytest
from app.services.ext_data import PullConfig
from app.services import ext_pull
# ── 模型: 默认值 / 序列化 / 非法值归一 ──────────────────────────────
def test_timeout_defaults_to_30_and_roundtrips():
assert PullConfig().timeout_seconds == 30
assert PullConfig(timeout_seconds=90).timeout_seconds == 90
d = PullConfig(url="https://x", timeout_seconds=120).to_dict()
assert d["timeout_seconds"] == 120
assert PullConfig.from_dict(d).timeout_seconds == 120
def test_timeout_missing_in_old_config_falls_back_to_30():
legacy = PullConfig(url="https://x").to_dict()
del legacy["timeout_seconds"] # 模拟旧版本 config.json
assert PullConfig.from_dict(legacy).timeout_seconds == 30
@pytest.mark.parametrize("bad", [0, -5, 3, 301, 9999, "abc", None])
def test_timeout_invalid_values_normalized_to_30(bad):
assert PullConfig(timeout_seconds=bad).timeout_seconds == 30
# ── 拉取路径: _request_json 把配置超时传给 httpx ─────────────────────
class _FakeResponse:
def raise_for_status(self):
pass
def json(self):
return {"items": [{"symbol": "600000.SH"}]}
class _FakeClient:
"""捕获 timeout 构造参数的 httpx.AsyncClient 替身。"""
last_timeout: object = None
def __init__(self, timeout=None, **kwargs):
_FakeClient.last_timeout = timeout
async def __aenter__(self):
return self
async def __aexit__(self, *exc):
return False
async def request(self, method, url, **kwargs):
return _FakeResponse()
async def test_request_json_uses_configured_timeout(monkeypatch):
monkeypatch.setattr(ext_pull.httpx, "AsyncClient", _FakeClient)
pull = PullConfig(url="https://x", timeout_seconds=90)
await ext_pull._request_json(pull, "cfg1")
assert _FakeClient.last_timeout == 90
async def test_request_json_defaults_to_30_without_attr(monkeypatch):
monkeypatch.setattr(ext_pull.httpx, "AsyncClient", _FakeClient)
# 测试用的简化 pull 对象没有 timeout_seconds 属性 → 兜底 30
pull = SimpleNamespace(
url="https://x", date_param=None, date_format="iso", headers=None,
method="GET", body=None, auth=None,
)
await ext_pull._request_json(pull, "cfg1")
assert _FakeClient.last_timeout == 30
# ── 探测路径: detect-url 请求体带 timeout_seconds ───────────────────
async def test_detect_url_uses_request_timeout(monkeypatch):
import httpx
from app.api import ext_data as ext_api
monkeypatch.setattr(httpx, "AsyncClient", _FakeClient)
resp = await ext_api.detect_url(ext_api.DetectUrlReq(
url="https://x", response_path="items", timeout_seconds=120,
))
assert _FakeClient.last_timeout == 120
assert resp["status"] == "ok"
async def test_detect_url_timeout_defaults_to_30(monkeypatch):
import httpx
from app.api import ext_data as ext_api
monkeypatch.setattr(httpx, "AsyncClient", _FakeClient)
await ext_api.detect_url(ext_api.DetectUrlReq(url="https://x", response_path="items"))
assert _FakeClient.last_timeout == 30