@lanonasis/cli 3.2.14 → 3.4.15

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.
Files changed (36) hide show
  1. package/README.md +57 -13
  2. package/dist/commands/auth.js +7 -7
  3. package/dist/commands/completion.js +2 -0
  4. package/dist/commands/config.js +4 -4
  5. package/dist/commands/enhanced-memory.js +1 -1
  6. package/dist/commands/mcp.js +15 -9
  7. package/dist/core/achievements.js +1 -1
  8. package/dist/core/power-mode.js +5 -3
  9. package/dist/core/welcome.js +7 -6
  10. package/dist/enhanced-cli.js +6 -3
  11. package/dist/index-simple.js +5 -1
  12. package/dist/index.js +5 -1
  13. package/dist/mcp/access-control.d.ts +1 -1
  14. package/dist/mcp/access-control.js +4 -4
  15. package/dist/mcp/client/enhanced-client.js +4 -5
  16. package/dist/mcp/schemas/tool-schemas.d.ts +1 -1
  17. package/dist/mcp/schemas/tool-schemas.js +1 -1
  18. package/dist/mcp/server/lanonasis-server.d.ts +2 -1
  19. package/dist/mcp/server/lanonasis-server.js +7 -5
  20. package/dist/mcp/transports/transport-manager.js +3 -3
  21. package/dist/mcp-server.js +3 -3
  22. package/dist/utils/config.js +59 -10
  23. package/dist/utils/mcp-client.d.ts +4 -2
  24. package/dist/utils/mcp-client.js +86 -42
  25. package/package.json +3 -3
  26. package/dist/__tests__/auth-persistence.test.d.ts +0 -1
  27. package/dist/__tests__/auth-persistence.test.js +0 -243
  28. package/dist/__tests__/cross-device-integration.test.d.ts +0 -1
  29. package/dist/__tests__/cross-device-integration.test.js +0 -305
  30. package/dist/__tests__/mcp-connection-reliability.test.d.ts +0 -1
  31. package/dist/__tests__/mcp-connection-reliability.test.js +0 -489
  32. package/dist/__tests__/setup.d.ts +0 -1
  33. package/dist/__tests__/setup.js +0 -26
  34. package/dist/mcp/server/mcp/server/lanonasis-server.js +0 -911
  35. package/dist/mcp/server/utils/api.js +0 -431
  36. package/dist/mcp/server/utils/config.js +0 -855
