Files
enclava/backend/tests/clients/nginx_test_client.py
2025-08-25 17:13:15 +02:00

309 lines
12 KiB
Python

"""
Nginx reverse proxy test client for routing verification.
"""
import aiohttp
import asyncio
from typing import Dict, List, Optional, Any, Union
import json
import time
class NginxTestClient:
"""Test client for nginx reverse proxy routing"""
def __init__(self, base_url: str = "http://localhost:3001"):
self.base_url = base_url.rstrip('/')
self.session_timeout = aiohttp.ClientTimeout(total=30)
async def test_route(self,
path: str,
method: str = "GET",
headers: Optional[Dict[str, str]] = None,
data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
"""Test a specific route through nginx"""
url = f"{self.base_url}{path}"
async with aiohttp.ClientSession(timeout=self.session_timeout) as session:
start_time = time.time()
try:
async with session.request(
method=method,
url=url,
headers=headers,
json=data if data else None
) as response:
end_time = time.time()
# Read response
try:
response_data = await response.json()
except:
response_data = await response.text()
return {
"url": url,
"method": method,
"status_code": response.status,
"headers": dict(response.headers),
"response_time": end_time - start_time,
"response_data": response_data,
"success": 200 <= response.status < 400
}
except asyncio.TimeoutError:
return {
"url": url,
"method": method,
"error": "timeout",
"response_time": time.time() - start_time,
"success": False
}
except Exception as e:
return {
"url": url,
"method": method,
"error": str(e),
"response_time": time.time() - start_time,
"success": False
}
async def test_public_api_routes(self) -> Dict[str, Any]:
"""Test public API routing (/api/v1/)"""
routes_to_test = [
{"path": "/api/v1/models", "method": "GET", "expected_auth": True},
{"path": "/api/v1/chat/completions", "method": "POST", "expected_auth": True},
{"path": "/api/v1/embeddings", "method": "POST", "expected_auth": True},
{"path": "/api/v1/health", "method": "GET", "expected_auth": False},
]
results = {}
for route in routes_to_test:
# Test without authentication
result_unauth = await self.test_route(route["path"], route["method"])
# Test with authentication
headers = {"Authorization": "Bearer test-api-key"}
result_auth = await self.test_route(route["path"], route["method"], headers)
results[route["path"]] = {
"unauthenticated": result_unauth,
"authenticated": result_auth,
"expects_auth": route["expected_auth"],
"auth_working": (
result_unauth["status_code"] == 401 and
result_auth["status_code"] != 401
) if route["expected_auth"] else True
}
return results
async def test_internal_api_routes(self) -> Dict[str, Any]:
"""Test internal API routing (/api-internal/v1/)"""
routes_to_test = [
{"path": "/api-internal/v1/auth/me", "method": "GET"},
{"path": "/api-internal/v1/auth/register", "method": "POST"},
{"path": "/api-internal/v1/chatbot/list", "method": "GET"},
{"path": "/api-internal/v1/rag/collections", "method": "GET"},
]
results = {}
for route in routes_to_test:
# Test without authentication
result_unauth = await self.test_route(route["path"], route["method"])
# Test with JWT token
headers = {"Authorization": "Bearer test-jwt-token"}
result_auth = await self.test_route(route["path"], route["method"], headers)
results[route["path"]] = {
"unauthenticated": result_unauth,
"authenticated": result_auth,
"requires_auth": result_unauth["status_code"] == 401,
"auth_working": result_unauth["status_code"] == 401 and result_auth["status_code"] != 401
}
return results
async def test_frontend_routes(self) -> Dict[str, Any]:
"""Test frontend routing"""
routes_to_test = [
"/",
"/dashboard",
"/chatbots",
"/rag",
"/settings",
"/login"
]
results = {}
for path in routes_to_test:
result = await self.test_route(path)
results[path] = {
"status_code": result["status_code"],
"response_time": result["response_time"],
"serves_html": "text/html" in result["headers"].get("content-type", ""),
"success": result["success"]
}
return results
async def test_cors_headers(self) -> Dict[str, Any]:
"""Test CORS headers configuration"""
cors_tests = {}
# Test preflight request
cors_headers = {
"Origin": "http://localhost:3000",
"Access-Control-Request-Method": "POST",
"Access-Control-Request-Headers": "Authorization, Content-Type"
}
preflight_result = await self.test_route("/api/v1/models", "OPTIONS", cors_headers)
cors_tests["preflight"] = {
"status_code": preflight_result["status_code"],
"cors_headers": {
k: v for k, v in preflight_result["headers"].items()
if k.lower().startswith("access-control")
}
}
# Test actual CORS request
request_headers = {"Origin": "http://localhost:3000"}
cors_result = await self.test_route("/api/v1/models", "GET", request_headers)
cors_tests["request"] = {
"status_code": cors_result["status_code"],
"cors_headers": {
k: v for k, v in cors_result["headers"].items()
if k.lower().startswith("access-control")
}
}
return cors_tests
async def test_websocket_support(self) -> Dict[str, Any]:
"""Test WebSocket upgrade support for Next.js HMR"""
ws_headers = {
"Upgrade": "websocket",
"Connection": "upgrade",
"Sec-WebSocket-Key": "dGhlIHNhbXBsZSBub25jZQ==",
"Sec-WebSocket-Version": "13"
}
result = await self.test_route("/", "GET", ws_headers)
return {
"status_code": result["status_code"],
"upgrade_attempted": result["status_code"] in [101, 426], # 101 = Switching Protocols, 426 = Upgrade Required
"connection_header": result["headers"].get("connection", "").lower(),
"upgrade_header": result["headers"].get("upgrade", "").lower()
}
async def test_health_endpoints(self) -> Dict[str, Any]:
"""Test health check endpoints"""
health_endpoints = [
"/health",
"/api/v1/health",
"/test-status"
]
results = {}
for endpoint in health_endpoints:
result = await self.test_route(endpoint)
results[endpoint] = {
"status_code": result["status_code"],
"response_time": result["response_time"],
"response_data": result["response_data"],
"healthy": result["status_code"] == 200
}
return results
async def test_static_file_handling(self) -> Dict[str, Any]:
"""Test static file serving and caching"""
static_files = [
"/_next/static/test.js",
"/favicon.ico",
"/static/test.css"
]
results = {}
for file_path in static_files:
result = await self.test_route(file_path)
results[file_path] = {
"status_code": result["status_code"],
"cache_control": result["headers"].get("cache-control"),
"expires": result["headers"].get("expires"),
"content_type": result["headers"].get("content-type"),
"cached": "cache-control" in result["headers"] or "expires" in result["headers"]
}
return results
async def test_error_handling(self) -> Dict[str, Any]:
"""Test nginx error handling"""
error_tests = [
{"path": "/nonexistent-page", "expected_status": 404},
{"path": "/api/v1/nonexistent-endpoint", "expected_status": 404},
{"path": "/api-internal/v1/nonexistent-endpoint", "expected_status": 404}
]
results = {}
for test in error_tests:
result = await self.test_route(test["path"])
results[test["path"]] = {
"actual_status": result["status_code"],
"expected_status": test["expected_status"],
"correct_error": result["status_code"] == test["expected_status"],
"response_data": result["response_data"]
}
return results
async def test_load_balancing(self, num_requests: int = 50) -> Dict[str, Any]:
"""Test load balancing behavior with multiple requests"""
async def make_request(request_id: int) -> Dict[str, Any]:
result = await self.test_route("/health")
return {
"request_id": request_id,
"status_code": result["status_code"],
"response_time": result["response_time"],
"success": result["success"]
}
tasks = [make_request(i) for i in range(num_requests)]
results = await asyncio.gather(*tasks)
success_count = sum(1 for r in results if r["success"])
avg_response_time = sum(r["response_time"] for r in results) / len(results)
return {
"total_requests": num_requests,
"successful_requests": success_count,
"failure_rate": (num_requests - success_count) / num_requests,
"average_response_time": avg_response_time,
"min_response_time": min(r["response_time"] for r in results),
"max_response_time": max(r["response_time"] for r in results),
"results": results
}
async def run_comprehensive_test(self) -> Dict[str, Any]:
"""Run all nginx tests"""
return {
"public_api_routes": await self.test_public_api_routes(),
"internal_api_routes": await self.test_internal_api_routes(),
"frontend_routes": await self.test_frontend_routes(),
"cors_headers": await self.test_cors_headers(),
"websocket_support": await self.test_websocket_support(),
"health_endpoints": await self.test_health_endpoints(),
"static_files": await self.test_static_file_handling(),
"error_handling": await self.test_error_handling(),
"load_test": await self.test_load_balancing(20) # Smaller load test
}