Skip to content

Commit 62402b4

Browse files
test: add tests for proxy auth header in config endpoint
- Added tests for App.tsx config endpoint to verify X-MCP-Proxy-Auth header usage - Added tests for getMCPProxyAuthToken utility function - Tests cover: - Sending correct proxy auth header when token is configured - Not sending header when token is empty - Using custom header names (for future flexibility) - Handling config endpoint errors gracefully - All tests passing 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 37cff10 commit 62402b4

File tree

2 files changed

+312
-0
lines changed

2 files changed

+312
-0
lines changed
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
import React from "react";
2+
import { render, waitFor } from "@testing-library/react";
3+
import App from "../App";
4+
import { DEFAULT_INSPECTOR_CONFIG } from "../lib/constants";
5+
6+
// Mock auth dependencies first
7+
jest.mock("@modelcontextprotocol/sdk/client/auth.js", () => ({
8+
auth: jest.fn(),
9+
}));
10+
11+
jest.mock("../lib/oauth-state-machine", () => ({
12+
OAuthStateMachine: jest.fn(),
13+
}));
14+
15+
jest.mock("../lib/auth", () => ({
16+
InspectorOAuthClientProvider: jest.fn().mockImplementation(() => ({
17+
tokens: jest.fn().mockResolvedValue(null),
18+
clear: jest.fn(),
19+
})),
20+
DebugInspectorOAuthClientProvider: jest.fn(),
21+
}));
22+
23+
// Mock the config utils
24+
jest.mock("../utils/configUtils", () => ({
25+
...jest.requireActual("../utils/configUtils"),
26+
getMCPProxyAddress: jest.fn(() => "http://localhost:6277"),
27+
getMCPProxyAuthToken: jest.fn((config) => ({
28+
token: config.MCP_PROXY_AUTH_TOKEN.value,
29+
header: "X-MCP-Proxy-Auth",
30+
})),
31+
getInitialTransportType: jest.fn(() => "stdio"),
32+
getInitialSseUrl: jest.fn(() => "http://localhost:3001/sse"),
33+
getInitialCommand: jest.fn(() => "mcp-server-everything"),
34+
getInitialArgs: jest.fn(() => ""),
35+
initializeInspectorConfig: jest.fn(() => DEFAULT_INSPECTOR_CONFIG),
36+
saveInspectorConfig: jest.fn(),
37+
}));
38+
39+
// Mock other dependencies
40+
jest.mock("../lib/hooks/useConnection", () => ({
41+
useConnection: () => ({
42+
connectionStatus: "disconnected",
43+
serverCapabilities: null,
44+
mcpClient: null,
45+
requestHistory: [],
46+
makeRequest: jest.fn(),
47+
sendNotification: jest.fn(),
48+
handleCompletion: jest.fn(),
49+
completionsSupported: false,
50+
connect: jest.fn(),
51+
disconnect: jest.fn(),
52+
}),
53+
}));
54+
55+
jest.mock("../lib/hooks/useDraggablePane", () => ({
56+
useDraggablePane: () => ({
57+
height: 300,
58+
handleDragStart: jest.fn(),
59+
}),
60+
useDraggableSidebar: () => ({
61+
width: 320,
62+
isDragging: false,
63+
handleDragStart: jest.fn(),
64+
}),
65+
}));
66+
67+
jest.mock("../components/Sidebar", () => ({
68+
__esModule: true,
69+
default: () => <div>Sidebar</div>,
70+
}));
71+
72+
// Mock fetch
73+
global.fetch = jest.fn();
74+
75+
describe("App - Config Endpoint", () => {
76+
beforeEach(() => {
77+
jest.clearAllMocks();
78+
(global.fetch as jest.Mock).mockResolvedValue({
79+
json: () =>
80+
Promise.resolve({
81+
defaultEnvironment: { TEST_ENV: "test" },
82+
defaultCommand: "test-command",
83+
defaultArgs: "test-args",
84+
}),
85+
});
86+
});
87+
88+
afterEach(() => {
89+
jest.clearAllMocks();
90+
91+
// Reset getMCPProxyAuthToken to default behavior
92+
const { getMCPProxyAuthToken } = require("../utils/configUtils");
93+
getMCPProxyAuthToken.mockImplementation((config) => ({
94+
token: config.MCP_PROXY_AUTH_TOKEN.value,
95+
header: "X-MCP-Proxy-Auth",
96+
}));
97+
});
98+
99+
test("sends X-MCP-Proxy-Auth header when fetching config with proxy auth token", async () => {
100+
const mockConfig = {
101+
...DEFAULT_INSPECTOR_CONFIG,
102+
MCP_PROXY_AUTH_TOKEN: {
103+
...DEFAULT_INSPECTOR_CONFIG.MCP_PROXY_AUTH_TOKEN,
104+
value: "test-proxy-token",
105+
},
106+
};
107+
108+
// Mock initializeInspectorConfig to return our test config
109+
const { initializeInspectorConfig } = require("../utils/configUtils");
110+
initializeInspectorConfig.mockReturnValue(mockConfig);
111+
112+
render(<App />);
113+
114+
await waitFor(() => {
115+
expect(global.fetch).toHaveBeenCalledWith(
116+
"http://localhost:6277/config",
117+
{
118+
headers: {
119+
"X-MCP-Proxy-Auth": "Bearer test-proxy-token",
120+
},
121+
}
122+
);
123+
});
124+
});
125+
126+
test("does not send auth header when proxy auth token is empty", async () => {
127+
const mockConfig = {
128+
...DEFAULT_INSPECTOR_CONFIG,
129+
MCP_PROXY_AUTH_TOKEN: {
130+
...DEFAULT_INSPECTOR_CONFIG.MCP_PROXY_AUTH_TOKEN,
131+
value: "",
132+
},
133+
};
134+
135+
// Mock initializeInspectorConfig to return our test config
136+
const { initializeInspectorConfig } = require("../utils/configUtils");
137+
initializeInspectorConfig.mockReturnValue(mockConfig);
138+
139+
render(<App />);
140+
141+
await waitFor(() => {
142+
expect(global.fetch).toHaveBeenCalledWith(
143+
"http://localhost:6277/config",
144+
{
145+
headers: {},
146+
}
147+
);
148+
});
149+
});
150+
151+
test("uses custom header name if getMCPProxyAuthToken returns different header", async () => {
152+
const mockConfig = {
153+
...DEFAULT_INSPECTOR_CONFIG,
154+
MCP_PROXY_AUTH_TOKEN: {
155+
...DEFAULT_INSPECTOR_CONFIG.MCP_PROXY_AUTH_TOKEN,
156+
value: "test-proxy-token",
157+
},
158+
};
159+
160+
// Mock to return a custom header name
161+
const { getMCPProxyAuthToken, initializeInspectorConfig } = require("../utils/configUtils");
162+
getMCPProxyAuthToken.mockReturnValue({
163+
token: "test-proxy-token",
164+
header: "X-Custom-Auth",
165+
});
166+
initializeInspectorConfig.mockReturnValue(mockConfig);
167+
168+
render(<App />);
169+
170+
await waitFor(() => {
171+
expect(global.fetch).toHaveBeenCalledWith(
172+
"http://localhost:6277/config",
173+
{
174+
headers: {
175+
"X-Custom-Auth": "Bearer test-proxy-token",
176+
},
177+
}
178+
);
179+
});
180+
});
181+
182+
test("config endpoint response updates app state", async () => {
183+
const mockConfig = {
184+
...DEFAULT_INSPECTOR_CONFIG,
185+
MCP_PROXY_AUTH_TOKEN: {
186+
...DEFAULT_INSPECTOR_CONFIG.MCP_PROXY_AUTH_TOKEN,
187+
value: "test-proxy-token",
188+
},
189+
};
190+
191+
const { initializeInspectorConfig } = require("../utils/configUtils");
192+
initializeInspectorConfig.mockReturnValue(mockConfig);
193+
194+
const { container } = render(<App />);
195+
196+
await waitFor(() => {
197+
expect(global.fetch).toHaveBeenCalledTimes(1);
198+
});
199+
200+
// Verify the fetch was called with correct parameters
201+
expect(global.fetch).toHaveBeenCalledWith(
202+
"http://localhost:6277/config",
203+
expect.objectContaining({
204+
headers: expect.objectContaining({
205+
"X-MCP-Proxy-Auth": "Bearer test-proxy-token",
206+
}),
207+
})
208+
);
209+
});
210+
211+
test("handles config endpoint errors gracefully", async () => {
212+
const mockConfig = {
213+
...DEFAULT_INSPECTOR_CONFIG,
214+
MCP_PROXY_AUTH_TOKEN: {
215+
...DEFAULT_INSPECTOR_CONFIG.MCP_PROXY_AUTH_TOKEN,
216+
value: "test-proxy-token",
217+
},
218+
};
219+
220+
const { initializeInspectorConfig } = require("../utils/configUtils");
221+
initializeInspectorConfig.mockReturnValue(mockConfig);
222+
223+
// Mock fetch to reject
224+
(global.fetch as jest.Mock).mockRejectedValue(new Error("Network error"));
225+
226+
// Spy on console.error
227+
const consoleErrorSpy = jest.spyOn(console, "error").mockImplementation();
228+
229+
render(<App />);
230+
231+
await waitFor(() => {
232+
expect(consoleErrorSpy).toHaveBeenCalledWith(
233+
"Error fetching default environment:",
234+
expect.any(Error)
235+
);
236+
});
237+
238+
consoleErrorSpy.mockRestore();
239+
});
240+
});
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { getMCPProxyAuthToken } from "../configUtils";
2+
import { DEFAULT_INSPECTOR_CONFIG } from "../../lib/constants";
3+
import { InspectorConfig } from "../../lib/configurationTypes";
4+
5+
describe("configUtils", () => {
6+
describe("getMCPProxyAuthToken", () => {
7+
test("returns token and default header name", () => {
8+
const config: InspectorConfig = {
9+
...DEFAULT_INSPECTOR_CONFIG,
10+
MCP_PROXY_AUTH_TOKEN: {
11+
...DEFAULT_INSPECTOR_CONFIG.MCP_PROXY_AUTH_TOKEN,
12+
value: "test-token-123",
13+
},
14+
};
15+
16+
const result = getMCPProxyAuthToken(config);
17+
18+
expect(result).toEqual({
19+
token: "test-token-123",
20+
header: "X-MCP-Proxy-Auth",
21+
});
22+
});
23+
24+
test("returns empty token when not configured", () => {
25+
const config: InspectorConfig = {
26+
...DEFAULT_INSPECTOR_CONFIG,
27+
MCP_PROXY_AUTH_TOKEN: {
28+
...DEFAULT_INSPECTOR_CONFIG.MCP_PROXY_AUTH_TOKEN,
29+
value: "",
30+
},
31+
};
32+
33+
const result = getMCPProxyAuthToken(config);
34+
35+
expect(result).toEqual({
36+
token: "",
37+
header: "X-MCP-Proxy-Auth",
38+
});
39+
});
40+
41+
test("always returns X-MCP-Proxy-Auth as header name", () => {
42+
const config: InspectorConfig = {
43+
...DEFAULT_INSPECTOR_CONFIG,
44+
MCP_PROXY_AUTH_TOKEN: {
45+
...DEFAULT_INSPECTOR_CONFIG.MCP_PROXY_AUTH_TOKEN,
46+
value: "any-token",
47+
},
48+
};
49+
50+
const result = getMCPProxyAuthToken(config);
51+
52+
expect(result.header).toBe("X-MCP-Proxy-Auth");
53+
});
54+
55+
test("handles null/undefined value gracefully", () => {
56+
const config: InspectorConfig = {
57+
...DEFAULT_INSPECTOR_CONFIG,
58+
MCP_PROXY_AUTH_TOKEN: {
59+
...DEFAULT_INSPECTOR_CONFIG.MCP_PROXY_AUTH_TOKEN,
60+
value: null as any,
61+
},
62+
};
63+
64+
const result = getMCPProxyAuthToken(config);
65+
66+
expect(result).toEqual({
67+
token: null,
68+
header: "X-MCP-Proxy-Auth",
69+
});
70+
});
71+
});
72+
});

0 commit comments

Comments
 (0)