Files
2026-07-13 13:26:28 +08:00

85 lines
3.1 KiB
Python

"""Unit tests for data/futures/loader.py — list_cme_products().
The loader binds ML4T_DATA_PATH at import time (`from utils import
ML4T_DATA_PATH`), so monkeypatching the env var is insufficient —
we patch the module symbol directly.
"""
from __future__ import annotations
from pathlib import Path
import pytest
from data.exceptions import DataNotFoundError
from data.futures import loader as futures_loader
def _build_hive_fixture(root: Path, products: list[str]) -> None:
root.mkdir(parents=True, exist_ok=True)
for p in products:
(root / f"product={p}").mkdir()
def _build_individual_fixture(root: Path, products: list[str], include_empty: bool = False) -> None:
root.mkdir(parents=True, exist_ok=True)
for p in products:
pdir = root / p
pdir.mkdir()
(pdir / "data.parquet").write_bytes(b"fake-parquet")
if include_empty:
(root / "EMPTY_DIR").mkdir() # no data.parquet — must be skipped
@pytest.fixture
def isolated_data_path(tmp_path, monkeypatch):
"""Redirect the loader's bound ML4T_DATA_PATH to a temp directory."""
monkeypatch.setattr(futures_loader, "ML4T_DATA_PATH", tmp_path)
return tmp_path
def test_list_cme_products_hourly_returns_sorted_unique(isolated_data_path) -> None:
hive_root = isolated_data_path / "futures" / "market" / "continuous" / "hourly"
_build_hive_fixture(hive_root, ["ES", "NQ", "CL", "GC", "6E"])
products = futures_loader.list_cme_products()
assert products == ["6E", "CL", "ES", "GC", "NQ"]
def test_list_cme_products_ignores_non_product_subdirs(isolated_data_path) -> None:
hive_root = isolated_data_path / "futures" / "market" / "continuous" / "hourly"
hive_root.mkdir(parents=True, exist_ok=True)
(hive_root / "product=ES").mkdir()
(hive_root / "_metadata").mkdir()
(hive_root / "random_other_dir").mkdir()
(hive_root / "readme.txt").write_text("x")
assert futures_loader.list_cme_products() == ["ES"]
def test_list_cme_products_individual_requires_data_parquet(isolated_data_path) -> None:
ind_root = isolated_data_path / "futures" / "market" / "individual"
_build_individual_fixture(ind_root, ["ES", "CL"], include_empty=True)
assert futures_loader.list_cme_products(frequency="individual") == ["CL", "ES"]
def test_list_cme_products_raises_when_hourly_root_missing(isolated_data_path) -> None:
with pytest.raises(DataNotFoundError, match="CME Futures Hourly"):
futures_loader.list_cme_products()
def test_list_cme_products_raises_when_individual_root_missing(isolated_data_path) -> None:
with pytest.raises(DataNotFoundError, match="CME Futures Individual"):
futures_loader.list_cme_products(frequency="individual")
def test_list_cme_products_rejects_unknown_frequency(isolated_data_path) -> None:
with pytest.raises(ValueError, match="frequency must be"):
futures_loader.list_cme_products(frequency="daily")
def test_list_cme_products_returns_empty_list_for_empty_hive(isolated_data_path) -> None:
(isolated_data_path / "futures" / "market" / "continuous" / "hourly").mkdir(parents=True)
assert futures_loader.list_cme_products() == []