You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
swarms/api/api_tests.py

473 lines
15 KiB

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": "<DONE>",
"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()