| """
|
| API integration tests for FastAPI endpoints (INTG-01).
|
|
|
| Tests cover:
|
| - Agent endpoints (list, create, update, delete)
|
| - Canvas endpoints (present, submit, close)
|
| - Episode endpoints (create, retrieve, search)
|
| - User endpoints (profile, settings)
|
| - Request/response validation
|
| - Error handling (401, 403, 404, 422)
|
| """
|
|
|
| import pytest
|
| from fastapi.testclient import TestClient
|
| from sqlalchemy.orm import Session
|
|
|
| from tests.factories.agent_factory import (
|
| AgentFactory,
|
| StudentAgentFactory,
|
| InternAgentFactory,
|
| SupervisedAgentFactory,
|
| AutonomousAgentFactory
|
| )
|
| from tests.factories.user_factory import UserFactory, AdminUserFactory
|
| from tests.factories.execution_factory import AgentExecutionFactory
|
| from tests.factories.episode_factory import EpisodeFactory
|
| from core.models import AgentRegistry, AgentStatus, Episode
|
|
|
|
|
| class TestAgentEndpoints:
|
| """Integration tests for agent API endpoints."""
|
|
|
| def test_list_agents_returns_empty_list(self, client: TestClient):
|
| """Test listing agents when none exist."""
|
| response = client.get("/api/agents")
|
| assert response.status_code == 200
|
| data = response.json()
|
|
|
| if isinstance(data, dict):
|
| assert "agents" in data or "data" in data or data.get("success") is True
|
| else:
|
| assert isinstance(data, list)
|
|
|
| def test_list_agents_filters_by_maturity(self, client: TestClient, db_session: Session):
|
| """Test listing agents can filter by maturity level."""
|
|
|
| student = StudentAgentFactory(name="Student Agent")
|
| intern = InternAgentFactory(name="Intern Agent")
|
| db_session.commit()
|
|
|
| response = client.get("/api/agents")
|
| assert response.status_code == 200
|
| data = response.json()
|
|
|
|
|
| def test_create_agent_requires_authentication(self, client_no_auth: TestClient):
|
| """Test creating agent requires valid JWT token."""
|
| response = client_no_auth.post("/api/agents", json={
|
| "name": "Test Agent",
|
| "category": "testing"
|
| })
|
|
|
| assert response.status_code in [401, 403, 422]
|
|
|
| def test_create_agent_with_valid_token(self, client: TestClient, admin_token: str, db_session: Session):
|
| """Test creating agent with valid authentication."""
|
| response = client.post(
|
| "/api/agents",
|
| json={
|
| "name": "Test Agent",
|
| "category": "testing",
|
| "module_path": "test.module",
|
| "class_name": "TestClass"
|
| },
|
| headers={"Authorization": f"Bearer {admin_token}"}
|
| )
|
|
|
|
|
| assert response.status_code in [200, 201, 404, 405, 422]
|
|
|
| def test_get_agent_by_id(self, client: TestClient, db_session: Session):
|
| """Test retrieving a specific agent by ID."""
|
| agent = AgentFactory(name="Retrieval Test Agent")
|
| db_session.commit()
|
|
|
| response = client.get(f"/api/agents/{agent.id}")
|
|
|
| assert response.status_code in [200, 404, 405]
|
|
|
| def test_update_agent_requires_auth(self, client: TestClient, db_session: Session):
|
| """Test updating agent requires authentication."""
|
| agent = AgentFactory(name="Update Test Agent")
|
| db_session.commit()
|
|
|
| response = client.put(f"/api/agents/{agent.id}", json={
|
| "name": "Updated Agent"
|
| })
|
| assert response.status_code in [401, 403, 404, 405]
|
|
|
| def test_delete_agent_requires_auth(self, client: TestClient, db_session: Session):
|
| """Test deleting agent requires authentication."""
|
| agent = AgentFactory(name="Delete Test Agent")
|
| db_session.commit()
|
|
|
| response = client.delete(f"/api/agents/{agent.id}")
|
| assert response.status_code in [401, 403, 404, 405]
|
|
|
|
|
| class TestCanvasEndpoints:
|
| """Integration tests for canvas API endpoints."""
|
|
|
| def test_canvas_submit_requires_authentication(self, client_no_auth: TestClient):
|
| """Test canvas form submission requires authentication."""
|
| response = client_no_auth.post("/api/canvas/submit", json={
|
| "canvas_id": "test-canvas",
|
| "form_data": {"field1": "value1"}
|
| })
|
| assert response.status_code == 401
|
|
|
| def test_canvas_submit_with_valid_data(self, client: TestClient, auth_token: str):
|
| """Test canvas form submission with valid data."""
|
| response = client.post(
|
| "/api/canvas/submit",
|
| json={
|
| "canvas_id": "test-canvas",
|
| "form_data": {"field1": "value1"}
|
| },
|
| headers={"Authorization": f"Bearer {auth_token}"}
|
| )
|
|
|
| assert response.status_code in [200, 201, 400, 422]
|
|
|
| def test_canvas_status_endpoint(self, client: TestClient, auth_token: str):
|
| """Test canvas status endpoint returns proper response."""
|
| response = client.get(
|
| "/api/canvas/status",
|
| headers={"Authorization": f"Bearer {auth_token}"}
|
| )
|
| assert response.status_code == 200
|
| data = response.json()
|
|
|
| if isinstance(data, dict):
|
| assert "status" in data or "data" in data or data.get("success") is True
|
|
|
| def test_canvas_submit_with_agent_context(self, client: TestClient, auth_token: str, db_session: Session):
|
| """Test canvas submission with agent execution context."""
|
| agent = AgentFactory(name="Canvas Agent")
|
| db_session.commit()
|
|
|
| response = client.post(
|
| "/api/canvas/submit",
|
| json={
|
| "canvas_id": "test-canvas",
|
| "form_data": {"field1": "value1"},
|
| "agent_id": agent.id
|
| },
|
| headers={"Authorization": f"Bearer {auth_token}"}
|
| )
|
| assert response.status_code in [200, 201, 400, 403, 422]
|
|
|
| def test_canvas_submit_invalid_data(self, client: TestClient, auth_token: str):
|
| """Test canvas submission with invalid data returns validation error."""
|
| response = client.post(
|
| "/api/canvas/submit",
|
| json={
|
|
|
| "form_data": {"field1": "value1"}
|
| },
|
| headers={"Authorization": f"Bearer {auth_token}"}
|
| )
|
| assert response.status_code == 422
|
|
|
|
|
| class TestEpisodeEndpoints:
|
| """Integration tests for episode API endpoints."""
|
|
|
| def test_list_episodes_requires_authentication(self, client_no_auth: TestClient):
|
| """Test listing episodes requires authentication."""
|
| response = client.get("/api/episodes")
|
|
|
| assert response.status_code in [200, 401, 404]
|
|
|
| def test_create_episode_requires_auth(self, client: TestClient, db_session: Session):
|
| """Test creating episode requires authentication."""
|
| response = client.post("/api/episodes/create", json={
|
| "session_id": "test-session",
|
| "agent_id": "test-agent"
|
| })
|
| assert response.status_code in [401, 422]
|
|
|
| def test_create_episode_with_valid_data(self, client: TestClient, auth_token: str, db_session: Session):
|
| """Test creating episode with valid data."""
|
| agent = AgentFactory(name="Episode Agent")
|
| db_session.commit()
|
|
|
| response = client.post(
|
| "/api/episodes/create",
|
| json={
|
| "session_id": "test-session-123",
|
| "agent_id": agent.id,
|
| "title": "Test Episode"
|
| },
|
| headers={"Authorization": f"Bearer {auth_token}"}
|
| )
|
|
|
| assert response.status_code in [200, 201, 400, 404, 422]
|
|
|
| def test_list_episodes_for_agent(self, client: TestClient, db_session: Session):
|
| """Test episode list respects agent filtering."""
|
| agent = AgentFactory(name="Episode List Agent")
|
| episode = EpisodeFactory(agent_id=agent.id, title="Test Episode")
|
| db_session.commit()
|
|
|
| response = client.get(f"/api/episodes/{agent.id}/list")
|
| assert response.status_code == 200
|
| data = response.json()
|
|
|
|
|
| def test_retrieve_temporal_episodes(self, client: TestClient, db_session: Session):
|
| """Test temporal retrieval of episodes."""
|
| agent = AgentFactory(name="Temporal Agent")
|
| db_session.commit()
|
|
|
| response = client.post("/api/episodes/retrieve/temporal", json={
|
| "agent_id": agent.id,
|
| "time_range": "7d",
|
| "limit": 10
|
| })
|
| assert response.status_code in [200, 404, 422]
|
|
|
| def test_episode_feedback_submission(self, client: TestClient, auth_token: str, db_session: Session):
|
| """Test submitting feedback for an episode."""
|
| agent = AgentFactory(name="Feedback Agent")
|
| episode = EpisodeFactory(agent_id=agent.id)
|
| db_session.commit()
|
|
|
| response = client.post(
|
| f"/api/episodes/{episode.id}/feedback/submit",
|
| json={
|
| "feedback_type": "thumbs_up",
|
| "rating": 5,
|
| "corrections": "Great work!"
|
| },
|
| headers={"Authorization": f"Bearer {auth_token}"}
|
| )
|
| assert response.status_code in [200, 201, 404]
|
|
|
| def test_get_episode_stats(self, client: TestClient, db_session: Session):
|
| """Test retrieving episode statistics for an agent."""
|
| agent = AgentFactory(name="Stats Agent")
|
| db_session.commit()
|
|
|
| response = client.get(f"/api/episodes/stats/{agent.id}")
|
| assert response.status_code == 200
|
| data = response.json()
|
|
|
|
|
|
|
| class TestUserEndpoints:
|
| """Integration tests for user API endpoints."""
|
|
|
| def test_get_current_user_requires_auth(self, client_no_auth: TestClient):
|
| """Test getting current user requires authentication."""
|
| response = client.get("/api/users/me")
|
|
|
| assert response.status_code in [401, 404, 405]
|
|
|
| def test_get_current_user_with_token(self, client: TestClient, auth_token: str):
|
| """Test getting current user with valid token."""
|
| response = client.get(
|
| "/api/users/me",
|
| headers={"Authorization": f"Bearer {auth_token}"}
|
| )
|
|
|
| assert response.status_code in [200, 404, 405]
|
|
|
| def test_update_user_profile(self, client: TestClient, auth_token: str):
|
| """Test updating user profile."""
|
| response = client.put(
|
| "/api/users/me",
|
| json={
|
| "first_name": "Updated",
|
| "last_name": "Name"
|
| },
|
| headers={"Authorization": f"Bearer {auth_token}"}
|
| )
|
|
|
| assert response.status_code in [200, 404, 405]
|
|
|
| def test_list_users_requires_admin(self, client: TestClient, auth_token: str):
|
| """Test listing users requires admin privileges."""
|
| response = client.get(
|
| "/api/users",
|
| headers={"Authorization": f"Bearer {auth_token}"}
|
| )
|
|
|
| assert response.status_code in [401, 403, 404, 405]
|
|
|
| def test_list_users_with_admin_token(self, client: TestClient, admin_token: str):
|
| """Test listing users with admin token."""
|
| response = client.get(
|
| "/api/users",
|
| headers={"Authorization": f"Bearer {admin_token}"}
|
| )
|
|
|
| assert response.status_code in [200, 404, 405]
|
|
|
|
|
| class TestHealthEndpoints:
|
| """Integration tests for health check endpoints."""
|
|
|
| def test_root_endpoint(self, client: TestClient):
|
| """Test root endpoint returns API info."""
|
| response = client.get("/")
|
| assert response.status_code == 200
|
| data = response.json()
|
| assert isinstance(data, dict)
|
|
|
| if "name" in data or "status" in data or "version" in data:
|
| assert True
|
|
|
| def test_health_check(self, client: TestClient):
|
| """Test health check endpoint."""
|
| response = client.get("/health")
|
| assert response.status_code == 200
|
| data = response.json()
|
| assert isinstance(data, dict)
|
|
|
| if "status" in data:
|
| assert data["status"] in ["healthy", "ok", "running", "healthy_check_reload"]
|
|
|
|
|
| class TestErrorHandling:
|
| """Integration tests for API error handling."""
|
|
|
| def test_404_for_invalid_endpoint(self, client: TestClient):
|
| """Test 404 response for non-existent endpoint."""
|
| response = client.get("/api/this-endpoint-does-not-exist")
|
| assert response.status_code == 404
|
|
|
| def test_422_for_invalid_json(self, client: TestClient):
|
| """Test 422 response for malformed JSON in POST body."""
|
| response = client.post(
|
| "/api/episodes/create",
|
| data="invalid json{",
|
| headers={"Content-Type": "application/json"}
|
| )
|
| assert response.status_code == 422
|
|
|
| def test_405_for_method_not_allowed(self, client: TestClient):
|
| """Test 405 response for unsupported HTTP method."""
|
| response = client.patch("/api/agents")
|
| assert response.status_code in [405, 404]
|
|
|
| def test_missing_required_fields(self, client: TestClient, auth_token: str):
|
| """Test validation error for missing required fields."""
|
| response = client.post(
|
| "/api/episodes/create",
|
| json={
|
|
|
| "session_id": "test"
|
| },
|
| headers={"Authorization": f"Bearer {auth_token}"}
|
| )
|
| assert response.status_code in [400, 422]
|
|
|
| def test_invalid_token_format(self, client_no_auth: TestClient):
|
| """Test authentication fails with invalid token format."""
|
| response = client.get(
|
| "/api/canvas/status",
|
| headers={"Authorization": "InvalidFormat token"}
|
| )
|
| assert response.status_code in [401, 403, 422]
|
|
|