@@ -1,489 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
2
- import { MCPClient } from '../utils/mcp-client.js';
3
- import * as fs from 'fs/promises';
4
- import * as path from 'path';
5
- import * as os from 'os';
6
- import WebSocket from 'ws';
7
- import { EventSource } from 'eventsource';
8
- // Mock dependencies
9
- jest.mock('ws');
10
- jest.mock('eventsource');
11
- jest.mock('chalk', () => ({
12
- default: {
13
- blue: {
14
- bold: (str) => str,
15
- },
16
- cyan: (str) => str,
17
- gray: (str) => str,
18
- green: (str) => str,
19
- red: (str) => str,
20
- yellow: (str) => str,
21
- }
22
- }));
23
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
24
- const mockAxios = {
25
- get: jest.fn(),
26
- post: jest.fn()
27
- };
28
- jest.mock('axios', () => ({
29
- default: mockAxios,
30
- get: mockAxios.get,
31
- post: mockAxios.post
32
- }));
33
- // Mock MCP SDK
34
- jest.mock('@modelcontextprotocol/sdk/client/index.js', () => ({
35
- Client: jest.fn().mockImplementation(() => ({
36
- connect: jest.fn(),
37
- close: jest.fn(),
38
- callTool: jest.fn(),
39
- listTools: jest.fn()
40
- }))
41
- }));
42
- jest.mock('@modelcontextprotocol/sdk/client/stdio.js', () => ({
43
- StdioClientTransport: jest.fn()
44
- }));
45
- describe('MCP Connection Reliability Tests', () => {
46
- let mcpClient;
47
- let testConfigDir;
48
- let mockWebSocket;
49
- let mockEventSource;
50
- let mockMCPClient;
51
- beforeEach(async () => {
52
- // Create temporary test directory
53
- testConfigDir = path.join(os.tmpdir(), `test-mcp-reliability-${Date.now()}-${Math.random()}`);
54
- await fs.mkdir(testConfigDir, { recursive: true });
55
- // Create MCP client instance
56
- mcpClient = new MCPClient();
57
- // Mock the config to use test directory
58
- const config = mcpClient.config;
59
- config.configDir = testConfigDir;
60
- config.configPath = path.join(testConfigDir, 'config.json');
61
- config.lockFile = path.join(testConfigDir, 'config.lock');
62
- // Initialize with test credentials
63
- await config.init();
64
- await config.setAndSave('token', 'test-token');
65
- await config.setAndSave('vendorKey', 'pk_test123456789.sk_test123456789012345');
66
- // Setup mocks
67
- mockWebSocket = WebSocket;
68
- mockEventSource = EventSource;
69
- mockMCPClient = (await import('@modelcontextprotocol/sdk/client/index.js')).Client;
70
- // Clear all mocks
71
- mockAxios.get.mockClear();
72
- mockAxios.post.mockClear();
73
- mockWebSocket.mockClear();
74
- mockEventSource.mockClear();
75
- mockMCPClient.mockClear();
76
- });
77
- afterEach(async () => {
78
- // Disconnect and cleanup
79
- await mcpClient.disconnect();
80
- // Clean up test directory
81
- try {
82
- await fs.rm(testConfigDir, { recursive: true, force: true });
83
- }
84
- catch {
85
- // Ignore cleanup errors
86
- }
87
- });
88
- describe('Connection Retry Logic with Simulated Failures', () => {
89
- it('should retry connection on network failures with exponential backoff', async () => {
90
- // Mock network failure followed by success
91
- mockAxios.get
92
- .mockRejectedValueOnce(new Error('ECONNREFUSED'))
93
- .mockRejectedValueOnce(new Error('ECONNREFUSED'))
94
- .mockResolvedValueOnce({ status: 200, data: { status: 'ok' } });
95
- // Mock EventSource for SSE connection
96
- const mockSSEInstance = {
97
- onmessage: null,
98
- onerror: null,
99
- close: jest.fn()
100
- };
101
- mockEventSource.mockImplementation(() => mockSSEInstance);
102
- // Spy on console to verify retry messages
103
- const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => { });
104
- // Attempt connection with remote mode
105
- const connected = await mcpClient.connect({ connectionMode: 'remote' });
106
- // Should eventually succeed after retries
107
- expect(connected).toBe(true);
108
- // Should have made multiple attempts
109
- expect(mockAxios.get).toHaveBeenCalledTimes(3);
110
- // Should show retry messages
111
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Retry'));
112
- consoleSpy.mockRestore();
113
- });
114
- it('should fail after maximum retry attempts', async () => {
115
- // Mock persistent network failure
116
- mockAxios.get.mockRejectedValue(new Error('ECONNREFUSED'));
117
- const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
118
- // Attempt connection
119
- const connected = await mcpClient.connect({ connectionMode: 'remote' });
120
- // Should fail after max retries
121
- expect(connected).toBe(false);
122
- // Should have made maximum attempts (3 retries + 1 initial = 4 total)
123
- expect(mockAxios.get).toHaveBeenCalledTimes(4);
124
- // Should show failure message
125
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Failed to connect after'));
126
- consoleSpy.mockRestore();
127
- });
128
- it('should not retry authentication errors', async () => {
129
- // Mock authentication failure
130
- mockAxios.get.mockRejectedValue({
131
- response: { status: 401 },
132
- message: 'Unauthorized'
133
- });
134
- const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
135
- // Attempt connection
136
- const connected = await mcpClient.connect({ connectionMode: 'remote' });
137
- // Should fail immediately without retries
138
- expect(connected).toBe(false);
139
- // Should only make one attempt (no retries for auth errors)
140
- expect(mockAxios.get).toHaveBeenCalledTimes(1);
141
- // Should show authentication error
142
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Authentication failed'));
143
- consoleSpy.mockRestore();
144
- });
145
- it('should calculate exponential backoff delays correctly', async () => {
146
- // Access private method for testing
147
- const backoffMethod = mcpClient.exponentialBackoff.bind(mcpClient);
148
- // Test exponential backoff calculation
149
- const delay1 = await backoffMethod(1);
150
- const delay2 = await backoffMethod(2);
151
- const delay3 = await backoffMethod(3);
152
- const delay4 = await backoffMethod(4);
153
- // Should increase exponentially but with jitter
154
- expect(delay1).toBeGreaterThanOrEqual(750); // ~1000ms ± 25%
155
- expect(delay1).toBeLessThanOrEqual(1250);
156
- expect(delay2).toBeGreaterThanOrEqual(1500); // ~2000ms ± 25%
157
- expect(delay2).toBeLessThanOrEqual(2500);
158
- expect(delay3).toBeGreaterThanOrEqual(3000); // ~4000ms ± 25%
159
- expect(delay3).toBeLessThanOrEqual(5000);
160
- // Should cap at 10 seconds
161
- expect(delay4).toBeLessThanOrEqual(10000);
162
- });
163
- });
164
- describe('Health Monitoring and Automatic Reconnection', () => {
165
- it('should start health monitoring after successful connection', async () => {
166
- // Mock successful connection
167
- mockAxios.get.mockResolvedValue({ status: 200, data: { status: 'ok' } });
168
- const mockSSEInstance = {
169
- onmessage: null,
170
- onerror: null,
171
- close: jest.fn()
172
- };
173
- mockEventSource.mockImplementation(() => mockSSEInstance);
174
- // Connect
175
- const connected = await mcpClient.connect({ connectionMode: 'remote' });
176
- expect(connected).toBe(true);
177
- // Health monitoring should be active
178
- const healthInterval = mcpClient.healthCheckInterval;
179
- expect(healthInterval).toBeDefined();
180
- expect(healthInterval).not.toBeNull();
181
- });
182
- it('should perform health checks at regular intervals', async () => {
183
- // Mock successful connection
184
- mockAxios.get.mockResolvedValue({ status: 200, data: { status: 'ok' } });
185
- const mockSSEInstance = {
186
- onmessage: null,
187
- onerror: null,
188
- close: jest.fn()
189
- };
190
- mockEventSource.mockImplementation(() => mockSSEInstance);
191
- // Connect
192
- await mcpClient.connect({ connectionMode: 'remote' });
193
- // Wait for initial health check
194
- await new Promise(resolve => setTimeout(resolve, 100));
195
- // Should have made health check calls
196
- expect(mockAxios.get).toHaveBeenCalledWith(expect.stringContaining('/health'), expect.objectContaining({
197
- timeout: 5000
198
- }));
199
- });
200
- it('should attempt reconnection when health check fails', async () => {
201
- // Mock initial successful connection
202
- mockAxios.get.mockResolvedValueOnce({ status: 200, data: { status: 'ok' } });
203
- const mockSSEInstance = {
204
- onmessage: null,
205
- onerror: null,
206
- close: jest.fn()
207
- };
208
- mockEventSource.mockImplementation(() => mockSSEInstance);
209
- // Connect
210
- await mcpClient.connect({ connectionMode: 'remote' });
211
- // Mock health check failure followed by successful reconnection
212
- mockAxios.get
213
- .mockRejectedValueOnce(new Error('Health check failed'))
214
- .mockResolvedValueOnce({ status: 200, data: { status: 'ok' } });
215
- const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => { });
216
- // Trigger health check failure
217
- const performHealthCheck = mcpClient.performHealthCheck.bind(mcpClient);
218
- await performHealthCheck();
219
- // Should attempt reconnection
220
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Reconnected to MCP server'));
221
- consoleSpy.mockRestore();
222
- });
223
- it('should stop health monitoring when disconnected', async () => {
224
- // Mock successful connection
225
- mockAxios.get.mockResolvedValue({ status: 200, data: { status: 'ok' } });
226
- const mockSSEInstance = {
227
- onmessage: null,
228
- onerror: null,
229
- close: jest.fn()
230
- };
231
- mockEventSource.mockImplementation(() => mockSSEInstance);
232
- // Connect
233
- await mcpClient.connect({ connectionMode: 'remote' });
234
- // Verify health monitoring is active
235
- expect(mcpClient.healthCheckInterval).not.toBeNull();
236
- // Disconnect
237
- await mcpClient.disconnect();
238
- // Health monitoring should be stopped
239
- expect(mcpClient.healthCheckInterval).toBeNull();
240
- });
241
- });
242
- describe('Transport Protocol Fallback Scenarios', () => {
243
- it('should handle WebSocket connection failures gracefully', async () => {
244
- // Mock WebSocket connection failure
245
- const mockWSInstance = {
246
- on: jest.fn((event, callback) => {
247
- if (event === 'error') {
248
- // Simulate immediate error
249
- setTimeout(() => callback(new Error('WebSocket connection failed')), 10);
250
- }
251
- }),
252
- close: jest.fn(),
253
- send: jest.fn(),
254
- readyState: WebSocket.CONNECTING
255
- };
256
- mockWebSocket.mockImplementation(() => mockWSInstance);
257
- const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
258
- // Attempt WebSocket connection
259
- const connected = await mcpClient.connect({ connectionMode: 'websocket' });
260
- // Should fail gracefully
261
- expect(connected).toBe(false);
262
- // Should show appropriate error message
263
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('WebSocket error'));
264
- consoleSpy.mockRestore();
265
- });
266
- it('should handle SSE connection failures in remote mode', async () => {
267
- // Mock successful HTTP connection but SSE failure
268
- mockAxios.get.mockResolvedValue({ status: 200, data: { status: 'ok' } });
269
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
270
- const mockSSEInstance = {
271
- onmessage: null,
272
- onerror: null,
273
- close: jest.fn()
274
- };
275
- mockEventSource.mockImplementation(() => {
276
- const instance = mockSSEInstance;
277
- // Simulate SSE error
278
- setTimeout(() => {
279
- if (instance.onerror) {
280
- instance.onerror(new Error('SSE connection failed'));
281
- }
282
- }, 10);
283
- return instance;
284
- });
285
- const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
286
- // Connect in remote mode
287
- const connected = await mcpClient.connect({ connectionMode: 'remote' });
288
- // Should still connect (SSE is optional for remote mode)
289
- expect(connected).toBe(true);
290
- // Wait for SSE error
291
- await new Promise(resolve => setTimeout(resolve, 50));
292
- // Should log SSE error but not fail connection
293
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('SSE connection error'));
294
- consoleSpy.mockRestore();
295
- });
296
- it('should handle local MCP server not found', async () => {
297
- const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => { });
298
- // Attempt local connection (server file won't exist in test)
299
- const connected = await mcpClient.connect({
300
- connectionMode: 'local',
301
- serverPath: '/nonexistent/path/server.js'
302
- });
303
- // Should fail with helpful message
304
- expect(connected).toBe(false);
305
- // Should provide guidance for remote connection
306
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Local MCP server not found'));
307
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('For remote connection, use:'));
308
- consoleSpy.mockRestore();
309
- });
310
- it('should auto-reconnect WebSocket after connection drop', async () => {
311
- // Mock successful initial WebSocket connection
312
- const mockWSInstance = {
313
- on: jest.fn(),
314
- close: jest.fn(),
315
- send: jest.fn(),
316
- readyState: WebSocket.OPEN
317
- };
318
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
319
- let onCloseCallback = null;
320
- mockWSInstance.on.mockImplementation((event, callback) => {
321
- if (event === 'open') {
322
- setTimeout(() => callback(), 10);
323
- }
324
- else if (event === 'close') {
325
- onCloseCallback = callback;
326
- }
327
- });
328
- mockWebSocket.mockImplementation(() => mockWSInstance);
329
- const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => { });
330
- // Connect
331
- const connected = await mcpClient.connect({ connectionMode: 'websocket' });
332
- expect(connected).toBe(true);
333
- // Simulate connection drop
334
- if (onCloseCallback) {
335
- onCloseCallback(1006, 'Connection lost');
336
- }
337
- // Should log reconnection attempt
338
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('WebSocket connection closed'));
339
- consoleSpy.mockRestore();
340
- });
341
- });
342
- describe('Error Handling and User Guidance Accuracy', () => {
343
- it('should provide specific guidance for authentication errors', async () => {
344
- // Mock authentication error
345
- mockAxios.get.mockRejectedValue({
346
- response: { status: 401 },
347
- message: 'AUTHENTICATION_REQUIRED'
348
- });
349
- const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => { });
350
- // Attempt connection
351
- await mcpClient.connect({ connectionMode: 'remote' });
352
- // Should provide specific authentication guidance
353
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('No credentials found'));
354
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('lanonasis auth login'));
355
- consoleSpy.mockRestore();
356
- });
357
- it('should provide specific guidance for network errors', async () => {
358
- // Mock network error
359
- mockAxios.get.mockRejectedValue({
360
- code: 'ECONNREFUSED',
361
- message: 'connect ECONNREFUSED'
362
- });
363
- const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => { });
364
- // Attempt connection
365
- await mcpClient.connect({ connectionMode: 'remote' });
366
- // Should provide specific network guidance
367
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Connection refused'));
368
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Check https://mcp.lanonasis.com/health'));
369
- consoleSpy.mockRestore();
370
- });
371
- it('should provide specific guidance for timeout errors', async () => {
372
- // Mock timeout error
373
- mockAxios.get.mockRejectedValue({
374
- code: 'ETIMEDOUT',
375
- message: 'timeout'
376
- });
377
- const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => { });
378
- // Attempt connection
379
- await mcpClient.connect({ connectionMode: 'remote' });
380
- // Should provide specific timeout guidance
381
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Connection timeout'));
382
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Check network'));
383
- consoleSpy.mockRestore();
384
- });
385
- it('should provide specific guidance for SSL/TLS errors', async () => {
386
- // Mock SSL error
387
- mockAxios.get.mockRejectedValue({
388
- message: 'certificate verify failed'
389
- });
390
- const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => { });
391
- // Attempt connection
392
- await mcpClient.connect({ connectionMode: 'remote' });
393
- // Should provide specific SSL guidance
394
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('SSL/TLS certificate issue'));
395
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Check system time'));
396
- consoleSpy.mockRestore();
397
- });
398
- it('should validate authentication before connection attempts', async () => {
399
- // Create config without credentials
400
- const config = mcpClient.config;
401
- await config.clearInvalidCredentials();
402
- const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
403
- // Attempt connection
404
- const connected = await mcpClient.connect({ connectionMode: 'remote' });
405
- // Should fail with authentication error
406
- expect(connected).toBe(false);
407
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('AUTHENTICATION_REQUIRED'));
408
- consoleSpy.mockRestore();
409
- });
410
- it('should provide connection status with detailed information', () => {
411
- const status = mcpClient.getConnectionStatus();
412
- expect(status).toHaveProperty('connected');
413
- expect(status).toHaveProperty('mode');
414
- expect(status).toHaveProperty('server');
415
- expect(status).toHaveProperty('failureCount');
416
- expect(typeof status.connected).toBe('boolean');
417
- expect(typeof status.mode).toBe('string');
418
- expect(typeof status.failureCount).toBe('number');
419
- });
420
- it('should track connection uptime and health check status', async () => {
421
- // Mock successful connection
422
- mockAxios.get.mockResolvedValue({ status: 200, data: { status: 'ok' } });
423
- const mockSSEInstance = {
424
- onmessage: null,
425
- onerror: null,
426
- close: jest.fn()
427
- };
428
- mockEventSource.mockImplementation(() => mockSSEInstance);
429
- // Connect
430
- await mcpClient.connect({ connectionMode: 'remote' });
431
- // Wait a bit for connection to establish
432
- await new Promise(resolve => setTimeout(resolve, 100));
433
- const status = mcpClient.getConnectionStatus();
434
- expect(status.connected).toBe(true);
435
- expect(status.connectionUptime).toBeGreaterThan(0);
436
- expect(status.lastHealthCheck).toBeDefined();
437
- });
438
- });
439
- describe('Tool Execution Reliability', () => {
440
- it('should handle tool execution failures gracefully', async () => {
441
- // Mock successful connection
442
- mockAxios.get.mockResolvedValue({ status: 200, data: { status: 'ok' } });
443
- const mockSSEInstance = {
444
- onmessage: null,
445
- onerror: null,
446
- close: jest.fn()
447
- };
448
- mockEventSource.mockImplementation(() => mockSSEInstance);
449
- await mcpClient.connect({ connectionMode: 'remote' });
450
- // Mock tool execution failure
451
- mockAxios.mockRejectedValue({
452
- response: {
453
- status: 500,
454
- data: { error: 'Internal server error' }
455
- }
456
- });
457
- // Should handle tool execution error
458
- await expect(mcpClient.callTool('memory_create_memory', {
459
- title: 'test',
460
- content: 'test content'
461
- })).rejects.toThrow('Remote tool call failed');
462
- });
463
- it('should validate connection before tool execution', async () => {
464
- // Don't connect first
465
- // Should fail with connection error
466
- await expect(mcpClient.callTool('memory_create_memory', {
467
- title: 'test',
468
- content: 'test content'
469
- })).rejects.toThrow('Not connected to MCP server');
470
- });
471
- it('should list available tools correctly', async () => {
472
- // Mock successful connection
473
- mockAxios.get.mockResolvedValue({ status: 200, data: { status: 'ok' } });
474
- const mockSSEInstance = {
475
- onmessage: null,
476
- onerror: null,
477
- close: jest.fn()
478
- };
479
- mockEventSource.mockImplementation(() => mockSSEInstance);
480
- await mcpClient.connect({ connectionMode: 'remote' });
481
- // Should return list of available tools
482
- const tools = await mcpClient.listTools();
483
- expect(Array.isArray(tools)).toBe(true);
484
- expect(tools.length).toBeGreaterThan(0);
485
- expect(tools[0]).toHaveProperty('name');
486
- expect(tools[0]).toHaveProperty('description');
487
- });
488
- });
489
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,26 +0,0 @@
1
- import { jest, beforeAll, afterAll, afterEach } from '@jest/globals';
2
- // Set test environment
3
- process.env.NODE_ENV = 'test';
4
- process.env.CLI_VERBOSE = 'false';
5
- // Mock console methods to reduce noise in tests
6
- const originalConsole = { ...console };
7
- beforeAll(() => {
8
- // Suppress console output during tests unless explicitly needed
9
- console.log = jest.fn();
10
- console.info = jest.fn();
11
- console.warn = jest.fn();
12
- console.error = jest.fn();
13
- });
14
- afterAll(() => {
15
- // Restore console methods
16
- Object.assign(console, originalConsole);
17
- });
18
- // Global test timeout
19
- jest.setTimeout(30000);
20
- // Mock process.exit to prevent tests from actually exiting
21
- const mockExit = jest.fn();
22
- process.exit = mockExit;
23
- afterEach(() => {
24
- // Clear mock calls after each test
25
- mockExit.mockClear();
26
- });