Skip to content

Commit 05b279a

Browse files
authored
Merge pull request #768 from olaservo/cli-header-followups
Add CLI header integration tests and update test scripts
2 parents eaf16a7 + d22a8a6 commit 05b279a

File tree

4 files changed

+258
-4
lines changed

4 files changed

+258
-4
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ client/results.json
1717
client/test-results/
1818
client/e2e/test-results/
1919
mcp.json
20+
.claude/settings.local.json

cli/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@
1717
"scripts": {
1818
"build": "tsc",
1919
"postbuild": "node scripts/make-executable.js",
20-
"test": "node scripts/cli-tests.js && node scripts/cli-tool-tests.js",
20+
"test": "node scripts/cli-tests.js && node scripts/cli-tool-tests.js && node scripts/cli-header-tests.js",
2121
"test:cli": "node scripts/cli-tests.js",
22-
"test:cli-tools": "node scripts/cli-tool-tests.js"
22+
"test:cli-tools": "node scripts/cli-tool-tests.js",
23+
"test:cli-headers": "node scripts/cli-header-tests.js"
2324
},
2425
"devDependencies": {},
2526
"dependencies": {

cli/scripts/cli-header-tests.js

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Integration tests for header functionality
5+
* Tests the CLI header parsing end-to-end
6+
*/
7+
8+
import { spawn } from "node:child_process";
9+
import { resolve, dirname } from "node:path";
10+
import { fileURLToPath } from "node:url";
11+
12+
const __dirname = dirname(fileURLToPath(import.meta.url));
13+
const CLI_PATH = resolve(__dirname, "..", "build", "index.js");
14+
15+
// ANSI colors for output
16+
const colors = {
17+
GREEN: "\x1b[32m",
18+
RED: "\x1b[31m",
19+
YELLOW: "\x1b[33m",
20+
BLUE: "\x1b[34m",
21+
NC: "\x1b[0m", // No Color
22+
};
23+
24+
let testsPassed = 0;
25+
let testsFailed = 0;
26+
27+
/**
28+
* Run a CLI test with given arguments and check for expected behavior
29+
*/
30+
function runHeaderTest(
31+
testName,
32+
args,
33+
expectSuccess = false,
34+
expectedInOutput = null,
35+
) {
36+
return new Promise((resolve) => {
37+
console.log(`\n${colors.BLUE}Testing: ${testName}${colors.NC}`);
38+
console.log(
39+
`${colors.BLUE}Command: node ${CLI_PATH} ${args.join(" ")}${colors.NC}`,
40+
);
41+
42+
const child = spawn("node", [CLI_PATH, ...args], {
43+
stdio: ["pipe", "pipe", "pipe"],
44+
timeout: 10000,
45+
});
46+
47+
let stdout = "";
48+
let stderr = "";
49+
50+
child.stdout.on("data", (data) => {
51+
stdout += data.toString();
52+
});
53+
54+
child.stderr.on("data", (data) => {
55+
stderr += data.toString();
56+
});
57+
58+
child.on("close", (code) => {
59+
const output = stdout + stderr;
60+
let passed = true;
61+
let reason = "";
62+
63+
// Check exit code expectation
64+
if (expectSuccess && code !== 0) {
65+
passed = false;
66+
reason = `Expected success (exit code 0) but got ${code}`;
67+
} else if (!expectSuccess && code === 0) {
68+
passed = false;
69+
reason = `Expected failure (non-zero exit code) but got success`;
70+
}
71+
72+
// Check expected output
73+
if (passed && expectedInOutput && !output.includes(expectedInOutput)) {
74+
passed = false;
75+
reason = `Expected output to contain "${expectedInOutput}"`;
76+
}
77+
78+
if (passed) {
79+
console.log(`${colors.GREEN}PASS: ${testName}${colors.NC}`);
80+
testsPassed++;
81+
} else {
82+
console.log(`${colors.RED}FAIL: ${testName}${colors.NC}`);
83+
console.log(`${colors.RED}Reason: ${reason}${colors.NC}`);
84+
console.log(`${colors.RED}Exit code: ${code}${colors.NC}`);
85+
console.log(`${colors.RED}Output: ${output}${colors.NC}`);
86+
testsFailed++;
87+
}
88+
89+
resolve();
90+
});
91+
92+
child.on("error", (error) => {
93+
console.log(
94+
`${colors.RED}ERROR: ${testName} - ${error.message}${colors.NC}`,
95+
);
96+
testsFailed++;
97+
resolve();
98+
});
99+
});
100+
}
101+
102+
async function runHeaderIntegrationTests() {
103+
console.log(
104+
`${colors.YELLOW}=== MCP Inspector CLI Header Integration Tests ===${colors.NC}`,
105+
);
106+
console.log(
107+
`${colors.BLUE}Testing header parsing and validation${colors.NC}`,
108+
);
109+
110+
// Test 1: Valid header format should parse successfully (connection will fail)
111+
await runHeaderTest(
112+
"Valid single header",
113+
[
114+
"https://example.com",
115+
"--method",
116+
"tools/list",
117+
"--transport",
118+
"http",
119+
"--header",
120+
"Authorization: Bearer token123",
121+
],
122+
false,
123+
);
124+
125+
// Test 2: Multiple headers should parse successfully
126+
await runHeaderTest(
127+
"Multiple headers",
128+
[
129+
"https://example.com",
130+
"--method",
131+
"tools/list",
132+
"--transport",
133+
"http",
134+
"--header",
135+
"Authorization: Bearer token123",
136+
"--header",
137+
"X-API-Key: secret123",
138+
],
139+
false,
140+
);
141+
142+
// Test 3: Invalid header format - no colon
143+
await runHeaderTest(
144+
"Invalid header format - no colon",
145+
[
146+
"https://example.com",
147+
"--method",
148+
"tools/list",
149+
"--transport",
150+
"http",
151+
"--header",
152+
"InvalidHeader",
153+
],
154+
false,
155+
"Invalid header format",
156+
);
157+
158+
// Test 4: Invalid header format - empty name
159+
await runHeaderTest(
160+
"Invalid header format - empty name",
161+
[
162+
"https://example.com",
163+
"--method",
164+
"tools/list",
165+
"--transport",
166+
"http",
167+
"--header",
168+
": value",
169+
],
170+
false,
171+
"Invalid header format",
172+
);
173+
174+
// Test 5: Invalid header format - empty value
175+
await runHeaderTest(
176+
"Invalid header format - empty value",
177+
[
178+
"https://example.com",
179+
"--method",
180+
"tools/list",
181+
"--transport",
182+
"http",
183+
"--header",
184+
"Header:",
185+
],
186+
false,
187+
"Invalid header format",
188+
);
189+
190+
// Test 6: Header with colons in value
191+
await runHeaderTest(
192+
"Header with colons in value",
193+
[
194+
"https://example.com",
195+
"--method",
196+
"tools/list",
197+
"--transport",
198+
"http",
199+
"--header",
200+
"X-Time: 2023:12:25:10:30:45",
201+
],
202+
false,
203+
);
204+
205+
// Test 7: Whitespace handling
206+
await runHeaderTest(
207+
"Whitespace handling in headers",
208+
[
209+
"https://example.com",
210+
"--method",
211+
"tools/list",
212+
"--transport",
213+
"http",
214+
"--header",
215+
" X-Header : value with spaces ",
216+
],
217+
false,
218+
);
219+
220+
console.log(`\n${colors.YELLOW}=== Test Results ===${colors.NC}`);
221+
console.log(`${colors.GREEN}Tests passed: ${testsPassed}${colors.NC}`);
222+
console.log(`${colors.RED}Tests failed: ${testsFailed}${colors.NC}`);
223+
224+
if (testsFailed === 0) {
225+
console.log(
226+
`${colors.GREEN}All header integration tests passed!${colors.NC}`,
227+
);
228+
process.exit(0);
229+
} else {
230+
console.log(
231+
`${colors.RED}Some header integration tests failed.${colors.NC}`,
232+
);
233+
process.exit(1);
234+
}
235+
}
236+
237+
// Handle graceful shutdown
238+
process.on("SIGINT", () => {
239+
console.log(`\n${colors.YELLOW}Test interrupted by user${colors.NC}`);
240+
process.exit(1);
241+
});
242+
243+
process.on("SIGTERM", () => {
244+
console.log(`\n${colors.YELLOW}Test terminated${colors.NC}`);
245+
process.exit(1);
246+
});
247+
248+
// Run the tests
249+
runHeaderIntegrationTests().catch((error) => {
250+
console.error(`${colors.RED}Test runner error: ${error.message}${colors.NC}`);
251+
process.exit(1);
252+
});

cli/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,8 +352,8 @@ async function main(): Promise<void> {
352352
const args = parseArgs();
353353
await callMethod(args);
354354

355-
// Let Node.js naturally exit instead of force-exiting
356-
// process.exit(0) was causing stdout truncation
355+
// Explicitly exit to ensure process terminates in CI
356+
process.exit(0);
357357
} catch (error) {
358358
handleError(error);
359359
}

0 commit comments

Comments
 (0)