File size: 11,531 Bytes
824bf31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
"""
Module: tests.integration.test_basic_api
Description: Basic integration tests for core API endpoints
Author: Anderson H. Silva
Date: 2025-01-24
License: Proprietary - All rights reserved
"""

import pytest
from fastapi.testclient import TestClient
from httpx import AsyncClient
from unittest.mock import patch

# Import just the FastAPI app without triggering full agent imports
import sys
from unittest.mock import MagicMock

# Mock heavy dependencies before importing
sys.modules['numpy'] = MagicMock()
sys.modules['scikit-learn'] = MagicMock()
sys.modules['torch'] = MagicMock()
sys.modules['transformers'] = MagicMock()

from src.api.app import app


# Test client for synchronous tests
client = TestClient(app)


@pytest.fixture
async def async_client():
    """Async test client for async endpoints."""
    async with AsyncClient(app=app, base_url="http://test") as ac:
        yield ac


class TestBasicAPIFunctionality:
    """Test basic API functionality without heavy dependencies."""
    
    def test_root_endpoint(self):
        """Test root API endpoint."""
        response = client.get("/")
        
        assert response.status_code == 200
        data = response.json()
        
        assert "message" in data
        assert "version" in data
        assert "description" in data
        assert data["status"] == "operational"
    
    def test_api_info_endpoint(self):
        """Test API information endpoint."""
        response = client.get("/api/v1/info")
        
        assert response.status_code == 200
        data = response.json()
        
        assert "api" in data
        assert "agents" in data
        assert "data_sources" in data
        assert "formats" in data
        
        # Verify API info structure
        api_info = data["api"]
        assert api_info["name"] == "Cidadão.AI API"
        assert api_info["version"] == "1.0.0"
        
        # Verify agent information
        agents = data["agents"]
        assert "investigator" in agents
        assert "analyst" in agents
        assert "reporter" in agents
        
        # Check agent capabilities
        investigator = agents["investigator"]
        assert "description" in investigator
        assert "capabilities" in investigator
        assert isinstance(investigator["capabilities"], list)
    
    def test_openapi_schema_generation(self):
        """Test OpenAPI schema generation."""
        response = client.get("/openapi.json")
        
        assert response.status_code == 200
        schema = response.json()
        
        assert "openapi" in schema
        assert "info" in schema
        assert "paths" in schema
        assert "components" in schema
        
        # Verify API metadata
        info = schema["info"]
        assert info["title"] == "Cidadão.AI API"
        assert info["version"] == "1.0.0"
        
        # Verify some expected paths exist
        paths = schema["paths"]
        assert "/" in paths
        assert "/api/v1/info" in paths
    
    def test_docs_endpoint(self):
        """Test API documentation endpoint."""
        response = client.get("/docs")
        
        assert response.status_code == 200
        # Should return HTML content
        assert "text/html" in response.headers.get("content-type", "")
    
    def test_health_endpoint_basic(self):
        """Test basic health check endpoint."""
        response = client.get("/health/")
        
        assert response.status_code == 200
        data = response.json()
        
        assert "status" in data
        assert data["status"] in ["healthy", "degraded", "unhealthy"]
        assert "timestamp" in data
        assert "version" in data
        assert "uptime" in data
        assert "services" in data
    
    def test_health_liveness_probe(self):
        """Test Kubernetes liveness probe."""
        response = client.get("/health/live")
        
        assert response.status_code == 200
        data = response.json()
        
        assert data["status"] == "alive"
        assert "timestamp" in data
    
    def test_health_readiness_probe(self):
        """Test Kubernetes readiness probe."""
        response = client.get("/health/ready")
        
        # Should return 200 or 503 depending on services
        assert response.status_code in [200, 503]
        
        data = response.json()
        assert "status" in data
        assert data["status"] in ["ready", "not_ready"]


class TestAPIErrorHandling:
    """Test API error handling and edge cases."""
    
    def test_404_handling(self):
        """Test 404 error handling for non-existent endpoints."""
        response = client.get("/api/v1/nonexistent-endpoint")
        
        assert response.status_code == 404
        data = response.json()
        
        assert "detail" in data or "message" in data
    
    def test_method_not_allowed(self):
        """Test 405 method not allowed."""
        response = client.patch("/")  # Wrong method for root endpoint
        
        assert response.status_code == 405
    
    def test_large_payload_handling(self):
        """Test handling of very large payloads."""
        large_data = {"data": "x" * 1000}  # 1KB payload
        
        # Try posting to an endpoint that should exist
        response = client.post("/api/v1/info", json=large_data)
        
        # Should handle gracefully (405 for wrong method, or other appropriate error)
        assert response.status_code in [405, 422, 413]
    
    def test_invalid_json_payload(self):
        """Test handling of invalid JSON payloads."""
        response = client.post(
            "/api/v1/info",
            data="invalid json content",
            headers={"Content-Type": "application/json"}
        )
        
        # Should return 422 for invalid JSON
        assert response.status_code == 422


