@martinffx/atelier 2.0.0 → 3.0.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.
Files changed (81) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +94 -253
  3. package/package.json +1 -1
  4. package/skills/{spec-orchestrator → atelier-orchestrator}/SKILL.md +3 -3
  5. package/skills/atelier-setup/SKILL.md +80 -0
  6. package/skills/code-review/references/reviewers.md +3 -3
  7. package/skills/code-review/references/rq.md +6 -6
  8. package/skills/spec-brainstorm/SKILL.md +5 -5
  9. package/skills/spec-implement/SKILL.md +2 -3
  10. package/skills/spec-plan/SKILL.md +21 -29
  11. package/skills/spec-plan/references/plan_template.md +77 -0
  12. package/skills/python-architecture/SKILL.md +0 -148
  13. package/skills/python-architecture/references/data-modeling.md +0 -558
  14. package/skills/python-architecture/references/ddd.md +0 -569
  15. package/skills/python-architecture/references/functional-core.md +0 -337
  16. package/skills/python-build-tools/SKILL.md +0 -153
  17. package/skills/python-build-tools/references/basedpyright.md +0 -321
  18. package/skills/python-build-tools/references/ruff.md +0 -225
  19. package/skills/python-build-tools/references/uv.md +0 -257
  20. package/skills/python-fastapi/SKILL.md +0 -184
  21. package/skills/python-fastapi/references/api-design.md +0 -563
  22. package/skills/python-fastapi/references/dependencies.md +0 -479
  23. package/skills/python-fastapi/references/middleware.md +0 -475
  24. package/skills/python-fastapi/references/validation.md +0 -497
  25. package/skills/python-modern-python/SKILL.md +0 -201
  26. package/skills/python-modern-python/references/async.md +0 -76
  27. package/skills/python-modern-python/references/pattern-matching.md +0 -89
  28. package/skills/python-modern-python/references/typing.md +0 -96
  29. package/skills/python-monorepo/SKILL.md +0 -146
  30. package/skills/python-monorepo/references/docker.md +0 -515
  31. package/skills/python-monorepo/references/namespace-packages.md +0 -426
  32. package/skills/python-monorepo/references/workspace-config.md +0 -341
  33. package/skills/python-sqlalchemy/SKILL.md +0 -119
  34. package/skills/python-sqlalchemy/references/async.md +0 -51
  35. package/skills/python-sqlalchemy/references/models.md +0 -67
  36. package/skills/python-sqlalchemy/references/queries.md +0 -57
  37. package/skills/python-temporal/SKILL.md +0 -128
  38. package/skills/python-temporal/references/patterns.md +0 -87
  39. package/skills/python-temporal/references/testing.md +0 -54
  40. package/skills/python-testing/SKILL.md +0 -161
  41. package/skills/python-testing/references/boundaries.md +0 -449
  42. package/skills/python-testing/references/mocking.md +0 -449
  43. package/skills/python-testing/references/pytest.md +0 -524
  44. package/skills/typescript-api-design/SKILL.md +0 -396
  45. package/skills/typescript-api-design/references/error-responses.md +0 -469
  46. package/skills/typescript-build-tools/SKILL.md +0 -447
  47. package/skills/typescript-build-tools/references/biome.md +0 -436
  48. package/skills/typescript-build-tools/references/bun.md +0 -432
  49. package/skills/typescript-build-tools/references/turborepo.md +0 -426
  50. package/skills/typescript-build-tools/references/vitest.md +0 -518
  51. package/skills/typescript-drizzle-orm/SKILL.md +0 -398
  52. package/skills/typescript-drizzle-orm/references/cloudflare.md +0 -392
  53. package/skills/typescript-drizzle-orm/references/entity-pattern.md +0 -412
  54. package/skills/typescript-drizzle-orm/references/postgresql.md +0 -191
  55. package/skills/typescript-drizzle-orm/references/repository-pattern.md +0 -479
  56. package/skills/typescript-drizzle-orm/references/sqlite.md +0 -291
  57. package/skills/typescript-dynamodb-toolbox/SKILL.md +0 -370
  58. package/skills/typescript-dynamodb-toolbox/references/entity-layer.md +0 -574
  59. package/skills/typescript-dynamodb-toolbox/references/error-handling.md +0 -495
  60. package/skills/typescript-dynamodb-toolbox/references/modeling.md +0 -332
  61. package/skills/typescript-dynamodb-toolbox/references/repository-layer.md +0 -631
  62. package/skills/typescript-dynamodb-toolbox/references/testing.md +0 -718
  63. package/skills/typescript-dynamodb-toolbox/references/transactions.md +0 -423
  64. package/skills/typescript-effect-ts/SKILL.md +0 -341
  65. package/skills/typescript-effect-ts/references/core.md +0 -412
  66. package/skills/typescript-effect-ts/references/error-handling.md +0 -468
  67. package/skills/typescript-effect-ts/references/resources.md +0 -476
  68. package/skills/typescript-effect-ts/references/schema.md +0 -536
  69. package/skills/typescript-effect-ts/references/services.md +0 -461
  70. package/skills/typescript-fastify/SKILL.md +0 -405
  71. package/skills/typescript-fastify/references/plugins.md +0 -329
  72. package/skills/typescript-fastify/references/typeid.md +0 -315
  73. package/skills/typescript-functional-patterns/SKILL.md +0 -402
  74. package/skills/typescript-functional-patterns/references/adts.md +0 -497
  75. package/skills/typescript-functional-patterns/references/branded-types.md +0 -542
  76. package/skills/typescript-functional-patterns/references/migration-guide.md +0 -695
  77. package/skills/typescript-functional-patterns/references/option-result.md +0 -585
  78. package/skills/typescript-testing/SKILL.md +0 -353
  79. package/skills/typescript-testing/references/mocking.md +0 -675
  80. package/skills/typescript-testing/references/msw.md +0 -852
  81. package/skills/typescript-testing/references/snapshot-testing.md +0 -774
