Files
ai-exchange/tests/unit/test_database_readiness.py
T

130 lines
5.6 KiB
Python

import contextlib
import io
import json
import os
import unittest
from datetime import datetime, timezone
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://user:pass@localhost:5432/ai_ict_test")
from scripts import run_database_readiness
class DatabaseReadinessTests(unittest.TestCase):
def test_seed_readiness_reports_missing_seed_data(self) -> None:
result = run_database_readiness.evaluate_seed_readiness(
found_venues={"OKX"},
found_instruments=set(),
required_venues=("OKX",),
required_instruments=("BTC-USDT",),
)
self.assertFalse(result["ready"])
self.assertEqual(result["missing_instruments"], ["BTC-USDT"])
self.assertIn("missing_required_instruments", result["reasons"])
def test_candle_readiness_can_require_minimum_count(self) -> None:
result = run_database_readiness.evaluate_candle_readiness(candle_count=10, min_candles=20)
self.assertFalse(result["ready"])
self.assertIn("not_enough_candles", result["reasons"])
def test_candle_readiness_can_require_span_and_freshness(self) -> None:
result = run_database_readiness.evaluate_candle_readiness(
candle_count=100,
first_ts_open=datetime(2026, 4, 29, 8, 0, tzinfo=timezone.utc),
last_ts_open=datetime(2026, 4, 29, 8, 30, tzinfo=timezone.utc),
min_span_minutes=60,
max_age_minutes=10,
now=datetime(2026, 4, 29, 9, 0, tzinfo=timezone.utc),
)
self.assertFalse(result["ready"])
self.assertIn("not_enough_candle_span", result["reasons"])
self.assertIn("candles_too_stale", result["reasons"])
self.assertEqual(result["span_minutes"], 30.0)
self.assertEqual(result["age_minutes"], 30.0)
self.assertEqual(result["first_ts_open"], "2026-04-29T08:00:00+00:00")
def test_run_database_readiness_passes_when_all_gates_are_ready(self) -> None:
captured = {}
def fake_candle_probe(**kwargs):
captured.update(kwargs)
return {"ready": True, "candle_count": 3, "reasons": []}
result = run_database_readiness.run_database_readiness(
env={"READINESS_MIN_CANDLES": "2"},
db_probe=lambda: {"available": True, "value": 1},
schema_probe=lambda: {"ready": True, "missing_tables": [], "missing_columns": {}},
seed_probe=lambda **kwargs: {"ready": True, "reasons": []},
candle_probe=fake_candle_probe,
)
self.assertTrue(result["passed"])
self.assertEqual(result["reasons"], [])
self.assertEqual(result["action_plan"][0]["action"], "run_guarded_pipeline")
self.assertEqual(captured["min_candles"], 2)
self.assertEqual(captured["min_span_minutes"], run_database_readiness.DEFAULT_MIN_CANDLE_SPAN_MINUTES)
self.assertEqual(captured["max_age_minutes"], run_database_readiness.DEFAULT_MAX_CANDLE_AGE_MINUTES)
def test_run_database_readiness_collects_reasons(self) -> None:
result = run_database_readiness.run_database_readiness(
env={},
db_probe=lambda: {"available": True, "value": 1},
schema_probe=lambda: {"ready": False, "missing_tables": ["candles"], "missing_columns": {}},
seed_probe=lambda **kwargs: {"ready": False, "reasons": ["missing_required_instruments"]},
candle_probe=lambda **kwargs: {"ready": False, "reasons": ["not_enough_candles"]},
)
self.assertFalse(result["passed"])
self.assertEqual(result["reasons"], ["schema_not_ready", "missing_required_instruments", "not_enough_candles"])
self.assertEqual(
result["next_actions"],
[
"run_migrations_before_pipeline",
"verify_seed_migrations_or_seed_data",
"run_okx_backfill_or_load_replay_sample",
],
)
self.assertEqual(result["action_plan"][0]["command"][-1], "scripts/run_migrations.py")
self.assertTrue(result["action_plan"][0]["requires_database"])
def test_next_actions_default_to_guarded_pipeline_when_ready(self) -> None:
self.assertEqual(run_database_readiness.build_next_actions([]), ["run_guarded_pipeline"])
def test_next_actions_backfill_for_span_or_staleness(self) -> None:
self.assertEqual(
run_database_readiness.build_next_actions(["not_enough_candle_span", "candles_too_stale"]),
["run_okx_backfill_or_load_replay_sample"],
)
def test_main_can_emit_json(self) -> None:
output = io.StringIO()
original = run_database_readiness.run_database_readiness
try:
run_database_readiness.run_database_readiness = lambda env=None: {
"passed": True,
"database": {"available": True},
"schema": {"ready": True},
"seed_data": {"ready": True},
"candles": {"candle_count": 1},
"reasons": [],
"next_actions": ["run_guarded_pipeline"],
"action_plan": run_database_readiness.build_action_plan(["run_guarded_pipeline"]),
}
with contextlib.redirect_stdout(output):
exit_code = run_database_readiness.main(env={"READINESS_OUTPUT_JSON": "1"})
finally:
run_database_readiness.run_database_readiness = original
summary = json.loads(output.getvalue())
self.assertEqual(exit_code, 0)
self.assertTrue(summary["passed"])
self.assertEqual(summary["action_plan"][0]["action"], "run_guarded_pipeline")
if __name__ == "__main__":
unittest.main()