Files
HCFS/hcfs-python/test_api_v2.py
2025-07-30 09:34:16 +10:00

532 lines
20 KiB
Python

#!/usr/bin/env python3
"""
HCFS API v2 Test Client
Comprehensive test client for validating the production API functionality.
"""
import asyncio
import json
import time
from typing import List, Dict, Any
import httpx
import websocket
import threading
class HCFSAPIClient:
"""Test client for HCFS API v2."""
def __init__(self, base_url: str = "http://localhost:8000", api_key: str = "dev-key-123"):
self.base_url = base_url.rstrip('/')
self.api_key = api_key
self.headers = {
"X-API-Key": api_key,
"Content-Type": "application/json"
}
async def test_health_check(self) -> Dict[str, Any]:
"""Test health check endpoint."""
print("🔍 Testing health check...")
async with httpx.AsyncClient() as client:
response = await client.get(f"{self.base_url}/health")
print(f" Status: {response.status_code}")
if response.status_code == 200:
data = response.json()
print(f" System Status: {data['status']}")
print(f" Components: {len(data['components'])}")
return data
else:
print(f" Error: {response.text}")
return {}
async def test_context_crud(self) -> Dict[str, Any]:
"""Test context CRUD operations."""
print("\n📋 Testing Context CRUD operations...")
results = {"create": False, "read": False, "update": False, "delete": False}
async with httpx.AsyncClient() as client:
# Test Create
create_data = {
"path": "/test/api_test",
"content": "This is a test context for API validation",
"summary": "API test context",
"author": "test_client"
}
response = await client.post(
f"{self.base_url}/api/v1/contexts",
json=create_data,
headers=self.headers
)
if response.status_code == 200:
created_context = response.json()["data"]
context_id = created_context["id"]
print(f" ✅ Create: Context {context_id} created")
results["create"] = True
# Test Read
response = await client.get(
f"{self.base_url}/api/v1/contexts/{context_id}",
headers=self.headers
)
if response.status_code == 200:
read_context = response.json()["data"]
print(f" ✅ Read: Context {context_id} retrieved")
results["read"] = True
# Test Update
update_data = {
"content": "Updated test context content",
"summary": "Updated summary"
}
response = await client.put(
f"{self.base_url}/api/v1/contexts/{context_id}",
json=update_data,
headers=self.headers
)
if response.status_code == 200:
print(f" ✅ Update: Context {context_id} updated")
results["update"] = True
else:
print(f" ❌ Update failed: {response.status_code}")
# Test Delete
response = await client.delete(
f"{self.base_url}/api/v1/contexts/{context_id}",
headers=self.headers
)
if response.status_code == 200:
print(f" ✅ Delete: Context {context_id} deleted")
results["delete"] = True
else:
print(f" ❌ Delete failed: {response.status_code}")
else:
print(f" ❌ Read failed: {response.status_code}")
else:
print(f" ❌ Create failed: {response.status_code} - {response.text}")
return results
async def test_search_functionality(self) -> Dict[str, Any]:
"""Test search functionality."""
print("\n🔍 Testing Search functionality...")
results = {"semantic": False, "hybrid": False, "keyword": False}
# First, create some test contexts
test_contexts = [
{
"path": "/ml/algorithms",
"content": "Machine learning algorithms and neural networks for data analysis",
"summary": "ML algorithms overview",
"author": "test_client"
},
{
"path": "/web/development",
"content": "Web development using FastAPI and modern frameworks",
"summary": "Web dev guide",
"author": "test_client"
},
{
"path": "/database/systems",
"content": "Database management systems and SQL optimization techniques",
"summary": "Database guide",
"author": "test_client"
}
]
context_ids = []
async with httpx.AsyncClient() as client:
# Create test contexts
for context_data in test_contexts:
response = await client.post(
f"{self.base_url}/api/v1/contexts",
json=context_data,
headers=self.headers
)
if response.status_code == 200:
context_ids.append(response.json()["data"]["id"])
print(f" Created {len(context_ids)} test contexts")
# Wait a moment for embeddings to be generated
await asyncio.sleep(2)
# Test Semantic Search
search_data = {
"query": "machine learning neural networks",
"search_type": "semantic",
"top_k": 5
}
response = await client.post(
f"{self.base_url}/api/v1/search",
json=search_data,
headers=self.headers
)
if response.status_code == 200:
search_results = response.json()
print(f" ✅ Semantic Search: {search_results['total_results']} results in {search_results['search_time_ms']:.2f}ms")
results["semantic"] = True
else:
print(f" ❌ Semantic Search failed: {response.status_code}")
# Test Hybrid Search
search_data["search_type"] = "hybrid"
search_data["semantic_weight"] = 0.7
response = await client.post(
f"{self.base_url}/api/v1/search",
json=search_data,
headers=self.headers
)
if response.status_code == 200:
search_results = response.json()
print(f" ✅ Hybrid Search: {search_results['total_results']} results in {search_results['search_time_ms']:.2f}ms")
results["hybrid"] = True
else:
print(f" ❌ Hybrid Search failed: {response.status_code}")
# Test Keyword Search
search_data["search_type"] = "keyword"
response = await client.post(
f"{self.base_url}/api/v1/search",
json=search_data,
headers=self.headers
)
if response.status_code == 200:
search_results = response.json()
print(f" ✅ Keyword Search: {search_results['total_results']} results")
results["keyword"] = True
else:
print(f" ❌ Keyword Search failed: {response.status_code}")
# Cleanup test contexts
for context_id in context_ids:
await client.delete(
f"{self.base_url}/api/v1/contexts/{context_id}",
headers=self.headers
)
return results
async def test_batch_operations(self) -> Dict[str, Any]:
"""Test batch operations."""
print("\n📦 Testing Batch operations...")
batch_contexts = [
{
"path": f"/batch/test_{i}",
"content": f"Batch test context {i} with sample content",
"summary": f"Batch context {i}",
"author": "batch_client"
}
for i in range(5)
]
async with httpx.AsyncClient() as client:
response = await client.post(
f"{self.base_url}/api/v1/contexts/batch",
json={"contexts": batch_contexts},
headers=self.headers
)
if response.status_code == 200:
batch_result = response.json()["data"]
print(f" ✅ Batch Create: {batch_result['success_count']}/{batch_result['total_items']} succeeded")
# Cleanup
for context_id in batch_result["created_ids"]:
await client.delete(
f"{self.base_url}/api/v1/contexts/{context_id}",
headers=self.headers
)
return {"batch_create": True, "success_rate": batch_result['success_count'] / batch_result['total_items']}
else:
print(f" ❌ Batch Create failed: {response.status_code}")
return {"batch_create": False, "success_rate": 0.0}
async def test_pagination(self) -> Dict[str, Any]:
"""Test pagination functionality."""
print("\n📄 Testing Pagination...")
# Create multiple contexts for pagination testing
contexts = [
{
"path": f"/pagination/test_{i}",
"content": f"Pagination test context {i}",
"summary": f"Context {i}",
"author": "pagination_client"
}
for i in range(15)
]
context_ids = []
async with httpx.AsyncClient() as client:
# Create contexts
for context_data in contexts:
response = await client.post(
f"{self.base_url}/api/v1/contexts",
json=context_data,
headers=self.headers
)
if response.status_code == 200:
context_ids.append(response.json()["data"]["id"])
# Test pagination
response = await client.get(
f"{self.base_url}/api/v1/contexts?page=1&page_size=5&path_prefix=/pagination",
headers=self.headers
)
if response.status_code == 200:
page_data = response.json()
pagination_info = page_data["pagination"]
print(f" ✅ Page 1: {len(page_data['data'])} items")
print(f" Total: {pagination_info['total_items']}, Pages: {pagination_info['total_pages']}")
print(f" Has Next: {pagination_info['has_next']}, Has Previous: {pagination_info['has_previous']}")
# Cleanup
for context_id in context_ids:
await client.delete(
f"{self.base_url}/api/v1/contexts/{context_id}",
headers=self.headers
)
return {
"pagination_working": True,
"total_items": pagination_info['total_items'],
"items_per_page": len(page_data['data'])
}
else:
print(f" ❌ Pagination failed: {response.status_code}")
return {"pagination_working": False}
async def test_statistics_endpoint(self) -> Dict[str, Any]:
"""Test statistics endpoint."""
print("\n📊 Testing Statistics endpoint...")
async with httpx.AsyncClient() as client:
response = await client.get(
f"{self.base_url}/api/v1/stats",
headers=self.headers
)
if response.status_code == 200:
stats = response.json()
print(f" ✅ Statistics retrieved")
print(f" Total Contexts: {stats['context_stats']['total_contexts']}")
print(f" Active Connections: {stats['system_stats']['active_connections']}")
print(f" Cache Hit Rate: {stats['system_stats']['cache_hit_rate']:.2%}")
return {"stats_available": True, "data": stats}
else:
print(f" ❌ Statistics failed: {response.status_code}")
return {"stats_available": False}
def test_websocket_connection(self) -> Dict[str, Any]:
"""Test WebSocket connection."""
print("\n🔌 Testing WebSocket connection...")
try:
ws_url = self.base_url.replace("http", "ws") + "/ws"
def on_message(ws, message):
print(f" 📨 WebSocket message: {message}")
def on_error(ws, error):
print(f" ❌ WebSocket error: {error}")
def on_close(ws, close_status_code, close_msg):
print(f" 🔐 WebSocket closed")
def on_open(ws):
print(f" ✅ WebSocket connected")
# Send subscription request
subscription = {
"type": "subscribe",
"data": {
"path_prefix": "/test",
"event_types": ["created", "updated", "deleted"]
}
}
ws.send(json.dumps(subscription))
# Close after a moment
threading.Timer(2.0, ws.close).start()
ws = websocket.WebSocketApp(
ws_url,
on_open=on_open,
on_message=on_message,
on_error=on_error,
on_close=on_close
)
ws.run_forever(ping_interval=30, ping_timeout=10)
return {"websocket_working": True}
except Exception as e:
print(f" ❌ WebSocket test failed: {e}")
return {"websocket_working": False}
async def test_error_handling(self) -> Dict[str, Any]:
"""Test error handling."""
print("\n🚨 Testing Error handling...")
results = {}
async with httpx.AsyncClient() as client:
# Test 404 - Non-existent context
response = await client.get(
f"{self.base_url}/api/v1/contexts/999999",
headers=self.headers
)
if response.status_code == 404:
print(" ✅ 404 handling works")
results["404_handling"] = True
else:
print(f" ❌ Expected 404, got {response.status_code}")
results["404_handling"] = False
# Test 422 - Invalid data
invalid_data = {
"path": "", # Invalid empty path
"content": "", # Invalid empty content
}
response = await client.post(
f"{self.base_url}/api/v1/contexts",
json=invalid_data,
headers=self.headers
)
if response.status_code == 422:
print(" ✅ Validation error handling works")
results["validation_handling"] = True
else:
print(f" ❌ Expected 422, got {response.status_code}")
results["validation_handling"] = False
return results
async def run_comprehensive_test(self) -> Dict[str, Any]:
"""Run all tests comprehensively."""
print("🧪 HCFS API v2 Comprehensive Test Suite")
print("=" * 50)
start_time = time.time()
all_results = {}
# Run all tests
all_results["health"] = await self.test_health_check()
all_results["crud"] = await self.test_context_crud()
all_results["search"] = await self.test_search_functionality()
all_results["batch"] = await self.test_batch_operations()
all_results["pagination"] = await self.test_pagination()
all_results["statistics"] = await self.test_statistics_endpoint()
all_results["errors"] = await self.test_error_handling()
# WebSocket test (runs synchronously)
print("\n🔌 Testing WebSocket (this may take a moment)...")
all_results["websocket"] = self.test_websocket_connection()
total_time = time.time() - start_time
# Generate summary
print(f"\n📋 TEST SUMMARY")
print("=" * 30)
total_tests = 0
passed_tests = 0
for category, results in all_results.items():
if isinstance(results, dict):
category_tests = len([v for v in results.values() if isinstance(v, bool)])
category_passed = len([v for v in results.values() if v is True])
total_tests += category_tests
passed_tests += category_passed
if category_tests > 0:
success_rate = (category_passed / category_tests) * 100
print(f" {category.upper()}: {category_passed}/{category_tests} ({success_rate:.1f}%)")
overall_success_rate = (passed_tests / total_tests) * 100 if total_tests > 0 else 0
print(f"\n🎯 OVERALL RESULTS:")
print(f" Tests Passed: {passed_tests}/{total_tests}")
print(f" Success Rate: {overall_success_rate:.1f}%")
print(f" Total Time: {total_time:.2f}s")
if overall_success_rate >= 80:
print(f" Status: ✅ API IS PRODUCTION READY!")
elif overall_success_rate >= 60:
print(f" Status: ⚠️ API needs some improvements")
else:
print(f" Status: ❌ API has significant issues")
return {
"summary": {
"total_tests": total_tests,
"passed_tests": passed_tests,
"success_rate": overall_success_rate,
"total_time": total_time
},
"detailed_results": all_results
}
async def main():
"""Main function to run API tests."""
import argparse
parser = argparse.ArgumentParser(description="HCFS API v2 Test Client")
parser.add_argument("--url", default="http://localhost:8000", help="API base URL")
parser.add_argument("--api-key", default="dev-key-123", help="API key for authentication")
parser.add_argument("--test", choices=["all", "health", "crud", "search", "batch", "websocket"],
default="all", help="Specific test to run")
args = parser.parse_args()
client = HCFSAPIClient(base_url=args.url, api_key=args.api_key)
if args.test == "all":
await client.run_comprehensive_test()
elif args.test == "health":
await client.test_health_check()
elif args.test == "crud":
await client.test_context_crud()
elif args.test == "search":
await client.test_search_functionality()
elif args.test == "batch":
await client.test_batch_operations()
elif args.test == "websocket":
client.test_websocket_connection()
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
print("\n🛑 Test interrupted by user")
except Exception as e:
print(f"\n❌ Test failed with error: {e}")
import traceback
traceback.print_exc()