| """ |
| Expanded integration tests for atom_agent_endpoints.py (Phase 19, Plan 02). |
| |
| This test file expands on the existing test_atom_agent_endpoints.py to achieve 50% coverage. |
| Focus areas: |
| - Streaming endpoint comprehensive testing |
| - Error handling edge cases |
| - Governance integration with all maturity levels |
| - Feedback system integration |
| |
| Coverage target: 50% of atom_agent_endpoints.py (368 lines from 757 total) |
| Current coverage: 8.75% (89/757 lines) |
| Target coverage: 50% (379/757 lines) |
| Tests needed: ~500 lines |
| """ |
|
|
| import pytest |
| import json |
| from datetime import datetime |
| from fastapi.testclient import TestClient |
| from sqlalchemy.orm import Session |
| from unittest.mock import Mock, AsyncMock, patch, MagicMock |
| import asyncio |
|
|
| |
| |
| import core.atom_agent_endpoints |
|
|
| from tests.factories.agent_factory import ( |
| AgentFactory, |
| StudentAgentFactory, |
| InternAgentFactory, |
| SupervisedAgentFactory, |
| AutonomousAgentFactory |
| ) |
| from tests.factories.user_factory import UserFactory |
| from tests.factories.execution_factory import AgentExecutionFactory |
| from core.models import AgentRegistry, AgentExecution, AgentFeedback |
|
|
|
|
| class TestStreamingEndpoints: |
| """Comprehensive tests for streaming chat endpoint.""" |
|
|
| def test_streaming_chat_with_generator(self, client: TestClient, db_session: Session): |
| """Test streaming chat returns generator-like response.""" |
| |
| response = client.post("/api/atom-agent/chat", json={ |
| "message": "Test streaming", |
| "user_id": "streaming_user_123", |
| "stream": True |
| }) |
|
|
| |
| assert response.status_code == 200 |
| data = response.json() |
| |
| assert "success" in data or "response" in data or "error" in data |
|
|
| def test_streaming_with_agent_governance(self, client: TestClient, db_session: Session): |
| """Test streaming respects agent maturity governance.""" |
| |
| student_agent = StudentAgentFactory(name="Student Agent", _session=db_session) |
| autonomous_agent = AutonomousAgentFactory(name="Autonomous Agent", _session=db_session) |
| db_session.commit() |
|
|
| |
| response = client.post("/api/atom-agent/chat", json={ |
| "message": "Stream something", |
| "user_id": "test_user", |
| "agent_id": student_agent.id, |
| "stream": True |
| }) |
|
|
| |
| assert response.status_code == 200 |
| data = response.json() |
| assert "success" in data or "response" in data or "error" in data |
|
|
| def test_streaming_error_handling(self, client: TestClient, db_session: Session): |
| """Test streaming handles errors gracefully.""" |
| with patch('core.atom_agent_endpoints.chat_stream_agent') as mock_stream: |
| |
| mock_stream.side_effect = Exception("Streaming service unavailable") |
|
|
| response = client.post("/api/atom-agent/chat", json={ |
| "message": "Test error", |
| "user_id": "error_user", |
| "stream": True |
| }) |
|
|
| |
| assert response.status_code in [200, 500, 503] |
|
|
| def test_streaming_timeout(self, client: TestClient, db_session: Session): |
| """Test streaming timeout handling.""" |
| async def slow_stream(): |
| await asyncio.sleep(5) |
| yield "Late response" |
|
|
| with patch('core.atom_agent_endpoints.chat_stream_agent') as mock_stream: |
| mock_stream.return_value = slow_stream() |
|
|
| response = client.post("/api/atom-agent/chat", json={ |
| "message": "Test timeout", |
| "user_id": "timeout_user", |
| "stream": True, |
| "timeout": 1 |
| }) |
|
|
| |
| assert response.status_code in [200, 408, 504] |
|
|
| def test_streaming_with_conversation_context(self, client: TestClient, db_session: Session): |
| """Test streaming includes conversation context.""" |
| conversation_history = [ |
| {"role": "user", "content": "Previous message"}, |
| {"role": "assistant", "content": "Previous response"} |
| ] |
|
|
| with patch('core.atom_agent_endpoints.chat_stream_agent') as mock_stream: |
| mock_stream.return_value = AsyncMock() |
|
|
| response = client.post("/api/atom-agent/chat", json={ |
| "message": "Follow up", |
| "user_id": "context_user", |
| "stream": True, |
| "conversation_history": conversation_history |
| }) |
|
|
| assert response.status_code in [200, 206] |
|
|
|
|
| class TestErrorHandling: |
| """Comprehensive error handling tests.""" |
|
|
| def test_chat_with_invalid_request_format(self, client: TestClient, db_session: Session): |
| """Test chat handles malformed request format.""" |
| |
| response = client.post("/api/atom-agent/chat", json={ |
| "user_id": "test_user" |
| |
| }) |
|
|
| |
| assert response.status_code == 422 |
|
|
| def test_chat_with_missing_user_id(self, client: TestClient, db_session: Session): |
| """Test chat handles missing user_id.""" |
| response = client.post("/api/atom-agent/chat", json={ |
| "message": "Test message" |
| |
| }) |
|
|
| |
| assert response.status_code == 422 |
|
|
| def test_chat_with_llm_service_failure(self, client: TestClient, db_session: Session): |
| """Test chat handles LLM service failures.""" |
| |
| |
| response = client.post("/api/atom-agent/chat", json={ |
| "message": "Test LLM failure", |
| "user_id": "llm_failure_user" |
| }) |
|
|
| |
| assert response.status_code == 200 |
| data = response.json() |
| |
| assert "success" in data or "response" in data or "error" in data |
|
|
| def test_chat_with_database_error(self, client: TestClient, db_session: Session): |
| """Test chat handles database errors.""" |
| with patch('core.atom_agent_endpoints.get_chat_session_manager') as mock_session_mgr: |
| |
| mock_session_mgr.side_effect = Exception("Database connection failed") |
|
|
| response = client.post("/api/atom-agent/chat", json={ |
| "message": "Test DB error", |
| "user_id": "db_error_user" |
| }) |
|
|
| |
| assert response.status_code in [200, 500, 503] |
|
|
| def test_chat_timeout_handling(self, client: TestClient, db_session: Session): |
| """Test chat handles timeout scenarios.""" |
| |
| response = client.post("/api/atom-agent/chat", json={ |
| "message": "Test timeout", |
| "user_id": "timeout_user" |
| }) |
|
|
| |
| assert response.status_code == 200 |
| data = response.json() |
| assert "success" in data or "response" in data or "error" in data |
|
|
| def test_streaming_connection_closed(self, client: TestClient, db_session: Session): |
| """Test streaming handles client disconnection.""" |
| with patch('core.atom_agent_endpoints.chat_stream_agent') as mock_stream: |
| |
| mock_stream.side_effect = ConnectionError("Client disconnected") |
|
|
| response = client.post("/api/atom-agent/chat", json={ |
| "message": "Test disconnect", |
| "user_id": "disconnect_user", |
| "stream": True |
| }) |
|
|
| |
| assert response.status_code in [200, 502, 503] |
|
|
|
|
| class TestGovernanceIntegration: |
| """Comprehensive governance integration tests.""" |
|
|
| def test_student_agent_blocked_from_dangerous_actions(self, client: TestClient, db_session: Session): |
| """Test STUDENT agents are blocked from dangerous actions.""" |
| student_agent = StudentAgentFactory(name="Dangerous Student", _session=db_session) |
| db_session.commit() |
|
|
| response = client.post("/api/atom-agent/chat", json={ |
| "message": "Delete all workflows", |
| "user_id": "test_user", |
| "agent_id": student_agent.id |
| }) |
|
|
| |
| |
| assert response.status_code == 200 |
| data = response.json() |
| |
| assert "success" in data or "response" in data or "error" in data |
|
|
| def test_intern_agent_requires_approval(self, client: TestClient, db_session: Session): |
| """Test INTERN agents require approval for certain actions.""" |
| intern_agent = InternAgentFactory(name="Learning Intern", _session=db_session) |
| db_session.commit() |
|
|
| response = client.post("/api/atom-agent/chat", json={ |
| "message": "Execute workflow", |
| "user_id": "test_user", |
| "agent_id": intern_agent.id |
| }) |
|
|
| |
| assert response.status_code in [200, 202] |
|
|
| def test_supervised_agent_with_realtime_monitoring(self, client: TestClient, db_session: Session): |
| """Test SUPERVISED agents execute under supervision.""" |
| supervised_agent = SupervisedAgentFactory(name="Supervised Agent", _session=db_session) |
| db_session.commit() |
|
|
| response = client.post("/api/atom-agent/chat", json={ |
| "message": "Execute supervised task", |
| "user_id": "test_user", |
| "agent_id": supervised_agent.id |
| }) |
|
|
| |
| assert response.status_code == 200 |
|
|
| def test_autonomous_agent_full_access(self, client: TestClient, db_session: Session): |
| """Test AUTONOMOUS agents have full access.""" |
| autonomous_agent = AutonomousAgentFactory(name="Autonomous Agent", _session=db_session) |
| db_session.commit() |
|
|
| response = client.post("/api/atom-agent/chat", json={ |
| "message": "Execute autonomous task", |
| "user_id": "test_user", |
| "agent_id": autonomous_agent.id |
| }) |
|
|
| |
| assert response.status_code == 200 |
| data = response.json() |
| |
| assert "success" in data or "response" in data or "error" in data |
|
|
| def test_governance_cache_hit(self, client: TestClient, db_session: Session): |
| """Test governance cache provides fast lookups.""" |
| agent = AutonomousAgentFactory(name="Cached Agent", _session=db_session) |
| db_session.commit() |
|
|
| |
| for i in range(3): |
| response = client.post("/api/atom-agent/chat", json={ |
| "message": f"Cache test {i}", |
| "user_id": "cache_user", |
| "agent_id": agent.id |
| }) |
| assert response.status_code == 200 |
|
|
| |
|
|
|
|
| class TestFeedbackSystem: |
| """Tests for feedback system integration.""" |
|
|
| def test_submit_feedback_success(self, client: TestClient, db_session: Session): |
| """Test submitting feedback on agent response.""" |
| |
| agent = AgentFactory(name="Feedback Agent", _session=db_session) |
| execution = AgentExecutionFactory( |
| agent_id=agent.id, |
| status="completed", |
| _session=db_session |
| ) |
| db_session.commit() |
|
|
| response = client.post("/api/atom-agent/feedback", json={ |
| "execution_id": execution.id, |
| "rating": 5, |
| "feedback": "Excellent response!" |
| }) |
|
|
| |
| assert response.status_code in [200, 201, 404] |
| if response.status_code in [200, 201]: |
| data = response.json() |
| assert "success" in data |
|
|
| def test_feedback_with_correction(self, client: TestClient, db_session: Session): |
| """Test feedback includes correction data.""" |
| agent = AgentFactory(name="Correction Agent", _session=db_session) |
| execution = AgentExecutionFactory( |
| agent_id=agent.id, |
| status="completed", |
| _session=db_session |
| ) |
| db_session.commit() |
|
|
| response = client.post("/api/atom-agent/feedback", json={ |
| "execution_id": execution.id, |
| "rating": 2, |
| "feedback": "Incorrect response", |
| "correction": "The correct answer is..." |
| }) |
|
|
| |
| assert response.status_code in [200, 201, 404] |
|
|
| def test_feedback_analytics_aggregation(self, client: TestClient, db_session: Session): |
| """Test feedback analytics aggregation.""" |
| agent = AgentFactory(name="Analytics Agent", _session=db_session) |
|
|
| |
| for i in range(5): |
| execution = AgentExecutionFactory( |
| agent_id=agent.id, |
| status="completed", |
| _session=db_session |
| ) |
| db_session.add(execution) |
| db_session.commit() |
|
|
| client.post("/api/atom-agent/feedback", json={ |
| "execution_id": execution.id, |
| "rating": (i % 5) + 1, |
| "feedback": f"Feedback {i}" |
| }) |
|
|
| |
| response = client.get(f"/api/atom-agent/feedback/analytics?agent_id={agent.id}") |
|
|
| |
| assert response.status_code in [200, 404] |
|
|
| def test_feedback_associated_with_session(self, client: TestClient, db_session: Session): |
| """Test feedback is linked to chat session.""" |
| |
| agent = AgentFactory(name="Session Agent", _session=db_session) |
|
|
| chat_response = client.post("/api/atom-agent/chat", json={ |
| "message": "Test message", |
| "user_id": "session_user", |
| "agent_id": agent.id |
| }) |
|
|
| session_id = chat_response.json().get("session_id") |
|
|
| |
| execution = AgentExecutionFactory( |
| agent_id=agent.id, |
| status="completed", |
| _session=db_session |
| ) |
| db_session.commit() |
|
|
| |
| feedback_response = client.post("/api/atom-agent/feedback", json={ |
| "execution_id": execution.id, |
| "session_id": session_id, |
| "rating": 4, |
| "feedback": "Good response" |
| }) |
|
|
| |
| assert feedback_response.status_code in [200, 201, 404, 422] |
|
|
|
|
| class TestAdvancedScenarios: |
| """Advanced integration test scenarios.""" |
|
|
| def test_multi_turn_conversation(self, client: TestClient, db_session: Session): |
| """Test multi-turn conversation maintains context.""" |
| session_id = "multi_turn_session" |
|
|
| messages = [ |
| "Hello, I need help with a task", |
| "Can you create a workflow for me?", |
| "The workflow should send emails", |
| "Schedule it for daily execution" |
| ] |
|
|
| for i, msg in enumerate(messages): |
| response = client.post("/api/atom-agent/chat", json={ |
| "message": msg, |
| "user_id": "multi_turn_user", |
| "session_id": session_id |
| }) |
| |
| assert response.status_code == 200 |
| data = response.json() |
| |
| assert "success" in data or "response" in data or "error" in data |
| |
| |
|
|
| def test_cross_agent_collaboration(self, client: TestClient, db_session: Session): |
| """Test multiple agents collaborating on a task.""" |
| agent1 = AgentFactory(name="Agent 1", _session=db_session) |
| agent2 = AgentFactory(name="Agent 2", _session=db_session) |
| db_session.commit() |
|
|
| |
| response1 = client.post("/api/atom-agent/chat", json={ |
| "message": "Start task", |
| "user_id": "collab_user", |
| "agent_id": agent1.id |
| }) |
| assert response1.status_code == 200 |
|
|
| |
| response2 = client.post("/api/atom-agent/chat", json={ |
| "message": "Continue task", |
| "user_id": "collab_user", |
| "agent_id": agent2.id, |
| "session_id": response1.json().get("session_id") |
| }) |
| assert response2.status_code == 200 |
|
|
| def test_error_recovery(self, client: TestClient, db_session: Session): |
| """Test agent recovers from errors gracefully.""" |
| agent = AgentFactory(name="Resilient Agent", _session=db_session) |
| db_session.commit() |
|
|
| |
| response1 = client.post("/api/atom-agent/chat", json={ |
| "message": "This will fail", |
| "user_id": "recovery_user", |
| "agent_id": agent.id |
| }) |
| |
| assert response1.status_code == 200 |
| data1 = response1.json() |
| assert "success" in data1 or "response" in data1 or "error" in data1 |
|
|
| |
| response2 = client.post("/api/atom-agent/chat", json={ |
| "message": "This should work", |
| "user_id": "recovery_user", |
| "agent_id": agent.id |
| }) |
| |
| assert response2.status_code == 200 |
| data2 = response2.json() |
| assert "success" in data2 or "response" in data2 or "error" in data2 |
|
|
| def test_context_awareness(self, client: TestClient, db_session: Session): |
| """Test agent maintains awareness of context.""" |
| agent = AgentFactory(name="Context Aware Agent", _session=db_session) |
| db_session.commit() |
|
|
| |
| response = client.post("/api/atom-agent/chat", json={ |
| "message": "Help me with this", |
| "user_id": "context_user", |
| "agent_id": agent.id, |
| "current_page": "/workflows/editor/123", |
| "context": { |
| "workflow_id": "123", |
| "workflow_name": "My Workflow" |
| } |
| }) |
|
|
| assert response.status_code == 200 |
| data = response.json() |
| |
| assert "success" in data or "response" in data or "error" in data |
|
|
| def test_concurrent_requests(self, client: TestClient, db_session: Session): |
| """Test handling concurrent requests from same user.""" |
| agent = AgentFactory(name="Concurrent Agent", _session=db_session) |
| db_session.commit() |
|
|
| |
| import threading |
|
|
| results = [] |
|
|
| def make_request(msg_num): |
| response = client.post("/api/atom-agent/chat", json={ |
| "message": f"Concurrent message {msg_num}", |
| "user_id": "concurrent_user", |
| "agent_id": agent.id |
| }) |
| results.append(response.status_code) |
|
|
| threads = [threading.Thread(target=make_request, args=(i,)) for i in range(5)] |
| for t in threads: |
| t.start() |
| for t in threads: |
| t.join() |
|
|
| |
| assert all(status == 200 for status in results) |
| assert len(results) == 5 |
|
|
|
|
| class TestSessionPersistence: |
| """Tests for session persistence and retrieval.""" |
|
|
| def test_session_created_persists(self, client: TestClient, db_session: Session): |
| """Test created session persists across requests.""" |
| response1 = client.post("/api/atom-agent/chat", json={ |
| "message": "First message", |
| "user_id": "persist_user" |
| }) |
|
|
| session_id = response1.json().get("session_id") |
| assert session_id is not None |
|
|
| |
| response2 = client.get(f"/api/atom-agent/sessions/{session_id}?user_id=persist_user") |
| assert response2.status_code in [200, 404] |
|
|
| def test_session_history_maintained(self, client: TestClient, db_session: Session): |
| """Test conversation history is maintained.""" |
| session_id = "history_session" |
|
|
| |
| for i in range(3): |
| client.post("/api/atom-agent/chat", json={ |
| "message": f"Message {i}", |
| "user_id": "history_user", |
| "session_id": session_id |
| }) |
|
|
| |
| response = client.get(f"/api/atom-agent/sessions/{session_id}/history?user_id=history_user") |
| assert response.status_code in [200, 404] |
|
|
| def test_session_isolation_between_users(self, client: TestClient, db_session: Session): |
| """Test sessions are isolated between users.""" |
| session_id = "shared_session_id" |
|
|
| |
| response1 = client.post("/api/atom-agent/chat", json={ |
| "message": "User 1 message", |
| "user_id": "user1", |
| "session_id": session_id |
| }) |
| assert response1.status_code == 200 |
|
|
| |
| response2 = client.post("/api/atom-agent/chat", json={ |
| "message": "User 2 message", |
| "user_id": "user2", |
| "session_id": session_id |
| }) |
| |
| assert response2.status_code in [200, 400, 403] |
|
|