@tostudy-ai/mcp-setup 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +149 -0
  2. package/bin/cli.js +6 -0
  3. package/dist/__tests__/e2e-diagnostic-repair-flow.test.d.ts +52 -0
  4. package/dist/__tests__/e2e-diagnostic-repair-flow.test.d.ts.map +1 -0
  5. package/dist/__tests__/e2e-diagnostic-repair-flow.test.js +720 -0
  6. package/dist/__tests__/e2e-diagnostic-repair-flow.test.js.map +1 -0
  7. package/dist/__tests__/e2e-wizard-flow.test.d.ts +43 -0
  8. package/dist/__tests__/e2e-wizard-flow.test.d.ts.map +1 -0
  9. package/dist/__tests__/e2e-wizard-flow.test.js +407 -0
  10. package/dist/__tests__/e2e-wizard-flow.test.js.map +1 -0
  11. package/dist/__tests__/ide-handlers.test.d.ts +10 -0
  12. package/dist/__tests__/ide-handlers.test.d.ts.map +1 -0
  13. package/dist/__tests__/ide-handlers.test.js +336 -0
  14. package/dist/__tests__/ide-handlers.test.js.map +1 -0
  15. package/dist/__tests__/install-command.test.d.ts +10 -0
  16. package/dist/__tests__/install-command.test.d.ts.map +1 -0
  17. package/dist/__tests__/install-command.test.js +237 -0
  18. package/dist/__tests__/install-command.test.js.map +1 -0
  19. package/dist/config.d.ts +51 -0
  20. package/dist/config.d.ts.map +1 -0
  21. package/dist/config.js +117 -0
  22. package/dist/config.js.map +1 -0
  23. package/dist/detect.d.ts +42 -0
  24. package/dist/detect.d.ts.map +1 -0
  25. package/dist/detect.js +277 -0
  26. package/dist/detect.js.map +1 -0
  27. package/dist/diagnose.d.ts +36 -0
  28. package/dist/diagnose.d.ts.map +1 -0
  29. package/dist/diagnose.js +512 -0
  30. package/dist/diagnose.js.map +1 -0
  31. package/dist/ide-handlers/base.d.ts +36 -0
  32. package/dist/ide-handlers/base.d.ts.map +1 -0
  33. package/dist/ide-handlers/base.js +41 -0
  34. package/dist/ide-handlers/base.js.map +1 -0
  35. package/dist/ide-handlers/claude-code.d.ts +15 -0
  36. package/dist/ide-handlers/claude-code.d.ts.map +1 -0
  37. package/dist/ide-handlers/claude-code.js +50 -0
  38. package/dist/ide-handlers/claude-code.js.map +1 -0
  39. package/dist/ide-handlers/cursor.d.ts +15 -0
  40. package/dist/ide-handlers/cursor.d.ts.map +1 -0
  41. package/dist/ide-handlers/cursor.js +61 -0
  42. package/dist/ide-handlers/cursor.js.map +1 -0
  43. package/dist/ide-handlers/desktop.d.ts +16 -0
  44. package/dist/ide-handlers/desktop.d.ts.map +1 -0
  45. package/dist/ide-handlers/desktop.js +26 -0
  46. package/dist/ide-handlers/desktop.js.map +1 -0
  47. package/dist/ide-handlers/index.d.ts +21 -0
  48. package/dist/ide-handlers/index.d.ts.map +1 -0
  49. package/dist/ide-handlers/index.js +49 -0
  50. package/dist/ide-handlers/index.js.map +1 -0
  51. package/dist/ide-handlers/manual.d.ts +16 -0
  52. package/dist/ide-handlers/manual.d.ts.map +1 -0
  53. package/dist/ide-handlers/manual.js +34 -0
  54. package/dist/ide-handlers/manual.js.map +1 -0
  55. package/dist/ide-handlers/opencode.d.ts +15 -0
  56. package/dist/ide-handlers/opencode.d.ts.map +1 -0
  57. package/dist/ide-handlers/opencode.js +57 -0
  58. package/dist/ide-handlers/opencode.js.map +1 -0
  59. package/dist/ide-handlers/vscode.d.ts +16 -0
  60. package/dist/ide-handlers/vscode.d.ts.map +1 -0
  61. package/dist/ide-handlers/vscode.js +62 -0
  62. package/dist/ide-handlers/vscode.js.map +1 -0
  63. package/dist/index.d.ts +14 -0
  64. package/dist/index.d.ts.map +1 -0
  65. package/dist/index.js +501 -0
  66. package/dist/index.js.map +1 -0
  67. package/dist/prompts.d.ts +23 -0
  68. package/dist/prompts.d.ts.map +1 -0
  69. package/dist/prompts.js +68 -0
  70. package/dist/prompts.js.map +1 -0
  71. package/dist/repair.d.ts +50 -0
  72. package/dist/repair.d.ts.map +1 -0
  73. package/dist/repair.js +588 -0
  74. package/dist/repair.js.map +1 -0
  75. package/package.json +54 -0
