django-bolt 0.1.0__cp310-abi3-win_amd64.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.
Potentially problematic release.
This version of django-bolt might be problematic. Click here for more details.
- django_bolt/__init__.py +147 -0
- django_bolt/_core.pyd +0 -0
- django_bolt/admin/__init__.py +25 -0
- django_bolt/admin/admin_detection.py +179 -0
- django_bolt/admin/asgi_bridge.py +267 -0
- django_bolt/admin/routes.py +91 -0
- django_bolt/admin/static.py +155 -0
- django_bolt/admin/static_routes.py +111 -0
- django_bolt/api.py +1011 -0
- django_bolt/apps.py +7 -0
- django_bolt/async_collector.py +228 -0
- django_bolt/auth/README.md +464 -0
- django_bolt/auth/REVOCATION_EXAMPLE.md +391 -0
- django_bolt/auth/__init__.py +84 -0
- django_bolt/auth/backends.py +236 -0
- django_bolt/auth/guards.py +224 -0
- django_bolt/auth/jwt_utils.py +212 -0
- django_bolt/auth/revocation.py +286 -0
- django_bolt/auth/token.py +335 -0
- django_bolt/binding.py +363 -0
- django_bolt/bootstrap.py +77 -0
- django_bolt/cli.py +133 -0
- django_bolt/compression.py +104 -0
- django_bolt/decorators.py +159 -0
- django_bolt/dependencies.py +128 -0
- django_bolt/error_handlers.py +305 -0
- django_bolt/exceptions.py +294 -0
- django_bolt/health.py +129 -0
- django_bolt/logging/__init__.py +6 -0
- django_bolt/logging/config.py +357 -0
- django_bolt/logging/middleware.py +296 -0
- django_bolt/management/__init__.py +1 -0
- django_bolt/management/commands/__init__.py +0 -0
- django_bolt/management/commands/runbolt.py +427 -0
- django_bolt/middleware/__init__.py +32 -0
- django_bolt/middleware/compiler.py +131 -0
- django_bolt/middleware/middleware.py +247 -0
- django_bolt/openapi/__init__.py +23 -0
- django_bolt/openapi/config.py +196 -0
- django_bolt/openapi/plugins.py +439 -0
- django_bolt/openapi/routes.py +152 -0
- django_bolt/openapi/schema_generator.py +581 -0
- django_bolt/openapi/spec/__init__.py +68 -0
- django_bolt/openapi/spec/base.py +74 -0
- django_bolt/openapi/spec/callback.py +24 -0
- django_bolt/openapi/spec/components.py +72 -0
- django_bolt/openapi/spec/contact.py +21 -0
- django_bolt/openapi/spec/discriminator.py +25 -0
- django_bolt/openapi/spec/encoding.py +67 -0
- django_bolt/openapi/spec/enums.py +41 -0
- django_bolt/openapi/spec/example.py +36 -0
- django_bolt/openapi/spec/external_documentation.py +21 -0
- django_bolt/openapi/spec/header.py +132 -0
- django_bolt/openapi/spec/info.py +50 -0
- django_bolt/openapi/spec/license.py +28 -0
- django_bolt/openapi/spec/link.py +66 -0
- django_bolt/openapi/spec/media_type.py +51 -0
- django_bolt/openapi/spec/oauth_flow.py +36 -0
- django_bolt/openapi/spec/oauth_flows.py +28 -0
- django_bolt/openapi/spec/open_api.py +87 -0
- django_bolt/openapi/spec/operation.py +105 -0
- django_bolt/openapi/spec/parameter.py +147 -0
- django_bolt/openapi/spec/path_item.py +78 -0
- django_bolt/openapi/spec/paths.py +27 -0
- django_bolt/openapi/spec/reference.py +38 -0
- django_bolt/openapi/spec/request_body.py +38 -0
- django_bolt/openapi/spec/response.py +48 -0
- django_bolt/openapi/spec/responses.py +44 -0
- django_bolt/openapi/spec/schema.py +678 -0
- django_bolt/openapi/spec/security_requirement.py +28 -0
- django_bolt/openapi/spec/security_scheme.py +69 -0
- django_bolt/openapi/spec/server.py +34 -0
- django_bolt/openapi/spec/server_variable.py +32 -0
- django_bolt/openapi/spec/tag.py +32 -0
- django_bolt/openapi/spec/xml.py +44 -0
- django_bolt/pagination.py +669 -0
- django_bolt/param_functions.py +49 -0
- django_bolt/params.py +337 -0
- django_bolt/request_parsing.py +128 -0
- django_bolt/responses.py +214 -0
- django_bolt/router.py +48 -0
- django_bolt/serialization.py +193 -0
- django_bolt/status_codes.py +321 -0
- django_bolt/testing/__init__.py +10 -0
- django_bolt/testing/client.py +274 -0
- django_bolt/testing/helpers.py +93 -0
- django_bolt/tests/__init__.py +0 -0
- django_bolt/tests/admin_tests/__init__.py +1 -0
- django_bolt/tests/admin_tests/conftest.py +6 -0
- django_bolt/tests/admin_tests/test_admin_with_django.py +278 -0
- django_bolt/tests/admin_tests/urls.py +9 -0
- django_bolt/tests/cbv/__init__.py +0 -0
- django_bolt/tests/cbv/test_class_views.py +570 -0
- django_bolt/tests/cbv/test_class_views_django_orm.py +703 -0
- django_bolt/tests/cbv/test_class_views_features.py +1173 -0
- django_bolt/tests/cbv/test_class_views_with_client.py +622 -0
- django_bolt/tests/conftest.py +165 -0
- django_bolt/tests/test_action_decorator.py +399 -0
- django_bolt/tests/test_auth_secret_key.py +83 -0
- django_bolt/tests/test_decorator_syntax.py +159 -0
- django_bolt/tests/test_error_handling.py +481 -0
- django_bolt/tests/test_file_response.py +192 -0
- django_bolt/tests/test_global_cors.py +172 -0
- django_bolt/tests/test_guards_auth.py +441 -0
- django_bolt/tests/test_guards_integration.py +303 -0
- django_bolt/tests/test_health.py +283 -0
- django_bolt/tests/test_integration_validation.py +400 -0
- django_bolt/tests/test_json_validation.py +536 -0
- django_bolt/tests/test_jwt_auth.py +327 -0
- django_bolt/tests/test_jwt_token.py +458 -0
- django_bolt/tests/test_logging.py +837 -0
- django_bolt/tests/test_logging_merge.py +419 -0
- django_bolt/tests/test_middleware.py +492 -0
- django_bolt/tests/test_middleware_server.py +230 -0
- django_bolt/tests/test_model_viewset.py +323 -0
- django_bolt/tests/test_models.py +24 -0
- django_bolt/tests/test_pagination.py +1258 -0
- django_bolt/tests/test_parameter_validation.py +178 -0
- django_bolt/tests/test_syntax.py +626 -0
- django_bolt/tests/test_testing_utilities.py +163 -0
- django_bolt/tests/test_testing_utilities_simple.py +123 -0
- django_bolt/tests/test_viewset_unified.py +346 -0
- django_bolt/typing.py +273 -0
- django_bolt/views.py +1110 -0
- django_bolt-0.1.0.dist-info/METADATA +629 -0
- django_bolt-0.1.0.dist-info/RECORD +128 -0
- django_bolt-0.1.0.dist-info/WHEEL +4 -0
- django_bolt-0.1.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test middleware functionality in Django-Bolt
|
|
3
|
+
"""
|
|
4
|
+
import asyncio
|
|
5
|
+
import json
|
|
6
|
+
import time
|
|
7
|
+
import jwt
|
|
8
|
+
import pytest
|
|
9
|
+
from django_bolt import BoltAPI
|
|
10
|
+
from django_bolt.middleware import (
|
|
11
|
+
rate_limit, cors, skip_middleware,
|
|
12
|
+
Middleware, MiddlewareGroup, CORSMiddleware, RateLimitMiddleware
|
|
13
|
+
)
|
|
14
|
+
from django_bolt.auth import JWTAuthentication, APIKeyAuthentication
|
|
15
|
+
from django_bolt.auth import IsAuthenticated
|
|
16
|
+
import msgspec
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# Test models
|
|
20
|
+
class ItemModel(msgspec.Struct):
|
|
21
|
+
id: int
|
|
22
|
+
name: str
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Custom test middleware
|
|
26
|
+
class CustomTestMiddleware(Middleware):
|
|
27
|
+
def __init__(self, name: str):
|
|
28
|
+
self.name = name
|
|
29
|
+
self.call_count = 0
|
|
30
|
+
|
|
31
|
+
async def process_request(self, request, call_next):
|
|
32
|
+
self.call_count += 1
|
|
33
|
+
# Add marker to context
|
|
34
|
+
if request.get("context"):
|
|
35
|
+
request["context"][f"test_{self.name}"] = True
|
|
36
|
+
response = await call_next(request)
|
|
37
|
+
return response
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class TestMiddlewareDecorators:
|
|
41
|
+
"""Test middleware decorator functionality"""
|
|
42
|
+
|
|
43
|
+
def test_rate_limit_decorator(self):
|
|
44
|
+
"""Test rate limit decorator attaches metadata"""
|
|
45
|
+
api = BoltAPI()
|
|
46
|
+
|
|
47
|
+
@api.get("/limited")
|
|
48
|
+
@rate_limit(rps=50, burst=100, key="ip")
|
|
49
|
+
async def limited_endpoint():
|
|
50
|
+
return {"status": "ok"}
|
|
51
|
+
|
|
52
|
+
# Check that middleware metadata was attached
|
|
53
|
+
handler = api._handlers[0]
|
|
54
|
+
assert hasattr(handler, '__bolt_middleware__')
|
|
55
|
+
middleware = handler.__bolt_middleware__
|
|
56
|
+
assert len(middleware) > 0
|
|
57
|
+
assert middleware[0]['type'] == 'rate_limit'
|
|
58
|
+
assert middleware[0]['rps'] == 50
|
|
59
|
+
assert middleware[0]['burst'] == 100
|
|
60
|
+
assert middleware[0]['key'] == 'ip'
|
|
61
|
+
|
|
62
|
+
def test_cors_decorator(self):
|
|
63
|
+
"""Test CORS decorator attaches metadata"""
|
|
64
|
+
api = BoltAPI()
|
|
65
|
+
|
|
66
|
+
@api.get("/cors-test")
|
|
67
|
+
@cors(origins=["http://localhost:3000"], credentials=True, max_age=7200)
|
|
68
|
+
async def cors_endpoint():
|
|
69
|
+
return {"status": "ok"}
|
|
70
|
+
|
|
71
|
+
handler = api._handlers[0]
|
|
72
|
+
assert hasattr(handler, '__bolt_middleware__')
|
|
73
|
+
middleware = handler.__bolt_middleware__
|
|
74
|
+
assert len(middleware) > 0
|
|
75
|
+
assert middleware[0]['type'] == 'cors'
|
|
76
|
+
assert middleware[0]['origins'] == ["http://localhost:3000"]
|
|
77
|
+
assert middleware[0]['credentials'] == True
|
|
78
|
+
assert middleware[0]['max_age'] == 7200
|
|
79
|
+
|
|
80
|
+
def test_auth_via_guards(self):
|
|
81
|
+
"""Test authentication via guards parameter"""
|
|
82
|
+
api = BoltAPI()
|
|
83
|
+
|
|
84
|
+
@api.get(
|
|
85
|
+
"/protected",
|
|
86
|
+
auth=[JWTAuthentication(secret="test-secret", algorithms=["HS256", "HS384"])],
|
|
87
|
+
guards=[IsAuthenticated()]
|
|
88
|
+
)
|
|
89
|
+
async def protected_endpoint():
|
|
90
|
+
return {"status": "ok"}
|
|
91
|
+
|
|
92
|
+
# Check that middleware metadata was compiled
|
|
93
|
+
handler_id = 0
|
|
94
|
+
assert handler_id in api._handler_middleware
|
|
95
|
+
meta = api._handler_middleware[handler_id]
|
|
96
|
+
assert 'auth_backends' in meta
|
|
97
|
+
assert len(meta['auth_backends']) > 0
|
|
98
|
+
assert meta['auth_backends'][0]['type'] == 'jwt'
|
|
99
|
+
assert meta['auth_backends'][0]['secret'] == 'test-secret'
|
|
100
|
+
assert meta['auth_backends'][0]['algorithms'] == ["HS256", "HS384"]
|
|
101
|
+
|
|
102
|
+
def test_skip_middleware_decorator(self):
|
|
103
|
+
"""Test skip middleware decorator"""
|
|
104
|
+
api = BoltAPI()
|
|
105
|
+
|
|
106
|
+
@api.get("/no-middleware")
|
|
107
|
+
@skip_middleware("cors", "rate_limit")
|
|
108
|
+
async def no_middleware_endpoint():
|
|
109
|
+
return {"status": "ok"}
|
|
110
|
+
|
|
111
|
+
handler = api._handlers[0]
|
|
112
|
+
assert hasattr(handler, '__bolt_skip_middleware__')
|
|
113
|
+
skip = handler.__bolt_skip_middleware__
|
|
114
|
+
assert "cors" in skip
|
|
115
|
+
assert "rate_limit" in skip
|
|
116
|
+
|
|
117
|
+
def test_multiple_middleware(self):
|
|
118
|
+
"""Test multiple middleware decorators on same route"""
|
|
119
|
+
api = BoltAPI()
|
|
120
|
+
|
|
121
|
+
@api.post(
|
|
122
|
+
"/secure",
|
|
123
|
+
auth=[APIKeyAuthentication(api_keys={"key1", "key2"})],
|
|
124
|
+
guards=[IsAuthenticated()]
|
|
125
|
+
)
|
|
126
|
+
@rate_limit(rps=10)
|
|
127
|
+
@cors(origins=["https://app.example.com"])
|
|
128
|
+
async def secure_endpoint(data: dict):
|
|
129
|
+
return {"received": data}
|
|
130
|
+
|
|
131
|
+
handler = api._handlers[0]
|
|
132
|
+
middleware = handler.__bolt_middleware__
|
|
133
|
+
assert len(middleware) == 2 # rate_limit and cors
|
|
134
|
+
|
|
135
|
+
# Check they're all there
|
|
136
|
+
types = [m['type'] for m in middleware]
|
|
137
|
+
assert 'rate_limit' in types
|
|
138
|
+
assert 'cors' in types
|
|
139
|
+
|
|
140
|
+
# Check auth is in metadata
|
|
141
|
+
meta = api._handler_middleware[0]
|
|
142
|
+
assert 'auth_backends' in meta
|
|
143
|
+
assert len(meta['auth_backends']) > 0
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class TestMiddlewareGroups:
|
|
147
|
+
"""Test middleware grouping functionality"""
|
|
148
|
+
|
|
149
|
+
def test_middleware_group_creation(self):
|
|
150
|
+
"""Test creating and combining middleware groups"""
|
|
151
|
+
cors_mw = CORSMiddleware(origins=["http://localhost:3000"])
|
|
152
|
+
rate_mw = RateLimitMiddleware(rps=100)
|
|
153
|
+
|
|
154
|
+
group1 = MiddlewareGroup(cors_mw, rate_mw)
|
|
155
|
+
assert len(group1.middleware) == 2
|
|
156
|
+
|
|
157
|
+
# Test combining groups
|
|
158
|
+
group2 = MiddlewareGroup(cors_mw)
|
|
159
|
+
combined = group1 + group2
|
|
160
|
+
assert len(combined.middleware) == 3
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class TestGlobalMiddleware:
|
|
164
|
+
"""Test global middleware configuration"""
|
|
165
|
+
|
|
166
|
+
def test_global_middleware_config(self):
|
|
167
|
+
"""Test setting global middleware via BoltAPI constructor"""
|
|
168
|
+
api = BoltAPI(
|
|
169
|
+
middleware_config={
|
|
170
|
+
'cors': {
|
|
171
|
+
'origins': ['*'],
|
|
172
|
+
'credentials': False
|
|
173
|
+
},
|
|
174
|
+
'rate_limit': {
|
|
175
|
+
'rps': 1000,
|
|
176
|
+
'burst': 2000,
|
|
177
|
+
'key': 'ip'
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
assert 'cors' in api.middleware_config
|
|
183
|
+
assert 'rate_limit' in api.middleware_config
|
|
184
|
+
assert api.middleware_config['cors']['origins'] == ['*']
|
|
185
|
+
assert api.middleware_config['rate_limit']['rps'] == 1000
|
|
186
|
+
|
|
187
|
+
def test_global_middleware_instances(self):
|
|
188
|
+
"""Test setting global middleware instances"""
|
|
189
|
+
cors_mw = CORSMiddleware(origins=["http://localhost:3000"])
|
|
190
|
+
rate_mw = RateLimitMiddleware(rps=500)
|
|
191
|
+
|
|
192
|
+
api = BoltAPI(middleware=[cors_mw, rate_mw])
|
|
193
|
+
|
|
194
|
+
assert len(api.middleware) == 2
|
|
195
|
+
assert api.middleware[0] == cors_mw
|
|
196
|
+
assert api.middleware[1] == rate_mw
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class TestMiddlewareMetadata:
|
|
200
|
+
"""Test middleware metadata compilation"""
|
|
201
|
+
|
|
202
|
+
def test_middleware_metadata_compilation(self):
|
|
203
|
+
"""Test that middleware metadata is compiled correctly"""
|
|
204
|
+
api = BoltAPI(
|
|
205
|
+
middleware_config={
|
|
206
|
+
'cors': {'origins': ['*']}
|
|
207
|
+
}
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
@api.get("/test")
|
|
211
|
+
@rate_limit(rps=100)
|
|
212
|
+
async def test_endpoint():
|
|
213
|
+
return {"status": "ok"}
|
|
214
|
+
|
|
215
|
+
# Check handler middleware metadata
|
|
216
|
+
assert len(api._handler_middleware) > 0
|
|
217
|
+
handler_id = 0
|
|
218
|
+
meta = api._handler_middleware[handler_id]
|
|
219
|
+
|
|
220
|
+
assert 'middleware' in meta
|
|
221
|
+
assert len(meta['middleware']) == 2 # Global CORS + route rate_limit
|
|
222
|
+
|
|
223
|
+
# Check types
|
|
224
|
+
types = [m['type'] for m in meta['middleware']]
|
|
225
|
+
assert 'cors' in types
|
|
226
|
+
assert 'rate_limit' in types
|
|
227
|
+
|
|
228
|
+
def test_skip_global_middleware(self):
|
|
229
|
+
"""Test skipping global middleware on specific routes"""
|
|
230
|
+
api = BoltAPI(
|
|
231
|
+
middleware_config={
|
|
232
|
+
'cors': {'origins': ['*']},
|
|
233
|
+
'rate_limit': {'rps': 100}
|
|
234
|
+
}
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
@api.get("/no-cors")
|
|
238
|
+
@skip_middleware("cors")
|
|
239
|
+
async def no_cors_endpoint():
|
|
240
|
+
return {"status": "ok"}
|
|
241
|
+
|
|
242
|
+
handler_id = 0
|
|
243
|
+
meta = api._handler_middleware[handler_id]
|
|
244
|
+
|
|
245
|
+
# Should only have rate_limit, not cors
|
|
246
|
+
assert len(meta['middleware']) == 1
|
|
247
|
+
assert meta['middleware'][0]['type'] == 'rate_limit'
|
|
248
|
+
assert 'cors' in meta['skip']
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class TestMiddlewareExecution:
|
|
252
|
+
"""Test middleware execution in the pipeline"""
|
|
253
|
+
|
|
254
|
+
@pytest.mark.asyncio
|
|
255
|
+
async def test_request_dispatch_with_middleware(self):
|
|
256
|
+
"""Test that dispatch works with middleware metadata"""
|
|
257
|
+
api = BoltAPI()
|
|
258
|
+
|
|
259
|
+
@api.get("/test")
|
|
260
|
+
@cors(origins=["*"])
|
|
261
|
+
async def test_endpoint(request: dict):
|
|
262
|
+
# Access context
|
|
263
|
+
context = request.get("context")
|
|
264
|
+
return {
|
|
265
|
+
"has_context": context is not None,
|
|
266
|
+
"context_type": type(context).__name__ if context else None
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
# Create test request
|
|
270
|
+
test_request = {
|
|
271
|
+
"method": "GET",
|
|
272
|
+
"path": "/test",
|
|
273
|
+
"body": b"",
|
|
274
|
+
"params": {},
|
|
275
|
+
"query": {},
|
|
276
|
+
"headers": {},
|
|
277
|
+
"cookies": {},
|
|
278
|
+
"context": None # Will be populated by middleware
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
# Get handler
|
|
282
|
+
handler = api._handlers[0]
|
|
283
|
+
|
|
284
|
+
# Dispatch
|
|
285
|
+
result = await api._dispatch(handler, test_request)
|
|
286
|
+
status, headers, body = result
|
|
287
|
+
|
|
288
|
+
assert status == 200
|
|
289
|
+
data = json.loads(body)
|
|
290
|
+
assert "has_context" in data
|
|
291
|
+
|
|
292
|
+
@pytest.mark.asyncio
|
|
293
|
+
async def test_custom_middleware_execution(self):
|
|
294
|
+
"""Test custom middleware execution"""
|
|
295
|
+
test_mw = CustomTestMiddleware("test1")
|
|
296
|
+
api = BoltAPI(middleware=[test_mw])
|
|
297
|
+
|
|
298
|
+
@api.get("/test")
|
|
299
|
+
async def test_endpoint():
|
|
300
|
+
return {"status": "ok"}
|
|
301
|
+
|
|
302
|
+
# Note: This tests the Python side only
|
|
303
|
+
# Rust middleware execution happens in the server
|
|
304
|
+
assert test_mw.call_count == 0 # Not executed yet
|
|
305
|
+
|
|
306
|
+
@pytest.mark.asyncio
|
|
307
|
+
async def test_response_model_with_middleware(self):
|
|
308
|
+
"""Test response model validation with middleware"""
|
|
309
|
+
api = BoltAPI()
|
|
310
|
+
|
|
311
|
+
@api.post("/items", response_model=ItemModel)
|
|
312
|
+
@cors(origins=["*"])
|
|
313
|
+
async def create_item(item: ItemModel) -> ItemModel:
|
|
314
|
+
return item
|
|
315
|
+
|
|
316
|
+
test_request = {
|
|
317
|
+
"method": "POST",
|
|
318
|
+
"path": "/items",
|
|
319
|
+
"body": msgspec.json.encode({"id": 1, "name": "Test"}),
|
|
320
|
+
"params": {},
|
|
321
|
+
"query": {},
|
|
322
|
+
"headers": {"content-type": "application/json"},
|
|
323
|
+
"cookies": {},
|
|
324
|
+
"context": None
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
handler = api._handlers[0]
|
|
328
|
+
result = await api._dispatch(handler, test_request)
|
|
329
|
+
status, headers, body = result
|
|
330
|
+
|
|
331
|
+
assert status == 200
|
|
332
|
+
data = msgspec.json.decode(body)
|
|
333
|
+
assert data["id"] == 1
|
|
334
|
+
assert data["name"] == "Test"
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
class TestAuthTokenGeneration:
|
|
338
|
+
"""Test JWT token generation for auth testing"""
|
|
339
|
+
|
|
340
|
+
def test_generate_valid_jwt(self):
|
|
341
|
+
"""Generate a valid JWT token for testing"""
|
|
342
|
+
secret = "test-secret"
|
|
343
|
+
payload = {
|
|
344
|
+
"sub": "user123",
|
|
345
|
+
"exp": int(time.time()) + 3600, # 1 hour from now
|
|
346
|
+
"iat": int(time.time()),
|
|
347
|
+
"custom_claim": "test_value"
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
token = jwt.encode(payload, secret, algorithm="HS256")
|
|
351
|
+
assert token is not None
|
|
352
|
+
|
|
353
|
+
# Verify we can decode it
|
|
354
|
+
decoded = jwt.decode(token, secret, algorithms=["HS256"])
|
|
355
|
+
assert decoded["sub"] == "user123"
|
|
356
|
+
assert decoded["custom_claim"] == "test_value"
|
|
357
|
+
|
|
358
|
+
def test_generate_expired_jwt(self):
|
|
359
|
+
"""Generate an expired JWT token for testing"""
|
|
360
|
+
secret = "test-secret"
|
|
361
|
+
payload = {
|
|
362
|
+
"sub": "user123",
|
|
363
|
+
"exp": int(time.time()) - 3600, # 1 hour ago
|
|
364
|
+
"iat": int(time.time()) - 7200
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
token = jwt.encode(payload, secret, algorithm="HS256")
|
|
368
|
+
|
|
369
|
+
# Should raise error when verifying
|
|
370
|
+
with pytest.raises(jwt.ExpiredSignatureError):
|
|
371
|
+
jwt.decode(token, secret, algorithms=["HS256"])
|
|
372
|
+
|
|
373
|
+
def test_generate_invalid_signature_jwt(self):
|
|
374
|
+
"""Generate a JWT with wrong signature"""
|
|
375
|
+
secret = "test-secret"
|
|
376
|
+
wrong_secret = "wrong-secret"
|
|
377
|
+
payload = {
|
|
378
|
+
"sub": "user123",
|
|
379
|
+
"exp": int(time.time()) + 3600
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
token = jwt.encode(payload, wrong_secret, algorithm="HS256")
|
|
383
|
+
|
|
384
|
+
# Should raise error when verifying with correct secret
|
|
385
|
+
with pytest.raises(jwt.InvalidSignatureError):
|
|
386
|
+
jwt.decode(token, secret, algorithms=["HS256"])
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
class TestMiddlewareIntegration:
|
|
390
|
+
"""Integration tests for middleware with actual server"""
|
|
391
|
+
|
|
392
|
+
def test_middleware_registration(self):
|
|
393
|
+
"""Test that middleware gets registered with Rust"""
|
|
394
|
+
api = BoltAPI()
|
|
395
|
+
|
|
396
|
+
@api.get("/middleware-test")
|
|
397
|
+
@rate_limit(rps=100)
|
|
398
|
+
@cors(origins=["http://localhost:3000"])
|
|
399
|
+
async def test_endpoint():
|
|
400
|
+
return {"status": "ok"}
|
|
401
|
+
|
|
402
|
+
# Check routes are registered
|
|
403
|
+
assert len(api._routes) == 1
|
|
404
|
+
method, path, handler_id, handler = api._routes[0]
|
|
405
|
+
assert method == "GET"
|
|
406
|
+
assert path == "/middleware-test"
|
|
407
|
+
|
|
408
|
+
# Check middleware metadata
|
|
409
|
+
assert handler_id in api._handler_middleware
|
|
410
|
+
meta = api._handler_middleware[handler_id]
|
|
411
|
+
assert len(meta['middleware']) == 2
|
|
412
|
+
|
|
413
|
+
types = [m['type'] for m in meta['middleware']]
|
|
414
|
+
assert 'rate_limit' in types
|
|
415
|
+
assert 'cors' in types
|
|
416
|
+
|
|
417
|
+
def test_preflight_route(self):
|
|
418
|
+
"""Test OPTIONS preflight handling"""
|
|
419
|
+
api = BoltAPI()
|
|
420
|
+
|
|
421
|
+
@api.get("/api/data")
|
|
422
|
+
@cors(
|
|
423
|
+
origins=["http://localhost:3000", "https://app.example.com"],
|
|
424
|
+
methods=["GET", "POST", "PUT"],
|
|
425
|
+
headers=["Content-Type", "Authorization", "X-Custom"],
|
|
426
|
+
credentials=True,
|
|
427
|
+
max_age=7200
|
|
428
|
+
)
|
|
429
|
+
async def get_data():
|
|
430
|
+
return {"data": [1, 2, 3]}
|
|
431
|
+
|
|
432
|
+
# The preflight will be handled by Rust middleware
|
|
433
|
+
# Here we just verify the metadata is correct
|
|
434
|
+
meta = api._handler_middleware[0]
|
|
435
|
+
cors_config = next(m for m in meta['middleware'] if m['type'] == 'cors')
|
|
436
|
+
|
|
437
|
+
assert cors_config['origins'] == ["http://localhost:3000", "https://app.example.com"]
|
|
438
|
+
assert cors_config['methods'] == ["GET", "POST", "PUT"]
|
|
439
|
+
assert cors_config['headers'] == ["Content-Type", "Authorization", "X-Custom"]
|
|
440
|
+
assert cors_config['credentials'] == True
|
|
441
|
+
assert cors_config['max_age'] == 7200
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
if __name__ == "__main__":
|
|
445
|
+
# Run basic tests
|
|
446
|
+
print("Testing middleware decorators...")
|
|
447
|
+
test_decorators = TestMiddlewareDecorators()
|
|
448
|
+
test_decorators.test_rate_limit_decorator()
|
|
449
|
+
test_decorators.test_cors_decorator()
|
|
450
|
+
test_decorators.test_auth_via_guards()
|
|
451
|
+
test_decorators.test_skip_middleware_decorator()
|
|
452
|
+
test_decorators.test_multiple_middleware()
|
|
453
|
+
print("✓ Decorator tests passed")
|
|
454
|
+
|
|
455
|
+
print("\nTesting middleware groups...")
|
|
456
|
+
test_groups = TestMiddlewareGroups()
|
|
457
|
+
test_groups.test_middleware_group_creation()
|
|
458
|
+
print("✓ Group tests passed")
|
|
459
|
+
|
|
460
|
+
print("\nTesting global middleware...")
|
|
461
|
+
test_global = TestGlobalMiddleware()
|
|
462
|
+
test_global.test_global_middleware_config()
|
|
463
|
+
test_global.test_global_middleware_instances()
|
|
464
|
+
print("✓ Global middleware tests passed")
|
|
465
|
+
|
|
466
|
+
print("\nTesting middleware metadata...")
|
|
467
|
+
test_meta = TestMiddlewareMetadata()
|
|
468
|
+
test_meta.test_middleware_metadata_compilation()
|
|
469
|
+
test_meta.test_skip_global_middleware()
|
|
470
|
+
print("✓ Metadata tests passed")
|
|
471
|
+
|
|
472
|
+
print("\nTesting JWT generation...")
|
|
473
|
+
test_auth = TestAuthTokenGeneration()
|
|
474
|
+
test_auth.test_generate_valid_jwt()
|
|
475
|
+
test_auth.test_generate_expired_jwt()
|
|
476
|
+
test_auth.test_generate_invalid_signature_jwt()
|
|
477
|
+
print("✓ JWT tests passed")
|
|
478
|
+
|
|
479
|
+
print("\nTesting middleware integration...")
|
|
480
|
+
test_integration = TestMiddlewareIntegration()
|
|
481
|
+
test_integration.test_middleware_registration()
|
|
482
|
+
test_integration.test_preflight_route()
|
|
483
|
+
print("✓ Integration tests passed")
|
|
484
|
+
|
|
485
|
+
print("\nRunning async tests...")
|
|
486
|
+
test_exec = TestMiddlewareExecution()
|
|
487
|
+
asyncio.run(test_exec.test_request_dispatch_with_middleware())
|
|
488
|
+
asyncio.run(test_exec.test_custom_middleware_execution())
|
|
489
|
+
asyncio.run(test_exec.test_response_model_with_middleware())
|
|
490
|
+
print("✓ Async execution tests passed")
|
|
491
|
+
|
|
492
|
+
print("\n✅ All middleware tests passed!")
|