agentic-team-templates 0.5.0 → 0.6.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.
@@ -0,0 +1,398 @@
1
+ # Test Types
2
+
3
+ Guidelines for understanding and applying different types of tests effectively.
4
+
5
+ ## Testing Trophy Strategy
6
+
7
+ The Testing Trophy prioritizes tests by confidence-to-cost ratio:
8
+
9
+ ```
10
+ ┌─────────────┐
11
+ │ E2E │ Highest confidence, highest cost
12
+ ├─────────────┤
13
+ │ │
14
+ │ Integration │ Best confidence/cost ratio
15
+ │ │
16
+ ├─────────────┤
17
+ │ Unit │ Fast but narrow scope
18
+ ├─────────────┤
19
+ │ Static │ Zero runtime cost
20
+ └─────────────┘
21
+ ```
22
+
23
+ ## Static Analysis (~10%)
24
+
25
+ Catch errors at compile/lint time before tests even run.
26
+
27
+ ### What It Catches
28
+
29
+ - Type mismatches
30
+ - Null/undefined access
31
+ - Unused variables
32
+ - Import errors
33
+ - Formatting inconsistencies
34
+ - Common security issues
35
+
36
+ ### Tools
37
+
38
+ ```ts
39
+ // TypeScript strict mode
40
+ {
41
+ "compilerOptions": {
42
+ "strict": true,
43
+ "noImplicitAny": true,
44
+ "strictNullChecks": true,
45
+ "noUncheckedIndexedAccess": true
46
+ }
47
+ }
48
+ ```
49
+
50
+ ```js
51
+ // ESLint configuration
52
+ module.exports = {
53
+ extends: [
54
+ 'eslint:recommended',
55
+ 'plugin:@typescript-eslint/strict',
56
+ 'plugin:security/recommended',
57
+ ],
58
+ rules: {
59
+ '@typescript-eslint/no-explicit-any': 'error',
60
+ '@typescript-eslint/no-unused-vars': 'error',
61
+ },
62
+ };
63
+ ```
64
+
65
+ ### Cost-Benefit
66
+
67
+ - **Cost**: Near zero (runs in editor/CI)
68
+ - **Confidence**: Catches ~20% of bugs
69
+ - **Speed**: Instant feedback
70
+
71
+ ## Unit Tests (~20%)
72
+
73
+ Test isolated pieces of logic without dependencies.
74
+
75
+ ### What to Unit Test
76
+
77
+ - Pure functions (input → output)
78
+ - Utility functions
79
+ - Data transformations
80
+ - Validation logic
81
+ - Algorithms
82
+ - Business rules
83
+
84
+ ### Characteristics
85
+
86
+ - **Fast**: < 10ms per test
87
+ - **Isolated**: No I/O, no databases, no network
88
+ - **Deterministic**: Same result every time
89
+ - **Focused**: One assertion per concept
90
+
91
+ ### Example
92
+
93
+ ```ts
94
+ import { describe, it, expect } from 'vitest';
95
+ import { calculateDiscount, formatCurrency, validateEmail } from './utils';
96
+
97
+ describe('calculateDiscount', () => {
98
+ it('applies percentage discount correctly', () => {
99
+ expect(calculateDiscount(100, { type: 'percentage', value: 10 })).toBe(90);
100
+ });
101
+
102
+ it('applies fixed discount correctly', () => {
103
+ expect(calculateDiscount(100, { type: 'fixed', value: 15 })).toBe(85);
104
+ });
105
+
106
+ it('does not produce negative values', () => {
107
+ expect(calculateDiscount(10, { type: 'fixed', value: 100 })).toBe(0);
108
+ });
109
+
110
+ it('handles zero amount', () => {
111
+ expect(calculateDiscount(0, { type: 'percentage', value: 50 })).toBe(0);
112
+ });
113
+ });
114
+
115
+ describe('validateEmail', () => {
116
+ it.each([
117
+ ['test@example.com', true],
118
+ ['user.name@domain.org', true],
119
+ ['invalid', false],
120
+ ['@nodomain.com', false],
121
+ ['spaces in@email.com', false],
122
+ ])('validateEmail(%s) returns %s', (email, expected) => {
123
+ expect(validateEmail(email)).toBe(expected);
124
+ });
125
+ });
126
+ ```
127
+
128
+ ### What NOT to Unit Test
129
+
130
+ - Simple getters/setters
131
+ - Framework code
132
+ - Third-party libraries
133
+ - Trivial wrappers
134
+
135
+ ## Integration Tests (~60%)
136
+
137
+ Test multiple components working together. **This is where you get the most value.**
138
+
139
+ ### What to Integration Test
140
+
141
+ - Service layer with real database
142
+ - API routes end-to-end (minus external services)
143
+ - Repository functions with database
144
+ - Message handlers with real queues
145
+ - Multiple services interacting
146
+
147
+ ### Characteristics
148
+
149
+ - **Realistic**: Uses real dependencies (database, cache)
150
+ - **Valuable**: Catches integration issues
151
+ - **Moderate speed**: 100-500ms per test
152
+ - **Isolated data**: Each test has clean state
153
+
154
+ ### Example: Service Integration
155
+
156
+ ```ts
157
+ import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
158
+ import { db } from '../lib/db';
159
+ import { orderService } from './orderService';
160
+ import { createTestUser, createTestProduct } from '../test/factories';
161
+
162
+ describe('OrderService', () => {
163
+ let user: User;
164
+ let products: Product[];
165
+
166
+ beforeAll(async () => {
167
+ await db.$connect();
168
+ });
169
+
170
+ afterAll(async () => {
171
+ await db.$disconnect();
172
+ });
173
+
174
+ beforeEach(async () => {
175
+ // Clean slate
176
+ await db.$transaction([
177
+ db.orderItem.deleteMany(),
178
+ db.order.deleteMany(),
179
+ db.product.deleteMany(),
180
+ db.user.deleteMany(),
181
+ ]);
182
+
183
+ // Setup test data
184
+ user = await createTestUser();
185
+ products = await Promise.all([
186
+ createTestProduct({ price: 100, inventory: 10 }),
187
+ createTestProduct({ price: 50, inventory: 5 }),
188
+ ]);
189
+ });
190
+
191
+ describe('createOrder', () => {
192
+ it('creates order with correct total', async () => {
193
+ const order = await orderService.createOrder({
194
+ userId: user.id,
195
+ items: [
196
+ { productId: products[0].id, quantity: 2 },
197
+ { productId: products[1].id, quantity: 1 },
198
+ ],
199
+ });
200
+
201
+ expect(order.total).toBe(250); // (100 * 2) + (50 * 1)
202
+ expect(order.status).toBe('pending');
203
+ expect(order.items).toHaveLength(2);
204
+ });
205
+
206
+ it('decrements product inventory', async () => {
207
+ await orderService.createOrder({
208
+ userId: user.id,
209
+ items: [{ productId: products[0].id, quantity: 3 }],
210
+ });
211
+
212
+ const updatedProduct = await db.product.findUnique({
213
+ where: { id: products[0].id },
214
+ });
215
+ expect(updatedProduct?.inventory).toBe(7); // 10 - 3
216
+ });
217
+
218
+ it('rejects order when inventory insufficient', async () => {
219
+ await expect(
220
+ orderService.createOrder({
221
+ userId: user.id,
222
+ items: [{ productId: products[0].id, quantity: 100 }],
223
+ })
224
+ ).rejects.toThrow('Insufficient inventory');
225
+ });
226
+
227
+ it('rolls back on partial failure', async () => {
228
+ const initialInventory = products[0].inventory;
229
+
230
+ await expect(
231
+ orderService.createOrder({
232
+ userId: user.id,
233
+ items: [
234
+ { productId: products[0].id, quantity: 1 },
235
+ { productId: 'nonexistent', quantity: 1 },
236
+ ],
237
+ })
238
+ ).rejects.toThrow();
239
+
240
+ // Inventory should not have changed
241
+ const product = await db.product.findUnique({ where: { id: products[0].id } });
242
+ expect(product?.inventory).toBe(initialInventory);
243
+ });
244
+ });
245
+ });
246
+ ```
247
+
248
+ ### Example: API Integration
249
+
250
+ ```ts
251
+ import { describe, it, expect, beforeEach } from 'vitest';
252
+ import request from 'supertest';
253
+ import { app } from '../app';
254
+ import { db } from '../lib/db';
255
+ import { createTestUser, createAuthToken } from '../test/factories';
256
+
257
+ describe('POST /api/orders', () => {
258
+ let authToken: string;
259
+ let userId: string;
260
+
261
+ beforeEach(async () => {
262
+ await db.order.deleteMany();
263
+ await db.user.deleteMany();
264
+
265
+ const user = await createTestUser();
266
+ userId = user.id;
267
+ authToken = createAuthToken(user);
268
+ });
269
+
270
+ it('creates order for authenticated user', async () => {
271
+ const response = await request(app)
272
+ .post('/api/orders')
273
+ .set('Authorization', `Bearer ${authToken}`)
274
+ .send({
275
+ items: [{ productId: 'prod-1', quantity: 2 }],
276
+ });
277
+
278
+ expect(response.status).toBe(201);
279
+ expect(response.body.data.userId).toBe(userId);
280
+ });
281
+
282
+ it('returns 401 without authentication', async () => {
283
+ const response = await request(app)
284
+ .post('/api/orders')
285
+ .send({ items: [] });
286
+
287
+ expect(response.status).toBe(401);
288
+ });
289
+
290
+ it('returns 422 for invalid request body', async () => {
291
+ const response = await request(app)
292
+ .post('/api/orders')
293
+ .set('Authorization', `Bearer ${authToken}`)
294
+ .send({ items: 'not-an-array' });
295
+
296
+ expect(response.status).toBe(422);
297
+ expect(response.body.error.code).toBe('VALIDATION_ERROR');
298
+ });
299
+ });
300
+ ```
301
+
302
+ ## End-to-End Tests (~10%)
303
+
304
+ Test critical user journeys through the full stack.
305
+
306
+ ### What to E2E Test
307
+
308
+ - Critical revenue paths (checkout, payment)
309
+ - Authentication flows
310
+ - Core user journeys
311
+ - Cross-browser compatibility (sparse)
312
+
313
+ ### What NOT to E2E Test
314
+
315
+ - Every UI state
316
+ - Form validation (use integration tests)
317
+ - Edge cases (use unit tests)
318
+ - Error handling (use integration tests)
319
+
320
+ ### Characteristics
321
+
322
+ - **Slow**: 10-60 seconds per test
323
+ - **Expensive**: Full environment needed
324
+ - **Brittle**: More moving parts = more failure points
325
+ - **High confidence**: Tests the real thing
326
+
327
+ ### Example
328
+
329
+ ```ts
330
+ import { test, expect } from '@playwright/test';
331
+
332
+ test.describe('Checkout Flow', () => {
333
+ test.beforeEach(async ({ page }) => {
334
+ // Setup test user via API
335
+ await page.request.post('/api/test/seed-user');
336
+ });
337
+
338
+ test('user can complete purchase', async ({ page }) => {
339
+ // Login
340
+ await page.goto('/login');
341
+ await page.fill('[name="email"]', 'test@example.com');
342
+ await page.fill('[name="password"]', 'password123');
343
+ await page.click('button[type="submit"]');
344
+ await expect(page).toHaveURL('/dashboard');
345
+
346
+ // Add to cart
347
+ await page.goto('/products');
348
+ await page.click('[data-testid="product-1"] >> text=Add to Cart');
349
+ await expect(page.locator('[data-testid="cart-count"]')).toHaveText('1');
350
+
351
+ // Checkout
352
+ await page.click('text=Checkout');
353
+ await page.fill('[name="address"]', '123 Main St');
354
+ await page.fill('[name="city"]', 'Anytown');
355
+ await page.fill('[name="zip"]', '12345');
356
+ await page.click('text=Place Order');
357
+
358
+ // Verify
359
+ await expect(page.locator('h1')).toHaveText('Order Confirmed');
360
+ await expect(page.locator('[data-testid="order-number"]')).toBeVisible();
361
+ });
362
+
363
+ test('handles payment failure gracefully', async ({ page }) => {
364
+ await page.goto('/checkout');
365
+ // Use test card that triggers decline
366
+ await page.fill('[name="card-number"]', '4000000000000002');
367
+ await page.click('text=Pay');
368
+
369
+ await expect(page.locator('.error')).toHaveText('Payment declined');
370
+ await expect(page).toHaveURL('/checkout'); // Stays on page
371
+ });
372
+ });
373
+ ```
374
+
375
+ ## Comparison Matrix
376
+
377
+ | Aspect | Static | Unit | Integration | E2E |
378
+ |--------|--------|------|-------------|-----|
379
+ | Speed | Instant | <10ms | 100-500ms | 10-60s |
380
+ | Confidence | Low | Medium | High | Highest |
381
+ | Maintenance | Very Low | Low | Medium | High |
382
+ | Isolation | N/A | Complete | Partial | None |
383
+ | Real deps | No | No | Yes | Yes |
384
+ | When to run | Always | Always | Always | Critical paths |
385
+
386
+ ## Decision Guide
387
+
388
+ ```
389
+ Is it pure logic with no dependencies?
390
+ ├── Yes → Unit test
391
+ └── No
392
+ └── Does it involve database/services?
393
+ ├── Yes → Integration test
394
+ └── No
395
+ └── Is it a critical user journey?
396
+ ├── Yes → E2E test
397
+ └── No → Integration test
398
+ ```