@martinffx/atelier 0.1.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 (130) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/LICENSE +21 -0
  3. package/README.md +285 -0
  4. package/agents/architect.md +38 -0
  5. package/agents/oracle.md +40 -0
  6. package/agents/recon.md +39 -0
  7. package/dist/atelier.js +33082 -0
  8. package/package.json +44 -0
  9. package/skills/code-conventional-commit/SKILL.md +134 -0
  10. package/skills/code-conventional-commit/evals/evals.json +23 -0
  11. package/skills/code-conventional-commit/workspace/iteration-1/eval-0/with_skill/outputs/commit-message.txt +4 -0
  12. package/skills/code-conventional-commit/workspace/iteration-1/eval-0/with_skill/outputs/eval_metadata.json +6 -0
  13. package/skills/code-conventional-commit/workspace/iteration-1/eval-1/with_skill/outputs/eval_metadata.json +6 -0
  14. package/skills/code-conventional-commit/workspace/iteration-1/eval-1/with_skill/outputs/validation-result.txt +16 -0
  15. package/skills/code-conventional-commit/workspace/iteration-1/eval-2/with_skill/outputs/commit-message.txt +8 -0
  16. package/skills/code-conventional-commit/workspace/iteration-1/eval-2/with_skill/outputs/eval_metadata.json +6 -0
  17. package/skills/code-conventional-commit/workspace/iteration-1/report.html +1325 -0
  18. package/skills/code-debug/SKILL.md +271 -0
  19. package/skills/code-debug/references/language-tools.md +139 -0
  20. package/skills/code-debug/references/techniques.md +117 -0
  21. package/skills/code-docs/SKILL.md +171 -0
  22. package/skills/code-docs/references/api-docs.md +116 -0
  23. package/skills/code-docs/references/changelog.md +64 -0
  24. package/skills/code-docs/references/readme-structure.md +94 -0
  25. package/skills/code-git-worktrees/SKILL.md +218 -0
  26. package/skills/code-perf/SKILL.md +163 -0
  27. package/skills/code-perf/references/common-bottlenecks.md +124 -0
  28. package/skills/code-perf/references/optimization-patterns.md +167 -0
  29. package/skills/code-perf/references/profiling-tools.md +126 -0
  30. package/skills/code-review/SKILL.md +78 -0
  31. package/skills/code-review/references/output.md +200 -0
  32. package/skills/code-review/references/reviewers.md +719 -0
  33. package/skills/code-review/references/rq.md +357 -0
  34. package/skills/code-review/references/rs.md +30 -0
  35. package/skills/code-security/SKILL.md +183 -0
  36. package/skills/code-security/references/owasp-top-10.md +129 -0
  37. package/skills/code-security/references/security-tools.md +102 -0
  38. package/skills/code-security/references/vulnerability-patterns.md +129 -0
  39. package/skills/code-stacked-commit/SKILL.md +155 -0
  40. package/skills/code-subagents/SKILL.md +184 -0
  41. package/skills/code-subagents/references/code-quality-reviewer-prompt.md +48 -0
  42. package/skills/code-subagents/references/implementor-prompt.md +79 -0
  43. package/skills/code-subagents/references/spec-reviewer-prompt.md +62 -0
  44. package/skills/oracle-architect/SKILL.md +246 -0
  45. package/skills/oracle-architect/references/api-design.md +345 -0
  46. package/skills/oracle-architect/references/data-modeling.md +318 -0
  47. package/skills/oracle-architect/references/ddd-patterns.md +247 -0
  48. package/skills/oracle-challenge/SKILL.md +126 -0
  49. package/skills/oracle-testing/SKILL.md +237 -0
  50. package/skills/oracle-testing/references/anti-patterns.md +618 -0
  51. package/skills/oracle-testing/references/boundaries.md +579 -0
  52. package/skills/oracle-testing/references/core-testing.md +691 -0
  53. package/skills/oracle-testing/references/edge-testing.md +794 -0
  54. package/skills/oracle-testing/references/stub-driven-tdd.md +322 -0
  55. package/skills/oracle-thinkdeep/SKILL.md +164 -0
  56. package/skills/python-architecture/SKILL.md +148 -0
  57. package/skills/python-architecture/references/data-modeling.md +558 -0
  58. package/skills/python-architecture/references/ddd.md +569 -0
  59. package/skills/python-architecture/references/functional-core.md +337 -0
  60. package/skills/python-build-tools/SKILL.md +153 -0
  61. package/skills/python-build-tools/references/basedpyright.md +321 -0
  62. package/skills/python-build-tools/references/ruff.md +225 -0
  63. package/skills/python-build-tools/references/uv.md +257 -0
  64. package/skills/python-fastapi/SKILL.md +184 -0
  65. package/skills/python-fastapi/references/api-design.md +563 -0
  66. package/skills/python-fastapi/references/dependencies.md +479 -0
  67. package/skills/python-fastapi/references/middleware.md +475 -0
  68. package/skills/python-fastapi/references/validation.md +497 -0
  69. package/skills/python-modern-python/SKILL.md +201 -0
  70. package/skills/python-modern-python/references/async.md +76 -0
  71. package/skills/python-modern-python/references/pattern-matching.md +89 -0
  72. package/skills/python-modern-python/references/typing.md +96 -0
  73. package/skills/python-monorepo/SKILL.md +146 -0
  74. package/skills/python-monorepo/references/docker.md +515 -0
  75. package/skills/python-monorepo/references/namespace-packages.md +426 -0
  76. package/skills/python-monorepo/references/workspace-config.md +341 -0
  77. package/skills/python-sqlalchemy/SKILL.md +119 -0
  78. package/skills/python-sqlalchemy/references/async.md +51 -0
  79. package/skills/python-sqlalchemy/references/models.md +67 -0
  80. package/skills/python-sqlalchemy/references/queries.md +57 -0
  81. package/skills/python-temporal/SKILL.md +128 -0
  82. package/skills/python-temporal/references/patterns.md +87 -0
  83. package/skills/python-temporal/references/testing.md +54 -0
  84. package/skills/python-testing/SKILL.md +161 -0
  85. package/skills/python-testing/references/boundaries.md +449 -0
  86. package/skills/python-testing/references/mocking.md +449 -0
  87. package/skills/python-testing/references/pytest.md +524 -0
  88. package/skills/spec-brainstorm/SKILL.md +325 -0
  89. package/skills/spec-finish/SKILL.md +159 -0
  90. package/skills/spec-implement/SKILL.md +296 -0
  91. package/skills/spec-orchestrator/SKILL.md +156 -0
  92. package/skills/spec-plan/SKILL.md +248 -0
  93. package/skills/typescript-api-design/SKILL.md +396 -0
  94. package/skills/typescript-api-design/references/error-responses.md +469 -0
  95. package/skills/typescript-build-tools/SKILL.md +447 -0
  96. package/skills/typescript-build-tools/references/biome.md +436 -0
  97. package/skills/typescript-build-tools/references/bun.md +432 -0
  98. package/skills/typescript-build-tools/references/turborepo.md +426 -0
  99. package/skills/typescript-build-tools/references/vitest.md +518 -0
  100. package/skills/typescript-drizzle-orm/SKILL.md +398 -0
  101. package/skills/typescript-drizzle-orm/references/cloudflare.md +392 -0
  102. package/skills/typescript-drizzle-orm/references/entity-pattern.md +412 -0
  103. package/skills/typescript-drizzle-orm/references/postgresql.md +191 -0
  104. package/skills/typescript-drizzle-orm/references/repository-pattern.md +479 -0
  105. package/skills/typescript-drizzle-orm/references/sqlite.md +291 -0
  106. package/skills/typescript-dynamodb-toolbox/SKILL.md +370 -0
  107. package/skills/typescript-dynamodb-toolbox/references/entity-layer.md +574 -0
  108. package/skills/typescript-dynamodb-toolbox/references/error-handling.md +495 -0
  109. package/skills/typescript-dynamodb-toolbox/references/modeling.md +332 -0
  110. package/skills/typescript-dynamodb-toolbox/references/repository-layer.md +631 -0
  111. package/skills/typescript-dynamodb-toolbox/references/testing.md +718 -0
  112. package/skills/typescript-dynamodb-toolbox/references/transactions.md +423 -0
  113. package/skills/typescript-effect-ts/SKILL.md +341 -0
  114. package/skills/typescript-effect-ts/references/core.md +412 -0
  115. package/skills/typescript-effect-ts/references/error-handling.md +468 -0
  116. package/skills/typescript-effect-ts/references/resources.md +476 -0
  117. package/skills/typescript-effect-ts/references/schema.md +536 -0
  118. package/skills/typescript-effect-ts/references/services.md +461 -0
  119. package/skills/typescript-fastify/SKILL.md +405 -0
  120. package/skills/typescript-fastify/references/plugins.md +329 -0
  121. package/skills/typescript-fastify/references/typeid.md +315 -0
  122. package/skills/typescript-functional-patterns/SKILL.md +402 -0
  123. package/skills/typescript-functional-patterns/references/adts.md +497 -0
  124. package/skills/typescript-functional-patterns/references/branded-types.md +542 -0
  125. package/skills/typescript-functional-patterns/references/migration-guide.md +695 -0
  126. package/skills/typescript-functional-patterns/references/option-result.md +585 -0
  127. package/skills/typescript-testing/SKILL.md +353 -0
  128. package/skills/typescript-testing/references/mocking.md +675 -0
  129. package/skills/typescript-testing/references/msw.md +852 -0
  130. package/skills/typescript-testing/references/snapshot-testing.md +774 -0
