Files
set50-system/backend/tests/test_prices.py
Kunthawat Greethong ead9aeb25c chore: pre-existing in-tree work (event-study/research/vintages/prices + migration script + integrity docs)
Committing the prior uncommitted working-tree state that predates this session's
data-source work (was already modified/untracked at session start) so the tree
is clean before push. Includes: event-study + research report integrity/forward
observation work, prices tests, research hash migration script, and the
2026-08-23/24 engineering-log + test-evidence notes. Verified green as part of
the full 362-test suite.
2026-08-29 09:19:24 +07:00

862 lines
41 KiB
Python

import copy
import hashlib
import json
import tempfile
import unittest
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
from app.prices import (
DEFAULT_SYMBOL_MAP,
PIT_ARCHIVE_CONTRACT,
PriceSnapshotStore,
PriceSourceError,
_normalized_snapshot_hash,
_observation_id,
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"))
def valid_non_pit_snapshot(snapshot_id="prices-valid", retrieved_at="2026-02-01T00:00:00+00:00"):
return {
"schema_version": 1,
"source": {
"source_id": "test.prices",
"source_url": "https://example.test/prices",
"snapshot_id": snapshot_id,
"retrieved_at": retrieved_at,
"period_start": "2026-01-01",
"period_end": "2026-01-02",
"quality": "revised_vendor_history",
"point_in_time": False,
"parser_version": "test-v1",
"adjusted_prices": False,
},
"series": {
"AOT": {
"canonical_symbol": "AOT",
"provider_symbol": "AOT.BK",
"raw_payload_hash": hashlib.sha256(b"aot").hexdigest(),
"timezone": "Asia/Bangkok",
"bars": [{"date": "2026-01-01", "close": 100.0}],
}
},
}
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_normalize_yahoo_chart_rejects_malformed_provider_shapes_and_numbers(self):
cases = []
payload = copy.deepcopy(PAYLOAD)
payload["chart"]["result"][0]["meta"] = []
cases.append(("meta", payload))
payload = copy.deepcopy(PAYLOAD)
payload["chart"]["result"][0]["indicators"]["quote"][0] = []
cases.append(("quote", payload))
payload = copy.deepcopy(PAYLOAD)
payload["chart"]["result"][0]["timestamp"][0] = 10**100
cases.append(("timestamp overflow", payload))
for label, field, value in (
("boolean timestamp", "timestamp", True),
("boolean close", "close", True),
("boolean volume", "volume", True),
):
payload = copy.deepcopy(PAYLOAD)
if field == "timestamp":
payload["chart"]["result"][0][field][0] = value
else:
payload["chart"]["result"][0]["indicators"]["quote"][0][field][0] = value
cases.append((label, payload))
for label, payload in cases:
with self.subTest(label=label), self.assertRaises(PriceSourceError):
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_collector_records_observation_history_for_unchanged_payload(self):
provider = FakePriceProvider(PAYLOAD)
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
collect_price_snapshot(
root,
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",
)
collect_price_snapshot(
root,
start="2026-01-01",
end="2026-01-06",
symbol_map={"AOT": "AOT.BK", "SET50": "^SET.BK"},
provider=provider,
retrieved_at="2026-08-24T06:00:00+00:00",
)
store = PriceSnapshotStore(root)
manifest = store.load_manifest()
observations = manifest["observations"]
self.assertEqual(len(observations), 2)
self.assertEqual(observations[0]["revision_status"], "initial")
self.assertEqual(observations[1]["revision_status"], "unchanged")
self.assertEqual(observations[1]["previous_raw_payload_hash"], observations[0]["raw_payload_hash"])
self.assertFalse(observations[1]["diff"]["normalized_content_changed"])
entry = store.latest_snapshot_entry()
self.assertEqual(entry["first_seen_at"], "2026-08-23T06:00:00+00:00")
self.assertEqual(entry["last_seen_at"], "2026-08-24T06:00:00+00:00")
self.assertEqual(entry["observation_count"], 2)
def test_collector_records_revision_diff_without_overwriting_previous_snapshot(self):
changed_payload = copy.deepcopy(PAYLOAD)
changed_payload["chart"]["result"][0]["indicators"]["quote"][0]["close"][0] = 61.0
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
collect_price_snapshot(
root,
start="2026-01-01",
end="2026-01-06",
symbol_map={"AOT": "AOT.BK", "SET50": "^SET.BK"},
provider=FakePriceProvider(PAYLOAD),
retrieved_at="2026-08-23T06:00:00+00:00",
)
revised = collect_price_snapshot(
root,
start="2026-01-01",
end="2026-01-06",
symbol_map={"AOT": "AOT.BK", "SET50": "^SET.BK"},
provider=FakePriceProvider(changed_payload),
retrieved_at="2026-08-24T06:00:00+00:00",
)
store = PriceSnapshotStore(root)
manifest = store.load_manifest()
observations = manifest["observations"]
self.assertEqual(len(manifest["snapshots"]), 2)
self.assertEqual(len(observations), 2)
self.assertEqual(observations[1]["revision_status"], "revised")
self.assertTrue(observations[1]["diff"]["normalized_content_changed"])
self.assertIn("series.AOT.bars[0].close", observations[1]["diff"]["changed_paths"])
self.assertEqual(observations[1]["previous_snapshot_id"], observations[0]["snapshot_id"])
self.assertNotEqual(revised["source"]["snapshot_id"], observations[0]["snapshot_id"])
self.assertEqual(store.load_snapshot(observations[0]["snapshot_id"])["series"]["AOT"]["bars"][0]["close"], 61.5)
def test_price_store_rejects_tampered_observation_audit_entry(self):
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
collect_price_snapshot(
root,
start="2026-01-01",
end="2026-01-06",
symbol_map={"AOT": "AOT.BK", "SET50": "^SET.BK"},
provider=FakePriceProvider(PAYLOAD),
retrieved_at="2026-08-23T06:00:00+00:00",
)
store = PriceSnapshotStore(root)
manifest = store.load_manifest()
manifest["observations"][0]["diff"]["changed_paths"] = ["series.AOT.bars[0].close"]
store.manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
with self.assertRaises(PriceSourceError):
store.list_observations()
def test_price_store_rejects_recomputed_observation_id_with_mismatched_predecessor(self):
changed_payload = copy.deepcopy(PAYLOAD)
changed_payload["chart"]["result"][0]["indicators"]["quote"][0]["close"][0] = 61.0
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
collect_price_snapshot(
root,
start="2026-01-01",
end="2026-01-06",
symbol_map={"AOT": "AOT.BK", "SET50": "^SET.BK"},
provider=FakePriceProvider(PAYLOAD),
retrieved_at="2026-08-23T06:00:00+00:00",
)
collect_price_snapshot(
root,
start="2026-01-01",
end="2026-01-06",
symbol_map={"AOT": "AOT.BK", "SET50": "^SET.BK"},
provider=FakePriceProvider(changed_payload),
retrieved_at="2026-08-24T06:00:00+00:00",
)
store = PriceSnapshotStore(root)
manifest = store.load_manifest()
tampered = manifest["observations"][1]
tampered["previous_raw_payload_hash"] = "0" * 64
tampered["observation_id"] = _observation_id({key: value for key, value in tampered.items() if key != "observation_id"})
store.manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
with self.assertRaisesRegex(PriceSourceError, "previous.*hash|observation"):
store.list_observations()
def test_price_store_rejects_recomputed_observation_id_with_tampered_diff(self):
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
collect_price_snapshot(
root,
start="2026-01-01",
end="2026-01-06",
symbol_map={"AOT": "AOT.BK", "SET50": "^SET.BK"},
provider=FakePriceProvider(PAYLOAD),
retrieved_at="2026-08-23T06:00:00+00:00",
)
store = PriceSnapshotStore(root)
manifest = store.load_manifest()
tampered = manifest["observations"][0]
tampered["diff"]["changed_paths"] = ["series.AOT.bars[0].close"]
tampered["observation_id"] = _observation_id({key: value for key, value in tampered.items() if key != "observation_id"})
store.manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
with self.assertRaisesRegex(PriceSourceError, "diff|observation"):
store.list_observations()
def test_price_store_rejects_malformed_observation_id_types_without_type_error(self):
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
collect_price_snapshot(
root,
start="2026-01-01",
end="2026-01-06",
symbol_map={"AOT": "AOT.BK", "SET50": "^SET.BK"},
provider=FakePriceProvider(PAYLOAD),
retrieved_at="2026-08-23T06:00:00+00:00",
)
store = PriceSnapshotStore(root)
manifest = store.load_manifest()
tampered = manifest["observations"][0]
tampered["snapshot_id"] = []
tampered["observation_id"] = _observation_id({key: value for key, value in tampered.items() if key != "observation_id"})
store.manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
with self.assertRaisesRegex(PriceSourceError, "observation"):
store.list_observations()
def test_price_store_rejects_observation_history_truncation(self):
changed_payload = copy.deepcopy(PAYLOAD)
changed_payload["chart"]["result"][0]["indicators"]["quote"][0]["close"][0] = 61.0
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
collect_price_snapshot(
root,
start="2026-01-01",
end="2026-01-06",
symbol_map={"AOT": "AOT.BK", "SET50": "^SET.BK"},
provider=FakePriceProvider(PAYLOAD),
retrieved_at="2026-08-23T06:00:00+00:00",
)
collect_price_snapshot(
root,
start="2026-01-01",
end="2026-01-06",
symbol_map={"AOT": "AOT.BK", "SET50": "^SET.BK"},
provider=FakePriceProvider(changed_payload),
retrieved_at="2026-08-24T06:00:00+00:00",
)
store = PriceSnapshotStore(root)
manifest = store.load_manifest()
manifest["observations"] = manifest["observations"][:-1]
store.manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
with self.assertRaisesRegex(PriceSourceError, "observation"):
store.list_observations()
def test_collector_rejects_out_of_order_observation_after_future_capture(self):
revised_payload = copy.deepcopy(PAYLOAD)
revised_payload["chart"]["result"][0]["indicators"]["quote"][0]["close"][0] = 61.0
late_payload = copy.deepcopy(PAYLOAD)
late_payload["chart"]["result"][0]["indicators"]["quote"][0]["close"][0] = 62.0
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
collect_price_snapshot(
root,
start="2026-01-01",
end="2026-01-06",
symbol_map={"AOT": "AOT.BK", "SET50": "^SET.BK"},
provider=FakePriceProvider(PAYLOAD),
retrieved_at="2026-08-23T06:00:00+00:00",
)
collect_price_snapshot(
root,
start="2026-01-01",
end="2026-01-06",
symbol_map={"AOT": "AOT.BK", "SET50": "^SET.BK"},
provider=FakePriceProvider(revised_payload),
retrieved_at="2026-08-24T06:00:00+00:00",
)
before = json.loads((root / "manifest.json").read_text(encoding="utf-8"))
with self.assertRaisesRegex(PriceSourceError, "immutable predecessor"):
collect_price_snapshot(
root,
start="2026-01-01",
end="2026-01-06",
symbol_map={"AOT": "AOT.BK", "SET50": "^SET.BK"},
provider=FakePriceProvider(late_payload),
retrieved_at="2026-08-22T06:00:00+00:00",
)
after = json.loads((root / "manifest.json").read_text(encoding="utf-8"))
self.assertEqual(after, before)
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"})
def test_price_store_rejects_same_raw_hash_with_changed_stable_content(self):
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
store = PriceSnapshotStore(root)
snapshot = {
"schema_version": 1,
"source": {
"source_id": "test.prices",
"snapshot_id": "prices-same-raw",
"retrieved_at": "2026-02-01T00:00:00+00:00",
"period_start": "2026-01-01",
"period_end": "2026-01-01",
"quality": "revised_vendor_history",
"point_in_time": False,
},
"series": {
"AOT": {
"canonical_symbol": "AOT",
"provider_symbol": "AOT.BK",
"raw_payload_hash": hashlib.sha256(b"aot").hexdigest(),
"timezone": "Asia/Bangkok",
"bars": [{"date": "2026-01-01", "close": 100.0}],
}
},
}
raw = {"AOT.BK": b"aot"}
store.persist(snapshot, raw)
changed = copy.deepcopy(snapshot)
changed["series"]["AOT"]["bars"][0]["close"] = 101.0
with self.assertRaisesRegex(PriceSourceError, "immutable"):
store.persist(changed, raw)
def test_price_store_rejects_same_raw_payload_with_changed_normalized_content(self):
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
store = PriceSnapshotStore(root)
initial = valid_non_pit_snapshot("prices-same-raw-initial", "2026-02-01T00:00:00+00:00")
store.persist(initial, {"AOT.BK": b"aot"})
revised = valid_non_pit_snapshot("prices-same-raw-revised", "2026-02-02T00:00:00+00:00")
revised["series"]["AOT"]["bars"][0]["close"] = 101.0
with self.assertRaisesRegex(PriceSourceError, "normalized content"):
store.persist(revised, {"AOT.BK": b"aot"})
def test_price_store_rejects_point_in_time_flag_without_archive_contract(self):
with tempfile.TemporaryDirectory() as temp_dir:
store = PriceSnapshotStore(Path(temp_dir))
snapshot = {
"schema_version": 1,
"source": {
"source_id": "test.prices",
"snapshot_id": "prices-pit-missing-contract",
"retrieved_at": "2026-02-01T00:00:00+00:00",
"period_start": "2026-01-01",
"period_end": "2026-01-02",
"quality": "forward_market_archive",
"point_in_time": True,
},
"series": {"AOT": {"bars": [{"date": "2026-01-01", "close": 100.0}]}},
}
with self.assertRaisesRegex(PriceSourceError, "point-in-time archive contract"):
store.persist(snapshot, {"AOT.BK": b"aot"})
def test_price_store_requires_known_at_for_each_pit_bar(self):
with tempfile.TemporaryDirectory() as temp_dir:
store = PriceSnapshotStore(Path(temp_dir))
snapshot = {
"schema_version": 1,
"source": {
"source_id": "test.prices",
"source_url": "https://example.test/prices",
"snapshot_id": "prices-pit-missing-known-at",
"retrieved_at": "2026-02-01T00:00:00+00:00",
"period_start": "2026-01-01",
"period_end": "2026-01-01",
"quality": "point_in_time_archive",
"point_in_time": True,
"archive_contract": PIT_ARCHIVE_CONTRACT,
"provider_release_id": "release-2026-01-01",
"parser_version": "pit-test-v1",
"point_in_time_evidence": {
"known_at_field": "known_at",
"known_at_semantics": "provider_release_time",
"provider_release_id": "release-2026-01-01",
"release_published_at": "2026-01-01T17:00:00+07:00",
},
},
"series": {"AOT": {"canonical_symbol": "AOT", "timezone": "Asia/Bangkok", "bars": [{"session_date": "2026-01-01", "close": 100.0}]}},
}
with self.assertRaisesRegex(PriceSourceError, "known_at"):
store.persist(snapshot, {"AOT.BK": b"aot"})
def test_price_store_persists_and_loads_explicit_pit_archive_contract(self):
with tempfile.TemporaryDirectory() as temp_dir:
store = PriceSnapshotStore(Path(temp_dir))
snapshot = {
"schema_version": 1,
"source": {
"source_id": "test.prices",
"source_url": "https://example.test/prices",
"snapshot_id": "prices-pit-valid",
"retrieved_at": "2026-02-01T00:00:00+00:00",
"period_start": "2026-01-01",
"period_end": "2026-01-01",
"quality": "point_in_time_archive",
"point_in_time": True,
"archive_contract": PIT_ARCHIVE_CONTRACT,
"provider_release_id": "release-2026-01-01",
"parser_version": "pit-test-v1",
"point_in_time_evidence": {
"known_at_field": "known_at",
"known_at_semantics": "provider_release_time",
"provider_release_id": "release-2026-01-01",
"release_published_at": "2026-01-01T17:00:00+07:00",
},
},
"series": {
"AOT": {
"canonical_symbol": "AOT",
"timezone": "Asia/Bangkok",
"bars": [{
"session_date": "2026-01-01",
"open": 99.0,
"high": 101.0,
"low": 98.0,
"close": 100.0,
"adjusted_close": 100.0,
"volume": 1000,
"known_at": "2026-01-01T17:00:00+07:00",
}],
}
},
}
loaded = store.persist(snapshot, {"AOT.BK": b"aot"})
self.assertEqual(loaded["source"]["archive_contract"], PIT_ARCHIVE_CONTRACT)
self.assertEqual(store.load_snapshot("prices-pit-valid")["source"]["provider_release_id"], "release-2026-01-01")
manifest_entry = store.load_manifest()["snapshots"]["prices-pit-valid"]
self.assertEqual(manifest_entry["archive_contract"], PIT_ARCHIVE_CONTRACT)
self.assertTrue(manifest_entry["point_in_time"])
snapshot["series"]["AOT"]["bars"][0]["close"] = 101.0
with self.assertRaisesRegex(PriceSourceError, "immutable"):
store.persist(snapshot, {"AOT.BK": b"aot-revision"})
manifest = store.load_manifest()
manifest["snapshots"]["prices-pit-valid"]["period_end"] = "2026-01-02"
(Path(temp_dir) / "manifest.json").write_text(json.dumps(manifest), encoding="utf-8")
with self.assertRaisesRegex(PriceSourceError, "point-in-time metadata"):
store.load_snapshot("prices-pit-valid")
def test_price_store_totalizes_extreme_pit_numeric_values(self):
with tempfile.TemporaryDirectory() as temp_dir:
store = PriceSnapshotStore(Path(temp_dir))
snapshot = {
"schema_version": 1,
"source": {
"source_id": "test.prices",
"source_url": "https://example.test/prices",
"snapshot_id": "prices-pit-extreme-number",
"retrieved_at": "2026-02-01T00:00:00+00:00",
"period_start": "2026-01-01",
"period_end": "2026-01-01",
"quality": "point_in_time_archive",
"point_in_time": True,
"archive_contract": PIT_ARCHIVE_CONTRACT,
"provider_release_id": "release-2026-01-01",
"parser_version": "pit-test-v1",
"point_in_time_evidence": {
"known_at_field": "known_at",
"known_at_semantics": "provider_release_time",
"provider_release_id": "release-2026-01-01",
"release_published_at": "2026-01-01T17:00:00+07:00",
},
},
"series": {
"AOT": {
"canonical_symbol": "AOT",
"timezone": "Asia/Bangkok",
"bars": [{
"session_date": "2026-01-01",
"open": 99.0,
"high": 101.0,
"low": 98.0,
"close": 100.0,
"adjusted_close": 100.0,
"known_at": "2026-01-01T17:00:00+07:00",
"volume": 10**1000,
}],
}
},
}
with self.assertRaisesRegex(PriceSourceError, "point-in-time archive volume"):
store.persist(snapshot, {"AOT.BK": b"aot"})
def test_price_store_rejects_non_object_manifest_before_persist(self):
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
(root / "manifest.json").write_text("[]", encoding="utf-8")
store = PriceSnapshotStore(root)
snapshot = {
"schema_version": 1,
"source": {"source_id": "test.prices", "snapshot_id": "prices-malformed-manifest", "point_in_time": False},
"series": {},
}
with self.assertRaisesRegex(PriceSourceError, "manifest"):
store.persist(snapshot, {"AOT.BK": b"aot"})
def test_price_store_rejects_invalid_raw_payload_symbol(self):
with tempfile.TemporaryDirectory() as temp_dir:
store = PriceSnapshotStore(Path(temp_dir))
snapshot = {
"schema_version": 1,
"source": {"source_id": "test.prices", "snapshot_id": "prices-invalid-raw-symbol", "point_in_time": False},
"series": {},
}
raw_payloads: Any = {1: b"aot"}
with self.assertRaisesRegex(PriceSourceError, "raw payload"):
store.persist(snapshot, raw_payloads)
def test_price_store_rejects_series_provenance_mismatch_after_normalized_hash_refresh(self):
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
store = PriceSnapshotStore(root)
snapshot = valid_non_pit_snapshot()
store.persist(snapshot, {"AOT.BK": b"aot"})
snapshot_path = root / "snapshots" / "prices-valid.json"
original = json.loads(snapshot_path.read_text(encoding="utf-8"))
for field, value in (("provider_symbol", "PTT.BK"), ("raw_payload_hash", "0" * 64)):
tampered = copy.deepcopy(original)
tampered["series"]["AOT"][field] = value
tampered["source"]["normalized_snapshot_hash"] = _normalized_snapshot_hash(tampered)
snapshot_path.write_text(json.dumps(tampered), encoding="utf-8")
with self.subTest(field=field), self.assertRaisesRegex(PriceSourceError, "series"):
store.load_snapshot("prices-valid")
def test_price_store_rejects_missing_required_metadata_and_malformed_non_pit_series(self):
cases = []
snapshot = valid_non_pit_snapshot("prices-missing-schema")
snapshot.pop("schema_version")
cases.append(("schema_version", snapshot))
snapshot = valid_non_pit_snapshot("prices-missing-pit-flag")
snapshot["source"].pop("point_in_time")
cases.append(("point_in_time", snapshot))
snapshot = valid_non_pit_snapshot("prices-missing-source-id")
snapshot["source"].pop("source_id")
cases.append(("source identity", snapshot))
snapshot = valid_non_pit_snapshot("prices-malformed-series")
snapshot["series"]["AOT"]["bars"] = "not-a-list"
cases.append(("normalized series", snapshot))
snapshot = valid_non_pit_snapshot("prices-duplicate-bars")
snapshot["series"]["AOT"]["bars"].append(copy.deepcopy(snapshot["series"]["AOT"]["bars"][0]))
cases.append(("duplicate normalized dates", snapshot))
for label, snapshot in cases:
with tempfile.TemporaryDirectory() as temp_dir:
with self.subTest(label=label), self.assertRaises(PriceSourceError):
PriceSnapshotStore(Path(temp_dir)).persist(snapshot, {"AOT.BK": b"aot"})
def test_price_store_rejects_non_pit_manifest_metadata_tampering(self):
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
store = PriceSnapshotStore(root)
store.persist(valid_non_pit_snapshot(), {"AOT.BK": b"aot"})
manifest_path = root / "manifest.json"
original = json.loads(manifest_path.read_text(encoding="utf-8"))
for field, value in (("parser_version", "tampered-parser"), ("symbols", ["PTT"])):
manifest = copy.deepcopy(original)
manifest["snapshots"]["prices-valid"][field] = value
manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
with self.subTest(field=field), self.assertRaisesRegex(PriceSourceError, "metadata"):
store.load_snapshot("prices-valid")
manifest = copy.deepcopy(original)
manifest["snapshots"]["prices-valid"].pop("source_url")
manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
with self.assertRaisesRegex(PriceSourceError, "metadata"):
store.load_snapshot("prices-valid")
def test_price_store_rejects_out_of_order_insertion_without_changing_manifest(self):
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
store = PriceSnapshotStore(root)
late = valid_non_pit_snapshot("prices-late", "2026-02-01T18:30:00-07:00")
early = valid_non_pit_snapshot("prices-early", "2026-02-02T00:00:00+00:00")
store.persist(late, {"AOT.BK": b"aot"})
before = json.loads((root / "manifest.json").read_text(encoding="utf-8"))
with self.assertRaisesRegex(PriceSourceError, "immutable predecessor"):
store.persist(early, {"AOT.BK": b"aot"})
after = json.loads((root / "manifest.json").read_text(encoding="utf-8"))
self.assertEqual(after, before)
self.assertFalse((root / "snapshots" / "prices-early.json").exists())
def test_price_store_uses_deterministic_predecessor_for_equal_timestamps(self):
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
store = PriceSnapshotStore(root)
timestamp = "2026-02-01T00:00:00+00:00"
store.persist(valid_non_pit_snapshot("prices-a", timestamp), {"AOT.BK": b"aot"})
store.persist(valid_non_pit_snapshot("prices-b", timestamp), {"AOT.BK": b"aot"})
observation = next(item for item in store.load_manifest()["observations"] if item["snapshot_id"] == "prices-b")
self.assertEqual(observation["previous_snapshot_id"], "prices-a")
def test_price_store_rejects_reverse_arrival_for_equal_timestamp(self):
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
store = PriceSnapshotStore(root)
timestamp = "2026-02-01T00:00:00+00:00"
store.persist(valid_non_pit_snapshot("prices-b", timestamp), {"AOT.BK": b"aot"})
before = json.loads((root / "manifest.json").read_text(encoding="utf-8"))
with self.assertRaisesRegex(PriceSourceError, "immutable predecessor"):
store.persist(valid_non_pit_snapshot("prices-a", timestamp), {"AOT.BK": b"aot"})
after = json.loads((root / "manifest.json").read_text(encoding="utf-8"))
self.assertEqual(after, before)
def test_price_store_treats_same_snapshot_same_timestamp_as_idempotent(self):
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
store = PriceSnapshotStore(root)
snapshot = valid_non_pit_snapshot("prices-idempotent", "2026-02-01T00:00:00+00:00")
store.persist(snapshot, {"AOT.BK": b"aot"})
store.persist(copy.deepcopy(snapshot), {"AOT.BK": b"aot"})
self.assertEqual(len(store.list_observations()), 1)
def test_price_store_rejects_equal_timestamp_initial_node_with_canonical_predecessor(self):
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
store = PriceSnapshotStore(root)
timestamp = "2026-02-01T00:00:00+00:00"
store.persist(valid_non_pit_snapshot("prices-a", timestamp), {"AOT.BK": b"aot"})
store.persist(valid_non_pit_snapshot("prices-b", timestamp), {"AOT.BK": b"aot"})
manifest_path = root / "manifest.json"
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
observation = next(item for item in manifest["observations"] if item["snapshot_id"] == "prices-b")
observation.pop("observation_id")
observation["previous_snapshot_id"] = None
observation["previous_raw_payload_hash"] = None
observation["revision_status"] = "initial"
observation["observation_id"] = _observation_id(observation)
manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
with self.assertRaisesRegex(PriceSourceError, "predecessor chain"):
store.load_manifest()
def test_price_store_rejects_duplicate_equal_time_observations_for_one_snapshot(self):
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
store = PriceSnapshotStore(root)
snapshot_id = "prices-duplicate-time"
store.persist(valid_non_pit_snapshot(snapshot_id, "2026-02-01T00:00:00+00:00"), {"AOT.BK": b"aot"})
manifest_path = root / "manifest.json"
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
original = manifest["observations"][0]
duplicate_diff = copy.deepcopy(original["diff"])
duplicate_diff["raw_payload_changed"] = False
observations = []
for retrieved_at in ("2026-02-01T00:00:00+00:00", "2026-02-01T00:00:00Z"):
observation = copy.deepcopy(original)
observation["retrieved_at"] = retrieved_at
observation["previous_snapshot_id"] = snapshot_id
observation["previous_raw_payload_hash"] = original["raw_payload_hash"]
observation["revision_status"] = "unchanged"
observation["diff"] = copy.deepcopy(duplicate_diff)
observation["observation_id"] = _observation_id(
{key: value for key, value in observation.items() if key != "observation_id"}
)
observations.append(observation)
manifest["observations"] = observations
entry = manifest["snapshots"][snapshot_id]
entry["observation_count"] = 2
manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
with self.assertRaisesRegex(PriceSourceError, "ambiguous observation"):
store.load_manifest()
def test_price_store_orders_latest_snapshot_by_utc_time(self):
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
store = PriceSnapshotStore(root)
store.persist(valid_non_pit_snapshot("prices-early", "2026-02-02T00:00:00+00:00"), {"AOT.BK": b"aot"})
store.persist(valid_non_pit_snapshot("prices-late", "2026-02-01T18:30:00-07:00"), {"AOT.BK": b"aot"})
self.assertEqual(store.latest_snapshot_entry()["snapshot_id"], "prices-late")
def test_price_store_validates_timing_metadata_when_observation_count_is_omitted(self):
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
store = PriceSnapshotStore(root)
store.persist(valid_non_pit_snapshot(), {"AOT.BK": b"aot"})
manifest_path = root / "manifest.json"
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
entry = manifest["snapshots"]["prices-valid"]
entry.pop("observation_count")
entry["first_seen_at"] = "not-a-timestamp"
entry["last_seen_at"] = "not-a-timestamp"
manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
with self.assertRaisesRegex(PriceSourceError, "timing"):
store.load_manifest()
def test_price_store_rejects_observation_before_snapshot_first_capture(self):
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
store = PriceSnapshotStore(root)
store.persist(valid_non_pit_snapshot("prices-temporal", "2026-02-01T00:00:00+00:00"), {"AOT.BK": b"aot"})
manifest_path = root / "manifest.json"
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
observation = manifest["observations"][0]
observation["retrieved_at"] = "2026-01-31T00:00:00+00:00"
observation["observation_id"] = _observation_id({key: value for key, value in observation.items() if key != "observation_id"})
entry = manifest["snapshots"]["prices-temporal"]
entry["first_seen_at"] = "2026-01-31T00:00:00+00:00"
entry["last_seen_at"] = "2026-01-31T00:00:00+00:00"
manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
with self.assertRaisesRegex(PriceSourceError, "before snapshot"):
store.load_manifest()
def test_price_store_rejects_unreferenced_contract_snapshot(self):
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
store = PriceSnapshotStore(root)
store.persist(valid_non_pit_snapshot(), {"AOT.BK": b"aot"})
manifest_path = root / "manifest.json"
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
unreferenced = copy.deepcopy(manifest["snapshots"]["prices-valid"])
unreferenced["snapshot_id"] = "prices-unreferenced"
unreferenced["snapshot_file"] = "prices-unreferenced.json"
unreferenced.pop("observation_count")
unreferenced.pop("first_seen_at")
unreferenced.pop("last_seen_at")
manifest["snapshots"]["prices-unreferenced"] = unreferenced
manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
with self.assertRaisesRegex(PriceSourceError, "observation count"):
store.load_manifest()
def test_price_store_rejects_malformed_snapshot_manifest_entry(self):
with tempfile.TemporaryDirectory() as temp_dir:
root = Path(temp_dir)
store = PriceSnapshotStore(root)
store.persist(valid_non_pit_snapshot(), {"AOT.BK": b"aot"})
manifest_path = root / "manifest.json"
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
manifest["snapshots"]["prices-malformed-entry"] = []
manifest_path.write_text(json.dumps(manifest), encoding="utf-8")
with self.assertRaisesRegex(PriceSourceError, "snapshot manifest entry"):
store.load_manifest()
if __name__ == "__main__":
unittest.main()