trading/tests/api_gateway/routes/test_meet_kevin.py

173 lines
5.7 KiB
Python

"""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": []}