Merge pull request #23489 from BerriAI/litellm_mcp_tools_tests

[Test] MCP tools component unit tests
This commit is contained in:
yuneng-jiang 2026-03-12 15:31:31 -07:00 committed by GitHub
commit 8c96e43aab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 338 additions and 0 deletions

View file

@ -0,0 +1,55 @@
import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import { describe, it, expect, vi, beforeEach } from "vitest";
import userEvent from "@testing-library/user-event";
import MCPLogoSelector from "./MCPLogoSelector";
describe("MCPLogoSelector", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("should render the logo grid and custom URL input", () => {
render(<MCPLogoSelector />);
expect(screen.getByPlaceholderText(/paste a custom logo URL/i)).toBeInTheDocument();
});
it("should show a preview when a value is provided", () => {
render(<MCPLogoSelector value="/ui/assets/logos/github.svg" />);
expect(screen.getByAltText("Selected logo")).toBeInTheDocument();
});
it("should not show a preview when no value is provided", () => {
render(<MCPLogoSelector />);
expect(screen.queryByAltText("Selected logo")).not.toBeInTheDocument();
});
it("should call onChange with undefined when the clear button is clicked", async () => {
const onChange = vi.fn();
const user = userEvent.setup();
render(<MCPLogoSelector value="/ui/assets/logos/github.svg" onChange={onChange} />);
await user.click(screen.getByRole("button", { name: /✕/ }));
expect(onChange).toHaveBeenCalledWith(undefined);
});
it("should call onChange with the logo URL when a grid logo is clicked", async () => {
const onChange = vi.fn();
const user = userEvent.setup();
render(<MCPLogoSelector onChange={onChange} />);
const githubButton = screen.getByRole("button", { name: /GitHub/i });
await user.click(githubButton);
expect(onChange).toHaveBeenCalledWith("/ui/assets/logos/github.svg");
});
it("should deselect a logo when clicking the already-selected logo", async () => {
const onChange = vi.fn();
const user = userEvent.setup();
render(<MCPLogoSelector value="/ui/assets/logos/github.svg" onChange={onChange} />);
const githubButton = screen.getByRole("button", { name: /GitHub/i });
await user.click(githubButton);
expect(onChange).toHaveBeenCalledWith(undefined);
});
});

View file

@ -0,0 +1,62 @@
import { describe, it, expect } from "vitest";
import { FIELD_GROUPS, MCP_REQUIRED_FIELD_DEFS, SETTINGS_KEY } from "./MCPStandardsSettings";
import { MCPServer } from "./types";
const makeServer = (overrides: Partial<MCPServer> = {}): MCPServer => ({
server_id: "s1",
created_at: "2024-01-01",
created_by: "user",
updated_at: "2024-01-01",
updated_by: "user",
...overrides,
});
describe("FIELD_GROUPS", () => {
it("should contain four groups", () => {
expect(FIELD_GROUPS).toHaveLength(4);
expect(FIELD_GROUPS.map((g) => g.label)).toEqual([
"Documentation",
"Source",
"Connection",
"Security",
]);
});
});
describe("MCP_REQUIRED_FIELD_DEFS", () => {
it("should flatten all fields from groups", () => {
const totalFields = FIELD_GROUPS.reduce((sum, g) => sum + g.fields.length, 0);
expect(MCP_REQUIRED_FIELD_DEFS).toHaveLength(totalFields);
});
});
describe("field check functions", () => {
const findCheck = (key: string) =>
MCP_REQUIRED_FIELD_DEFS.find((f) => f.key === key)!.check;
it("should pass description check when description is present", () => {
expect(findCheck("description")(makeServer({ description: "A service" }))).toBe(true);
});
it("should fail description check when description is empty", () => {
expect(findCheck("description")(makeServer({ description: " " }))).toBe(false);
});
it("should pass auth check when auth_type is not none", () => {
expect(findCheck("auth_type")(makeServer({ auth_type: "oauth2" }))).toBe(true);
});
it("should fail auth check when auth_type is none", () => {
expect(findCheck("auth_type")(makeServer({ auth_type: "none" }))).toBe(false);
});
it("should fail auth check when auth_type is missing", () => {
expect(findCheck("auth_type")(makeServer())).toBe(false);
});
});
describe("SETTINGS_KEY", () => {
it("should equal mcp_required_fields", () => {
expect(SETTINGS_KEY).toBe("mcp_required_fields");
});
});

