@bradtaylorsf/alpha-loop 1.1.2 → 1.1.3

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.
@@ -1,294 +0,0 @@
1
- /**
2
- * Express Router Template
3
- * Use this template for creating new resource routers
4
- */
5
-
6
- import { Router } from 'express';
7
- import { z } from 'zod';
8
- import { validateRequest } from '../middleware/validation';
9
- import { requireAuth } from '../middleware/auth';
10
- import { AppError } from '../utils/errors';
11
-
12
- const router = Router();
13
-
14
- // ============================================================================
15
- // Validation Schemas
16
- // ============================================================================
17
-
18
- const CreateSchema = z.object({
19
- // Define your fields here
20
- name: z.string().min(1).max(255),
21
- description: z.string().optional()
22
- });
23
-
24
- const UpdateSchema = CreateSchema.partial();
25
-
26
- const QuerySchema = z.object({
27
- page: z.coerce.number().int().min(1).default(1),
28
- limit: z.coerce.number().int().min(1).max(100).default(20),
29
- sortBy: z.enum(['createdAt', 'updatedAt', 'name']).default('createdAt'),
30
- sortOrder: z.enum(['asc', 'desc']).default('desc')
31
- });
32
-
33
- // Type inference
34
- type CreateDto = z.infer<typeof CreateSchema>;
35
- type UpdateDto = z.infer<typeof UpdateSchema>;
36
- type QueryDto = z.infer<typeof QuerySchema>;
37
-
38
- // ============================================================================
39
- // Routes
40
- // ============================================================================
41
-
42
- /**
43
- * @openapi
44
- * /api/resources:
45
- * get:
46
- * summary: List all resources
47
- * tags: [Resources]
48
- * security:
49
- * - bearerAuth: []
50
- * parameters:
51
- * - in: query
52
- * name: page
53
- * schema:
54
- * type: integer
55
- * minimum: 1
56
- * - in: query
57
- * name: limit
58
- * schema:
59
- * type: integer
60
- * minimum: 1
61
- * maximum: 100
62
- * responses:
63
- * 200:
64
- * description: List of resources
65
- */
66
- router.get(
67
- '/',
68
- requireAuth,
69
- validateRequest(QuerySchema),
70
- async (req, res, next) => {
71
- try {
72
- const query = req.body as QueryDto;
73
- const { page, limit, sortBy, sortOrder } = query;
74
- const offset = (page - 1) * limit;
75
-
76
- // TODO: Replace with actual database query
77
- const resources = [];
78
- const total = 0;
79
-
80
- res.json({
81
- data: resources,
82
- pagination: {
83
- page,
84
- limit,
85
- total,
86
- totalPages: Math.ceil(total / limit)
87
- }
88
- });
89
- } catch (err) {
90
- next(err);
91
- }
92
- }
93
- );
94
-
95
- /**
96
- * @openapi
97
- * /api/resources/{id}:
98
- * get:
99
- * summary: Get a single resource
100
- * tags: [Resources]
101
- * security:
102
- * - bearerAuth: []
103
- * parameters:
104
- * - in: path
105
- * name: id
106
- * required: true
107
- * schema:
108
- * type: string
109
- * responses:
110
- * 200:
111
- * description: Resource found
112
- * 404:
113
- * description: Resource not found
114
- */
115
- router.get('/:id', requireAuth, async (req, res, next) => {
116
- try {
117
- const { id } = req.params;
118
-
119
- // TODO: Replace with actual database query
120
- const resource = null;
121
-
122
- if (!resource) {
123
- throw new AppError('Resource not found', 404, 'RESOURCE_NOT_FOUND');
124
- }
125
-
126
- res.json({ data: resource });
127
- } catch (err) {
128
- next(err);
129
- }
130
- });
131
-
132
- /**
133
- * @openapi
134
- * /api/resources:
135
- * post:
136
- * summary: Create a new resource
137
- * tags: [Resources]
138
- * security:
139
- * - bearerAuth: []
140
- * requestBody:
141
- * required: true
142
- * content:
143
- * application/json:
144
- * schema:
145
- * type: object
146
- * required:
147
- * - name
148
- * properties:
149
- * name:
150
- * type: string
151
- * description:
152
- * type: string
153
- * responses:
154
- * 201:
155
- * description: Resource created
156
- */
157
- router.post(
158
- '/',
159
- requireAuth,
160
- validateRequest(CreateSchema),
161
- async (req, res, next) => {
162
- try {
163
- const data = req.body as CreateDto;
164
- const userId = (req as any).userId;
165
-
166
- // TODO: Replace with actual database insert
167
- const resource = {
168
- id: 'generated-id',
169
- ...data,
170
- userId,
171
- createdAt: new Date().toISOString(),
172
- updatedAt: new Date().toISOString()
173
- };
174
-
175
- res.status(201).json({ data: resource });
176
- } catch (err) {
177
- next(err);
178
- }
179
- }
180
- );
181
-
182
- /**
183
- * @openapi
184
- * /api/resources/{id}:
185
- * put:
186
- * summary: Update a resource
187
- * tags: [Resources]
188
- * security:
189
- * - bearerAuth: []
190
- * parameters:
191
- * - in: path
192
- * name: id
193
- * required: true
194
- * schema:
195
- * type: string
196
- * requestBody:
197
- * required: true
198
- * content:
199
- * application/json:
200
- * schema:
201
- * type: object
202
- * properties:
203
- * name:
204
- * type: string
205
- * description:
206
- * type: string
207
- * responses:
208
- * 200:
209
- * description: Resource updated
210
- * 404:
211
- * description: Resource not found
212
- */
213
- router.put(
214
- '/:id',
215
- requireAuth,
216
- validateRequest(UpdateSchema),
217
- async (req, res, next) => {
218
- try {
219
- const { id } = req.params;
220
- const data = req.body as UpdateDto;
221
- const userId = (req as any).userId;
222
-
223
- // TODO: Replace with actual database query
224
- const resource = null;
225
-
226
- if (!resource) {
227
- throw new AppError('Resource not found', 404, 'RESOURCE_NOT_FOUND');
228
- }
229
-
230
- // Check authorization
231
- if ((resource as any).userId !== userId) {
232
- throw new AppError('Forbidden', 403, 'FORBIDDEN');
233
- }
234
-
235
- // TODO: Replace with actual database update
236
- const updated = {
237
- ...resource,
238
- ...data,
239
- updatedAt: new Date().toISOString()
240
- };
241
-
242
- res.json({ data: updated });
243
- } catch (err) {
244
- next(err);
245
- }
246
- }
247
- );
248
-
249
- /**
250
- * @openapi
251
- * /api/resources/{id}:
252
- * delete:
253
- * summary: Delete a resource
254
- * tags: [Resources]
255
- * security:
256
- * - bearerAuth: []
257
- * parameters:
258
- * - in: path
259
- * name: id
260
- * required: true
261
- * schema:
262
- * type: string
263
- * responses:
264
- * 204:
265
- * description: Resource deleted
266
- * 404:
267
- * description: Resource not found
268
- */
269
- router.delete('/:id', requireAuth, async (req, res, next) => {
270
- try {
271
- const { id } = req.params;
272
- const userId = (req as any).userId;
273
-
274
- // TODO: Replace with actual database query
275
- const resource = null;
276
-
277
- if (!resource) {
278
- throw new AppError('Resource not found', 404, 'RESOURCE_NOT_FOUND');
279
- }
280
-
281
- // Check authorization
282
- if ((resource as any).userId !== userId) {
283
- throw new AppError('Forbidden', 403, 'FORBIDDEN');
284
- }
285
-
286
- // TODO: Replace with actual database delete
287
-
288
- res.status(204).send();
289
- } catch (err) {
290
- next(err);
291
- }
292
- });
293
-
294
- export default router;
@@ -1,397 +0,0 @@
1
- ---
2
- name: jest-mock-patterns
3
- description: Common Jest mocking gotchas and solutions. Document common Jest mocking pitfalls including resetMocks behavior, module mocking order, and system global mocking.
4
- ---
5
-
6
- # Jest Mock Patterns Skill
7
-
8
- Comprehensive guide to Jest mocking patterns, common gotchas, and solutions.
9
-
10
- ## Configuration Gotchas
11
-
12
- ### resetMocks vs clearMocks vs restoreMocks
13
-
14
- | Option | Clears call history | Clears return values | Restores original |
15
- |--------|--------------------|-----------------------|-------------------|
16
- | `clearMocks` | Yes | No | No |
17
- | `resetMocks` | Yes | **Yes** | No |
18
- | `restoreMocks` | Yes | Yes | Yes |
19
-
20
- **CRITICAL**: AlphaCoder uses `resetMocks: true` in Jest config!
21
-
22
- ### The resetMocks: true Gotcha
23
-
24
- **Problem:** Mock return values are cleared between tests.
25
-
26
- ```typescript
27
- // ❌ WRONG - Return value lost after first test
28
- const mockGetSession = jest.fn().mockReturnValue({ id: 1, status: 'active' });
29
- jest.mock('./session-manager', () => ({ getSession: mockGetSession }));
30
-
31
- describe('Session tests', () => {
32
- it('test 1', () => {
33
- expect(mockGetSession()).toEqual({ id: 1, status: 'active' }); // PASS
34
- });
35
-
36
- it('test 2', () => {
37
- // mockGetSession now returns undefined due to resetMocks!
38
- expect(mockGetSession()).toEqual({ id: 1, status: 'active' }); // FAIL
39
- });
40
- });
41
- ```
42
-
43
- **Solution:** Re-set mock return values in `beforeEach`:
44
-
45
- ```typescript
46
- // ✅ CORRECT - Reset return values each test
47
- import { getSession } from './session-manager';
48
- jest.mock('./session-manager');
49
-
50
- const mockedGetSession = getSession as jest.Mock;
51
-
52
- describe('Session tests', () => {
53
- beforeEach(() => {
54
- mockedGetSession.mockReturnValue({ id: 1, status: 'active' });
55
- });
56
-
57
- it('test 1', () => {
58
- expect(mockedGetSession()).toEqual({ id: 1, status: 'active' }); // PASS
59
- });
60
-
61
- it('test 2', () => {
62
- expect(mockedGetSession()).toEqual({ id: 1, status: 'active' }); // PASS
63
- });
64
- });
65
- ```
66
-
67
- **Alternative:** Mock factory function (called for each test):
68
-
69
- ```typescript
70
- // ✅ ALSO CORRECT - Factory function approach
71
- jest.mock('./session-manager', () => ({
72
- getSession: jest.fn(() => ({ id: 1, status: 'active' })),
73
- }));
74
- ```
75
-
76
- ## Module Mocking Order
77
-
78
- ### Mocks MUST Be Before Imports
79
-
80
- Jest hoists `jest.mock()` calls, but the mock factory runs at import time.
81
-
82
- ```typescript
83
- // ✅ CORRECT ORDER
84
- jest.mock('./database');
85
- import { getDatabase } from './database'; // Receives mocked version
86
-
87
- // ❌ WRONG ORDER - Import already resolved
88
- import { getDatabase } from './database'; // Gets real version
89
- jest.mock('./database'); // Too late!
90
- ```
91
-
92
- ### Dynamic Imports with Mocks
93
-
94
- For complex cases, use dynamic imports:
95
-
96
- ```typescript
97
- jest.mock('./database');
98
-
99
- describe('tests', () => {
100
- let myModule: typeof import('./my-module');
101
-
102
- beforeEach(async () => {
103
- // Fresh import each test
104
- myModule = await import('./my-module');
105
- });
106
- });
107
- ```
108
-
109
- ## Mocking System Globals
110
-
111
- ### process.kill for PID Checking
112
-
113
- Standard pattern for testing code that checks if processes are alive:
114
-
115
- ```typescript
116
- // Create test helpers
117
- const originalKill = process.kill;
118
- const deadPids = new Set<number>();
119
-
120
- beforeEach(() => {
121
- deadPids.clear();
122
- (process.kill as jest.Mock) = jest.fn((pid: number, signal?: number) => {
123
- // Signal 0 = check if process exists (without killing)
124
- if (signal === 0 && deadPids.has(pid)) {
125
- const error = new Error('ESRCH: no such process');
126
- (error as NodeJS.ErrnoException).code = 'ESRCH';
127
- throw error;
128
- }
129
- return true;
130
- });
131
- });
132
-
133
- afterEach(() => {
134
- process.kill = originalKill;
135
- });
136
-
137
- // Usage in tests:
138
- describe('Process detection', () => {
139
- it('returns true for alive process', () => {
140
- expect(isProcessAlive(12345)).toBe(true);
141
- });
142
-
143
- it('returns false for dead process', () => {
144
- deadPids.add(12345);
145
- expect(isProcessAlive(12345)).toBe(false);
146
- });
147
- });
148
- ```
149
-
150
- ### process.pid
151
-
152
- ```typescript
153
- const originalPid = process.pid;
154
-
155
- beforeEach(() => {
156
- Object.defineProperty(process, 'pid', { value: 99999, writable: true });
157
- });
158
-
159
- afterEach(() => {
160
- Object.defineProperty(process, 'pid', { value: originalPid, writable: true });
161
- });
162
- ```
163
-
164
- ### Date/Time Mocking
165
-
166
- ```typescript
167
- describe('Time-sensitive tests', () => {
168
- beforeEach(() => {
169
- jest.useFakeTimers();
170
- jest.setSystemTime(new Date('2025-01-01T00:00:00Z'));
171
- });
172
-
173
- afterEach(() => {
174
- jest.useRealTimers();
175
- });
176
-
177
- it('uses mocked time', () => {
178
- expect(new Date().toISOString()).toBe('2025-01-01T00:00:00.000Z');
179
- });
180
-
181
- it('can advance time', () => {
182
- jest.advanceTimersByTime(60000); // 1 minute
183
- expect(new Date().toISOString()).toBe('2025-01-01T00:01:00.000Z');
184
- });
185
- });
186
- ```
187
-
188
- ### setTimeout/setInterval
189
-
190
- ```typescript
191
- beforeEach(() => {
192
- jest.useFakeTimers();
193
- });
194
-
195
- afterEach(() => {
196
- jest.useRealTimers();
197
- });
198
-
199
- it('handles delayed callback', () => {
200
- const callback = jest.fn();
201
- setTimeout(callback, 1000);
202
-
203
- expect(callback).not.toHaveBeenCalled();
204
-
205
- jest.advanceTimersByTime(1000);
206
-
207
- expect(callback).toHaveBeenCalledTimes(1);
208
- });
209
- ```
210
-
211
- ## Mocking Expensive Infrastructure
212
-
213
- ### Claude CLI / AgentSession
214
-
215
- Avoid spawning real Claude processes ($$$):
216
-
217
- ```typescript
218
- jest.mock('../../src/server/agent.js', () => ({
219
- AgentSession: jest.fn().mockImplementation(() => ({
220
- run: jest.fn().mockResolvedValue(undefined),
221
- on: jest.fn(),
222
- stop: jest.fn(),
223
- emit: jest.fn(),
224
- })),
225
- }));
226
- ```
227
-
228
- ### Session Manager
229
-
230
- ```typescript
231
- const mockGetActiveSession = jest.fn();
232
- const mockStartSession = jest.fn();
233
- const mockStopSession = jest.fn();
234
-
235
- jest.mock('../../src/server/session-manager.js', () => ({
236
- getActiveSession: mockGetActiveSession,
237
- startSession: mockStartSession,
238
- stopSession: mockStopSession,
239
- getActiveSessionCount: jest.fn().mockReturnValue(0),
240
- }));
241
-
242
- // In beforeEach, reset return values:
243
- beforeEach(() => {
244
- mockGetActiveSession.mockReturnValue(null);
245
- mockStartSession.mockResolvedValue({ on: jest.fn() });
246
- });
247
- ```
248
-
249
- ### WebSocket Broadcasting
250
-
251
- ```typescript
252
- const mockBroadcast = jest.fn();
253
- jest.mock('../../src/server/websocket-broadcaster.js', () => ({
254
- broadcast: mockBroadcast,
255
- }));
256
-
257
- // Verify broadcasts in tests:
258
- expect(mockBroadcast).toHaveBeenCalledWith({
259
- type: 'session_started',
260
- payload: expect.objectContaining({
261
- projectId: 1,
262
- sessionType: 'coding',
263
- }),
264
- });
265
- ```
266
-
267
- ### Database (In-Memory SQLite)
268
-
269
- ```typescript
270
- import Database from 'better-sqlite3';
271
-
272
- let testDb: Database.Database;
273
-
274
- beforeAll(() => {
275
- testDb = new Database(':memory:');
276
- testDb.exec(`
277
- CREATE TABLE sessions (
278
- id INTEGER PRIMARY KEY,
279
- status TEXT DEFAULT 'pending'
280
- );
281
- `);
282
- });
283
-
284
- afterAll(() => {
285
- testDb.close();
286
- });
287
-
288
- // Mock getDatabase to return test db
289
- jest.mock('../../src/server/database.js', () => ({
290
- getDatabase: () => testDb,
291
- }));
292
- ```
293
-
294
- ## Spy vs Mock
295
-
296
- ### When to Use Spies
297
-
298
- Spies observe calls to real implementations:
299
-
300
- ```typescript
301
- // Spy on existing method - real implementation runs
302
- const consoleSpy = jest.spyOn(console, 'log');
303
-
304
- doSomething(); // console.log actually runs
305
-
306
- expect(consoleSpy).toHaveBeenCalledWith('expected message');
307
-
308
- consoleSpy.mockRestore();
309
- ```
310
-
311
- ### When to Use Mocks
312
-
313
- Mocks replace implementations entirely:
314
-
315
- ```typescript
316
- // Mock replaces implementation
317
- jest.spyOn(console, 'log').mockImplementation(() => {});
318
-
319
- doSomething(); // console.log is silenced
320
-
321
- expect(console.log).toHaveBeenCalled();
322
- ```
323
-
324
- ## Common Patterns
325
-
326
- ### Testing Async Code
327
-
328
- ```typescript
329
- it('handles async operations', async () => {
330
- mockFetch.mockResolvedValue({ data: 'result' });
331
-
332
- const result = await fetchData();
333
-
334
- expect(result).toEqual({ data: 'result' });
335
- });
336
-
337
- it('handles async errors', async () => {
338
- mockFetch.mockRejectedValue(new Error('Network error'));
339
-
340
- await expect(fetchData()).rejects.toThrow('Network error');
341
- });
342
- ```
343
-
344
- ### Testing Event Emitters
345
-
346
- ```typescript
347
- it('emits events correctly', () => {
348
- const mockCallback = jest.fn();
349
- emitter.on('event', mockCallback);
350
-
351
- emitter.emit('event', { data: 'test' });
352
-
353
- expect(mockCallback).toHaveBeenCalledWith({ data: 'test' });
354
- });
355
- ```
356
-
357
- ### Partial Mocking
358
-
359
- Mock only specific exports:
360
-
361
- ```typescript
362
- jest.mock('./utils', () => ({
363
- ...jest.requireActual('./utils'), // Keep real implementations
364
- expensiveFunction: jest.fn(), // Mock only this one
365
- }));
366
- ```
367
-
368
- ## Troubleshooting
369
-
370
- ### Mock Not Being Applied
371
-
372
- 1. Check mock order (before imports)
373
- 2. Check mock path matches import path exactly
374
- 3. Check for `.js` extension in ESM projects
375
-
376
- ### Mock Return Value Undefined
377
-
378
- 1. Check if `resetMocks: true` in Jest config
379
- 2. Add return value in `beforeEach`
380
-
381
- ### Tests Pass Individually, Fail Together
382
-
383
- 1. Mock state leaking between tests
384
- 2. Missing cleanup in `afterEach`
385
- 3. Shared mutable state
386
-
387
- ### Timeout Errors
388
-
389
- 1. Missing `await` on async operations
390
- 2. Unresolved promises in mocked functions
391
- 3. `useFakeTimers()` blocking real timers
392
-
393
- ## Reference
394
-
395
- - Jest Mocking: https://jestjs.io/docs/mock-functions
396
- - Jest Timer Mocks: https://jestjs.io/docs/timer-mocks
397
- - Jest ES6 Mocks: https://jestjs.io/docs/es6-class-mocks