agentic-team-templates 0.3.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 +280 -0
- package/bin/cli.js +5 -0
- package/package.json +47 -0
- package/src/index.js +521 -0
- package/templates/_shared/code-quality.md +162 -0
- package/templates/_shared/communication.md +114 -0
- package/templates/_shared/core-principles.md +62 -0
- package/templates/_shared/git-workflow.md +165 -0
- package/templates/_shared/security-fundamentals.md +173 -0
- package/templates/blockchain/.cursorrules/defi-patterns.md +520 -0
- package/templates/blockchain/.cursorrules/gas-optimization.md +339 -0
- package/templates/blockchain/.cursorrules/overview.md +130 -0
- package/templates/blockchain/.cursorrules/security.md +318 -0
- package/templates/blockchain/.cursorrules/smart-contracts.md +364 -0
- package/templates/blockchain/.cursorrules/testing.md +415 -0
- package/templates/blockchain/.cursorrules/web3-integration.md +538 -0
- package/templates/blockchain/CLAUDE.md +389 -0
- package/templates/cli-tools/.cursorrules/architecture.md +412 -0
- package/templates/cli-tools/.cursorrules/arguments.md +406 -0
- package/templates/cli-tools/.cursorrules/distribution.md +546 -0
- package/templates/cli-tools/.cursorrules/error-handling.md +455 -0
- package/templates/cli-tools/.cursorrules/overview.md +136 -0
- package/templates/cli-tools/.cursorrules/testing.md +537 -0
- package/templates/cli-tools/.cursorrules/user-experience.md +545 -0
- package/templates/cli-tools/CLAUDE.md +356 -0
- package/templates/data-engineering/.cursorrules/data-modeling.md +367 -0
- package/templates/data-engineering/.cursorrules/data-quality.md +455 -0
- package/templates/data-engineering/.cursorrules/overview.md +85 -0
- package/templates/data-engineering/.cursorrules/performance.md +339 -0
- package/templates/data-engineering/.cursorrules/pipeline-design.md +280 -0
- package/templates/data-engineering/.cursorrules/security.md +460 -0
- package/templates/data-engineering/.cursorrules/testing.md +452 -0
- package/templates/data-engineering/CLAUDE.md +974 -0
- package/templates/devops-sre/.cursorrules/capacity-planning.md +653 -0
- package/templates/devops-sre/.cursorrules/change-management.md +584 -0
- package/templates/devops-sre/.cursorrules/chaos-engineering.md +651 -0
- package/templates/devops-sre/.cursorrules/disaster-recovery.md +641 -0
- package/templates/devops-sre/.cursorrules/incident-management.md +565 -0
- package/templates/devops-sre/.cursorrules/observability.md +714 -0
- package/templates/devops-sre/.cursorrules/overview.md +230 -0
- package/templates/devops-sre/.cursorrules/postmortems.md +588 -0
- package/templates/devops-sre/.cursorrules/runbooks.md +760 -0
- package/templates/devops-sre/.cursorrules/slo-sli.md +617 -0
- package/templates/devops-sre/.cursorrules/toil-reduction.md +567 -0
- package/templates/devops-sre/CLAUDE.md +1007 -0
- package/templates/documentation/.cursorrules/adr.md +277 -0
- package/templates/documentation/.cursorrules/api-documentation.md +411 -0
- package/templates/documentation/.cursorrules/code-comments.md +253 -0
- package/templates/documentation/.cursorrules/maintenance.md +260 -0
- package/templates/documentation/.cursorrules/overview.md +82 -0
- package/templates/documentation/.cursorrules/readme-standards.md +306 -0
- package/templates/documentation/CLAUDE.md +120 -0
- package/templates/fullstack/.cursorrules/api-contracts.md +331 -0
- package/templates/fullstack/.cursorrules/architecture.md +298 -0
- package/templates/fullstack/.cursorrules/overview.md +109 -0
- package/templates/fullstack/.cursorrules/shared-types.md +348 -0
- package/templates/fullstack/.cursorrules/testing.md +386 -0
- package/templates/fullstack/CLAUDE.md +349 -0
- package/templates/ml-ai/.cursorrules/data-engineering.md +483 -0
- package/templates/ml-ai/.cursorrules/deployment.md +601 -0
- package/templates/ml-ai/.cursorrules/model-development.md +538 -0
- package/templates/ml-ai/.cursorrules/monitoring.md +658 -0
- package/templates/ml-ai/.cursorrules/overview.md +131 -0
- package/templates/ml-ai/.cursorrules/security.md +637 -0
- package/templates/ml-ai/.cursorrules/testing.md +678 -0
- package/templates/ml-ai/CLAUDE.md +1136 -0
- package/templates/mobile/.cursorrules/navigation.md +246 -0
- package/templates/mobile/.cursorrules/offline-first.md +302 -0
- package/templates/mobile/.cursorrules/overview.md +71 -0
- package/templates/mobile/.cursorrules/performance.md +345 -0
- package/templates/mobile/.cursorrules/testing.md +339 -0
- package/templates/mobile/CLAUDE.md +233 -0
- package/templates/platform-engineering/.cursorrules/ci-cd.md +778 -0
- package/templates/platform-engineering/.cursorrules/developer-experience.md +632 -0
- package/templates/platform-engineering/.cursorrules/infrastructure-as-code.md +600 -0
- package/templates/platform-engineering/.cursorrules/kubernetes.md +710 -0
- package/templates/platform-engineering/.cursorrules/observability.md +747 -0
- package/templates/platform-engineering/.cursorrules/overview.md +215 -0
- package/templates/platform-engineering/.cursorrules/security.md +855 -0
- package/templates/platform-engineering/.cursorrules/testing.md +878 -0
- package/templates/platform-engineering/CLAUDE.md +850 -0
- package/templates/utility-agent/.cursorrules/action-control.md +284 -0
- package/templates/utility-agent/.cursorrules/context-management.md +186 -0
- package/templates/utility-agent/.cursorrules/hallucination-prevention.md +253 -0
- package/templates/utility-agent/.cursorrules/overview.md +78 -0
- package/templates/utility-agent/.cursorrules/token-optimization.md +369 -0
- package/templates/utility-agent/CLAUDE.md +513 -0
- package/templates/web-backend/.cursorrules/api-design.md +255 -0
- package/templates/web-backend/.cursorrules/authentication.md +309 -0
- package/templates/web-backend/.cursorrules/database-patterns.md +298 -0
- package/templates/web-backend/.cursorrules/error-handling.md +366 -0
- package/templates/web-backend/.cursorrules/overview.md +69 -0
- package/templates/web-backend/.cursorrules/security.md +358 -0
- package/templates/web-backend/.cursorrules/testing.md +395 -0
- package/templates/web-backend/CLAUDE.md +366 -0
- package/templates/web-frontend/.cursorrules/accessibility.md +296 -0
- package/templates/web-frontend/.cursorrules/component-patterns.md +204 -0
- package/templates/web-frontend/.cursorrules/overview.md +72 -0
- package/templates/web-frontend/.cursorrules/performance.md +325 -0
- package/templates/web-frontend/.cursorrules/state-management.md +227 -0
- package/templates/web-frontend/.cursorrules/styling.md +271 -0
- package/templates/web-frontend/.cursorrules/testing.md +311 -0
- package/templates/web-frontend/CLAUDE.md +399 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
# Backend Testing
|
|
2
|
+
|
|
3
|
+
Guidelines for testing backend applications effectively.
|
|
4
|
+
|
|
5
|
+
## Testing Pyramid
|
|
6
|
+
|
|
7
|
+
1. **Unit Tests** - Fast, isolated, test business logic
|
|
8
|
+
2. **Integration Tests** - Test components together, database interactions
|
|
9
|
+
3. **E2E/API Tests** - Test full request/response cycle
|
|
10
|
+
|
|
11
|
+
## Unit Tests
|
|
12
|
+
|
|
13
|
+
For pure business logic and utilities.
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
// services/pricing.test.ts
|
|
17
|
+
import { describe, it, expect } from 'vitest';
|
|
18
|
+
import { calculateDiscount, applyTax } from './pricing';
|
|
19
|
+
|
|
20
|
+
describe('calculateDiscount', () => {
|
|
21
|
+
it('applies percentage discount', () => {
|
|
22
|
+
expect(calculateDiscount(100, { type: 'percentage', value: 10 })).toBe(90);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('applies fixed discount', () => {
|
|
26
|
+
expect(calculateDiscount(100, { type: 'fixed', value: 15 })).toBe(85);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('does not go below zero', () => {
|
|
30
|
+
expect(calculateDiscount(10, { type: 'fixed', value: 20 })).toBe(0);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('handles zero price', () => {
|
|
34
|
+
expect(calculateDiscount(0, { type: 'percentage', value: 50 })).toBe(0);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('applyTax', () => {
|
|
39
|
+
it('calculates tax correctly', () => {
|
|
40
|
+
expect(applyTax(100, 0.08)).toBe(108);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('rounds to two decimal places', () => {
|
|
44
|
+
expect(applyTax(99.99, 0.0825)).toBe(108.24);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Integration Tests
|
|
50
|
+
|
|
51
|
+
Test with real database connections.
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
// repositories/userRepository.test.ts
|
|
55
|
+
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
|
|
56
|
+
import { db } from '../lib/db';
|
|
57
|
+
import { userRepository } from './userRepository';
|
|
58
|
+
|
|
59
|
+
describe('userRepository', () => {
|
|
60
|
+
beforeAll(async () => {
|
|
61
|
+
await db.$connect();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
afterAll(async () => {
|
|
65
|
+
await db.$disconnect();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
beforeEach(async () => {
|
|
69
|
+
// Clean up before each test
|
|
70
|
+
await db.user.deleteMany();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('create', () => {
|
|
74
|
+
it('creates a user with valid data', async () => {
|
|
75
|
+
const user = await userRepository.create({
|
|
76
|
+
email: 'test@example.com',
|
|
77
|
+
name: 'Test User',
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
expect(user.id).toBeDefined();
|
|
81
|
+
expect(user.email).toBe('test@example.com');
|
|
82
|
+
expect(user.name).toBe('Test User');
|
|
83
|
+
expect(user.createdAt).toBeInstanceOf(Date);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('throws on duplicate email', async () => {
|
|
87
|
+
await userRepository.create({ email: 'test@example.com', name: 'User 1' });
|
|
88
|
+
|
|
89
|
+
await expect(
|
|
90
|
+
userRepository.create({ email: 'test@example.com', name: 'User 2' })
|
|
91
|
+
).rejects.toThrow();
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('findById', () => {
|
|
96
|
+
it('returns user when found', async () => {
|
|
97
|
+
const created = await userRepository.create({
|
|
98
|
+
email: 'test@example.com',
|
|
99
|
+
name: 'Test User',
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const found = await userRepository.findById(created.id);
|
|
103
|
+
|
|
104
|
+
expect(found).toEqual(created);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('returns null when not found', async () => {
|
|
108
|
+
const found = await userRepository.findById('nonexistent-id');
|
|
109
|
+
|
|
110
|
+
expect(found).toBeNull();
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## API Tests
|
|
117
|
+
|
|
118
|
+
Test the full HTTP request/response cycle.
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
// routes/users.test.ts
|
|
122
|
+
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
|
|
123
|
+
import request from 'supertest';
|
|
124
|
+
import { app } from '../app';
|
|
125
|
+
import { db } from '../lib/db';
|
|
126
|
+
import { generateToken } from '../lib/auth';
|
|
127
|
+
|
|
128
|
+
describe('Users API', () => {
|
|
129
|
+
let authToken: string;
|
|
130
|
+
|
|
131
|
+
beforeAll(async () => {
|
|
132
|
+
await db.$connect();
|
|
133
|
+
// Create test user and get token
|
|
134
|
+
const user = await db.user.create({
|
|
135
|
+
data: { email: 'admin@test.com', name: 'Admin', role: 'admin' },
|
|
136
|
+
});
|
|
137
|
+
authToken = generateToken(user);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
afterAll(async () => {
|
|
141
|
+
await db.$disconnect();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
beforeEach(async () => {
|
|
145
|
+
await db.user.deleteMany({ where: { email: { not: 'admin@test.com' } } });
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe('GET /users', () => {
|
|
149
|
+
it('returns list of users', async () => {
|
|
150
|
+
await db.user.create({ data: { email: 'user1@test.com', name: 'User 1' } });
|
|
151
|
+
await db.user.create({ data: { email: 'user2@test.com', name: 'User 2' } });
|
|
152
|
+
|
|
153
|
+
const response = await request(app)
|
|
154
|
+
.get('/users')
|
|
155
|
+
.set('Authorization', `Bearer ${authToken}`);
|
|
156
|
+
|
|
157
|
+
expect(response.status).toBe(200);
|
|
158
|
+
expect(response.body.data).toHaveLength(3); // 2 + admin
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('requires authentication', async () => {
|
|
162
|
+
const response = await request(app).get('/users');
|
|
163
|
+
|
|
164
|
+
expect(response.status).toBe(401);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe('POST /users', () => {
|
|
169
|
+
it('creates a new user', async () => {
|
|
170
|
+
const response = await request(app)
|
|
171
|
+
.post('/users')
|
|
172
|
+
.set('Authorization', `Bearer ${authToken}`)
|
|
173
|
+
.send({ email: 'new@test.com', name: 'New User' });
|
|
174
|
+
|
|
175
|
+
expect(response.status).toBe(201);
|
|
176
|
+
expect(response.body.data.email).toBe('new@test.com');
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('validates email format', async () => {
|
|
180
|
+
const response = await request(app)
|
|
181
|
+
.post('/users')
|
|
182
|
+
.set('Authorization', `Bearer ${authToken}`)
|
|
183
|
+
.send({ email: 'invalid-email', name: 'Test' });
|
|
184
|
+
|
|
185
|
+
expect(response.status).toBe(422);
|
|
186
|
+
expect(response.body.error.code).toBe('VALIDATION_ERROR');
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('rejects duplicate email', async () => {
|
|
190
|
+
await db.user.create({ data: { email: 'existing@test.com', name: 'Existing' } });
|
|
191
|
+
|
|
192
|
+
const response = await request(app)
|
|
193
|
+
.post('/users')
|
|
194
|
+
.set('Authorization', `Bearer ${authToken}`)
|
|
195
|
+
.send({ email: 'existing@test.com', name: 'Test' });
|
|
196
|
+
|
|
197
|
+
expect(response.status).toBe(409);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe('PUT /users/:id', () => {
|
|
202
|
+
it('updates user', async () => {
|
|
203
|
+
const user = await db.user.create({
|
|
204
|
+
data: { email: 'update@test.com', name: 'Original' },
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const response = await request(app)
|
|
208
|
+
.put(`/users/${user.id}`)
|
|
209
|
+
.set('Authorization', `Bearer ${authToken}`)
|
|
210
|
+
.send({ name: 'Updated' });
|
|
211
|
+
|
|
212
|
+
expect(response.status).toBe(200);
|
|
213
|
+
expect(response.body.data.name).toBe('Updated');
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('returns 404 for nonexistent user', async () => {
|
|
217
|
+
const response = await request(app)
|
|
218
|
+
.put('/users/nonexistent-id')
|
|
219
|
+
.set('Authorization', `Bearer ${authToken}`)
|
|
220
|
+
.send({ name: 'Test' });
|
|
221
|
+
|
|
222
|
+
expect(response.status).toBe(404);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Mocking
|
|
229
|
+
|
|
230
|
+
### Mock External Services
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
import { vi } from 'vitest';
|
|
234
|
+
import { sendEmail } from '../lib/email';
|
|
235
|
+
import { createUser } from './userService';
|
|
236
|
+
|
|
237
|
+
vi.mock('../lib/email', () => ({
|
|
238
|
+
sendEmail: vi.fn().mockResolvedValue({ success: true }),
|
|
239
|
+
}));
|
|
240
|
+
|
|
241
|
+
describe('createUser', () => {
|
|
242
|
+
it('sends welcome email after creation', async () => {
|
|
243
|
+
await createUser({ email: 'test@example.com', name: 'Test' });
|
|
244
|
+
|
|
245
|
+
expect(sendEmail).toHaveBeenCalledWith({
|
|
246
|
+
to: 'test@example.com',
|
|
247
|
+
template: 'welcome',
|
|
248
|
+
data: expect.objectContaining({ name: 'Test' }),
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Mock Database
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
// For unit testing services without database
|
|
258
|
+
import { vi } from 'vitest';
|
|
259
|
+
import { userRepository } from '../repositories/userRepository';
|
|
260
|
+
import { userService } from './userService';
|
|
261
|
+
|
|
262
|
+
vi.mock('../repositories/userRepository');
|
|
263
|
+
|
|
264
|
+
describe('userService', () => {
|
|
265
|
+
it('returns user by email', async () => {
|
|
266
|
+
const mockUser = { id: '1', email: 'test@example.com', name: 'Test' };
|
|
267
|
+
vi.mocked(userRepository.findByEmail).mockResolvedValue(mockUser);
|
|
268
|
+
|
|
269
|
+
const result = await userService.findByEmail('test@example.com');
|
|
270
|
+
|
|
271
|
+
expect(result).toEqual(mockUser);
|
|
272
|
+
expect(userRepository.findByEmail).toHaveBeenCalledWith('test@example.com');
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Test Utilities
|
|
278
|
+
|
|
279
|
+
### Factories
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
// test/factories/user.ts
|
|
283
|
+
import { faker } from '@faker-js/faker';
|
|
284
|
+
import { db } from '../../lib/db';
|
|
285
|
+
|
|
286
|
+
export const createTestUser = async (overrides = {}) => {
|
|
287
|
+
return db.user.create({
|
|
288
|
+
data: {
|
|
289
|
+
email: faker.internet.email(),
|
|
290
|
+
name: faker.person.fullName(),
|
|
291
|
+
role: 'user',
|
|
292
|
+
...overrides,
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
// Usage in tests
|
|
298
|
+
const user = await createTestUser({ role: 'admin' });
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Test Database Setup
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
// test/setup.ts
|
|
305
|
+
import { db } from '../lib/db';
|
|
306
|
+
|
|
307
|
+
beforeAll(async () => {
|
|
308
|
+
// Use test database
|
|
309
|
+
process.env.DATABASE_URL = process.env.TEST_DATABASE_URL;
|
|
310
|
+
await db.$connect();
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
afterAll(async () => {
|
|
314
|
+
await db.$disconnect();
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
beforeEach(async () => {
|
|
318
|
+
// Clean all tables
|
|
319
|
+
const tables = await db.$queryRaw`
|
|
320
|
+
SELECT tablename FROM pg_tables WHERE schemaname = 'public'
|
|
321
|
+
`;
|
|
322
|
+
for (const { tablename } of tables) {
|
|
323
|
+
await db.$executeRawUnsafe(`TRUNCATE TABLE "${tablename}" CASCADE`);
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Best Practices
|
|
329
|
+
|
|
330
|
+
### Test Behavior, Not Implementation
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
// Bad: Testing implementation
|
|
334
|
+
it('calls repository.save', async () => {
|
|
335
|
+
await userService.create(userData);
|
|
336
|
+
expect(userRepository.save).toHaveBeenCalled();
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// Good: Testing behavior
|
|
340
|
+
it('creates user with correct data', async () => {
|
|
341
|
+
const user = await userService.create(userData);
|
|
342
|
+
|
|
343
|
+
const saved = await db.user.findUnique({ where: { id: user.id } });
|
|
344
|
+
expect(saved.email).toBe(userData.email);
|
|
345
|
+
});
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### One Assertion Per Concept
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
// Good: Focused tests
|
|
352
|
+
it('returns 401 when not authenticated', async () => {
|
|
353
|
+
const response = await request(app).get('/protected');
|
|
354
|
+
expect(response.status).toBe(401);
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('returns 403 when not authorized', async () => {
|
|
358
|
+
const response = await request(app)
|
|
359
|
+
.get('/admin')
|
|
360
|
+
.set('Authorization', `Bearer ${userToken}`);
|
|
361
|
+
expect(response.status).toBe(403);
|
|
362
|
+
});
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Use Descriptive Test Names
|
|
366
|
+
|
|
367
|
+
```ts
|
|
368
|
+
// Good: Clear what's being tested
|
|
369
|
+
describe('POST /orders', () => {
|
|
370
|
+
it('creates order when cart is valid', () => {});
|
|
371
|
+
it('returns 400 when cart is empty', () => {});
|
|
372
|
+
it('returns 409 when item is out of stock', () => {});
|
|
373
|
+
it('decrements inventory after successful order', () => {});
|
|
374
|
+
});
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
## Test Organization
|
|
378
|
+
|
|
379
|
+
```
|
|
380
|
+
src/
|
|
381
|
+
├── services/
|
|
382
|
+
│ ├── userService.ts
|
|
383
|
+
│ └── userService.test.ts # Co-located unit tests
|
|
384
|
+
├── repositories/
|
|
385
|
+
│ ├── userRepository.ts
|
|
386
|
+
│ └── userRepository.test.ts # Integration tests
|
|
387
|
+
└── routes/
|
|
388
|
+
├── users.ts
|
|
389
|
+
└── users.test.ts # API tests
|
|
390
|
+
|
|
391
|
+
test/
|
|
392
|
+
├── setup.ts # Global test setup
|
|
393
|
+
├── factories/ # Test data factories
|
|
394
|
+
└── fixtures/ # Static test data
|
|
395
|
+
```
|