@smicolon/ai-kit 0.3.2 → 0.4.0
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.
- package/README.md +73 -40
- package/dist/index.js +260 -126
- package/package.json +5 -5
- package/.claude-plugin/marketplace.json +0 -369
- package/packs/architect/CHANGELOG.md +0 -17
- package/packs/architect/README.md +0 -58
- package/packs/architect/agents/system-architect.md +0 -768
- package/packs/architect/commands/diagram-create.md +0 -300
- package/packs/better-auth/.mcp.json +0 -14
- package/packs/better-auth/CHANGELOG.md +0 -26
- package/packs/better-auth/README.md +0 -125
- package/packs/better-auth/agents/auth-architect.md +0 -278
- package/packs/better-auth/commands/auth-provider-add.md +0 -265
- package/packs/better-auth/commands/auth-setup.md +0 -298
- package/packs/better-auth/skills/auth-security/SKILL.md +0 -425
- package/packs/better-auth/skills/better-auth-patterns/SKILL.md +0 -455
- package/packs/dev-loop/CHANGELOG.md +0 -69
- package/packs/dev-loop/README.md +0 -155
- package/packs/dev-loop/commands/cancel-dev.md +0 -21
- package/packs/dev-loop/commands/dev-loop.md +0 -72
- package/packs/dev-loop/commands/dev-plan.md +0 -351
- package/packs/dev-loop/hooks/hooks.json +0 -15
- package/packs/dev-loop/hooks/stop-hook.sh +0 -178
- package/packs/dev-loop/scripts/setup-dev-loop.sh +0 -194
- package/packs/dev-loop/skills/tdd-planner/SKILL.md +0 -249
- package/packs/dev-loop/skills/tdd-planner/references/framework-patterns.md +0 -874
- package/packs/dev-loop/skills/tdd-planner/references/good-example.md +0 -260
- package/packs/dev-loop/skills/tdd-planner/references/plan-template.md +0 -275
- package/packs/django/CHANGELOG.md +0 -39
- package/packs/django/README.md +0 -92
- package/packs/django/agents/django-architect.md +0 -182
- package/packs/django/agents/django-builder.md +0 -250
- package/packs/django/agents/django-feature-based.md +0 -420
- package/packs/django/agents/django-reviewer.md +0 -253
- package/packs/django/agents/django-tester.md +0 -230
- package/packs/django/commands/api-endpoint.md +0 -285
- package/packs/django/commands/model-create.md +0 -178
- package/packs/django/commands/test-generate.md +0 -325
- package/packs/django/rules/migrations.md +0 -138
- package/packs/django/rules/models.md +0 -167
- package/packs/django/rules/serializers.md +0 -126
- package/packs/django/rules/services.md +0 -131
- package/packs/django/rules/tests.md +0 -140
- package/packs/django/rules/views.md +0 -102
- package/packs/django/skills/import-convention-enforcer/SKILL.md +0 -226
- package/packs/django/skills/import-convention-enforcer/patterns/django-imports.md +0 -343
- package/packs/django/skills/migration-safety-checker/SKILL.md +0 -375
- package/packs/django/skills/model-entity-validator/SKILL.md +0 -298
- package/packs/django/skills/performance-optimizer/SKILL.md +0 -447
- package/packs/django/skills/red-phase-verifier/SKILL.md +0 -180
- package/packs/django/skills/security-first-validator/SKILL.md +0 -435
- package/packs/django/skills/test-coverage-advisor/SKILL.md +0 -394
- package/packs/django/skills/test-validity-checker/SKILL.md +0 -194
- package/packs/failure-log/CHANGELOG.md +0 -20
- package/packs/failure-log/README.md +0 -168
- package/packs/failure-log/commands/failure-add.md +0 -106
- package/packs/failure-log/commands/failure-list.md +0 -89
- package/packs/failure-log/hooks/hooks.json +0 -16
- package/packs/failure-log/hooks/scripts/inject-failures.sh +0 -64
- package/packs/failure-log/skills/failure-log-manager/SKILL.md +0 -164
- package/packs/flutter/CHANGELOG.md +0 -19
- package/packs/flutter/README.md +0 -170
- package/packs/flutter/agents/flutter-architect.md +0 -166
- package/packs/flutter/agents/flutter-builder.md +0 -303
- package/packs/flutter/agents/release-manager.md +0 -355
- package/packs/flutter/commands/fastlane-setup.md +0 -188
- package/packs/flutter/commands/flutter-build.md +0 -90
- package/packs/flutter/commands/flutter-deploy.md +0 -133
- package/packs/flutter/commands/flutter-test.md +0 -117
- package/packs/flutter/commands/signing-setup.md +0 -209
- package/packs/flutter/hooks/hooks.json +0 -17
- package/packs/flutter/skills/fastlane-knowledge/SKILL.md +0 -193
- package/packs/flutter/skills/flutter-architecture/SKILL.md +0 -127
- package/packs/flutter/skills/store-publishing/SKILL.md +0 -163
- package/packs/hono/CHANGELOG.md +0 -19
- package/packs/hono/README.md +0 -143
- package/packs/hono/agents/hono-architect.md +0 -240
- package/packs/hono/agents/hono-builder.md +0 -285
- package/packs/hono/agents/hono-reviewer.md +0 -279
- package/packs/hono/agents/hono-tester.md +0 -346
- package/packs/hono/commands/middleware-create.md +0 -223
- package/packs/hono/commands/project-init.md +0 -306
- package/packs/hono/commands/route-create.md +0 -153
- package/packs/hono/commands/rpc-client.md +0 -263
- package/packs/hono/skills/cloudflare-bindings/SKILL.md +0 -408
- package/packs/hono/skills/hono-patterns/SKILL.md +0 -309
- package/packs/hono/skills/rpc-typesafe/SKILL.md +0 -388
- package/packs/hono/skills/zod-validation/SKILL.md +0 -332
- package/packs/nestjs/CHANGELOG.md +0 -29
- package/packs/nestjs/README.md +0 -75
- package/packs/nestjs/agents/nestjs-architect.md +0 -402
- package/packs/nestjs/agents/nestjs-builder.md +0 -301
- package/packs/nestjs/agents/nestjs-tester.md +0 -437
- package/packs/nestjs/commands/module-create.md +0 -369
- package/packs/nestjs/rules/controllers.md +0 -92
- package/packs/nestjs/rules/dto.md +0 -124
- package/packs/nestjs/rules/entities.md +0 -102
- package/packs/nestjs/rules/services.md +0 -106
- package/packs/nestjs/skills/barrel-export-manager/SKILL.md +0 -389
- package/packs/nestjs/skills/import-convention-enforcer/SKILL.md +0 -365
- package/packs/nextjs/CHANGELOG.md +0 -36
- package/packs/nextjs/README.md +0 -76
- package/packs/nextjs/agents/frontend-tester.md +0 -680
- package/packs/nextjs/agents/frontend-visual.md +0 -820
- package/packs/nextjs/agents/nextjs-architect.md +0 -331
- package/packs/nextjs/agents/nextjs-modular.md +0 -433
- package/packs/nextjs/commands/component-create.md +0 -398
- package/packs/nextjs/rules/api-routes.md +0 -129
- package/packs/nextjs/rules/components.md +0 -106
- package/packs/nextjs/rules/hooks.md +0 -132
- package/packs/nextjs/skills/accessibility-validator/SKILL.md +0 -445
- package/packs/nextjs/skills/import-convention-enforcer/SKILL.md +0 -399
- package/packs/nextjs/skills/react-form-validator/SKILL.md +0 -569
- package/packs/nuxtjs/CHANGELOG.md +0 -30
- package/packs/nuxtjs/README.md +0 -56
- package/packs/nuxtjs/agents/frontend-tester.md +0 -680
- package/packs/nuxtjs/agents/frontend-visual.md +0 -820
- package/packs/nuxtjs/agents/nuxtjs-architect.md +0 -537
- package/packs/nuxtjs/commands/component-create.md +0 -223
- package/packs/nuxtjs/rules/components.md +0 -101
- package/packs/nuxtjs/rules/composables.md +0 -118
- package/packs/nuxtjs/rules/server-routes.md +0 -127
- package/packs/nuxtjs/skills/accessibility-validator/SKILL.md +0 -183
- package/packs/nuxtjs/skills/import-convention-enforcer/SKILL.md +0 -196
- package/packs/nuxtjs/skills/veevalidate-form-validator/SKILL.md +0 -190
- package/packs/onboard/CHANGELOG.md +0 -22
- package/packs/onboard/README.md +0 -103
- package/packs/onboard/agents/onboard-guide.md +0 -118
- package/packs/onboard/commands/onboard.md +0 -313
- package/packs/onboard/skills/onboard-context-provider/SKILL.md +0 -98
- package/packs/tanstack-router/CHANGELOG.md +0 -30
- package/packs/tanstack-router/README.md +0 -113
- package/packs/tanstack-router/agents/tanstack-architect.md +0 -173
- package/packs/tanstack-router/agents/tanstack-builder.md +0 -360
- package/packs/tanstack-router/agents/tanstack-tester.md +0 -454
- package/packs/tanstack-router/commands/form-create.md +0 -313
- package/packs/tanstack-router/commands/query-create.md +0 -263
- package/packs/tanstack-router/commands/route-create.md +0 -190
- package/packs/tanstack-router/commands/table-create.md +0 -413
- package/packs/tanstack-router/skills/ai-patterns/SKILL.md +0 -370
- package/packs/tanstack-router/skills/db-patterns/SKILL.md +0 -346
- package/packs/tanstack-router/skills/devtools-patterns/SKILL.md +0 -415
- package/packs/tanstack-router/skills/form-patterns/SKILL.md +0 -425
- package/packs/tanstack-router/skills/pacer-patterns/SKILL.md +0 -341
- package/packs/tanstack-router/skills/query-patterns/SKILL.md +0 -359
- package/packs/tanstack-router/skills/router-patterns/SKILL.md +0 -285
- package/packs/tanstack-router/skills/store-patterns/SKILL.md +0 -351
- package/packs/tanstack-router/skills/table-patterns/SKILL.md +0 -531
- package/packs/tanstack-router/skills/tanstack-conventions/SKILL.md +0 -428
- package/packs/tanstack-router/skills/virtual-patterns/SKILL.md +0 -490
- package/packs/worktree/CHANGELOG.md +0 -45
- package/packs/worktree/README.md +0 -219
- package/packs/worktree/commands/wt.md +0 -93
- package/packs/worktree/scripts/wt.sh +0 -957
- package/packs/worktree/skills/worktree-manager/SKILL.md +0 -113
|
@@ -1,325 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: test-generate
|
|
3
|
-
description: Generate comprehensive tests for Django code (90%+ coverage target)
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Django Test Generation
|
|
7
|
-
|
|
8
|
-
You are a Django testing specialist. Your task is to generate comprehensive tests that achieve 90%+ code coverage following Smicolon standards.
|
|
9
|
-
|
|
10
|
-
## Core Requirements
|
|
11
|
-
|
|
12
|
-
### Import Pattern (CRITICAL)
|
|
13
|
-
```python
|
|
14
|
-
# ✅ CORRECT
|
|
15
|
-
import users.models as _users_models
|
|
16
|
-
import users.services as _users_services
|
|
17
|
-
import users.serializers as _users_serializers
|
|
18
|
-
from django.test import TestCase
|
|
19
|
-
from rest_framework.test import APITestCase, APIClient
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
### Test Categories
|
|
23
|
-
|
|
24
|
-
1. **Model Tests** - Test model methods, constraints, relationships
|
|
25
|
-
2. **Service Tests** - Test business logic, transactions
|
|
26
|
-
3. **API Tests** - Test endpoints, permissions, validation
|
|
27
|
-
4. **Integration Tests** - Test complete workflows
|
|
28
|
-
|
|
29
|
-
## Test Structure
|
|
30
|
-
|
|
31
|
-
### Model Tests
|
|
32
|
-
```python
|
|
33
|
-
# app/tests/test_models.py
|
|
34
|
-
import users.models as _users_models
|
|
35
|
-
from django.test import TestCase
|
|
36
|
-
from django.db import IntegrityError
|
|
37
|
-
|
|
38
|
-
class ProductModelTest(TestCase):
|
|
39
|
-
def setUp(self):
|
|
40
|
-
self.product = _models.Product.objects.create(
|
|
41
|
-
name='Test Product',
|
|
42
|
-
slug='test-product',
|
|
43
|
-
description='Test description',
|
|
44
|
-
price=99.99,
|
|
45
|
-
stock=10
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
def test_product_creation(self):
|
|
49
|
-
"""Test product is created with required fields"""
|
|
50
|
-
self.assertIsNotNone(self.product.id)
|
|
51
|
-
self.assertIsNotNone(self.product.created_at)
|
|
52
|
-
self.assertFalse(self.product.is_deleted)
|
|
53
|
-
|
|
54
|
-
def test_product_str(self):
|
|
55
|
-
"""Test string representation"""
|
|
56
|
-
self.assertEqual(str(self.product), 'Test Product')
|
|
57
|
-
|
|
58
|
-
def test_slug_unique_constraint(self):
|
|
59
|
-
"""Test slug must be unique"""
|
|
60
|
-
with self.assertRaises(IntegrityError):
|
|
61
|
-
_models.Product.objects.create(
|
|
62
|
-
name='Another Product',
|
|
63
|
-
slug='test-product', # Duplicate slug
|
|
64
|
-
price=49.99
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
def test_price_non_negative(self):
|
|
68
|
-
"""Test price cannot be negative"""
|
|
69
|
-
with self.assertRaises(IntegrityError):
|
|
70
|
-
_models.Product.objects.create(
|
|
71
|
-
name='Invalid Product',
|
|
72
|
-
slug='invalid',
|
|
73
|
-
price=-10.00
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
def test_soft_delete(self):
|
|
77
|
-
"""Test soft delete functionality"""
|
|
78
|
-
product_id = self.product.id
|
|
79
|
-
self.product.is_deleted = True
|
|
80
|
-
self.product.save()
|
|
81
|
-
|
|
82
|
-
# Product still exists in database
|
|
83
|
-
product = _models.Product.objects.get(id=product_id)
|
|
84
|
-
self.assertTrue(product.is_deleted)
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### Service Tests
|
|
88
|
-
```python
|
|
89
|
-
# app/tests/test_services.py
|
|
90
|
-
import users.models as _users_models
|
|
91
|
-
import users.services as _users_services
|
|
92
|
-
from django.test import TestCase, TransactionTestCase
|
|
93
|
-
from django.db import transaction
|
|
94
|
-
|
|
95
|
-
class ProductServiceTest(TransactionTestCase):
|
|
96
|
-
def setUp(self):
|
|
97
|
-
self.user = # Create test user
|
|
98
|
-
|
|
99
|
-
def test_create_product(self):
|
|
100
|
-
"""Test product creation through service"""
|
|
101
|
-
data = {
|
|
102
|
-
'name': 'New Product',
|
|
103
|
-
'slug': 'new-product',
|
|
104
|
-
'description': 'New description',
|
|
105
|
-
'price': 149.99,
|
|
106
|
-
'stock': 5
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
product = _services.ProductService.create_product(data, self.user)
|
|
110
|
-
|
|
111
|
-
self.assertIsNotNone(product.id)
|
|
112
|
-
self.assertEqual(product.name, 'New Product')
|
|
113
|
-
self.assertEqual(product.created_by, self.user)
|
|
114
|
-
|
|
115
|
-
def test_update_product(self):
|
|
116
|
-
"""Test product update through service"""
|
|
117
|
-
product = _models.Product.objects.create(
|
|
118
|
-
name='Original',
|
|
119
|
-
slug='original',
|
|
120
|
-
price=100.00
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
data = {'name': 'Updated'}
|
|
124
|
-
updated = _services.ProductService.update_product(str(product.id), data)
|
|
125
|
-
|
|
126
|
-
self.assertEqual(updated.name, 'Updated')
|
|
127
|
-
self.assertEqual(updated.slug, 'original') # Unchanged
|
|
128
|
-
|
|
129
|
-
def test_delete_product(self):
|
|
130
|
-
"""Test soft delete through service"""
|
|
131
|
-
product = _models.Product.objects.create(
|
|
132
|
-
name='To Delete',
|
|
133
|
-
slug='to-delete',
|
|
134
|
-
price=50.00
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
_services.ProductService.delete_product(str(product.id))
|
|
138
|
-
|
|
139
|
-
product.refresh_from_db()
|
|
140
|
-
self.assertTrue(product.is_deleted)
|
|
141
|
-
|
|
142
|
-
def test_transaction_rollback(self):
|
|
143
|
-
"""Test transaction rollback on error"""
|
|
144
|
-
with self.assertRaises(Exception):
|
|
145
|
-
with transaction.atomic():
|
|
146
|
-
product = _models.Product.objects.create(
|
|
147
|
-
name='Test',
|
|
148
|
-
slug='test',
|
|
149
|
-
price=100.00
|
|
150
|
-
)
|
|
151
|
-
raise Exception("Simulated error")
|
|
152
|
-
|
|
153
|
-
# Product should not exist due to rollback
|
|
154
|
-
self.assertEqual(_models.Product.objects.count(), 0)
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
### API Tests
|
|
158
|
-
```python
|
|
159
|
-
# app/tests/test_api.py
|
|
160
|
-
import users.models as _users_models
|
|
161
|
-
from rest_framework.test import APITestCase
|
|
162
|
-
from rest_framework import status
|
|
163
|
-
|
|
164
|
-
class ProductAPITest(APITestCase):
|
|
165
|
-
def setUp(self):
|
|
166
|
-
self.user = # Create test user
|
|
167
|
-
self.client.force_authenticate(user=self.user)
|
|
168
|
-
|
|
169
|
-
self.product = _models.Product.objects.create(
|
|
170
|
-
name='Test Product',
|
|
171
|
-
slug='test-product',
|
|
172
|
-
price=99.99
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
def test_list_products(self):
|
|
176
|
-
"""Test listing products"""
|
|
177
|
-
response = self.client.get('/api/products/')
|
|
178
|
-
|
|
179
|
-
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
180
|
-
self.assertEqual(len(response.data), 1)
|
|
181
|
-
|
|
182
|
-
def test_create_product(self):
|
|
183
|
-
"""Test creating product via API"""
|
|
184
|
-
data = {
|
|
185
|
-
'name': 'New Product',
|
|
186
|
-
'slug': 'new-product',
|
|
187
|
-
'description': 'Description',
|
|
188
|
-
'price': '149.99',
|
|
189
|
-
'stock': 10
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
response = self.client.post('/api/products/', data)
|
|
193
|
-
|
|
194
|
-
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
|
195
|
-
self.assertEqual(_models.Product.objects.count(), 2)
|
|
196
|
-
|
|
197
|
-
def test_create_product_validation_error(self):
|
|
198
|
-
"""Test validation errors"""
|
|
199
|
-
data = {
|
|
200
|
-
'name': 'Invalid',
|
|
201
|
-
'slug': 'invalid',
|
|
202
|
-
'price': '-10.00' # Negative price
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
response = self.client.post('/api/products/', data)
|
|
206
|
-
|
|
207
|
-
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
208
|
-
self.assertIn('price', response.data)
|
|
209
|
-
|
|
210
|
-
def test_update_product(self):
|
|
211
|
-
"""Test updating product"""
|
|
212
|
-
data = {'name': 'Updated Name'}
|
|
213
|
-
|
|
214
|
-
response = self.client.patch(
|
|
215
|
-
f'/api/products/{self.product.id}/',
|
|
216
|
-
data
|
|
217
|
-
)
|
|
218
|
-
|
|
219
|
-
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
|
220
|
-
self.product.refresh_from_db()
|
|
221
|
-
self.assertEqual(self.product.name, 'Updated Name')
|
|
222
|
-
|
|
223
|
-
def test_delete_product(self):
|
|
224
|
-
"""Test deleting product"""
|
|
225
|
-
response = self.client.delete(f'/api/products/{self.product.id}/')
|
|
226
|
-
|
|
227
|
-
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
|
228
|
-
self.product.refresh_from_db()
|
|
229
|
-
self.assertTrue(self.product.is_deleted)
|
|
230
|
-
|
|
231
|
-
def test_permissions_unauthenticated(self):
|
|
232
|
-
"""Test unauthenticated access is denied"""
|
|
233
|
-
self.client.force_authenticate(user=None)
|
|
234
|
-
|
|
235
|
-
response = self.client.get('/api/products/')
|
|
236
|
-
|
|
237
|
-
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
|
238
|
-
|
|
239
|
-
def test_query_optimization(self):
|
|
240
|
-
"""Test queryset is optimized"""
|
|
241
|
-
with self.assertNumQueries(2): # Should use select_related
|
|
242
|
-
response = self.client.get('/api/products/')
|
|
243
|
-
list(response.data) # Force evaluation
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
### Integration Tests
|
|
247
|
-
```python
|
|
248
|
-
# app/tests/test_integration.py
|
|
249
|
-
import users.models as _users_models
|
|
250
|
-
from rest_framework.test import APITestCase
|
|
251
|
-
|
|
252
|
-
class ProductWorkflowTest(APITestCase):
|
|
253
|
-
def test_complete_product_lifecycle(self):
|
|
254
|
-
"""Test complete product creation to deletion workflow"""
|
|
255
|
-
user = # Create test user
|
|
256
|
-
self.client.force_authenticate(user=user)
|
|
257
|
-
|
|
258
|
-
# 1. Create product
|
|
259
|
-
create_data = {
|
|
260
|
-
'name': 'Workflow Product',
|
|
261
|
-
'slug': 'workflow-product',
|
|
262
|
-
'price': '99.99',
|
|
263
|
-
'stock': 10
|
|
264
|
-
}
|
|
265
|
-
create_response = self.client.post('/api/products/', create_data)
|
|
266
|
-
product_id = create_response.data['id']
|
|
267
|
-
|
|
268
|
-
# 2. Retrieve product
|
|
269
|
-
get_response = self.client.get(f'/api/products/{product_id}/')
|
|
270
|
-
self.assertEqual(get_response.data['name'], 'Workflow Product')
|
|
271
|
-
|
|
272
|
-
# 3. Update product
|
|
273
|
-
update_data = {'stock': 20}
|
|
274
|
-
self.client.patch(f'/api/products/{product_id}/', update_data)
|
|
275
|
-
|
|
276
|
-
# 4. Custom action
|
|
277
|
-
restock_data = {'quantity': 5}
|
|
278
|
-
self.client.post(f'/api/products/{product_id}/restock/', restock_data)
|
|
279
|
-
|
|
280
|
-
# 5. Verify final state
|
|
281
|
-
final_response = self.client.get(f'/api/products/{product_id}/')
|
|
282
|
-
self.assertEqual(final_response.data['stock'], 25)
|
|
283
|
-
|
|
284
|
-
# 6. Delete product
|
|
285
|
-
self.client.delete(f'/api/products/{product_id}/')
|
|
286
|
-
|
|
287
|
-
# 7. Verify soft delete
|
|
288
|
-
product = _models.Product.objects.get(id=product_id)
|
|
289
|
-
self.assertTrue(product.is_deleted)
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
## Running Tests
|
|
293
|
-
|
|
294
|
-
```bash
|
|
295
|
-
# Run all tests
|
|
296
|
-
python manage.py test
|
|
297
|
-
|
|
298
|
-
# Run specific test file
|
|
299
|
-
python manage.py test app.tests.test_models
|
|
300
|
-
|
|
301
|
-
# Run specific test class
|
|
302
|
-
python manage.py test app.tests.test_models.ProductModelTest
|
|
303
|
-
|
|
304
|
-
# Run with coverage
|
|
305
|
-
coverage run --source='.' manage.py test
|
|
306
|
-
coverage report
|
|
307
|
-
coverage html
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
## Quality Checklist
|
|
311
|
-
|
|
312
|
-
- [ ] Model tests (creation, constraints, methods)
|
|
313
|
-
- [ ] Service tests (business logic, transactions)
|
|
314
|
-
- [ ] API tests (CRUD operations)
|
|
315
|
-
- [ ] Permission tests
|
|
316
|
-
- [ ] Validation tests
|
|
317
|
-
- [ ] Error handling tests
|
|
318
|
-
- [ ] Integration tests
|
|
319
|
-
- [ ] Edge cases covered
|
|
320
|
-
- [ ] Query optimization tests
|
|
321
|
-
- [ ] 90%+ code coverage
|
|
322
|
-
- [ ] Absolute imports with aliases
|
|
323
|
-
- [ ] Clear test names and docstrings
|
|
324
|
-
|
|
325
|
-
Now, ask the user what code they want to test!
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
paths:
|
|
3
|
-
- "**/migrations/**/*.py"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Django Migration Standards
|
|
7
|
-
|
|
8
|
-
## Safety First
|
|
9
|
-
|
|
10
|
-
Migrations MUST be:
|
|
11
|
-
- Reversible
|
|
12
|
-
- Non-destructive
|
|
13
|
-
- Tested before deployment
|
|
14
|
-
|
|
15
|
-
## Dangerous Operations
|
|
16
|
-
|
|
17
|
-
### Column Removal (3-step process)
|
|
18
|
-
|
|
19
|
-
```python
|
|
20
|
-
# Step 1: Make nullable (Migration 1)
|
|
21
|
-
migrations.AlterField(
|
|
22
|
-
model_name='user',
|
|
23
|
-
name='legacy_field',
|
|
24
|
-
field=models.CharField(max_length=100, null=True, blank=True),
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
# Step 2: Deploy code that stops writing to field
|
|
28
|
-
# Step 3: Remove field (Migration 2, separate deploy)
|
|
29
|
-
migrations.RemoveField(
|
|
30
|
-
model_name='user',
|
|
31
|
-
name='legacy_field',
|
|
32
|
-
)
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
### Type Changes
|
|
36
|
-
|
|
37
|
-
```python
|
|
38
|
-
# WRONG - Data loss risk
|
|
39
|
-
migrations.AlterField(
|
|
40
|
-
model_name='product',
|
|
41
|
-
name='price',
|
|
42
|
-
field=models.IntegerField(), # Was DecimalField!
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
# CORRECT - Add new field, migrate data, remove old
|
|
46
|
-
migrations.AddField(
|
|
47
|
-
model_name='product',
|
|
48
|
-
name='price_cents',
|
|
49
|
-
field=models.IntegerField(null=True),
|
|
50
|
-
)
|
|
51
|
-
migrations.RunPython(migrate_price_to_cents, reverse_migrate),
|
|
52
|
-
migrations.RemoveField('product', 'price'),
|
|
53
|
-
migrations.RenameField('product', 'price_cents', 'price'),
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## Requirements
|
|
57
|
-
|
|
58
|
-
- Always include `reverse_code` for RunPython
|
|
59
|
-
- Test migrations: forward AND backward
|
|
60
|
-
- Never use `--fake` in production
|
|
61
|
-
- Review auto-generated migrations before committing
|
|
62
|
-
|
|
63
|
-
## RunPython Template
|
|
64
|
-
|
|
65
|
-
```python
|
|
66
|
-
def migrate_forward(apps, schema_editor):
|
|
67
|
-
"""
|
|
68
|
-
Migration: Convert price to cents.
|
|
69
|
-
"""
|
|
70
|
-
Product = apps.get_model('products', 'Product')
|
|
71
|
-
for product in Product.objects.all():
|
|
72
|
-
product.price_cents = int(product.price * 100)
|
|
73
|
-
product.save(update_fields=['price_cents'])
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def migrate_backward(apps, schema_editor):
|
|
77
|
-
"""
|
|
78
|
-
Reverse: Convert cents back to price.
|
|
79
|
-
"""
|
|
80
|
-
Product = apps.get_model('products', 'Product')
|
|
81
|
-
for product in Product.objects.all():
|
|
82
|
-
product.price = product.price_cents / 100
|
|
83
|
-
product.save(update_fields=['price'])
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
class Migration(migrations.Migration):
|
|
87
|
-
operations = [
|
|
88
|
-
migrations.RunPython(migrate_forward, migrate_backward),
|
|
89
|
-
]
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
## Forbidden Patterns
|
|
93
|
-
|
|
94
|
-
- `RunPython(migrate_forward, migrations.RunPython.noop)` - Always provide reverse
|
|
95
|
-
- `migrations.DeleteModel` without data backup plan
|
|
96
|
-
- Large data migrations without batching
|
|
97
|
-
- Migrations that lock tables for long periods
|
|
98
|
-
|
|
99
|
-
## Large Table Migrations
|
|
100
|
-
|
|
101
|
-
For tables with >1M rows:
|
|
102
|
-
|
|
103
|
-
```python
|
|
104
|
-
def migrate_in_batches(apps, schema_editor):
|
|
105
|
-
"""Migrate data in batches to avoid lock timeouts."""
|
|
106
|
-
Product = apps.get_model('products', 'Product')
|
|
107
|
-
batch_size = 1000
|
|
108
|
-
total = Product.objects.count()
|
|
109
|
-
|
|
110
|
-
for start in range(0, total, batch_size):
|
|
111
|
-
batch = Product.objects.all()[start:start + batch_size]
|
|
112
|
-
for product in batch:
|
|
113
|
-
product.new_field = transform(product.old_field)
|
|
114
|
-
Product.objects.bulk_update(batch, ['new_field'])
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
## Migration Naming
|
|
118
|
-
|
|
119
|
-
Use descriptive names:
|
|
120
|
-
|
|
121
|
-
```bash
|
|
122
|
-
# GOOD
|
|
123
|
-
python manage.py makemigrations --name add_email_verified_field
|
|
124
|
-
python manage.py makemigrations --name remove_legacy_status_column
|
|
125
|
-
|
|
126
|
-
# BAD (auto-generated)
|
|
127
|
-
0023_auto_20241201_1234.py
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
## Pre-Deployment Checklist
|
|
131
|
-
|
|
132
|
-
- [ ] Migration is reversible
|
|
133
|
-
- [ ] Tested forward migration locally
|
|
134
|
-
- [ ] Tested backward migration locally
|
|
135
|
-
- [ ] No destructive operations without 3-step process
|
|
136
|
-
- [ ] Large tables use batching
|
|
137
|
-
- [ ] RunPython has reverse_code
|
|
138
|
-
- [ ] Migration name is descriptive
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
paths:
|
|
3
|
-
- "**/models.py"
|
|
4
|
-
- "**/models/*.py"
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Django Model Standards
|
|
8
|
-
|
|
9
|
-
## BaseModel Pattern (MANDATORY)
|
|
10
|
-
|
|
11
|
-
All models MUST inherit from `BaseModel`. Never repeat UUID/timestamp fields.
|
|
12
|
-
|
|
13
|
-
### Step 1: Ensure BaseModel Exists
|
|
14
|
-
|
|
15
|
-
Check for BaseModel in your project (usually in `core/models.py` or `shared/models.py`):
|
|
16
|
-
|
|
17
|
-
```python
|
|
18
|
-
# core/models.py
|
|
19
|
-
import uuid
|
|
20
|
-
from django.db import models
|
|
21
|
-
|
|
22
|
-
class BaseModel(models.Model):
|
|
23
|
-
"""
|
|
24
|
-
Abstract base model providing:
|
|
25
|
-
- UUID primary key
|
|
26
|
-
- Automatic timestamps (created_at, updated_at)
|
|
27
|
-
- Soft delete support (is_deleted)
|
|
28
|
-
|
|
29
|
-
All models MUST inherit from this class.
|
|
30
|
-
"""
|
|
31
|
-
|
|
32
|
-
id = models.UUIDField(
|
|
33
|
-
primary_key=True,
|
|
34
|
-
default=uuid.uuid4,
|
|
35
|
-
editable=False
|
|
36
|
-
)
|
|
37
|
-
created_at = models.DateTimeField(auto_now_add=True)
|
|
38
|
-
updated_at = models.DateTimeField(auto_now=True)
|
|
39
|
-
is_deleted = models.BooleanField(default=False)
|
|
40
|
-
|
|
41
|
-
class Meta:
|
|
42
|
-
abstract = True
|
|
43
|
-
ordering = ['-created_at']
|
|
44
|
-
|
|
45
|
-
def soft_delete(self) -> None:
|
|
46
|
-
"""Soft delete the record."""
|
|
47
|
-
self.is_deleted = True
|
|
48
|
-
self.save(update_fields=['is_deleted', 'updated_at'])
|
|
49
|
-
|
|
50
|
-
def restore(self) -> None:
|
|
51
|
-
"""Restore a soft-deleted record."""
|
|
52
|
-
self.is_deleted = False
|
|
53
|
-
self.save(update_fields=['is_deleted', 'updated_at'])
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### Step 2: Inherit from BaseModel
|
|
57
|
-
|
|
58
|
-
```python
|
|
59
|
-
# users/models.py
|
|
60
|
-
from django.db import models
|
|
61
|
-
import core.models as _core_models
|
|
62
|
-
|
|
63
|
-
class User(_core_models.BaseModel):
|
|
64
|
-
"""User model - inherits id, timestamps, soft delete from BaseModel."""
|
|
65
|
-
|
|
66
|
-
email = models.EmailField(unique=True, db_index=True)
|
|
67
|
-
first_name = models.CharField(max_length=100)
|
|
68
|
-
last_name = models.CharField(max_length=100)
|
|
69
|
-
|
|
70
|
-
class Meta:
|
|
71
|
-
db_table = 'users'
|
|
72
|
-
verbose_name = 'User'
|
|
73
|
-
verbose_name_plural = 'Users'
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
## Wrong vs Right
|
|
77
|
-
|
|
78
|
-
```python
|
|
79
|
-
# ❌ WRONG - Repeating fields that should come from BaseModel
|
|
80
|
-
class Product(models.Model):
|
|
81
|
-
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
82
|
-
created_at = models.DateTimeField(auto_now_add=True)
|
|
83
|
-
updated_at = models.DateTimeField(auto_now=True)
|
|
84
|
-
is_deleted = models.BooleanField(default=False)
|
|
85
|
-
name = models.CharField(max_length=255)
|
|
86
|
-
|
|
87
|
-
# ✅ CORRECT - Inherit from BaseModel
|
|
88
|
-
import core.models as _core_models
|
|
89
|
-
|
|
90
|
-
class Product(_core_models.BaseModel):
|
|
91
|
-
"""Product model."""
|
|
92
|
-
name = models.CharField(max_length=255)
|
|
93
|
-
price = models.DecimalField(max_digits=10, decimal_places=2)
|
|
94
|
-
|
|
95
|
-
class Meta:
|
|
96
|
-
db_table = 'products'
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
## Import Pattern
|
|
100
|
-
|
|
101
|
-
```python
|
|
102
|
-
# ✅ CORRECT - Absolute import with alias
|
|
103
|
-
import core.models as _core_models
|
|
104
|
-
|
|
105
|
-
class MyModel(_core_models.BaseModel):
|
|
106
|
-
pass
|
|
107
|
-
|
|
108
|
-
# ❌ WRONG - Relative import
|
|
109
|
-
from .base import BaseModel
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
## Custom Managers
|
|
113
|
-
|
|
114
|
-
For soft delete support:
|
|
115
|
-
|
|
116
|
-
```python
|
|
117
|
-
class ActiveManager(models.Manager):
|
|
118
|
-
def get_queryset(self):
|
|
119
|
-
return super().get_queryset().filter(is_deleted=False)
|
|
120
|
-
|
|
121
|
-
class Product(_core_models.BaseModel):
|
|
122
|
-
name = models.CharField(max_length=255)
|
|
123
|
-
|
|
124
|
-
objects = ActiveManager() # Default: excludes deleted
|
|
125
|
-
all_objects = models.Manager() # Include deleted
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
## Field Naming Conventions
|
|
129
|
-
|
|
130
|
-
- Use snake_case for all field names
|
|
131
|
-
- Boolean fields: `is_*` or `has_*` prefix
|
|
132
|
-
- DateTime fields: `*_at` suffix
|
|
133
|
-
- Foreign keys: descriptive name (not `{model}_id`)
|
|
134
|
-
|
|
135
|
-
```python
|
|
136
|
-
# ✅ CORRECT
|
|
137
|
-
is_active = models.BooleanField()
|
|
138
|
-
published_at = models.DateTimeField()
|
|
139
|
-
author = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
140
|
-
|
|
141
|
-
# ❌ WRONG
|
|
142
|
-
active = models.BooleanField() # No prefix
|
|
143
|
-
publishedAt = models.DateTimeField() # camelCase
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
## Meta Options
|
|
147
|
-
|
|
148
|
-
Always define:
|
|
149
|
-
|
|
150
|
-
```python
|
|
151
|
-
class Meta:
|
|
152
|
-
db_table = 'products' # Explicit table name
|
|
153
|
-
ordering = ['-created_at'] # Default ordering
|
|
154
|
-
verbose_name = 'Product'
|
|
155
|
-
verbose_name_plural = 'Products'
|
|
156
|
-
indexes = [
|
|
157
|
-
models.Index(fields=['name']),
|
|
158
|
-
]
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
## Forbidden Patterns
|
|
162
|
-
|
|
163
|
-
- `models.AutoField` - BaseModel uses UUID
|
|
164
|
-
- `id = models.UUIDField(...)` in non-base models - Inherit from BaseModel
|
|
165
|
-
- `created_at = ...` in non-base models - Inherit from BaseModel
|
|
166
|
-
- Hard deletes - Use `soft_delete()` method
|
|
167
|
-
- Business logic in models - Use service layer
|