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