Raise coverage threshold to 95% and expand test coverage
- pyproject.toml: fail_under 80 → 95 - test_alpha: +79 lines - test_cli_workflow: +226 lines - test_derived: +121 lines - test_downloader_contracts: +169 lines - test_features: +16 lines - test_minute_downloader: +81 lines - test_portfolio: +208 lines
This commit is contained in:
@@ -132,6 +132,41 @@ def test_download_daily_raises_when_requested_source_has_no_data(monkeypatch):
|
||||
)
|
||||
|
||||
|
||||
def test_download_daily_akshare_source_skips_baostock(monkeypatch):
|
||||
calls: list[str] = []
|
||||
fallback = pd.DataFrame({
|
||||
"symbol": ["sh600000"],
|
||||
"date": ["2024-01-02"],
|
||||
"open": [10.0],
|
||||
"high": [11.0],
|
||||
"low": [9.0],
|
||||
"close": [10.5],
|
||||
"volume": [1000.0],
|
||||
"amount": [10500.0],
|
||||
})
|
||||
|
||||
monkeypatch.setattr(
|
||||
downloader,
|
||||
"_download_baostock",
|
||||
lambda *args: calls.append("baostock") or None,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
downloader,
|
||||
"_download_akshare",
|
||||
lambda *args: calls.append("akshare") or fallback,
|
||||
)
|
||||
|
||||
result = download_daily(
|
||||
"sh600000",
|
||||
"2024-01-02",
|
||||
"2024-01-02",
|
||||
source="akshare",
|
||||
)
|
||||
|
||||
assert calls == ["akshare"]
|
||||
assert result["date"].tolist() == [pd.Timestamp("2024-01-02")]
|
||||
|
||||
|
||||
def test_akshare_daily_downloader_maps_columns_and_failures(monkeypatch):
|
||||
calls: list[dict] = []
|
||||
raw = pd.DataFrame({
|
||||
@@ -326,6 +361,34 @@ def test_download_daily_batch_periodic_relogin_and_none_result(monkeypatch):
|
||||
assert logout_count == 2
|
||||
|
||||
|
||||
def test_download_daily_batch_empty_rows_yields_none(monkeypatch):
|
||||
monkeypatch.setattr(downloader.bs, "login", lambda: None)
|
||||
monkeypatch.setattr(downloader.bs, "logout", lambda: None)
|
||||
monkeypatch.setattr(
|
||||
downloader.bs,
|
||||
"query_history_k_data_plus",
|
||||
lambda **kwargs: _FakeResult([]),
|
||||
)
|
||||
|
||||
assert list(download_daily_batch(["sh600000"], "2024-01-02", "2024-01-02")) == [
|
||||
("sh600000", None)
|
||||
]
|
||||
|
||||
|
||||
def test_download_daily_batch_generic_exception_yields_none(monkeypatch):
|
||||
monkeypatch.setattr(downloader.bs, "login", lambda: None)
|
||||
monkeypatch.setattr(downloader.bs, "logout", lambda: None)
|
||||
monkeypatch.setattr(
|
||||
downloader.bs,
|
||||
"query_history_k_data_plus",
|
||||
lambda **kwargs: (_ for _ in ()).throw(RuntimeError("query failed")),
|
||||
)
|
||||
|
||||
assert list(download_daily_batch(["sh600000"], "2024-01-02", "2024-01-02")) == [
|
||||
("sh600000", None)
|
||||
]
|
||||
|
||||
|
||||
def test_download_daily_batch_relogs_and_retries_session_loss(monkeypatch):
|
||||
responses = [
|
||||
_FakeResult([], error_code="10002007", error_msg="用户未登录"),
|
||||
@@ -359,6 +422,32 @@ def test_download_daily_batch_relogs_and_retries_session_loss(monkeypatch):
|
||||
assert logout_count == 2
|
||||
|
||||
|
||||
def test_download_daily_batch_second_session_loss_and_logout_failure(monkeypatch):
|
||||
responses = [
|
||||
_FakeResult([], error_code="10002007", error_msg="用户未登录"),
|
||||
_FakeResult([], error_code="10002007", error_msg="用户未登录"),
|
||||
]
|
||||
logout_count = 0
|
||||
|
||||
def fake_logout():
|
||||
nonlocal logout_count
|
||||
logout_count += 1
|
||||
raise RuntimeError("logout failed")
|
||||
|
||||
monkeypatch.setattr(downloader.bs, "login", lambda: None)
|
||||
monkeypatch.setattr(downloader.bs, "logout", fake_logout)
|
||||
monkeypatch.setattr(
|
||||
downloader.bs,
|
||||
"query_history_k_data_plus",
|
||||
lambda **kwargs: responses.pop(0),
|
||||
)
|
||||
|
||||
assert list(download_daily_batch(["sh600000"], "2024-01-02", "2024-01-02")) == [
|
||||
("sh600000", None)
|
||||
]
|
||||
assert logout_count == 2
|
||||
|
||||
|
||||
def test_download_daily_batch_uses_akshare_fallback_when_enabled(monkeypatch):
|
||||
fallback = pd.DataFrame({
|
||||
"symbol": ["sh600000"],
|
||||
@@ -438,6 +527,10 @@ def test_download_universe_writes_daily_partitions_from_mock_batch(tmp_path, mon
|
||||
|
||||
monkeypatch.setattr(pipeline_downloader, "download_daily_batch", fake_batch)
|
||||
|
||||
stale_file = tmp_path / "toy" / "month=2024-01" / "stale.pq"
|
||||
stale_file.parent.mkdir(parents=True)
|
||||
batch_frame.iloc[[0]][DATA_COLUMNS[2:]].to_parquet(stale_file, index=False)
|
||||
|
||||
stats = download_universe(
|
||||
universe="toy",
|
||||
start_date="2024-01-02",
|
||||
@@ -458,5 +551,81 @@ def test_download_universe_writes_daily_partitions_from_mock_batch(tmp_path, mon
|
||||
}
|
||||
assert (dataset_path / "month=2024-01").exists()
|
||||
assert (dataset_path / "month=2024-02").exists()
|
||||
assert not stale_file.exists()
|
||||
assert written[DATA_COLUMNS].columns.tolist() == DATA_COLUMNS
|
||||
assert written["symbol_name"].tolist() == ["PF Bank", "PF Bank"]
|
||||
|
||||
|
||||
def test_download_universe_raises_when_all_daily_symbols_empty(tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(
|
||||
pipeline_downloader,
|
||||
"_resolve_universe",
|
||||
lambda universe, max_symbols=0: pd.DataFrame({
|
||||
"symbol_id": ["sh600000"],
|
||||
"symbol_name": ["PF Bank"],
|
||||
}),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
pipeline_downloader,
|
||||
"download_daily_batch",
|
||||
lambda symbols, start, end, adjust="qfq": iter([("sh600000", None)]),
|
||||
)
|
||||
|
||||
with pytest.raises(RuntimeError, match="No data downloaded"):
|
||||
download_universe(
|
||||
universe="toy",
|
||||
start_date="2024-01-02",
|
||||
end_date="2024-01-02",
|
||||
output_dir=str(tmp_path),
|
||||
)
|
||||
|
||||
|
||||
def test_download_universe_progress_branch_at_100_symbols(tmp_path, monkeypatch):
|
||||
symbols = [f"sh6{i:05d}" for i in range(100)]
|
||||
batch_frame = pd.DataFrame({
|
||||
"symbol": ["sh600000"],
|
||||
"date": [pd.Timestamp("2024-01-02")],
|
||||
"open": [10.0],
|
||||
"high": [11.0],
|
||||
"low": [9.0],
|
||||
"close": [10.5],
|
||||
"preclose": [10.0],
|
||||
"volume": [1000.0],
|
||||
"amount": [10500.0],
|
||||
"vwap": [10.5],
|
||||
"turn": [1.0],
|
||||
"pctChg": [5.0],
|
||||
"tradestatus": [1],
|
||||
"isST": [0],
|
||||
"peTTM": [8.0],
|
||||
"pbMRQ": [1.1],
|
||||
"psTTM": [2.1],
|
||||
"pcfNcfTTM": [3.1],
|
||||
})
|
||||
|
||||
monkeypatch.setattr(
|
||||
pipeline_downloader,
|
||||
"_resolve_universe",
|
||||
lambda universe, max_symbols=0: pd.DataFrame({
|
||||
"symbol_id": symbols,
|
||||
"symbol_name": symbols,
|
||||
}),
|
||||
)
|
||||
|
||||
def fake_batch(requested_symbols, start, end, adjust="qfq"):
|
||||
assert requested_symbols == symbols
|
||||
for symbol in requested_symbols:
|
||||
yield symbol, batch_frame.copy()
|
||||
|
||||
monkeypatch.setattr(pipeline_downloader, "download_daily_batch", fake_batch)
|
||||
|
||||
stats = download_universe(
|
||||
universe="toy100",
|
||||
start_date="2024-01-02",
|
||||
end_date="2024-01-02",
|
||||
output_dir=str(tmp_path),
|
||||
chunk_size=200,
|
||||
)
|
||||
|
||||
assert stats["n_symbols"] == 100
|
||||
assert stats["n_rows"] == 100
|
||||
|
||||
Reference in New Issue
Block a user