Files
set50-system/backend/tests/test_prices.py
2026-08-23 12:38:17 +07:00

106 lines
4.3 KiB
Python

import copy
import hashlib
import json
import tempfile
import unittest
from datetime import datetime, timezone
from pathlib import Path
from app.prices import (
DEFAULT_SYMBOL_MAP,
PriceSnapshotStore,
PriceSourceError,
collect_price_snapshot,
normalize_yahoo_chart,
trading_dates,
)
FIXTURE = Path(__file__).parent / "fixtures" / "yahoo_chart_aot.json"
PAYLOAD = json.loads(FIXTURE.read_text(encoding="utf-8"))
class FakePriceProvider:
def __init__(self, payload):
self.payload = payload
self.calls = []
def fetch_series(self, canonical_symbol, provider_symbol, start, end):
self.calls.append((canonical_symbol, provider_symbol, start, end))
body = json.dumps(self.payload, sort_keys=True).encode("utf-8")
return normalize_yahoo_chart(
self.payload,
canonical_symbol=canonical_symbol,
provider_symbol=provider_symbol,
retrieved_at="2026-08-23T06:00:00+00:00",
raw_payload_hash=hashlib.sha256(body).hexdigest(),
), body
class PriceSnapshotTests(unittest.TestCase):
def test_default_symbol_map_uses_set_provider_symbols(self):
self.assertEqual(DEFAULT_SYMBOL_MAP["AOT"], "AOT.BK")
self.assertEqual(DEFAULT_SYMBOL_MAP["SET50"], "^SET.BK")
self.assertEqual(DEFAULT_SYMBOL_MAP["PTT"], "PTT.BK")
def test_normalize_yahoo_chart_preserves_adjusted_close_and_trading_dates(self):
series = normalize_yahoo_chart(
PAYLOAD,
canonical_symbol="AOT",
provider_symbol="AOT.BK",
retrieved_at="2026-08-23T06:00:00+00:00",
raw_payload_hash="a" * 64,
)
self.assertEqual(series["exchange"], "SET")
self.assertEqual(series["currency"], "THB")
self.assertEqual([bar["date"] for bar in series["bars"]], ["2026-01-02", "2026-01-05"])
self.assertEqual(series["bars"][0]["adjusted_close"], 60.0)
self.assertEqual(trading_dates(series), ["2026-01-02", "2026-01-05"])
def test_normalize_yahoo_chart_skips_incomplete_rows(self):
payload = copy.deepcopy(PAYLOAD)
payload["chart"]["result"][0]["indicators"]["quote"][0]["close"][0] = None
payload["chart"]["result"][0]["indicators"]["adjclose"][0]["adjclose"][0] = None
series = normalize_yahoo_chart(
payload,
canonical_symbol="AOT",
provider_symbol="AOT.BK",
retrieved_at="2026-08-23T06:00:00+00:00",
raw_payload_hash="a" * 64,
)
self.assertEqual(len(series["bars"]), 1)
def test_normalize_yahoo_chart_rejects_missing_result(self):
payload = copy.deepcopy(PAYLOAD)
payload["chart"]["result"] = []
with self.assertRaisesRegex(PriceSourceError, "result"):
normalize_yahoo_chart(payload, canonical_symbol="AOT", provider_symbol="AOT.BK", retrieved_at="2026-08-23T06:00:00+00:00", raw_payload_hash="a" * 64)
def test_collector_persists_revised_vendor_history_snapshot(self):
provider = FakePriceProvider(PAYLOAD)
with tempfile.TemporaryDirectory() as temp_dir:
snapshot = collect_price_snapshot(
Path(temp_dir),
start="2026-01-01",
end="2026-01-06",
symbol_map={"AOT": "AOT.BK", "SET50": "^SET.BK"},
provider=provider,
retrieved_at="2026-08-23T06:00:00+00:00",
)
self.assertEqual(snapshot["source"]["quality"], "revised_vendor_history")
self.assertFalse(snapshot["source"]["point_in_time"])
self.assertEqual(snapshot["source"]["bar_counts"]["AOT"], 2)
manifest = PriceSnapshotStore(Path(temp_dir)).load_manifest()
self.assertEqual(len(manifest["snapshots"]), 1)
self.assertEqual(len(provider.calls), 2)
def test_price_store_rejects_snapshot_with_mismatched_raw_hash(self):
with tempfile.TemporaryDirectory() as temp_dir:
store = PriceSnapshotStore(Path(temp_dir))
snapshot = {"source": {"snapshot_id": "prices-test", "raw_payload_hash": "a" * 64}}
with self.assertRaisesRegex(PriceSourceError, "raw payload hash"):
store.persist(snapshot, {"AOT.BK": b"different"})
if __name__ == "__main__":
unittest.main()