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.
- package/README.md +40 -1
- package/bin/cli.js +4 -1
- package/package.json +8 -3
- package/src/index.js +471 -7
- package/src/index.test.js +947 -0
- package/templates/testing/.cursorrules/advanced-techniques.md +596 -0
- package/templates/testing/.cursorrules/ci-cd-integration.md +603 -0
- package/templates/testing/.cursorrules/overview.md +163 -0
- package/templates/testing/.cursorrules/performance-testing.md +536 -0
- package/templates/testing/.cursorrules/quality-metrics.md +456 -0
- package/templates/testing/.cursorrules/reliability.md +557 -0
- package/templates/testing/.cursorrules/tdd-methodology.md +294 -0
- package/templates/testing/.cursorrules/test-data.md +565 -0
- package/templates/testing/.cursorrules/test-design.md +511 -0
- package/templates/testing/.cursorrules/test-types.md +398 -0
- package/templates/testing/CLAUDE.md +1134 -0
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
# Test Data Management
|
|
2
|
+
|
|
3
|
+
Guidelines for managing test data effectively.
|
|
4
|
+
|
|
5
|
+
## Core Principles
|
|
6
|
+
|
|
7
|
+
1. **Realistic Data** - Test data should resemble production data
|
|
8
|
+
2. **Isolated State** - Each test owns its data
|
|
9
|
+
3. **Minimal Setup** - Only create what the test needs
|
|
10
|
+
4. **Deterministic** - Same test always uses same logical data
|
|
11
|
+
5. **No Production Data** - Never use real user data
|
|
12
|
+
|
|
13
|
+
## Faker for Realistic Data
|
|
14
|
+
|
|
15
|
+
Use Faker to generate realistic test data:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
// factories/index.ts
|
|
19
|
+
import { faker } from '@faker-js/faker';
|
|
20
|
+
|
|
21
|
+
// Seed for deterministic tests (optional)
|
|
22
|
+
faker.seed(12345);
|
|
23
|
+
|
|
24
|
+
export const factories = {
|
|
25
|
+
user: (overrides: Partial<User> = {}): User => ({
|
|
26
|
+
id: faker.string.uuid(),
|
|
27
|
+
email: faker.internet.email(),
|
|
28
|
+
name: faker.person.fullName(),
|
|
29
|
+
phone: faker.phone.number(),
|
|
30
|
+
address: {
|
|
31
|
+
street: faker.location.streetAddress(),
|
|
32
|
+
city: faker.location.city(),
|
|
33
|
+
state: faker.location.state(),
|
|
34
|
+
zip: faker.location.zipCode(),
|
|
35
|
+
},
|
|
36
|
+
createdAt: faker.date.past(),
|
|
37
|
+
updatedAt: new Date(),
|
|
38
|
+
...overrides,
|
|
39
|
+
}),
|
|
40
|
+
|
|
41
|
+
product: (overrides: Partial<Product> = {}): Product => ({
|
|
42
|
+
id: faker.string.uuid(),
|
|
43
|
+
name: faker.commerce.productName(),
|
|
44
|
+
description: faker.commerce.productDescription(),
|
|
45
|
+
price: parseFloat(faker.commerce.price({ min: 10, max: 1000 })),
|
|
46
|
+
sku: faker.string.alphanumeric(10).toUpperCase(),
|
|
47
|
+
inventory: faker.number.int({ min: 0, max: 100 }),
|
|
48
|
+
category: faker.commerce.department(),
|
|
49
|
+
createdAt: faker.date.past(),
|
|
50
|
+
...overrides,
|
|
51
|
+
}),
|
|
52
|
+
|
|
53
|
+
order: (overrides: Partial<Order> = {}): Order => ({
|
|
54
|
+
id: faker.string.uuid(),
|
|
55
|
+
userId: faker.string.uuid(),
|
|
56
|
+
items: [],
|
|
57
|
+
total: 0,
|
|
58
|
+
status: 'pending',
|
|
59
|
+
shippingAddress: factories.address(),
|
|
60
|
+
createdAt: new Date(),
|
|
61
|
+
updatedAt: new Date(),
|
|
62
|
+
...overrides,
|
|
63
|
+
}),
|
|
64
|
+
|
|
65
|
+
address: (overrides: Partial<Address> = {}): Address => ({
|
|
66
|
+
street: faker.location.streetAddress(),
|
|
67
|
+
city: faker.location.city(),
|
|
68
|
+
state: faker.location.state(),
|
|
69
|
+
zip: faker.location.zipCode(),
|
|
70
|
+
country: 'US',
|
|
71
|
+
...overrides,
|
|
72
|
+
}),
|
|
73
|
+
};
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Database Factories
|
|
77
|
+
|
|
78
|
+
For integration tests that need real database records:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
// factories/db.ts
|
|
82
|
+
import { db } from '../lib/db';
|
|
83
|
+
import { factories } from './index';
|
|
84
|
+
|
|
85
|
+
export const dbFactories = {
|
|
86
|
+
user: async (overrides: Partial<User> = {}): Promise<User> => {
|
|
87
|
+
const userData = factories.user(overrides);
|
|
88
|
+
return db.user.create({ data: userData });
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
product: async (overrides: Partial<Product> = {}): Promise<Product> => {
|
|
92
|
+
const productData = factories.product(overrides);
|
|
93
|
+
return db.product.create({ data: productData });
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
order: async (
|
|
97
|
+
user: User,
|
|
98
|
+
items: Array<{ product: Product; quantity: number }>
|
|
99
|
+
): Promise<Order> => {
|
|
100
|
+
const total = items.reduce(
|
|
101
|
+
(sum, item) => sum + item.product.price * item.quantity,
|
|
102
|
+
0
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
return db.order.create({
|
|
106
|
+
data: {
|
|
107
|
+
userId: user.id,
|
|
108
|
+
total,
|
|
109
|
+
status: 'pending',
|
|
110
|
+
items: {
|
|
111
|
+
create: items.map((item) => ({
|
|
112
|
+
productId: item.product.id,
|
|
113
|
+
quantity: item.quantity,
|
|
114
|
+
price: item.product.price,
|
|
115
|
+
})),
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
include: { items: true },
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Builder Pattern for Complex Objects
|
|
125
|
+
|
|
126
|
+
When objects have many configurations:
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
// builders/OrderBuilder.ts
|
|
130
|
+
import { faker } from '@faker-js/faker';
|
|
131
|
+
|
|
132
|
+
export class OrderBuilder {
|
|
133
|
+
private data: Partial<Order> = {};
|
|
134
|
+
private items: OrderItem[] = [];
|
|
135
|
+
|
|
136
|
+
static create(): OrderBuilder {
|
|
137
|
+
return new OrderBuilder();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
withUser(user: User): this {
|
|
141
|
+
this.data.userId = user.id;
|
|
142
|
+
return this;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
withItem(product: Product, quantity: number = 1): this {
|
|
146
|
+
this.items.push({
|
|
147
|
+
id: faker.string.uuid(),
|
|
148
|
+
productId: product.id,
|
|
149
|
+
quantity,
|
|
150
|
+
price: product.price,
|
|
151
|
+
});
|
|
152
|
+
return this;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
withStatus(status: OrderStatus): this {
|
|
156
|
+
this.data.status = status;
|
|
157
|
+
return this;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
withDiscount(discount: Discount): this {
|
|
161
|
+
this.data.discount = discount;
|
|
162
|
+
return this;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
shipped(): this {
|
|
166
|
+
this.data.status = 'shipped';
|
|
167
|
+
this.data.shippedAt = new Date();
|
|
168
|
+
return this;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
cancelled(): this {
|
|
172
|
+
this.data.status = 'cancelled';
|
|
173
|
+
this.data.cancelledAt = new Date();
|
|
174
|
+
return this;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
build(): Order {
|
|
178
|
+
const total = this.items.reduce(
|
|
179
|
+
(sum, item) => sum + item.price * item.quantity,
|
|
180
|
+
0
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
id: faker.string.uuid(),
|
|
185
|
+
userId: this.data.userId ?? faker.string.uuid(),
|
|
186
|
+
items: this.items,
|
|
187
|
+
total: this.data.discount
|
|
188
|
+
? applyDiscount(total, this.data.discount)
|
|
189
|
+
: total,
|
|
190
|
+
status: this.data.status ?? 'pending',
|
|
191
|
+
discount: this.data.discount,
|
|
192
|
+
shippedAt: this.data.shippedAt,
|
|
193
|
+
cancelledAt: this.data.cancelledAt,
|
|
194
|
+
createdAt: new Date(),
|
|
195
|
+
updatedAt: new Date(),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async persist(db: Database): Promise<Order> {
|
|
200
|
+
const order = this.build();
|
|
201
|
+
return db.order.create({
|
|
202
|
+
data: order,
|
|
203
|
+
include: { items: true },
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Usage
|
|
209
|
+
const order = OrderBuilder.create()
|
|
210
|
+
.withUser(testUser)
|
|
211
|
+
.withItem(product1, 2)
|
|
212
|
+
.withItem(product2, 1)
|
|
213
|
+
.withDiscount({ type: 'percentage', value: 10 })
|
|
214
|
+
.shipped()
|
|
215
|
+
.build();
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Object Mother Pattern
|
|
219
|
+
|
|
220
|
+
Pre-configured scenarios for common test cases:
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
// mothers/UserMother.ts
|
|
224
|
+
import { dbFactories } from '../factories/db';
|
|
225
|
+
|
|
226
|
+
export const UserMother = {
|
|
227
|
+
// Simple users
|
|
228
|
+
admin: () => dbFactories.user({ role: 'admin', verified: true }),
|
|
229
|
+
regular: () => dbFactories.user({ role: 'user', verified: true }),
|
|
230
|
+
unverified: () => dbFactories.user({ verified: false }),
|
|
231
|
+
suspended: () => dbFactories.user({ status: 'suspended' }),
|
|
232
|
+
|
|
233
|
+
// Complex scenarios
|
|
234
|
+
async withOrders(count: number = 3): Promise<User> {
|
|
235
|
+
const user = await dbFactories.user();
|
|
236
|
+
const products = await Promise.all(
|
|
237
|
+
Array.from({ length: count }, () => dbFactories.product())
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
for (let i = 0; i < count; i++) {
|
|
241
|
+
await dbFactories.order(user, [{ product: products[i], quantity: 1 }]);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return user;
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
async withExpiredSubscription(): Promise<User> {
|
|
248
|
+
const user = await dbFactories.user();
|
|
249
|
+
await db.subscription.create({
|
|
250
|
+
data: {
|
|
251
|
+
userId: user.id,
|
|
252
|
+
plan: 'premium',
|
|
253
|
+
expiresAt: new Date(Date.now() - 86400000), // Yesterday
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
return user;
|
|
257
|
+
},
|
|
258
|
+
|
|
259
|
+
async withActiveSubscription(plan: Plan = 'premium'): Promise<User> {
|
|
260
|
+
const user = await dbFactories.user();
|
|
261
|
+
await db.subscription.create({
|
|
262
|
+
data: {
|
|
263
|
+
userId: user.id,
|
|
264
|
+
plan,
|
|
265
|
+
expiresAt: new Date(Date.now() + 30 * 86400000), // 30 days
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
return user;
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// Usage in tests
|
|
273
|
+
it('shows renewal prompt for expired subscription', async () => {
|
|
274
|
+
const user = await UserMother.withExpiredSubscription();
|
|
275
|
+
const result = await checkSubscriptionStatus(user.id);
|
|
276
|
+
expect(result.showRenewalPrompt).toBe(true);
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Fixtures for Static Data
|
|
281
|
+
|
|
282
|
+
Use fixtures for data that doesn't change:
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
// fixtures/countries.json
|
|
286
|
+
[
|
|
287
|
+
{ "code": "US", "name": "United States", "currency": "USD" },
|
|
288
|
+
{ "code": "GB", "name": "United Kingdom", "currency": "GBP" },
|
|
289
|
+
{ "code": "EU", "name": "European Union", "currency": "EUR" }
|
|
290
|
+
]
|
|
291
|
+
|
|
292
|
+
// fixtures/taxRates.json
|
|
293
|
+
{
|
|
294
|
+
"US": { "standard": 0.08, "reduced": 0.04, "exempt": 0 },
|
|
295
|
+
"GB": { "standard": 0.20, "reduced": 0.05, "exempt": 0 },
|
|
296
|
+
"EU": { "standard": 0.21, "reduced": 0.10, "exempt": 0 }
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Usage
|
|
300
|
+
import countries from '../fixtures/countries.json';
|
|
301
|
+
import taxRates from '../fixtures/taxRates.json';
|
|
302
|
+
|
|
303
|
+
it.each(countries)('calculates tax for $name', ({ code }) => {
|
|
304
|
+
const rate = taxRates[code].standard;
|
|
305
|
+
expect(calculateTax(100, code)).toBe(100 * (1 + rate));
|
|
306
|
+
});
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Database Cleanup Strategies
|
|
310
|
+
|
|
311
|
+
### Transaction Rollback (Fastest)
|
|
312
|
+
|
|
313
|
+
```ts
|
|
314
|
+
// test/setup.ts
|
|
315
|
+
import { db } from '../lib/db';
|
|
316
|
+
|
|
317
|
+
beforeEach(async () => {
|
|
318
|
+
// Start transaction
|
|
319
|
+
await db.$executeRaw`BEGIN`;
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
afterEach(async () => {
|
|
323
|
+
// Rollback - no cleanup needed
|
|
324
|
+
await db.$executeRaw`ROLLBACK`;
|
|
325
|
+
});
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### Truncate Tables
|
|
329
|
+
|
|
330
|
+
```ts
|
|
331
|
+
// test/setup.ts
|
|
332
|
+
export async function cleanDatabase() {
|
|
333
|
+
const tables = ['order_item', 'order', 'product', 'user'];
|
|
334
|
+
|
|
335
|
+
await db.$transaction(
|
|
336
|
+
tables.map((table) =>
|
|
337
|
+
db.$executeRawUnsafe(`TRUNCATE TABLE "${table}" CASCADE`)
|
|
338
|
+
)
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
beforeEach(async () => {
|
|
343
|
+
await cleanDatabase();
|
|
344
|
+
});
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Delete in Order (Safest)
|
|
348
|
+
|
|
349
|
+
```ts
|
|
350
|
+
// test/setup.ts
|
|
351
|
+
export async function cleanDatabase() {
|
|
352
|
+
// Delete in dependency order (children first)
|
|
353
|
+
await db.$transaction([
|
|
354
|
+
db.orderItem.deleteMany(),
|
|
355
|
+
db.order.deleteMany(),
|
|
356
|
+
db.product.deleteMany(),
|
|
357
|
+
db.user.deleteMany(),
|
|
358
|
+
]);
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## Seeding Test Database
|
|
363
|
+
|
|
364
|
+
```ts
|
|
365
|
+
// test/seed.ts
|
|
366
|
+
import { dbFactories } from './factories/db';
|
|
367
|
+
|
|
368
|
+
export async function seedTestDatabase() {
|
|
369
|
+
// Create base data
|
|
370
|
+
const admin = await dbFactories.user({
|
|
371
|
+
email: 'admin@test.com',
|
|
372
|
+
role: 'admin',
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
const regularUser = await dbFactories.user({
|
|
376
|
+
email: 'user@test.com',
|
|
377
|
+
role: 'user',
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
const products = await Promise.all([
|
|
381
|
+
dbFactories.product({ name: 'Widget A', price: 100 }),
|
|
382
|
+
dbFactories.product({ name: 'Widget B', price: 200 }),
|
|
383
|
+
dbFactories.product({ name: 'Widget C', price: 50 }),
|
|
384
|
+
]);
|
|
385
|
+
|
|
386
|
+
return { admin, regularUser, products };
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Usage in global setup
|
|
390
|
+
// test/globalSetup.ts
|
|
391
|
+
export default async function globalSetup() {
|
|
392
|
+
const testDb = await createTestDatabase();
|
|
393
|
+
await runMigrations(testDb);
|
|
394
|
+
await seedTestDatabase();
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
## Deterministic Random Data
|
|
399
|
+
|
|
400
|
+
When you need "random" data but reproducible tests:
|
|
401
|
+
|
|
402
|
+
```ts
|
|
403
|
+
// Seed Faker for determinism
|
|
404
|
+
import { faker } from '@faker-js/faker';
|
|
405
|
+
|
|
406
|
+
// Global seed for all tests
|
|
407
|
+
faker.seed(12345);
|
|
408
|
+
|
|
409
|
+
// Or per-test seeding based on test name
|
|
410
|
+
beforeEach((context) => {
|
|
411
|
+
const seed = hashCode(context.task.name);
|
|
412
|
+
faker.seed(seed);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
function hashCode(str: string): number {
|
|
416
|
+
let hash = 0;
|
|
417
|
+
for (let i = 0; i < str.length; i++) {
|
|
418
|
+
hash = (hash << 5) - hash + str.charCodeAt(i);
|
|
419
|
+
hash |= 0;
|
|
420
|
+
}
|
|
421
|
+
return Math.abs(hash);
|
|
422
|
+
}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
## Snapshot Testing for Data
|
|
426
|
+
|
|
427
|
+
When test data is complex and stability is important:
|
|
428
|
+
|
|
429
|
+
```ts
|
|
430
|
+
// Capture expected data shape
|
|
431
|
+
it('generates correct order data', () => {
|
|
432
|
+
const order = OrderBuilder.create()
|
|
433
|
+
.withItem(product, 2)
|
|
434
|
+
.build();
|
|
435
|
+
|
|
436
|
+
expect(order).toMatchSnapshot({
|
|
437
|
+
id: expect.any(String),
|
|
438
|
+
createdAt: expect.any(Date),
|
|
439
|
+
updatedAt: expect.any(Date),
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
## Parameterized Tests
|
|
445
|
+
|
|
446
|
+
Test multiple scenarios efficiently:
|
|
447
|
+
|
|
448
|
+
```ts
|
|
449
|
+
// Using test.each
|
|
450
|
+
describe('validateEmail', () => {
|
|
451
|
+
const validEmails = [
|
|
452
|
+
'test@example.com',
|
|
453
|
+
'user.name@domain.org',
|
|
454
|
+
'user+tag@example.co.uk',
|
|
455
|
+
];
|
|
456
|
+
|
|
457
|
+
const invalidEmails = [
|
|
458
|
+
'',
|
|
459
|
+
'invalid',
|
|
460
|
+
'@nodomain.com',
|
|
461
|
+
'spaces in@email.com',
|
|
462
|
+
'no@tld',
|
|
463
|
+
];
|
|
464
|
+
|
|
465
|
+
it.each(validEmails)('accepts valid email: %s', (email) => {
|
|
466
|
+
expect(validateEmail(email)).toBe(true);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
it.each(invalidEmails)('rejects invalid email: %s', (email) => {
|
|
470
|
+
expect(validateEmail(email)).toBe(false);
|
|
471
|
+
});
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
// With test case objects
|
|
475
|
+
describe('calculateShipping', () => {
|
|
476
|
+
const testCases = [
|
|
477
|
+
{ weight: 0, distance: 100, expected: 0 },
|
|
478
|
+
{ weight: 1, distance: 100, expected: 5 },
|
|
479
|
+
{ weight: 5, distance: 100, expected: 15 },
|
|
480
|
+
{ weight: 1, distance: 500, expected: 10 },
|
|
481
|
+
{ weight: 10, distance: 1000, expected: 50 },
|
|
482
|
+
];
|
|
483
|
+
|
|
484
|
+
it.each(testCases)(
|
|
485
|
+
'calculates $expected for weight=$weight, distance=$distance',
|
|
486
|
+
({ weight, distance, expected }) => {
|
|
487
|
+
expect(calculateShipping(weight, distance)).toBe(expected);
|
|
488
|
+
}
|
|
489
|
+
);
|
|
490
|
+
});
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
## Anti-Patterns
|
|
494
|
+
|
|
495
|
+
### Shared Mutable State
|
|
496
|
+
|
|
497
|
+
```ts
|
|
498
|
+
// Bad: Tests affect each other
|
|
499
|
+
let testUser = createUser();
|
|
500
|
+
|
|
501
|
+
it('test 1', () => {
|
|
502
|
+
testUser.name = 'Changed';
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it('test 2', () => {
|
|
506
|
+
expect(testUser.name).toBe('Original'); // Fails!
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
// Good: Fresh data per test
|
|
510
|
+
let testUser: User;
|
|
511
|
+
|
|
512
|
+
beforeEach(() => {
|
|
513
|
+
testUser = createUser();
|
|
514
|
+
});
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### Hard-Coded IDs
|
|
518
|
+
|
|
519
|
+
```ts
|
|
520
|
+
// Bad: Magic IDs
|
|
521
|
+
const order = await getOrder('550e8400-e29b-41d4-a716-446655440000');
|
|
522
|
+
|
|
523
|
+
// Good: Generated IDs
|
|
524
|
+
const created = await createOrder(data);
|
|
525
|
+
const order = await getOrder(created.id);
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### Production Data
|
|
529
|
+
|
|
530
|
+
```ts
|
|
531
|
+
// Bad: Using real user data
|
|
532
|
+
const user = { email: 'real.customer@example.com', ... };
|
|
533
|
+
|
|
534
|
+
// Good: Fake data
|
|
535
|
+
const user = factories.user();
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### Over-specified Data
|
|
539
|
+
|
|
540
|
+
```ts
|
|
541
|
+
// Bad: Too much irrelevant data
|
|
542
|
+
it('calculates discount', () => {
|
|
543
|
+
const order = {
|
|
544
|
+
id: '123',
|
|
545
|
+
userId: 'user-1',
|
|
546
|
+
items: [/* ... */],
|
|
547
|
+
createdAt: new Date(),
|
|
548
|
+
updatedAt: new Date(),
|
|
549
|
+
shippingAddress: { /* ... */ },
|
|
550
|
+
billingAddress: { /* ... */ },
|
|
551
|
+
notes: 'Please handle with care',
|
|
552
|
+
// ... 20 more fields
|
|
553
|
+
};
|
|
554
|
+
expect(calculateDiscount(order)).toBe(10);
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
// Good: Only relevant data
|
|
558
|
+
it('calculates discount', () => {
|
|
559
|
+
const order = factories.order({
|
|
560
|
+
total: 100,
|
|
561
|
+
discount: { type: 'percentage', value: 10 },
|
|
562
|
+
});
|
|
563
|
+
expect(calculateDiscount(order)).toBe(10);
|
|
564
|
+
});
|
|
565
|
+
```
|