@@ -0,0 +1,720 @@
1
+ /**
2
+ * E2E Test: Diagnostic and Repair Flow
3
+ *
4
+ * This test verifies the end-to-end diagnostic and repair flow:
5
+ * 1. Simulate common issues (port conflict, missing config, invalid JSON, etc.)
6
+ * 2. Run mcp-setup diagnose command
7
+ * 3. Verify issue detected with clear description
8
+ * 4. Run mcp-setup repair command
9
+ * 5. Verify issue fixed and MCP configuration is valid
10
+ *
11
+ * NOTE: Port conflict tests require elevated permissions on some systems.
12
+ * Run with: npx tsx src/__tests__/e2e-diagnostic-repair-flow.test.ts
13
+ */
14
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from 'node:fs';
15
+ import { dirname, join } from 'node:path';
16
+ import { platform, tmpdir } from 'node:os';
17
+ import { createServer } from 'node:net';
18
+ // Import the modules we're testing
19
+ import { getClaudeConfigPath, readClaudeConfig, writeClaudeConfig, addTostudyMcpServer, isTostudyMcpConfigured, getTostudyMcpConfig, } from '../config.js';
20
+ import { runDiagnostics } from '../diagnose.js';
21
+ import { repairAllIssues, repairIssue, diagnoseAndRepair, issueRequiresUserInput, } from '../repair.js';
22
+ // Test constants
23
+ const TEST_API_KEY = 'test-diagnostic-repair-api-key-12345678901234567890';
24
+ const TEST_PLATFORM_URL = 'https://tostudy.com';
25
+ const DEFAULT_MCP_PORT = 3701;
26
+ const results = [];
27
+ const context = {
28
+ configBackup: null,
29
+ portServer: null,
30
+ tempDir: '',
31
+ };
32
+ // ==================================================
33
+ // Utility Functions
34
+ // ==================================================
35
+ function log(message) {
36
+ process.stdout.write(`${message}\n`);
37
+ }
38
+ function logStep(step, description) {
39
+ log(`\n${'━'.repeat(60)}`);
40
+ log(`Step ${step}: ${description}`);
41
+ log('━'.repeat(60));
42
+ }
43
+ function logResult(name, passed, message) {
44
+ const icon = passed ? '✓' : '✗';
45
+ const color = passed ? '\x1b[32m' : '\x1b[31m';
46
+ const reset = '\x1b[0m';
47
+ log(` ${color}${icon}${reset} ${name}: ${message}`);
48
+ }
49
+ function assert(condition, message) {
50
+ if (!condition) {
51
+ throw new Error(`Assertion failed: ${message}`);
52
+ }
53
+ }
54
+ async function runTest(name, testFn) {
55
+ const start = Date.now();
56
+ try {
57
+ await testFn();
58
+ const duration = Date.now() - start;
59
+ results.push({ name, passed: true, message: 'OK', duration });
60
+ logResult(name, true, `OK (${duration}ms)`);
61
+ }
62
+ catch (error) {
63
+ const duration = Date.now() - start;
64
+ const message = error instanceof Error ? error.message : String(error);
65
+ results.push({ name, passed: false, message, duration });
66
+ logResult(name, false, message);
67
+ }
68
+ }
69
+ // ==================================================
70
+ // Setup and Teardown
71
+ // ==================================================
72
+ function backupConfig() {
73
+ const configPath = getClaudeConfigPath();
74
+ if (existsSync(configPath)) {
75
+ context.configBackup = readFileSync(configPath, 'utf-8');
76
+ log(` Backed up existing config from ${configPath}`);
77
+ }
78
+ }
79
+ function restoreConfig() {
80
+ const configPath = getClaudeConfigPath();
81
+ if (context.configBackup !== null) {
82
+ writeFileSync(configPath, context.configBackup, 'utf-8');
83
+ log(` Restored original config to ${configPath}`);
84
+ }
85
+ else if (existsSync(configPath)) {
86
+ log(` Test created new config at ${configPath}`);
87
+ }
88
+ }
89
+ function createTempDir() {
90
+ context.tempDir = join(tmpdir(), `mcp-e2e-test-${Date.now()}`);
91
+ mkdirSync(context.tempDir, { recursive: true });
92
+ log(` Created temp directory: ${context.tempDir}`);
93
+ }
94
+ function cleanupTempDir() {
95
+ if (context.tempDir && existsSync(context.tempDir)) {
96
+ rmSync(context.tempDir, { recursive: true, force: true });
97
+ log(` Cleaned up temp directory: ${context.tempDir}`);
98
+ }
99
+ }
100
+ async function startPortBlocker(port) {
101
+ return new Promise((resolve, reject) => {
102
+ const server = createServer();
103
+ server.once('error', reject);
104
+ server.listen(port, '127.0.0.1', () => {
105
+ context.portServer = server;
106
+ resolve(server);
107
+ });
108
+ });
109
+ }
110
+ function stopPortBlocker() {
111
+ if (context.portServer) {
112
+ context.portServer.close();
113
+ context.portServer = null;
114
+ }
115
+ }
116
+ // ==================================================
117
+ // Issue Simulation Helpers
118
+ // ==================================================
119
+ function simulateMissingConfig() {
120
+ const configPath = getClaudeConfigPath();
121
+ if (existsSync(configPath)) {
122
+ rmSync(configPath);
123
+ }
124
+ }
125
+ function simulateInvalidJsonConfig() {
126
+ const configPath = getClaudeConfigPath();
127
+ const configDir = dirname(configPath);
128
+ if (!existsSync(configDir)) {
129
+ mkdirSync(configDir, { recursive: true });
130
+ }
131
+ writeFileSync(configPath, '{ invalid json content !!!', 'utf-8');
132
+ }
133
+ function simulateMissingMcpConfig() {
134
+ const configPath = getClaudeConfigPath();
135
+ const configDir = dirname(configPath);
136
+ if (!existsSync(configDir)) {
137
+ mkdirSync(configDir, { recursive: true });
138
+ }
139
+ writeFileSync(configPath, JSON.stringify({ mcpServers: {} }, null, 2), 'utf-8');
140
+ }
141
+ function simulateMissingApiKey() {
142
+ const config = {
143
+ mcpServers: {
144
+ 'tostudy': {
145
+ type: 'sse',
146
+ url: 'https://tostudy.com:3701/mcp/sse',
147
+ headers: {},
148
+ },
149
+ },
150
+ };
151
+ writeClaudeConfig(config);
152
+ }
153
+ function simulateInvalidApiKeyFormat() {
154
+ const config = {
155
+ mcpServers: {
156
+ 'tostudy': {
157
+ type: 'sse',
158
+ url: 'https://tostudy.com:3701/mcp/sse',
159
+ headers: {
160
+ Authorization: 'invalid-no-bearer-prefix', // Missing "Bearer "
161
+ },
162
+ },
163
+ },
164
+ };
165
+ writeClaudeConfig(config);
166
+ }
167
+ function simulateMissingServerUrl() {
168
+ const config = {
169
+ mcpServers: {
170
+ 'tostudy': {
171
+ type: 'sse',
172
+ headers: {
173
+ Authorization: `Bearer ${TEST_API_KEY}`,
174
+ },
175
+ },
176
+ },
177
+ };
178
+ writeClaudeConfig(config);
179
+ }
180
+ function simulateInvalidServerUrlPath() {
181
+ const config = {
182
+ mcpServers: {
183
+ 'tostudy': {
184
+ type: 'sse',
185
+ url: 'https://tostudy.com:3701', // Missing /mcp/sse path
186
+ headers: {
187
+ Authorization: `Bearer ${TEST_API_KEY}`,
188
+ },
189
+ },
190
+ },
191
+ };
192
+ writeClaudeConfig(config);
193
+ }
194
+ function simulateDuplicateServers() {
195
+ const config = {
196
+ mcpServers: {
197
+ 'tostudy': {
198
+ type: 'sse',
199
+ url: 'https://tostudy.com:3701/mcp/sse',
200
+ headers: {
201
+ Authorization: `Bearer ${TEST_API_KEY}`,
202
+ },
203
+ },
204
+ 'catalyst-mcp': {
205
+ type: 'sse',
206
+ url: 'https://tostudy.com:3701/mcp/sse',
207
+ headers: {
208
+ Authorization: `Bearer ${TEST_API_KEY}`,
209
+ },
210
+ },
211
+ 'ana-test': {
212
+ type: 'sse',
213
+ url: 'https://tostudy.com:3701/mcp/sse',
214
+ headers: {
215
+ Authorization: `Bearer ${TEST_API_KEY}`,
216
+ },
217
+ },
218
+ },
219
+ };
220
+ writeClaudeConfig(config);
221
+ }
222
+ function simulateShortApiKey() {
223
+ const config = {
224
+ mcpServers: {
225
+ 'tostudy': {
226
+ type: 'sse',
227
+ url: 'https://tostudy.com:3701/mcp/sse',
228
+ headers: {
229
+ Authorization: 'Bearer short', // Too short API key
230
+ },
231
+ },
232
+ },
233
+ };
234
+ writeClaudeConfig(config);
235
+ }
236
+ // ==================================================
237
+ // Test Suites
238
+ // ==================================================
239
+ /**
240
+ * Step 1: Test detection of missing config file
241
+ */
242
+ async function testMissingConfigDetection() {
243
+ logStep(1, 'Missing Config File Detection & Repair');
244
+ await runTest('Detect missing config file', async () => {
245
+ simulateMissingConfig();
246
+ const report = await runDiagnostics();
247
+ const issue = report.issues.find((i) => i.id === 'config-missing');
248
+ assert(issue !== undefined, 'Should detect config-missing issue');
249
+ assert(issue?.severity === 'warning', 'Missing config should be a warning');
250
+ assert(issue?.autoFixable === true, 'Missing config should be auto-fixable');
251
+ log(` Detected: ${issue?.title}`);
252
+ log(` Severity: ${issue?.severity}`);
253
+ });
254
+ await runTest('Repair missing config file', async () => {
255
+ simulateMissingConfig();
256
+ const { diagnostic, repair } = await diagnoseAndRepair(TEST_API_KEY, TEST_PLATFORM_URL);
257
+ const result = repair.results.find((r) => r.issueId === 'config-missing');
258
+ assert(result !== undefined, 'Should have repair result for config-missing');
259
+ assert(result?.success === true, 'Repair should succeed');
260
+ // Verify config file was created
261
+ const configPath = getClaudeConfigPath();
262
+ assert(existsSync(configPath), 'Config file should exist after repair');
263
+ log(` Repair result: ${result?.message}`);
264
+ log(` Action: ${result?.action}`);
265
+ });
266
+ await runTest('Verify config missing issue resolved', async () => {
267
+ // Config should have been created by previous repair
268
+ const report = await runDiagnostics();
269
+ const issue = report.issues.find((i) => i.id === 'config-missing');
270
+ assert(issue === undefined, 'Config-missing issue should be resolved');
271
+ log(` Config file verified to exist`);
272
+ });
273
+ }
274
+ /**
275
+ * Step 2: Test detection of invalid JSON config
276
+ */
277
+ async function testInvalidJsonDetection() {
278
+ logStep(2, 'Invalid JSON Config Detection & Repair');
279
+ await runTest('Detect invalid JSON config', async () => {
280
+ simulateInvalidJsonConfig();
281
+ const report = await runDiagnostics();
282
+ const issue = report.issues.find((i) => i.id === 'config-invalid-json');
283
+ assert(issue !== undefined, 'Should detect config-invalid-json issue');
284
+ assert(issue?.severity === 'critical', 'Invalid JSON should be critical');
285
+ assert(issue?.autoFixable === true, 'Invalid JSON should be auto-fixable');
286
+ log(` Detected: ${issue?.title}`);
287
+ log(` Description: ${issue?.description}`);
288
+ });
289
+ await runTest('Repair invalid JSON config', async () => {
290
+ simulateInvalidJsonConfig();
291
+ const { repair } = await diagnoseAndRepair(TEST_API_KEY, TEST_PLATFORM_URL);
292
+ const result = repair.results.find((r) => r.issueId === 'config-invalid-json');
293
+ assert(result !== undefined, 'Should have repair result for config-invalid-json');
294
+ assert(result?.success === true, 'Repair should succeed');
295
+ // Verify config file is now valid JSON
296
+ const configPath = getClaudeConfigPath();
297
+ const content = readFileSync(configPath, 'utf-8');
298
+ let parsed;
299
+ try {
300
+ parsed = JSON.parse(content);
301
+ }
302
+ catch {
303
+ throw new Error('Config file should be valid JSON after repair');
304
+ }
305
+ log(` Repair result: ${result?.message}`);
306
+ log(` Backup created: ${result?.action}`);
307
+ });
308
+ await runTest('Verify invalid JSON issue resolved', async () => {
309
+ const report = await runDiagnostics();
310
+ const issue = report.issues.find((i) => i.id === 'config-invalid-json');
311
+ assert(issue === undefined, 'Invalid JSON issue should be resolved');
312
+ log(` Config file is now valid JSON`);
313
+ });
314
+ }
315
+ /**
316
+ * Step 3: Test detection of missing MCP configuration
317
+ */
318
+ async function testMissingMcpConfigDetection() {
319
+ logStep(3, 'Missing MCP Configuration Detection & Repair');
320
+ await runTest('Detect missing MCP config', async () => {
321
+ simulateMissingMcpConfig();
322
+ const report = await runDiagnostics();
323
+ const issue = report.issues.find((i) => i.id === 'mcp-not-configured');
324
+ assert(issue !== undefined, 'Should detect mcp-not-configured issue');
325
+ assert(issue?.autoFixable === true, 'Missing MCP config should be auto-fixable');
326
+ log(` Detected: ${issue?.title}`);
327
+ log(` Requires user input: ${issueRequiresUserInput(issue?.id || '')}`);
328
+ });
329
+ await runTest('Repair missing MCP config (with API key)', async () => {
330
+ simulateMissingMcpConfig();
331
+ const { repair } = await diagnoseAndRepair(TEST_API_KEY, TEST_PLATFORM_URL);
332
+ const result = repair.results.find((r) => r.issueId === 'mcp-not-configured');
333
+ assert(result !== undefined, 'Should have repair result for mcp-not-configured');
334
+ assert(result?.success === true, 'Repair should succeed with API key provided');
335
+ // Verify MCP is now configured
336
+ assert(isTostudyMcpConfigured() === true, 'MCP should be configured after repair');
337
+ log(` Repair result: ${result?.message}`);
338
+ log(` MCP configured: true`);
339
+ });
340
+ await runTest('Repair fails without API key', async () => {
341
+ simulateMissingMcpConfig();
342
+ const { repair } = await diagnoseAndRepair(); // No API key
343
+ const result = repair.results.find((r) => r.issueId === 'mcp-not-configured');
344
+ assert(result !== undefined, 'Should have repair result');
345
+ assert(result?.success === false, 'Repair should fail without API key');
346
+ assert(repair.requiresUserInput.includes('mcp-not-configured'), 'Should require user input');
347
+ log(` Repair failed as expected (no API key)`);
348
+ log(` Requires user input: true`);
349
+ });
350
+ }
351
+ /**
352
+ * Step 4: Test detection of API key issues
353
+ */
354
+ async function testApiKeyIssuesDetection() {
355
+ logStep(4, 'API Key Issues Detection & Repair');
356
+ await runTest('Detect missing API key', async () => {
357
+ simulateMissingApiKey();
358
+ const report = await runDiagnostics();
359
+ const issue = report.issues.find((i) => i.id === 'api-key-missing');
360
+ assert(issue !== undefined, 'Should detect api-key-missing issue');
361
+ assert(issue?.severity === 'critical', 'Missing API key should be critical');
362
+ log(` Detected: ${issue?.title}`);
363
+ log(` Severity: ${issue?.severity}`);
364
+ });
365
+ await runTest('Detect invalid API key format', async () => {
366
+ simulateInvalidApiKeyFormat();
367
+ const report = await runDiagnostics();
368
+ const issue = report.issues.find((i) => i.id === 'api-key-invalid-format');
369
+ assert(issue !== undefined, 'Should detect api-key-invalid-format issue');
370
+ assert(issue?.autoFixable === true, 'Invalid format should be auto-fixable');
371
+ log(` Detected: ${issue?.title}`);
372
+ log(` Description: ${issue?.description}`);
373
+ });
374
+ await runTest('Repair invalid API key format', async () => {
375
+ simulateInvalidApiKeyFormat();
376
+ const { repair } = await diagnoseAndRepair();
377
+ const result = repair.results.find((r) => r.issueId === 'api-key-invalid-format');
378
+ assert(result !== undefined, 'Should have repair result');
379
+ assert(result?.success === true, 'Repair should succeed for format fix');
380
+ log(` Repair result: ${result?.message}`);
381
+ log(` Action: ${result?.action}`);
382
+ });
383
+ await runTest('Detect short API key', async () => {
384
+ simulateShortApiKey();
385
+ const report = await runDiagnostics();
386
+ const issue = report.issues.find((i) => i.id === 'api-key-too-short');
387
+ assert(issue !== undefined, 'Should detect api-key-too-short issue');
388
+ assert(issue?.severity === 'warning', 'Short API key should be a warning');
389
+ log(` Detected: ${issue?.title}`);
390
+ log(` Description: ${issue?.description}`);
391
+ });
392
+ }
393
+ /**
394
+ * Step 5: Test detection of server URL issues
395
+ */
396
+ async function testServerUrlIssuesDetection() {
397
+ logStep(5, 'Server URL Issues Detection & Repair');
398
+ await runTest('Detect missing server URL', async () => {
399
+ simulateMissingServerUrl();
400
+ const report = await runDiagnostics();
401
+ const issue = report.issues.find((i) => i.id === 'server-url-missing');
402
+ assert(issue !== undefined, 'Should detect server-url-missing issue');
403
+ assert(issue?.severity === 'critical', 'Missing URL should be critical');
404
+ log(` Detected: ${issue?.title}`);
405
+ log(` Severity: ${issue?.severity}`);
406
+ });
407
+ await runTest('Repair missing server URL', async () => {
408
+ simulateMissingServerUrl();
409
+ const { repair } = await diagnoseAndRepair(TEST_API_KEY, TEST_PLATFORM_URL);
410
+ const result = repair.results.find((r) => r.issueId === 'server-url-missing');
411
+ assert(result !== undefined, 'Should have repair result');
412
+ assert(result?.success === true, 'Repair should succeed');
413
+ const mcpConfig = getTostudyMcpConfig();
414
+ assert(mcpConfig?.url !== undefined, 'URL should be set after repair');
415
+ log(` Repair result: ${result?.message}`);
416
+ log(` Action: ${result?.action}`);
417
+ });
418
+ await runTest('Detect missing SSE path in URL', async () => {
419
+ simulateInvalidServerUrlPath();
420
+ const report = await runDiagnostics();
421
+ const issue = report.issues.find((i) => i.id === 'server-url-missing-sse-path');
422
+ assert(issue !== undefined, 'Should detect server-url-missing-sse-path issue');
423
+ assert(issue?.autoFixable === true, 'Missing path should be auto-fixable');
424
+ log(` Detected: ${issue?.title}`);
425
+ log(` Description: ${issue?.description}`);
426
+ });
427
+ await runTest('Repair missing SSE path', async () => {
428
+ simulateInvalidServerUrlPath();
429
+ const { repair } = await diagnoseAndRepair(TEST_API_KEY, TEST_PLATFORM_URL);
430
+ const result = repair.results.find((r) => r.issueId === 'server-url-missing-sse-path');
431
+ assert(result !== undefined, 'Should have repair result');
432
+ assert(result?.success === true, 'Repair should succeed');
433
+ const mcpConfig = getTostudyMcpConfig();
434
+ assert(mcpConfig?.url?.endsWith('/mcp/sse') === true, 'URL should end with /mcp/sse after repair');
435
+ log(` Repair result: ${result?.message}`);
436
+ log(` Final URL: ${mcpConfig?.url}`);
437
+ });
438
+ }
439
+ /**
440
+ * Step 6: Test detection of duplicate servers
441
+ */
442
+ async function testDuplicateServersDetection() {
443
+ logStep(6, 'Duplicate Servers Detection & Repair');
444
+ await runTest('Detect duplicate servers', async () => {
445
+ simulateDuplicateServers();
446
+ const report = await runDiagnostics();
447
+ const issue = report.issues.find((i) => i.id === 'duplicate-servers');
448
+ assert(issue !== undefined, 'Should detect duplicate-servers issue');
449
+ assert(issue?.autoFixable === true, 'Duplicate servers should be auto-fixable');
450
+ log(` Detected: ${issue?.title}`);
451
+ log(` Description: ${issue?.description}`);
452
+ });
453
+ await runTest('Repair duplicate servers', async () => {
454
+ simulateDuplicateServers();
455
+ const { repair } = await diagnoseAndRepair(TEST_API_KEY, TEST_PLATFORM_URL);
456
+ const result = repair.results.find((r) => r.issueId === 'duplicate-servers');
457
+ assert(result !== undefined, 'Should have repair result');
458
+ assert(result?.success === true, 'Repair should succeed');
459
+ // Verify only one server remains
460
+ const config = readClaudeConfig();
461
+ const anaServers = Object.keys(config.mcpServers || {}).filter((key) => key.toLowerCase().includes('ana') ||
462
+ key.toLowerCase().includes('catalyst'));
463
+ assert(anaServers.length === 1, 'Should have only one Catalyst server after repair');
464
+ assert(anaServers[0] === 'tostudy', 'Should keep tostudy as the primary server');
465
+ log(` Repair result: ${result?.message}`);
466
+ log(` Action: ${result?.action}`);
467
+ });
468
+ }
469
+ /**
470
+ * Step 7: Test port conflict detection
471
+ */
472
+ async function testPortConflictDetection() {
473
+ logStep(7, 'Port Conflict Detection');
474
+ await runTest('Detect port conflict', async () => {
475
+ // Try to start port blocker - might fail if port is already in use
476
+ let server = null;
477
+ try {
478
+ server = await startPortBlocker(DEFAULT_MCP_PORT);
479
+ }
480
+ catch {
481
+ log(` Port ${DEFAULT_MCP_PORT} already in use - testing with existing blocker`);
482
+ }
483
+ try {
484
+ // Set up a valid config first
485
+ addTostudyMcpServer(TEST_API_KEY, TEST_PLATFORM_URL);
486
+ const report = await runDiagnostics();
487
+ const issue = report.issues.find((i) => i.id === 'port-conflict');
488
+ // Port conflict is detected if port is not available
489
+ if (issue) {
490
+ assert(issue.severity === 'warning', 'Port conflict should be a warning');
491
+ assert(issue.autoFixable === false, 'Port conflict is not auto-fixable');
492
+ log(` Detected: ${issue.title}`);
493
+ log(` Description: ${issue.description}`);
494
+ }
495
+ else {
496
+ log(` No port conflict detected (port ${DEFAULT_MCP_PORT} is available)`);
497
+ }
498
+ }
499
+ finally {
500
+ if (server) {
501
+ server.close();
502
+ }
503
+ stopPortBlocker();
504
+ }
505
+ });
506
+ }
507
+ /**
508
+ * Step 8: Complete diagnostic and repair flow
509
+ */
510
+ async function testCompleteFlow() {
511
+ logStep(8, 'Complete Diagnostic & Repair Flow Simulation');
512
+ await runTest('Full flow: detect multiple issues', async () => {
513
+ // Create a config with multiple issues
514
+ const configPath = getClaudeConfigPath();
515
+ const configDir = dirname(configPath);
516
+ if (!existsSync(configDir)) {
517
+ mkdirSync(configDir, { recursive: true });
518
+ }
519
+ // Config with missing API key and wrong URL path
520
+ const problematicConfig = {
521
+ mcpServers: {
522
+ 'tostudy': {
523
+ type: 'sse',
524
+ url: 'https://tostudy.com:3701', // Missing /mcp/sse
525
+ headers: {}, // Missing Authorization
526
+ },
527
+ 'catalyst-duplicate': {
528
+ type: 'sse',
529
+ url: 'https://tostudy.com:3701/mcp/sse',
530
+ headers: {
531
+ Authorization: 'Bearer test-key',
532
+ },
533
+ },
534
+ },
535
+ };
536
+ writeFileSync(configPath, JSON.stringify(problematicConfig, null, 2), 'utf-8');
537
+ const report = await runDiagnostics();
538
+ log(` Total issues found: ${report.issues.length}`);
539
+ for (const issue of report.issues) {
540
+ const icon = issue.severity === 'critical' ? '🔴' :
541
+ issue.severity === 'warning' ? '🟡' : '🔵';
542
+ log(` ${icon} ${issue.id}: ${issue.title}`);
543
+ }
544
+ assert(report.issues.length >= 2, 'Should detect multiple issues');
545
+ });
546
+ await runTest('Full flow: repair all issues', async () => {
547
+ const { diagnostic, repair } = await diagnoseAndRepair(TEST_API_KEY, TEST_PLATFORM_URL);
548
+ log(` Repairs attempted: ${repair.repairsAttempted}`);
549
+ log(` Repairs succeeded: ${repair.repairsSucceeded}`);
550
+ log(` Repairs failed: ${repair.repairsFailed}`);
551
+ for (const result of repair.results) {
552
+ const icon = result.success ? '✓' : '✗';
553
+ log(` ${icon} ${result.issueId}: ${result.message}`);
554
+ }
555
+ assert(repair.repairsSucceeded > 0, 'Should have at least one successful repair');
556
+ });
557
+ await runTest('Full flow: verify issues resolved', async () => {
558
+ const report = await runDiagnostics();
559
+ const criticalIssues = report.issues.filter((i) => i.severity === 'critical');
560
+ log(` Remaining issues: ${report.issues.length}`);
561
+ log(` Critical issues: ${criticalIssues.length}`);
562
+ log(` Overall passed: ${report.passed}`);
563
+ assert(report.passed === true, 'Report should pass after repairs');
564
+ assert(criticalIssues.length === 0, 'Should have no critical issues');
565
+ });
566
+ await runTest('Verify final configuration state', async () => {
567
+ assert(isTostudyMcpConfigured() === true, 'MCP should be configured');
568
+ const mcpConfig = getTostudyMcpConfig();
569
+ assert(mcpConfig !== null, 'MCP config should exist');
570
+ assert(mcpConfig?.type === 'sse', 'Type should be sse');
571
+ assert(mcpConfig?.url?.endsWith('/mcp/sse') === true, 'URL should end with /mcp/sse');
572
+ assert(mcpConfig?.headers?.['Authorization']?.startsWith('Bearer ') === true, 'Should have Bearer token');
573
+ log(` MCP configured: true`);
574
+ log(` Type: ${mcpConfig?.type}`);
575
+ log(` URL: ${mcpConfig?.url}`);
576
+ log(` Has auth header: true`);
577
+ });
578
+ }
579
+ /**
580
+ * Step 9: Test issue helper functions
581
+ */
582
+ async function testHelperFunctions() {
583
+ logStep(9, 'Helper Functions Verification');
584
+ await runTest('issueRequiresUserInput identifies correct issues', () => {
585
+ assert(issueRequiresUserInput('mcp-not-configured') === true, 'mcp-not-configured requires input');
586
+ assert(issueRequiresUserInput('api-key-missing') === true, 'api-key-missing requires input');
587
+ assert(issueRequiresUserInput('api-key-too-short') === true, 'api-key-too-short requires input');
588
+ assert(issueRequiresUserInput('auth-failed') === true, 'auth-failed requires input');
589
+ assert(issueRequiresUserInput('config-missing') === false, 'config-missing does not require input');
590
+ assert(issueRequiresUserInput('duplicate-servers') === false, 'duplicate-servers does not require input');
591
+ log(` All input requirement checks passed`);
592
+ });
593
+ await runTest('repairIssue handles unknown issues gracefully', () => {
594
+ const unknownIssue = {
595
+ id: 'unknown-issue-xyz',
596
+ severity: 'warning',
597
+ title: 'Unknown Issue',
598
+ description: 'Test unknown issue',
599
+ suggestion: 'Test suggestion',
600
+ autoFixable: true,
601
+ };
602
+ const result = repairIssue(unknownIssue);
603
+ assert(result.success === false, 'Unknown issue repair should fail');
604
+ assert(result.message.includes('Sem funcao de reparo'), 'Should indicate no repair function');
605
+ log(` Unknown issue handled: ${result.message}`);
606
+ });
607
+ await runTest('DiagnosticReport structure is correct', async () => {
608
+ addTostudyMcpServer(TEST_API_KEY, TEST_PLATFORM_URL);
609
+ const report = await runDiagnostics();
610
+ assert(typeof report.timestamp === 'string', 'Should have timestamp');
611
+ assert(typeof report.claudeInstalled === 'boolean', 'Should have claudeInstalled');
612
+ assert(typeof report.mcpConfigured === 'boolean', 'Should have mcpConfigured');
613
+ assert(Array.isArray(report.issues), 'Should have issues array');
614
+ assert(typeof report.passed === 'boolean', 'Should have passed status');
615
+ for (const issue of report.issues) {
616
+ assert(typeof issue.id === 'string', 'Issue should have id');
617
+ assert(typeof issue.severity === 'string', 'Issue should have severity');
618
+ assert(typeof issue.title === 'string', 'Issue should have title');
619
+ assert(typeof issue.description === 'string', 'Issue should have description');
620
+ assert(typeof issue.suggestion === 'string', 'Issue should have suggestion');
621
+ assert(typeof issue.autoFixable === 'boolean', 'Issue should have autoFixable');
622
+ }
623
+ log(` Report structure validated`);
624
+ log(` Timestamp: ${report.timestamp}`);
625
+ log(` Issues: ${report.issues.length}`);
626
+ });
627
+ await runTest('RepairReport structure is correct', async () => {
628
+ const diagnostic = await runDiagnostics();
629
+ const repair = repairAllIssues(diagnostic, TEST_API_KEY, TEST_PLATFORM_URL);
630
+ assert(typeof repair.timestamp === 'string', 'Should have timestamp');
631
+ assert(typeof repair.repairsAttempted === 'number', 'Should have repairsAttempted');
632
+ assert(typeof repair.repairsSucceeded === 'number', 'Should have repairsSucceeded');
633
+ assert(typeof repair.repairsFailed === 'number', 'Should have repairsFailed');
634
+ assert(Array.isArray(repair.results), 'Should have results array');
635
+ assert(Array.isArray(repair.requiresUserInput), 'Should have requiresUserInput array');
636
+ for (const result of repair.results) {
637
+ assert(typeof result.issueId === 'string', 'Result should have issueId');
638
+ assert(typeof result.success === 'boolean', 'Result should have success');
639
+ assert(typeof result.message === 'string', 'Result should have message');
640
+ }
641
+ log(` Repair report structure validated`);
642
+ log(` Timestamp: ${repair.timestamp}`);
643
+ log(` Results: ${repair.results.length}`);
644
+ });
645
+ }
646
+ // ==================================================
647
+ // Summary and Main Runner
648
+ // ==================================================
649
+ function printSummary() {
650
+ log('\n' + '═'.repeat(60));
651
+ log('TEST SUMMARY: Diagnostic & Repair E2E Flow');
652
+ log('═'.repeat(60));
653
+ const passed = results.filter((r) => r.passed).length;
654
+ const failed = results.filter((r) => !r.passed).length;
655
+ const total = results.length;
656
+ log(`\nTotal: ${total} | Passed: ${passed} | Failed: ${failed}\n`);
657
+ if (failed > 0) {
658
+ log('FAILED TESTS:');
659
+ for (const result of results.filter((r) => !r.passed)) {
660
+ log(` ✗ ${result.name}`);
661
+ log(` ${result.message}`);
662
+ }
663
+ log('');
664
+ }
665
+ const totalTime = results.reduce((sum, r) => sum + r.duration, 0);
666
+ log(`Total time: ${totalTime}ms`);
667
+ log('');
668
+ if (failed === 0) {
669
+ log('\x1b[32m✓ All E2E diagnostic and repair tests passed!\x1b[0m');
670
+ }
671
+ else {
672
+ log(`\x1b[31m✗ ${failed} test(s) failed\x1b[0m`);
673
+ process.exitCode = 1;
674
+ }
675
+ }
676
+ async function main() {
677
+ log('═'.repeat(60));
678
+ log('E2E TEST: Diagnostic and Repair Flow');
679
+ log('═'.repeat(60));
680
+ log(`Platform: ${platform()}`);
681
+ log(`Node: ${process.version}`);
682
+ log(`Time: ${new Date().toISOString()}`);
683
+ try {
684
+ // Setup
685
+ log('\n--- Setup ---');
686
+ backupConfig();
687
+ createTempDir();
688
+ // Run all test steps
689
+ await testMissingConfigDetection();
690
+ await testInvalidJsonDetection();
691
+ await testMissingMcpConfigDetection();
692
+ await testApiKeyIssuesDetection();
693
+ await testServerUrlIssuesDetection();
694
+ await testDuplicateServersDetection();
695
+ await testPortConflictDetection();
696
+ await testCompleteFlow();
697
+ await testHelperFunctions();
698
+ // Cleanup
699
+ log('\n--- Cleanup ---');
700
+ restoreConfig();
701
+ cleanupTempDir();
702
+ stopPortBlocker();
703
+ // Print summary
704
+ printSummary();
705
+ }
706
+ catch (error) {
707
+ log(`\n\x1b[31mFATAL ERROR: ${error instanceof Error ? error.message : String(error)}\x1b[0m`);
708
+ restoreConfig();
709
+ cleanupTempDir();
710
+ stopPortBlocker();
711
+ process.exitCode = 1;
712
+ }
713
+ }
714
+ // Run if executed directly
715
+ if (import.meta.url.endsWith(process.argv[1]?.replace(/^file:\/\//, '') || '')) {
716
+ main();
717
+ }
718
+ // Export for use as a module
719
+ export { testMissingConfigDetection, testInvalidJsonDetection, testMissingMcpConfigDetection, testApiKeyIssuesDetection, testServerUrlIssuesDetection, testDuplicateServersDetection, testPortConflictDetection, testCompleteFlow, testHelperFunctions, main as runDiagnosticRepairE2ETests, };
720
+ //# sourceMappingURL=e2e-diagnostic-repair-flow.test.js.map