django-bolt 0.1.0__cp310-abi3-win_amd64.whl → 0.1.1__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.1.dist-info}/METADATA +179 -197
  16. {django_bolt-0.1.0.dist-info → django_bolt-0.1.1.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.1.dist-info}/WHEEL +0 -0
  56. {django_bolt-0.1.0.dist-info → django_bolt-0.1.1.dist-info}/entry_points.txt +0 -0
@@ -1,303 +0,0 @@
1
- """
2
- Integration tests for guards and authentication with TestClient.
3
-
4
- Tests the full request flow including Rust-side authentication and guard evaluation.
5
- """
6
- import jwt
7
- import time
8
- import pytest
9
- from django_bolt import BoltAPI
10
- from django_bolt.auth import JWTAuthentication, APIKeyAuthentication
11
- from django_bolt.auth import (
12
- AllowAny, IsAuthenticated, IsAdminUser, IsStaff,
13
- HasPermission, HasAnyPermission
14
- )
15
- from django_bolt.testing import TestClient
16
-
17
-
18
- def create_token(user_id="user123", is_staff=False, is_admin=False, permissions=None):
19
- """Helper to create JWT tokens"""
20
- payload = {
21
- "sub": user_id,
22
- "exp": int(time.time()) + 3600,
23
- "iat": int(time.time()),
24
- "is_staff": is_staff,
25
- "is_superuser": is_admin,
26
- "permissions": permissions or []
27
- }
28
- return jwt.encode(payload, "test-secret", algorithm="HS256")
29
-
30
-
31
- @pytest.fixture(scope="module")
32
- def api():
33
- """Create test API with guards and authentication"""
34
- # Setup Django
35
- import django
36
- from django.conf import settings
37
- from django.core.management import call_command
38
-
39
- if not settings.configured:
40
- settings.configure(
41
- DEBUG=True,
42
- SECRET_KEY='test-secret-key-for-guards',
43
- INSTALLED_APPS=[
44
- 'django.contrib.contenttypes',
45
- 'django.contrib.auth',
46
- 'django_bolt',
47
- ],
48
- DATABASES={
49
- 'default': {
50
- 'ENGINE': 'django.db.backends.sqlite3',
51
- 'NAME': ':memory:',
52
- }
53
- },
54
- USE_TZ=True,
55
- )
56
- django.setup()
57
- # Run migrations to create tables
58
- call_command('migrate', '--run-syncdb', verbosity=0)
59
-
60
- api = BoltAPI()
61
-
62
- # Public endpoint with AllowAny
63
- @api.get("/public", guards=[AllowAny()])
64
- async def public_endpoint():
65
- return {"message": "public", "auth": "not required"}
66
-
67
- # Protected endpoint requiring authentication
68
- @api.get(
69
- "/protected",
70
- auth=[JWTAuthentication(secret="test-secret")],
71
- guards=[IsAuthenticated()]
72
- )
73
- async def protected_endpoint(request: dict):
74
- context = request.get("context", {})
75
- return {
76
- "message": "protected",
77
- "user_id": context.get("user_id"),
78
- "is_staff": context.get("is_staff", False),
79
- "is_admin": context.get("is_admin", False),
80
- }
81
-
82
- # Admin-only endpoint
83
- @api.get(
84
- "/admin",
85
- auth=[JWTAuthentication(secret="test-secret")],
86
- guards=[IsAdminUser()]
87
- )
88
- async def admin_endpoint(request: dict):
89
- context = request["context"]
90
- return {
91
- "message": "admin area",
92
- "user_id": context["user_id"],
93
- "is_admin": context["is_admin"],
94
- }
95
-
96
- # Staff-only endpoint
97
- @api.get(
98
- "/staff",
99
- auth=[JWTAuthentication(secret="test-secret")],
100
- guards=[IsStaff()]
101
- )
102
- async def staff_endpoint(request: dict):
103
- return {
104
- "message": "staff area",
105
- "user_id": request["context"]["user_id"],
106
- }
107
-
108
- # Permission-required endpoint
109
- @api.get(
110
- "/delete-users",
111
- auth=[JWTAuthentication(secret="test-secret")],
112
- guards=[HasPermission("users.delete")]
113
- )
114
- async def delete_users_endpoint():
115
- return {"message": "deleting users"}
116
-
117
- # Multiple permissions (any)
118
- @api.get(
119
- "/moderate",
120
- auth=[JWTAuthentication(secret="test-secret")],
121
- guards=[HasAnyPermission("users.moderate", "posts.moderate")]
122
- )
123
- async def moderate_endpoint():
124
- return {"message": "moderating content"}
125
-
126
- # API key authentication
127
- @api.get(
128
- "/api-endpoint",
129
- auth=[APIKeyAuthentication(api_keys={"valid-key-123", "valid-key-456"})],
130
- guards=[IsAuthenticated()]
131
- )
132
- async def api_key_endpoint(request: dict):
133
- return {
134
- "message": "API key valid",
135
- "user_id": request["context"].get("user_id"),
136
- "backend": request["context"].get("auth_backend"),
137
- }
138
-
139
- # Full context inspection endpoint
140
- @api.get(
141
- "/context",
142
- auth=[JWTAuthentication(secret="test-secret")],
143
- guards=[IsAuthenticated()]
144
- )
145
- async def context_endpoint(request: dict):
146
- context = request.get("context", {})
147
- return {
148
- "context_keys": list(context.keys()) if hasattr(context, 'keys') else [],
149
- "user_id": context.get("user_id"),
150
- "is_staff": context.get("is_staff"),
151
- "is_admin": context.get("is_admin"),
152
- "auth_backend": context.get("auth_backend"),
153
- "has_claims": "auth_claims" in context,
154
- "has_permissions": "permissions" in context,
155
- }
156
-
157
- return api
158
-
159
-
160
- @pytest.fixture(scope="module")
161
- def client(api):
162
- """Create TestClient for the API"""
163
- with TestClient(api) as client:
164
- yield client
165
-
166
-
167
- def test_public_endpoint(client):
168
- """Test public endpoint (AllowAny)"""
169
- response = client.get("/public")
170
- assert response.status_code == 200
171
- assert response.json()["auth"] == "not required"
172
-
173
-
174
- def test_protected_endpoint_without_token(client):
175
- """Test protected endpoint without token (should fail with 401)"""
176
- response = client.get("/protected")
177
- assert response.status_code == 401
178
-
179
-
180
- def test_protected_endpoint_with_valid_token(client):
181
- """Test protected endpoint with valid token"""
182
- token = create_token(user_id="user123")
183
- response = client.get("/protected", headers={"Authorization": f"Bearer {token}"})
184
- assert response.status_code == 200
185
- data = response.json()
186
- assert data["message"] == "protected"
187
- assert data["user_id"] == "user123"
188
-
189
-
190
- def test_admin_endpoint_with_non_admin_token(client):
191
- """Test admin endpoint with non-admin token (should fail with 403)"""
192
- token = create_token(user_id="regular-user", is_admin=False)
193
- response = client.get("/admin", headers={"Authorization": f"Bearer {token}"})
194
- assert response.status_code == 403
195
-
196
-
197
- def test_admin_endpoint_with_admin_token(client):
198
- """Test admin endpoint with admin token"""
199
- token = create_token(user_id="admin-user", is_admin=True)
200
- response = client.get("/admin", headers={"Authorization": f"Bearer {token}"})
201
- assert response.status_code == 200
202
- data = response.json()
203
- assert data["message"] == "admin area"
204
- assert data["is_admin"] is True
205
-
206
-
207
- def test_staff_endpoint_with_non_staff_token(client):
208
- """Test staff endpoint with non-staff token"""
209
- token = create_token(user_id="regular-user", is_staff=False)
210
- response = client.get("/staff", headers={"Authorization": f"Bearer {token}"})
211
- assert response.status_code == 403
212
-
213
-
214
- def test_staff_endpoint_with_staff_token(client):
215
- """Test staff endpoint with staff token"""
216
- token = create_token(user_id="staff-user", is_staff=True)
217
- response = client.get("/staff", headers={"Authorization": f"Bearer {token}"})
218
- assert response.status_code == 200
219
- data = response.json()
220
- assert data["message"] == "staff area"
221
-
222
-
223
- def test_permission_based_endpoint_without_permission(client):
224
- """Test permission-based endpoint without required permission"""
225
- token = create_token(user_id="user-no-perms", permissions=[])
226
- response = client.get("/delete-users", headers={"Authorization": f"Bearer {token}"})
227
- assert response.status_code == 403
228
-
229
-
230
- def test_permission_based_endpoint_with_permission(client):
231
- """Test permission-based endpoint with required permission"""
232
- token = create_token(user_id="user-with-perms", permissions=["users.delete"])
233
- response = client.get("/delete-users", headers={"Authorization": f"Bearer {token}"})
234
- assert response.status_code == 200
235
- assert response.json()["message"] == "deleting users"
236
-
237
-
238
- def test_has_any_permission_with_one_match(client):
239
- """Test HasAnyPermission guard with one matching permission"""
240
- token = create_token(user_id="moderator", permissions=["users.moderate"])
241
- response = client.get("/moderate", headers={"Authorization": f"Bearer {token}"})
242
- assert response.status_code == 200
243
- assert response.json()["message"] == "moderating content"
244
-
245
-
246
- def test_has_any_permission_without_match(client):
247
- """Test HasAnyPermission guard without any matching permission"""
248
- token = create_token(user_id="user", permissions=["other.permission"])
249
- response = client.get("/moderate", headers={"Authorization": f"Bearer {token}"})
250
- assert response.status_code == 403
251
-
252
-
253
- def test_api_key_authentication_without_key(client):
254
- """Test API key authentication without key"""
255
- response = client.get("/api-endpoint")
256
- assert response.status_code == 401
257
-
258
-
259
- def test_api_key_authentication_with_valid_key(client):
260
- """Test API key authentication with valid key"""
261
- response = client.get("/api-endpoint", headers={"X-API-Key": "valid-key-123"})
262
- assert response.status_code == 200
263
- data = response.json()
264
- assert data["message"] == "API key valid"
265
-
266
-
267
- def test_api_key_authentication_with_invalid_key(client):
268
- """Test API key authentication with invalid key"""
269
- response = client.get("/api-endpoint", headers={"X-API-Key": "invalid-key"})
270
- assert response.status_code == 401
271
-
272
-
273
- def test_context_population(client):
274
- """Test that context is properly populated"""
275
- token = create_token(user_id="context-user", is_staff=True, permissions=["test.permission"])
276
- response = client.get("/context", headers={"Authorization": f"Bearer {token}"})
277
- assert response.status_code == 200
278
- data = response.json()
279
- assert data["user_id"] == "context-user"
280
- assert data["is_staff"] is True
281
- assert "context_keys" in data
282
-
283
-
284
- def test_invalid_jwt_signature(client):
285
- """Test invalid JWT signature"""
286
- token = jwt.encode(
287
- {"sub": "user123", "exp": int(time.time()) + 3600},
288
- "wrong-secret", # Different secret
289
- algorithm="HS256"
290
- )
291
- response = client.get("/protected", headers={"Authorization": f"Bearer {token}"})
292
- assert response.status_code == 401
293
-
294
-
295
- def test_expired_jwt_token(client):
296
- """Test expired JWT token"""
297
- token = jwt.encode(
298
- {"sub": "user123", "exp": int(time.time()) - 3600}, # Expired
299
- "test-secret",
300
- algorithm="HS256"
301
- )
302
- response = client.get("/protected", headers={"Authorization": f"Bearer {token}"})
303
- assert response.status_code == 401
@@ -1,283 +0,0 @@
1
- """Tests for Django-Bolt health check system."""
2
-
3
- import pytest
4
- import asyncio
5
- from django_bolt.health import (
6
- HealthCheck,
7
- check_database,
8
- health_handler,
9
- ready_handler,
10
- add_health_check,
11
- register_health_checks,
12
- )
13
-
14
-
15
- class TestHealthCheck:
16
- """Test HealthCheck class."""
17
-
18
- def test_health_check_initialization(self):
19
- """Test HealthCheck initialization."""
20
- hc = HealthCheck()
21
- assert hc._checks == []
22
-
23
- def test_add_check(self):
24
- """Test adding custom health checks."""
25
- hc = HealthCheck()
26
-
27
- async def custom_check():
28
- return True, "OK"
29
-
30
- hc.add_check(custom_check)
31
- assert len(hc._checks) == 1
32
- assert hc._checks[0] == custom_check
33
-
34
- @pytest.mark.asyncio
35
- async def test_run_checks_all_healthy(self):
36
- """Test run_checks when all checks pass."""
37
- hc = HealthCheck()
38
-
39
- async def check1():
40
- return True, "Check 1 OK"
41
-
42
- async def check2():
43
- return True, "Check 2 OK"
44
-
45
- hc.add_check(check1)
46
- hc.add_check(check2)
47
-
48
- results = await hc.run_checks()
49
- assert results["status"] == "healthy"
50
- assert "check1" in results["checks"]
51
- assert "check2" in results["checks"]
52
- assert results["checks"]["check1"]["healthy"] is True
53
- assert results["checks"]["check2"]["healthy"] is True
54
-
55
- @pytest.mark.asyncio
56
- async def test_run_checks_one_unhealthy(self):
57
- """Test run_checks when one check fails."""
58
- hc = HealthCheck()
59
-
60
- async def check1():
61
- return True, "Check 1 OK"
62
-
63
- async def check2():
64
- return False, "Check 2 failed"
65
-
66
- hc.add_check(check1)
67
- hc.add_check(check2)
68
-
69
- results = await hc.run_checks()
70
- assert results["status"] == "unhealthy"
71
- assert results["checks"]["check1"]["healthy"] is True
72
- assert results["checks"]["check2"]["healthy"] is False
73
- assert "failed" in results["checks"]["check2"]["message"]
74
-
75
- @pytest.mark.asyncio
76
- async def test_run_checks_exception_handling(self):
77
- """Test run_checks handles exceptions in checks."""
78
- hc = HealthCheck()
79
-
80
- async def failing_check():
81
- raise RuntimeError("Check crashed")
82
-
83
- hc.add_check(failing_check)
84
-
85
- results = await hc.run_checks()
86
- assert results["status"] == "unhealthy"
87
- assert results["checks"]["failing_check"]["healthy"] is False
88
- assert "Check crashed" in results["checks"]["failing_check"]["message"]
89
-
90
-
91
- class TestDatabaseCheck:
92
- """Test database health check."""
93
-
94
- @pytest.mark.asyncio
95
- async def test_check_database_success(self):
96
- """Test database check succeeds with valid connection."""
97
- # This test requires Django to be configured
98
- try:
99
- from django.conf import settings
100
- if not settings.configured:
101
- pytest.skip("Django not configured")
102
-
103
- healthy, message = await check_database()
104
- # Should either succeed or fail gracefully
105
- assert isinstance(healthy, bool)
106
- assert isinstance(message, str)
107
- except ImportError:
108
- pytest.skip("Django not available")
109
-
110
- @pytest.mark.asyncio
111
- async def test_check_database_handles_error(self):
112
- """Test database check handles connection errors."""
113
- # Even if database is not available, check should not raise
114
- try:
115
- healthy, message = await check_database()
116
- assert isinstance(healthy, bool)
117
- assert isinstance(message, str)
118
- except Exception:
119
- pytest.fail("check_database should not raise exceptions")
120
-
121
-
122
- class TestHealthHandlers:
123
- """Test health endpoint handlers."""
124
-
125
- @pytest.mark.asyncio
126
- async def test_health_handler(self):
127
- """Test basic health endpoint."""
128
- result = await health_handler()
129
- assert isinstance(result, dict)
130
- assert result["status"] == "ok"
131
-
132
- @pytest.mark.asyncio
133
- async def test_ready_handler(self):
134
- """Test readiness endpoint."""
135
- result = await ready_handler()
136
- assert isinstance(result, dict)
137
- assert "status" in result
138
- assert "checks" in result
139
- # Status should be healthy or unhealthy
140
- assert result["status"] in ["healthy", "unhealthy"]
141
-
142
-
143
- class TestHealthIntegration:
144
- """Integration tests for health checks."""
145
-
146
- def test_register_health_checks(self):
147
- """Test registering health checks on API."""
148
- from django_bolt import BoltAPI
149
-
150
- api = BoltAPI()
151
- initial_route_count = len(api._routes)
152
-
153
- register_health_checks(api)
154
-
155
- # Check that routes were registered (should have 2 more routes)
156
- assert len(api._routes) == initial_route_count + 2
157
-
158
- # Check handler names contain health/ready
159
- handlers = [api._handlers[route[2]] for route in api._routes[initial_route_count:]]
160
- handler_names = [h.__name__ for h in handlers]
161
- assert "health_handler" in handler_names
162
- assert "ready_handler" in handler_names
163
-
164
- def test_add_health_check_global(self):
165
- """Test adding global health check."""
166
- async def custom_check():
167
- return True, "Custom check OK"
168
-
169
- add_health_check(custom_check)
170
-
171
- # Check should be added to global instance
172
- from django_bolt.health import _health_check
173
- assert custom_check in _health_check._checks
174
-
175
- # Clean up
176
- _health_check._checks.remove(custom_check)
177
-
178
- @pytest.mark.asyncio
179
- async def test_custom_health_check_integration(self):
180
- """Test custom health check integration."""
181
- from django_bolt.health import _health_check
182
-
183
- # Clear existing checks
184
- original_checks = _health_check._checks.copy()
185
- _health_check._checks.clear()
186
-
187
- # Add custom check
188
- async def redis_check():
189
- # Simulate Redis check
190
- return True, "Redis OK"
191
-
192
- add_health_check(redis_check)
193
-
194
- # Run ready handler
195
- result = await ready_handler()
196
- assert result["status"] == "healthy"
197
- assert "redis_check" in result["checks"]
198
- assert result["checks"]["redis_check"]["healthy"] is True
199
-
200
- # Restore original checks
201
- _health_check._checks = original_checks
202
-
203
-
204
- class TestHealthCheckScenarios:
205
- """Test real-world health check scenarios."""
206
-
207
- @pytest.mark.asyncio
208
- async def test_multiple_services_all_healthy(self):
209
- """Test health check with multiple healthy services."""
210
- hc = HealthCheck()
211
-
212
- async def check_database():
213
- return True, "Database OK"
214
-
215
- async def check_redis():
216
- return True, "Redis OK"
217
-
218
- async def check_queue():
219
- return True, "Queue OK"
220
-
221
- hc.add_check(check_database)
222
- hc.add_check(check_redis)
223
- hc.add_check(check_queue)
224
-
225
- results = await hc.run_checks()
226
- assert results["status"] == "healthy"
227
- assert len(results["checks"]) == 3
228
-
229
- @pytest.mark.asyncio
230
- async def test_multiple_services_one_degraded(self):
231
- """Test health check with one degraded service."""
232
- hc = HealthCheck()
233
-
234
- async def check_database():
235
- return True, "Database OK"
236
-
237
- async def check_redis():
238
- return False, "Redis connection timeout"
239
-
240
- async def check_queue():
241
- return True, "Queue OK"
242
-
243
- hc.add_check(check_database)
244
- hc.add_check(check_redis)
245
- hc.add_check(check_queue)
246
-
247
- results = await hc.run_checks()
248
- assert results["status"] == "unhealthy"
249
- assert results["checks"]["check_database"]["healthy"] is True
250
- assert results["checks"]["check_redis"]["healthy"] is False
251
- assert results["checks"]["check_queue"]["healthy"] is True
252
-
253
- @pytest.mark.asyncio
254
- async def test_async_check_performance(self):
255
- """Test that async checks run concurrently."""
256
- hc = HealthCheck()
257
- import time
258
-
259
- start_time = time.time()
260
-
261
- async def slow_check1():
262
- await asyncio.sleep(0.1)
263
- return True, "Check 1 OK"
264
-
265
- async def slow_check2():
266
- await asyncio.sleep(0.1)
267
- return True, "Check 2 OK"
268
-
269
- hc.add_check(slow_check1)
270
- hc.add_check(slow_check2)
271
-
272
- results = await hc.run_checks()
273
- elapsed = time.time() - start_time
274
-
275
- # If checks run sequentially, would take ~0.2s
276
- # If checks run concurrently, should take ~0.1s
277
- # We're currently running sequentially, so this will be >0.15s
278
- # TODO: Optimize to run checks concurrently
279
- assert elapsed < 0.3 # Just check it completes reasonably fast
280
-
281
-
282
- if __name__ == "__main__":
283
- pytest.main([__file__, "-v"])