"""验证 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")