200 lines
6.1 KiB
Python
200 lines
6.1 KiB
Python
|
|
"""End-to-end integration tests for the FusionAGI API."""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
starlette = __import__("pytest").importorskip("starlette")
|
||
|
|
fastapi = __import__("pytest").importorskip("fastapi")
|
||
|
|
|
||
|
|
from starlette.testclient import TestClient # noqa: E402
|
||
|
|
|
||
|
|
from fusionagi.api.app import create_app # noqa: E402
|
||
|
|
|
||
|
|
|
||
|
|
def _client() -> TestClient:
|
||
|
|
app = create_app(cors_origins=["*"])
|
||
|
|
return TestClient(app)
|
||
|
|
|
||
|
|
|
||
|
|
class TestSessionLifecycle:
|
||
|
|
"""Test the full session lifecycle: create → prompt → response."""
|
||
|
|
|
||
|
|
def test_create_session(self) -> None:
|
||
|
|
c = _client()
|
||
|
|
resp = c.post("/v1/sessions", json={"user_id": "test-user"})
|
||
|
|
assert resp.status_code == 200
|
||
|
|
data = resp.json()
|
||
|
|
assert "session_id" in data
|
||
|
|
|
||
|
|
def test_prompt_requires_session(self) -> None:
|
||
|
|
c = _client()
|
||
|
|
resp = c.post("/v1/sessions", json={"user_id": "test-user"})
|
||
|
|
sid = resp.json()["session_id"]
|
||
|
|
resp = c.post(f"/v1/sessions/{sid}/prompt", json={"prompt": "Hello"})
|
||
|
|
assert resp.status_code == 200
|
||
|
|
|
||
|
|
def test_unknown_session_returns_error(self) -> None:
|
||
|
|
c = _client()
|
||
|
|
resp = c.post("/v1/sessions/nonexistent/prompt", json={"prompt": "Hello"})
|
||
|
|
assert resp.status_code in (404, 422, 500)
|
||
|
|
|
||
|
|
|
||
|
|
class TestAdminEndpoints:
|
||
|
|
"""Test admin API endpoints."""
|
||
|
|
|
||
|
|
def test_system_status(self) -> None:
|
||
|
|
c = _client()
|
||
|
|
resp = c.get("/v1/admin/status")
|
||
|
|
assert resp.status_code == 200
|
||
|
|
data = resp.json()
|
||
|
|
assert data["status"] == "healthy"
|
||
|
|
assert "uptime_seconds" in data
|
||
|
|
|
||
|
|
def test_list_voices(self) -> None:
|
||
|
|
c = _client()
|
||
|
|
resp = c.get("/v1/admin/voices")
|
||
|
|
assert resp.status_code == 200
|
||
|
|
assert isinstance(resp.json(), list)
|
||
|
|
|
||
|
|
def test_add_voice(self) -> None:
|
||
|
|
c = _client()
|
||
|
|
resp = c.post("/v1/admin/voices", json={"name": "Test Voice", "language": "en-US"})
|
||
|
|
assert resp.status_code == 200
|
||
|
|
assert resp.json()["name"] == "Test Voice"
|
||
|
|
|
||
|
|
def test_ethics_endpoint(self) -> None:
|
||
|
|
c = _client()
|
||
|
|
resp = c.get("/v1/admin/ethics")
|
||
|
|
assert resp.status_code == 200
|
||
|
|
assert isinstance(resp.json(), list)
|
||
|
|
|
||
|
|
def test_consequences_endpoint(self) -> None:
|
||
|
|
c = _client()
|
||
|
|
resp = c.get("/v1/admin/consequences")
|
||
|
|
assert resp.status_code == 200
|
||
|
|
|
||
|
|
def test_insights_endpoint(self) -> None:
|
||
|
|
c = _client()
|
||
|
|
resp = c.get("/v1/admin/insights")
|
||
|
|
assert resp.status_code == 200
|
||
|
|
|
||
|
|
def test_conversation_style(self) -> None:
|
||
|
|
c = _client()
|
||
|
|
resp = c.post("/v1/admin/conversation-style", json={"formality": "formal", "verbosity": "concise"})
|
||
|
|
assert resp.status_code == 200
|
||
|
|
|
||
|
|
def test_telemetry(self) -> None:
|
||
|
|
c = _client()
|
||
|
|
resp = c.get("/v1/admin/telemetry")
|
||
|
|
assert resp.status_code == 200
|
||
|
|
assert "traces" in resp.json()
|
||
|
|
|
||
|
|
|
||
|
|
class TestTenantEndpoints:
|
||
|
|
"""Test multi-tenant API."""
|
||
|
|
|
||
|
|
def test_current_tenant_default(self) -> None:
|
||
|
|
c = _client()
|
||
|
|
resp = c.get("/v1/admin/tenants/current")
|
||
|
|
assert resp.status_code == 200
|
||
|
|
data = resp.json()
|
||
|
|
assert data["tenant_id"] == "default"
|
||
|
|
assert data["is_default"] is True
|
||
|
|
|
||
|
|
def test_current_tenant_custom(self) -> None:
|
||
|
|
c = _client()
|
||
|
|
resp = c.get("/v1/admin/tenants/current", headers={"X-Tenant-ID": "acme"})
|
||
|
|
assert resp.status_code == 200
|
||
|
|
assert resp.json()["tenant_id"] == "acme"
|
||
|
|
|
||
|
|
def test_list_tenants(self) -> None:
|
||
|
|
c = _client()
|
||
|
|
resp = c.get("/v1/admin/tenants")
|
||
|
|
assert resp.status_code == 200
|
||
|
|
assert "tenants" in resp.json()
|
||
|
|
|
||
|
|
def test_create_tenant(self) -> None:
|
||
|
|
c = _client()
|
||
|
|
resp = c.post("/v1/admin/tenants", json={"id": "test-org", "name": "Test Org"})
|
||
|
|
assert resp.status_code == 200
|
||
|
|
assert resp.json()["id"] == "test-org"
|
||
|
|
|
||
|
|
|
||
|
|
class TestPluginEndpoints:
|
||
|
|
"""Test plugin marketplace API."""
|
||
|
|
|
||
|
|
def test_list_plugins(self) -> None:
|
||
|
|
c = _client()
|
||
|
|
resp = c.get("/v1/admin/plugins")
|
||
|
|
assert resp.status_code == 200
|
||
|
|
data = resp.json()
|
||
|
|
assert "available" in data
|
||
|
|
assert "installed" in data
|
||
|
|
|
||
|
|
def test_register_and_install_plugin(self) -> None:
|
||
|
|
c = _client()
|
||
|
|
resp = c.post("/v1/admin/plugins", json={
|
||
|
|
"id": "test-plugin",
|
||
|
|
"name": "Test Plugin",
|
||
|
|
"description": "A test plugin",
|
||
|
|
"version": "1.0.0",
|
||
|
|
})
|
||
|
|
assert resp.status_code == 200
|
||
|
|
assert resp.json()["id"] == "test-plugin"
|
||
|
|
|
||
|
|
resp = c.post("/v1/admin/plugins/test-plugin/install")
|
||
|
|
assert resp.status_code == 200
|
||
|
|
assert resp.json()["status"] == "installed"
|
||
|
|
|
||
|
|
|
||
|
|
class TestBackupEndpoints:
|
||
|
|
"""Test backup/restore API."""
|
||
|
|
|
||
|
|
def test_list_backups(self) -> None:
|
||
|
|
c = _client()
|
||
|
|
resp = c.get("/v1/admin/backups")
|
||
|
|
assert resp.status_code == 200
|
||
|
|
assert "backups" in resp.json()
|
||
|
|
|
||
|
|
|
||
|
|
class TestVersionNegotiation:
|
||
|
|
"""Test API version negotiation."""
|
||
|
|
|
||
|
|
def test_version_endpoint(self) -> None:
|
||
|
|
c = _client()
|
||
|
|
resp = c.get("/version")
|
||
|
|
assert resp.status_code == 200
|
||
|
|
data = resp.json()
|
||
|
|
assert "current_version" in data
|
||
|
|
assert "supported_versions" in data
|
||
|
|
|
||
|
|
def test_version_header(self) -> None:
|
||
|
|
c = _client()
|
||
|
|
resp = c.get("/v1/admin/status")
|
||
|
|
assert "x-api-version" in resp.headers
|
||
|
|
|
||
|
|
def test_unsupported_version(self) -> None:
|
||
|
|
c = _client()
|
||
|
|
resp = c.get("/v1/admin/status", headers={"Accept-Version": "99"})
|
||
|
|
assert resp.status_code == 400
|
||
|
|
|
||
|
|
|
||
|
|
class TestSSEStreaming:
|
||
|
|
"""Test SSE streaming endpoint."""
|
||
|
|
|
||
|
|
def test_sse_endpoint_exists(self) -> None:
|
||
|
|
c = _client()
|
||
|
|
resp = c.post("/v1/sessions/test-session/stream/sse", json={"prompt": "Hi"})
|
||
|
|
assert resp.status_code == 200
|
||
|
|
assert resp.headers["content-type"].startswith("text/event-stream")
|
||
|
|
|
||
|
|
|
||
|
|
class TestOpenAICompat:
|
||
|
|
"""Test OpenAI-compatible endpoints."""
|
||
|
|
|
||
|
|
def test_models_list(self) -> None:
|
||
|
|
c = _client()
|
||
|
|
resp = c.get("/v1/models")
|
||
|
|
assert resp.status_code == 200
|
||
|
|
data = resp.json()
|
||
|
|
assert "data" in data
|