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:
parent
fdae89ecbf
commit
37cff101b1
1 changed files with 200 additions and 6 deletions
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue