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,1054 @@
1
+ """
2
+ Response Models Test Module
3
+
4
+ This module provides comprehensive unit tests for all response models
5
+ in the MCP Security Framework. It tests validation, factory methods,
6
+ and edge cases for all response classes.
7
+
8
+ Test Classes:
9
+ TestSecurityResponse: Tests for base security response model
10
+ TestErrorResponse: Tests for error response model
11
+ TestSuccessResponse: Tests for success response model
12
+ TestValidationResponse: Tests for validation response model
13
+ TestAuthResponse: Tests for authentication response model
14
+ TestCertificateResponse: Tests for certificate response model
15
+ TestPermissionResponse: Tests for permission response model
16
+ TestRateLimitResponse: Tests for rate limiting response model
17
+
18
+ Author: MCP Security Team
19
+ Version: 1.0.0
20
+ License: MIT
21
+ """
22
+
23
+ from datetime import datetime, timedelta, timezone
24
+
25
+ import pytest
26
+ from pydantic import ValidationError
27
+
28
+ from mcp_security_framework.schemas.models import (
29
+ AuthMethod,
30
+ AuthResult,
31
+ AuthStatus,
32
+ CertificateInfo,
33
+ CertificateType,
34
+ RateLimitStatus,
35
+ ValidationResult,
36
+ ValidationStatus,
37
+ )
38
+ from mcp_security_framework.schemas.responses import (
39
+ AuthResponse,
40
+ CertificateResponse,
41
+ ErrorCode,
42
+ ErrorResponse,
43
+ PermissionResponse,
44
+ RateLimitResponse,
45
+ ResponseStatus,
46
+ SecurityResponse,
47
+ SuccessResponse,
48
+ ValidationResponse,
49
+ )
50
+
51
+
52
+ class TestSecurityResponse:
53
+ """Test suite for SecurityResponse class."""
54
+
55
+ def test_security_response_basic(self):
56
+ """Test SecurityResponse with basic information."""
57
+ response = SecurityResponse(
58
+ status=ResponseStatus.SUCCESS,
59
+ message="Operation completed successfully",
60
+ data={"key": "value"},
61
+ )
62
+
63
+ assert response.status == ResponseStatus.SUCCESS
64
+ assert response.message == "Operation completed successfully"
65
+ assert response.data == {"key": "value"}
66
+ assert response.timestamp is not None
67
+ assert response.request_id is None
68
+ assert response.version == "1.0.0"
69
+ assert response.metadata == {}
70
+
71
+ def test_security_response_with_metadata(self):
72
+ """Test SecurityResponse with metadata."""
73
+ response = SecurityResponse(
74
+ status=ResponseStatus.SUCCESS,
75
+ message="Operation completed successfully",
76
+ data={"key": "value"},
77
+ request_id="req-123",
78
+ metadata={"user_id": "12345", "operation": "create"},
79
+ )
80
+
81
+ assert response.request_id == "req-123"
82
+ assert response.metadata == {"user_id": "12345", "operation": "create"}
83
+
84
+ def test_security_response_message_validation(self):
85
+ """Test SecurityResponse message validation."""
86
+ # Valid message
87
+ response = SecurityResponse(
88
+ status=ResponseStatus.SUCCESS, message="Valid message"
89
+ )
90
+ assert response.message == "Valid message"
91
+
92
+ # Message with whitespace
93
+ response = SecurityResponse(
94
+ status=ResponseStatus.SUCCESS, message=" Valid message "
95
+ )
96
+ assert response.message == "Valid message"
97
+
98
+ # Empty message
99
+ with pytest.raises(ValidationError) as exc_info:
100
+ SecurityResponse(status=ResponseStatus.SUCCESS, message="")
101
+
102
+ assert "Response message cannot be empty" in str(exc_info.value)
103
+
104
+ def test_security_response_properties(self):
105
+ """Test SecurityResponse properties."""
106
+ # Success response
107
+ response = SecurityResponse(status=ResponseStatus.SUCCESS, message="Success")
108
+
109
+ assert response.is_success is True
110
+ assert response.is_error is False
111
+
112
+ # Error response
113
+ response = SecurityResponse(status=ResponseStatus.ERROR, message="Error")
114
+
115
+ assert response.is_success is False
116
+ assert response.is_error is True
117
+
118
+
119
+ class TestErrorResponse:
120
+ """Test suite for ErrorResponse class."""
121
+
122
+ def test_error_response_basic(self):
123
+ """Test ErrorResponse with basic information."""
124
+ error_response = ErrorResponse(
125
+ status=ResponseStatus.ERROR,
126
+ message="Authentication failed",
127
+ error_code=ErrorCode.AUTHENTICATION_FAILED,
128
+ http_status_code=401,
129
+ details="Invalid API key provided",
130
+ )
131
+
132
+ assert error_response.status == ResponseStatus.ERROR
133
+ assert error_response.message == "Authentication failed"
134
+ assert error_response.error_code == ErrorCode.AUTHENTICATION_FAILED
135
+ assert error_response.http_status_code == 401
136
+ assert error_response.details == "Invalid API key provided"
137
+ assert error_response.error_type == "SecurityError"
138
+ assert error_response.field_errors == {}
139
+ assert error_response.stack_trace is None
140
+ assert error_response.retry_after is None
141
+
142
+ def test_error_response_complete(self):
143
+ """Test ErrorResponse with complete information."""
144
+ error_response = ErrorResponse(
145
+ status=ResponseStatus.ERROR,
146
+ message="Validation failed",
147
+ error_code=ErrorCode.VALIDATION_ERROR,
148
+ http_status_code=400,
149
+ details="Multiple validation errors occurred",
150
+ field_errors={"username": ["Required field"], "email": ["Invalid format"]},
151
+ stack_trace="Traceback (most recent call last):...",
152
+ retry_after=60,
153
+ error_type="ValidationError",
154
+ )
155
+
156
+ assert error_response.field_errors == {
157
+ "username": ["Required field"],
158
+ "email": ["Invalid format"],
159
+ }
160
+ assert error_response.stack_trace == "Traceback (most recent call last):..."
161
+ assert error_response.retry_after == 60
162
+ assert error_response.error_type == "ValidationError"
163
+
164
+ def test_error_response_status_validation(self):
165
+ """Test ErrorResponse status validation."""
166
+ with pytest.raises(ValidationError) as exc_info:
167
+ ErrorResponse(
168
+ status=ResponseStatus.SUCCESS, # Should be ERROR
169
+ message="Error message",
170
+ error_code=ErrorCode.AUTHENTICATION_FAILED,
171
+ http_status_code=401,
172
+ )
173
+
174
+ assert "Error responses must have ERROR status" in str(exc_info.value)
175
+
176
+ def test_error_response_http_status_code_validation(self):
177
+ """Test ErrorResponse HTTP status code validation."""
178
+ # Valid range
179
+ error_response = ErrorResponse(
180
+ status=ResponseStatus.ERROR,
181
+ message="Error",
182
+ error_code=ErrorCode.AUTHENTICATION_FAILED,
183
+ http_status_code=401,
184
+ )
185
+ assert error_response.http_status_code == 401
186
+
187
+ # Invalid range - too low
188
+ with pytest.raises(ValidationError) as exc_info:
189
+ ErrorResponse(
190
+ status=ResponseStatus.ERROR,
191
+ message="Error",
192
+ error_code=ErrorCode.AUTHENTICATION_FAILED,
193
+ http_status_code=200, # Should be 4xx or 5xx
194
+ )
195
+
196
+ assert "Input should be greater than or equal to 400" in str(exc_info.value)
197
+
198
+ # Invalid range - too high
199
+ with pytest.raises(ValidationError) as exc_info:
200
+ ErrorResponse(
201
+ status=ResponseStatus.ERROR,
202
+ message="Error",
203
+ error_code=ErrorCode.AUTHENTICATION_FAILED,
204
+ http_status_code=600, # Should be 4xx or 5xx
205
+ )
206
+
207
+ assert "Input should be less than or equal to 599" in str(exc_info.value)
208
+
209
+ def test_error_response_consistency_validation(self):
210
+ """Test ErrorResponse consistency validation."""
211
+ # Authentication failed should have 401
212
+ with pytest.raises(ValidationError) as exc_info:
213
+ ErrorResponse(
214
+ status=ResponseStatus.ERROR,
215
+ message="Authentication failed",
216
+ error_code=ErrorCode.AUTHENTICATION_FAILED,
217
+ http_status_code=403, # Should be 401
218
+ )
219
+
220
+ assert "Authentication failed should have HTTP status 401" in str(
221
+ exc_info.value
222
+ )
223
+
224
+ # Insufficient permissions should have 403
225
+ with pytest.raises(ValidationError) as exc_info:
226
+ ErrorResponse(
227
+ status=ResponseStatus.ERROR,
228
+ message="Insufficient permissions",
229
+ error_code=ErrorCode.INSUFFICIENT_PERMISSIONS,
230
+ http_status_code=401, # Should be 403
231
+ )
232
+
233
+ assert "Insufficient permissions should have HTTP status 403" in str(
234
+ exc_info.value
235
+ )
236
+
237
+ # Rate limit exceeded should have 429
238
+ with pytest.raises(ValidationError) as exc_info:
239
+ ErrorResponse(
240
+ status=ResponseStatus.ERROR,
241
+ message="Rate limit exceeded",
242
+ error_code=ErrorCode.RATE_LIMIT_EXCEEDED,
243
+ http_status_code=400, # Should be 429
244
+ )
245
+
246
+ assert "Rate limit exceeded should have HTTP status 429" in str(exc_info.value)
247
+
248
+ def test_error_response_factory_methods(self):
249
+ """Test ErrorResponse factory methods."""
250
+ # Authentication error
251
+ auth_error = ErrorResponse.create_authentication_error(
252
+ "Invalid credentials", "API key not found"
253
+ )
254
+
255
+ assert auth_error.status == ResponseStatus.ERROR
256
+ assert auth_error.message == "Invalid credentials"
257
+ assert auth_error.error_code == ErrorCode.AUTHENTICATION_FAILED
258
+ assert auth_error.http_status_code == 401
259
+ assert auth_error.details == "API key not found"
260
+ assert auth_error.error_type == "AuthenticationError"
261
+
262
+ # Permission error
263
+ perm_error = ErrorResponse.create_permission_error(
264
+ "Access denied", "User lacks required permissions"
265
+ )
266
+
267
+ assert perm_error.status == ResponseStatus.ERROR
268
+ assert perm_error.message == "Access denied"
269
+ assert perm_error.error_code == ErrorCode.INSUFFICIENT_PERMISSIONS
270
+ assert perm_error.http_status_code == 403
271
+ assert perm_error.details == "User lacks required permissions"
272
+ assert perm_error.error_type == "PermissionError"
273
+
274
+ # Rate limit error
275
+ rate_error = ErrorResponse.create_rate_limit_error("Too many requests", 60)
276
+
277
+ assert rate_error.status == ResponseStatus.ERROR
278
+ assert rate_error.message == "Too many requests"
279
+ assert rate_error.error_code == ErrorCode.RATE_LIMIT_EXCEEDED
280
+ assert rate_error.http_status_code == 429
281
+ assert rate_error.retry_after == 60
282
+ assert rate_error.error_type == "RateLimitError"
283
+
284
+ # Validation error
285
+ field_errors = {"username": ["Required"], "email": ["Invalid format"]}
286
+ val_error = ErrorResponse.create_validation_error(
287
+ "Validation failed", field_errors
288
+ )
289
+
290
+ assert val_error.status == ResponseStatus.ERROR
291
+ assert val_error.message == "Validation failed"
292
+ assert val_error.error_code == ErrorCode.VALIDATION_ERROR
293
+ assert val_error.http_status_code == 400
294
+ assert val_error.field_errors == field_errors
295
+ assert val_error.error_type == "ValidationError"
296
+
297
+
298
+ class TestSuccessResponse:
299
+ """Test suite for SuccessResponse class."""
300
+
301
+ def test_success_response_basic(self):
302
+ """Test SuccessResponse with basic information."""
303
+ success_response = SuccessResponse(
304
+ status=ResponseStatus.SUCCESS,
305
+ message="Operation completed successfully",
306
+ data={"user_id": "12345", "status": "active"},
307
+ )
308
+
309
+ assert success_response.status == ResponseStatus.SUCCESS
310
+ assert success_response.message == "Operation completed successfully"
311
+ assert success_response.data == {"user_id": "12345", "status": "active"}
312
+ assert success_response.total_count is None
313
+ assert success_response.page is None
314
+ assert success_response.page_size is None
315
+ assert success_response.has_more is None
316
+
317
+ def test_success_response_with_pagination(self):
318
+ """Test SuccessResponse with pagination information."""
319
+ success_response = SuccessResponse(
320
+ status=ResponseStatus.SUCCESS,
321
+ message="Users retrieved successfully",
322
+ data=[{"id": "1"}, {"id": "2"}],
323
+ total_count=100,
324
+ page=1,
325
+ page_size=10,
326
+ has_more=True,
327
+ )
328
+
329
+ assert success_response.total_count == 100
330
+ assert success_response.page == 1
331
+ assert success_response.page_size == 10
332
+ assert success_response.has_more is True
333
+
334
+ def test_success_response_status_validation(self):
335
+ """Test SuccessResponse status validation."""
336
+ with pytest.raises(ValidationError) as exc_info:
337
+ SuccessResponse(
338
+ status=ResponseStatus.ERROR, # Should be SUCCESS
339
+ message="Success",
340
+ data={"key": "value"},
341
+ )
342
+
343
+ assert "Success responses must have SUCCESS status" in str(exc_info.value)
344
+
345
+ def test_success_response_data_validation(self):
346
+ """Test SuccessResponse data validation."""
347
+ with pytest.raises(ValidationError) as exc_info:
348
+ SuccessResponse(
349
+ status=ResponseStatus.SUCCESS,
350
+ message="Success",
351
+ data=None, # Should not be None
352
+ )
353
+
354
+ assert "Success responses must have data" in str(exc_info.value)
355
+
356
+ def test_success_response_pagination_validation(self):
357
+ """Test SuccessResponse pagination validation."""
358
+ # Valid pagination
359
+ success_response = SuccessResponse(
360
+ status=ResponseStatus.SUCCESS,
361
+ message="Success",
362
+ data=[],
363
+ total_count=0,
364
+ page=1,
365
+ page_size=10,
366
+ )
367
+ assert success_response.total_count == 0
368
+ assert success_response.page == 1
369
+ assert success_response.page_size == 10
370
+
371
+ # Invalid total_count
372
+ with pytest.raises(ValidationError):
373
+ SuccessResponse(
374
+ message="Success", data=[], total_count=-1
375
+ ) # Should be >= 0
376
+
377
+ # Invalid page
378
+ with pytest.raises(ValidationError):
379
+ SuccessResponse(message="Success", data=[], page=0) # Should be >= 1
380
+
381
+ # Invalid page_size
382
+ with pytest.raises(ValidationError):
383
+ SuccessResponse(message="Success", data=[], page_size=0) # Should be >= 1
384
+
385
+ def test_success_response_factory_method(self):
386
+ """Test SuccessResponse factory method."""
387
+ data = {"user_id": "12345", "name": "John Doe"}
388
+ success_response = SuccessResponse.create_success(
389
+ data=data, message="User created successfully"
390
+ )
391
+
392
+ assert success_response.status == ResponseStatus.SUCCESS
393
+ assert success_response.message == "User created successfully"
394
+ assert success_response.data == data
395
+
396
+ # Default message
397
+ success_response = SuccessResponse.create_success(data=data)
398
+ assert success_response.message == "Operation completed successfully"
399
+
400
+
401
+ class TestValidationResponse:
402
+ """Test suite for ValidationResponse class."""
403
+
404
+ def test_validation_response_success(self):
405
+ """Test ValidationResponse with successful validation."""
406
+ validation_result = ValidationResult(
407
+ is_valid=True,
408
+ status=ValidationStatus.VALID,
409
+ field_name="username",
410
+ value="testuser",
411
+ )
412
+
413
+ validation_response = ValidationResponse(
414
+ status=ResponseStatus.SUCCESS,
415
+ message="Validation completed successfully",
416
+ validation_result=validation_result,
417
+ )
418
+
419
+ assert validation_response.status == ResponseStatus.SUCCESS
420
+ assert validation_response.message == "Validation completed successfully"
421
+ assert validation_response.validation_result == validation_result
422
+ assert validation_response.field_errors == {}
423
+ assert validation_response.warnings == []
424
+ assert validation_response.suggestions == []
425
+
426
+ def test_validation_response_error(self):
427
+ """Test ValidationResponse with validation error."""
428
+ validation_result = ValidationResult(
429
+ is_valid=False,
430
+ status=ValidationStatus.INVALID,
431
+ field_name="password",
432
+ value="",
433
+ error_code=400,
434
+ error_message="Password cannot be empty",
435
+ )
436
+
437
+ field_errors = {"password": ["Required field"], "email": ["Invalid format"]}
438
+ validation_response = ValidationResponse(
439
+ status=ResponseStatus.ERROR,
440
+ message="Validation failed",
441
+ validation_result=validation_result,
442
+ field_errors=field_errors,
443
+ warnings=["Password is too weak"],
444
+ suggestions=["Use at least 8 characters"],
445
+ )
446
+
447
+ assert validation_response.status == ResponseStatus.ERROR
448
+ assert validation_response.message == "Validation failed"
449
+ assert validation_response.validation_result == validation_result
450
+ assert validation_response.field_errors == field_errors
451
+ assert validation_response.warnings == ["Password is too weak"]
452
+ assert validation_response.suggestions == ["Use at least 8 characters"]
453
+
454
+ def test_validation_response_status_validation(self):
455
+ """Test ValidationResponse status validation."""
456
+ # Valid validation should have SUCCESS status
457
+ validation_result = ValidationResult(
458
+ is_valid=True, status=ValidationStatus.VALID
459
+ )
460
+
461
+ with pytest.raises(ValidationError) as exc_info:
462
+ ValidationResponse(
463
+ status=ResponseStatus.ERROR, # Should be SUCCESS
464
+ message="Validation failed",
465
+ validation_result=validation_result,
466
+ )
467
+
468
+ assert "Valid validation must have SUCCESS status" in str(exc_info.value)
469
+
470
+ # Invalid validation should have ERROR status
471
+ validation_result = ValidationResult(
472
+ is_valid=False, status=ValidationStatus.INVALID
473
+ )
474
+
475
+ with pytest.raises(ValidationError) as exc_info:
476
+ ValidationResponse(
477
+ status=ResponseStatus.SUCCESS, # Should be ERROR
478
+ message="Validation succeeded",
479
+ validation_result=validation_result,
480
+ )
481
+
482
+ assert "Invalid validation must have ERROR status" in str(exc_info.value)
483
+
484
+ def test_validation_response_factory_methods(self):
485
+ """Test ValidationResponse factory methods."""
486
+ # Success validation
487
+ validation_result = ValidationResult(
488
+ is_valid=True,
489
+ status=ValidationStatus.VALID,
490
+ field_name="username",
491
+ value="testuser",
492
+ )
493
+
494
+ success_response = ValidationResponse.create_validation_success(
495
+ validation_result
496
+ )
497
+
498
+ assert success_response.status == ResponseStatus.SUCCESS
499
+ assert success_response.message == "Validation completed successfully"
500
+ assert success_response.validation_result == validation_result
501
+
502
+ # Error validation
503
+ validation_result = ValidationResult(
504
+ is_valid=False,
505
+ status=ValidationStatus.INVALID,
506
+ field_name="password",
507
+ value="",
508
+ error_code=400,
509
+ error_message="Password cannot be empty",
510
+ )
511
+
512
+ field_errors = {"password": ["Required field"]}
513
+ error_response = ValidationResponse.create_validation_error(
514
+ validation_result, field_errors
515
+ )
516
+
517
+ assert error_response.status == ResponseStatus.ERROR
518
+ assert error_response.message == "Validation failed"
519
+ assert error_response.validation_result == validation_result
520
+ assert error_response.field_errors == field_errors
521
+
522
+
523
+ class TestAuthResponse:
524
+ """Test suite for AuthResponse class."""
525
+
526
+ def test_auth_response_success(self):
527
+ """Test AuthResponse with successful authentication."""
528
+ auth_result = AuthResult(
529
+ is_valid=True,
530
+ status=AuthStatus.SUCCESS,
531
+ username="testuser",
532
+ roles=["user", "admin"],
533
+ permissions={"read", "write"},
534
+ auth_method=AuthMethod.API_KEY,
535
+ )
536
+
537
+ auth_response = AuthResponse(
538
+ status=ResponseStatus.SUCCESS,
539
+ message="Authentication successful",
540
+ auth_result=auth_result,
541
+ user_info={"email": "test@example.com"},
542
+ session_info={"session_id": "sess-123"},
543
+ token_info={"expires_in": 3600},
544
+ )
545
+
546
+ assert auth_response.status == ResponseStatus.SUCCESS
547
+ assert auth_response.message == "Authentication successful"
548
+ assert auth_response.auth_result == auth_result
549
+ assert auth_response.user_info == {"email": "test@example.com"}
550
+ assert auth_response.session_info == {"session_id": "sess-123"}
551
+ assert auth_response.token_info == {"expires_in": 3600}
552
+
553
+ def test_auth_response_error(self):
554
+ """Test AuthResponse with authentication error."""
555
+ auth_result = AuthResult(
556
+ is_valid=False,
557
+ status=AuthStatus.FAILED,
558
+ error_code=401,
559
+ error_message="Invalid credentials",
560
+ )
561
+
562
+ auth_response = AuthResponse(
563
+ status=ResponseStatus.ERROR,
564
+ message="Authentication failed",
565
+ auth_result=auth_result,
566
+ )
567
+
568
+ assert auth_response.status == ResponseStatus.ERROR
569
+ assert auth_response.message == "Authentication failed"
570
+ assert auth_response.auth_result == auth_result
571
+ assert auth_response.user_info == {}
572
+ assert auth_response.session_info == {}
573
+ assert auth_response.token_info == {}
574
+
575
+ def test_auth_response_status_validation(self):
576
+ """Test AuthResponse status validation."""
577
+ # Successful auth should have SUCCESS status
578
+ auth_result = AuthResult(is_valid=True, status=AuthStatus.SUCCESS)
579
+
580
+ with pytest.raises(ValidationError) as exc_info:
581
+ AuthResponse(
582
+ status=ResponseStatus.ERROR, # Should be SUCCESS
583
+ message="Authentication failed",
584
+ auth_result=auth_result,
585
+ )
586
+
587
+ assert "Successful authentication must have SUCCESS status" in str(
588
+ exc_info.value
589
+ )
590
+
591
+ # Failed auth should have ERROR status
592
+ auth_result = AuthResult(is_valid=False, status=AuthStatus.FAILED)
593
+
594
+ with pytest.raises(ValidationError) as exc_info:
595
+ AuthResponse(
596
+ status=ResponseStatus.SUCCESS, # Should be ERROR
597
+ message="Authentication succeeded",
598
+ auth_result=auth_result,
599
+ )
600
+
601
+ assert "Failed authentication must have ERROR status" in str(exc_info.value)
602
+
603
+ def test_auth_response_factory_methods(self):
604
+ """Test AuthResponse factory methods."""
605
+ # Success authentication
606
+ auth_result = AuthResult(
607
+ is_valid=True,
608
+ status=AuthStatus.SUCCESS,
609
+ username="testuser",
610
+ roles=["user"],
611
+ permissions={"read"},
612
+ )
613
+
614
+ user_info = {"email": "test@example.com"}
615
+ success_response = AuthResponse.create_auth_success(auth_result, user_info)
616
+
617
+ assert success_response.status == ResponseStatus.SUCCESS
618
+ assert success_response.message == "Authentication successful"
619
+ assert success_response.auth_result == auth_result
620
+ assert success_response.user_info == user_info
621
+
622
+ # Error authentication
623
+ auth_result = AuthResult(
624
+ is_valid=False,
625
+ status=AuthStatus.FAILED,
626
+ error_code=401,
627
+ error_message="Invalid credentials",
628
+ )
629
+
630
+ error_response = AuthResponse.create_auth_error(auth_result)
631
+
632
+ assert error_response.status == ResponseStatus.ERROR
633
+ assert error_response.message == "Invalid credentials"
634
+ assert error_response.auth_result == auth_result
635
+
636
+
637
+ class TestCertificateResponse:
638
+ """Test suite for CertificateResponse class."""
639
+
640
+ def test_certificate_response_success(self):
641
+ """Test CertificateResponse with successful certificate info."""
642
+ cert_info = CertificateInfo(
643
+ subject={"CN": "test.example.com"},
644
+ issuer={"CN": "Test CA"},
645
+ serial_number="123456789",
646
+ not_before=datetime.now(timezone.utc),
647
+ not_after=datetime.now(timezone.utc) + timedelta(days=365),
648
+ certificate_type=CertificateType.SERVER,
649
+ key_size=2048,
650
+ signature_algorithm="sha256WithRSAEncryption",
651
+ )
652
+
653
+ cert_response = CertificateResponse(
654
+ status=ResponseStatus.SUCCESS,
655
+ message="Certificate information retrieved successfully",
656
+ certificate_info=cert_info,
657
+ chain_info={"length": 2},
658
+ expiry_info={"days_remaining": 300},
659
+ )
660
+
661
+ assert cert_response.status == ResponseStatus.SUCCESS
662
+ assert cert_response.message == "Certificate information retrieved successfully"
663
+ assert cert_response.certificate_info == cert_info
664
+ assert cert_response.validation_result is None
665
+ assert cert_response.chain_info == {"length": 2}
666
+ assert cert_response.expiry_info == {"days_remaining": 300}
667
+
668
+ def test_certificate_response_with_validation(self):
669
+ """Test CertificateResponse with validation result."""
670
+ cert_info = CertificateInfo(
671
+ subject={"CN": "test.example.com"},
672
+ issuer={"CN": "Test CA"},
673
+ serial_number="123456789",
674
+ not_before=datetime.now(timezone.utc),
675
+ not_after=datetime.now(timezone.utc) + timedelta(days=365),
676
+ certificate_type=CertificateType.SERVER,
677
+ key_size=2048,
678
+ signature_algorithm="sha256WithRSAEncryption",
679
+ )
680
+
681
+ validation_result = ValidationResult(
682
+ is_valid=True,
683
+ status=ValidationStatus.VALID,
684
+ field_name="certificate",
685
+ value=cert_info,
686
+ )
687
+
688
+ cert_response = CertificateResponse(
689
+ status=ResponseStatus.SUCCESS,
690
+ message="Certificate validated successfully",
691
+ certificate_info=cert_info,
692
+ validation_result=validation_result,
693
+ )
694
+
695
+ assert cert_response.validation_result == validation_result
696
+
697
+ def test_certificate_response_validation_consistency(self):
698
+ """Test CertificateResponse validation consistency."""
699
+ cert_info = CertificateInfo(
700
+ subject={"CN": "test.example.com"},
701
+ issuer={"CN": "Test CA"},
702
+ serial_number="123456789",
703
+ not_before=datetime.now(timezone.utc),
704
+ not_after=datetime.now(timezone.utc) + timedelta(days=365),
705
+ certificate_type=CertificateType.SERVER,
706
+ key_size=2048,
707
+ signature_algorithm="sha256WithRSAEncryption",
708
+ )
709
+
710
+ validation_result = ValidationResult(
711
+ is_valid=False,
712
+ status=ValidationStatus.INVALID,
713
+ field_name="certificate",
714
+ value=cert_info,
715
+ )
716
+
717
+ # Invalid validation cannot have SUCCESS status
718
+ with pytest.raises(ValidationError) as exc_info:
719
+ CertificateResponse(
720
+ status=ResponseStatus.SUCCESS, # Should be ERROR
721
+ message="Certificate validated successfully",
722
+ certificate_info=cert_info,
723
+ validation_result=validation_result,
724
+ )
725
+
726
+ assert "Invalid certificate validation cannot have SUCCESS status" in str(
727
+ exc_info.value
728
+ )
729
+
730
+ def test_certificate_response_factory_method(self):
731
+ """Test CertificateResponse factory method."""
732
+ cert_info = CertificateInfo(
733
+ subject={"CN": "test.example.com"},
734
+ issuer={"CN": "Test CA"},
735
+ serial_number="123456789",
736
+ not_before=datetime.now(timezone.utc),
737
+ not_after=datetime.now(timezone.utc) + timedelta(days=365),
738
+ certificate_type=CertificateType.SERVER,
739
+ key_size=2048,
740
+ signature_algorithm="sha256WithRSAEncryption",
741
+ )
742
+
743
+ validation_result = ValidationResult(
744
+ is_valid=True,
745
+ status=ValidationStatus.VALID,
746
+ field_name="certificate",
747
+ value=cert_info,
748
+ )
749
+
750
+ success_response = CertificateResponse.create_certificate_success(
751
+ cert_info, validation_result
752
+ )
753
+
754
+ assert success_response.status == ResponseStatus.SUCCESS
755
+ assert (
756
+ success_response.message == "Certificate information retrieved successfully"
757
+ )
758
+ assert success_response.certificate_info == cert_info
759
+ assert success_response.validation_result == validation_result
760
+
761
+
762
+ class TestPermissionResponse:
763
+ """Test suite for PermissionResponse class."""
764
+
765
+ def test_permission_response_granted(self):
766
+ """Test PermissionResponse with granted access."""
767
+ user_roles = ["user", "admin"]
768
+ user_permissions = {"read", "write", "delete"}
769
+ required_permissions = ["read", "write"]
770
+
771
+ perm_response = PermissionResponse(
772
+ status=ResponseStatus.SUCCESS,
773
+ message="Access granted",
774
+ user_roles=user_roles,
775
+ user_permissions=user_permissions,
776
+ effective_permissions=user_permissions,
777
+ access_granted=True,
778
+ required_permissions=required_permissions,
779
+ missing_permissions=[],
780
+ )
781
+
782
+ assert perm_response.status == ResponseStatus.SUCCESS
783
+ assert perm_response.message == "Access granted"
784
+ assert perm_response.user_roles == user_roles
785
+ assert perm_response.user_permissions == user_permissions
786
+ assert perm_response.effective_permissions == user_permissions
787
+ assert perm_response.access_granted is True
788
+ assert perm_response.required_permissions == required_permissions
789
+ assert perm_response.missing_permissions == []
790
+
791
+ def test_permission_response_denied(self):
792
+ """Test PermissionResponse with denied access."""
793
+ user_roles = ["user"]
794
+ user_permissions = {"read"}
795
+ required_permissions = ["read", "write", "delete"]
796
+ missing_permissions = ["write", "delete"]
797
+
798
+ perm_response = PermissionResponse(
799
+ status=ResponseStatus.ERROR,
800
+ message="Access denied - insufficient permissions",
801
+ user_roles=user_roles,
802
+ user_permissions=user_permissions,
803
+ effective_permissions=user_permissions,
804
+ access_granted=False,
805
+ required_permissions=required_permissions,
806
+ missing_permissions=missing_permissions,
807
+ )
808
+
809
+ assert perm_response.status == ResponseStatus.ERROR
810
+ assert perm_response.message == "Access denied - insufficient permissions"
811
+ assert perm_response.user_roles == user_roles
812
+ assert perm_response.user_permissions == user_permissions
813
+ assert perm_response.effective_permissions == user_permissions
814
+ assert perm_response.access_granted is False
815
+ assert perm_response.required_permissions == required_permissions
816
+ assert perm_response.missing_permissions == missing_permissions
817
+
818
+ def test_permission_response_validation_consistency(self):
819
+ """Test PermissionResponse validation consistency."""
820
+ user_roles = ["user"]
821
+ user_permissions = {"read"}
822
+ required_permissions = ["read", "write"]
823
+
824
+ # Granted access should have SUCCESS status
825
+ with pytest.raises(ValidationError) as exc_info:
826
+ PermissionResponse(
827
+ status=ResponseStatus.ERROR, # Should be SUCCESS
828
+ message="Access granted",
829
+ user_roles=user_roles,
830
+ user_permissions=user_permissions,
831
+ effective_permissions=user_permissions,
832
+ access_granted=True,
833
+ required_permissions=required_permissions,
834
+ missing_permissions=[],
835
+ )
836
+
837
+ assert "Granted access must have SUCCESS status" in str(exc_info.value)
838
+
839
+ # Denied access should have ERROR status
840
+ with pytest.raises(ValidationError) as exc_info:
841
+ PermissionResponse(
842
+ status=ResponseStatus.SUCCESS, # Should be ERROR
843
+ message="Access denied",
844
+ user_roles=user_roles,
845
+ user_permissions=user_permissions,
846
+ effective_permissions=user_permissions,
847
+ access_granted=False,
848
+ required_permissions=required_permissions,
849
+ missing_permissions=["write"],
850
+ )
851
+
852
+ assert "Denied access must have ERROR status" in str(exc_info.value)
853
+
854
+ def test_permission_response_factory_methods(self):
855
+ """Test PermissionResponse factory methods."""
856
+ user_roles = ["user", "admin"]
857
+ user_permissions = {"read", "write", "delete"}
858
+ required_permissions = ["read", "write"]
859
+
860
+ # Granted access
861
+ granted_response = PermissionResponse.create_permission_granted(
862
+ user_roles, user_permissions, required_permissions
863
+ )
864
+
865
+ assert granted_response.status == ResponseStatus.SUCCESS
866
+ assert granted_response.message == "Access granted"
867
+ assert granted_response.user_roles == user_roles
868
+ assert granted_response.user_permissions == user_permissions
869
+ assert granted_response.effective_permissions == user_permissions
870
+ assert granted_response.access_granted is True
871
+ assert granted_response.required_permissions == required_permissions
872
+ assert granted_response.missing_permissions == []
873
+
874
+ # Denied access
875
+ denied_response = PermissionResponse.create_permission_denied(
876
+ user_roles, user_permissions, required_permissions
877
+ )
878
+
879
+ assert denied_response.status == ResponseStatus.ERROR
880
+ assert denied_response.message == "Access denied - insufficient permissions"
881
+ assert denied_response.user_roles == user_roles
882
+ assert denied_response.user_permissions == user_permissions
883
+ assert denied_response.effective_permissions == user_permissions
884
+ assert denied_response.access_granted is False
885
+ assert denied_response.required_permissions == required_permissions
886
+ assert (
887
+ denied_response.missing_permissions == []
888
+ ) # User has all required permissions
889
+
890
+
891
+ class TestRateLimitResponse:
892
+ """Test suite for RateLimitResponse class."""
893
+
894
+ def test_rate_limit_response_within_limit(self):
895
+ """Test RateLimitResponse when within rate limit."""
896
+ now = datetime.now(timezone.utc)
897
+ rate_limit_status = RateLimitStatus(
898
+ identifier="192.168.1.1",
899
+ current_count=5,
900
+ limit=10,
901
+ window_start=now,
902
+ window_end=now + timedelta(minutes=1),
903
+ is_exceeded=False,
904
+ remaining_requests=5,
905
+ reset_time=now + timedelta(minutes=1),
906
+ window_size_seconds=60,
907
+ )
908
+
909
+ rate_response = RateLimitResponse(
910
+ status=ResponseStatus.SUCCESS,
911
+ message="Rate limit status",
912
+ rate_limit_status=rate_limit_status,
913
+ usage_percentage=50.0,
914
+ reset_time=now + timedelta(minutes=1),
915
+ )
916
+
917
+ assert rate_response.status == ResponseStatus.SUCCESS
918
+ assert rate_response.message == "Rate limit status"
919
+ assert rate_response.rate_limit_status == rate_limit_status
920
+ assert rate_response.usage_percentage == 50.0
921
+ assert rate_response.reset_time == now + timedelta(minutes=1)
922
+ assert rate_response.retry_after is None
923
+
924
+ def test_rate_limit_response_exceeded(self):
925
+ """Test RateLimitResponse when rate limit is exceeded."""
926
+ now = datetime.now(timezone.utc)
927
+ rate_limit_status = RateLimitStatus(
928
+ identifier="192.168.1.1",
929
+ current_count=15,
930
+ limit=10,
931
+ window_start=now,
932
+ window_end=now + timedelta(minutes=1),
933
+ is_exceeded=True,
934
+ remaining_requests=0,
935
+ reset_time=now + timedelta(minutes=1),
936
+ window_size_seconds=60,
937
+ )
938
+
939
+ rate_response = RateLimitResponse(
940
+ status=ResponseStatus.ERROR,
941
+ message="Rate limit exceeded",
942
+ rate_limit_status=rate_limit_status,
943
+ usage_percentage=150.0,
944
+ reset_time=now + timedelta(minutes=1),
945
+ retry_after=60,
946
+ )
947
+
948
+ assert rate_response.status == ResponseStatus.ERROR
949
+ assert rate_response.message == "Rate limit exceeded"
950
+ assert rate_response.rate_limit_status == rate_limit_status
951
+ assert rate_response.usage_percentage == 150.0
952
+ assert rate_response.reset_time == now + timedelta(minutes=1)
953
+ assert rate_response.retry_after == 60
954
+
955
+ def test_rate_limit_response_validation_consistency(self):
956
+ """Test RateLimitResponse validation consistency."""
957
+ now = datetime.now(timezone.utc)
958
+
959
+ # Exceeded rate limit should have ERROR status
960
+ rate_limit_status = RateLimitStatus(
961
+ identifier="192.168.1.1",
962
+ current_count=15,
963
+ limit=10,
964
+ window_start=now,
965
+ window_end=now + timedelta(minutes=1),
966
+ is_exceeded=True,
967
+ remaining_requests=0,
968
+ reset_time=now + timedelta(minutes=1),
969
+ window_size_seconds=60,
970
+ )
971
+
972
+ with pytest.raises(ValidationError) as exc_info:
973
+ RateLimitResponse(
974
+ status=ResponseStatus.SUCCESS, # Should be ERROR
975
+ message="Rate limit status",
976
+ rate_limit_status=rate_limit_status,
977
+ usage_percentage=150.0,
978
+ reset_time=now + timedelta(minutes=1),
979
+ )
980
+
981
+ assert "Exceeded rate limit must have ERROR status" in str(exc_info.value)
982
+
983
+ # Within rate limit should have SUCCESS status
984
+ rate_limit_status = RateLimitStatus(
985
+ identifier="192.168.1.1",
986
+ current_count=5,
987
+ limit=10,
988
+ window_start=now,
989
+ window_end=now + timedelta(minutes=1),
990
+ is_exceeded=False,
991
+ remaining_requests=5,
992
+ reset_time=now + timedelta(minutes=1),
993
+ window_size_seconds=60,
994
+ )
995
+
996
+ with pytest.raises(ValidationError) as exc_info:
997
+ RateLimitResponse(
998
+ status=ResponseStatus.ERROR, # Should be SUCCESS
999
+ message="Rate limit exceeded",
1000
+ rate_limit_status=rate_limit_status,
1001
+ usage_percentage=50.0,
1002
+ reset_time=now + timedelta(minutes=1),
1003
+ )
1004
+
1005
+ assert "Within rate limit must have SUCCESS status" in str(exc_info.value)
1006
+
1007
+ def test_rate_limit_response_factory_method(self):
1008
+ """Test RateLimitResponse factory method."""
1009
+ now = datetime.now(timezone.utc)
1010
+
1011
+ # Within limit
1012
+ rate_limit_status = RateLimitStatus(
1013
+ identifier="192.168.1.1",
1014
+ current_count=5,
1015
+ limit=10,
1016
+ window_start=now,
1017
+ window_end=now + timedelta(minutes=1),
1018
+ is_exceeded=False,
1019
+ remaining_requests=5,
1020
+ reset_time=now + timedelta(minutes=1),
1021
+ window_size_seconds=60,
1022
+ )
1023
+
1024
+ response = RateLimitResponse.create_rate_limit_status(rate_limit_status)
1025
+
1026
+ assert response.status == ResponseStatus.SUCCESS
1027
+ assert response.message == "Rate limit status"
1028
+ assert response.rate_limit_status == rate_limit_status
1029
+ assert response.usage_percentage == 50.0
1030
+ assert response.reset_time == now + timedelta(minutes=1)
1031
+ assert response.retry_after is None
1032
+
1033
+ # Exceeded limit
1034
+ rate_limit_status = RateLimitStatus(
1035
+ identifier="192.168.1.1",
1036
+ current_count=15,
1037
+ limit=10,
1038
+ window_start=now,
1039
+ window_end=now + timedelta(minutes=1),
1040
+ is_exceeded=True,
1041
+ remaining_requests=0,
1042
+ reset_time=now + timedelta(minutes=1),
1043
+ window_size_seconds=60,
1044
+ )
1045
+
1046
+ response = RateLimitResponse.create_rate_limit_status(rate_limit_status)
1047
+
1048
+ assert response.status == ResponseStatus.ERROR
1049
+ assert response.message == "Rate limit exceeded"
1050
+ assert response.rate_limit_status == rate_limit_status
1051
+ assert response.usage_percentage == 150.0
1052
+ assert response.reset_time == now + timedelta(minutes=1)
1053
+ assert response.retry_after is not None
1054
+ assert response.retry_after >= 59 # Allow for small time differences