"""Smoke tests for /api/meet-kevin/* routes. Three behaviors covered: 1. GET /api/meet-kevin/health → 200 with counts_by_status, daily_cost_usd, daily_cost_cap_usd 2. GET /api/meet-kevin/videos (empty DB) → {"videos": [], "total": 0, "page": 1, "per_page": 20} 3. GET /api/meet-kevin/stocks (empty DB) → {"stocks": []} """ from __future__ import annotations from unittest.mock import AsyncMock, MagicMock import pytest from fastapi.testclient import TestClient from services.api_gateway.auth.middleware import get_current_user from services.api_gateway.config import ApiGatewayConfig from services.api_gateway.main import create_app # --------------------------------------------------------------------------- # Fixtures # --------------------------------------------------------------------------- @pytest.fixture() def config() -> ApiGatewayConfig: return ApiGatewayConfig( jwt_secret_key="test-secret-meet-kevin", database_url="sqlite+aiosqlite:///:memory:", redis_url="redis://localhost:6379/0", ) @pytest.fixture() def mock_user() -> dict: return {"sub": "user-test", "username": "tester", "type": "access"} @pytest.fixture() def mock_redis() -> AsyncMock: redis = AsyncMock() redis.get = AsyncMock(return_value=None) redis.set = AsyncMock() return redis @pytest.fixture() def mock_session(): """Async context-manager session mock.""" session = AsyncMock() session.__aenter__ = AsyncMock(return_value=session) session.__aexit__ = AsyncMock(return_value=False) return session @pytest.fixture() def mock_factory(mock_session): factory = MagicMock(return_value=mock_session) return factory def _scalar_result(value): """Mock execute() result that returns a scalar.""" result = MagicMock() result.scalar.return_value = value result.scalar_one_or_none.return_value = value result.scalars.return_value.all.return_value = [] result.all.return_value = [] return result def _rows_result(rows): """Mock execute() result that returns rows.""" result = MagicMock() result.all.return_value = rows result.scalars.return_value.all.return_value = rows result.scalar.return_value = len(rows) result.scalar_one_or_none.return_value = rows[0] if rows else None return result @pytest.fixture() def client(config, mock_user, mock_redis, mock_factory) -> TestClient: app = create_app(config) app.dependency_overrides[get_current_user] = lambda: mock_user app.state.redis = mock_redis app.state.db_session_factory = mock_factory app.state.db_engine = MagicMock() app.state.config = config return TestClient(app, raise_server_exceptions=False) # --------------------------------------------------------------------------- # Test: GET /api/meet-kevin/health # --------------------------------------------------------------------------- class TestMeetKevinHealth: def test_health_returns_required_keys( self, client: TestClient, mock_session: AsyncMock ) -> None: """health returns counts_by_status, daily_cost_usd, daily_cost_cap_usd.""" # Health calls: count queries by status + daily cost sum + last_polled_at # Return Nones/zeros for empty DB — any 4 sequential scalar calls mock_session.execute = AsyncMock( side_effect=[ _scalar_result(0), # discovered count _scalar_result(0), # captioned count _scalar_result(0), # analyzed count _scalar_result(0), # failed count _scalar_result(0), # skipped count _scalar_result(None), # daily cost sum _scalar_result(None), # last_polled_at _scalar_result(None), # daily_cost_cap_usd ] ) resp = client.get("/api/meet-kevin/health") assert resp.status_code == 200, resp.text data = resp.json() assert "counts_by_status" in data assert "daily_cost_usd" in data assert "daily_cost_cap_usd" in data # --------------------------------------------------------------------------- # Test: GET /api/meet-kevin/videos (empty DB) # --------------------------------------------------------------------------- class TestMeetKevinVideos: def test_videos_empty_db_returns_empty_page( self, client: TestClient, mock_session: AsyncMock ) -> None: """GET /api/meet-kevin/videos on empty DB returns empty paginated response.""" # Two execute calls: count query + data query mock_session.execute = AsyncMock( side_effect=[ _scalar_result(0), # count _rows_result([]), # data rows ] ) resp = client.get("/api/meet-kevin/videos") assert resp.status_code == 200, resp.text data = resp.json() assert data["videos"] == [] assert data["total"] == 0 assert data["page"] == 1 assert data["per_page"] == 20 # --------------------------------------------------------------------------- # Test: GET /api/meet-kevin/stocks (empty DB) # --------------------------------------------------------------------------- class TestMeetKevinStocks: def test_stocks_empty_db_returns_empty_list( self, client: TestClient, mock_session: AsyncMock ) -> None: """GET /api/meet-kevin/stocks on empty DB returns {"stocks": []}.""" mock_session.execute = AsyncMock( side_effect=[ _rows_result([]), # aggregate query ] ) resp = client.get("/api/meet-kevin/stocks") assert resp.status_code == 200, resp.text data = resp.json() assert data == {"stocks": []}