File size: 7,865 Bytes
67befa7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
#!/usr/bin/env python3
"""Test script for new features: health check, stats, rate limiting."""

import sys
import time
import httpx
from typing import Dict, Any


API_URL = "http://localhost:8080"


async def test_health_endpoint(client: httpx.AsyncClient) -> Dict[str, Any]:
    """Test health endpoint with model readiness check."""
    print("Testing /health endpoint...")
    try:
        response = await client.get(f"{API_URL}/health")
        assert response.status_code == 200, f"Expected 200, got {response.status_code}"
        data = response.json()
        
        # Check required fields
        assert "status" in data, "Missing 'status' field"
        assert "model_ready" in data, "Missing 'model_ready' field"
        assert "service" in data, "Missing 'service' field"
        
        print(f"  βœ“ Status: {data['status']}")
        print(f"  βœ“ Model ready: {data['model_ready']}")
        print(f"  βœ“ Service: {data['service']}")
        
        return {"success": True, "data": data}
    except Exception as e:
        print(f"  βœ— Failed: {e}")
        return {"success": False, "error": str(e)}


async def test_stats_endpoint(client: httpx.AsyncClient) -> Dict[str, Any]:
    """Test stats endpoint."""
    print("\nTesting /v1/stats endpoint...")
    try:
        response = await client.get(f"{API_URL}/v1/stats")
        assert response.status_code == 200, f"Expected 200, got {response.status_code}"
        data = response.json()
        
        # Check required fields
        required_fields = [
            "uptime_seconds", "total_requests", "total_tokens",
            "average_total_tokens", "requests_per_hour", "tokens_per_hour"
        ]
        for field in required_fields:
            assert field in data, f"Missing '{field}' field"
        
        print(f"  βœ“ Uptime: {data['uptime_seconds']}s ({data.get('uptime_hours', 0):.2f}h)")
        print(f"  βœ“ Total requests: {data['total_requests']}")
        print(f"  βœ“ Total tokens: {data['total_tokens']}")
        print(f"  βœ“ Average tokens: {data['average_total_tokens']:.2f}")
        print(f"  βœ“ Requests/hour: {data['requests_per_hour']:.2f}")
        print(f"  βœ“ Tokens/hour: {data['tokens_per_hour']:.2f}")
        
        if data.get('requests_by_model'):
            print(f"  βœ“ Models used: {list(data['requests_by_model'].keys())}")
        
        if data.get('finish_reasons'):
            print(f"  βœ“ Finish reasons: {data['finish_reasons']}")
        
        return {"success": True, "data": data}
    except Exception as e:
        print(f"  βœ— Failed: {e}")
        return {"success": False, "error": str(e)}


async def test_rate_limiting(client: httpx.AsyncClient) -> Dict[str, Any]:
    """Test rate limiting (should allow requests, check headers)."""
    print("\nTesting rate limiting...")
    try:
        # Make a request to check rate limit headers
        response = await client.get(f"{API_URL}/v1/models")
        assert response.status_code == 200, f"Expected 200, got {response.status_code}"
        
        # Check for rate limit headers
        headers = response.headers
        rate_limit_headers = [
            "X-RateLimit-Limit-Minute",
            "X-RateLimit-Limit-Hour",
            "X-RateLimit-Remaining-Minute",
            "X-RateLimit-Remaining-Hour"
        ]
        
        found_headers = []
        for header in rate_limit_headers:
            if header in headers:
                found_headers.append(header)
                print(f"  βœ“ {header}: {headers[header]}")
        
        if len(found_headers) == len(rate_limit_headers):
            print("  βœ“ All rate limit headers present")
            return {"success": True, "headers": {h: headers[h] for h in rate_limit_headers}}
        else:
            missing = set(rate_limit_headers) - set(found_headers)
            print(f"  ⚠ Missing headers: {missing}")
            return {"success": False, "error": f"Missing headers: {missing}"}
            
    except Exception as e:
        print(f"  βœ— Failed: {e}")
        return {"success": False, "error": str(e)}


async def test_error_sanitization(client: httpx.AsyncClient) -> Dict[str, Any]:
    """Test that error messages are sanitized."""
    print("\nTesting error sanitization...")
    try:
        # Make an invalid request
        response = await client.post(
            f"{API_URL}/v1/chat/completions",
            json={
                "model": "test",
                "messages": [],  # Empty messages should fail
            }
        )
        
        assert response.status_code == 400, f"Expected 400, got {response.status_code}"
        data = response.json()
        
        # Check error structure
        assert "error" in data, "Missing 'error' field"
        assert "message" in data["error"], "Missing 'message' in error"
        assert "type" in data["error"], "Missing 'type' in error"
        
        error_msg = data["error"]["message"]
        # Should not contain internal details like file paths, stack traces, etc.
        internal_indicators = ["Traceback", "File", "line", ".py", "Exception:"]
        for indicator in internal_indicators:
            assert indicator.lower() not in error_msg.lower(), f"Error message contains internal details: {indicator}"
        
        print(f"  βœ“ Error properly formatted: {error_msg[:100]}")
        print(f"  βœ“ Error type: {data['error']['type']}")
        
        return {"success": True, "error": data["error"]}
    except Exception as e:
        print(f"  βœ— Failed: {e}")
        return {"success": False, "error": str(e)}


async def test_root_endpoint(client: httpx.AsyncClient) -> Dict[str, Any]:
    """Test root endpoint."""
    print("\nTesting / endpoint...")
    try:
        response = await client.get(f"{API_URL}/")
        assert response.status_code == 200, f"Expected 200, got {response.status_code}"
        data = response.json()
        
        assert "status" in data, "Missing 'status' field"
        print(f"  βœ“ Status: {data['status']}")
        print(f"  βœ“ Service: {data.get('service', 'N/A')}")
        
        return {"success": True, "data": data}
    except Exception as e:
        print(f"  βœ— Failed: {e}")
        return {"success": False, "error": str(e)}


async def main():
    """Run all tests."""
    print("=" * 70)
    print("Testing New Features")
    print("=" * 70)
    print(f"API URL: {API_URL}")
    print()
    
    timeout = httpx.Timeout(30.0, connect=10.0)
    async with httpx.AsyncClient(timeout=timeout) as client:
        results = []
        
        # Test root endpoint
        results.append(await test_root_endpoint(client))
        
        # Test health endpoint
        results.append(await test_health_endpoint(client))
        
        # Test stats endpoint (before any requests)
        results.append(await test_stats_endpoint(client))
        
        # Test rate limiting
        results.append(await test_rate_limiting(client))
        
        # Test error sanitization
        results.append(await test_error_sanitization(client))
        
        # Test stats endpoint again (after requests)
        print("\nTesting /v1/stats endpoint (after requests)...")
        results.append(await test_stats_endpoint(client))
    
    # Summary
    print("\n" + "=" * 70)
    print("Summary")
    print("=" * 70)
    passed = sum(1 for r in results if r["success"])
    total = len(results)
    print(f"Passed: {passed}/{total}")
    
    if passed == total:
        print("βœ“ All tests passed!")
        return 0
    else:
        print("βœ— Some tests failed")
        for i, r in enumerate(results, 1):
            if not r["success"]:
                print(f"  Test {i}: {r.get('error', 'Unknown error')}")
        return 1


if __name__ == "__main__":
    import asyncio
    sys.exit(asyncio.run(main()))