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.
- mcp_security_framework/__init__.py +96 -0
- mcp_security_framework/cli/__init__.py +18 -0
- mcp_security_framework/cli/cert_cli.py +511 -0
- mcp_security_framework/cli/security_cli.py +791 -0
- mcp_security_framework/constants.py +209 -0
- mcp_security_framework/core/__init__.py +61 -0
- mcp_security_framework/core/auth_manager.py +1011 -0
- mcp_security_framework/core/cert_manager.py +1663 -0
- mcp_security_framework/core/permission_manager.py +735 -0
- mcp_security_framework/core/rate_limiter.py +602 -0
- mcp_security_framework/core/security_manager.py +943 -0
- mcp_security_framework/core/ssl_manager.py +735 -0
- mcp_security_framework/examples/__init__.py +75 -0
- mcp_security_framework/examples/django_example.py +615 -0
- mcp_security_framework/examples/fastapi_example.py +472 -0
- mcp_security_framework/examples/flask_example.py +506 -0
- mcp_security_framework/examples/gateway_example.py +803 -0
- mcp_security_framework/examples/microservice_example.py +690 -0
- mcp_security_framework/examples/standalone_example.py +576 -0
- mcp_security_framework/middleware/__init__.py +250 -0
- mcp_security_framework/middleware/auth_middleware.py +292 -0
- mcp_security_framework/middleware/fastapi_auth_middleware.py +447 -0
- mcp_security_framework/middleware/fastapi_middleware.py +757 -0
- mcp_security_framework/middleware/flask_auth_middleware.py +465 -0
- mcp_security_framework/middleware/flask_middleware.py +591 -0
- mcp_security_framework/middleware/mtls_middleware.py +439 -0
- mcp_security_framework/middleware/rate_limit_middleware.py +403 -0
- mcp_security_framework/middleware/security_middleware.py +507 -0
- mcp_security_framework/schemas/__init__.py +109 -0
- mcp_security_framework/schemas/config.py +694 -0
- mcp_security_framework/schemas/models.py +709 -0
- mcp_security_framework/schemas/responses.py +686 -0
- mcp_security_framework/tests/__init__.py +0 -0
- mcp_security_framework/utils/__init__.py +121 -0
- mcp_security_framework/utils/cert_utils.py +525 -0
- mcp_security_framework/utils/crypto_utils.py +475 -0
- mcp_security_framework/utils/validation_utils.py +571 -0
- mcp_security_framework-0.1.0.dist-info/METADATA +411 -0
- mcp_security_framework-0.1.0.dist-info/RECORD +76 -0
- mcp_security_framework-0.1.0.dist-info/WHEEL +5 -0
- mcp_security_framework-0.1.0.dist-info/entry_points.txt +3 -0
- mcp_security_framework-0.1.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/test_cli/__init__.py +0 -0
- tests/test_cli/test_cert_cli.py +379 -0
- tests/test_cli/test_security_cli.py +657 -0
- tests/test_core/__init__.py +0 -0
- tests/test_core/test_auth_manager.py +582 -0
- tests/test_core/test_cert_manager.py +795 -0
- tests/test_core/test_permission_manager.py +395 -0
- tests/test_core/test_rate_limiter.py +626 -0
- tests/test_core/test_security_manager.py +841 -0
- tests/test_core/test_ssl_manager.py +532 -0
- tests/test_examples/__init__.py +8 -0
- tests/test_examples/test_fastapi_example.py +264 -0
- tests/test_examples/test_flask_example.py +238 -0
- tests/test_examples/test_standalone_example.py +292 -0
- tests/test_integration/__init__.py +0 -0
- tests/test_integration/test_auth_flow.py +502 -0
- tests/test_integration/test_certificate_flow.py +527 -0
- tests/test_integration/test_fastapi_integration.py +341 -0
- tests/test_integration/test_flask_integration.py +398 -0
- tests/test_integration/test_standalone_integration.py +493 -0
- tests/test_middleware/__init__.py +0 -0
- tests/test_middleware/test_fastapi_middleware.py +523 -0
- tests/test_middleware/test_flask_middleware.py +582 -0
- tests/test_middleware/test_security_middleware.py +493 -0
- tests/test_schemas/__init__.py +0 -0
- tests/test_schemas/test_config.py +811 -0
- tests/test_schemas/test_models.py +879 -0
- tests/test_schemas/test_responses.py +1054 -0
- tests/test_schemas/test_serialization.py +493 -0
- tests/test_utils/__init__.py +0 -0
- tests/test_utils/test_cert_utils.py +510 -0
- tests/test_utils/test_crypto_utils.py +603 -0
- tests/test_utils/test_validation_utils.py +477 -0
@@ -0,0 +1,493 @@
|
|
1
|
+
"""
|
2
|
+
Serialization Tests Module
|
3
|
+
|
4
|
+
This module provides comprehensive tests for serialization and deserialization
|
5
|
+
of all data models in the MCP Security Framework. It tests JSON serialization,
|
6
|
+
model validation, and data consistency.
|
7
|
+
|
8
|
+
Test Classes:
|
9
|
+
TestConfigSerialization: Tests for configuration model serialization
|
10
|
+
TestModelSerialization: Tests for data model serialization
|
11
|
+
TestResponseSerialization: Tests for response model serialization
|
12
|
+
|
13
|
+
Author: MCP Security Team
|
14
|
+
Version: 1.0.0
|
15
|
+
License: MIT
|
16
|
+
"""
|
17
|
+
|
18
|
+
from datetime import datetime, timezone
|
19
|
+
from typing import Any, Dict
|
20
|
+
|
21
|
+
from mcp_security_framework.schemas.config import (
|
22
|
+
AuthConfig,
|
23
|
+
AuthMethod,
|
24
|
+
CertificateConfig,
|
25
|
+
LoggingConfig,
|
26
|
+
LogLevel,
|
27
|
+
PermissionConfig,
|
28
|
+
RateLimitConfig,
|
29
|
+
SecurityConfig,
|
30
|
+
SSLConfig,
|
31
|
+
TLSVersion,
|
32
|
+
)
|
33
|
+
from mcp_security_framework.schemas.models import (
|
34
|
+
AuthResult,
|
35
|
+
AuthStatus,
|
36
|
+
CertificateInfo,
|
37
|
+
CertificateType,
|
38
|
+
RateLimitStatus,
|
39
|
+
ValidationResult,
|
40
|
+
ValidationStatus,
|
41
|
+
)
|
42
|
+
from mcp_security_framework.schemas.responses import (
|
43
|
+
ErrorCode,
|
44
|
+
ErrorResponse,
|
45
|
+
ResponseStatus,
|
46
|
+
SecurityResponse,
|
47
|
+
SuccessResponse,
|
48
|
+
)
|
49
|
+
|
50
|
+
|
51
|
+
class TestConfigSerialization:
|
52
|
+
"""Test suite for configuration model serialization."""
|
53
|
+
|
54
|
+
def test_ssl_config_serialization(self):
|
55
|
+
"""Test SSLConfig serialization and deserialization."""
|
56
|
+
config = SSLConfig(
|
57
|
+
enabled=False, # Disabled to avoid file validation
|
58
|
+
cert_file=None,
|
59
|
+
key_file=None,
|
60
|
+
ca_cert_file=None,
|
61
|
+
verify_mode="CERT_REQUIRED",
|
62
|
+
min_tls_version=TLSVersion.TLS_1_2,
|
63
|
+
max_tls_version=TLSVersion.TLS_1_3,
|
64
|
+
cipher_suite="ECDHE-RSA-AES256-GCM-SHA384",
|
65
|
+
check_hostname=True,
|
66
|
+
check_expiry=True,
|
67
|
+
expiry_warning_days=30,
|
68
|
+
)
|
69
|
+
|
70
|
+
# Test model_dump
|
71
|
+
data = config.model_dump()
|
72
|
+
assert data["enabled"] is False
|
73
|
+
assert data["cert_file"] is None
|
74
|
+
assert data["key_file"] is None
|
75
|
+
assert data["verify_mode"] == "CERT_REQUIRED"
|
76
|
+
assert data["min_tls_version"] == "TLSv1.2"
|
77
|
+
assert data["max_tls_version"] == "TLSv1.3"
|
78
|
+
|
79
|
+
# Test model_dump_json
|
80
|
+
json_str = config.model_dump_json()
|
81
|
+
assert isinstance(json_str, str)
|
82
|
+
assert "TLSv1.2" in json_str
|
83
|
+
assert "CERT_REQUIRED" in json_str
|
84
|
+
|
85
|
+
# Test deserialization
|
86
|
+
parsed_config = SSLConfig.model_validate_json(json_str)
|
87
|
+
assert parsed_config.enabled == config.enabled
|
88
|
+
assert parsed_config.cert_file == config.cert_file
|
89
|
+
assert parsed_config.min_tls_version == config.min_tls_version
|
90
|
+
|
91
|
+
def test_auth_config_serialization(self):
|
92
|
+
"""Test AuthConfig serialization and deserialization."""
|
93
|
+
config = AuthConfig(
|
94
|
+
enabled=True,
|
95
|
+
methods=[AuthMethod.API_KEY, AuthMethod.JWT],
|
96
|
+
api_keys={"admin": "admin_key_123", "user": "user_key_456"},
|
97
|
+
jwt_secret="secret_key",
|
98
|
+
jwt_expiry_hours=24,
|
99
|
+
jwt_algorithm="HS256",
|
100
|
+
public_paths=["/docs", "/health"],
|
101
|
+
require_auth_for_all=True,
|
102
|
+
default_role="guest",
|
103
|
+
)
|
104
|
+
|
105
|
+
# Test model_dump
|
106
|
+
data = config.model_dump()
|
107
|
+
assert data["enabled"] is True
|
108
|
+
assert data["methods"] == ["api_key", "jwt"]
|
109
|
+
assert data["api_keys"]["admin"] == "admin_key_123"
|
110
|
+
assert data["jwt_algorithm"] == "HS256"
|
111
|
+
|
112
|
+
# Test model_dump_json
|
113
|
+
json_str = config.model_dump_json()
|
114
|
+
assert isinstance(json_str, str)
|
115
|
+
assert "admin_key_123" in json_str
|
116
|
+
assert "api_key" in json_str
|
117
|
+
|
118
|
+
# Test deserialization
|
119
|
+
parsed_config = AuthConfig.model_validate_json(json_str)
|
120
|
+
assert parsed_config.enabled == config.enabled
|
121
|
+
assert parsed_config.methods == config.methods
|
122
|
+
assert parsed_config.api_keys == config.api_keys
|
123
|
+
|
124
|
+
def test_security_config_serialization(self):
|
125
|
+
"""Test SecurityConfig serialization and deserialization."""
|
126
|
+
config = SecurityConfig(
|
127
|
+
ssl=SSLConfig(enabled=False), # Disabled to avoid file validation
|
128
|
+
auth=AuthConfig(enabled=True, methods=[AuthMethod.API_KEY]),
|
129
|
+
certificates=CertificateConfig(cert_storage_path="./certs"),
|
130
|
+
permissions=PermissionConfig(
|
131
|
+
roles_file=None
|
132
|
+
), # Set to None to avoid file validation
|
133
|
+
rate_limit=RateLimitConfig(enabled=True, default_requests_per_minute=100),
|
134
|
+
logging=LoggingConfig(level=LogLevel.INFO),
|
135
|
+
)
|
136
|
+
|
137
|
+
# Test model_dump
|
138
|
+
data = config.model_dump()
|
139
|
+
assert data["ssl"]["enabled"] is False
|
140
|
+
assert data["auth"]["enabled"] is True
|
141
|
+
assert data["certificates"]["cert_storage_path"] == "./certs"
|
142
|
+
assert data["permissions"]["roles_file"] is None
|
143
|
+
assert data["rate_limit"]["default_requests_per_minute"] == 100
|
144
|
+
|
145
|
+
# Test model_dump_json
|
146
|
+
json_str = config.model_dump_json()
|
147
|
+
assert isinstance(json_str, str)
|
148
|
+
|
149
|
+
# Test deserialization
|
150
|
+
parsed_config = SecurityConfig.model_validate_json(json_str)
|
151
|
+
assert parsed_config.ssl.enabled == config.ssl.enabled
|
152
|
+
assert parsed_config.auth.enabled == config.auth.enabled
|
153
|
+
assert (
|
154
|
+
parsed_config.certificates.cert_storage_path
|
155
|
+
== config.certificates.cert_storage_path
|
156
|
+
)
|
157
|
+
|
158
|
+
|
159
|
+
class TestModelSerialization:
|
160
|
+
"""Test suite for data model serialization."""
|
161
|
+
|
162
|
+
def test_auth_result_serialization(self):
|
163
|
+
"""Test AuthResult serialization and deserialization."""
|
164
|
+
auth_result = AuthResult(
|
165
|
+
is_valid=True,
|
166
|
+
status=AuthStatus.SUCCESS,
|
167
|
+
username="test_user",
|
168
|
+
user_id="user_123",
|
169
|
+
roles=["admin", "user"],
|
170
|
+
permissions={"read", "write", "delete"},
|
171
|
+
auth_method=AuthMethod.API_KEY,
|
172
|
+
auth_timestamp=datetime.now(timezone.utc),
|
173
|
+
error_code=None,
|
174
|
+
error_message=None,
|
175
|
+
metadata={"ip": "192.168.1.1", "user_agent": "test-agent"},
|
176
|
+
)
|
177
|
+
|
178
|
+
# Test model_dump
|
179
|
+
data = auth_result.model_dump()
|
180
|
+
assert data["is_valid"] is True
|
181
|
+
assert data["status"] == "success"
|
182
|
+
assert data["username"] == "test_user"
|
183
|
+
assert data["roles"] == ["admin", "user"]
|
184
|
+
assert data["auth_method"] == "api_key"
|
185
|
+
assert "ip" in data["metadata"]
|
186
|
+
|
187
|
+
# Test model_dump_json
|
188
|
+
json_str = auth_result.model_dump_json()
|
189
|
+
assert isinstance(json_str, str)
|
190
|
+
assert "test_user" in json_str
|
191
|
+
assert "admin" in json_str
|
192
|
+
|
193
|
+
# Test deserialization
|
194
|
+
parsed_result = AuthResult.model_validate_json(json_str)
|
195
|
+
assert parsed_result.is_valid == auth_result.is_valid
|
196
|
+
assert parsed_result.status == auth_result.status
|
197
|
+
assert parsed_result.username == auth_result.username
|
198
|
+
assert parsed_result.roles == auth_result.roles
|
199
|
+
|
200
|
+
def test_validation_result_serialization(self):
|
201
|
+
"""Test ValidationResult serialization and deserialization."""
|
202
|
+
validation_result = ValidationResult(
|
203
|
+
is_valid=True,
|
204
|
+
status=ValidationStatus.VALID,
|
205
|
+
field_name="permissions",
|
206
|
+
value=["read", "write"],
|
207
|
+
error_code=None,
|
208
|
+
error_message=None,
|
209
|
+
warnings=[],
|
210
|
+
metadata={"resource": "/api/data", "action": "GET"},
|
211
|
+
)
|
212
|
+
|
213
|
+
# Test model_dump
|
214
|
+
data = validation_result.model_dump()
|
215
|
+
assert data["is_valid"] is True
|
216
|
+
assert data["status"] == "valid"
|
217
|
+
assert data["field_name"] == "permissions"
|
218
|
+
assert data["value"] == ["read", "write"]
|
219
|
+
assert data["metadata"]["resource"] == "/api/data"
|
220
|
+
|
221
|
+
# Test model_dump_json
|
222
|
+
json_str = validation_result.model_dump_json()
|
223
|
+
assert isinstance(json_str, str)
|
224
|
+
assert "read" in json_str
|
225
|
+
assert "write" in json_str
|
226
|
+
|
227
|
+
# Test deserialization
|
228
|
+
parsed_result = ValidationResult.model_validate_json(json_str)
|
229
|
+
assert parsed_result.is_valid == validation_result.is_valid
|
230
|
+
assert parsed_result.status == validation_result.status
|
231
|
+
assert parsed_result.field_name == validation_result.field_name
|
232
|
+
|
233
|
+
def test_certificate_info_serialization(self):
|
234
|
+
"""Test CertificateInfo serialization and deserialization."""
|
235
|
+
cert_info = CertificateInfo(
|
236
|
+
subject={"CN": "test.example.com", "O": "Test Org"},
|
237
|
+
issuer={"CN": "Test CA", "O": "Test CA Org"},
|
238
|
+
serial_number="1234567890",
|
239
|
+
not_before=datetime.now(timezone.utc),
|
240
|
+
not_after=datetime.now(timezone.utc),
|
241
|
+
certificate_type=CertificateType.SERVER,
|
242
|
+
is_expired=False,
|
243
|
+
is_revoked=False,
|
244
|
+
revocation_reason=None,
|
245
|
+
roles=["server"],
|
246
|
+
permissions={"ssl"},
|
247
|
+
key_usage=["digitalSignature", "keyEncipherment"],
|
248
|
+
extended_key_usage=["serverAuth"],
|
249
|
+
signature_algorithm="sha256WithRSAEncryption",
|
250
|
+
public_key_algorithm="rsaEncryption",
|
251
|
+
key_size=2048,
|
252
|
+
)
|
253
|
+
|
254
|
+
# Test model_dump
|
255
|
+
data = cert_info.model_dump()
|
256
|
+
assert data["subject"]["CN"] == "test.example.com"
|
257
|
+
assert data["issuer"]["CN"] == "Test CA"
|
258
|
+
assert data["serial_number"] == "1234567890"
|
259
|
+
assert data["roles"] == ["server"]
|
260
|
+
assert data["key_size"] == 2048
|
261
|
+
|
262
|
+
# Test model_dump_json
|
263
|
+
json_str = cert_info.model_dump_json()
|
264
|
+
assert isinstance(json_str, str)
|
265
|
+
assert "test.example.com" in json_str
|
266
|
+
assert "Test CA" in json_str
|
267
|
+
|
268
|
+
# Test deserialization
|
269
|
+
parsed_info = CertificateInfo.model_validate_json(json_str)
|
270
|
+
assert parsed_info.subject["CN"] == cert_info.subject["CN"]
|
271
|
+
assert parsed_info.issuer["CN"] == cert_info.issuer["CN"]
|
272
|
+
assert parsed_info.key_size == cert_info.key_size
|
273
|
+
|
274
|
+
def test_rate_limit_status_serialization(self):
|
275
|
+
"""Test RateLimitStatus serialization and deserialization."""
|
276
|
+
now = datetime.now(timezone.utc)
|
277
|
+
rate_limit_status = RateLimitStatus(
|
278
|
+
identifier="user:test_user",
|
279
|
+
current_count=50,
|
280
|
+
limit=100,
|
281
|
+
window_start=now,
|
282
|
+
window_end=now,
|
283
|
+
window_size_seconds=60,
|
284
|
+
is_exceeded=False,
|
285
|
+
remaining_requests=50,
|
286
|
+
reset_time=now,
|
287
|
+
)
|
288
|
+
|
289
|
+
# Test model_dump
|
290
|
+
data = rate_limit_status.model_dump()
|
291
|
+
assert data["identifier"] == "user:test_user"
|
292
|
+
assert data["current_count"] == 50
|
293
|
+
assert data["limit"] == 100
|
294
|
+
assert data["is_exceeded"] is False
|
295
|
+
assert data["remaining_requests"] == 50
|
296
|
+
|
297
|
+
# Test model_dump_json
|
298
|
+
json_str = rate_limit_status.model_dump_json()
|
299
|
+
assert isinstance(json_str, str)
|
300
|
+
assert "test_user" in json_str
|
301
|
+
assert "50" in json_str
|
302
|
+
|
303
|
+
# Test deserialization
|
304
|
+
parsed_status = RateLimitStatus.model_validate_json(json_str)
|
305
|
+
assert parsed_status.identifier == rate_limit_status.identifier
|
306
|
+
assert parsed_status.current_count == rate_limit_status.current_count
|
307
|
+
assert parsed_status.remaining_requests == rate_limit_status.remaining_requests
|
308
|
+
|
309
|
+
|
310
|
+
class TestResponseSerialization:
|
311
|
+
"""Test suite for response model serialization."""
|
312
|
+
|
313
|
+
def test_security_response_serialization(self):
|
314
|
+
"""Test SecurityResponse serialization and deserialization."""
|
315
|
+
response = SecurityResponse[Dict[str, Any]](
|
316
|
+
status=ResponseStatus.SUCCESS,
|
317
|
+
message="Operation completed successfully",
|
318
|
+
data={"result": "success", "count": 5},
|
319
|
+
timestamp=datetime.now(timezone.utc),
|
320
|
+
request_id="req_123",
|
321
|
+
metadata={"version": "1.0.0", "environment": "production"},
|
322
|
+
)
|
323
|
+
|
324
|
+
# Test model_dump
|
325
|
+
data = response.model_dump()
|
326
|
+
assert data["status"] == "success"
|
327
|
+
assert data["message"] == "Operation completed successfully"
|
328
|
+
assert data["data"]["result"] == "success"
|
329
|
+
assert data["data"]["count"] == 5
|
330
|
+
assert data["metadata"]["version"] == "1.0.0"
|
331
|
+
|
332
|
+
# Test model_dump_json
|
333
|
+
json_str = response.model_dump_json()
|
334
|
+
assert isinstance(json_str, str)
|
335
|
+
assert "success" in json_str
|
336
|
+
assert "Operation completed successfully" in json_str
|
337
|
+
|
338
|
+
# Test deserialization
|
339
|
+
parsed_response = SecurityResponse.model_validate_json(json_str)
|
340
|
+
assert parsed_response.status == response.status
|
341
|
+
assert parsed_response.message == response.message
|
342
|
+
assert parsed_response.data["result"] == response.data["result"]
|
343
|
+
|
344
|
+
def test_error_response_serialization(self):
|
345
|
+
"""Test ErrorResponse serialization and deserialization."""
|
346
|
+
error_response = ErrorResponse(
|
347
|
+
status=ResponseStatus.ERROR,
|
348
|
+
message="Authentication failed",
|
349
|
+
error_code=ErrorCode.AUTHENTICATION_FAILED,
|
350
|
+
error_type="AuthenticationError",
|
351
|
+
http_status_code=401,
|
352
|
+
details="Invalid credentials for user test_user",
|
353
|
+
timestamp=datetime.now(timezone.utc),
|
354
|
+
request_id="req_456",
|
355
|
+
)
|
356
|
+
|
357
|
+
# Test model_dump
|
358
|
+
data = error_response.model_dump()
|
359
|
+
assert data["status"] == "error"
|
360
|
+
assert data["message"] == "Authentication failed"
|
361
|
+
assert data["error_code"] == "AUTHENTICATION_FAILED"
|
362
|
+
assert data["http_status_code"] == 401
|
363
|
+
assert "test_user" in data["details"]
|
364
|
+
|
365
|
+
# Test model_dump_json
|
366
|
+
json_str = error_response.model_dump_json()
|
367
|
+
assert isinstance(json_str, str)
|
368
|
+
assert "Authentication failed" in json_str
|
369
|
+
assert "AUTHENTICATION_FAILED" in json_str
|
370
|
+
|
371
|
+
# Test deserialization
|
372
|
+
parsed_response = ErrorResponse.model_validate_json(json_str)
|
373
|
+
assert parsed_response.status == error_response.status
|
374
|
+
assert parsed_response.error_code == error_response.error_code
|
375
|
+
assert parsed_response.http_status_code == error_response.http_status_code
|
376
|
+
|
377
|
+
def test_success_response_serialization(self):
|
378
|
+
"""Test SuccessResponse serialization and deserialization."""
|
379
|
+
success_response = SuccessResponse[Dict[str, Any]](
|
380
|
+
status=ResponseStatus.SUCCESS,
|
381
|
+
message="Data retrieved successfully",
|
382
|
+
data={"items": [{"id": 1, "name": "item1"}, {"id": 2, "name": "item2"}]},
|
383
|
+
timestamp=datetime.now(timezone.utc),
|
384
|
+
request_id="req_789",
|
385
|
+
)
|
386
|
+
|
387
|
+
# Test model_dump
|
388
|
+
data = success_response.model_dump()
|
389
|
+
assert data["status"] == "success"
|
390
|
+
assert data["message"] == "Data retrieved successfully"
|
391
|
+
assert len(data["data"]["items"]) == 2
|
392
|
+
|
393
|
+
# Test model_dump_json
|
394
|
+
json_str = success_response.model_dump_json()
|
395
|
+
assert isinstance(json_str, str)
|
396
|
+
assert "Data retrieved successfully" in json_str
|
397
|
+
assert "item1" in json_str
|
398
|
+
assert "item2" in json_str
|
399
|
+
|
400
|
+
# Test deserialization
|
401
|
+
parsed_response = SuccessResponse.model_validate_json(json_str)
|
402
|
+
assert parsed_response.status == success_response.status
|
403
|
+
assert parsed_response.message == success_response.message
|
404
|
+
assert len(parsed_response.data["items"]) == 2
|
405
|
+
|
406
|
+
|
407
|
+
class TestEdgeCases:
|
408
|
+
"""Test suite for edge cases in serialization."""
|
409
|
+
|
410
|
+
def test_empty_data_serialization(self):
|
411
|
+
"""Test serialization with empty data."""
|
412
|
+
auth_result = AuthResult(
|
413
|
+
is_valid=False,
|
414
|
+
status=AuthStatus.FAILED,
|
415
|
+
username=None,
|
416
|
+
user_id=None,
|
417
|
+
roles=[],
|
418
|
+
permissions=set(),
|
419
|
+
auth_method=None,
|
420
|
+
auth_timestamp=datetime.now(timezone.utc),
|
421
|
+
error_code=-32001,
|
422
|
+
error_message="Authentication failed",
|
423
|
+
metadata={},
|
424
|
+
)
|
425
|
+
|
426
|
+
json_str = auth_result.model_dump_json()
|
427
|
+
parsed_result = AuthResult.model_validate_json(json_str)
|
428
|
+
|
429
|
+
assert parsed_result.is_valid is False
|
430
|
+
assert parsed_result.status == AuthStatus.FAILED
|
431
|
+
assert parsed_result.username is None
|
432
|
+
assert parsed_result.roles == []
|
433
|
+
assert parsed_result.error_code == -32001
|
434
|
+
|
435
|
+
def test_nested_object_serialization(self):
|
436
|
+
"""Test serialization of nested objects."""
|
437
|
+
config = SecurityConfig(
|
438
|
+
ssl=SSLConfig(enabled=False), # Disabled to avoid file validation
|
439
|
+
auth=AuthConfig(
|
440
|
+
enabled=True,
|
441
|
+
methods=[AuthMethod.API_KEY],
|
442
|
+
api_keys={"admin": "admin_key"},
|
443
|
+
),
|
444
|
+
certificates=CertificateConfig(cert_storage_path="./certs"),
|
445
|
+
permissions=PermissionConfig(
|
446
|
+
roles_file=None
|
447
|
+
), # Set to None to avoid file validation
|
448
|
+
rate_limit=RateLimitConfig(enabled=True, default_requests_per_minute=100),
|
449
|
+
logging=LoggingConfig(level=LogLevel.INFO),
|
450
|
+
)
|
451
|
+
|
452
|
+
json_str = config.model_dump_json()
|
453
|
+
parsed_config = SecurityConfig.model_validate_json(json_str)
|
454
|
+
|
455
|
+
assert parsed_config.ssl.enabled is False
|
456
|
+
assert parsed_config.auth.enabled is True
|
457
|
+
assert parsed_config.auth.api_keys["admin"] == "admin_key"
|
458
|
+
assert parsed_config.rate_limit.default_requests_per_minute == 100
|
459
|
+
|
460
|
+
def test_datetime_serialization(self):
|
461
|
+
"""Test datetime serialization and deserialization."""
|
462
|
+
timestamp = datetime.now(timezone.utc)
|
463
|
+
|
464
|
+
auth_result = AuthResult(
|
465
|
+
is_valid=True,
|
466
|
+
status=AuthStatus.SUCCESS,
|
467
|
+
username="test_user",
|
468
|
+
auth_timestamp=timestamp,
|
469
|
+
roles=["user"],
|
470
|
+
)
|
471
|
+
|
472
|
+
json_str = auth_result.model_dump_json()
|
473
|
+
parsed_result = AuthResult.model_validate_json(json_str)
|
474
|
+
|
475
|
+
# Check that datetime is properly serialized and deserialized
|
476
|
+
assert parsed_result.auth_timestamp is not None
|
477
|
+
assert isinstance(parsed_result.auth_timestamp, datetime)
|
478
|
+
|
479
|
+
def test_enum_serialization(self):
|
480
|
+
"""Test enum serialization and deserialization."""
|
481
|
+
auth_result = AuthResult(
|
482
|
+
is_valid=True,
|
483
|
+
status=AuthStatus.SUCCESS,
|
484
|
+
username="test_user",
|
485
|
+
auth_method=AuthMethod.JWT,
|
486
|
+
roles=["user"],
|
487
|
+
)
|
488
|
+
|
489
|
+
json_str = auth_result.model_dump_json()
|
490
|
+
parsed_result = AuthResult.model_validate_json(json_str)
|
491
|
+
|
492
|
+
assert parsed_result.auth_method == AuthMethod.JWT
|
493
|
+
assert parsed_result.auth_method == "jwt"
|
File without changes
|