@@ -0,0 +1,579 @@
1
+ # Layer Boundary Testing Patterns
2
+
3
+ Detailed boundary testing patterns for each layer in the architecture.
4
+
5
+ ## Core Principle
6
+
7
+ Test the contract between layers, not the implementation within layers.
8
+
9
+ ```
10
+ Test here ──────▼──────────────────▼────── Test here
11
+ Effectful Edge │ Functional Core
12
+ (stub) │ (unit test)
13
+ ```
14
+
15
+ ## Entity Boundary (Functional Core)
16
+
17
+ Pure functions with no dependencies.
18
+
19
+ ### What to Test at Entity Boundary
20
+
21
+ - Validation rules
22
+ - Business rules
23
+ - Data transformations (fromRequest, toRecord, toResponse)
24
+ - Invariants and state management
25
+ - Edge cases and error conditions
26
+
27
+ ### Entity Test Examples
28
+
29
+ ```typescript
30
+ describe('Order entity', () => {
31
+ describe('validation', () => {
32
+ it('rejects empty items', () => {
33
+ const order = new Order('1', 'C1', [], 'pending', 0);
34
+ const result = order.validate();
35
+ expect(result.ok).toBe(false);
36
+ expect(result.error).toContain('at least one item');
37
+ });
38
+
39
+ it('rejects negative total', () => {
40
+ const order = new Order('1', 'C1', [], 'pending', -10);
41
+ const result = order.validate();
42
+ expect(result.ok).toBe(false);
43
+ expect(result.error).toContain('total must be positive');
44
+ });
45
+
46
+ it('accepts valid order', () => {
47
+ const order = new Order('1', 'C1', [{ productId: 'P1', quantity: 1, price: 10 }], 'pending', 10);
48
+ const result = order.validate();
49
+ expect(result.ok).toBe(true);
50
+ });
51
+ });
52
+
53
+ describe('business rules', () => {
54
+ it('cannot cancel shipped order', () => {
55
+ const order = new Order('1', 'C1', [], 'shipped', 0);
56
+ expect(order.canCancel()).toBe(false);
57
+ });
58
+
59
+ it('can cancel pending order', () => {
60
+ const order = new Order('1', 'C1', [], 'pending', 0);
61
+ expect(order.canCancel()).toBe(true);
62
+ });
63
+
64
+ it('calculates discount correctly', () => {
65
+ const order = new Order('1', 'C1', [], 'pending', 100);
66
+ const discounted = order.applyDiscount(0.1); // 10% discount
67
+ expect(discounted.total).toBe(90);
68
+ });
69
+ });
70
+
71
+ describe('transformations', () => {
72
+ it('converts request to entity with calculated total', () => {
73
+ const order = Order.fromRequest({
74
+ customerId: 'C1',
75
+ items: [
76
+ { productId: 'P1', quantity: 2, price: 10 },
77
+ { productId: 'P2', quantity: 1, price: 15 }
78
+ ]
79
+ });
80
+ expect(order.customerId).toBe('C1');
81
+ expect(order.total).toBe(35);
82
+ expect(order.status).toBe('pending');
83
+ });
84
+
85
+ it('converts entity to database record', () => {
86
+ const order = new Order('123', 'C1', [{ productId: 'P1', quantity: 2, price: 10 }], 'pending', 20);
87
+ const record = order.toRecord();
88
+
89
+ expect(record.id).toBe('123');
90
+ expect(record.customer_id).toBe('C1');
91
+ expect(record.status).toBe('pending');
92
+ expect(record.items).toBe(JSON.stringify([{ productId: 'P1', quantity: 2, price: 10 }]));
93
+ expect(record.total).toBe(20);
94
+ });
95
+
96
+ it('converts database record back to entity', () => {
97
+ const record = {
98
+ id: '123',
99
+ customer_id: 'C1',
100
+ items: '[{"productId":"P1","quantity":2,"price":10}]',
101
+ status: 'pending',
102
+ total: 20
103
+ };
104
+ const order = Order.fromRecord(record);
105
+
106
+ expect(order.id).toBe('123');
107
+ expect(order.customerId).toBe('C1');
108
+ expect(order.items).toHaveLength(1);
109
+ expect(order.items[0].productId).toBe('P1');
110
+ });
111
+
112
+ it('converts entity to API response', () => {
113
+ const order = new Order('123', 'C1', [{ productId: 'P1', quantity: 2, price: 10 }], 'pending', 20);
114
+ const response = order.toResponse();
115
+
116
+ expect(response.id).toBe('123');
117
+ expect(response.customerId).toBe('C1');
118
+ expect(response.status).toBe('pending');
119
+ expect(response.total).toBe(20);
120
+ });
121
+ });
122
+
123
+ describe('invariants', () => {
124
+ it('maintains total consistency with items', () => {
125
+ const order = new Order('1', 'C1', [{ productId: 'P1', quantity: 2, price: 10 }], 'pending', 20);
126
+
127
+ // Invariant: total always matches item calculation
128
+ const calculated = order.items.reduce((sum, item) => sum + item.quantity * item.price, 0);
129
+ expect(order.total).toBe(calculated);
130
+ });
131
+
132
+ it('prevents invalid status transitions', () => {
133
+ const order = new Order('1', 'C1', [], 'shipped', 0);
134
+
135
+ // Cannot go backwards
136
+ expect(() => order.updateStatus('pending')).toThrow('Invalid status transition');
137
+ });
138
+ });
139
+ });
140
+ ```
141
+
142
+ ## Service Boundary (Functional Core)
143
+
144
+ Orchestration logic with stubbed dependencies.
145
+
146
+ ### What to Test at Service Boundary
147
+
148
+ - Business workflow execution
149
+ - Orchestration of multiple entities
150
+ - Error propagation and handling
151
+ - Repository interaction patterns
152
+ - Validation and state transitions
153
+
154
+ ### Service Test Examples
155
+
156
+ ```typescript
157
+ describe('OrderService', () => {
158
+ let service: OrderService;
159
+ let mockOrderRepo: OrderRepository;
160
+ let mockInventoryRepo: InventoryRepository;
161
+
162
+ beforeEach(() => {
163
+ mockOrderRepo = {
164
+ save: vi.fn(),
165
+ findById: vi.fn(),
166
+ delete: vi.fn()
167
+ };
168
+ mockInventoryRepo = {
169
+ checkAvailability: vi.fn(),
170
+ reserve: vi.fn(),
171
+ release: vi.fn()
172
+ };
173
+
174
+ service = new OrderService(mockOrderRepo, mockInventoryRepo);
175
+ });
176
+
177
+ describe('createOrder', () => {
178
+ it('creates order with valid data', async () => {
179
+ mockInventoryRepo.checkAvailability.mockResolvedValue({ available: true });
180
+ mockOrderRepo.save.mockResolvedValue({ id: '123' });
181
+
182
+ const result = await service.createOrder({
183
+ customerId: 'C1',
184
+ items: [{ productId: 'P1', quantity: 2 }]
185
+ });
186
+
187
+ expect(result.ok).toBe(true);
188
+ expect(mockOrderRepo.save).toHaveBeenCalled();
189
+ });
190
+
191
+ it('checks inventory before saving', async () => {
192
+ mockInventoryRepo.checkAvailability.mockResolvedValue({ available: true });
193
+ mockOrderRepo.save.mockResolvedValue({ id: '123' });
194
+
195
+ await service.createOrder({
196
+ customerId: 'C1',
197
+ items: [
198
+ { productId: 'P1', quantity: 2 },
199
+ { productId: 'P2', quantity: 3 }
200
+ ]
201
+ });
202
+
203
+ expect(mockInventoryRepo.checkAvailability).toHaveBeenCalledWith(
204
+ expect.arrayContaining([
205
+ expect.objectContaining({ productId: 'P1', quantity: 2 }),
206
+ expect.objectContaining({ productId: 'P2', quantity: 3 })
207
+ ])
208
+ );
209
+ });
210
+
211
+ it('does not save order when inventory unavailable', async () => {
212
+ mockInventoryRepo.checkAvailability.mockResolvedValue({ available: false });
213
+
214
+ const result = await service.createOrder({
215
+ customerId: 'C1',
216
+ items: [{ productId: 'P1', quantity: 100 }]
217
+ });
218
+
219
+ expect(result.ok).toBe(false);
220
+ expect(mockOrderRepo.save).not.toHaveBeenCalled();
221
+ });
222
+
223
+ it('reverses inventory reservation on save failure', async () => {
224
+ mockInventoryRepo.checkAvailability.mockResolvedValue({ available: true });
225
+ mockOrderRepo.save.mockRejectedValue(new Error('DB error'));
226
+
227
+ const result = await service.createOrder({
228
+ customerId: 'C1',
229
+ items: [{ productId: 'P1', quantity: 2 }]
230
+ });
231
+
232
+ expect(result.ok).toBe(false);
233
+ expect(mockInventoryRepo.release).toHaveBeenCalled();
234
+ });
235
+ });
236
+
237
+ describe('cancelOrder', () => {
238
+ it('finds order and cancels it', async () => {
239
+ mockOrderRepo.findById.mockResolvedValue({ id: '123', status: 'pending' });
240
+ mockOrderRepo.save.mockResolvedValue({ id: '123', status: 'cancelled' });
241
+
242
+ const result = await service.cancelOrder('123');
243
+
244
+ expect(result.ok).toBe(true);
245
+ expect(mockOrderRepo.save).toHaveBeenCalled();
246
+ });
247
+
248
+ it('releases inventory when cancelling', async () => {
249
+ mockOrderRepo.findById.mockResolvedValue({
250
+ id: '123',
251
+ status: 'pending',
252
+ items: [{ productId: 'P1', quantity: 2 }]
253
+ });
254
+
255
+ await service.cancelOrder('123');
256
+
257
+ expect(mockInventoryRepo.release).toHaveBeenCalledWith(
258
+ expect.arrayContaining([
259
+ expect.objectContaining({ productId: 'P1', quantity: 2 })
260
+ ])
261
+ );
262
+ });
263
+
264
+ it('returns error if order not found', async () => {
265
+ mockOrderRepo.findById.mockResolvedValue(null);
266
+
267
+ const result = await service.cancelOrder('999');
268
+
269
+ expect(result.ok).toBe(false);
270
+ expect(mockOrderRepo.save).not.toHaveBeenCalled();
271
+ });
272
+
273
+ it('returns error if order cannot be cancelled', async () => {
274
+ mockOrderRepo.findById.mockResolvedValue({ id: '123', status: 'shipped' });
275
+
276
+ const result = await service.cancelOrder('123');
277
+
278
+ expect(result.ok).toBe(false);
279
+ });
280
+ });
281
+ });
282
+ ```
283
+
284
+ ## Router Boundary (Effectful Edge)
285
+
286
+ HTTP request/response handling.
287
+
288
+ ### What to Test at Router Boundary
289
+
290
+ - Request parsing and validation
291
+ - Response status codes and format
292
+ - Error response handling
293
+ - Request/response transformations
294
+ - HTTP headers and content types
295
+
296
+ ### Router Test Examples
297
+
298
+ ```typescript
299
+ describe('Order API', () => {
300
+ let app: Express;
301
+ let mockService: OrderService;
302
+
303
+ beforeEach(() => {
304
+ mockService = {
305
+ createOrder: vi.fn(),
306
+ cancelOrder: vi.fn(),
307
+ getOrder: vi.fn()
308
+ };
309
+ app = createApp(mockService);
310
+ });
311
+
312
+ describe('POST /orders', () => {
313
+ it('returns 201 for valid request', async () => {
314
+ mockService.createOrder.mockResolvedValue(
315
+ Ok(new Order('123', 'C1', [], 'pending', 0))
316
+ );
317
+
318
+ const response = await request(app)
319
+ .post('/orders')
320
+ .send({ customerId: 'C1', items: [{ productId: 'P1', quantity: 2 }] });
321
+
322
+ expect(response.status).toBe(201);
323
+ expect(response.body.id).toBe('123');
324
+ expect(response.body.status).toBe('pending');
325
+ });
326
+
327
+ it('returns 400 for invalid request body', async () => {
328
+ const response = await request(app)
329
+ .post('/orders')
330
+ .send({ customerId: 'C1' }); // Missing items
331
+
332
+ expect(response.status).toBe(400);
333
+ expect(response.body.error).toBeDefined();
334
+ });
335
+
336
+ it('returns 400 when service returns error', async () => {
337
+ mockService.createOrder.mockResolvedValue(
338
+ Err('Invalid order')
339
+ );
340
+
341
+ const response = await request(app)
342
+ .post('/orders')
343
+ .send({ customerId: 'C1', items: [] });
344
+
345
+ expect(response.status).toBe(400);
346
+ expect(response.body.error).toBe('Invalid order');
347
+ });
348
+
349
+ it('includes correct content-type header', async () => {
350
+ mockService.createOrder.mockResolvedValue(
351
+ Ok(new Order('123', 'C1', [], 'pending', 0))
352
+ );
353
+
354
+ const response = await request(app)
355
+ .post('/orders')
356
+ .send({ customerId: 'C1', items: [{ productId: 'P1', quantity: 2 }] });
357
+
358
+ expect(response.headers['content-type']).toMatch(/application\/json/);
359
+ });
360
+ });
361
+
362
+ describe('GET /orders/:id', () => {
363
+ it('returns 200 with order', async () => {
364
+ mockService.getOrder.mockResolvedValue(
365
+ Ok(new Order('123', 'C1', [], 'pending', 0))
366
+ );
367
+
368
+ const response = await request(app)
369
+ .get('/orders/123');
370
+
371
+ expect(response.status).toBe(200);
372
+ expect(response.body.id).toBe('123');
373
+ });
374
+
375
+ it('returns 404 when order not found', async () => {
376
+ mockService.getOrder.mockResolvedValue(
377
+ Err('Order not found')
378
+ );
379
+
380
+ const response = await request(app)
381
+ .get('/orders/999');
382
+
383
+ expect(response.status).toBe(404);
384
+ });
385
+ });
386
+
387
+ describe('DELETE /orders/:id', () => {
388
+ it('returns 204 on successful cancel', async () => {
389
+ mockService.cancelOrder.mockResolvedValue(Ok(undefined));
390
+
391
+ const response = await request(app)
392
+ .delete('/orders/123');
393
+
394
+ expect(response.status).toBe(204);
395
+ expect(response.body).toEqual({});
396
+ });
397
+
398
+ it('returns 400 when cancel fails', async () => {
399
+ mockService.cancelOrder.mockResolvedValue(
400
+ Err('Cannot cancel shipped order')
401
+ );
402
+
403
+ const response = await request(app)
404
+ .delete('/orders/123');
405
+
406
+ expect(response.status).toBe(400);
407
+ expect(response.body.error).toBe('Cannot cancel shipped order');
408
+ });
409
+ });
410
+ });
411
+ ```
412
+
413
+ ## Repository Boundary (Effectful Edge)
414
+
415
+ Database operations.
416
+
417
+ ### What to Test at Repository Boundary
418
+
419
+ - Save operations preserve data
420
+ - Retrieval returns correct data
421
+ - Updates modify existing records
422
+ - Deletes remove records
423
+ - Complex queries return expected results
424
+ - Transaction handling
425
+
426
+ ### Repository Test Examples
427
+
428
+ ```typescript
429
+ describe('OrderRepository', () => {
430
+ let repo: OrderRepository;
431
+ let testDb: Database;
432
+
433
+ beforeEach(async () => {
434
+ testDb = await createTestDatabase();
435
+ repo = new OrderRepository(testDb);
436
+ });
437
+
438
+ afterEach(async () => {
439
+ await testDb.close();
440
+ });
441
+
442
+ describe('save', () => {
443
+ it('saves order to database', async () => {
444
+ const record = {
445
+ id: '123',
446
+ customer_id: 'C1',
447
+ items: JSON.stringify([{ productId: 'P1', quantity: 2 }]),
448
+ status: 'pending',
449
+ total: 20
450
+ };
451
+
452
+ const saved = await repo.save(record);
453
+
454
+ expect(saved.id).toBe('123');
455
+ expect(saved.customer_id).toBe('C1');
456
+
457
+ // Verify in database
458
+ const found = await testDb.orders.findOne({ id: '123' });
459
+ expect(found).toBeDefined();
460
+ });
461
+
462
+ it('updates existing order', async () => {
463
+ const record = {
464
+ id: '123',
465
+ customer_id: 'C1',
466
+ items: JSON.stringify([]),
467
+ status: 'pending',
468
+ total: 0
469
+ };
470
+
471
+ await repo.save(record);
472
+
473
+ const updated = { ...record, status: 'shipped' };
474
+ await repo.save(updated);
475
+
476
+ const found = await testDb.orders.findOne({ id: '123' });
477
+ expect(found.status).toBe('shipped');
478
+ });
479
+ });
480
+
481
+ describe('findById', () => {
482
+ it('retrieves order by id', async () => {
483
+ const record = {
484
+ id: '123',
485
+ customer_id: 'C1',
486
+ items: JSON.stringify([]),
487
+ status: 'pending',
488
+ total: 0
489
+ };
490
+
491
+ await repo.save(record);
492
+ const found = await repo.findById('123');
493
+
494
+ expect(found).toEqual(record);
495
+ });
496
+
497
+ it('returns null for non-existent id', async () => {
498
+ const found = await repo.findById('999');
499
+
500
+ expect(found).toBeNull();
501
+ });
502
+ });
503
+
504
+ describe('delete', () => {
505
+ it('removes order from database', async () => {
506
+ const record = {
507
+ id: '123',
508
+ customer_id: 'C1',
509
+ items: JSON.stringify([]),
510
+ status: 'pending',
511
+ total: 0
512
+ };
513
+
514
+ await repo.save(record);
515
+ await repo.delete('123');
516
+
517
+ const found = await testDb.orders.findOne({ id: '123' });
518
+ expect(found).toBeUndefined();
519
+ });
520
+ });
521
+ });
522
+ ```
523
+
524
+ ## Consumer Boundary (Effectful Edge)
525
+
526
+ Event message handling.
527
+
528
+ ### Consumer Test Examples
529
+
530
+ ```typescript
531
+ describe('OrderConsumer', () => {
532
+ let consumer: OrderConsumer;
533
+ let mockService: OrderService;
534
+
535
+ beforeEach(() => {
536
+ mockService = {
537
+ processOrder: vi.fn(),
538
+ handlePayment: vi.fn()
539
+ };
540
+ consumer = new OrderConsumer(mockService);
541
+ });
542
+
543
+ describe('OrderPlaced event', () => {
544
+ it('calls service with event data', async () => {
545
+ mockService.processOrder.mockResolvedValue(Ok({}));
546
+
547
+ const event = {
548
+ type: 'OrderPlaced',
549
+ data: { orderId: '123', customerId: 'C1' }
550
+ };
551
+
552
+ await consumer.handle(event);
553
+
554
+ expect(mockService.processOrder).toHaveBeenCalledWith('123');
555
+ });
556
+
557
+ it('ignores unknown event types', async () => {
558
+ const event = {
559
+ type: 'UnknownEvent',
560
+ data: {}
561
+ };
562
+
563
+ await consumer.handle(event);
564
+
565
+ expect(mockService.processOrder).not.toHaveBeenCalled();
566
+ });
567
+ });
568
+ });
569
+ ```
570
+
571
+ ## Summary Table
572
+
573
+ | Boundary | Test Type | What to Stub | What to Assert |
574
+ |----------|-----------|--------------|-----------------|
575
+ | **Entity** | Unit | Nothing (pure) | Validation, rules, transforms |
576
+ | **Service** | Unit | Repositories, other services | Orchestration logic, error handling |
577
+ | **Router** | Integration | Service | Status codes, response format, headers |
578
+ | **Repository** | Integration | Database connection | CRUD operations, queries |
579
+ | **Consumer** | Integration | Service, event source | Event parsing, service calls |