@@ -1,449 +0,0 @@
1
- # Layer Boundary Testing
2
-
3
- Test at component boundaries, not internals. Each layer has specific test focus areas.
4
-
5
- ## What to Test at Each Layer
6
-
7
- ### Entity Layer
8
- - ✅ Domain logic and validation
9
- - ✅ Transformations (from_request, to_response, to_record)
10
- - ✅ Business rules and calculated fields
11
- - ❌ Simple getters/setters
12
- - ❌ Python language features
13
-
14
- ### Repository Layer
15
- - ✅ CRUD operations
16
- - ✅ Query logic and filters
17
- - ✅ Entity ↔ Record transformations
18
- - ✅ Database constraints
19
- - ❌ ORM framework code
20
- - ❌ SQL generation
21
-
22
- ### Service Layer
23
- - ✅ Business workflows and orchestration
24
- - ✅ Error handling and validation
25
- - ✅ Dependency coordination
26
- - ✅ Complex logic
27
- - ❌ Trivial delegation
28
- - ❌ Single-line pass-throughs
29
-
30
- ### Router Layer
31
- - ✅ Request validation and deserialization
32
- - ✅ Response serialization
33
- - ✅ Status codes and HTTP semantics
34
- - ✅ Error response formatting
35
- - ❌ Framework routing mechanics
36
- - ❌ Dependency injection
37
-
38
- ## Entity Layer Examples
39
-
40
- ### Test Transformations
41
-
42
- ```python
43
- from entities import Product
44
- from decimal import Decimal
45
- from uuid import uuid4
46
-
47
- def test_product_from_request():
48
- """Test creation from request object"""
49
- request = CreateProductRequest(
50
- name="Widget",
51
- price=Decimal("9.99"),
52
- sku="SKU123"
53
- )
54
-
55
- product = Product.from_request(request)
56
-
57
- assert product.name == "Widget"
58
- assert product.price == Decimal("9.99")
59
- assert product.sku == "SKU123"
60
- assert isinstance(product.id, UUID)
61
- assert product.created_at is not None
62
-
63
- def test_product_to_response():
64
- """Test transformation to API response"""
65
- product = Product(
66
- id=uuid4(),
67
- name="Widget",
68
- price=Decimal("9.99"),
69
- sku="SKU123",
70
- created_at=datetime.now(),
71
- )
72
-
73
- response = product.to_response()
74
-
75
- assert response.id == str(product.id)
76
- assert response.name == "Widget"
77
- assert response.price == 9.99 # Decimal → float
78
-
79
- def test_product_to_record():
80
- """Test transformation to database record"""
81
- product = Product(
82
- id=uuid4(),
83
- name="Widget",
84
- price=Decimal("9.99"),
85
- )
86
-
87
- record = product.to_record()
88
-
89
- assert record.id == product.id
90
- assert record.name == "Widget"
91
- assert record.price == Decimal("9.99")
92
- ```
93
-
94
- ### Test Validation
95
-
96
- ```python
97
- def test_product_validate_price():
98
- """Test price validation"""
99
- with pytest.raises(ValueError, match="Price must be positive"):
100
- Product(
101
- id=uuid4(),
102
- name="Widget",
103
- price=Decimal("-10"),
104
- )
105
-
106
- def test_product_validate_name():
107
- """Test name is required"""
108
- with pytest.raises(ValueError, match="Name is required"):
109
- Product(
110
- id=uuid4(),
111
- name="",
112
- price=Decimal("9.99"),
113
- )
114
- ```
115
-
116
- ### Test Business Logic
117
-
118
- ```python
119
- def test_product_apply_discount():
120
- """Test discount calculation"""
121
- product = Product(
122
- id=uuid4(),
123
- name="Widget",
124
- price=Decimal("100"),
125
- )
126
-
127
- discounted = product.apply_discount(Decimal("0.1"))
128
-
129
- assert discounted.price == Decimal("90")
130
- assert product.price == Decimal("100") # Original unchanged
131
-
132
- def test_product_tax_calculation():
133
- """Test tax on price"""
134
- product = Product(
135
- id=uuid4(),
136
- name="Widget",
137
- price=Decimal("100"),
138
- )
139
-
140
- tax = product.calculate_tax(Decimal("0.08"))
141
-
142
- assert tax == Decimal("8")
143
- ```
144
-
145
- ## Service Layer Examples
146
-
147
- ### Test Business Workflows
148
-
149
- ```python
150
- from unittest.mock import Mock, call
151
-
152
- def test_create_product_service():
153
- """Test service orchestration"""
154
- # Stub repository
155
- mock_repo = Mock()
156
- mock_repo.save.return_value = Product(
157
- id=uuid4(),
158
- name="Widget",
159
- price=Decimal("9.99"),
160
- )
161
-
162
- # Test service
163
- service = ProductService(repo=mock_repo)
164
- request = CreateProductRequest(name="Widget", price=Decimal("9.99"))
165
-
166
- result = service.create(request)
167
-
168
- # Verify repository was called
169
- mock_repo.save.assert_called_once()
170
- assert result.name == "Widget"
171
-
172
- def test_apply_bulk_discount():
173
- """Test orchestration of complex business logic"""
174
- mock_repo = Mock()
175
- mock_repo.find_all.return_value = [
176
- Product(id=uuid4(), name="A", price=Decimal("100")),
177
- Product(id=uuid4(), name="B", price=Decimal("100")),
178
- ]
179
- mock_repo.save.return_value = None
180
-
181
- service = ProductService(repo=mock_repo)
182
- service.apply_bulk_discount(Decimal("0.1"))
183
-
184
- # Verify save called twice
185
- assert mock_repo.save.call_count == 2
186
- ```
187
-
188
- ### Test Error Handling
189
-
190
- ```python
191
- def test_create_product_validation_error():
192
- """Test service error handling"""
193
- mock_repo = Mock()
194
-
195
- service = ProductService(repo=mock_repo)
196
-
197
- with pytest.raises(ValueError, match="Invalid price"):
198
- service.create(CreateProductRequest(
199
- name="Widget",
200
- price=Decimal("-10"),
201
- ))
202
-
203
- # Repository not called on validation error
204
- mock_repo.save.assert_not_called()
205
- ```
206
-
207
- ## Repository Layer Examples
208
-
209
- ### Test Data Access
210
-
211
- ```python
212
- import pytest
213
- from sqlalchemy import create_engine
214
- from sqlalchemy.orm import sessionmaker
215
-
216
- @pytest.fixture
217
- def test_db():
218
- """In-memory test database"""
219
- engine = create_engine("sqlite:///:memory:")
220
- Base.metadata.create_all(engine)
221
- SessionLocal = sessionmaker(bind=engine)
222
- db = SessionLocal()
223
- try:
224
- yield db
225
- finally:
226
- db.close()
227
-
228
- def test_repository_save(test_db):
229
- """Test saving entity"""
230
- repo = ProductRepository(test_db)
231
-
232
- product = Product(
233
- id=uuid4(),
234
- name="Widget",
235
- price=Decimal("9.99"),
236
- )
237
- saved = repo.save(product)
238
-
239
- assert saved.id == product.id
240
- # Verify in database
241
- record = test_db.query(ProductRecord).filter_by(id=product.id).first()
242
- assert record is not None
243
- assert record.name == "Widget"
244
-
245
- def test_repository_find_by_id(test_db):
246
- """Test retrieving entity by ID"""
247
- repo = ProductRepository(test_db)
248
- product_id = uuid4()
249
-
250
- # Create record
251
- test_db.add(ProductRecord(
252
- id=product_id,
253
- name="Widget",
254
- price=Decimal("9.99"),
255
- ))
256
- test_db.commit()
257
-
258
- # Find entity
259
- found = repo.find_by_id(product_id)
260
-
261
- assert found is not None
262
- assert found.id == product_id
263
- assert found.name == "Widget"
264
-
265
- def test_repository_delete(test_db):
266
- """Test deleting entity"""
267
- repo = ProductRepository(test_db)
268
- product_id = uuid4()
269
-
270
- # Create record
271
- test_db.add(ProductRecord(id=product_id, name="Widget"))
272
- test_db.commit()
273
-
274
- # Delete
275
- repo.delete(product_id)
276
-
277
- # Verify deleted
278
- assert test_db.query(ProductRecord).filter_by(id=product_id).first() is None
279
- ```
280
-
281
- ### Test Query Logic
282
-
283
- ```python
284
- def test_repository_find_by_name(test_db):
285
- """Test complex query"""
286
- repo = ProductRepository(test_db)
287
-
288
- # Insert test data
289
- test_db.add_all([
290
- ProductRecord(id=uuid4(), name="Widget A", price=Decimal("10")),
291
- ProductRecord(id=uuid4(), name="Widget B", price=Decimal("20")),
292
- ProductRecord(id=uuid4(), name="Gadget", price=Decimal("15")),
293
- ])
294
- test_db.commit()
295
-
296
- # Query
297
- results = repo.find_by_name("Widget")
298
-
299
- assert len(results) == 2
300
- assert all("Widget" in p.name for p in results)
301
-
302
- def test_repository_find_expensive_products(test_db):
303
- """Test filtering"""
304
- repo = ProductRepository(test_db)
305
-
306
- test_db.add_all([
307
- ProductRecord(id=uuid4(), name="A", price=Decimal("10")),
308
- ProductRecord(id=uuid4(), name="B", price=Decimal("100")),
309
- ProductRecord(id=uuid4(), name="C", price=Decimal("200")),
310
- ])
311
- test_db.commit()
312
-
313
- results = repo.find_by_price_above(Decimal("50"))
314
-
315
- assert len(results) == 2
316
- ```
317
-
318
- ## Router Layer Examples
319
-
320
- ### Test HTTP Endpoints
321
-
322
- ```python
323
- from fastapi.testclient import TestClient
324
-
325
- def test_create_product_endpoint():
326
- """Test POST endpoint"""
327
- client = TestClient(app)
328
-
329
- response = client.post(
330
- "/products",
331
- json={"name": "Widget", "price": 9.99},
332
- )
333
-
334
- assert response.status_code == 201
335
- data = response.json()
336
- assert data["name"] == "Widget"
337
- assert data["price"] == 9.99
338
- assert "id" in data
339
-
340
- def test_get_product_endpoint(test_db):
341
- """Test GET endpoint"""
342
- client = TestClient(app)
343
-
344
- # Create product
345
- create_response = client.post(
346
- "/products",
347
- json={"name": "Widget", "price": 9.99},
348
- )
349
- product_id = create_response.json()["id"]
350
-
351
- # Get product
352
- response = client.get(f"/products/{product_id}")
353
-
354
- assert response.status_code == 200
355
- assert response.json()["id"] == product_id
356
-
357
- def test_update_product_endpoint():
358
- """Test PUT endpoint"""
359
- client = TestClient(app)
360
-
361
- # Create
362
- create_resp = client.post(
363
- "/products",
364
- json={"name": "Widget", "price": 9.99},
365
- )
366
- product_id = create_resp.json()["id"]
367
-
368
- # Update
369
- response = client.put(
370
- f"/products/{product_id}",
371
- json={"name": "Updated Widget", "price": 19.99},
372
- )
373
-
374
- assert response.status_code == 200
375
- assert response.json()["name"] == "Updated Widget"
376
- ```
377
-
378
- ### Test Error Responses
379
-
380
- ```python
381
- def test_get_nonexistent_product():
382
- """Test 404 response"""
383
- client = TestClient(app)
384
-
385
- response = client.get(f"/products/{uuid4()}")
386
-
387
- assert response.status_code == 404
388
- assert "not found" in response.json()["detail"].lower()
389
-
390
- def test_invalid_request_body():
391
- """Test 422 validation error"""
392
- client = TestClient(app)
393
-
394
- response = client.post(
395
- "/products",
396
- json={"name": "Widget"}, # Missing price
397
- )
398
-
399
- assert response.status_code == 422
400
- ```
401
-
402
- ## Testing Anti-Patterns to Avoid
403
-
404
- ### Don't Test Everything
405
-
406
- ```python
407
- # BAD: Testing simple getters
408
- def test_get_name():
409
- product = Product(id=uuid4(), name="Widget")
410
- assert product.name == "Widget" # Trivial
411
-
412
- # GOOD: Test meaningful business logic
413
- def test_apply_discount_calculates_correctly():
414
- product = Product(id=uuid4(), price=Decimal("100"))
415
- discounted = product.apply_discount(Decimal("0.1"))
416
- assert discounted.price == Decimal("90")
417
- ```
418
-
419
- ### Don't Test Framework Code
420
-
421
- ```python
422
- # BAD: Testing SQLAlchemy internals
423
- def test_sqlalchemy_session():
424
- session = SessionLocal()
425
- assert session is not None # Framework concern
426
-
427
- # GOOD: Test your query logic
428
- def test_find_by_price_returns_products():
429
- repo = ProductRepository(test_db)
430
- results = repo.find_by_price_above(Decimal("50"))
431
- assert all(p.price > 50 for p in results)
432
- ```
433
-
434
- ### Don't Over-Mock
435
-
436
- ```python
437
- # BAD: Mocking too much
438
- def test_service():
439
- mock_repo = Mock()
440
- mock_entity = Mock()
441
- mock_response = Mock()
442
- # Multiple layers of mocks make test fragile
443
-
444
- # GOOD: Real objects, stubbed boundaries
445
- def test_service(test_db):
446
- repo = ProductRepository(test_db) # Real
447
- service = ProductService(repo) # Real
448
- result = service.create(request) # One boundary tested
449
- ```