test: add tests for X-MCP-Proxy-Auth header implementation

- Added comprehensive test coverage for proxy authentication using X-MCP-Proxy-Auth header
- Tests verify proper header separation between proxy and server authentication
- Fixed transport mocks to properly capture options and headers
- Updated config structure in tests to match production code
- All proxy auth tests now passing

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Jerome 2025-06-14 17:20:30 +01:00
commit 37cff101b1
No known key found for this signature in database

View file

@ -3,6 +3,7 @@ import { useConnection } from "../useConnection";
import { z } from "zod";
import { ClientRequest } from "@modelcontextprotocol/sdk/types.js";
import { DEFAULT_INSPECTOR_CONFIG } from "../../constants";
import { SSEClientTransportOptions } from "@modelcontextprotocol/sdk/client/sse.js";
// Mock fetch
global.fetch = jest.fn().mockResolvedValue({
@ -23,21 +24,46 @@ const mockClient = {
setRequestHandler: jest.fn(),
};
// Mock transport instances
const mockSSETransport: {
start: jest.Mock;
url: URL | undefined;
options: SSEClientTransportOptions | undefined;
} = {
start: jest.fn(),
url: undefined,
options: undefined,
};
const mockStreamableHTTPTransport: {
start: jest.Mock;
url: URL | undefined;
options: SSEClientTransportOptions | undefined;
} = {
start: jest.fn(),
url: undefined,
options: undefined,
};
jest.mock("@modelcontextprotocol/sdk/client/index.js", () => ({
Client: jest.fn().mockImplementation(() => mockClient),
}));
jest.mock("@modelcontextprotocol/sdk/client/sse.js", () => ({
SSEClientTransport: jest.fn((url) => ({
toString: () => url,
})),
SSEClientTransport: jest.fn((url, options) => {
mockSSETransport.url = url;
mockSSETransport.options = options;
return mockSSETransport;
}),
SseError: jest.fn(),
}));
jest.mock("@modelcontextprotocol/sdk/client/streamableHttp.js", () => ({
StreamableHTTPClientTransport: jest.fn((url) => ({
toString: () => url,
})),
StreamableHTTPClientTransport: jest.fn((url, options) => {
mockStreamableHTTPTransport.url = url;
mockStreamableHTTPTransport.options = options;
return mockStreamableHTTPTransport;
}),
}));
jest.mock("@modelcontextprotocol/sdk/client/auth.js", () => ({
@ -259,4 +285,172 @@ describe("useConnection", () => {
);
});
});
describe("Proxy Authentication Headers", () => {
beforeEach(() => {
jest.clearAllMocks();
// Reset the mock transport objects
mockSSETransport.url = undefined;
mockSSETransport.options = undefined;
mockStreamableHTTPTransport.url = undefined;
mockStreamableHTTPTransport.options = undefined;
});
test("sends X-MCP-Proxy-Auth header when proxy auth token is configured", async () => {
const propsWithProxyAuth = {
...defaultProps,
config: {
...DEFAULT_INSPECTOR_CONFIG,
MCP_PROXY_AUTH_TOKEN: {
...DEFAULT_INSPECTOR_CONFIG.MCP_PROXY_AUTH_TOKEN,
value: "test-proxy-token",
},
},
};
const { result } = renderHook(() => useConnection(propsWithProxyAuth));
await act(async () => {
await result.current.connect();
});
// Check that the transport was created with the correct headers
expect(mockSSETransport.options).toBeDefined();
expect(mockSSETransport.options?.requestInit).toBeDefined();
expect(mockSSETransport.options?.requestInit?.headers).toHaveProperty(
"X-MCP-Proxy-Auth",
"Bearer test-proxy-token",
);
expect(mockSSETransport?.options?.eventSourceInit?.fetch).toBeDefined();
// Verify the fetch function includes the proxy auth header
const mockFetch = mockSSETransport.options?.eventSourceInit?.fetch;
const testUrl = "http://test.com";
await mockFetch?.(testUrl);
expect(global.fetch).toHaveBeenCalledTimes(2);
expect(
(global.fetch as jest.Mock).mock.calls[0][1].headers,
).toHaveProperty("X-MCP-Proxy-Auth", "Bearer test-proxy-token");
expect((global.fetch as jest.Mock).mock.calls[1][0]).toBe(testUrl);
expect(
(global.fetch as jest.Mock).mock.calls[1][1].headers,
).toHaveProperty("X-MCP-Proxy-Auth", "Bearer test-proxy-token");
});
test("does NOT send Authorization header for proxy auth", async () => {
const propsWithProxyAuth = {
...defaultProps,
config: {
...DEFAULT_INSPECTOR_CONFIG,
proxyAuthToken: "test-proxy-token",
},
};
const { result } = renderHook(() => useConnection(propsWithProxyAuth));
await act(async () => {
await result.current.connect();
});
// Check that Authorization header is NOT used for proxy auth
expect(mockSSETransport.options?.requestInit?.headers).not.toHaveProperty(
"Authorization",
"Bearer test-proxy-token",
);
});
test("preserves server Authorization header when proxy auth is configured", async () => {
const propsWithBothAuth = {
...defaultProps,
bearerToken: "server-auth-token",
config: {
...DEFAULT_INSPECTOR_CONFIG,
MCP_PROXY_AUTH_TOKEN: {
...DEFAULT_INSPECTOR_CONFIG.MCP_PROXY_AUTH_TOKEN,
value: "test-proxy-token",
},
},
};
const { result } = renderHook(() => useConnection(propsWithBothAuth));
await act(async () => {
await result.current.connect();
});
// Check that both headers are present and distinct
const headers = mockSSETransport.options?.requestInit?.headers;
expect(headers).toHaveProperty(
"Authorization",
"Bearer server-auth-token",
);
expect(headers).toHaveProperty(
"X-MCP-Proxy-Auth",
"Bearer test-proxy-token",
);
});
test("sends X-MCP-Proxy-Auth in health check requests", async () => {
const fetchMock = global.fetch as jest.Mock;
fetchMock.mockClear();
const propsWithProxyAuth = {
...defaultProps,
config: {
...DEFAULT_INSPECTOR_CONFIG,
MCP_PROXY_AUTH_TOKEN: {
...DEFAULT_INSPECTOR_CONFIG.MCP_PROXY_AUTH_TOKEN,
value: "test-proxy-token",
},
},
};
const { result } = renderHook(() => useConnection(propsWithProxyAuth));
await act(async () => {
await result.current.connect();
});
// Find the health check call
const healthCheckCall = fetchMock.mock.calls.find(
(call) => call[0].pathname === "/health",
);
expect(healthCheckCall).toBeDefined();
expect(healthCheckCall[1].headers).toHaveProperty(
"X-MCP-Proxy-Auth",
"Bearer test-proxy-token",
);
});
test("works correctly with streamable-http transport", async () => {
const propsWithStreamableHttp = {
...defaultProps,
transportType: "streamable-http" as const,
config: {
...DEFAULT_INSPECTOR_CONFIG,
MCP_PROXY_AUTH_TOKEN: {
...DEFAULT_INSPECTOR_CONFIG.MCP_PROXY_AUTH_TOKEN,
value: "test-proxy-token",
},
},
};
const { result } = renderHook(() =>
useConnection(propsWithStreamableHttp),
);
await act(async () => {
await result.current.connect();
});
// Check that the streamable HTTP transport was created with the correct headers
expect(mockStreamableHTTPTransport.options).toBeDefined();
expect(
mockStreamableHTTPTransport.options?.requestInit?.headers,
).toHaveProperty("X-MCP-Proxy-Auth", "Bearer test-proxy-token");
});
});
});