cc-codeconductor 0.2.1

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