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,458 +0,0 @@
1
- """
2
- Test JWT Token class for Django-Bolt.
3
-
4
- Tests the Token dataclass for encoding/decoding JWTs with performance focus.
5
- """
6
- import pytest
7
- from datetime import datetime, timedelta, timezone
8
- from django_bolt.auth import Token
9
- import jwt as pyjwt
10
-
11
-
12
- class TestTokenCreation:
13
- """Test Token creation and validation"""
14
-
15
- def test_create_basic_token(self):
16
- """Test creating a basic token"""
17
- exp = datetime.now(timezone.utc) + timedelta(hours=1)
18
- token = Token(
19
- sub="user123",
20
- exp=exp,
21
- )
22
-
23
- assert token.sub == "user123"
24
- assert token.exp == exp.replace(microsecond=0) # Microseconds stripped
25
- assert token.iat <= datetime.now(timezone.utc)
26
-
27
- def test_create_token_with_staff_flags(self):
28
- """Test creating token with staff/admin flags"""
29
- exp = datetime.now(timezone.utc) + timedelta(hours=1)
30
- token = Token(
31
- sub="admin123",
32
- exp=exp,
33
- is_staff=True,
34
- is_admin=True,
35
- )
36
-
37
- assert token.is_staff is True
38
- assert token.is_admin is True
39
-
40
- def test_create_token_with_permissions(self):
41
- """Test creating token with permissions list"""
42
- exp = datetime.now(timezone.utc) + timedelta(hours=1)
43
- token = Token(
44
- sub="user123",
45
- exp=exp,
46
- permissions=["users.view", "users.create", "posts.edit"],
47
- )
48
-
49
- assert len(token.permissions) == 3
50
- assert "users.view" in token.permissions
51
-
52
- def test_create_token_with_audience_issuer(self):
53
- """Test creating token with aud and iss claims"""
54
- exp = datetime.now(timezone.utc) + timedelta(hours=1)
55
- token = Token(
56
- sub="user123",
57
- exp=exp,
58
- aud="my-api",
59
- iss="auth-service",
60
- )
61
-
62
- assert token.aud == "my-api"
63
- assert token.iss == "auth-service"
64
-
65
- def test_create_token_with_extras(self):
66
- """Test creating token with custom extra claims"""
67
- exp = datetime.now(timezone.utc) + timedelta(hours=1)
68
- token = Token(
69
- sub="user123",
70
- exp=exp,
71
- extras={
72
- "tenant_id": "acme-corp",
73
- "role": "manager",
74
- "department": "engineering",
75
- }
76
- )
77
-
78
- assert token.extras["tenant_id"] == "acme-corp"
79
- assert token.extras["role"] == "manager"
80
-
81
- def test_create_factory_method(self):
82
- """Test Token.create() factory method"""
83
- token = Token.create(
84
- sub="user123",
85
- expires_delta=timedelta(minutes=30),
86
- issuer="my-service",
87
- is_staff=True,
88
- permissions=["read", "write"],
89
- tenant="acme",
90
- )
91
-
92
- assert token.sub == "user123"
93
- assert token.iss == "my-service"
94
- assert token.is_staff is True
95
- assert token.permissions == ["read", "write"]
96
- assert token.extras["tenant"] == "acme"
97
-
98
- # Check expiration is ~30 minutes from now
99
- now = datetime.now(timezone.utc)
100
- exp_delta = (token.exp - now).total_seconds()
101
- assert 1790 < exp_delta < 1810 # ~30 minutes with some tolerance
102
-
103
-
104
- class TestTokenValidation:
105
- """Test Token validation logic"""
106
-
107
- def test_empty_subject_raises_error(self):
108
- """Test that empty subject raises ValueError"""
109
- exp = datetime.now(timezone.utc) + timedelta(hours=1)
110
-
111
- with pytest.raises(ValueError, match="sub.*must be.*non-empty"):
112
- Token(sub="", exp=exp)
113
-
114
- def test_past_expiration_raises_error(self):
115
- """Test that past expiration raises ValueError"""
116
- exp = datetime.now(timezone.utc) - timedelta(hours=1)
117
-
118
- with pytest.raises(ValueError, match="exp.*must be in the future"):
119
- Token(sub="user123", exp=exp)
120
-
121
- def test_future_iat_raises_error(self):
122
- """Test that future iat raises ValueError"""
123
- exp = datetime.now(timezone.utc) + timedelta(hours=2)
124
- iat = datetime.now(timezone.utc) + timedelta(hours=1)
125
-
126
- with pytest.raises(ValueError, match="iat.*must be current or past"):
127
- Token(sub="user123", exp=exp, iat=iat)
128
-
129
- def test_datetime_normalization(self):
130
- """Test that datetimes are normalized to UTC without microseconds"""
131
- exp = datetime.now(timezone.utc) + timedelta(hours=1)
132
- exp_with_micros = exp.replace(microsecond=123456)
133
-
134
- token = Token(sub="user123", exp=exp_with_micros)
135
-
136
- # Should have microseconds stripped
137
- assert token.exp.microsecond == 0
138
- assert token.iat.microsecond == 0
139
-
140
-
141
- class TestTokenEncoding:
142
- """Test Token encoding to JWT strings"""
143
-
144
- def test_encode_basic_token(self):
145
- """Test encoding a basic token"""
146
- exp = datetime.now(timezone.utc) + timedelta(hours=1)
147
- token = Token(sub="user123", exp=exp)
148
-
149
- secret = "test-secret"
150
- encoded = token.encode(secret=secret)
151
-
152
- # Should be a valid JWT string (3 parts separated by dots)
153
- assert isinstance(encoded, str)
154
- parts = encoded.split('.')
155
- assert len(parts) == 3
156
-
157
- # Decode to verify contents
158
- decoded = pyjwt.decode(encoded, secret, algorithms=["HS256"])
159
- assert decoded["sub"] == "user123"
160
- assert "exp" in decoded
161
- assert "iat" in decoded
162
-
163
- def test_encode_with_claims(self):
164
- """Test encoding token with all claims"""
165
- exp = datetime.now(timezone.utc) + timedelta(hours=1)
166
- token = Token(
167
- sub="user123",
168
- exp=exp,
169
- is_staff=True,
170
- is_admin=False,
171
- permissions=["read", "write"],
172
- aud="my-api",
173
- iss="auth-service",
174
- )
175
-
176
- secret = "test-secret"
177
- encoded = token.encode(secret=secret)
178
-
179
- decoded = pyjwt.decode(
180
- encoded,
181
- secret,
182
- algorithms=["HS256"],
183
- audience="my-api",
184
- issuer="auth-service"
185
- )
186
-
187
- assert decoded["sub"] == "user123"
188
- assert decoded["is_staff"] is True
189
- assert decoded["is_admin"] is False
190
- assert decoded["permissions"] == ["read", "write"]
191
- assert decoded["aud"] == "my-api"
192
- assert decoded["iss"] == "auth-service"
193
-
194
- def test_encode_with_different_algorithms(self):
195
- """Test encoding with different algorithms"""
196
- exp = datetime.now(timezone.utc) + timedelta(hours=1)
197
- token = Token(sub="user123", exp=exp)
198
-
199
- for algorithm in ["HS256", "HS384", "HS512"]:
200
- secret = "test-secret-for-" + algorithm
201
- encoded = token.encode(secret=secret, algorithm=algorithm)
202
-
203
- decoded = pyjwt.decode(encoded, secret, algorithms=[algorithm])
204
- assert decoded["sub"] == "user123"
205
-
206
- def test_encode_with_custom_headers(self):
207
- """Test encoding with custom JWT headers"""
208
- exp = datetime.now(timezone.utc) + timedelta(hours=1)
209
- token = Token(sub="user123", exp=exp)
210
-
211
- secret = "test-secret"
212
- headers = {"kid": "key-id-123", "typ": "JWT"}
213
- encoded = token.encode(secret=secret, headers=headers)
214
-
215
- # Decode without verification to check headers
216
- unverified = pyjwt.get_unverified_header(encoded)
217
- assert unverified["kid"] == "key-id-123"
218
-
219
-
220
- class TestTokenDecoding:
221
- """Test Token decoding from JWT strings"""
222
-
223
- def test_decode_basic_token(self):
224
- """Test decoding a basic token"""
225
- exp = datetime.now(timezone.utc) + timedelta(hours=1)
226
- original = Token(sub="user123", exp=exp)
227
-
228
- secret = "test-secret"
229
- encoded = original.encode(secret=secret)
230
-
231
- # Decode back
232
- decoded = Token.decode(encoded, secret=secret)
233
-
234
- assert decoded.sub == "user123"
235
- assert decoded.exp.timestamp() == pytest.approx(original.exp.timestamp(), abs=1)
236
- assert decoded.iat.timestamp() == pytest.approx(original.iat.timestamp(), abs=1)
237
-
238
- def test_decode_with_all_claims(self):
239
- """Test decoding token with all claims"""
240
- exp = datetime.now(timezone.utc) + timedelta(hours=1)
241
- original = Token(
242
- sub="admin123",
243
- exp=exp,
244
- is_staff=True,
245
- is_admin=True,
246
- permissions=["full-access"],
247
- aud="my-api",
248
- iss="auth-service",
249
- jti="unique-id-123",
250
- )
251
-
252
- secret = "test-secret"
253
- encoded = original.encode(secret=secret)
254
-
255
- decoded = Token.decode(
256
- encoded,
257
- secret=secret,
258
- audience="my-api",
259
- issuer="auth-service"
260
- )
261
-
262
- assert decoded.sub == "admin123"
263
- assert decoded.is_staff is True
264
- assert decoded.is_admin is True
265
- assert decoded.permissions == ["full-access"]
266
- assert decoded.aud == "my-api"
267
- assert decoded.iss == "auth-service"
268
- assert decoded.jti == "unique-id-123"
269
-
270
- def test_decode_with_extras(self):
271
- """Test decoding token with extra claims"""
272
- exp = datetime.now(timezone.utc) + timedelta(hours=1)
273
- original = Token(
274
- sub="user123",
275
- exp=exp,
276
- extras={"tenant": "acme", "role": "admin", "level": 5}
277
- )
278
-
279
- secret = "test-secret"
280
- encoded = original.encode(secret=secret)
281
-
282
- decoded = Token.decode(encoded, secret=secret)
283
-
284
- assert decoded.extras["tenant"] == "acme"
285
- assert decoded.extras["role"] == "admin"
286
- assert decoded.extras["level"] == 5
287
-
288
- def test_decode_expired_token_raises_error(self):
289
- """Test that decoding expired token raises ValueError"""
290
- # Create token that will be expired
291
- payload = {
292
- "sub": "user123",
293
- "exp": int((datetime.now(timezone.utc) - timedelta(hours=1)).timestamp()),
294
- "iat": int((datetime.now(timezone.utc) - timedelta(hours=2)).timestamp()),
295
- }
296
-
297
- secret = "test-secret"
298
- encoded = pyjwt.encode(payload, secret, algorithm="HS256")
299
-
300
- with pytest.raises(ValueError, match="Failed to decode token"):
301
- Token.decode(encoded, secret=secret)
302
-
303
- def test_decode_with_wrong_secret_raises_error(self):
304
- """Test that decoding with wrong secret raises ValueError"""
305
- exp = datetime.now(timezone.utc) + timedelta(hours=1)
306
- token = Token(sub="user123", exp=exp)
307
-
308
- encoded = token.encode(secret="secret1")
309
-
310
- with pytest.raises(ValueError, match="Failed to decode token"):
311
- Token.decode(encoded, secret="wrong-secret")
312
-
313
- def test_decode_with_audience_mismatch_raises_error(self):
314
- """Test that audience mismatch raises ValueError"""
315
- exp = datetime.now(timezone.utc) + timedelta(hours=1)
316
- token = Token(sub="user123", exp=exp, aud="api1")
317
-
318
- secret = "test-secret"
319
- encoded = token.encode(secret=secret)
320
-
321
- with pytest.raises(ValueError, match="Failed to decode token"):
322
- Token.decode(encoded, secret=secret, audience="wrong-audience")
323
-
324
- def test_decode_skip_expiry_verification(self):
325
- """Test decoding with expiry verification disabled"""
326
- # Create expired token
327
- payload = {
328
- "sub": "user123",
329
- "exp": int((datetime.now(timezone.utc) - timedelta(hours=1)).timestamp()),
330
- "iat": int((datetime.now(timezone.utc) - timedelta(hours=2)).timestamp()),
331
- }
332
-
333
- secret = "test-secret"
334
- encoded = pyjwt.encode(payload, secret, algorithm="HS256")
335
-
336
- # Should succeed with verify_exp=False
337
- decoded = Token.decode(encoded, secret=secret, verify_exp=False)
338
- assert decoded.sub == "user123"
339
-
340
-
341
- class TestTokenToDictConversion:
342
- """Test Token.to_dict() conversion"""
343
-
344
- def test_to_dict_basic(self):
345
- """Test converting token to dict"""
346
- exp = datetime.now(timezone.utc) + timedelta(hours=1)
347
- token = Token(sub="user123", exp=exp)
348
-
349
- d = token.to_dict()
350
-
351
- assert d["sub"] == "user123"
352
- assert isinstance(d["exp"], int) # Unix timestamp
353
- assert isinstance(d["iat"], int)
354
- assert d["exp"] > d["iat"]
355
-
356
- def test_to_dict_with_optional_fields(self):
357
- """Test to_dict with optional fields"""
358
- exp = datetime.now(timezone.utc) + timedelta(hours=1)
359
- token = Token(
360
- sub="user123",
361
- exp=exp,
362
- aud="my-api",
363
- iss="auth",
364
- jti="unique",
365
- is_staff=True,
366
- )
367
-
368
- d = token.to_dict()
369
-
370
- assert d["aud"] == "my-api"
371
- assert d["iss"] == "auth"
372
- assert d["jti"] == "unique"
373
- assert d["is_staff"] is True
374
-
375
- def test_to_dict_excludes_none_values(self):
376
- """Test that None values are excluded from dict"""
377
- exp = datetime.now(timezone.utc) + timedelta(hours=1)
378
- token = Token(
379
- sub="user123",
380
- exp=exp,
381
- aud=None,
382
- iss=None,
383
- )
384
-
385
- d = token.to_dict()
386
-
387
- assert "aud" not in d
388
- assert "iss" not in d
389
-
390
- def test_to_dict_includes_extras(self):
391
- """Test that extras are included in dict"""
392
- exp = datetime.now(timezone.utc) + timedelta(hours=1)
393
- token = Token(
394
- sub="user123",
395
- exp=exp,
396
- extras={"custom1": "value1", "custom2": 42}
397
- )
398
-
399
- d = token.to_dict()
400
-
401
- assert d["custom1"] == "value1"
402
- assert d["custom2"] == 42
403
-
404
-
405
- class TestTokenRoundTrip:
406
- """Test encoding and decoding round-trip"""
407
-
408
- def test_round_trip_preserves_data(self):
409
- """Test that encode->decode preserves all data"""
410
- exp = datetime.now(timezone.utc) + timedelta(hours=1)
411
- original = Token(
412
- sub="user123",
413
- exp=exp,
414
- is_staff=True,
415
- is_admin=False,
416
- permissions=["read", "write", "delete"],
417
- aud="my-api",
418
- iss="auth-service",
419
- extras={"tenant": "acme", "role": "manager"}
420
- )
421
-
422
- secret = "test-secret"
423
- encoded = original.encode(secret=secret)
424
- decoded = Token.decode(
425
- encoded,
426
- secret=secret,
427
- audience="my-api",
428
- issuer="auth-service"
429
- )
430
-
431
- # Compare all fields (timestamps may differ slightly due to rounding)
432
- assert decoded.sub == original.sub
433
- assert decoded.exp.timestamp() == pytest.approx(original.exp.timestamp(), abs=1)
434
- assert decoded.is_staff == original.is_staff
435
- assert decoded.is_admin == original.is_admin
436
- assert decoded.permissions == original.permissions
437
- assert decoded.aud == original.aud
438
- assert decoded.iss == original.iss
439
- assert decoded.extras == original.extras
440
-
441
- def test_multiple_round_trips(self):
442
- """Test multiple encode/decode cycles"""
443
- exp = datetime.now(timezone.utc) + timedelta(hours=1)
444
- token = Token(sub="user123", exp=exp, is_staff=True)
445
-
446
- secret = "test-secret"
447
-
448
- # Multiple round trips
449
- for _ in range(3):
450
- encoded = token.encode(secret=secret)
451
- token = Token.decode(encoded, secret=secret)
452
-
453
- assert token.sub == "user123"
454
- assert token.is_staff is True
455
-
456
-
457
- if __name__ == "__main__":
458
- pytest.main([__file__, "-v"])