"""Tests for the issued-forecast archive (store + API endpoint).""" import asyncio import datetime import pytest from src.forecast_history import ForecastHistoryStore def _rows(as_of="2026-08-12T10:00:00"): return [ { "as_of": as_of, "station_code": "P.1", "horizon_hours": h, "predicted_max_level": 2.8 + h / 100, "p_warning": 0.02, "p_danger": 0.001, "current_level": 2.76, "model_version": "hgb-v1+test", "trained_at": "2026-08-12T10:52:00", "source": "model", } for h in (6, 12, 24) ] class TestForecastHistoryStore: @pytest.fixture def store(self, tmp_path): store = ForecastHistoryStore(f"sqlite:///{tmp_path}/fh.db", "sqlite") assert store.connect() return store def test_rejects_non_sql(self): with pytest.raises(ValueError): ForecastHistoryStore("http://x", "victoriametrics") def test_roundtrip_and_upsert(self, store): assert store.save_rows(_rows()) == 3 # Same as_of again -> upsert, still 3 rows assert store.save_rows(_rows()) == 3 rows = store.fetch("P.1") assert len(rows) == 3 assert [r["horizon_hours"] for r in rows] == [6, 12, 24] assert rows[2]["predicted_max_level"] == pytest.approx(3.04) assert rows[0]["model_version"] == "hgb-v1+test" def test_fetch_filters(self, store): store.save_rows(_rows("2026-08-12T10:00:00")) store.save_rows(_rows("2026-08-12T11:00:00")) only_24 = store.fetch("P.1", horizon_hours=24) assert len(only_24) == 2 assert all(r["horizon_hours"] == 24 for r in only_24) windowed = store.fetch( "P.1", start=datetime.datetime(2026, 8, 12, 10, 30), end=datetime.datetime(2026, 8, 12, 12, 0), ) assert len(windowed) == 3 # only the 11:00 issue assert store.fetch("P.99") == [] def test_skips_malformed_rows(self, store): rows = _rows() + [{"station_code": None, "as_of": None}] assert store.save_rows(rows) == 3 class TestForecastHistoryEndpoint: def test_endpoint_returns_rows(self, tmp_path, monkeypatch): from src import web_api store = ForecastHistoryStore(f"sqlite:///{tmp_path}/api-fh.db", "sqlite") assert store.connect() now = datetime.datetime.now().replace(minute=0, second=0, microsecond=0) store.save_rows(_rows(now.isoformat())) monkeypatch.setitem(web_api.app_state, "forecast_store", store) rows = asyncio.run( web_api.get_forecast_history( "P.1", hours=48, start=None, end=None, horizon=24 ) ) assert len(rows) == 1 assert rows[0]["horizon_hours"] == 24 assert rows[0]["station_code"] == "P.1" def test_endpoint_without_store(self, monkeypatch): from src import web_api monkeypatch.setitem(web_api.app_state, "forecast_store", None) assert ( asyncio.run( web_api.get_forecast_history( "P.1", hours=168, start=None, end=None, horizon=None ) ) == [] )