diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-17 14:32:37 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-17 14:32:37 +0800 |
| commit | 5c0ba37eda80d39e6ceca59bb1d5f4942f858995 (patch) | |
| tree | 948723f9cedf7ccb0707fa6ee516bd30fe20fd10 /tests/worker/test_normalize.py | |
| download | strategy-lab-5c0ba37eda80d39e6ceca59bb1d5f4942f858995.tar.gz | |
chore: establish Strategy Lab source baseline (development, not release)
Diffstat (limited to 'tests/worker/test_normalize.py')
| -rw-r--r-- | tests/worker/test_normalize.py | 92 |
1 files changed, 92 insertions, 0 deletions
diff --git a/tests/worker/test_normalize.py b/tests/worker/test_normalize.py new file mode 100644 index 0000000..a4c9a38 --- /dev/null +++ b/tests/worker/test_normalize.py @@ -0,0 +1,92 @@ +"""Normalization tests — synthetic fixtures only (_synthetic).""" +import math +import sys +import types +from pathlib import Path + +import pandas as pd +import pytest + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) +sys.path.insert(0, str(Path(__file__).resolve().parent)) + +from fixtures_synth import synthetic_daily, SYNTH # noqa: E402 +from worker.normalize import normalize_frame, build_manifest_entry, \ + compute_object_hash, coverage_summary, NormalizeError # noqa: E402 + + +def test_synthetic_marker_present(): + assert SYNTH["_synthetic"] is True + df = synthetic_daily() + assert df.attrs["synthetic"] is True + + +def test_normalize_keeps_raw_extra_fields_and_precision(): + df = synthetic_daily() + out = normalize_frame(df, symbol="SH#600000") + assert "_synthetic" not in out.columns + # canonical columns present + for col in ("date", "open", "high", "low", "close", "volume"): + assert col in out.columns + # raw extra fields preserved + assert "amount" in out.columns and "turnover" in out.columns + # precision kept, no rounding drift + assert out["close"].iloc[0] == df["close"].iloc[0] + assert out["volume"].iloc[3] == df["volume"].iloc[3] + # symbol never substituted across instruments + assert out["symbol"].iloc[5] == "SH#600000" + assert out["date"].notna().all() + + +def test_normalize_rejects_missing_ohlcv(): + df = synthetic_daily() + df.loc[4, "close"] = math.nan + with pytest.raises(NormalizeError): + normalize_frame(df, symbol="SH#600000") + + +def test_normalize_rejects_bad_dates(): + df = synthetic_daily() + df["date"] = df["date"].astype(object) + df.loc[2, "date"] = "not-a-date" + with pytest.raises(NormalizeError): + normalize_frame(df, symbol="SH#600000") + + +def test_normalize_sorts_and_dedups_dates(): + df = synthetic_daily() + df = pd.concat([df.iloc[5:], df.iloc[:5]]).reset_index(drop=True) + out = normalize_frame(df, symbol="SH#600000") + assert list(out["date"]) == sorted(out["date"]) + assert len(out) == 20 + + +def test_object_hash_stable_and_content_sensitive(): + df = synthetic_daily() + h1 = compute_object_hash(df) + h2 = compute_object_hash(normalize_frame(df, symbol="SH#600000")) + assert h1 == h2 and len(h1) == 64 + df2 = synthetic_daily(base=11.0) + assert compute_object_hash(df2) != h1 + + +def test_coverage_summary_and_manifest_entry(): + df = synthetic_daily(days=10, extra_fields=False) + cov = coverage_summary(df) + assert cov["actual_start"] == df["date"].iloc[0] + entry = build_manifest_entry( + df, instrument={"symbol": "SH#600000", "market": "cn", "asset_type": "stock"}, + provider="akshare", endpoint="stock_zh_a_hist", params={"period": "daily"}, + adjustment="none", requested_start="2024-01-01", requested_end="2024-12-31", + raw_object_hash="rawhash", warnings=[], + schema_version="1", normalization_version="1", + path="objects/ab/cd.json", fetched_at="2026-09-16T00:00:00Z", + akshare_version="1.18.95", + ) + assert entry["immutable"] is True + assert entry["row_count"] == 10 + assert entry["adjustment"] == "none" + assert entry["actual_start"] <= entry["actual_end"] + assert "potential symbol substitution" not in entry["warnings"] + for internal in ("path",): + assert internal in entry # server strips internal-only fields downstream |
