@lanonasis/cli 3.3.15 → 3.5.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.
- package/README.md +57 -13
- package/dist/commands/auth.js +11 -5
- package/dist/commands/completion.js +2 -0
- package/dist/commands/config.js +4 -4
- package/dist/commands/enhanced-memory.js +1 -1
- package/dist/commands/mcp.js +15 -9
- package/dist/core/achievements.js +1 -1
- package/dist/core/power-mode.js +5 -3
- package/dist/core/welcome.js +6 -6
- package/dist/enhanced-cli.js +6 -3
- package/dist/index-simple.js +5 -1
- package/dist/index.js +5 -1
- package/dist/mcp/access-control.d.ts +1 -1
- package/dist/mcp/access-control.js +4 -4
- package/dist/mcp/client/enhanced-client.js +5 -7
- package/dist/mcp/schemas/tool-schemas.d.ts +1 -1
- package/dist/mcp/schemas/tool-schemas.js +1 -1
- package/dist/mcp/server/lanonasis-server.d.ts +2 -1
- package/dist/mcp/server/lanonasis-server.js +7 -5
- package/dist/mcp/transports/transport-manager.js +3 -3
- package/dist/mcp-server.d.ts +1 -0
- package/dist/mcp-server.js +43 -9
- package/dist/utils/config.d.ts +10 -1
- package/dist/utils/config.js +97 -17
- package/dist/utils/mcp-client.d.ts +21 -2
- package/dist/utils/mcp-client.js +117 -46
- package/package.json +3 -3
- package/dist/__tests__/auth-persistence.test.d.ts +0 -1
- package/dist/__tests__/auth-persistence.test.js +0 -243
- package/dist/__tests__/cross-device-integration.test.d.ts +0 -1
- package/dist/__tests__/cross-device-integration.test.js +0 -305
- package/dist/__tests__/mcp-connection-reliability.test.d.ts +0 -1
- package/dist/__tests__/mcp-connection-reliability.test.js +0 -489
- package/dist/__tests__/setup.d.ts +0 -1
- package/dist/__tests__/setup.js +0 -26
- package/dist/mcp/server/mcp/server/lanonasis-server.js +0 -911
- package/dist/mcp/server/utils/api.js +0 -431
- 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 {};
|
package/dist/__tests__/setup.js
DELETED
|
@@ -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
|
-
});
|