Files
echoes-of-the-ash/tests/test_api.py
2025-11-07 15:27:13 +01:00

453 lines
15 KiB
Python

#!/usr/bin/env python3
"""
Comprehensive API Testing Suite
Tests all API endpoints with realistic test data
"""
import asyncio
import httpx
import json
from typing import Dict, Any
# Configuration
API_URL = "http://localhost:8000"
API_INTERNAL_KEY = "bot-internal-key-9f8e7d6c5b4a3210fedcba9876543210"
class Colors:
GREEN = '\033[92m'
RED = '\033[91m'
YELLOW = '\033[93m'
BLUE = '\033[94m'
RESET = '\033[0m'
class APITester:
def __init__(self):
self.client = httpx.AsyncClient(timeout=30.0)
self.test_results = []
self.auth_token = None
self.test_user_id = None
self.test_telegram_id = 999999999
self.test_username = "test_user"
self.test_password = "Test123!@#"
async def log(self, message: str, color: str = Colors.RESET):
print(f"{color}{message}{Colors.RESET}")
async def test_endpoint(self, name: str, method: str, url: str,
expected_status: int = 200, **kwargs) -> Dict[str, Any]:
"""Test a single endpoint"""
try:
if method == "GET":
response = await self.client.get(url, **kwargs)
elif method == "POST":
response = await self.client.post(url, **kwargs)
elif method == "PATCH":
response = await self.client.patch(url, **kwargs)
elif method == "DELETE":
response = await self.client.delete(url, **kwargs)
else:
raise ValueError(f"Unsupported method: {method}")
success = response.status_code == expected_status
result = {
"name": name,
"success": success,
"status": response.status_code,
"expected": expected_status
}
if success:
await self.log(f"{name} - Status: {response.status_code}", Colors.GREEN)
try:
data = response.json()
if data and not isinstance(data, dict):
await self.log(f" Response: {str(data)[:100]}", Colors.BLUE)
elif data:
await self.log(f" Response: {json.dumps(data, indent=2)[:200]}...", Colors.BLUE)
except:
pass
else:
await self.log(f"{name} - Expected: {expected_status}, Got: {response.status_code}", Colors.RED)
await self.log(f" Response: {response.text[:200]}", Colors.RED)
self.test_results.append(result)
return response
except Exception as e:
await self.log(f"{name} - Exception: {str(e)}", Colors.RED)
self.test_results.append({
"name": name,
"success": False,
"error": str(e)
})
return None
async def setup_test_data(self):
"""Create test data in the database"""
await self.log("\n🔧 Setting up test data...", Colors.YELLOW)
# Create internal API headers
internal_headers = {"X-Internal-Key": API_INTERNAL_KEY}
# Create a Telegram user
await self.test_endpoint(
"Create Telegram Player",
"POST",
f"{API_URL}/api/internal/player",
params={"telegram_id": self.test_telegram_id, "name": "Test Telegram User"},
headers=internal_headers,
expected_status=200
)
# Get the player to get their ID
response = await self.test_endpoint(
"Get Telegram Player by telegram_id",
"GET",
f"{API_URL}/api/internal/player/{self.test_telegram_id}",
headers=internal_headers,
expected_status=200
)
if response and response.status_code == 200:
player_data = response.json()
self.test_user_id = player_data.get('id')
await self.log(f" Test user ID: {self.test_user_id}", Colors.BLUE)
# Add some items to inventory
await self.test_endpoint(
"Add item to inventory (knife)",
"POST",
f"{API_URL}/api/internal/player/{self.test_user_id}/inventory",
headers=internal_headers,
json={"item_id": "knife", "quantity": 1},
expected_status=200
)
await self.test_endpoint(
"Add item to inventory (water)",
"POST",
f"{API_URL}/api/internal/player/{self.test_user_id}/inventory",
headers=internal_headers,
json={"item_id": "water", "quantity": 3},
expected_status=200
)
# Create a dropped item
await self.test_endpoint(
"Create dropped item",
"POST",
f"{API_URL}/api/internal/dropped-items",
headers=internal_headers,
params={"item_id": "bandage", "quantity": 2, "location_id": "start_point"},
expected_status=200
)
# Create a wandering enemy
await self.test_endpoint(
"Spawn wandering enemy",
"POST",
f"{API_URL}/api/internal/wandering-enemies",
headers=internal_headers,
params={"npc_id": "mutant_rat", "location_id": "start_point", "current_hp": 30, "max_hp": 30},
expected_status=200
)
async def test_health_check(self):
await self.log("\n📋 Testing Health Check", Colors.YELLOW)
await self.test_endpoint(
"Health Check",
"GET",
f"{API_URL}/health"
)
async def test_auth_endpoints(self):
await self.log("\n🔐 Testing Authentication Endpoints", Colors.YELLOW)
# Register
response = await self.test_endpoint(
"Register Web User",
"POST",
f"{API_URL}/api/auth/register",
json={
"username": self.test_username,
"password": self.test_password,
"name": "Test User"
},
expected_status=200
)
# Login
response = await self.test_endpoint(
"Login Web User",
"POST",
f"{API_URL}/api/auth/login",
json={
"username": self.test_username,
"password": self.test_password
},
expected_status=200
)
if response and response.status_code == 200:
data = response.json()
self.auth_token = data.get('access_token')
await self.log(f" Auth token obtained", Colors.BLUE)
# Get current user
if self.auth_token:
await self.test_endpoint(
"Get Current User (Me)",
"GET",
f"{API_URL}/api/auth/me",
headers={"Authorization": f"Bearer {self.auth_token}"}
)
async def test_game_endpoints(self):
if not self.auth_token:
await self.log("\n⚠️ Skipping game endpoints (no auth token)", Colors.YELLOW)
return
await self.log("\n🎮 Testing Game Endpoints", Colors.YELLOW)
headers = {"Authorization": f"Bearer {self.auth_token}"}
# Game state
await self.test_endpoint(
"Get Game State",
"GET",
f"{API_URL}/api/game/state",
headers=headers
)
# Profile
await self.test_endpoint(
"Get Player Profile",
"GET",
f"{API_URL}/api/game/profile",
headers=headers
)
# Location
await self.test_endpoint(
"Get Current Location",
"GET",
f"{API_URL}/api/game/location",
headers=headers
)
# Inventory
await self.test_endpoint(
"Get Inventory",
"GET",
f"{API_URL}/api/game/inventory",
headers=headers
)
# Move (should fail - need stamina/valid direction)
await self.test_endpoint(
"Move (expect failure)",
"POST",
f"{API_URL}/api/game/move",
headers=headers,
json={"direction": "north"},
expected_status=400 # Expect failure
)
# Inspect
await self.test_endpoint(
"Inspect Area",
"POST",
f"{API_URL}/api/game/inspect",
headers=headers
)
async def test_internal_endpoints(self):
await self.log("\n🔧 Testing Internal Bot API Endpoints", Colors.YELLOW)
internal_headers = {"X-Internal-Key": API_INTERNAL_KEY}
if not self.test_user_id:
await self.log(" No test user ID available", Colors.RED)
return
# Player operations
await self.test_endpoint(
"Get Player by ID",
"GET",
f"{API_URL}/api/internal/player/by_id/{self.test_user_id}",
headers=internal_headers
)
await self.test_endpoint(
"Update Player",
"PATCH",
f"{API_URL}/api/internal/player/{self.test_user_id}",
headers=internal_headers,
json={"hp": 95}
)
# Inventory operations
await self.test_endpoint(
"Get Player Inventory",
"GET",
f"{API_URL}/api/internal/player/{self.test_user_id}/inventory",
headers=internal_headers
)
# Movement
await self.test_endpoint(
"Move Player",
"POST",
f"{API_URL}/api/internal/player/{self.test_user_id}/move",
headers=internal_headers,
json={"location_id": "abandoned_house"}
)
# Location queries
await self.test_endpoint(
"Get Dropped Items in Location",
"GET",
f"{API_URL}/api/internal/location/start_point/dropped-items",
headers=internal_headers
)
await self.test_endpoint(
"Get Wandering Enemies in Location",
"GET",
f"{API_URL}/api/internal/location/start_point/wandering-enemies",
headers=internal_headers
)
# Combat operations
await self.test_endpoint(
"Get Combat State",
"GET",
f"{API_URL}/api/internal/player/{self.test_user_id}/combat",
headers=internal_headers
)
# Create combat
combat_response = await self.test_endpoint(
"Create Combat",
"POST",
f"{API_URL}/api/internal/combat/create",
headers=internal_headers,
json={
"player_id": self.test_user_id,
"npc_id": "zombie",
"npc_hp": 50,
"npc_max_hp": 50,
"location_id": "abandoned_house",
"from_wandering": False
}
)
if combat_response and combat_response.status_code == 200:
# Update combat
await self.test_endpoint(
"Update Combat",
"PATCH",
f"{API_URL}/api/internal/combat/{self.test_user_id}",
headers=internal_headers,
json={"npc_hp": 40, "turn": "npc"}
)
# End combat
await self.test_endpoint(
"End Combat",
"DELETE",
f"{API_URL}/api/internal/combat/{self.test_user_id}",
headers=internal_headers
)
# Cooldown operations
await self.test_endpoint(
"Set Cooldown",
"POST",
f"{API_URL}/api/internal/cooldown/test_cooldown_key",
headers=internal_headers,
params={"duration_seconds": 300}
)
await self.test_endpoint(
"Get Cooldown",
"GET",
f"{API_URL}/api/internal/cooldown/test_cooldown_key",
headers=internal_headers
)
# Corpse operations
await self.test_endpoint(
"Create NPC Corpse",
"POST",
f"{API_URL}/api/internal/corpses/npc",
headers=internal_headers,
params={
"npc_id": "zombie",
"location_id": "abandoned_house",
"loot_remaining": json.dumps([{"item_id": "cloth", "quantity": 2}])
}
)
await self.test_endpoint(
"Get NPC Corpses in Location",
"GET",
f"{API_URL}/api/internal/location/abandoned_house/corpses/npc",
headers=internal_headers
)
# Status effects
await self.test_endpoint(
"Get Player Status Effects",
"GET",
f"{API_URL}/api/internal/player/{self.test_user_id}/status-effects",
headers=internal_headers
)
async def print_summary(self):
await self.log("\n" + "="*60, Colors.BLUE)
await self.log("📊 TEST SUMMARY", Colors.BLUE)
await self.log("="*60, Colors.BLUE)
total = len(self.test_results)
passed = sum(1 for r in self.test_results if r.get('success', False))
failed = total - passed
await self.log(f"\nTotal Tests: {total}", Colors.BLUE)
await self.log(f"Passed: {passed}", Colors.GREEN)
await self.log(f"Failed: {failed}", Colors.RED if failed > 0 else Colors.GREEN)
await self.log(f"Success Rate: {(passed/total*100):.1f}%", Colors.GREEN if failed == 0 else Colors.YELLOW)
if failed > 0:
await self.log("\n❌ Failed Tests:", Colors.RED)
for result in self.test_results:
if not result.get('success', False):
await self.log(f" - {result['name']}", Colors.RED)
if 'error' in result:
await self.log(f" Error: {result['error']}", Colors.RED)
elif 'status' in result:
await self.log(f" Expected: {result['expected']}, Got: {result['status']}", Colors.RED)
async def run_all_tests(self):
await self.log("🚀 Starting API Test Suite", Colors.BLUE)
await self.log("="*60, Colors.BLUE)
try:
await self.test_health_check()
await self.setup_test_data()
await self.test_auth_endpoints()
await self.test_game_endpoints()
await self.test_internal_endpoints()
await self.print_summary()
finally:
await self.client.aclose()
async def main():
tester = APITester()
await tester.run_all_tests()
if __name__ == "__main__":
asyncio.run(main())