"""Tests for provider health history store and API endpoint.""" import time from datetime import UTC, datetime, timedelta from unittest.mock import MagicMock import pytest from src.infrastructure.router.history import HealthHistoryStore @pytest.fixture def store(): """In-memory history store for testing.""" s = HealthHistoryStore(db_path=":memory:") yield s s.close() @pytest.fixture def sample_providers(): return [ { "name": "anthropic", "status": "healthy", "error_rate": 0.01, "avg_latency_ms": 250.5, "circuit_state": "closed", "total_requests": 100, }, { "name": "local", "status": "degraded", "error_rate": 0.15, "avg_latency_ms": 80.0, "circuit_state": "closed", "total_requests": 50, }, ] def test_record_and_retrieve(store, sample_providers): store.record_snapshot(sample_providers) history = store.get_history(hours=1) assert len(history) == 1 assert len(history[0]["providers"]) == 2 assert history[0]["providers"][0]["name"] == "anthropic" assert history[0]["providers"][1]["name"] == "local" assert "timestamp" in history[0] def test_multiple_snapshots(store, sample_providers): store.record_snapshot(sample_providers) time.sleep(0.01) store.record_snapshot(sample_providers) history = store.get_history(hours=1) assert len(history) == 2 def test_hours_filtering(store, sample_providers): old_ts = (datetime.now(UTC) - timedelta(hours=48)).isoformat() store._conn.execute( """INSERT INTO snapshots (timestamp, provider_name, status, error_rate, avg_latency_ms, circuit_state, total_requests) VALUES (?, ?, ?, ?, ?, ?, ?)""", (old_ts, "anthropic", "healthy", 0.0, 100.0, "closed", 10), ) store._conn.commit() store.record_snapshot(sample_providers) history = store.get_history(hours=24) assert len(history) == 1 history = store.get_history(hours=72) assert len(history) == 2 def test_prune(store, sample_providers): old_ts = (datetime.now(UTC) - timedelta(hours=200)).isoformat() store._conn.execute( """INSERT INTO snapshots (timestamp, provider_name, status, error_rate, avg_latency_ms, circuit_state, total_requests) VALUES (?, ?, ?, ?, ?, ?, ?)""", (old_ts, "anthropic", "healthy", 0.0, 100.0, "closed", 10), ) store._conn.commit() store.record_snapshot(sample_providers) deleted = store.prune(keep_hours=168) assert deleted == 1 history = store.get_history(hours=999) assert len(history) == 1 def test_empty_history(store): assert store.get_history(hours=24) == [] def test_capture_snapshot_from_router(store): mock_metrics = MagicMock() mock_metrics.error_rate = 0.05 mock_metrics.avg_latency_ms = 200.0 mock_metrics.total_requests = 42 mock_provider = MagicMock() mock_provider.name = "test-provider" mock_provider.status.value = "healthy" mock_provider.metrics = mock_metrics mock_provider.circuit_state.value = "closed" mock_router = MagicMock() mock_router.providers = [mock_provider] store._capture_snapshot(mock_router) history = store.get_history(hours=1) assert len(history) == 1 p = history[0]["providers"][0] assert p["name"] == "test-provider" assert p["status"] == "healthy" assert p["error_rate"] == 0.05 assert p["total_requests"] == 42 def test_history_api_endpoint(store, sample_providers): """GET /api/v1/router/history returns snapshot data.""" store.record_snapshot(sample_providers) from fastapi import FastAPI from fastapi.testclient import TestClient from src.infrastructure.router.api import get_cascade_router from src.infrastructure.router.api import router as api_router from src.infrastructure.router.history import get_history_store app = FastAPI() app.include_router(api_router) app.dependency_overrides[get_history_store] = lambda: store app.dependency_overrides[get_cascade_router] = lambda: MagicMock() client = TestClient(app) resp = client.get("/api/v1/router/history?hours=1") assert resp.status_code == 200 data = resp.json() assert len(data) == 1 assert len(data[0]["providers"]) == 2 assert data[0]["providers"][0]["name"] == "anthropic" app.dependency_overrides.clear()