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,703 +0,0 @@
1
- """
2
- Django ORM Integration Tests for Class-Based Views.
3
-
4
- This test suite verifies that ViewSets and Mixins work correctly with
5
- real Django ORM operations (like Django REST Framework).
6
-
7
- Tests cover:
8
- - Real database queries with Django async ORM
9
- - ViewSet with all CRUD operations
10
- - ListMixin with Article.objects.all()
11
- - RetrieveMixin with Article.objects.aget(pk=pk)
12
- - CreateMixin with Article.objects.acreate(**data)
13
- - UpdateMixin with obj.asave()
14
- - PartialUpdateMixin with partial updates + asave()
15
- - DestroyMixin with obj.adelete()
16
- - End-to-end CRUD workflows
17
- """
18
- import pytest
19
- import msgspec
20
- from django_bolt import BoltAPI
21
- from django_bolt.testing import TestClient
22
- from django_bolt.views import (
23
- APIView,
24
- ViewSet,
25
- ListMixin,
26
- RetrieveMixin,
27
- CreateMixin,
28
- UpdateMixin,
29
- PartialUpdateMixin,
30
- DestroyMixin,
31
- )
32
- from django_bolt.exceptions import HTTPException
33
- from ..test_models import Article
34
-
35
-
36
- # --- Fixtures ---
37
-
38
- @pytest.fixture
39
- def api():
40
- """Create a fresh BoltAPI instance for each test."""
41
- return BoltAPI()
42
-
43
-
44
- @pytest.fixture
45
- def sample_articles(db):
46
- """Create sample articles in the database."""
47
- from asgiref.sync import async_to_sync
48
-
49
- articles = []
50
- for i in range(1, 4):
51
- article = async_to_sync(Article.objects.acreate)(
52
- title=f"Article {i}",
53
- content=f"Content {i}",
54
- author="Test Author",
55
- is_published=(i % 2 == 0),
56
- )
57
- articles.append(article)
58
- return articles
59
-
60
-
61
- # --- Schemas ---
62
-
63
- class ArticleSchema(msgspec.Struct):
64
- """Full article schema (without datetime fields for simplicity)."""
65
- id: int
66
- title: str
67
- content: str
68
- author: str
69
- is_published: bool
70
-
71
- @classmethod
72
- def from_model(cls, obj):
73
- """Convert Django model instance to schema."""
74
- return cls(
75
- id=obj.id,
76
- title=obj.title,
77
- content=obj.content,
78
- author=obj.author,
79
- is_published=obj.is_published,
80
- )
81
-
82
-
83
- class ArticleCreateSchema(msgspec.Struct):
84
- """Schema for creating articles."""
85
- title: str
86
- content: str
87
- author: str
88
-
89
-
90
- class ArticleUpdateSchema(msgspec.Struct):
91
- """Schema for updating articles (full update)."""
92
- title: str
93
- content: str
94
- author: str
95
- is_published: bool
96
-
97
-
98
- class ArticlePartialUpdateSchema(msgspec.Struct):
99
- """Schema for partial updates (all fields optional)."""
100
- title: str | None = None
101
- content: str | None = None
102
- author: str | None = None
103
- is_published: bool | None = None
104
-
105
-
106
- # --- ListMixin Tests ---
107
-
108
- @pytest.mark.django_db(transaction=True)
109
- def test_simple_list_without_mixin(api, sample_articles):
110
- """Test simple list without mixin to debug."""
111
-
112
- @api.view("/articles/simple")
113
- class ArticleListView(APIView):
114
- async def get(self, request) -> list:
115
- articles = []
116
- async for article in Article.objects.all():
117
- articles.append({
118
- "id": article.id,
119
- "title": article.title,
120
- "content": article.content,
121
- "author": article.author,
122
- "is_published": article.is_published,
123
- })
124
- return articles
125
-
126
- with TestClient(api) as client:
127
- response = client.get("/articles/simple")
128
- if response.status_code != 200:
129
- print(f"Error response: {response.text}")
130
- assert response.status_code == 200
131
- data = response.json()
132
- assert len(data) == 3
133
-
134
-
135
- @pytest.mark.django_db(transaction=True)
136
- def test_list_mixin_with_real_django_orm(api, sample_articles):
137
- """Test ListMixin with real Django ORM queryset."""
138
-
139
- @api.view("/articles")
140
- class ArticleListView(ListMixin, APIView):
141
- serializer_class = ArticleSchema
142
-
143
- async def get_queryset(self):
144
- return Article.objects.all()
145
-
146
- with TestClient(api) as client:
147
- response = client.get("/articles")
148
- if response.status_code != 200:
149
- print(f"Error response: {response.text}")
150
- assert response.status_code == 200
151
- data = response.json()
152
-
153
- # Verify we got all articles
154
- assert isinstance(data, list)
155
- assert len(data) == 3
156
-
157
- # Verify data structure
158
- for article_data in data:
159
- assert "id" in article_data
160
- assert "title" in article_data
161
- assert "content" in article_data
162
- assert "author" in article_data
163
- assert "is_published" in article_data
164
-
165
-
166
- @pytest.mark.django_db(transaction=True)
167
- def test_list_mixin_filtered_queryset(api, sample_articles):
168
- """Test ListMixin with filtered Django queryset."""
169
-
170
- @api.view("/articles/published")
171
- class PublishedArticleListView(ListMixin, APIView):
172
- serializer_class = ArticleSchema
173
-
174
- async def get_queryset(self):
175
- return Article.objects.filter(is_published=True)
176
-
177
- with TestClient(api) as client:
178
- response = client.get("/articles/published")
179
- assert response.status_code == 200
180
- data = response.json()
181
-
182
- # Should only get published articles (even numbered from fixture)
183
- assert isinstance(data, list)
184
- assert len(data) == 1 # Only Article 2 is published (2 % 2 == 0)
185
- assert all(article["is_published"] for article in data)
186
-
187
-
188
- # --- RetrieveMixin Tests ---
189
-
190
- @pytest.mark.django_db(transaction=True)
191
- def test_retrieve_mixin_with_real_django_orm(api, sample_articles):
192
- """Test RetrieveMixin with real Django ORM aget()."""
193
-
194
- @api.view("/articles/{pk}")
195
- class ArticleDetailView(RetrieveMixin, ViewSet):
196
- serializer_class = ArticleSchema
197
-
198
- async def get_queryset(self):
199
- return Article.objects.all()
200
-
201
- article_id = sample_articles[0].id
202
-
203
- with TestClient(api) as client:
204
- response = client.get(f"/articles/{article_id}")
205
- assert response.status_code == 200
206
- data = response.json()
207
-
208
- # Verify correct article retrieved
209
- assert data["id"] == article_id
210
- assert data["title"] == "Article 1"
211
- assert data["content"] == "Content 1"
212
- assert data["author"] == "Test Author"
213
-
214
-
215
- @pytest.mark.django_db(transaction=True)
216
- def test_retrieve_mixin_not_found(api):
217
- """Test RetrieveMixin returns 404 when object doesn't exist."""
218
-
219
- @api.view("/articles/{pk}")
220
- class ArticleDetailView(RetrieveMixin, ViewSet):
221
- serializer_class = ArticleSchema
222
-
223
- async def get_queryset(self):
224
- return Article.objects.all()
225
-
226
- with TestClient(api) as client:
227
- response = client.get("/articles/99999")
228
- assert response.status_code == 404
229
-
230
-
231
- # --- CreateMixin Tests ---
232
-
233
- @pytest.mark.django_db(transaction=True)
234
- def test_create_mixin_with_real_django_orm(api):
235
- """Test CreateMixin with real Django ORM acreate()."""
236
-
237
- @api.view("/articles")
238
- class ArticleCreateView(ViewSet):
239
- serializer_class = ArticleSchema
240
-
241
- async def get_queryset(self):
242
- return Article.objects.all()
243
-
244
- async def post(self, request, data: ArticleCreateSchema):
245
- """Create a new article."""
246
- article = await Article.objects.acreate(
247
- title=data.title,
248
- content=data.content,
249
- author=data.author,
250
- )
251
- return ArticleSchema.from_model(article)
252
-
253
- with TestClient(api) as client:
254
- response = client.post(
255
- "/articles",
256
- json={
257
- "title": "New Article",
258
- "content": "New Content",
259
- "author": "New Author",
260
- },
261
- )
262
- assert response.status_code == 200
263
- data = response.json()
264
-
265
- # Verify article was created
266
- assert data["title"] == "New Article"
267
- assert data["content"] == "New Content"
268
- assert data["author"] == "New Author"
269
- assert "id" in data
270
-
271
- # Verify it's actually in the database
272
- from asgiref.sync import async_to_sync
273
- article_id = data["id"]
274
- article = async_to_sync(Article.objects.aget)(id=article_id)
275
- assert article.title == "New Article"
276
- assert article.content == "New Content"
277
-
278
-
279
- # --- UpdateMixin Tests ---
280
-
281
- @pytest.mark.django_db(transaction=True)
282
- def test_update_mixin_with_real_django_orm(api, sample_articles):
283
- """Test UpdateMixin with real Django ORM asave()."""
284
-
285
- @api.view("/articles/{pk}")
286
- class ArticleUpdateView(ViewSet):
287
- serializer_class = ArticleSchema
288
-
289
- async def get_queryset(self):
290
- return Article.objects.all()
291
-
292
- async def put(self, request, pk: int, data: ArticleUpdateSchema):
293
- """Update an article (full update)."""
294
- article = await self.get_object(pk)
295
- article.title = data.title
296
- article.content = data.content
297
- article.author = data.author
298
- article.is_published = data.is_published
299
- await article.asave()
300
- return ArticleSchema.from_model(article)
301
-
302
- article_id = sample_articles[0].id
303
-
304
- with TestClient(api) as client:
305
- response = client.put(
306
- f"/articles/{article_id}",
307
- json={
308
- "title": "Updated Title",
309
- "content": "Updated Content",
310
- "author": "Updated Author",
311
- "is_published": True,
312
- },
313
- )
314
- assert response.status_code == 200
315
- data = response.json()
316
-
317
- # Verify response
318
- assert data["id"] == article_id
319
- assert data["title"] == "Updated Title"
320
- assert data["content"] == "Updated Content"
321
- assert data["is_published"] is True
322
-
323
- # Verify database was updated
324
- from asgiref.sync import async_to_sync
325
- article = async_to_sync(Article.objects.aget)(id=article_id)
326
- assert article.title == "Updated Title"
327
- assert article.content == "Updated Content"
328
- assert article.is_published is True
329
-
330
-
331
- # --- PartialUpdateMixin Tests ---
332
-
333
- @pytest.mark.django_db(transaction=True)
334
- def test_partial_update_mixin_with_real_django_orm(api, sample_articles):
335
- """Test PartialUpdateMixin with real Django ORM asave()."""
336
-
337
- @api.view("/articles/{pk}")
338
- class ArticlePartialUpdateView(ViewSet):
339
- serializer_class = ArticleSchema
340
-
341
- async def get_queryset(self):
342
- return Article.objects.all()
343
-
344
- async def patch(self, request, pk: int, data: ArticlePartialUpdateSchema):
345
- """Partially update an article."""
346
- article = await self.get_object(pk)
347
- if data.title is not None:
348
- article.title = data.title
349
- if data.content is not None:
350
- article.content = data.content
351
- if data.author is not None:
352
- article.author = data.author
353
- if data.is_published is not None:
354
- article.is_published = data.is_published
355
- await article.asave()
356
- return ArticleSchema.from_model(article)
357
-
358
- article_id = sample_articles[0].id
359
- original_content = sample_articles[0].content
360
-
361
- with TestClient(api) as client:
362
- # Only update title, leave other fields unchanged
363
- response = client.patch(
364
- f"/articles/{article_id}",
365
- json={"title": "Partially Updated Title"},
366
- )
367
- assert response.status_code == 200
368
- data = response.json()
369
-
370
- # Verify title was updated
371
- assert data["title"] == "Partially Updated Title"
372
-
373
- # Verify database was updated and other fields unchanged
374
- from asgiref.sync import async_to_sync
375
- article = async_to_sync(Article.objects.aget)(id=article_id)
376
- assert article.title == "Partially Updated Title"
377
- assert article.content == original_content # Unchanged
378
-
379
-
380
- # --- DestroyMixin Tests ---
381
-
382
- @pytest.mark.django_db(transaction=True)
383
- def test_destroy_mixin_with_real_django_orm(api, sample_articles):
384
- """Test DestroyMixin with real Django ORM adelete()."""
385
-
386
- @api.view("/articles/{pk}")
387
- class ArticleDestroyView(DestroyMixin, ViewSet):
388
- async def get_queryset(self):
389
- return Article.objects.all()
390
-
391
- article_id = sample_articles[0].id
392
-
393
- # Verify article exists before deletion
394
- from asgiref.sync import async_to_sync
395
- exists_before = async_to_sync(Article.objects.filter(id=article_id).aexists)()
396
- assert exists_before is True
397
-
398
- with TestClient(api) as client:
399
- response = client.delete(f"/articles/{article_id}")
400
- assert response.status_code == 200
401
- data = response.json()
402
- assert "detail" in data
403
-
404
- # Verify article was deleted from database
405
- exists_after = async_to_sync(Article.objects.filter(id=article_id).aexists)()
406
- assert exists_after is False
407
-
408
-
409
- # --- Full CRUD ViewSet Tests ---
410
-
411
- @pytest.mark.django_db(transaction=True)
412
- def test_full_crud_viewset_with_django_orm(api):
413
- """
414
- Test a complete CRUD ViewSet with all mixins using real Django ORM.
415
- This verifies the ViewSet works like Django REST Framework.
416
- """
417
-
418
- class ArticleViewSet(ViewSet):
419
- serializer_class = ArticleSchema
420
-
421
- async def get_queryset(self):
422
- return Article.objects.all()
423
-
424
- async def get(self, request):
425
- """List all articles."""
426
- articles = []
427
- async for article in Article.objects.all():
428
- articles.append(ArticleSchema.from_model(article))
429
- return articles
430
-
431
- async def post(self, request, data: ArticleCreateSchema):
432
- """Create an article."""
433
- article = await Article.objects.acreate(
434
- title=data.title,
435
- content=data.content,
436
- author=data.author,
437
- )
438
- return ArticleSchema.from_model(article)
439
-
440
- class ArticleDetailViewSet(ViewSet):
441
- serializer_class = ArticleSchema
442
-
443
- async def get_queryset(self):
444
- return Article.objects.all()
445
-
446
- async def get(self, request, pk: int):
447
- """Retrieve an article."""
448
- article = await self.get_object(pk)
449
- return ArticleSchema.from_model(article)
450
-
451
- async def put(self, request, pk: int, data: ArticleUpdateSchema):
452
- """Update an article."""
453
- article = await self.get_object(pk)
454
- article.title = data.title
455
- article.content = data.content
456
- article.author = data.author
457
- article.is_published = data.is_published
458
- await article.asave()
459
- return ArticleSchema.from_model(article)
460
-
461
- async def patch(self, request, pk: int, data: ArticlePartialUpdateSchema):
462
- """Partially update an article."""
463
- article = await self.get_object(pk)
464
- if data.title is not None:
465
- article.title = data.title
466
- if data.content is not None:
467
- article.content = data.content
468
- if data.author is not None:
469
- article.author = data.author
470
- if data.is_published is not None:
471
- article.is_published = data.is_published
472
- await article.asave()
473
- return ArticleSchema.from_model(article)
474
-
475
- async def delete(self, request, pk: int):
476
- """Delete an article."""
477
- article = await self.get_object(pk)
478
- await article.adelete()
479
- return {"detail": "Object deleted successfully"}
480
-
481
- # Register routes with decorator syntax
482
- @api.view("/articles", methods=["GET", "POST"])
483
- class ArticleViewSetRegistered(ArticleViewSet):
484
- pass
485
-
486
- @api.view("/articles/{pk}", methods=["GET", "PUT", "PATCH", "DELETE"])
487
- class ArticleDetailViewSetRegistered(ArticleDetailViewSet):
488
- pass
489
-
490
- with TestClient(api) as client:
491
- # 1. List (should be empty initially)
492
- response = client.get("/articles")
493
- assert response.status_code == 200
494
- assert response.json() == []
495
-
496
- # 2. Create first article
497
- response = client.post(
498
- "/articles",
499
- json={
500
- "title": "First Article",
501
- "content": "First Content",
502
- "author": "Author 1",
503
- },
504
- )
505
- assert response.status_code == 200
506
- article1_id = response.json()["id"]
507
-
508
- # 3. Create second article
509
- response = client.post(
510
- "/articles",
511
- json={
512
- "title": "Second Article",
513
- "content": "Second Content",
514
- "author": "Author 2",
515
- },
516
- )
517
- assert response.status_code == 200
518
- article2_id = response.json()["id"]
519
-
520
- # 4. List (should now have 2 articles)
521
- response = client.get("/articles")
522
- assert response.status_code == 200
523
- articles = response.json()
524
- assert len(articles) == 2
525
-
526
- # 5. Retrieve single article
527
- response = client.get(f"/articles/{article1_id}")
528
- assert response.status_code == 200
529
- data = response.json()
530
- assert data["title"] == "First Article"
531
-
532
- # 6. Update (full)
533
- response = client.put(
534
- f"/articles/{article1_id}",
535
- json={
536
- "title": "Updated First Article",
537
- "content": "Updated Content",
538
- "author": "Updated Author",
539
- "is_published": True,
540
- },
541
- )
542
- assert response.status_code == 200
543
- assert response.json()["title"] == "Updated First Article"
544
-
545
- # 7. Partial update
546
- response = client.patch(
547
- f"/articles/{article2_id}",
548
- json={"title": "Partially Updated Second Article"},
549
- )
550
- assert response.status_code == 200
551
- assert response.json()["title"] == "Partially Updated Second Article"
552
-
553
- # 8. Delete
554
- response = client.delete(f"/articles/{article1_id}")
555
- assert response.status_code == 200
556
-
557
- # 9. Verify deletion
558
- response = client.get(f"/articles/{article1_id}")
559
- assert response.status_code == 404
560
-
561
- # 10. List (should now have 1 article)
562
- response = client.get("/articles")
563
- assert response.status_code == 200
564
- articles = response.json()
565
- assert len(articles) == 1
566
- assert articles[0]["id"] == article2_id
567
-
568
-
569
- # --- Custom ViewSet Tests ---
570
-
571
- @pytest.mark.django_db(transaction=True)
572
- def test_custom_viewset_method_with_django_orm(api, sample_articles):
573
- """Test custom ViewSet method with Django ORM operations."""
574
-
575
- @api.view("/articles/{pk}")
576
- class ArticleViewSet(ViewSet):
577
- async def get_queryset(self):
578
- return Article.objects.all()
579
-
580
- async def get(self, request, pk: int) -> dict:
581
- """Custom retrieve with additional business logic."""
582
- article = await self.get_object(pk)
583
-
584
- # Custom logic: count total articles by same author
585
- author_count = await Article.objects.filter(author=article.author).acount()
586
-
587
- return {
588
- "id": article.id,
589
- "title": article.title,
590
- "content": article.content,
591
- "author": article.author,
592
- "author_article_count": author_count,
593
- }
594
-
595
- article_id = sample_articles[0].id
596
-
597
- with TestClient(api) as client:
598
- response = client.get(f"/articles/{article_id}")
599
- assert response.status_code == 200
600
- data = response.json()
601
-
602
- assert data["id"] == article_id
603
- assert data["title"] == "Article 1"
604
- # All 3 sample articles have same author
605
- assert data["author_article_count"] == 3
606
-
607
-
608
- # --- Queryset Filtering Tests ---
609
-
610
- @pytest.mark.django_db(transaction=True)
611
- def test_viewset_with_filtered_queryset(api, sample_articles):
612
- """Test ViewSet with custom queryset filtering."""
613
-
614
- @api.view("/articles/published")
615
- class PublishedArticleViewSet(ListMixin, ViewSet):
616
- serializer_class = ArticleSchema
617
-
618
- async def get_queryset(self):
619
- # Override to only return published articles
620
- return Article.objects.filter(is_published=True).order_by("-created_at")
621
-
622
- with TestClient(api) as client:
623
- response = client.get("/articles/published")
624
- assert response.status_code == 200
625
- data = response.json()
626
-
627
- # Should only get published articles
628
- assert len(data) == 1
629
- assert all(article["is_published"] for article in data)
630
-
631
-
632
- # --- Edge Cases ---
633
-
634
- @pytest.mark.django_db(transaction=True)
635
- def test_update_nonexistent_article(api):
636
- """Test updating a non-existent article returns 404."""
637
-
638
- @api.view("/articles/{pk}")
639
- class ArticleUpdateView(ViewSet):
640
- serializer_class = ArticleSchema
641
-
642
- async def get_queryset(self):
643
- return Article.objects.all()
644
-
645
- async def put(self, request, pk: int, data: ArticleUpdateSchema):
646
- """Update an article."""
647
- article = await self.get_object(pk) # This will raise 404
648
- article.title = data.title
649
- article.content = data.content
650
- article.author = data.author
651
- article.is_published = data.is_published
652
- await article.asave()
653
- return ArticleSchema.from_model(article)
654
-
655
- with TestClient(api) as client:
656
- response = client.put(
657
- "/articles/99999",
658
- json={
659
- "title": "Test",
660
- "content": "Test",
661
- "author": "Test",
662
- "is_published": False,
663
- },
664
- )
665
- assert response.status_code == 404
666
-
667
-
668
- @pytest.mark.django_db(transaction=True)
669
- def test_delete_nonexistent_article(api):
670
- """Test deleting a non-existent article returns 404."""
671
-
672
- @api.view("/articles/{pk}")
673
- class ArticleDestroyView(DestroyMixin, ViewSet):
674
- async def get_queryset(self):
675
- return Article.objects.all()
676
-
677
- with TestClient(api) as client:
678
- response = client.delete("/articles/99999")
679
- assert response.status_code == 404
680
-
681
-
682
- @pytest.mark.django_db(transaction=True)
683
- def test_async_queryset_iteration(api, sample_articles):
684
- """Test that async queryset iteration works correctly."""
685
-
686
- @api.view("/articles")
687
- class ArticleListView(APIView):
688
- async def get(self, request) -> list:
689
- articles = []
690
- # Test async iteration like ListMixin does
691
- async for article in Article.objects.all():
692
- articles.append({
693
- "id": article.id,
694
- "title": article.title,
695
- })
696
- return articles
697
-
698
- with TestClient(api) as client:
699
- response = client.get("/articles")
700
- assert response.status_code == 200
701
- data = response.json()
702
- assert len(data) == 3
703
- assert all("id" in article and "title" in article for article in data)