metacoding 1.5.0 → 2.0.1
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/CHANGELOG.md +59 -0
- package/README.md +108 -514
- package/lib/cli.d.ts.map +1 -1
- package/lib/cli.js +18 -19
- package/lib/cli.js.map +1 -1
- package/lib/commands/init.d.ts +8 -14
- package/lib/commands/init.d.ts.map +1 -1
- package/lib/commands/init.js +105 -387
- package/lib/commands/init.js.map +1 -1
- package/lib/commands/update.d.ts +9 -9
- package/lib/commands/update.d.ts.map +1 -1
- package/lib/commands/update.js +141 -320
- package/lib/commands/update.js.map +1 -1
- package/lib/services/backup.d.ts +1 -1
- package/lib/services/backup.d.ts.map +1 -1
- package/lib/services/backup.js +10 -6
- package/lib/services/backup.js.map +1 -1
- package/lib/services/filesystem.d.ts.map +1 -1
- package/lib/services/filesystem.js +11 -5
- package/lib/services/filesystem.js.map +1 -1
- package/lib/services/gitignore-manager.js +5 -5
- package/lib/services/gitignore-manager.js.map +1 -1
- package/lib/services/project-detector.d.ts +9 -8
- package/lib/services/project-detector.d.ts.map +1 -1
- package/lib/services/project-detector.js +79 -197
- package/lib/services/project-detector.js.map +1 -1
- package/lib/services/skill-manager.d.ts +23 -0
- package/lib/services/skill-manager.d.ts.map +1 -0
- package/lib/services/skill-manager.js +212 -0
- package/lib/services/skill-manager.js.map +1 -0
- package/lib/types/index.d.ts +5 -15
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +9 -17
- package/skills/metacoding-workflow/SKILL.md +52 -0
- package/skills/metacoding-workflow/agents/openai.yaml +4 -0
- package/skills/metacoding-workflow/assets/templates/changelog-entry.md +6 -0
- package/skills/metacoding-workflow/assets/templates/project-context.md +18 -0
- package/skills/metacoding-workflow/assets/templates/repeated-task-checklist.md +8 -0
- package/skills/metacoding-workflow/assets/templates/task-entry.md +9 -0
- package/skills/metacoding-workflow/assets/templates/test-plan.md +8 -0
- package/skills/metacoding-workflow/references/javascript.md +7 -0
- package/skills/metacoding-workflow/references/node.md +7 -0
- package/skills/metacoding-workflow/references/platform-adaptation.md +37 -0
- package/skills/metacoding-workflow/references/python.md +7 -0
- package/skills/metacoding-workflow/references/react.md +7 -0
- package/skills/metacoding-workflow/references/repository-organization.md +84 -0
- package/skills/metacoding-workflow/references/typescript.md +7 -0
- package/skills/metacoding-workflow/references/workflow-rules.md +54 -0
- package/skills/vendor-templates/claude-agent.md.template +41 -0
- package/lib/services/assistant-adapter.d.ts +0 -18
- package/lib/services/assistant-adapter.d.ts.map +0 -1
- package/lib/services/assistant-adapter.js +0 -246
- package/lib/services/assistant-adapter.js.map +0 -1
- package/lib/services/cursor.d.ts +0 -47
- package/lib/services/cursor.d.ts.map +0 -1
- package/lib/services/cursor.js +0 -314
- package/lib/services/cursor.js.map +0 -1
- package/lib/services/template-manager.d.ts +0 -23
- package/lib/services/template-manager.d.ts.map +0 -1
- package/lib/services/template-manager.js +0 -374
- package/lib/services/template-manager.js.map +0 -1
- package/lib/services/vscode.d.ts +0 -10
- package/lib/services/vscode.d.ts.map +0 -1
- package/lib/services/vscode.js +0 -108
- package/lib/services/vscode.js.map +0 -1
- package/templates/assistants/AGENTS.md +0 -203
- package/templates/assistants/CLAUDE.md +0 -156
- package/templates/assistants/GEMINI.md +0 -193
- package/templates/general/code-review.instructions.md +0 -265
- package/templates/general/copilot-instructions.md +0 -427
- package/templates/general/docs-update.instructions.md +0 -275
- package/templates/general/release.instructions.md +0 -242
- package/templates/general/template.json +0 -9
- package/templates/general/test-runner.instructions.md +0 -188
- package/templates/javascript/javascript.coding.instructions.md +0 -500
- package/templates/javascript/javascript.docs.instructions.md +0 -563
- package/templates/javascript/javascript.testing.instructions.md +0 -686
- package/templates/javascript/template.json +0 -36
- package/templates/node/nodejs.coding.instructions.md +0 -249
- package/templates/node/nodejs.docs.instructions.md +0 -261
- package/templates/node/nodejs.testing.instructions.md +0 -373
- package/templates/node/template.json +0 -23
- package/templates/python/python.coding.instructions.md +0 -338
- package/templates/python/python.docs.instructions.md +0 -1178
- package/templates/python/python.testing.instructions.md +0 -1073
- package/templates/python/template.json +0 -75
- package/templates/react/react.coding.instructions.md +0 -694
- package/templates/react/react.docs.instructions.md +0 -451
- package/templates/react/react.testing.instructions.md +0 -192
- package/templates/react/template.json +0 -14
- package/templates/react/test-runner.instructions.md +0 -135
- package/templates/typescript/template.json +0 -16
- package/templates/typescript/typescript.coding.instructions.md +0 -368
- package/templates/typescript/typescript.docs.instructions.md +0 -760
- package/templates/typescript/typescript.testing.instructions.md +0 -739
|
@@ -1,739 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: 'TypeScript/Node.js-specific testing patterns and frameworks'
|
|
3
|
-
applyTo: 'test/**/*.{ts,js}'
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# TypeScript/Node.js Testing Standards
|
|
7
|
-
|
|
8
|
-
## Testing Framework and Setup
|
|
9
|
-
|
|
10
|
-
### Primary Testing Framework: Jest
|
|
11
|
-
|
|
12
|
-
```typescript
|
|
13
|
-
// jest.config.js
|
|
14
|
-
module.exports = {
|
|
15
|
-
preset: 'ts-jest',
|
|
16
|
-
testEnvironment: 'node',
|
|
17
|
-
roots: ['<rootDir>/src', '<rootDir>/test'],
|
|
18
|
-
testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
|
|
19
|
-
transform: {
|
|
20
|
-
'^.+\\.ts$': 'ts-jest',
|
|
21
|
-
},
|
|
22
|
-
collectCoverageFrom: ['src/**/*.ts', '!src/**/*.d.ts'],
|
|
23
|
-
coverageDirectory: 'coverage',
|
|
24
|
-
coverageReporters: ['text', 'lcov', 'html'],
|
|
25
|
-
};
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
### Test Case Naming Conventions
|
|
29
|
-
|
|
30
|
-
**Area Prefixes for Node.js/Backend:**
|
|
31
|
-
|
|
32
|
-
- `API` - API endpoint tests
|
|
33
|
-
- `SRV` - Service layer tests
|
|
34
|
-
- `DB` - Database/Data layer tests
|
|
35
|
-
- `MW` - Middleware tests
|
|
36
|
-
- `ROUTE` - Routing logic tests
|
|
37
|
-
- `AUTH` - Authentication/Authorization tests
|
|
38
|
-
- `UTIL` - Utility function tests
|
|
39
|
-
- `CONFIG` - Configuration management tests
|
|
40
|
-
- `CORE` - Core business logic tests
|
|
41
|
-
- `INT` - Integration tests
|
|
42
|
-
- `E2E` - End-to-end tests
|
|
43
|
-
|
|
44
|
-
**Examples:**
|
|
45
|
-
|
|
46
|
-
- `API-UNIT-001` - First unit test for API endpoints
|
|
47
|
-
- `SRV-UNIT-001` - First unit test for Service layer
|
|
48
|
-
- `DB-INT-001` - First integration test for Database
|
|
49
|
-
- `E2E-FLOW-001` - First end-to-end workflow test
|
|
50
|
-
|
|
51
|
-
## Unit Testing Patterns
|
|
52
|
-
|
|
53
|
-
### Service Layer Testing
|
|
54
|
-
|
|
55
|
-
```typescript
|
|
56
|
-
// test/unit/user-service.test.ts
|
|
57
|
-
import { UserService } from '../../src/services/user-service';
|
|
58
|
-
import { UserRepository } from '../../src/repositories/user-repository';
|
|
59
|
-
import { User } from '../../src/types/user';
|
|
60
|
-
|
|
61
|
-
// Mock the repository
|
|
62
|
-
jest.mock('../../src/repositories/user-repository');
|
|
63
|
-
const MockUserRepository = UserRepository as jest.MockedClass<
|
|
64
|
-
typeof UserRepository
|
|
65
|
-
>;
|
|
66
|
-
|
|
67
|
-
describe('UserService', () => {
|
|
68
|
-
let userService: UserService;
|
|
69
|
-
let mockUserRepository: jest.Mocked<UserRepository>;
|
|
70
|
-
|
|
71
|
-
beforeEach(() => {
|
|
72
|
-
mockUserRepository =
|
|
73
|
-
new MockUserRepository() as jest.Mocked<UserRepository>;
|
|
74
|
-
userService = new UserService(mockUserRepository);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
afterEach(() => {
|
|
78
|
-
jest.clearAllMocks();
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
describe('getUserById', () => {
|
|
82
|
-
it('should return user when user exists', async () => {
|
|
83
|
-
// Arrange
|
|
84
|
-
const userId = '123';
|
|
85
|
-
const expectedUser: User = {
|
|
86
|
-
id: userId,
|
|
87
|
-
name: 'John Doe',
|
|
88
|
-
email: 'john@example.com',
|
|
89
|
-
};
|
|
90
|
-
mockUserRepository.findById.mockResolvedValue(expectedUser);
|
|
91
|
-
|
|
92
|
-
// Act
|
|
93
|
-
const result = await userService.getUserById(userId);
|
|
94
|
-
|
|
95
|
-
// Assert
|
|
96
|
-
expect(result).toEqual(expectedUser);
|
|
97
|
-
expect(mockUserRepository.findById).toHaveBeenCalledWith(userId);
|
|
98
|
-
expect(mockUserRepository.findById).toHaveBeenCalledTimes(1);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('should throw error when user not found', async () => {
|
|
102
|
-
// Arrange
|
|
103
|
-
const userId = '999';
|
|
104
|
-
mockUserRepository.findById.mockResolvedValue(null);
|
|
105
|
-
|
|
106
|
-
// Act & Assert
|
|
107
|
-
await expect(userService.getUserById(userId)).rejects.toThrow(
|
|
108
|
-
'User not found'
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
expect(mockUserRepository.findById).toHaveBeenCalledWith(userId);
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
});
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### API Endpoint Testing
|
|
118
|
-
|
|
119
|
-
```typescript
|
|
120
|
-
// test/unit/user-controller.test.ts
|
|
121
|
-
import request from 'supertest';
|
|
122
|
-
import { app } from '../../src/app';
|
|
123
|
-
import { UserService } from '../../src/services/user-service';
|
|
124
|
-
|
|
125
|
-
// Mock the service
|
|
126
|
-
jest.mock('../../src/services/user-service');
|
|
127
|
-
const MockUserService = UserService as jest.MockedClass<typeof UserService>;
|
|
128
|
-
|
|
129
|
-
describe('User Controller', () => {
|
|
130
|
-
let mockUserService: jest.Mocked<UserService>;
|
|
131
|
-
|
|
132
|
-
beforeEach(() => {
|
|
133
|
-
mockUserService = new MockUserService() as jest.Mocked<UserService>;
|
|
134
|
-
// Inject mock into controller if using dependency injection
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
afterEach(() => {
|
|
138
|
-
jest.clearAllMocks();
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
describe('GET /api/users/:id', () => {
|
|
142
|
-
it('should return user when valid id provided', async () => {
|
|
143
|
-
// Arrange
|
|
144
|
-
const userId = '123';
|
|
145
|
-
const mockUser = {
|
|
146
|
-
id: userId,
|
|
147
|
-
name: 'John Doe',
|
|
148
|
-
email: 'john@example.com',
|
|
149
|
-
};
|
|
150
|
-
mockUserService.getUserById.mockResolvedValue(mockUser);
|
|
151
|
-
|
|
152
|
-
// Act
|
|
153
|
-
const response = await request(app)
|
|
154
|
-
.get(`/api/users/${userId}`)
|
|
155
|
-
.expect(200);
|
|
156
|
-
|
|
157
|
-
// Assert
|
|
158
|
-
expect(response.body).toEqual(mockUser);
|
|
159
|
-
expect(mockUserService.getUserById).toHaveBeenCalledWith(userId);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it('should return 404 when user not found', async () => {
|
|
163
|
-
// Arrange
|
|
164
|
-
const userId = '999';
|
|
165
|
-
mockUserService.getUserById.mockRejectedValue(
|
|
166
|
-
new Error('User not found')
|
|
167
|
-
);
|
|
168
|
-
|
|
169
|
-
// Act
|
|
170
|
-
const response = await request(app)
|
|
171
|
-
.get(`/api/users/${userId}`)
|
|
172
|
-
.expect(404);
|
|
173
|
-
|
|
174
|
-
// Assert
|
|
175
|
-
expect(response.body).toEqual({ error: 'User not found' });
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it('should return 400 when invalid id format', async () => {
|
|
179
|
-
// Arrange
|
|
180
|
-
const invalidId = 'invalid-id';
|
|
181
|
-
|
|
182
|
-
// Act
|
|
183
|
-
const response = await request(app)
|
|
184
|
-
.get(`/api/users/${invalidId}`)
|
|
185
|
-
.expect(400);
|
|
186
|
-
|
|
187
|
-
// Assert
|
|
188
|
-
expect(response.body).toEqual({ error: 'Invalid user ID format' });
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
### Utility Function Testing
|
|
195
|
-
|
|
196
|
-
```typescript
|
|
197
|
-
// test/unit/string-utils.test.ts
|
|
198
|
-
import { capitalize, slugify, truncate } from '../../src/utils/string-utils';
|
|
199
|
-
|
|
200
|
-
describe('String Utils', () => {
|
|
201
|
-
describe('capitalize', () => {
|
|
202
|
-
it('should capitalize first letter of string', () => {
|
|
203
|
-
expect(capitalize('hello')).toBe('Hello');
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
it('should handle empty string', () => {
|
|
207
|
-
expect(capitalize('')).toBe('');
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
it('should handle single character', () => {
|
|
211
|
-
expect(capitalize('a')).toBe('A');
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
it('should not change already capitalized string', () => {
|
|
215
|
-
expect(capitalize('Hello')).toBe('Hello');
|
|
216
|
-
});
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
describe('slugify', () => {
|
|
220
|
-
it('should convert spaces to hyphens', () => {
|
|
221
|
-
expect(slugify('Hello World')).toBe('hello-world');
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
it('should remove special characters', () => {
|
|
225
|
-
expect(slugify('Hello! @World#')).toBe('hello-world');
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
it('should handle multiple spaces', () => {
|
|
229
|
-
expect(slugify('Hello World')).toBe('hello-world');
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
describe('truncate', () => {
|
|
234
|
-
it('should truncate long strings', () => {
|
|
235
|
-
const longString = 'This is a very long string that should be truncated';
|
|
236
|
-
expect(truncate(longString, 20)).toBe('This is a very lo...');
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
it('should not truncate short strings', () => {
|
|
240
|
-
const shortString = 'Short';
|
|
241
|
-
expect(truncate(shortString, 20)).toBe('Short');
|
|
242
|
-
});
|
|
243
|
-
});
|
|
244
|
-
});
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
## Integration Testing
|
|
248
|
-
|
|
249
|
-
### Database Integration Testing
|
|
250
|
-
|
|
251
|
-
```typescript
|
|
252
|
-
// test/integration/user-repository.test.ts
|
|
253
|
-
import { UserRepository } from '../../src/repositories/user-repository';
|
|
254
|
-
import { DatabaseConnection } from '../../src/database/connection';
|
|
255
|
-
import { User } from '../../src/types/user';
|
|
256
|
-
|
|
257
|
-
describe('UserRepository Integration', () => {
|
|
258
|
-
let userRepository: UserRepository;
|
|
259
|
-
let db: DatabaseConnection;
|
|
260
|
-
|
|
261
|
-
beforeAll(async () => {
|
|
262
|
-
// Setup test database
|
|
263
|
-
db = new DatabaseConnection(process.env.TEST_DATABASE_URL);
|
|
264
|
-
await db.connect();
|
|
265
|
-
userRepository = new UserRepository(db);
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
afterAll(async () => {
|
|
269
|
-
await db.disconnect();
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
beforeEach(async () => {
|
|
273
|
-
// Clean database before each test
|
|
274
|
-
await db.query('DELETE FROM users');
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
describe('findById', () => {
|
|
278
|
-
it('should find existing user', async () => {
|
|
279
|
-
// Arrange
|
|
280
|
-
const userData: Omit<User, 'id'> = {
|
|
281
|
-
name: 'John Doe',
|
|
282
|
-
email: 'john@example.com',
|
|
283
|
-
};
|
|
284
|
-
const createdUser = await userRepository.create(userData);
|
|
285
|
-
|
|
286
|
-
// Act
|
|
287
|
-
const foundUser = await userRepository.findById(createdUser.id);
|
|
288
|
-
|
|
289
|
-
// Assert
|
|
290
|
-
expect(foundUser).toEqual(createdUser);
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
it('should return null for non-existent user', async () => {
|
|
294
|
-
// Act
|
|
295
|
-
const foundUser = await userRepository.findById('999');
|
|
296
|
-
|
|
297
|
-
// Assert
|
|
298
|
-
expect(foundUser).toBeNull();
|
|
299
|
-
});
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
describe('create', () => {
|
|
303
|
-
it('should create new user with generated id', async () => {
|
|
304
|
-
// Arrange
|
|
305
|
-
const userData: Omit<User, 'id'> = {
|
|
306
|
-
name: 'Jane Doe',
|
|
307
|
-
email: 'jane@example.com',
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
// Act
|
|
311
|
-
const createdUser = await userRepository.create(userData);
|
|
312
|
-
|
|
313
|
-
// Assert
|
|
314
|
-
expect(createdUser).toMatchObject(userData);
|
|
315
|
-
expect(createdUser.id).toBeDefined();
|
|
316
|
-
expect(typeof createdUser.id).toBe('string');
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
it('should enforce unique email constraint', async () => {
|
|
320
|
-
// Arrange
|
|
321
|
-
const userData: Omit<User, 'id'> = {
|
|
322
|
-
name: 'John Doe',
|
|
323
|
-
email: 'duplicate@example.com',
|
|
324
|
-
};
|
|
325
|
-
await userRepository.create(userData);
|
|
326
|
-
|
|
327
|
-
// Act & Assert
|
|
328
|
-
await expect(userRepository.create(userData)).rejects.toThrow(
|
|
329
|
-
'Email already exists'
|
|
330
|
-
);
|
|
331
|
-
});
|
|
332
|
-
});
|
|
333
|
-
});
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
### API Integration Testing
|
|
337
|
-
|
|
338
|
-
```typescript
|
|
339
|
-
// test/integration/user-api.test.ts
|
|
340
|
-
import request from 'supertest';
|
|
341
|
-
import { app } from '../../src/app';
|
|
342
|
-
import { DatabaseConnection } from '../../src/database/connection';
|
|
343
|
-
|
|
344
|
-
describe('User API Integration', () => {
|
|
345
|
-
let db: DatabaseConnection;
|
|
346
|
-
|
|
347
|
-
beforeAll(async () => {
|
|
348
|
-
db = new DatabaseConnection(process.env.TEST_DATABASE_URL);
|
|
349
|
-
await db.connect();
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
afterAll(async () => {
|
|
353
|
-
await db.disconnect();
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
beforeEach(async () => {
|
|
357
|
-
await db.query('DELETE FROM users');
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
describe('POST /api/users', () => {
|
|
361
|
-
it('should create new user and return 201', async () => {
|
|
362
|
-
// Arrange
|
|
363
|
-
const userData = {
|
|
364
|
-
name: 'John Doe',
|
|
365
|
-
email: 'john@example.com',
|
|
366
|
-
};
|
|
367
|
-
|
|
368
|
-
// Act
|
|
369
|
-
const response = await request(app)
|
|
370
|
-
.post('/api/users')
|
|
371
|
-
.send(userData)
|
|
372
|
-
.expect(201);
|
|
373
|
-
|
|
374
|
-
// Assert
|
|
375
|
-
expect(response.body).toMatchObject(userData);
|
|
376
|
-
expect(response.body.id).toBeDefined();
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
it('should validate required fields', async () => {
|
|
380
|
-
// Arrange
|
|
381
|
-
const invalidData = {
|
|
382
|
-
name: 'John Doe',
|
|
383
|
-
// Missing email
|
|
384
|
-
};
|
|
385
|
-
|
|
386
|
-
// Act
|
|
387
|
-
const response = await request(app)
|
|
388
|
-
.post('/api/users')
|
|
389
|
-
.send(invalidData)
|
|
390
|
-
.expect(400);
|
|
391
|
-
|
|
392
|
-
// Assert
|
|
393
|
-
expect(response.body.error).toContain('email is required');
|
|
394
|
-
});
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
describe('GET /api/users', () => {
|
|
398
|
-
it('should return list of users', async () => {
|
|
399
|
-
// Arrange
|
|
400
|
-
const users = [
|
|
401
|
-
{ name: 'John Doe', email: 'john@example.com' },
|
|
402
|
-
{ name: 'Jane Doe', email: 'jane@example.com' },
|
|
403
|
-
];
|
|
404
|
-
|
|
405
|
-
for (const user of users) {
|
|
406
|
-
await request(app).post('/api/users').send(user);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// Act
|
|
410
|
-
const response = await request(app).get('/api/users').expect(200);
|
|
411
|
-
|
|
412
|
-
// Assert
|
|
413
|
-
expect(response.body).toHaveLength(2);
|
|
414
|
-
expect(response.body[0]).toMatchObject(users[0]);
|
|
415
|
-
expect(response.body[1]).toMatchObject(users[1]);
|
|
416
|
-
});
|
|
417
|
-
});
|
|
418
|
-
});
|
|
419
|
-
```
|
|
420
|
-
|
|
421
|
-
## Mocking Strategies
|
|
422
|
-
|
|
423
|
-
### External Service Mocking
|
|
424
|
-
|
|
425
|
-
```typescript
|
|
426
|
-
// test/mocks/external-api.ts
|
|
427
|
-
import nock from 'nock';
|
|
428
|
-
|
|
429
|
-
export const mockExternalAPI = {
|
|
430
|
-
mockUserLookup: (userId: string, userData: any) => {
|
|
431
|
-
return nock('https://api.external-service.com')
|
|
432
|
-
.get(`/users/${userId}`)
|
|
433
|
-
.reply(200, userData);
|
|
434
|
-
},
|
|
435
|
-
|
|
436
|
-
mockUserLookupError: (userId: string, statusCode: number = 500) => {
|
|
437
|
-
return nock('https://api.external-service.com')
|
|
438
|
-
.get(`/users/${userId}`)
|
|
439
|
-
.reply(statusCode, { error: 'External service error' });
|
|
440
|
-
},
|
|
441
|
-
|
|
442
|
-
cleanup: () => {
|
|
443
|
-
nock.cleanAll();
|
|
444
|
-
},
|
|
445
|
-
};
|
|
446
|
-
|
|
447
|
-
// Usage in tests
|
|
448
|
-
describe('External Service Integration', () => {
|
|
449
|
-
afterEach(() => {
|
|
450
|
-
mockExternalAPI.cleanup();
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
it('should handle external API success', async () => {
|
|
454
|
-
// Arrange
|
|
455
|
-
const userId = '123';
|
|
456
|
-
const userData = { id: userId, name: 'John Doe' };
|
|
457
|
-
mockExternalAPI.mockUserLookup(userId, userData);
|
|
458
|
-
|
|
459
|
-
// Act & Assert
|
|
460
|
-
const result = await externalService.getUser(userId);
|
|
461
|
-
expect(result).toEqual(userData);
|
|
462
|
-
});
|
|
463
|
-
});
|
|
464
|
-
```
|
|
465
|
-
|
|
466
|
-
### File System Mocking
|
|
467
|
-
|
|
468
|
-
```typescript
|
|
469
|
-
// test/unit/file-service.test.ts
|
|
470
|
-
import fs from 'fs/promises';
|
|
471
|
-
import { FileService } from '../../src/services/file-service';
|
|
472
|
-
|
|
473
|
-
// Mock fs module
|
|
474
|
-
jest.mock('fs/promises');
|
|
475
|
-
const mockFs = fs as jest.Mocked<typeof fs>;
|
|
476
|
-
|
|
477
|
-
describe('FileService', () => {
|
|
478
|
-
let fileService: FileService;
|
|
479
|
-
|
|
480
|
-
beforeEach(() => {
|
|
481
|
-
fileService = new FileService();
|
|
482
|
-
});
|
|
483
|
-
|
|
484
|
-
afterEach(() => {
|
|
485
|
-
jest.clearAllMocks();
|
|
486
|
-
});
|
|
487
|
-
|
|
488
|
-
describe('readConfig', () => {
|
|
489
|
-
it('should read and parse JSON config file', async () => {
|
|
490
|
-
// Arrange
|
|
491
|
-
const configData = { host: 'localhost', port: 3000 };
|
|
492
|
-
const configJson = JSON.stringify(configData);
|
|
493
|
-
mockFs.readFile.mockResolvedValue(configJson);
|
|
494
|
-
|
|
495
|
-
// Act
|
|
496
|
-
const result = await fileService.readConfig('/path/to/config.json');
|
|
497
|
-
|
|
498
|
-
// Assert
|
|
499
|
-
expect(result).toEqual(configData);
|
|
500
|
-
expect(mockFs.readFile).toHaveBeenCalledWith(
|
|
501
|
-
'/path/to/config.json',
|
|
502
|
-
'utf8'
|
|
503
|
-
);
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
it('should handle file read errors', async () => {
|
|
507
|
-
// Arrange
|
|
508
|
-
mockFs.readFile.mockRejectedValue(new Error('File not found'));
|
|
509
|
-
|
|
510
|
-
// Act & Assert
|
|
511
|
-
await expect(
|
|
512
|
-
fileService.readConfig('/nonexistent/config.json')
|
|
513
|
-
).rejects.toThrow('File not found');
|
|
514
|
-
});
|
|
515
|
-
});
|
|
516
|
-
});
|
|
517
|
-
```
|
|
518
|
-
|
|
519
|
-
## Async Testing Patterns
|
|
520
|
-
|
|
521
|
-
### Promise Testing
|
|
522
|
-
|
|
523
|
-
```typescript
|
|
524
|
-
// test/unit/async-service.test.ts
|
|
525
|
-
import { AsyncService } from '../../src/services/async-service';
|
|
526
|
-
|
|
527
|
-
describe('AsyncService', () => {
|
|
528
|
-
let asyncService: AsyncService;
|
|
529
|
-
|
|
530
|
-
beforeEach(() => {
|
|
531
|
-
asyncService = new AsyncService();
|
|
532
|
-
});
|
|
533
|
-
|
|
534
|
-
describe('processData', () => {
|
|
535
|
-
it('should process data asynchronously', async () => {
|
|
536
|
-
// Arrange
|
|
537
|
-
const inputData = ['item1', 'item2', 'item3'];
|
|
538
|
-
|
|
539
|
-
// Act
|
|
540
|
-
const result = await asyncService.processData(inputData);
|
|
541
|
-
|
|
542
|
-
// Assert
|
|
543
|
-
expect(result).toHaveLength(3);
|
|
544
|
-
expect(result[0]).toContain('processed');
|
|
545
|
-
});
|
|
546
|
-
|
|
547
|
-
it('should handle processing errors', async () => {
|
|
548
|
-
// Arrange
|
|
549
|
-
const invalidData = [null, undefined];
|
|
550
|
-
|
|
551
|
-
// Act & Assert
|
|
552
|
-
await expect(asyncService.processData(invalidData)).rejects.toThrow(
|
|
553
|
-
'Invalid data provided'
|
|
554
|
-
);
|
|
555
|
-
});
|
|
556
|
-
});
|
|
557
|
-
|
|
558
|
-
describe('processWithTimeout', () => {
|
|
559
|
-
it('should complete within timeout', async () => {
|
|
560
|
-
// Arrange
|
|
561
|
-
const data = 'test data';
|
|
562
|
-
const timeout = 1000;
|
|
563
|
-
|
|
564
|
-
// Act
|
|
565
|
-
const startTime = Date.now();
|
|
566
|
-
const result = await asyncService.processWithTimeout(data, timeout);
|
|
567
|
-
const endTime = Date.now();
|
|
568
|
-
|
|
569
|
-
// Assert
|
|
570
|
-
expect(result).toBeDefined();
|
|
571
|
-
expect(endTime - startTime).toBeLessThan(timeout);
|
|
572
|
-
});
|
|
573
|
-
|
|
574
|
-
it('should timeout for long operations', async () => {
|
|
575
|
-
// Arrange
|
|
576
|
-
const data = 'long processing data';
|
|
577
|
-
const shortTimeout = 100;
|
|
578
|
-
|
|
579
|
-
// Act & Assert
|
|
580
|
-
await expect(
|
|
581
|
-
asyncService.processWithTimeout(data, shortTimeout)
|
|
582
|
-
).rejects.toThrow('Operation timed out');
|
|
583
|
-
});
|
|
584
|
-
});
|
|
585
|
-
});
|
|
586
|
-
```
|
|
587
|
-
|
|
588
|
-
### Event Emitter Testing
|
|
589
|
-
|
|
590
|
-
```typescript
|
|
591
|
-
// test/unit/event-service.test.ts
|
|
592
|
-
import { EventService } from '../../src/services/event-service';
|
|
593
|
-
|
|
594
|
-
describe('EventService', () => {
|
|
595
|
-
let eventService: EventService;
|
|
596
|
-
|
|
597
|
-
beforeEach(() => {
|
|
598
|
-
eventService = new EventService();
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
afterEach(() => {
|
|
602
|
-
eventService.removeAllListeners();
|
|
603
|
-
});
|
|
604
|
-
|
|
605
|
-
describe('user events', () => {
|
|
606
|
-
it('should emit user created event', (done) => {
|
|
607
|
-
// Arrange
|
|
608
|
-
const userData = { id: '123', name: 'John Doe' };
|
|
609
|
-
|
|
610
|
-
// Act
|
|
611
|
-
eventService.on('user:created', (data) => {
|
|
612
|
-
// Assert
|
|
613
|
-
expect(data).toEqual(userData);
|
|
614
|
-
done();
|
|
615
|
-
});
|
|
616
|
-
|
|
617
|
-
eventService.createUser(userData);
|
|
618
|
-
});
|
|
619
|
-
|
|
620
|
-
it('should handle multiple listeners', () => {
|
|
621
|
-
// Arrange
|
|
622
|
-
const userData = { id: '123', name: 'John Doe' };
|
|
623
|
-
const listener1 = jest.fn();
|
|
624
|
-
const listener2 = jest.fn();
|
|
625
|
-
|
|
626
|
-
eventService.on('user:created', listener1);
|
|
627
|
-
eventService.on('user:created', listener2);
|
|
628
|
-
|
|
629
|
-
// Act
|
|
630
|
-
eventService.createUser(userData);
|
|
631
|
-
|
|
632
|
-
// Assert
|
|
633
|
-
expect(listener1).toHaveBeenCalledWith(userData);
|
|
634
|
-
expect(listener2).toHaveBeenCalledWith(userData);
|
|
635
|
-
});
|
|
636
|
-
});
|
|
637
|
-
});
|
|
638
|
-
```
|
|
639
|
-
|
|
640
|
-
## Performance Testing
|
|
641
|
-
|
|
642
|
-
### Benchmark Testing
|
|
643
|
-
|
|
644
|
-
```typescript
|
|
645
|
-
// test/performance/string-utils.perf.test.ts
|
|
646
|
-
import { performance } from 'perf_hooks';
|
|
647
|
-
import { processLargeString } from '../../src/utils/string-utils';
|
|
648
|
-
|
|
649
|
-
describe('String Utils Performance', () => {
|
|
650
|
-
const PERFORMANCE_THRESHOLD = 100; // milliseconds
|
|
651
|
-
|
|
652
|
-
describe('processLargeString', () => {
|
|
653
|
-
it('should process large string within performance threshold', () => {
|
|
654
|
-
// Arrange
|
|
655
|
-
const largeString = 'x'.repeat(1000000); // 1MB string
|
|
656
|
-
|
|
657
|
-
// Act
|
|
658
|
-
const startTime = performance.now();
|
|
659
|
-
const result = processLargeString(largeString);
|
|
660
|
-
const endTime = performance.now();
|
|
661
|
-
|
|
662
|
-
// Assert
|
|
663
|
-
expect(result).toBeDefined();
|
|
664
|
-
expect(endTime - startTime).toBeLessThan(PERFORMANCE_THRESHOLD);
|
|
665
|
-
});
|
|
666
|
-
});
|
|
667
|
-
});
|
|
668
|
-
```
|
|
669
|
-
|
|
670
|
-
## Test Utilities and Helpers
|
|
671
|
-
|
|
672
|
-
### Test Data Factories
|
|
673
|
-
|
|
674
|
-
```typescript
|
|
675
|
-
// test/factories/user-factory.ts
|
|
676
|
-
import { User } from '../../src/types/user';
|
|
677
|
-
|
|
678
|
-
export class UserFactory {
|
|
679
|
-
static create(overrides: Partial<User> = {}): User {
|
|
680
|
-
return {
|
|
681
|
-
id: Math.random().toString(36).substr(2, 9),
|
|
682
|
-
name: 'Test User',
|
|
683
|
-
email: 'test@example.com',
|
|
684
|
-
createdAt: new Date(),
|
|
685
|
-
...overrides,
|
|
686
|
-
};
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
static createMany(count: number, overrides: Partial<User> = {}): User[] {
|
|
690
|
-
return Array.from({ length: count }, (_, index) =>
|
|
691
|
-
this.create({
|
|
692
|
-
...overrides,
|
|
693
|
-
email: `test${index}@example.com`,
|
|
694
|
-
})
|
|
695
|
-
);
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
// Usage in tests
|
|
700
|
-
describe('User Service', () => {
|
|
701
|
-
it('should process multiple users', () => {
|
|
702
|
-
const users = UserFactory.createMany(5);
|
|
703
|
-
expect(users).toHaveLength(5);
|
|
704
|
-
expect(users[0].email).toBe('test0@example.com');
|
|
705
|
-
});
|
|
706
|
-
});
|
|
707
|
-
```
|
|
708
|
-
|
|
709
|
-
### Custom Matchers
|
|
710
|
-
|
|
711
|
-
```typescript
|
|
712
|
-
// test/matchers/custom-matchers.ts
|
|
713
|
-
export const customMatchers = {
|
|
714
|
-
toBeValidUser(received: any) {
|
|
715
|
-
const pass =
|
|
716
|
-
typeof received === 'object' &&
|
|
717
|
-
typeof received.id === 'string' &&
|
|
718
|
-
typeof received.name === 'string' &&
|
|
719
|
-
typeof received.email === 'string' &&
|
|
720
|
-
received.email.includes('@');
|
|
721
|
-
|
|
722
|
-
return {
|
|
723
|
-
message: () => `Expected ${received} to be a valid user object`,
|
|
724
|
-
pass,
|
|
725
|
-
};
|
|
726
|
-
},
|
|
727
|
-
};
|
|
728
|
-
|
|
729
|
-
// Setup in test files
|
|
730
|
-
expect.extend(customMatchers);
|
|
731
|
-
|
|
732
|
-
// Usage
|
|
733
|
-
describe('User Service', () => {
|
|
734
|
-
it('should return valid user', () => {
|
|
735
|
-
const user = userService.getUser('123');
|
|
736
|
-
expect(user).toBeValidUser();
|
|
737
|
-
});
|
|
738
|
-
});
|
|
739
|
-
```
|