class TestAPICORSAndSecurity:
    """Test CORS and security configurations."""
    
    def test_cors_preflight_handling(self):
        """Test CORS preflight request handling."""
        headers = {
            "Origin": "http://localhost:3000",
            "Access-Control-Request-Method": "POST",
            "Access-Control-Request-Headers": "Content-Type"
        }
        
        response = client.options("/api/v1/info", headers=headers)
        
        # Should handle CORS preflight (200 or 204)
        assert response.status_code in [200, 204]
    
    def test_cors_headers_in_response(self):
        """Test that CORS headers are included in responses."""
        headers = {"Origin": "http://localhost:3000"}
        
        response = client.get("/", headers=headers)
        
        assert response.status_code == 200
        # CORS headers might be added by middleware
        # In test environment, they might not be present
    
    def test_trusted_host_validation(self):
        """Test trusted host middleware behavior."""
        # Test with potentially malicious host header
        headers = {"Host": "malicious-site.example.com"}
        
        response = client.get("/", headers=headers)
        
        # Should either accept (if not configured) or reject
        assert response.status_code in [200, 400, 403]
    
    def test_security_headers_present(self):
        """Test that basic security headers are present."""
        response = client.get("/")
        
        headers = response.headers
        
        # Basic security check - should have content-type
        assert "content-type" in headers
        
        # Additional security headers might be added by middleware
        # In production: X-Content-Type-Options, X-Frame-Options, etc.


class TestAPIPerformance:
    """Test basic API performance characteristics."""
    
    def test_response_time_reasonable(self):
        """Test that basic endpoints respond within reasonable time."""
        import time
        
        start_time = time.time()
        response = client.get("/")
        end_time = time.time()
        
        response_time = end_time - start_time
        
        assert response.status_code == 200
        assert response_time < 2.0  # Should respond within 2 seconds
    
    def test_concurrent_requests_handling(self):
        """Test basic concurrent request handling."""
        import threading
        import time
        
        results = []
        
        def make_request():
            response = client.get("/")
            results.append(response.status_code)
        
        # Create 3 concurrent requests
        threads = []
        for _ in range(3):
            thread = threading.Thread(target=make_request)
            threads.append(thread)
            thread.start()
        
        # Wait for all to complete
        for thread in threads:
            thread.join()
        
        # All should succeed
        assert len(results) == 3
        assert all(status == 200 for status in results)
    
    def test_health_check_performance(self):
        """Test that health checks are fast."""
        import time
        
        start_time = time.time()
        response = client.get("/health/live")
        end_time = time.time()
        
        response_time = end_time - start_time
        
        assert response.status_code == 200
        assert response_time < 1.0  # Health checks should be very fast


@pytest.mark.asyncio
class TestAsyncEndpoints:
    """Test async endpoint functionality."""
    
    async def test_async_client_basic_functionality(self, async_client):
        """Test that async client works with basic endpoints."""
        response = await async_client.get("/")
        
        assert response.status_code == 200
        data = response.json()
        
        assert "message" in data
        assert "version" in data
    
    async def test_async_health_check(self, async_client):
        """Test health check via async client."""
        response = await async_client.get("/health/")
        
        assert response.status_code == 200
        data = response.json()
        
        assert "status" in data
        assert "services" in data
    
    async def test_async_api_info(self, async_client):
        """Test API info via async client."""
        response = await async_client.get("/api/v1/info")
        
        assert response.status_code == 200
        data = response.json()
        
        assert "api" in data
        assert "agents" in data


class TestAPIValidation:
    """Test API request validation."""
    
    def test_content_type_validation(self):
        """Test content type validation for POST requests."""
        # Try posting without proper content-type
        response = client.post("/api/v1/info", data="test data")
        
        # Should handle appropriately
        assert response.status_code in [405, 415, 422]
    
    def test_accept_header_handling(self):
        """Test Accept header handling."""
        # Request JSON specifically
        headers = {"Accept": "application/json"}
        response = client.get("/", headers=headers)
        
        assert response.status_code == 200
        assert "application/json" in response.headers.get("content-type", "")
    
    def test_user_agent_handling(self):
        """Test User-Agent header handling."""
        headers = {"User-Agent": "Test Client / Integration Test"}
        response = client.get("/", headers=headers)
        
        assert response.status_code == 200
        # Should handle any user agent gracefully


if __name__ == "__main__":
    pytest.main([__file__, "-v"])