mcp-security-framework 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. mcp_security_framework/__init__.py +96 -0
  2. mcp_security_framework/cli/__init__.py +18 -0
  3. mcp_security_framework/cli/cert_cli.py +511 -0
  4. mcp_security_framework/cli/security_cli.py +791 -0
  5. mcp_security_framework/constants.py +209 -0
  6. mcp_security_framework/core/__init__.py +61 -0
  7. mcp_security_framework/core/auth_manager.py +1011 -0
  8. mcp_security_framework/core/cert_manager.py +1663 -0
  9. mcp_security_framework/core/permission_manager.py +735 -0
  10. mcp_security_framework/core/rate_limiter.py +602 -0
  11. mcp_security_framework/core/security_manager.py +943 -0
  12. mcp_security_framework/core/ssl_manager.py +735 -0
  13. mcp_security_framework/examples/__init__.py +75 -0
  14. mcp_security_framework/examples/django_example.py +615 -0
  15. mcp_security_framework/examples/fastapi_example.py +472 -0
  16. mcp_security_framework/examples/flask_example.py +506 -0
  17. mcp_security_framework/examples/gateway_example.py +803 -0
  18. mcp_security_framework/examples/microservice_example.py +690 -0
  19. mcp_security_framework/examples/standalone_example.py +576 -0
  20. mcp_security_framework/middleware/__init__.py +250 -0
  21. mcp_security_framework/middleware/auth_middleware.py +292 -0
  22. mcp_security_framework/middleware/fastapi_auth_middleware.py +447 -0
  23. mcp_security_framework/middleware/fastapi_middleware.py +757 -0
  24. mcp_security_framework/middleware/flask_auth_middleware.py +465 -0
  25. mcp_security_framework/middleware/flask_middleware.py +591 -0
  26. mcp_security_framework/middleware/mtls_middleware.py +439 -0
  27. mcp_security_framework/middleware/rate_limit_middleware.py +403 -0
  28. mcp_security_framework/middleware/security_middleware.py +507 -0
  29. mcp_security_framework/schemas/__init__.py +109 -0
  30. mcp_security_framework/schemas/config.py +694 -0
  31. mcp_security_framework/schemas/models.py +709 -0
  32. mcp_security_framework/schemas/responses.py +686 -0
  33. mcp_security_framework/tests/__init__.py +0 -0
  34. mcp_security_framework/utils/__init__.py +121 -0
  35. mcp_security_framework/utils/cert_utils.py +525 -0
  36. mcp_security_framework/utils/crypto_utils.py +475 -0
  37. mcp_security_framework/utils/validation_utils.py +571 -0
  38. mcp_security_framework-0.1.0.dist-info/METADATA +411 -0
  39. mcp_security_framework-0.1.0.dist-info/RECORD +76 -0
  40. mcp_security_framework-0.1.0.dist-info/WHEEL +5 -0
  41. mcp_security_framework-0.1.0.dist-info/entry_points.txt +3 -0
  42. mcp_security_framework-0.1.0.dist-info/top_level.txt +2 -0
  43. tests/__init__.py +0 -0
  44. tests/test_cli/__init__.py +0 -0
  45. tests/test_cli/test_cert_cli.py +379 -0
  46. tests/test_cli/test_security_cli.py +657 -0
  47. tests/test_core/__init__.py +0 -0
  48. tests/test_core/test_auth_manager.py +582 -0
  49. tests/test_core/test_cert_manager.py +795 -0
  50. tests/test_core/test_permission_manager.py +395 -0
  51. tests/test_core/test_rate_limiter.py +626 -0
  52. tests/test_core/test_security_manager.py +841 -0
  53. tests/test_core/test_ssl_manager.py +532 -0
  54. tests/test_examples/__init__.py +8 -0
  55. tests/test_examples/test_fastapi_example.py +264 -0
  56. tests/test_examples/test_flask_example.py +238 -0
  57. tests/test_examples/test_standalone_example.py +292 -0
  58. tests/test_integration/__init__.py +0 -0
  59. tests/test_integration/test_auth_flow.py +502 -0
  60. tests/test_integration/test_certificate_flow.py +527 -0
  61. tests/test_integration/test_fastapi_integration.py +341 -0
  62. tests/test_integration/test_flask_integration.py +398 -0
  63. tests/test_integration/test_standalone_integration.py +493 -0
  64. tests/test_middleware/__init__.py +0 -0
  65. tests/test_middleware/test_fastapi_middleware.py +523 -0
  66. tests/test_middleware/test_flask_middleware.py +582 -0
  67. tests/test_middleware/test_security_middleware.py +493 -0
  68. tests/test_schemas/__init__.py +0 -0
  69. tests/test_schemas/test_config.py +811 -0
  70. tests/test_schemas/test_models.py +879 -0
  71. tests/test_schemas/test_responses.py +1054 -0
  72. tests/test_schemas/test_serialization.py +493 -0
  73. tests/test_utils/__init__.py +0 -0
  74. tests/test_utils/test_cert_utils.py +510 -0
  75. tests/test_utils/test_crypto_utils.py +603 -0
  76. tests/test_utils/test_validation_utils.py +477 -0
