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"); }); });