cc-codeconductor 0.4.3 → 0.5.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 +1 -1
- package/dist/index.js +2642 -226
- package/package.json +1 -1
- package/presets/agy/skills/evaluation/SKILL.md +6 -0
- package/presets/agy/skills/openspec/SKILL.md +32 -0
- package/presets/agy/workflows/cc-openspec.md +62 -0
- package/presets/agy/workflows/cc-scorecard.md +17 -0
- package/presets/claude/commands/cc/openspec.md +144 -0
- package/presets/claude/commands/cc/review.md +13 -2
- package/presets/claude/commands/cc/scorecard.md +65 -0
- package/presets/claude/skills/evaluation/SKILL.md +42 -0
- package/presets/claude/skills/openspec/SKILL.md +54 -0
- package/presets/codex/AGENTS.md +57 -0
- package/presets/cursor/.cursorignore +15 -0
- package/presets/cursor/AGENTS.md +504 -0
- package/presets/cursor/agents/architect.md +211 -0
- package/presets/cursor/agents/complexity-auditor.md +76 -0
- package/presets/cursor/agents/contract-builder.md +75 -0
- package/presets/cursor/agents/docs.md +180 -0
- package/presets/cursor/agents/goal-planner.md +71 -0
- package/presets/cursor/agents/implementer.md +161 -0
- package/presets/cursor/agents/orchestrator.md +377 -0
- package/presets/cursor/agents/repo-explorer.md +100 -0
- package/presets/cursor/agents/reviewer.md +237 -0
- package/presets/cursor/agents/security-reviewer.md +113 -0
- package/presets/cursor/agents/task-coach.md +145 -0
- package/presets/cursor/agents/tester.md +241 -0
- package/presets/cursor/commands/cc/api-contract.md +58 -0
- package/presets/cursor/commands/cc/db-migration.md +58 -0
- package/presets/cursor/commands/cc/feature.md +115 -0
- package/presets/cursor/commands/cc/fix.md +121 -0
- package/presets/cursor/commands/cc/openspec.md +144 -0
- package/presets/cursor/commands/cc/pagespeed.md +103 -0
- package/presets/cursor/commands/cc/refactor.md +148 -0
- package/presets/cursor/commands/cc/review.md +137 -0
- package/presets/cursor/commands/cc/scorecard.md +65 -0
- package/presets/cursor/commands/cc/tdd-cycle.md +226 -0
- package/presets/cursor/commands/cc/test-plan.md +138 -0
- package/presets/cursor/rules/behavioral-discipline.mdc +14 -0
- package/presets/cursor/rules/context-budget.mdc +12 -0
- package/presets/cursor/rules/orchestration.mdc +12 -0
- package/presets/cursor/rules/yagni-stdlib.mdc +11 -0
- package/presets/cursor/skills/android/SKILL.md +122 -0
- package/presets/cursor/skills/api-versioning/SKILL.md +394 -0
- package/presets/cursor/skills/astro/SKILL.md +322 -0
- package/presets/cursor/skills/auth-token-inspector/SKILL.md +33 -0
- package/presets/cursor/skills/code-review/SKILL.md +208 -0
- package/presets/cursor/skills/conductor-setup/SKILL.md +127 -0
- package/presets/cursor/skills/django-orm/SKILL.md +463 -0
- package/presets/cursor/skills/django-testing/SKILL.md +417 -0
- package/presets/cursor/skills/django-uv/SKILL.md +409 -0
- package/presets/cursor/skills/drizzle-schema-architect/SKILL.md +54 -0
- package/presets/cursor/skills/evaluation/SKILL.md +8 -0
- package/presets/cursor/skills/fastapi-pydantic-strict/SKILL.md +46 -0
- package/presets/cursor/skills/find-skills/SKILL.md +144 -0
- package/presets/cursor/skills/jpa-nplusone-detector/SKILL.md +49 -0
- package/presets/cursor/skills/jpa-postgres/SKILL.md +626 -0
- package/presets/cursor/skills/laravel-specialist/SKILL.md +267 -0
- package/presets/cursor/skills/laravel-specialist/references/eloquent.md +351 -0
- package/presets/cursor/skills/laravel-specialist/references/livewire.md +512 -0
- package/presets/cursor/skills/laravel-specialist/references/queues.md +423 -0
- package/presets/cursor/skills/laravel-specialist/references/routing.md +362 -0
- package/presets/cursor/skills/laravel-specialist/references/testing.md +522 -0
- package/presets/cursor/skills/livewire-alpine-bridge/SKILL.md +39 -0
- package/presets/cursor/skills/multi-agent-orchestration/README.md +144 -0
- package/presets/cursor/skills/multi-agent-orchestration/SKILL.md +579 -0
- package/presets/cursor/skills/multi-agent-orchestration/examples/framework_implementations.py +362 -0
- package/presets/cursor/skills/multi-agent-orchestration/examples/orchestration_patterns.py +411 -0
- package/presets/cursor/skills/multi-agent-orchestration/scripts/agent_communication.py +334 -0
- package/presets/cursor/skills/multi-agent-orchestration/scripts/benchmarking.py +341 -0
- package/presets/cursor/skills/multi-agent-orchestration/scripts/workflow_management.py +334 -0
- package/presets/cursor/skills/nextjs-typescript/SKILL.md +394 -0
- package/presets/cursor/skills/openspec/SKILL.md +52 -0
- package/presets/cursor/skills/pagespeed-insights/SKILL.md +445 -0
- package/presets/cursor/skills/pagespeed-insights/reference.md +50 -0
- package/presets/cursor/skills/pagespeed-perf/SKILL.md +281 -0
- package/presets/cursor/skills/php-pro/SKILL.md +210 -0
- package/presets/cursor/skills/php-pro/references/async-patterns.md +412 -0
- package/presets/cursor/skills/php-pro/references/laravel-patterns.md +377 -0
- package/presets/cursor/skills/php-pro/references/modern-php-features.md +323 -0
- package/presets/cursor/skills/php-pro/references/symfony-patterns.md +466 -0
- package/presets/cursor/skills/php-pro/references/testing-quality.md +466 -0
- package/presets/cursor/skills/python/SKILL.md +613 -0
- package/presets/cursor/skills/python-django-stack/SKILL.md +500 -0
- package/presets/cursor/skills/python-fastapi-stack/SKILL.md +464 -0
- package/presets/cursor/skills/security/SKILL.md +384 -0
- package/presets/cursor/skills/seo-analytics-injector/SKILL.md +44 -0
- package/presets/cursor/skills/spring-auth-auditor/SKILL.md +33 -0
- package/presets/cursor/skills/spring-boot-feature/SKILL.md +566 -0
- package/presets/cursor/skills/spring-boot-kotlin/SKILL.md +408 -0
- package/presets/cursor/skills/spring-boot-testing-strategy/SKILL.md +479 -0
- package/presets/cursor/skills/sqlalchemy/SKILL.md +473 -0
- package/presets/cursor/skills/tailwind-responsive-auditor/SKILL.md +32 -0
- package/presets/cursor/skills/tdd-mutation-tester/SKILL.md +28 -0
- package/presets/cursor/skills/testing-tdd/SKILL.md +592 -0
- package/presets/cursor/skills/workflow-orchestration-patterns/SKILL.md +98 -0
- package/presets/cursor/skills/workflow-orchestration-patterns/references/details.md +223 -0
- package/presets/opencode/agents/architect.md +1 -2
- package/presets/opencode/agents/complexity-auditor.md +1 -0
- package/presets/opencode/agents/contract-builder.md +93 -0
- package/presets/opencode/agents/docs.md +1 -2
- package/presets/opencode/agents/goal-planner.md +82 -0
- package/presets/opencode/agents/implementer.md +9 -2
- package/presets/opencode/agents/orchestrator.md +50 -12
- package/presets/opencode/agents/repo-explorer.md +0 -1
- package/presets/opencode/agents/reviewer.md +23 -2
- package/presets/opencode/agents/security-reviewer.md +129 -0
- package/presets/opencode/agents/task-coach.md +1 -2
- package/presets/opencode/agents/tester.md +1 -2
- package/presets/opencode/commands/cc-openspec.md +61 -0
- package/presets/opencode/commands/cc-scorecard.md +16 -0
- package/presets/opencode/prompts/v0.5.0/architect.md +222 -0
- package/presets/opencode/prompts/v0.5.0/complexity-auditor.md +91 -0
- package/presets/opencode/prompts/v0.5.0/contract-builder.md +84 -0
- package/presets/opencode/prompts/v0.5.0/docs.md +190 -0
- package/presets/opencode/prompts/v0.5.0/goal-planner.md +80 -0
- package/presets/opencode/prompts/v0.5.0/implementer.md +171 -0
- package/presets/opencode/prompts/v0.5.0/orchestrator.md +388 -0
- package/presets/opencode/prompts/v0.5.0/repo-explorer.md +111 -0
- package/presets/opencode/prompts/v0.5.0/reviewer.md +248 -0
- package/presets/opencode/prompts/v0.5.0/security-reviewer.md +123 -0
- package/presets/opencode/prompts/v0.5.0/task-coach.md +156 -0
- package/presets/opencode/prompts/v0.5.0/tester.md +252 -0
- package/presets/opencode/skills/evaluation/SKILL.md +6 -0
- package/presets/opencode/skills/openspec/SKILL.md +50 -0
- package/presets/templates/BACKLOG.md +33 -0
- package/presets/templates/execution-profile.yml +6 -0
- package/presets/templates/model-comparison.md +11 -0
- package/presets/templates/regression-checklist.yml +10 -0
- package/src/presets/manifests/agy.yml +2 -2
- package/src/presets/manifests/claude.yml +2 -2
- package/src/presets/manifests/codex.yml +2 -2
- package/src/presets/manifests/cursor.yml +19 -3
- package/src/presets/manifests/gemini.yml +2 -2
- package/src/presets/manifests/opencode.yml +2 -2
- package/src/presets/models/agy.yml +21 -0
- package/src/presets/models/claude.yml +18 -0
- package/src/presets/models/codex.yml +18 -0
- package/src/presets/models/cursor.yml +39 -9
- package/src/presets/models/gemini.yml +18 -0
- package/src/presets/models/opencode.yml +18 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: django-testing
|
|
3
|
+
version: 1.0.0
|
|
4
|
+
name: Django Testing
|
|
5
|
+
description: >
|
|
6
|
+
Testing patterns for multi-tenant Django POS projects using pytest-django,
|
|
7
|
+
RequestFactory, and mocks. No factories, no complex conftest.
|
|
8
|
+
Trigger: When writing or reviewing tests in any apps/ directory.
|
|
9
|
+
|
|
10
|
+
user-invokable: true
|
|
11
|
+
license: MIT
|
|
12
|
+
metadata:
|
|
13
|
+
author: lgzarturo
|
|
14
|
+
category: testing
|
|
15
|
+
|
|
16
|
+
compatibility:
|
|
17
|
+
tools: [claude, codex, gemini, agy, opencode]
|
|
18
|
+
stacks:
|
|
19
|
+
languages: [python]
|
|
20
|
+
frameworks: [django, pytest-django, django-tenants]
|
|
21
|
+
|
|
22
|
+
risk:
|
|
23
|
+
level: low
|
|
24
|
+
can_execute_shell: true
|
|
25
|
+
can_modify_files: true
|
|
26
|
+
requires_network: false
|
|
27
|
+
|
|
28
|
+
inputs:
|
|
29
|
+
- view and service files under apps/
|
|
30
|
+
- existing test files (apps/*/tests/ or apps/*/tests.py)
|
|
31
|
+
- pytest configuration (pyproject.toml)
|
|
32
|
+
|
|
33
|
+
outputs:
|
|
34
|
+
- SimpleTestCase-based unit test classes
|
|
35
|
+
- mock configurations for tenant-app models
|
|
36
|
+
- RequestFactory request builders
|
|
37
|
+
- FakeSession pattern implementations
|
|
38
|
+
- queryset chain mock helpers
|
|
39
|
+
- pytest test files following project conventions
|
|
40
|
+
|
|
41
|
+
quality:
|
|
42
|
+
reviewed_by: codeconductor-core
|
|
43
|
+
version: 0.1.0
|
|
44
|
+
paths:
|
|
45
|
+
- "**/test_*.py"
|
|
46
|
+
- "**/tests/**"
|
|
47
|
+
---
|
|
48
|
+
## When to Use
|
|
49
|
+
|
|
50
|
+
- Writing any test in `apps/*/tests/` or `apps/*/tests.py`
|
|
51
|
+
- Deciding which test base class to use
|
|
52
|
+
- Mocking ORM calls for TENANT_APP models
|
|
53
|
+
- Testing views that use custom access decorators
|
|
54
|
+
- Testing APIs, PDFs, and cart operations
|
|
55
|
+
|
|
56
|
+
## Test Architecture in Multi-Tenant Projects
|
|
57
|
+
|
|
58
|
+
### The Schema Problem
|
|
59
|
+
|
|
60
|
+
This project uses `django-tenants` with multi-schema PostgreSQL:
|
|
61
|
+
|
|
62
|
+
- **Shared apps** (public schema): `core`, `users`, Django contrib
|
|
63
|
+
- **Tenant apps** (per-store schema): `employees`, `catalog`, `cart`, `orders`,
|
|
64
|
+
`pos`, `storefront`, `analytics`
|
|
65
|
+
|
|
66
|
+
**The test runner uses the public schema**, so tenant app tables DON'T exist in
|
|
67
|
+
tests. This profoundly affects how we write tests.
|
|
68
|
+
|
|
69
|
+
### Base Class Selection
|
|
70
|
+
|
|
71
|
+
| Condition | Use | Notes |
|
|
72
|
+
| ------------------------------------------- | ------------------------ | --------------------- |
|
|
73
|
+
| No DB access needed | `SimpleTestCase` | No transaction, no DB |
|
|
74
|
+
| Only public schema models (`User`, `Store`) | `TestCase` | Uses public schema |
|
|
75
|
+
| **Any tenant app model** | `SimpleTestCase` + mocks | **ALWAYS** |
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
# WRONG — crashes because tables don't exist
|
|
79
|
+
class TestProductAPI(TestCase):
|
|
80
|
+
def test_list_products(self):
|
|
81
|
+
product = Product.objects.create(...) # Table does not exist!
|
|
82
|
+
|
|
83
|
+
# CORRECT — SimpleTestCase with mocks
|
|
84
|
+
class TestProductAPI(SimpleTestCase):
|
|
85
|
+
@patch("apps.catalog.views.Product")
|
|
86
|
+
def test_list_products(self, mock_product):
|
|
87
|
+
mock_product.objects.filter.return_value = MagicMock()
|
|
88
|
+
response = api_catalog_products(request)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Mock Patterns
|
|
92
|
+
|
|
93
|
+
### 1. RequestFactory — Never TestClient
|
|
94
|
+
|
|
95
|
+
`TestClient` runs `TenantMainMiddleware`, which redirects tenant URLs and breaks
|
|
96
|
+
view tests. Use `RequestFactory` and call the view directly:
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from django.test import RequestFactory
|
|
100
|
+
|
|
101
|
+
factory = RequestFactory()
|
|
102
|
+
|
|
103
|
+
# GET request
|
|
104
|
+
request = factory.get("/pos/api/products/")
|
|
105
|
+
request.user = mock_user
|
|
106
|
+
|
|
107
|
+
# POST request with JSON
|
|
108
|
+
request = factory.post(
|
|
109
|
+
"/pos/api/cart/add/",
|
|
110
|
+
data=json.dumps({"product_id": 1, "quantity": 2}),
|
|
111
|
+
content_type="application/json"
|
|
112
|
+
)
|
|
113
|
+
request.user = mock_user
|
|
114
|
+
|
|
115
|
+
# Call the view directly — no middleware
|
|
116
|
+
response = api_search_products(request)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 2. Bypass Access Decorators
|
|
120
|
+
|
|
121
|
+
`require_pos_access` and `require_manager_access` check `EmployeeProfile`
|
|
122
|
+
(tenant app). Bypass with `is_superuser`:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
# The decorator does:
|
|
126
|
+
# def require_pos_access(view):
|
|
127
|
+
# def wrapper(request, *args, **kwargs):
|
|
128
|
+
# if not request.user.is_superuser:
|
|
129
|
+
# emp = EmployeeProfile.objects.get(user=request.user)
|
|
130
|
+
# if not emp.has_pos_access:
|
|
131
|
+
# return JsonResponse({"error": "No access"}, status=403)
|
|
132
|
+
# return view(request, *args, **kwargs)
|
|
133
|
+
|
|
134
|
+
mock_user = MagicMock()
|
|
135
|
+
mock_user.is_authenticated = True
|
|
136
|
+
mock_user.is_superuser = True # Skip EmployeeProfile lookup
|
|
137
|
+
|
|
138
|
+
request.user = mock_user
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 3. FakeSession Pattern
|
|
142
|
+
|
|
143
|
+
Carts and session-dependent views need a writable session dict:
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
class FakeSession(dict):
|
|
147
|
+
modified = False
|
|
148
|
+
|
|
149
|
+
def save(self):
|
|
150
|
+
pass
|
|
151
|
+
|
|
152
|
+
request.session = FakeSession()
|
|
153
|
+
request.session["pos_cart"] = {}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### 4. MagicMock.DoesNotExist Trap
|
|
157
|
+
|
|
158
|
+
`except Model.DoesNotExist` won't catch a plain `MagicMock`. Create a real
|
|
159
|
+
exception class:
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
# When mocking a model
|
|
163
|
+
mock_cart = MagicMock()
|
|
164
|
+
mock_cart.DoesNotExist = type("DoesNotExist", (Exception,), {})
|
|
165
|
+
|
|
166
|
+
with pytest.raises(mock_cart.DoesNotExist):
|
|
167
|
+
TemporalCart.objects.get(session_key=...)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### 5. MagicMock name= Trap
|
|
171
|
+
|
|
172
|
+
`MagicMock(name="X")` sets the internal repr, NOT the `.name` attribute:
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
# WRONG — .name will be a MagicMock, not "Papel A4"
|
|
176
|
+
mock = MagicMock(name="Papel A4")
|
|
177
|
+
print(mock.name) # <MagicMock name='Papel A4'>
|
|
178
|
+
|
|
179
|
+
# CORRECT
|
|
180
|
+
mock = MagicMock()
|
|
181
|
+
mock.name = "Papel A4"
|
|
182
|
+
mock.price = "1500.00"
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### 6. Queryset Chain Mocking
|
|
186
|
+
|
|
187
|
+
Mock each chained call explicitly:
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
def _setup_product_mock(mock_product_model, product_data=None):
|
|
191
|
+
"""Setup complete Product mock with chained queryset."""
|
|
192
|
+
mock_qs = MagicMock()
|
|
193
|
+
|
|
194
|
+
# Each method in the chain returns the same mock
|
|
195
|
+
mock_product_model.objects.filter.return_value = mock_qs
|
|
196
|
+
mock_qs.select_related.return_value = mock_qs
|
|
197
|
+
mock_qs.annotate.return_value = mock_qs
|
|
198
|
+
mock_qs.order_by.return_value = mock_qs
|
|
199
|
+
mock_qs.__getitem__ = lambda self, s: [mock_product] if isinstance(s, slice) else mock_product
|
|
200
|
+
mock_qs.count.return_value = 1
|
|
201
|
+
|
|
202
|
+
return mock_qs
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### 7. Mock Model with Real Data
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
def _create_mock_product(product_id=1, name="Test Product", price="1000.00"):
|
|
209
|
+
mock = MagicMock()
|
|
210
|
+
mock.id = product_id
|
|
211
|
+
mock.name = name
|
|
212
|
+
mock.price = price
|
|
213
|
+
mock.is_active = True
|
|
214
|
+
mock.stock = 10
|
|
215
|
+
mock.is_service = False
|
|
216
|
+
mock.category_id = 1
|
|
217
|
+
mock.__str__ = lambda self: self.name
|
|
218
|
+
return mock
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## File Structure
|
|
222
|
+
|
|
223
|
+
```
|
|
224
|
+
apps/{app}/tests.py # simple apps, one file
|
|
225
|
+
apps/{app}/tests/__init__.py # complex apps
|
|
226
|
+
apps/{app}/tests/test_{feature}.py
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Section Separators
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
# ── TestClassName ────────────────────────────────────────
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Module Docstring
|
|
236
|
+
|
|
237
|
+
Every test file must explain the multi-tenant constraint:
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
"""
|
|
241
|
+
Tests for catalog API.
|
|
242
|
+
|
|
243
|
+
NOTE: catalog models are TENANT_APP — they live in per-store schemas.
|
|
244
|
+
The test runner uses the public schema, so these tables don't exist.
|
|
245
|
+
All tests use SimpleTestCase + mocks.
|
|
246
|
+
"""
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Naming Conventions
|
|
250
|
+
|
|
251
|
+
- `_make_{thing}()` — builds a request or object
|
|
252
|
+
- `_build_{thing}()` — constructs data structures
|
|
253
|
+
- `_setup_{thing}_mock()` — configures a mock for reuse
|
|
254
|
+
|
|
255
|
+
## Test Types
|
|
256
|
+
|
|
257
|
+
### 1. API/View Tests
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
class TestProductSearch(SimpleTestCase):
|
|
261
|
+
"""Tests for POS product search."""
|
|
262
|
+
|
|
263
|
+
def _make_request(self, query="", offset=0, limit=20):
|
|
264
|
+
factory = RequestFactory()
|
|
265
|
+
request = factory.get(f"/pos/api/search/?q={query}&offset={offset}&limit={limit}")
|
|
266
|
+
request.user = MagicMock()
|
|
267
|
+
request.user.is_authenticated = True
|
|
268
|
+
request.user.is_superuser = True
|
|
269
|
+
return request
|
|
270
|
+
|
|
271
|
+
@patch("apps.pos.views.Product")
|
|
272
|
+
def test_search_returns_results(self, mock_product):
|
|
273
|
+
mock_product_obj = _create_mock_product(name="Lápices")
|
|
274
|
+
mock_qs = MagicMock()
|
|
275
|
+
mock_qs.select_related.return_value = mock_qs
|
|
276
|
+
mock_qs.annotate.return_value = mock_qs
|
|
277
|
+
mock_qs.filter.return_value = mock_qs
|
|
278
|
+
mock_qs.__getitem__ = lambda self, s: [mock_product_obj] if isinstance(s, slice) else mock_product_obj
|
|
279
|
+
mock_product.objects.filter.return_value = mock_qs
|
|
280
|
+
|
|
281
|
+
request = self._make_request(query="lápices")
|
|
282
|
+
response = api_search_products(request)
|
|
283
|
+
|
|
284
|
+
self.assertEqual(response.status_code, 200)
|
|
285
|
+
data = json.loads(response.content)
|
|
286
|
+
self.assertIn("results", data)
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### 2. Service Tests
|
|
290
|
+
|
|
291
|
+
```python
|
|
292
|
+
class TestCartOperations(SimpleTestCase):
|
|
293
|
+
"""Tests for cart operations."""
|
|
294
|
+
|
|
295
|
+
def _build_cart_data(self, items):
|
|
296
|
+
"""Build cart data as it arrives from request."""
|
|
297
|
+
return {str(k): {"quantity": v["quantity"], "price": str(v["price"])}
|
|
298
|
+
for k, v in items.items()}
|
|
299
|
+
|
|
300
|
+
@patch("apps.pos.services.CartService.get_products_from_db")
|
|
301
|
+
def test_calculate_total(self, mock_get_products):
|
|
302
|
+
mock_get_products.return_value = {
|
|
303
|
+
1: _create_mock_product(price="1000.00"),
|
|
304
|
+
2: _create_mock_product(price="2500.00"),
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
cart_data = self._build_cart_data({1: {"quantity": 2}, 2: {"quantity": 1}})
|
|
308
|
+
total = CartService.calculate_total(cart_data)
|
|
309
|
+
|
|
310
|
+
self.assertEqual(total, Decimal("4500.00"))
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### 3. PDF Generation Tests
|
|
314
|
+
|
|
315
|
+
```python
|
|
316
|
+
class TestRestockPDF(SimpleTestCase):
|
|
317
|
+
"""Tests for restock PDF generation."""
|
|
318
|
+
|
|
319
|
+
def test_pdf_generates_correct_content(self):
|
|
320
|
+
products = [
|
|
321
|
+
{"name": "Product 1", "sku": "SKU001", "stock": 5, "min_stock": 10},
|
|
322
|
+
{"name": "Product 2", "sku": "SKU002", "stock": 0, "min_stock": 5},
|
|
323
|
+
]
|
|
324
|
+
|
|
325
|
+
pdf_bytes = generate_restock_pdf("Test Store", products)
|
|
326
|
+
|
|
327
|
+
# Verify it's a valid PDF
|
|
328
|
+
self.assertTrue(pdf_bytes.startswith(b"%PDF"))
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## Testing Commands
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
# Run all tests
|
|
335
|
+
make tests
|
|
336
|
+
|
|
337
|
+
# Run a single file
|
|
338
|
+
uv run pytest apps/pos/tests/test_cart_checkout.py
|
|
339
|
+
|
|
340
|
+
# Run a single test
|
|
341
|
+
uv run pytest apps/pos/tests/test_cart_checkout.py::TestCartCheckout::test_checkout_success -v
|
|
342
|
+
|
|
343
|
+
# Force fresh DB (after schema changes)
|
|
344
|
+
uv run pytest --create-db
|
|
345
|
+
|
|
346
|
+
# With coverage
|
|
347
|
+
make tests-coverage
|
|
348
|
+
|
|
349
|
+
# Run with verbose output
|
|
350
|
+
uv run pytest -v --tb=short
|
|
351
|
+
|
|
352
|
+
# Run only failed tests from last run
|
|
353
|
+
uv run pytest --lf
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
## pytest Configuration
|
|
357
|
+
|
|
358
|
+
In `pyproject.toml`:
|
|
359
|
+
|
|
360
|
+
```toml
|
|
361
|
+
[tool.pytest.ini_options]
|
|
362
|
+
DJANGO_SETTINGS_MODULE = "config.settings"
|
|
363
|
+
python_files = ["test_*.py", "*_test.py"]
|
|
364
|
+
python_classes = ["Test*"]
|
|
365
|
+
python_functions = ["test_*"]
|
|
366
|
+
addopts = "--reuse-db --nomigrations -x"
|
|
367
|
+
testpaths = ["apps"]
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
## Common Errors
|
|
371
|
+
|
|
372
|
+
### Error: "no such table"
|
|
373
|
+
|
|
374
|
+
```python
|
|
375
|
+
# CAUSE: TestCase for tenant app model
|
|
376
|
+
# SOLUTION: SimpleTestCase + mocks
|
|
377
|
+
|
|
378
|
+
# WRONG
|
|
379
|
+
class TestProduct(TestCase):
|
|
380
|
+
def test_create(self):
|
|
381
|
+
Product.objects.create(name="Test")
|
|
382
|
+
|
|
383
|
+
# CORRECT
|
|
384
|
+
class TestProduct(SimpleTestCase):
|
|
385
|
+
@patch("apps.catalog.models.Product")
|
|
386
|
+
def test_create(self, mock_product):
|
|
387
|
+
mock_product.objects.create.return_value = MagicMock(id=1)
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Error: "relation does not exist"
|
|
391
|
+
|
|
392
|
+
Same as above — schema mismatch between test and tenant.
|
|
393
|
+
|
|
394
|
+
### Mocking transaction.atomic
|
|
395
|
+
|
|
396
|
+
```python
|
|
397
|
+
from unittest.mock import patch, MagicMock
|
|
398
|
+
|
|
399
|
+
@patch("django.db.transaction.atomic")
|
|
400
|
+
def test_order_creation(self, mock_atomic):
|
|
401
|
+
# transaction.atomic as context manager
|
|
402
|
+
mock_context = MagicMock()
|
|
403
|
+
mock_atomic.return_value = mock_context
|
|
404
|
+
|
|
405
|
+
# Code uses "with transaction.atomic()"
|
|
406
|
+
# Mock must support context protocol
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
## Resources
|
|
410
|
+
|
|
411
|
+
- **Canonical queryset mock**: `apps/pos/tests/test_search_products.py`
|
|
412
|
+
- **FakeSession + transaction mock**: `apps/pos/tests/test_cart_checkout.py`
|
|
413
|
+
- **DoesNotExist + MagicMock.name traps**:
|
|
414
|
+
`apps/pos/tests/test_temporal_cart.py`
|
|
415
|
+
- **Multi-decorator bypass**: `apps/catalog/tests/test_api_catalog_products.py`
|
|
416
|
+
- **pytest config**: `pyproject.toml` → `[tool.pytest.ini_options]`
|
|
417
|
+
- **pytest-django docs**: <https://pytest-django.readthedocs.io/>
|