Improve offline coverage for data boundaries
This commit is contained in:
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import pytest
|
||||
|
||||
import data.downloader as downloader
|
||||
import pipeline.data.downloader as pipeline_downloader
|
||||
@@ -119,6 +120,121 @@ def test_download_daily_falls_back_to_akshare_when_baostock_empty(monkeypatch):
|
||||
assert result["date"].tolist() == [pd.Timestamp("2024-01-02")]
|
||||
|
||||
|
||||
def test_download_daily_raises_when_requested_source_has_no_data(monkeypatch):
|
||||
monkeypatch.setattr(downloader, "_download_baostock", lambda *args: None)
|
||||
|
||||
with pytest.raises(RuntimeError, match="Failed to download data for sh600000"):
|
||||
download_daily(
|
||||
"sh600000",
|
||||
"2024-01-02",
|
||||
"2024-01-02",
|
||||
source="baostock",
|
||||
)
|
||||
|
||||
|
||||
def test_akshare_daily_downloader_maps_columns_and_failures(monkeypatch):
|
||||
calls: list[dict] = []
|
||||
raw = pd.DataFrame({
|
||||
"日期": ["2024-01-02"],
|
||||
"开盘": [10.0],
|
||||
"最高": [11.0],
|
||||
"最低": [9.0],
|
||||
"收盘": [10.5],
|
||||
"成交量": [1000.0],
|
||||
"成交额": [10500.0],
|
||||
"换手率": [1.2],
|
||||
})
|
||||
|
||||
def fake_hist(**kwargs):
|
||||
calls.append(kwargs)
|
||||
return raw.copy()
|
||||
|
||||
monkeypatch.setattr(downloader.ak, "stock_zh_a_hist", fake_hist)
|
||||
|
||||
result = downloader._download_akshare(
|
||||
"sh600000",
|
||||
"20240102",
|
||||
"20240102",
|
||||
adjust="",
|
||||
)
|
||||
|
||||
assert calls == [{
|
||||
"symbol": "600000",
|
||||
"period": "daily",
|
||||
"start_date": "20240102",
|
||||
"end_date": "20240102",
|
||||
"adjust": "",
|
||||
}]
|
||||
assert result is not None
|
||||
assert result.columns.tolist() == [
|
||||
"symbol", "date", "open", "high", "low", "close", "volume", "amount",
|
||||
]
|
||||
assert result["symbol"].tolist() == ["sh600000"]
|
||||
|
||||
monkeypatch.setattr(downloader.ak, "stock_zh_a_hist", lambda **kwargs: pd.DataFrame())
|
||||
assert downloader._download_akshare("sh600000", "20240102", "20240102") is None
|
||||
|
||||
monkeypatch.setattr(
|
||||
downloader.ak,
|
||||
"stock_zh_a_hist",
|
||||
lambda **kwargs: (_ for _ in ()).throw(RuntimeError("provider down")),
|
||||
)
|
||||
assert downloader._download_akshare("sh600000", "20240102", "20240102") is None
|
||||
|
||||
|
||||
def test_baostock_daily_downloader_maps_errors_and_logout_failures(monkeypatch):
|
||||
query_calls: list[dict] = []
|
||||
row = ["2024-01-02", "10", "11", "9", "10.5", "1000", "10500"]
|
||||
|
||||
monkeypatch.setattr(downloader.bs, "login", lambda: None)
|
||||
monkeypatch.setattr(
|
||||
downloader.bs,
|
||||
"logout",
|
||||
lambda: (_ for _ in ()).throw(RuntimeError("logout failed")),
|
||||
)
|
||||
|
||||
def fake_query(**kwargs):
|
||||
query_calls.append(kwargs)
|
||||
return _FakeResult([row])
|
||||
|
||||
monkeypatch.setattr(downloader.bs, "query_history_k_data_plus", fake_query)
|
||||
|
||||
result = downloader._download_baostock(
|
||||
"sz000001",
|
||||
"2024-01-02",
|
||||
"2024-01-02",
|
||||
adjust="none",
|
||||
)
|
||||
|
||||
assert query_calls[0]["code"] == "sz.000001"
|
||||
assert query_calls[0]["adjustflag"] == "3"
|
||||
assert result is not None
|
||||
assert result["symbol"].tolist() == ["sz000001"]
|
||||
assert pd.api.types.is_numeric_dtype(result["close"])
|
||||
|
||||
monkeypatch.setattr(downloader.bs, "logout", lambda: None)
|
||||
monkeypatch.setattr(
|
||||
downloader.bs,
|
||||
"query_history_k_data_plus",
|
||||
lambda **kwargs: _FakeResult([], error_code="1", error_msg="bad symbol"),
|
||||
)
|
||||
assert downloader._download_baostock("sz000001", "2024-01-02", "2024-01-02") is None
|
||||
|
||||
monkeypatch.setattr(
|
||||
downloader.bs,
|
||||
"query_history_k_data_plus",
|
||||
lambda **kwargs: _FakeResult([]),
|
||||
)
|
||||
assert downloader._download_baostock("sz000001", "2024-01-02", "2024-01-02") is None
|
||||
|
||||
monkeypatch.setattr(
|
||||
downloader.bs,
|
||||
"query_history_k_data_plus",
|
||||
lambda **kwargs: (_ for _ in ()).throw(RuntimeError("query failed")),
|
||||
)
|
||||
assert downloader._download_baostock("sz000001", "2024-01-02", "2024-01-02") is None
|
||||
|
||||
|
||||
def test_download_daily_batch_maps_rich_schema_and_vwap(monkeypatch):
|
||||
query_calls: list[dict] = []
|
||||
login_count = 0
|
||||
@@ -170,6 +286,46 @@ def test_download_daily_batch_maps_rich_schema_and_vwap(monkeypatch):
|
||||
assert pd.api.types.is_numeric_dtype(frame["tradestatus"])
|
||||
|
||||
|
||||
def test_download_daily_batch_periodic_relogin_and_none_result(monkeypatch):
|
||||
responses = [
|
||||
_FakeResult([], error_code="1", error_msg="bad symbol"),
|
||||
_FakeResult([_daily_batch_row(date="2024-01-03")]),
|
||||
]
|
||||
login_count = 0
|
||||
logout_count = 0
|
||||
|
||||
def fake_login():
|
||||
nonlocal login_count
|
||||
login_count += 1
|
||||
|
||||
def fake_logout():
|
||||
nonlocal logout_count
|
||||
logout_count += 1
|
||||
|
||||
monkeypatch.setattr(downloader.bs, "login", fake_login)
|
||||
monkeypatch.setattr(downloader.bs, "logout", fake_logout)
|
||||
monkeypatch.setattr(
|
||||
downloader.bs,
|
||||
"query_history_k_data_plus",
|
||||
lambda **kwargs: responses.pop(0),
|
||||
)
|
||||
|
||||
results = list(
|
||||
download_daily_batch(
|
||||
["sh600000", "sz000001"],
|
||||
"2024-01-02",
|
||||
"2024-01-03",
|
||||
relogin_every=1,
|
||||
)
|
||||
)
|
||||
|
||||
assert results[0] == ("sh600000", None)
|
||||
assert results[1][0] == "sz000001"
|
||||
assert results[1][1] is not None
|
||||
assert login_count == 2
|
||||
assert logout_count == 2
|
||||
|
||||
|
||||
def test_download_daily_batch_relogs_and_retries_session_loss(monkeypatch):
|
||||
responses = [
|
||||
_FakeResult([], error_code="10002007", error_msg="用户未登录"),
|
||||
|
||||
Reference in New Issue
Block a user