| """ |
| 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, 405, 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_no_auth: TestClient, db_session: Session): |
| """Test updating agent requires authentication.""" |
| agent = AgentFactory(name="Update Test Agent") |
| db_session.commit() |
|
|
| response = client_no_auth.put(f"/api/agents/{agent.id}", json={ |
| "name": "Updated Agent" |
| }) |
| assert response.status_code in [401, 403, 405, 422] |
|
|
| def test_delete_agent_requires_auth(self, client_no_auth: TestClient, db_session: Session): |
| """Test deleting agent requires authentication.""" |
| agent = AgentFactory(name="Delete Test Agent") |
| db_session.commit() |
|
|
| response = client_no_auth.delete(f"/api/agents/{agent.id}") |
| assert response.status_code in [401, 403, 405, 422] |
|
|
|
|
| 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_no_auth.get("/api/episodes") |
| |
| assert response.status_code in [200, 401, 404] |
|
|
| def test_create_episode_requires_auth(self, client_no_auth: TestClient, db_session: Session): |
| """Test creating episode requires authentication.""" |
| response = client_no_auth.post("/api/episodes/create", json={ |
| "session_id": "test-session", |
| "agent_id": "test-agent" |
| }) |
| assert response.status_code in [401, 403, 405, 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_no_auth.get("/api/users/me") |
| |
| assert response.status_code in [401, 403, 404, 405, 422] |
|
|
| 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, 422] |
|
|
| 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_no_auth.get( |
| "/api/canvas/status", |
| headers={"Authorization": "InvalidFormat token"} |
| ) |
| assert response.status_code in [401, 403, 422] |
|
|