SHA256
142 lines
4.6 KiB
Python
142 lines
4.6 KiB
Python
"""验证 daily.py 中腾讯/东方财富 HTTP 解析逻辑(不打网络,用 requests.get mock)"""
|
||
|
||
from unittest.mock import patch, MagicMock
|
||
|
||
from src.fetchers import daily
|
||
|
||
|
||
def _make_response(payload: dict) -> MagicMock:
|
||
resp = MagicMock()
|
||
resp.json.return_value = payload
|
||
return resp
|
||
|
||
|
||
# ── 腾讯解析 ──
|
||
|
||
def test_tencent_qfqday_parsed_correctly():
|
||
"""腾讯 qfqday 数组每项格式: [date, open, close, high, low, volume]"""
|
||
payload = {
|
||
"data": {
|
||
"sh600000": {
|
||
"qfqday": [
|
||
["2026-05-08", "10.00", "10.50", "10.80", "9.90", "12345"],
|
||
["2026-05-09", "10.50", "11.00", "11.20", "10.40", "23456"],
|
||
]
|
||
}
|
||
}
|
||
}
|
||
|
||
with patch.object(daily.requests, "get", return_value=_make_response(payload)):
|
||
rows = daily._fetch_tencent("600000", "20260508", "20260509")
|
||
|
||
assert rows is not None
|
||
assert len(rows) == 2
|
||
assert rows[0]["code"] == "600000"
|
||
assert rows[0]["date"] == "2026-05-08"
|
||
assert rows[0]["open"] == 10.0
|
||
assert rows[0]["close"] == 10.5 # 注意: K 线第三位是 close
|
||
assert rows[0]["high"] == 10.8
|
||
assert rows[0]["low"] == 9.9
|
||
assert rows[0]["volume"] == 12345
|
||
# 腾讯不返回这些
|
||
assert rows[0]["pct_change"] is None
|
||
assert rows[0]["amplitude"] is None
|
||
|
||
|
||
def test_tencent_falls_back_to_day_when_no_qfq():
|
||
"""qfqday 缺失时退到 day"""
|
||
payload = {
|
||
"data": {
|
||
"sz000001": {
|
||
"day": [
|
||
["2026-05-08", "10", "10.5", "10.8", "9.9", "100"],
|
||
]
|
||
}
|
||
}
|
||
}
|
||
with patch.object(daily.requests, "get", return_value=_make_response(payload)):
|
||
rows = daily._fetch_tencent("000001", "20260508", "20260508")
|
||
assert rows and rows[0]["close"] == 10.5
|
||
|
||
|
||
def test_tencent_empty_returns_none():
|
||
payload = {"data": {"sh600000": {}}}
|
||
with patch.object(daily.requests, "get", return_value=_make_response(payload)):
|
||
rows = daily._fetch_tencent("600000", "20260508", "20260508")
|
||
assert rows is None
|
||
|
||
|
||
def test_tencent_beijing_returns_none_without_request():
|
||
"""北交所代码应直接返回 None,不发起请求"""
|
||
with patch.object(daily.requests, "get") as mock_get:
|
||
rows = daily._fetch_tencent("920001", "20260508", "20260508")
|
||
assert rows is None
|
||
mock_get.assert_not_called()
|
||
|
||
|
||
# ── 东方财富解析 ──
|
||
|
||
def test_eastmoney_kline_parsed_correctly():
|
||
"""东方财富 klines 每行: date,open,close,high,low,volume,amount,amplitude,pct_change,change,turnover_rate"""
|
||
payload = {
|
||
"data": {
|
||
"klines": [
|
||
"2026-05-08,10.00,10.50,10.80,9.90,12345,123456789,9.0,5.0,0.5,1.2",
|
||
]
|
||
}
|
||
}
|
||
with patch.object(daily.requests, "get", return_value=_make_response(payload)):
|
||
rows = daily._fetch_eastmoney("600000", "20260508", "20260508")
|
||
|
||
assert rows is not None and len(rows) == 1
|
||
r = rows[0]
|
||
assert r["code"] == "600000"
|
||
assert r["date"] == "2026-05-08"
|
||
assert r["open"] == 10.0
|
||
assert r["close"] == 10.5
|
||
assert r["high"] == 10.8
|
||
assert r["low"] == 9.9
|
||
assert r["volume"] == 12345
|
||
assert r["turnover"] == 123456789
|
||
assert r["amplitude"] == 9.0
|
||
assert r["pct_change"] == 5.0
|
||
assert r["change"] == 0.5
|
||
assert r["turnover_rate"] == 1.2
|
||
|
||
|
||
def test_eastmoney_handles_empty_optional_fields():
|
||
"""空字符串字段应转为 None,不应抛 ValueError"""
|
||
payload = {
|
||
"data": {
|
||
"klines": [
|
||
"2026-05-08,10.00,10.50,10.80,9.90,12345,123456789,,,,",
|
||
]
|
||
}
|
||
}
|
||
with patch.object(daily.requests, "get", return_value=_make_response(payload)):
|
||
rows = daily._fetch_eastmoney("600000", "20260508", "20260508")
|
||
r = rows[0]
|
||
assert r["amplitude"] is None
|
||
assert r["pct_change"] is None
|
||
assert r["change"] is None
|
||
assert r["turnover_rate"] is None
|
||
|
||
|
||
def test_eastmoney_no_klines_returns_none():
|
||
payload = {"data": {"klines": []}}
|
||
with patch.object(daily.requests, "get", return_value=_make_response(payload)):
|
||
rows = daily._fetch_eastmoney("600000", "20260508", "20260508")
|
||
assert rows is None
|
||
|
||
|
||
def test_eastmoney_request_exception_raises_runtime():
|
||
"""网络/JSON 异常应包成 RuntimeError,让上层多源切换逻辑能捕获并切下一源"""
|
||
import pytest
|
||
|
||
def _boom(*_a, **_kw):
|
||
raise ConnectionError("network down")
|
||
|
||
with patch.object(daily.requests, "get", side_effect=_boom):
|
||
with pytest.raises(RuntimeError, match="东方财富请求失败"):
|
||
daily._fetch_eastmoney("600000", "20260508", "20260508")
|