View file

@ -0,0 +1,87 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import { describe, it, expect, vi, beforeEach } from "vitest";
import userEvent from "@testing-library/user-event";
import MCPConnectionStatus from "./mcp_connection_status";
describe("MCPConnectionStatus", () => {
const defaultProps = {
formValues: { url: "https://example.com/mcp" },
tools: [] as any[],
isLoadingTools: false,
toolsError: null,
toolsErrorStackTrace: null,
canFetchTools: false,
fetchTools: vi.fn(),
};
beforeEach(() => {
vi.clearAllMocks();
});
it("should render nothing when canFetchTools is false and no URL is set", () => {
const { container } = render(
<MCPConnectionStatus {...defaultProps} formValues={{}} />
);
expect(container.firstChild).toBeNull();
});
it("should show 'Complete required fields' message when URL is set but canFetchTools is false", () => {
render(<MCPConnectionStatus {...defaultProps} />);
expect(screen.getByText(/Complete required fields to test connection/i)).toBeInTheDocument();
});
it("should show 'Connection successful' when tools are loaded", () => {
render(
<MCPConnectionStatus
{...defaultProps}
canFetchTools={true}
tools={[{ name: "tool1" }]}
/>
);
expect(screen.getByText("Connection successful")).toBeInTheDocument();
expect(screen.getByText("Connected")).toBeInTheDocument();
});
it("should show loading state when isLoadingTools is true", () => {
render(
<MCPConnectionStatus
{...defaultProps}
canFetchTools={true}
isLoadingTools={true}
/>
);
expect(screen.getByText(/Testing connection to MCP server/i)).toBeInTheDocument();
expect(screen.getByText("Connecting...")).toBeInTheDocument();
});
it("should show error state with retry button when toolsError is set", async () => {
const fetchTools = vi.fn();
const user = userEvent.setup();
render(
<MCPConnectionStatus
{...defaultProps}
canFetchTools={true}
toolsError="Connection refused"
fetchTools={fetchTools}
/>
);
expect(screen.getByText("Connection Failed")).toBeInTheDocument();
expect(screen.getByText("Connection refused")).toBeInTheDocument();
await user.click(screen.getByRole("button", { name: /retry/i }));
expect(fetchTools).toHaveBeenCalled();
});
it("should show 'No tools found' when connection succeeds but no tools returned", () => {
render(
<MCPConnectionStatus
{...defaultProps}
canFetchTools={true}
tools={[]}
/>
);
expect(screen.getByText(/No tools found for this MCP server/i)).toBeInTheDocument();
});
});

View file