@@ -0,0 +1,523 @@
1
+ """
2
+ FastAPI Security Middleware Tests
3
+
4
+ This module provides comprehensive unit tests for the FastAPISecurityMiddleware
5
+ class and its FastAPI-specific functionality.
6
+
7
+ Test Coverage:
8
+ - FastAPISecurityMiddleware initialization
9
+ - FastAPI request processing
10
+ - FastAPI authentication methods
11
+ - FastAPI response creation
12
+ - FastAPI error handling
13
+ - FastAPI header management
14
+ - FastAPI rate limiting integration
15
+ - FastAPI-specific request/response handling
16
+
17
+ Author: MCP Security Team
18
+ Version: 1.0.0
19
+ License: MIT
20
+ """
21
+
22
+ import pytest
23
+ from unittest.mock import Mock, patch, AsyncMock, MagicMock
24
+ from typing import Dict, List, Any
25
+
26
+ from fastapi import Request, Response, HTTPException, status
27
+ from fastapi.responses import JSONResponse
28
+
29
+ from mcp_security_framework.middleware.fastapi_middleware import (
30
+ FastAPISecurityMiddleware,
31
+ FastAPIMiddlewareError
32
+ )
33
+ from mcp_security_framework.core.security_manager import SecurityManager
34
+ from mcp_security_framework.schemas.config import SecurityConfig, AuthConfig, RateLimitConfig
35
+ from mcp_security_framework.schemas.models import AuthResult, ValidationResult, ValidationStatus, AuthStatus, AuthMethod
36
+
37
+
38
+ class TestFastAPISecurityMiddleware:
39
+ """Test suite for FastAPISecurityMiddleware class."""
40
+
41
+ def setup_method(self):
42
+ """Set up test fixtures before each test method."""
43
+ # Create mock security manager
44
+ self.mock_security_manager = Mock(spec=SecurityManager)
45
+ self.mock_security_manager.config = SecurityConfig(
46
+ auth=AuthConfig(
47
+ enabled=True,
48
+ methods=["api_key", "jwt"],
49
+ public_paths=["/health", "/docs"],
50
+ jwt_secret="test_jwt_secret_key"
51
+ ),
52
+ rate_limit=RateLimitConfig(
53
+ enabled=True,
54
+ default_requests_per_minute=100,
55
+ window_size_seconds=60
56
+ )
57
+ )
58
+
59
+ # Create mock auth manager
60
+ self.mock_auth_manager = Mock()
61
+ self.mock_security_manager.auth_manager = self.mock_auth_manager
62
+
63
+ # Setup rate_limiter mock
64
+ self.mock_security_manager.rate_limiter = Mock()
65
+
66
+ # Create middleware instance
67
+ self.middleware = FastAPISecurityMiddleware(self.mock_security_manager)
68
+
69
+ def create_mock_request(self, path: str = "/api/test", headers: Dict[str, str] = None) -> Mock:
70
+ """Create a mock FastAPI request for testing."""
71
+ mock_request = Mock(spec=Request)
72
+ mock_request.url.path = path
73
+ mock_request.method = "GET"
74
+ mock_request.headers = headers or {}
75
+ mock_request.client = Mock()
76
+ mock_request.client.host = "127.0.0.1"
77
+ return mock_request
78
+
79
+ def test_initialization_success(self):
80
+ """Test successful FastAPI middleware initialization."""
81
+ assert isinstance(self.middleware, FastAPISecurityMiddleware)
82
+ assert self.middleware.security_manager == self.mock_security_manager
83
+ assert self.middleware.config == self.mock_security_manager.config
84
+
85
+ @pytest.mark.asyncio
86
+ async def test_call_success(self):
87
+ """Test successful middleware call."""
88
+ mock_request = self.create_mock_request()
89
+ mock_call_next = AsyncMock()
90
+ mock_response = Mock(spec=Response)
91
+ mock_response.status_code = 200
92
+ mock_response.headers = {}
93
+ mock_call_next.return_value = mock_response
94
+
95
+ # Mock successful authentication and authorization
96
+ self.mock_security_manager.rate_limiter.check_rate_limit.return_value = True
97
+
98
+ # Mock the _authenticate_request method directly
99
+ auth_result = AuthResult(
100
+ is_valid=True,
101
+ status=AuthStatus.SUCCESS,
102
+ username="test_user",
103
+ roles=["user"],
104
+ auth_method=AuthMethod.API_KEY
105
+ )
106
+ self.middleware._authenticate_request = AsyncMock(return_value=auth_result)
107
+
108
+ self.mock_security_manager.check_permissions.return_value = ValidationResult(
109
+ is_valid=True,
110
+ status=ValidationStatus.VALID
111
+ )
112
+
113
+ result = await self.middleware(mock_request, mock_call_next)
114
+
115
+ assert result == mock_response
116
+ mock_call_next.assert_called_once_with(mock_request)
117
+
118
+ @pytest.mark.asyncio
119
+ async def test_call_rate_limit_exceeded(self):
120
+ """Test middleware call with rate limit exceeded."""
121
+ mock_request = self.create_mock_request()
122
+ mock_call_next = AsyncMock()
123
+
124
+ # Mock _is_public_path to return False so rate limiting is checked
125
+ self.middleware._is_public_path = Mock(return_value=False)
126
+ # Mock the security manager's check_rate_limit method
127
+ self.mock_security_manager.check_rate_limit.return_value = False
128
+
129
+ result = await self.middleware(mock_request, mock_call_next)
130
+
131
+ assert isinstance(result, JSONResponse)
132
+ assert result.status_code == status.HTTP_429_TOO_MANY_REQUESTS
133
+ assert "Rate limit exceeded" in result.body.decode()
134
+ mock_call_next.assert_not_called()
135
+
136
+ @pytest.mark.asyncio
137
+ async def test_call_public_path(self):
138
+ """Test middleware call with public path."""
139
+ mock_request = self.create_mock_request(path="/health")
140
+ mock_call_next = AsyncMock()
141
+ mock_response = Mock(spec=Response)
142
+ mock_response.status_code = 200
143
+ mock_response.headers = {}
144
+ mock_call_next.return_value = mock_response
145
+
146
+ self.mock_security_manager.rate_limiter.check_rate_limit.return_value = True
147
+
148
+ result = await self.middleware(mock_request, mock_call_next)
149
+
150
+ assert result == mock_response
151
+ mock_call_next.assert_called_once_with(mock_request)
152
+
153
+ @pytest.mark.asyncio
154
+ async def test_call_authentication_failed(self):
155
+ """Test middleware call with authentication failure."""
156
+ mock_request = self.create_mock_request()
157
+ mock_call_next = AsyncMock()
158
+
159
+ self.mock_security_manager.rate_limiter.check_rate_limit.return_value = True
160
+ self.mock_auth_manager.authenticate_api_key.return_value = AuthResult(
161
+ is_valid=False,
162
+ status=AuthStatus.FAILED,
163
+ username=None,
164
+ roles=[],
165
+ auth_method=AuthMethod.API_KEY,
166
+ error_code=-32005,
167
+ error_message="Authentication failed"
168
+ )
169
+
170
+ result = await self.middleware(mock_request, mock_call_next)
171
+
172
+ assert isinstance(result, JSONResponse)
173
+ assert result.status_code == status.HTTP_401_UNAUTHORIZED
174
+ assert "Authentication failed" in result.body.decode()
175
+ mock_call_next.assert_not_called()
176
+
177
+ @pytest.mark.asyncio
178
+ async def test_call_permission_denied(self):
179
+ """Test middleware call with permission denied."""
180
+ mock_request = self.create_mock_request()
181
+ mock_call_next = AsyncMock()
182
+
183
+ self.mock_security_manager.rate_limiter.check_rate_limit.return_value = True
184
+
185
+ # Mock successful authentication
186
+ auth_result = AuthResult(
187
+ is_valid=True,
188
+ status=AuthStatus.SUCCESS,
189
+ username="test_user",
190
+ roles=["user"],
191
+ auth_method=AuthMethod.API_KEY
192
+ )
193
+ self.middleware._authenticate_request = AsyncMock(return_value=auth_result)
194
+
195
+ # Mock the _validate_permissions method directly
196
+ self.middleware._validate_permissions = Mock(return_value=False)
197
+
198
+ result = await self.middleware(mock_request, mock_call_next)
199
+
200
+ assert isinstance(result, JSONResponse)
201
+ assert result.status_code == status.HTTP_403_FORBIDDEN
202
+ assert "Permission denied" in result.body.decode()
203
+ mock_call_next.assert_not_called()
204
+
205
+ def test_get_rate_limit_identifier(self):
206
+ """Test getting rate limit identifier from request."""
207
+ mock_request = self.create_mock_request()
208
+ result = self.middleware._get_rate_limit_identifier(mock_request)
209
+ assert result == "127.0.0.1"
210
+
211
+ def test_get_rate_limit_identifier_with_forwarded_for(self):
212
+ """Test getting rate limit identifier with X-Forwarded-For header."""
213
+ headers = {"X-Forwarded-For": "192.168.1.1, 10.0.0.1"}
214
+ mock_request = self.create_mock_request(headers=headers)
215
+ result = self.middleware._get_rate_limit_identifier(mock_request)
216
+ assert result == "192.168.1.1"
217
+
218
+ def test_get_rate_limit_identifier_with_real_ip(self):
219
+ """Test getting rate limit identifier with X-Real-IP header."""
220
+ headers = {"X-Real-IP": "192.168.1.100"}
221
+ mock_request = self.create_mock_request(headers=headers)
222
+ result = self.middleware._get_rate_limit_identifier(mock_request)
223
+ assert result == "192.168.1.100"
224
+
225
+ def test_get_request_path(self):
226
+ """Test getting request path from FastAPI request."""
227
+ mock_request = self.create_mock_request(path="/api/users")
228
+ result = self.middleware._get_request_path(mock_request)
229
+ assert result == "/api/users"
230
+
231
+ def test_get_required_permissions_from_state(self):
232
+ """Test getting required permissions from request state."""
233
+ mock_request = self.create_mock_request()
234
+ mock_request.state.required_permissions = ["read", "write"]
235
+
236
+ result = self.middleware._get_required_permissions(mock_request)
237
+ assert result == ["read", "write"]
238
+
239
+ def test_get_required_permissions_default(self):
240
+ """Test getting required permissions when not set."""
241
+ mock_request = self.create_mock_request()
242
+ # Ensure state doesn't have required_permissions
243
+ if hasattr(mock_request, 'state'):
244
+ delattr(mock_request.state, 'required_permissions')
245
+ result = self.middleware._get_required_permissions(mock_request)
246
+ assert result == []
247
+
248
+ @pytest.mark.asyncio
249
+ async def test_try_api_key_auth_success(self):
250
+ """Test successful API key authentication."""
251
+ headers = {"X-API-Key": "valid_key"}
252
+ mock_request = self.create_mock_request(headers=headers)
253
+
254
+ expected_result = AuthResult(
255
+ is_valid=True,
256
+ status=AuthStatus.SUCCESS,
257
+ username="test_user",
258
+ roles=["user"],
259
+ auth_method=AuthMethod.API_KEY
260
+ )
261
+ self.mock_auth_manager.authenticate_api_key.return_value = expected_result
262
+
263
+ result = await self.middleware._try_api_key_auth(mock_request)
264
+
265
+ assert result == expected_result
266
+ self.mock_auth_manager.authenticate_api_key.assert_called_once_with("valid_key")
267
+
268
+ @pytest.mark.asyncio
269
+ async def test_try_api_key_auth_from_authorization_header(self):
270
+ """Test API key authentication from Authorization header."""
271
+ headers = {"Authorization": "Bearer api_key_123"}
272
+ mock_request = self.create_mock_request(headers=headers)
273
+
274
+ expected_result = AuthResult(
275
+ is_valid=True,
276
+ status=AuthStatus.SUCCESS,
277
+ username="test_user",
278
+ roles=["user"],
279
+ auth_method=AuthMethod.API_KEY
280
+ )
281
+ self.mock_auth_manager.authenticate_api_key.return_value = expected_result
282
+
283
+ result = await self.middleware._try_api_key_auth(mock_request)
284
+
285
+ assert result == expected_result
286
+ self.mock_auth_manager.authenticate_api_key.assert_called_once_with("api_key_123")
287
+
288
+ @pytest.mark.asyncio
289
+ async def test_try_api_key_auth_no_key(self):
290
+ """Test API key authentication with no key provided."""
291
+ mock_request = self.create_mock_request()
292
+
293
+ result = await self.middleware._try_api_key_auth(mock_request)
294
+
295
+ assert result.is_valid is False
296
+ assert result.error_code == -32012
297
+ assert "API key not found" in result.error_message
298
+
299
+ @pytest.mark.asyncio
300
+ async def test_try_jwt_auth_success(self):
301
+ """Test successful JWT authentication."""
302
+ headers = {"Authorization": "Bearer jwt_token_123"}
303
+ mock_request = self.create_mock_request(headers=headers)
304
+
305
+ expected_result = AuthResult(
306
+ is_valid=True,
307
+ status=AuthStatus.SUCCESS,
308
+ username="test_user",
309
+ roles=["user"],
310
+ auth_method=AuthMethod.JWT
311
+ )
312
+ self.mock_auth_manager.authenticate_jwt_token.return_value = expected_result
313
+
314
+ result = await self.middleware._try_jwt_auth(mock_request)
315
+
316
+ assert result == expected_result
317
+ self.mock_auth_manager.authenticate_jwt_token.assert_called_once_with("jwt_token_123")
318
+
319
+ @pytest.mark.asyncio
320
+ async def test_try_jwt_auth_no_token(self):
321
+ """Test JWT authentication with no token provided."""
322
+ mock_request = self.create_mock_request()
323
+
324
+ result = await self.middleware._try_jwt_auth(mock_request)
325
+
326
+ assert result.is_valid is False
327
+ assert result.error_code == -32013
328
+ assert "JWT token not found" in result.error_message
329
+
330
+ @pytest.mark.asyncio
331
+ async def test_try_jwt_auth_invalid_header(self):
332
+ """Test JWT authentication with invalid Authorization header."""
333
+ headers = {"Authorization": "Invalid jwt_token_123"}
334
+ mock_request = self.create_mock_request(headers=headers)
335
+
336
+ result = await self.middleware._try_jwt_auth(mock_request)
337
+
338
+ assert result.is_valid is False
339
+ assert result.error_code == -32013
340
+ assert "JWT token not found" in result.error_message
341
+
342
+ @pytest.mark.asyncio
343
+ async def test_try_certificate_auth_not_implemented(self):
344
+ """Test certificate authentication (not implemented)."""
345
+ mock_request = self.create_mock_request()
346
+
347
+ result = await self.middleware._try_certificate_auth(mock_request)
348
+
349
+ assert result.is_valid is False
350
+ assert result.error_code == -32014
351
+ assert "not implemented" in result.error_message
352
+
353
+ @pytest.mark.asyncio
354
+ async def test_try_basic_auth_not_implemented(self):
355
+ """Test basic authentication (not implemented)."""
356
+ mock_request = self.create_mock_request()
357
+
358
+ result = await self.middleware._try_basic_auth(mock_request)
359
+
360
+ assert result.is_valid is False
361
+ assert result.error_code == -32015
362
+ assert "not found" in result.error_message
363
+
364
+ @pytest.mark.asyncio
365
+ async def test_try_auth_method_unsupported(self):
366
+ """Test authentication with unsupported method."""
367
+ mock_request = self.create_mock_request()
368
+
369
+ result = await self.middleware._try_auth_method(mock_request, "unsupported_method")
370
+
371
+ assert result.is_valid is False
372
+ assert result.error_code == -32010
373
+ assert "Unsupported authentication method" in result.error_message
374
+
375
+ def test_apply_security_headers(self):
376
+ """Test applying security headers to FastAPI response."""
377
+ mock_response = Mock(spec=Response)
378
+ mock_response.headers = {}
379
+
380
+ headers = {
381
+ "X-Content-Type-Options": "nosniff",
382
+ "X-Frame-Options": "DENY",
383
+ "X-XSS-Protection": "1; mode=block"
384
+ }
385
+
386
+ self.middleware._apply_security_headers(mock_response, headers)
387
+
388
+ assert mock_response.headers["X-Content-Type-Options"] == "nosniff"
389
+ assert mock_response.headers["X-Frame-Options"] == "DENY"
390
+ assert mock_response.headers["X-XSS-Protection"] == "1; mode=block"
391
+
392
+ def test_create_error_response(self):
393
+ """Test creating error response."""
394
+ result = self.middleware._create_error_response(400, "Bad request")
395
+
396
+ assert isinstance(result, JSONResponse)
397
+ assert result.status_code == 400
398
+ assert "Security violation" in result.body.decode()
399
+ assert "Bad request" in result.body.decode()
400
+
401
+ def test_rate_limit_response(self):
402
+ """Test creating rate limit response."""
403
+ result = self.middleware._rate_limit_response()
404
+
405
+ assert isinstance(result, JSONResponse)
406
+ assert result.status_code == status.HTTP_429_TOO_MANY_REQUESTS
407
+ assert "Rate limit exceeded" in result.body.decode()
408
+ assert "Retry-After" in result.headers
409
+
410
+ def test_auth_error_response(self):
411
+ """Test creating authentication error response."""
412
+ auth_result = AuthResult(
413
+ is_valid=False,
414
+ status=AuthStatus.FAILED,
415
+ username=None,
416
+ roles=[],
417
+ auth_method=AuthMethod.API_KEY,
418
+ error_code=-32005,
419
+ error_message="Invalid API key"
420
+ )
421
+
422
+ result = self.middleware._auth_error_response(auth_result)
423
+
424
+ assert isinstance(result, JSONResponse)
425
+ assert result.status_code == status.HTTP_401_UNAUTHORIZED
426
+ assert "Authentication failed" in result.body.decode()
427
+ assert "Invalid API key" in result.body.decode()
428
+ assert "WWW-Authenticate" in result.headers
429
+
430
+ def test_permission_error_response(self):
431
+ """Test creating permission error response."""
432
+ result = self.middleware._permission_error_response()
433
+
434
+ assert isinstance(result, JSONResponse)
435
+ assert result.status_code == status.HTTP_403_FORBIDDEN
436
+ assert "Permission denied" in result.body.decode()
437
+ assert "Insufficient permissions" in result.body.decode()
438
+
439
+ def test_get_client_ip_from_forwarded_for(self):
440
+ """Test getting client IP from X-Forwarded-For header."""
441
+ headers = {"X-Forwarded-For": "192.168.1.1, 10.0.0.1"}
442
+ mock_request = self.create_mock_request(headers=headers)
443
+
444
+ result = self.middleware._get_client_ip(mock_request)
445
+ assert result == "192.168.1.1"
446
+
447
+ def test_get_client_ip_from_real_ip(self):
448
+ """Test getting client IP from X-Real-IP header."""
449
+ headers = {"X-Real-IP": "192.168.1.100"}
450
+ mock_request = self.create_mock_request(headers=headers)
451
+
452
+ result = self.middleware._get_client_ip(mock_request)
453
+ assert result == "192.168.1.100"
454
+
455
+ def test_get_client_ip_from_client_host(self):
456
+ """Test getting client IP from client host."""
457
+ mock_request = self.create_mock_request()
458
+ mock_request.client.host = "192.168.1.50"
459
+
460
+ result = self.middleware._get_client_ip(mock_request)
461
+ assert result == "192.168.1.50"
462
+
463
+ def test_get_client_ip_fallback(self):
464
+ """Test getting client IP with fallback."""
465
+ mock_request = self.create_mock_request()
466
+ mock_request.client = None
467
+
468
+ result = self.middleware._get_client_ip(mock_request)
469
+ assert result == "127.0.0.1"
470
+
471
+ @pytest.mark.asyncio
472
+ async def test_call_with_http_exception(self):
473
+ """Test middleware call that raises HTTPException."""
474
+ mock_request = self.create_mock_request()
475
+ mock_call_next = AsyncMock()
476
+ mock_call_next.side_effect = HTTPException(status_code=500, detail="Internal error")
477
+
478
+ # Mock successful authentication to reach call_next
479
+ auth_result = AuthResult(
480
+ is_valid=True,
481
+ status=AuthStatus.SUCCESS,
482
+ username="test_user",
483
+ roles=["user"],
484
+ auth_method=AuthMethod.API_KEY
485
+ )
486
+ self.middleware._authenticate_request = AsyncMock(return_value=auth_result)
487
+ self.mock_security_manager.check_permissions.return_value = ValidationResult(
488
+ is_valid=True,
489
+ status=ValidationStatus.VALID
490
+ )
491
+
492
+ with pytest.raises(HTTPException) as exc_info:
493
+ await self.middleware(mock_request, mock_call_next)
494
+
495
+ assert exc_info.value.status_code == 500
496
+ assert exc_info.value.detail == "Internal error"
497
+
498
+ @pytest.mark.asyncio
499
+ async def test_call_with_general_exception(self):
500
+ """Test middleware call with general exception."""
501
+ mock_request = self.create_mock_request()
502
+ mock_call_next = AsyncMock()
503
+ mock_call_next.side_effect = Exception("General error")
504
+
505
+ # Mock successful authentication to reach call_next
506
+ auth_result = AuthResult(
507
+ is_valid=True,
508
+ status=AuthStatus.SUCCESS,
509
+ username="test_user",
510
+ roles=["user"],
511
+ auth_method=AuthMethod.API_KEY
512
+ )
513
+ self.middleware._authenticate_request = AsyncMock(return_value=auth_result)
514
+ self.mock_security_manager.check_permissions.return_value = ValidationResult(
515
+ is_valid=True,
516
+ status=ValidationStatus.VALID
517
+ )
518
+
519
+ with pytest.raises(FastAPIMiddlewareError) as exc_info:
520
+ await self.middleware(mock_request, mock_call_next)
521
+
522
+ assert "Middleware processing failed" in str(exc_info.value)
523
+ assert exc_info.value.error_code == -32003