* Hooks are in multiple places in the codebase and some camelCase, some snake-case. This PR relocates them all to the lib/hooks folder and normalizes camelCase naming. Settled on src/client/lib/hooks for the location since most of them were already there. - Refactor/move useTheme.ts from client/src/lib to client/src/lib/hooks - Refactor/move useToast.ts from client/src/hooks/ to client/src/lib/hooks/useToast.ts - Removed client/src/hooks
167 lines
4.4 KiB
TypeScript
167 lines
4.4 KiB
TypeScript
import { renderHook, act } from "@testing-library/react";
|
|
import { useConnection } from "../useConnection";
|
|
import { z } from "zod";
|
|
import { ClientRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
import { DEFAULT_INSPECTOR_CONFIG } from "../../constants";
|
|
|
|
// Mock fetch
|
|
global.fetch = jest.fn().mockResolvedValue({
|
|
json: () => Promise.resolve({ status: "ok" }),
|
|
});
|
|
|
|
// Mock the SDK dependencies
|
|
const mockRequest = jest.fn().mockResolvedValue({ test: "response" });
|
|
const mockClient = {
|
|
request: mockRequest,
|
|
notification: jest.fn(),
|
|
connect: jest.fn().mockResolvedValue(undefined),
|
|
close: jest.fn(),
|
|
getServerCapabilities: jest.fn(),
|
|
getServerVersion: jest.fn(),
|
|
getInstructions: jest.fn(),
|
|
setNotificationHandler: jest.fn(),
|
|
setRequestHandler: jest.fn(),
|
|
};
|
|
|
|
jest.mock("@modelcontextprotocol/sdk/client/index.js", () => ({
|
|
Client: jest.fn().mockImplementation(() => mockClient),
|
|
}));
|
|
|
|
jest.mock("@modelcontextprotocol/sdk/client/sse.js", () => ({
|
|
SSEClientTransport: jest.fn(),
|
|
SseError: jest.fn(),
|
|
}));
|
|
|
|
jest.mock("@modelcontextprotocol/sdk/client/auth.js", () => ({
|
|
auth: jest.fn().mockResolvedValue("AUTHORIZED"),
|
|
}));
|
|
|
|
// Mock the toast hook
|
|
jest.mock("@/lib/hooks/useToast", () => ({
|
|
useToast: () => ({
|
|
toast: jest.fn(),
|
|
}),
|
|
}));
|
|
|
|
// Mock the auth provider
|
|
jest.mock("../../auth", () => ({
|
|
InspectorOAuthClientProvider: jest.fn().mockImplementation(() => ({
|
|
tokens: jest.fn().mockResolvedValue({ access_token: "mock-token" }),
|
|
})),
|
|
}));
|
|
|
|
describe("useConnection", () => {
|
|
const defaultProps = {
|
|
transportType: "sse" as const,
|
|
command: "",
|
|
args: "",
|
|
sseUrl: "http://localhost:8080",
|
|
env: {},
|
|
config: DEFAULT_INSPECTOR_CONFIG,
|
|
};
|
|
|
|
describe("Request Configuration", () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
test("uses the default config values in makeRequest", async () => {
|
|
const { result } = renderHook(() => useConnection(defaultProps));
|
|
|
|
// Connect the client
|
|
await act(async () => {
|
|
await result.current.connect();
|
|
});
|
|
|
|
// Wait for state update
|
|
await act(async () => {
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
});
|
|
|
|
const mockRequest: ClientRequest = {
|
|
method: "ping",
|
|
params: {},
|
|
};
|
|
|
|
const mockSchema = z.object({
|
|
test: z.string(),
|
|
});
|
|
|
|
await act(async () => {
|
|
await result.current.makeRequest(mockRequest, mockSchema);
|
|
});
|
|
|
|
expect(mockClient.request).toHaveBeenCalledWith(
|
|
mockRequest,
|
|
mockSchema,
|
|
expect.objectContaining({
|
|
timeout: DEFAULT_INSPECTOR_CONFIG.MCP_SERVER_REQUEST_TIMEOUT.value,
|
|
maxTotalTimeout:
|
|
DEFAULT_INSPECTOR_CONFIG.MCP_REQUEST_MAX_TOTAL_TIMEOUT.value,
|
|
resetTimeoutOnProgress:
|
|
DEFAULT_INSPECTOR_CONFIG.MCP_REQUEST_TIMEOUT_RESET_ON_PROGRESS
|
|
.value,
|
|
}),
|
|
);
|
|
});
|
|
|
|
test("overrides the default config values when passed in options in makeRequest", async () => {
|
|
const { result } = renderHook(() => useConnection(defaultProps));
|
|
|
|
// Connect the client
|
|
await act(async () => {
|
|
await result.current.connect();
|
|
});
|
|
|
|
// Wait for state update
|
|
await act(async () => {
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
});
|
|
|
|
const mockRequest: ClientRequest = {
|
|
method: "ping",
|
|
params: {},
|
|
};
|
|
|
|
const mockSchema = z.object({
|
|
test: z.string(),
|
|
});
|
|
|
|
await act(async () => {
|
|
await result.current.makeRequest(mockRequest, mockSchema, {
|
|
timeout: 1000,
|
|
maxTotalTimeout: 2000,
|
|
resetTimeoutOnProgress: false,
|
|
});
|
|
});
|
|
|
|
expect(mockClient.request).toHaveBeenCalledWith(
|
|
mockRequest,
|
|
mockSchema,
|
|
expect.objectContaining({
|
|
timeout: 1000,
|
|
maxTotalTimeout: 2000,
|
|
resetTimeoutOnProgress: false,
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
|
|
test("throws error when mcpClient is not connected", async () => {
|
|
const { result } = renderHook(() => useConnection(defaultProps));
|
|
|
|
const mockRequest: ClientRequest = {
|
|
method: "ping",
|
|
params: {},
|
|
};
|
|
|
|
const mockSchema = z.object({
|
|
test: z.string(),
|
|
});
|
|
|
|
await expect(
|
|
result.current.makeRequest(mockRequest, mockSchema),
|
|
).rejects.toThrow("MCP client not connected");
|
|
});
|
|
});
|