@ -0,0 +1,59 @@
import { describe, it, expect } from "vitest";
import { AUTH_TYPE, OAUTH_FLOW, TRANSPORT, handleTransport, handleAuth } from "./types";
describe("handleTransport", () => {
it("should default to SSE when transport is null", () => {
expect(handleTransport(null)).toBe(TRANSPORT.SSE);
});
it("should default to SSE when transport is undefined", () => {
expect(handleTransport(undefined)).toBe(TRANSPORT.SSE);
});
it("should return openapi when specPath is present and transport is not stdio", () => {
expect(handleTransport("http", "/spec.yaml")).toBe(TRANSPORT.OPENAPI);
});
it("should keep stdio even when specPath is present", () => {
expect(handleTransport(TRANSPORT.STDIO, "/spec.yaml")).toBe(TRANSPORT.STDIO);
});
it("should return the transport as-is when no specPath", () => {
expect(handleTransport("http")).toBe("http");
});
});
describe("handleAuth", () => {
it("should default to NONE when authType is null", () => {
expect(handleAuth(null)).toBe(AUTH_TYPE.NONE);
});
it("should default to NONE when authType is undefined", () => {
expect(handleAuth(undefined)).toBe(AUTH_TYPE.NONE);
});
it("should return the provided auth type", () => {
expect(handleAuth(AUTH_TYPE.OAUTH2)).toBe("oauth2");
});
});
describe("constants", () => {
it("should define all expected auth types", () => {
expect(AUTH_TYPE.NONE).toBe("none");
expect(AUTH_TYPE.API_KEY).toBe("api_key");
expect(AUTH_TYPE.BEARER_TOKEN).toBe("bearer_token");
expect(AUTH_TYPE.OAUTH2).toBe("oauth2");
});
it("should define all expected transport types", () => {
expect(TRANSPORT.SSE).toBe("sse");
expect(TRANSPORT.HTTP).toBe("http");
expect(TRANSPORT.STDIO).toBe("stdio");
expect(TRANSPORT.OPENAPI).toBe("openapi");
});
it("should define OAuth flow types", () => {
expect(OAUTH_FLOW.INTERACTIVE).toBe("interactive");
expect(OAUTH_FLOW.M2M).toBe("m2m");
});
});

View file

@ -0,0 +1,75 @@
import { describe, it, expect } from "vitest";
import {
extractMCPToken,
maskUrl,
getMaskedAndFullUrl,
validateMCPServerUrl,
validateMCPServerName,
} from "./utils";
describe("extractMCPToken", () => {
it("should extract token after /mcp/", () => {
const result = extractMCPToken("https://example.com/mcp/abc123");
expect(result).toEqual({ token: "abc123", baseUrl: "https://example.com/mcp/" });
});
it("should return null token when URL has no /mcp/ segment", () => {
const result = extractMCPToken("https://example.com/api/v1");
expect(result).toEqual({ token: null, baseUrl: "https://example.com/api/v1" });
});
it("should return null token when nothing follows /mcp/", () => {
const result = extractMCPToken("https://example.com/mcp/");
expect(result).toEqual({ token: null, baseUrl: "https://example.com/mcp/" });
});
});
describe("maskUrl", () => {
it("should replace the token with ellipsis", () => {
expect(maskUrl("https://example.com/mcp/secret-token")).toBe("https://example.com/mcp/...");
});
it("should return the original URL when there is no token", () => {
expect(maskUrl("https://example.com/api")).toBe("https://example.com/api");
});
});
describe("getMaskedAndFullUrl", () => {
it("should return hasToken true when a token exists", () => {
const result = getMaskedAndFullUrl("https://example.com/mcp/tok");
expect(result).toEqual({ maskedUrl: "https://example.com/mcp/...", hasToken: true });
});
it("should return hasToken false when no token exists", () => {
const result = getMaskedAndFullUrl("https://example.com/api");
expect(result).toEqual({ maskedUrl: "https://example.com/api", hasToken: false });
});
});
describe("validateMCPServerUrl", () => {
it("should resolve for a valid HTTP URL", async () => {
await expect(validateMCPServerUrl("https://example.com/path")).resolves.toBeUndefined();
});
it("should resolve for an empty string", async () => {
await expect(validateMCPServerUrl("")).resolves.toBeUndefined();
});
it("should reject for an invalid URL", async () => {
await expect(validateMCPServerUrl("not-a-url")).rejects.toBeDefined();
});
});
describe("validateMCPServerName", () => {
it("should resolve for a valid underscore name", async () => {
await expect(validateMCPServerName("my_server")).resolves.toBeUndefined();
});
it("should reject names containing hyphens", async () => {
await expect(validateMCPServerName("my-server")).rejects.toBeDefined();
});
it("should reject names containing spaces", async () => {
await expect(validateMCPServerName("my server")).rejects.toBeDefined();
});
});