django-bolt 0.1.0__cp310-abi3-win_amd64.whl → 0.1.2__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.

Files changed (56) hide show
  1. django_bolt/__init__.py +2 -2
  2. django_bolt/_core.pyd +0 -0
  3. django_bolt/_json.py +169 -0
  4. django_bolt/admin/static_routes.py +15 -21
  5. django_bolt/api.py +181 -61
  6. django_bolt/auth/__init__.py +2 -2
  7. django_bolt/decorators.py +15 -3
  8. django_bolt/dependencies.py +30 -24
  9. django_bolt/error_handlers.py +2 -1
  10. django_bolt/openapi/plugins.py +3 -2
  11. django_bolt/openapi/schema_generator.py +65 -20
  12. django_bolt/pagination.py +2 -1
  13. django_bolt/responses.py +3 -2
  14. django_bolt/serialization.py +5 -4
  15. {django_bolt-0.1.0.dist-info → django_bolt-0.1.2.dist-info}/METADATA +181 -201
  16. {django_bolt-0.1.0.dist-info → django_bolt-0.1.2.dist-info}/RECORD +18 -55
  17. django_bolt/auth/README.md +0 -464
  18. django_bolt/auth/REVOCATION_EXAMPLE.md +0 -391
  19. django_bolt/tests/__init__.py +0 -0
  20. django_bolt/tests/admin_tests/__init__.py +0 -1
  21. django_bolt/tests/admin_tests/conftest.py +0 -6
  22. django_bolt/tests/admin_tests/test_admin_with_django.py +0 -278
  23. django_bolt/tests/admin_tests/urls.py +0 -9
  24. django_bolt/tests/cbv/__init__.py +0 -0
  25. django_bolt/tests/cbv/test_class_views.py +0 -570
  26. django_bolt/tests/cbv/test_class_views_django_orm.py +0 -703
  27. django_bolt/tests/cbv/test_class_views_features.py +0 -1173
  28. django_bolt/tests/cbv/test_class_views_with_client.py +0 -622
  29. django_bolt/tests/conftest.py +0 -165
  30. django_bolt/tests/test_action_decorator.py +0 -399
  31. django_bolt/tests/test_auth_secret_key.py +0 -83
  32. django_bolt/tests/test_decorator_syntax.py +0 -159
  33. django_bolt/tests/test_error_handling.py +0 -481
  34. django_bolt/tests/test_file_response.py +0 -192
  35. django_bolt/tests/test_global_cors.py +0 -172
  36. django_bolt/tests/test_guards_auth.py +0 -441
  37. django_bolt/tests/test_guards_integration.py +0 -303
  38. django_bolt/tests/test_health.py +0 -283
  39. django_bolt/tests/test_integration_validation.py +0 -400
  40. django_bolt/tests/test_json_validation.py +0 -536
  41. django_bolt/tests/test_jwt_auth.py +0 -327
  42. django_bolt/tests/test_jwt_token.py +0 -458
  43. django_bolt/tests/test_logging.py +0 -837
  44. django_bolt/tests/test_logging_merge.py +0 -419
  45. django_bolt/tests/test_middleware.py +0 -492
  46. django_bolt/tests/test_middleware_server.py +0 -230
  47. django_bolt/tests/test_model_viewset.py +0 -323
  48. django_bolt/tests/test_models.py +0 -24
  49. django_bolt/tests/test_pagination.py +0 -1258
  50. django_bolt/tests/test_parameter_validation.py +0 -178
  51. django_bolt/tests/test_syntax.py +0 -626
  52. django_bolt/tests/test_testing_utilities.py +0 -163
  53. django_bolt/tests/test_testing_utilities_simple.py +0 -123
  54. django_bolt/tests/test_viewset_unified.py +0 -346
  55. {django_bolt-0.1.0.dist-info → django_bolt-0.1.2.dist-info}/WHEEL +0 -0
  56. {django_bolt-0.1.0.dist-info → django_bolt-0.1.2.dist-info}/entry_points.txt +0 -0
@@ -1,441 +0,0 @@
1
- """
2
- Test guards and authentication system for Django-Bolt.
3
-
4
- Tests the new DRF-inspired guards and authentication classes.
5
- """
6
- import jwt
7
- import time
8
- import pytest
9
- from django_bolt import BoltAPI
10
- from django_bolt.auth import (
11
- JWTAuthentication,
12
- APIKeyAuthentication,
13
- AuthContext,
14
- AllowAny,
15
- IsAuthenticated,
16
- IsAdminUser,
17
- IsStaff,
18
- HasPermission,
19
- HasAnyPermission,
20
- HasAllPermissions
21
- )
22
-
23
-
24
- class TestAuthenticationClasses:
25
- """Test authentication class configurations"""
26
-
27
- def test_jwt_authentication_basic(self):
28
- """Test JWT authentication class initialization"""
29
- auth = JWTAuthentication(secret="test-secret")
30
- assert auth.secret == "test-secret"
31
- assert auth.algorithms == ["HS256"]
32
- assert auth.scheme_name == "jwt"
33
-
34
- metadata = auth.to_metadata()
35
- assert metadata["type"] == "jwt"
36
- assert metadata["secret"] == "test-secret"
37
- assert metadata["algorithms"] == ["HS256"]
38
-
39
- def test_jwt_authentication_with_options(self):
40
- """Test JWT authentication with all options"""
41
- auth = JWTAuthentication(
42
- secret="my-secret",
43
- algorithms=["HS256", "HS384"],
44
- header="x-auth-token",
45
- audience="my-app",
46
- issuer="auth-server"
47
- )
48
-
49
- metadata = auth.to_metadata()
50
- assert metadata["secret"] == "my-secret"
51
- assert metadata["algorithms"] == ["HS256", "HS384"]
52
- assert metadata["header"] == "x-auth-token"
53
- assert metadata["audience"] == "my-app"
54
- assert metadata["issuer"] == "auth-server"
55
-
56
- def test_api_key_authentication(self):
57
- """Test API key authentication class"""
58
- auth = APIKeyAuthentication(
59
- api_keys={"key1", "key2", "key3"},
60
- header="x-api-key"
61
- )
62
-
63
- assert auth.scheme_name == "api_key"
64
- metadata = auth.to_metadata()
65
- assert metadata["type"] == "api_key"
66
- assert set(metadata["api_keys"]) == {"key1", "key2", "key3"}
67
- assert metadata["header"] == "x-api-key"
68
-
69
- def test_api_key_with_permissions(self):
70
- """Test API key authentication with permission mapping"""
71
- auth = APIKeyAuthentication(
72
- api_keys={"admin-key", "read-key"},
73
- key_permissions={
74
- "admin-key": ["users.create", "users.delete"],
75
- "read-key": ["users.view"]
76
- }
77
- )
78
-
79
- metadata = auth.to_metadata()
80
- assert "admin-key" in metadata["key_permissions"]
81
- assert "users.create" in metadata["key_permissions"]["admin-key"]
82
-
83
-
84
- class TestPermissionGuards:
85
- """Test permission guard classes"""
86
-
87
- def test_allow_any(self):
88
- """Test AllowAny guard"""
89
- guard = AllowAny()
90
- assert guard.guard_name == "allow_any"
91
- assert guard.to_metadata() == {"type": "allow_any"}
92
-
93
- # Should allow without auth context
94
- assert guard.has_permission(None) == True
95
-
96
- def test_is_authenticated(self):
97
- """Test IsAuthenticated guard"""
98
- guard = IsAuthenticated()
99
- assert guard.guard_name == "is_authenticated"
100
- assert guard.to_metadata() == {"type": "is_authenticated"}
101
-
102
- # Should deny without auth context
103
- assert guard.has_permission(None) == False
104
-
105
- # Should allow with auth context
106
- ctx = AuthContext(user_id="user123")
107
- assert guard.has_permission(ctx) == True
108
-
109
- def test_is_admin(self):
110
- """Test IsAdminUser guard"""
111
- guard = IsAdminUser()
112
- assert guard.guard_name == "is_admin"
113
-
114
- # Should deny without auth
115
- assert guard.has_permission(None) == False
116
-
117
- # Should deny non-admin user
118
- ctx = AuthContext(user_id="user123", is_admin=False)
119
- assert guard.has_permission(ctx) == False
120
-
121
- # Should allow admin user
122
- ctx = AuthContext(user_id="admin", is_admin=True)
123
- assert guard.has_permission(ctx) == True
124
-
125
- def test_is_staff(self):
126
- """Test IsStaff guard"""
127
- guard = IsStaff()
128
- assert guard.guard_name == "is_staff"
129
-
130
- # Should deny non-staff
131
- ctx = AuthContext(user_id="user", is_staff=False)
132
- assert guard.has_permission(ctx) == False
133
-
134
- # Should allow staff
135
- ctx = AuthContext(user_id="staff", is_staff=True)
136
- assert guard.has_permission(ctx) == True
137
-
138
- def test_has_permission(self):
139
- """Test HasPermission guard"""
140
- guard = HasPermission("users.delete")
141
- assert guard.guard_name == "has_permission"
142
-
143
- metadata = guard.to_metadata()
144
- assert metadata["type"] == "has_permission"
145
- assert metadata["permission"] == "users.delete"
146
-
147
- # Should deny without permission
148
- ctx = AuthContext(user_id="user", permissions={"users.view"})
149
- assert guard.has_permission(ctx) == False
150
-
151
- # Should allow with permission
152
- ctx = AuthContext(user_id="user", permissions={"users.delete", "users.view"})
153
- assert guard.has_permission(ctx) == True
154
-
155
- def test_has_any_permission(self):
156
- """Test HasAnyPermission guard"""
157
- guard = HasAnyPermission("users.create", "users.update")
158
-
159
- metadata = guard.to_metadata()
160
- assert metadata["type"] == "has_any_permission"
161
- assert set(metadata["permissions"]) == {"users.create", "users.update"}
162
-
163
- # Should deny without any permission
164
- ctx = AuthContext(user_id="user", permissions={"users.view"})
165
- assert guard.has_permission(ctx) == False
166
-
167
- # Should allow with one permission
168
- ctx = AuthContext(user_id="user", permissions={"users.view", "users.create"})
169
- assert guard.has_permission(ctx) == True
170
-
171
- def test_has_all_permissions(self):
172
- """Test HasAllPermissions guard"""
173
- guard = HasAllPermissions("users.create", "users.delete")
174
-
175
- metadata = guard.to_metadata()
176
- assert metadata["type"] == "has_all_permissions"
177
-
178
- # Should deny without all permissions
179
- ctx = AuthContext(user_id="user", permissions={"users.create"})
180
- assert guard.has_permission(ctx) == False
181
-
182
- # Should allow with all permissions
183
- ctx = AuthContext(user_id="user", permissions={"users.create", "users.delete", "users.view"})
184
- assert guard.has_permission(ctx) == True
185
-
186
-
187
- class TestRouteDecoratorAPI:
188
- """Test route decorator with guards and auth parameters"""
189
-
190
- def test_route_with_guards(self):
191
- """Test route decorator with guards parameter"""
192
- api = BoltAPI()
193
-
194
- @api.get("/admin", guards=[IsAdminUser()])
195
- async def admin_endpoint():
196
- return {"message": "admin only"}
197
-
198
- # Check that metadata was compiled
199
- handler = api._handlers[0]
200
- handler_id = 0
201
- assert handler_id in api._handler_middleware
202
-
203
- metadata = api._handler_middleware[handler_id]
204
- assert "guards" in metadata
205
- assert len(metadata["guards"]) == 1
206
- assert metadata["guards"][0]["type"] == "is_admin"
207
-
208
- def test_route_with_auth_override(self):
209
- """Test route with custom auth backend"""
210
- api = BoltAPI()
211
-
212
- @api.get(
213
- "/api-only",
214
- auth=[APIKeyAuthentication(api_keys={"key1"})],
215
- guards=[IsAuthenticated()]
216
- )
217
- async def api_endpoint():
218
- return {"message": "API key only"}
219
-
220
- handler_id = 0
221
- metadata = api._handler_middleware[handler_id]
222
-
223
- assert "auth_backends" in metadata
224
- assert len(metadata["auth_backends"]) == 1
225
- assert metadata["auth_backends"][0]["type"] == "api_key"
226
-
227
- assert "guards" in metadata
228
- assert metadata["guards"][0]["type"] == "is_authenticated"
229
-
230
- def test_route_with_multiple_guards(self):
231
- """Test route with multiple guards"""
232
- api = BoltAPI()
233
-
234
- @api.get("/restricted", guards=[
235
- IsAuthenticated(),
236
- IsStaff(),
237
- HasPermission("users.delete")
238
- ])
239
- async def restricted_endpoint():
240
- return {"message": "highly restricted"}
241
-
242
- handler_id = 0
243
- metadata = api._handler_middleware[handler_id]
244
-
245
- assert len(metadata["guards"]) == 3
246
- assert metadata["guards"][0]["type"] == "is_authenticated"
247
- assert metadata["guards"][1]["type"] == "is_staff"
248
- assert metadata["guards"][2]["type"] == "has_permission"
249
-
250
- def test_public_route_with_allow_any(self):
251
- """Test that AllowAny explicitly bypasses global defaults"""
252
- api = BoltAPI()
253
-
254
- @api.get("/public", guards=[AllowAny()])
255
- async def public_endpoint():
256
- return {"message": "public"}
257
-
258
- handler_id = 0
259
- metadata = api._handler_middleware[handler_id]
260
-
261
- assert "guards" in metadata
262
- assert metadata["guards"][0]["type"] == "allow_any"
263
-
264
-
265
- class TestMetadataCompilation:
266
- """Test that metadata is properly compiled and merged"""
267
-
268
- def test_global_auth_defaults(self):
269
- """Test that global auth defaults are used when no per-route auth"""
270
- import django
271
- from django.conf import settings
272
-
273
- if not settings.configured:
274
- settings.configure(
275
- SECRET_KEY="test-key",
276
- BOLT_AUTHENTICATION_CLASSES=[
277
- JWTAuthentication(secret="global-secret")
278
- ],
279
- BOLT_DEFAULT_PERMISSION_CLASSES=[IsAuthenticated()]
280
- )
281
- django.setup()
282
-
283
- api = BoltAPI()
284
-
285
- @api.get("/default-protected")
286
- async def protected():
287
- return {"message": "uses global defaults"}
288
-
289
- # Should use global defaults
290
- handler_id = 0
291
- if handler_id in api._handler_middleware:
292
- metadata = api._handler_middleware[handler_id]
293
- # Global defaults should be applied
294
- assert "auth_backends" in metadata or "guards" in metadata
295
-
296
- def test_per_route_override_precedence(self):
297
- """Test that per-route auth/guards override global defaults"""
298
- api = BoltAPI()
299
-
300
- @api.get(
301
- "/override",
302
- auth=[APIKeyAuthentication(api_keys={"key1"})],
303
- guards=[AllowAny()]
304
- )
305
- async def override_endpoint():
306
- return {"message": "overrides global"}
307
-
308
- handler_id = 0
309
- metadata = api._handler_middleware[handler_id]
310
-
311
- # Should have per-route config, not global
312
- assert metadata["auth_backends"][0]["type"] == "api_key"
313
- assert metadata["guards"][0]["type"] == "allow_any"
314
-
315
-
316
- class TestJWTTokenHandling:
317
- """Test JWT token creation and validation patterns"""
318
-
319
- def test_create_valid_jwt(self):
320
- """Test creating a valid JWT token"""
321
- secret = "test-secret"
322
- payload = {
323
- "sub": "user123",
324
- "exp": int(time.time()) + 3600,
325
- "iat": int(time.time()),
326
- "is_staff": True,
327
- "is_admin": False,
328
- "permissions": ["users.view", "users.create"]
329
- }
330
-
331
- token = jwt.encode(payload, secret, algorithm="HS256")
332
-
333
- # Verify we can decode it
334
- decoded = jwt.decode(token, secret, algorithms=["HS256"])
335
- assert decoded["sub"] == "user123"
336
- assert decoded["is_staff"] == True
337
- assert "users.view" in decoded["permissions"]
338
-
339
- def test_expired_jwt(self):
340
- """Test that expired tokens are rejected"""
341
- secret = "test-secret"
342
- payload = {
343
- "sub": "user123",
344
- "exp": int(time.time()) - 100, # Expired 100 seconds ago
345
- "iat": int(time.time()) - 200,
346
- }
347
-
348
- token = jwt.encode(payload, secret, algorithm="HS256")
349
-
350
- # Should raise ExpiredSignatureError
351
- with pytest.raises(jwt.ExpiredSignatureError):
352
- jwt.decode(token, secret, algorithms=["HS256"])
353
-
354
- def test_invalid_signature(self):
355
- """Test that tokens with wrong signature are rejected"""
356
- token = jwt.encode({"sub": "user"}, "secret1", algorithm="HS256")
357
-
358
- # Try to decode with different secret
359
- with pytest.raises(jwt.InvalidSignatureError):
360
- jwt.decode(token, "secret2", algorithms=["HS256"])
361
-
362
-
363
- class TestContextPopulation:
364
- """Test that request context is properly populated with auth data"""
365
-
366
- def test_context_structure(self):
367
- """Test expected context structure"""
368
- # This would be tested in integration tests
369
- # Here we document the expected structure
370
- expected_context = {
371
- "user_id": "user123",
372
- "is_staff": False,
373
- "is_admin": False,
374
- "auth_backend": "jwt",
375
- "permissions": ["users.view"],
376
- "auth_claims": {
377
- "sub": "user123",
378
- "exp": 1234567890,
379
- "iat": 1234567800,
380
- # ... other JWT claims
381
- }
382
- }
383
-
384
- # Verify structure
385
- assert "user_id" in expected_context
386
- assert "is_staff" in expected_context
387
- assert "is_admin" in expected_context
388
- assert "auth_backend" in expected_context
389
-
390
-
391
- class TestEdgeCases:
392
- """Test edge cases and error conditions"""
393
-
394
- def test_empty_guards_list(self):
395
- """Test route with empty guards list"""
396
- api = BoltAPI()
397
-
398
- @api.get("/no-guards", guards=[])
399
- async def no_guards():
400
- return {"message": "no guards"}
401
-
402
- # Should not create metadata if guards list is empty
403
- handler_id = 0
404
- # Empty guards should result in no metadata or default behavior
405
-
406
- def test_none_auth_parameter(self):
407
- """Test route with auth=None (should use global defaults)"""
408
- api = BoltAPI()
409
-
410
- @api.get("/default-auth", auth=None, guards=[IsAuthenticated()])
411
- async def default_auth():
412
- return {"message": "uses global auth"}
413
-
414
- # Should fall back to global defaults
415
- handler_id = 0
416
- metadata = api._handler_middleware.get(handler_id)
417
- # Verify it uses global defaults or no auth
418
-
419
- def test_mixed_guard_types(self):
420
- """Test route with mix of instance and class guards"""
421
- api = BoltAPI()
422
-
423
- @api.get("/mixed", guards=[IsAuthenticated(), IsStaff])
424
- async def mixed_guards():
425
- return {"message": "mixed guards"}
426
-
427
- handler_id = 0
428
- metadata = api._handler_middleware[handler_id]
429
-
430
- # Both should be compiled correctly
431
- assert len(metadata["guards"]) == 2
432
-
433
- def test_permission_guard_with_special_chars(self):
434
- """Test permission with dots and underscores"""
435
- guard = HasPermission("myapp.custom_permission.delete")
436
- metadata = guard.to_metadata()
437
- assert metadata["permission"] == "myapp.custom_permission.delete"
438
-
439
-
440
- if __name__ == "__main__":
441
- pytest.main([__file__, "-v"])