import asyncio import json from datetime import datetime from typing import Any, Dict, List, Optional from uuid import UUID import httpx from loguru import logger # Configure logger logger.add( "tests/api_test_{time}.log", rotation="1 day", retention="7 days", level="DEBUG", format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}", ) class TestConfig: """Test configuration and utilities""" BASE_URL: str = "http://localhost:8000/v1" TEST_USERNAME: str = "test_user" api_key: Optional[str] = None user_id: Optional[UUID] = None test_agent_id: Optional[UUID] = None class TestResult: """Model for test results""" def __init__( self, test_name: str, status: str, duration: float, error: Optional[str] = None, details: Optional[Dict[str, Any]] = None, ): self.test_name = test_name self.status = status self.duration = duration self.error = error self.details = details or {} def dict(self): return { "test_name": self.test_name, "status": self.status, "duration": self.duration, "error": self.error, "details": self.details, } async def log_response( response: httpx.Response, test_name: str ) -> None: """Log API response details""" logger.debug(f"\n{test_name} Response:") logger.debug(f"Status Code: {response.status_code}") logger.debug(f"Headers: {dict(response.headers)}") try: logger.debug(f"Body: {response.json()}") except json.JSONDecodeError: logger.debug(f"Body: {response.text}") async def create_test_user() -> TestResult: """Create a test user and get API key""" start_time = datetime.now() try: async with httpx.AsyncClient() as client: response = await client.post( f"{TestConfig.BASE_URL}/users", json={"username": TestConfig.TEST_USERNAME}, ) await log_response(response, "Create User") if response.status_code == 200: data = response.json() TestConfig.api_key = data["api_key"] TestConfig.user_id = UUID(data["user_id"]) return TestResult( test_name="create_test_user", status="passed", duration=( datetime.now() - start_time ).total_seconds(), details={"user_id": str(TestConfig.user_id)}, ) else: return TestResult( test_name="create_test_user", status="failed", duration=( datetime.now() - start_time ).total_seconds(), error=f"Failed to create user: {response.text}", ) except Exception as e: logger.error(f"Error in create_test_user: {str(e)}") return TestResult( test_name="create_test_user", status="error", duration=(datetime.now() - start_time).total_seconds(), error=str(e), ) async def create_test_agent() -> TestResult: """Create a test agent""" start_time = datetime.now() try: # Create agent config according to the AgentConfig model agent_config = { "agent_name": "test_agent", "model_name": "gpt-4", "description": "Test agent for API testing", "system_prompt": "You are a test agent.", "temperature": 0.1, "max_loops": 1, "dynamic_temperature_enabled": True, "user_name": TestConfig.TEST_USERNAME, "retry_attempts": 1, "context_length": 4000, "output_type": "string", "streaming_on": False, "tags": ["test", "api"], "stopping_token": "", "auto_generate_prompt": False, } async with httpx.AsyncClient() as client: response = await client.post( f"{TestConfig.BASE_URL}/agent", json=agent_config, headers={"api-key": TestConfig.api_key}, ) await log_response(response, "Create Agent") if response.status_code == 200: data = response.json() TestConfig.test_agent_id = UUID(data["agent_id"]) return TestResult( test_name="create_test_agent", status="passed", duration=( datetime.now() - start_time ).total_seconds(), details={ "agent_id": str(TestConfig.test_agent_id) }, ) else: return TestResult( test_name="create_test_agent", status="failed", duration=( datetime.now() - start_time ).total_seconds(), error=f"Failed to create agent: {response.text}", ) except Exception as e: logger.error(f"Error in create_test_agent: {str(e)}") return TestResult( test_name="create_test_agent", status="error", duration=(datetime.now() - start_time).total_seconds(), error=str(e), ) async def test_agent_completion() -> TestResult: """Test agent completion endpoint""" start_time = datetime.now() try: completion_request = { "prompt": "Hello, this is a test prompt.", "agent_id": str(TestConfig.test_agent_id), "max_tokens": 100, "temperature_override": 0.5, "stream": False, } async with httpx.AsyncClient() as client: response = await client.post( f"{TestConfig.BASE_URL}/agent/completions", json=completion_request, headers={"api-key": TestConfig.api_key}, ) await log_response(response, "Agent Completion") if response.status_code == 200: return TestResult( test_name="test_agent_completion", status="passed", duration=( datetime.now() - start_time ).total_seconds(), details={"response": response.json()}, ) else: return TestResult( test_name="test_agent_completion", status="failed", duration=( datetime.now() - start_time ).total_seconds(), error=f"Failed completion test: {response.text}", ) except Exception as e: logger.error(f"Error in test_agent_completion: {str(e)}") return TestResult( test_name="test_agent_completion", status="error", duration=(datetime.now() - start_time).total_seconds(), error=str(e), ) async def test_agent_metrics() -> TestResult: """Test agent metrics endpoint""" start_time = datetime.now() try: if not TestConfig.test_agent_id: return TestResult( test_name="test_agent_metrics", status="failed", duration=( datetime.now() - start_time ).total_seconds(), error="No test agent ID available", ) async with httpx.AsyncClient() as client: response = await client.get( f"{TestConfig.BASE_URL}/agent/{str(TestConfig.test_agent_id)}/metrics", headers={"api-key": TestConfig.api_key}, ) await log_response(response, "Agent Metrics") if response.status_code == 200: return TestResult( test_name="test_agent_metrics", status="passed", duration=( datetime.now() - start_time ).total_seconds(), details={"metrics": response.json()}, ) else: return TestResult( test_name="test_agent_metrics", status="failed", duration=( datetime.now() - start_time ).total_seconds(), error=f"Failed metrics test: {response.text}", ) except Exception as e: logger.error(f"Error in test_agent_metrics: {str(e)}") return TestResult( test_name="test_agent_metrics", status="error", duration=(datetime.now() - start_time).total_seconds(), error=str(e), ) async def test_update_agent() -> TestResult: """Test agent update endpoint""" start_time = datetime.now() try: if not TestConfig.test_agent_id: return TestResult( test_name="test_update_agent", status="failed", duration=( datetime.now() - start_time ).total_seconds(), error="No test agent ID available", ) update_data = { "description": "Updated test agent description", "tags": ["test", "updated"], "max_loops": 2, } async with httpx.AsyncClient() as client: response = await client.patch( f"{TestConfig.BASE_URL}/agent/{str(TestConfig.test_agent_id)}", json=update_data, headers={"api-key": TestConfig.api_key}, ) await log_response(response, "Update Agent") if response.status_code == 200: return TestResult( test_name="test_update_agent", status="passed", duration=( datetime.now() - start_time ).total_seconds(), details={"update_response": response.json()}, ) else: return TestResult( test_name="test_update_agent", status="failed", duration=( datetime.now() - start_time ).total_seconds(), error=f"Failed update test: {response.text}", ) except Exception as e: logger.error(f"Error in test_update_agent: {str(e)}") return TestResult( test_name="test_update_agent", status="error", duration=(datetime.now() - start_time).total_seconds(), error=str(e), ) async def test_error_handling() -> TestResult: """Test API error handling""" start_time = datetime.now() try: async with httpx.AsyncClient() as client: # Test with invalid API key invalid_agent_id = "00000000-0000-0000-0000-000000000000" response = await client.get( f"{TestConfig.BASE_URL}/agent/{invalid_agent_id}/metrics", headers={"api-key": "invalid_key"}, ) await log_response(response, "Invalid API Key Test") if response.status_code in [401, 403]: return TestResult( test_name="test_error_handling", status="passed", duration=( datetime.now() - start_time ).total_seconds(), details={"error_response": response.json()}, ) else: return TestResult( test_name="test_error_handling", status="failed", duration=( datetime.now() - start_time ).total_seconds(), error="Error handling test failed", ) except Exception as e: logger.error(f"Error in test_error_handling: {str(e)}") return TestResult( test_name="test_error_handling", status="error", duration=(datetime.now() - start_time).total_seconds(), error=str(e), ) async def cleanup_test_resources() -> TestResult: """Clean up test resources""" start_time = datetime.now() try: if TestConfig.test_agent_id: async with httpx.AsyncClient() as client: response = await client.delete( f"{TestConfig.BASE_URL}/agent/{str(TestConfig.test_agent_id)}", headers={"api-key": TestConfig.api_key}, ) await log_response(response, "Delete Agent") return TestResult( test_name="cleanup_test_resources", status="passed", duration=(datetime.now() - start_time).total_seconds(), details={"cleanup": "completed"}, ) except Exception as e: logger.error(f"Error in cleanup_test_resources: {str(e)}") return TestResult( test_name="cleanup_test_resources", status="error", duration=(datetime.now() - start_time).total_seconds(), error=str(e), ) async def run_all_tests() -> List[TestResult]: """Run all tests in sequence""" logger.info("Starting API test suite") results = [] # Initialize results.append(await create_test_user()) if results[-1].status != "passed": logger.error( "Failed to create test user, aborting remaining tests" ) return results # Add delay to ensure user is properly created await asyncio.sleep(1) # Core tests test_functions = [ create_test_agent, test_agent_completion, test_agent_metrics, test_update_agent, test_error_handling, ] for test_func in test_functions: result = await test_func() results.append(result) logger.info(f"Test {result.test_name}: {result.status}") if result.error: logger.error( f"Error in {result.test_name}: {result.error}" ) # Add small delay between tests await asyncio.sleep(0.5) # Cleanup results.append(await cleanup_test_resources()) # Log summary passed = sum(1 for r in results if r.status == "passed") failed = sum(1 for r in results if r.status == "failed") errors = sum(1 for r in results if r.status == "error") logger.info("\nTest Summary:") logger.info(f"Total Tests: {len(results)}") logger.info(f"Passed: {passed}") logger.info(f"Failed: {failed}") logger.info(f"Errors: {errors}") return results def main(): """Main entry point for running tests""" logger.info("Starting API testing suite") try: results = asyncio.run(run_all_tests()) # Write results to JSON file with open("test_results.json", "w") as f: json.dump( [result.dict() for result in results], f, indent=2, default=str, ) logger.info("Test results written to test_results.json") except Exception: logger.error("Fatal error in test suite: ") main()