#!/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()