Synthra / tests /api /test_app.py
DevWizard-Vandan
fix(api): mount Next.js frontend as default app and prefix all API routes under /api
cede2ad
Raw
History Blame Contribute Delete
5.48 kB
"""Tests for the FastAPI service layer."""
from typing import Generator
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from fastapi.testclient import TestClient
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _make_mock_state() -> MagicMock:
"""Return a fully-configured mock ServiceState."""
state = MagicMock()
state.is_ready = True
state.campaign_count.return_value = 0
state.simulations_executed.return_value = 0
state.initialize = AsyncMock()
state.shutdown = AsyncMock()
state.governor = MagicMock()
return state
# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------
@pytest.fixture()
def client() -> Generator[TestClient, None, None]:
"""Provide a TestClient with mocked ServiceState initialization."""
mock_state = _make_mock_state()
with patch("synthra.api.app.ServiceState.get_instance", return_value=mock_state):
from synthra.api.app import create_app
application = create_app()
with TestClient(application, raise_server_exceptions=True) as tc:
yield tc
# ---------------------------------------------------------------------------
# Root
# ---------------------------------------------------------------------------
def test_root_returns_service_identity(client: TestClient) -> None:
"""GET /api returns service name, version, and running status."""
response = client.get("/api")
assert response.status_code == 200
body = response.json()
assert body["service"] == "Synthra"
assert body["version"] == "0.1.0"
assert body["status"] == "running"
# ---------------------------------------------------------------------------
# Health
# ---------------------------------------------------------------------------
def test_health_returns_healthy(client: TestClient) -> None:
"""GET /api/health returns status=healthy."""
response = client.get("/api/health")
assert response.status_code == 200
assert response.json() == {"status": "healthy"}
def test_health_is_fast(client: TestClient) -> None:
"""GET /api/health must complete without touching state or DB."""
import time
start = time.perf_counter()
response = client.get("/api/health")
elapsed = time.perf_counter() - start
assert response.status_code == 200
# Must complete within 200 ms even on slow CI
assert elapsed < 0.2
# ---------------------------------------------------------------------------
# Status
# ---------------------------------------------------------------------------
def test_status_returns_expected_schema(client: TestClient) -> None:
"""GET /api/status returns version, uptime_seconds, and memory sub-object."""
response = client.get("/api/status")
assert response.status_code == 200
body = response.json()
assert body["version"] == "0.1.0"
assert isinstance(body["uptime_seconds"], float)
assert body["uptime_seconds"] >= 0
mem = body["memory"]
assert mem["backend"] == "sqlite"
assert isinstance(mem["campaign_count"], int)
assert isinstance(mem["simulations_executed"], int)
# ---------------------------------------------------------------------------
# Campaigns (stub)
# ---------------------------------------------------------------------------
def test_campaigns_list_returns_empty(client: TestClient) -> None:
"""GET /api/campaigns returns empty list and total."""
response = client.get("/api/campaigns")
assert response.status_code == 200
body = response.json()
assert body["campaigns"] == []
assert body["total"] == 0
# ---------------------------------------------------------------------------
# Import sanity
# ---------------------------------------------------------------------------
def test_app_module_importable() -> None:
"""The app module must be importable from the expected path."""
from synthra.api import app as app_module # noqa: F401
assert hasattr(app_module, "app")
def test_state_module_importable() -> None:
"""ServiceState must be importable from synthra.api.state."""
from synthra.api.state import ServiceState # noqa: F401
assert ServiceState is not None
def test_run_campaign_endpoint(client: TestClient) -> None:
"""POST /api/campaigns/{campaign_id}/run calls governor.run_campaign."""
from synthra.api.app import ServiceState
mock_state = ServiceState.get_instance()
response = client.post("/api/campaigns/CMP-0001/run")
assert response.status_code == 200
assert response.json() == {
"status": "success",
"message": "Campaign CMP-0001 queued for execution",
}
mock_state.governor.run_campaign.assert_called_once_with("CMP-0001")
def test_run_campaign_endpoint_error(client: TestClient) -> None:
"""POST /api/campaigns/{campaign_id}/run handles errors properly."""
from synthra.api.app import ServiceState
mock_state = ServiceState.get_instance()
mock_state.governor.run_campaign.side_effect = ValueError("Some campaign error")
response = client.post("/api/campaigns/CMP-0001/run")
assert response.status_code == 200
assert response.json() == {
"status": "error",
"message": "Some campaign error",
}