What a mess
This commit is contained in:
452
tests/test_api.py
Normal file
452
tests/test_api.py
Normal file
@@ -0,0 +1,452 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user