diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterSettings.test.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterSettings.test.tsx
new file mode 100644
index 000000000000..2b9d9ba9f843
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterSettings.test.tsx
@@ -0,0 +1,165 @@
+import React from "react";
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import { render, screen, act } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import MCPSemanticFilterSettings from "./MCPSemanticFilterSettings";
+import { useMCPSemanticFilterSettings } from "@/app/(dashboard)/hooks/mcpSemanticFilterSettings/useMCPSemanticFilterSettings";
+import { useUpdateMCPSemanticFilterSettings } from "@/app/(dashboard)/hooks/mcpSemanticFilterSettings/useUpdateMCPSemanticFilterSettings";
+
+vi.mock(
+ "@/app/(dashboard)/hooks/mcpSemanticFilterSettings/useMCPSemanticFilterSettings",
+ () => ({ useMCPSemanticFilterSettings: vi.fn() })
+);
+
+vi.mock(
+ "@/app/(dashboard)/hooks/mcpSemanticFilterSettings/useUpdateMCPSemanticFilterSettings",
+ () => ({ useUpdateMCPSemanticFilterSettings: vi.fn() })
+);
+
+vi.mock("@/components/playground/llm_calls/fetch_models", () => ({
+ fetchAvailableModels: vi.fn().mockResolvedValue([]),
+}));
+
+vi.mock("./MCPSemanticFilterTestPanel", () => ({
+ default: () =>
,
+}));
+
+vi.mock("./semanticFilterTestUtils", () => ({
+ getCurlCommand: vi.fn().mockReturnValue("curl ..."),
+ runSemanticFilterTest: vi.fn(),
+}));
+
+const mockMutate = vi.fn();
+
+const defaultSettingsData = {
+ field_schema: {
+ properties: {
+ enabled: { description: "Enable semantic filtering for MCP tools" },
+ },
+ },
+ values: {
+ enabled: false,
+ embedding_model: "text-embedding-3-small",
+ top_k: 10,
+ similarity_threshold: 0.3,
+ },
+};
+
+// Helper that renders the component and flushes the fetchAvailableModels effect
+async function renderSettings(props: React.ComponentProps) {
+ render();
+ if (props.accessToken) {
+ // Let the async fetchAvailableModels effect settle to avoid act() warnings
+ await act(async () => {});
+ }
+}
+
+describe("MCPSemanticFilterSettings", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ vi.mocked(useMCPSemanticFilterSettings).mockReturnValue({
+ data: defaultSettingsData,
+ isLoading: false,
+ isError: false,
+ error: null,
+ } as any);
+ vi.mocked(useUpdateMCPSemanticFilterSettings).mockReturnValue({
+ mutate: mockMutate,
+ isPending: false,
+ error: null,
+ } as any);
+ });
+
+ it("should render", async () => {
+ await renderSettings({ accessToken: "test-token" });
+ expect(screen.getByText("Semantic Tool Filtering")).toBeInTheDocument();
+ });
+
+ it("should show a login prompt when accessToken is null", () => {
+ render();
+ expect(screen.getByText(/please log in/i)).toBeInTheDocument();
+ });
+
+ it("should not render the form when accessToken is null", () => {
+ render();
+ expect(screen.queryByText("Enable Semantic Filtering")).not.toBeInTheDocument();
+ });
+
+ it("should not show the settings content while loading", async () => {
+ vi.mocked(useMCPSemanticFilterSettings).mockReturnValue({
+ data: undefined,
+ isLoading: true,
+ isError: false,
+ error: null,
+ } as any);
+ await renderSettings({ accessToken: "test-token" });
+ expect(screen.queryByText("Semantic Tool Filtering")).not.toBeInTheDocument();
+ });
+
+ it("should show an error alert when data fails to load", async () => {
+ vi.mocked(useMCPSemanticFilterSettings).mockReturnValue({
+ data: undefined,
+ isLoading: false,
+ isError: true,
+ error: new Error("Network error"),
+ } as any);
+ await renderSettings({ accessToken: "test-token" });
+ expect(
+ screen.getByText("Could not load MCP Semantic Filter settings")
+ ).toBeInTheDocument();
+ expect(screen.getByText("Network error")).toBeInTheDocument();
+ });
+
+ it("should show the error message from the error object when loading fails", async () => {
+ vi.mocked(useMCPSemanticFilterSettings).mockReturnValue({
+ data: undefined,
+ isLoading: false,
+ isError: true,
+ error: new Error("Connection refused"),
+ } as any);
+ await renderSettings({ accessToken: "test-token" });
+ expect(screen.getByText("Connection refused")).toBeInTheDocument();
+ });
+
+ it("should render the info alert and form fields when data is loaded", async () => {
+ await renderSettings({ accessToken: "test-token" });
+ expect(screen.getByText("Semantic Tool Filtering")).toBeInTheDocument();
+ expect(screen.getByText("Enable Semantic Filtering")).toBeInTheDocument();
+ expect(screen.getByText("Top K Results")).toBeInTheDocument();
+ expect(screen.getByText("Similarity Threshold")).toBeInTheDocument();
+ });
+
+ it("should render the test panel", async () => {
+ await renderSettings({ accessToken: "test-token" });
+ expect(screen.getByTestId("mcp-test-panel")).toBeInTheDocument();
+ });
+
+ it("should have Save Settings button disabled initially", async () => {
+ await renderSettings({ accessToken: "test-token" });
+ expect(
+ screen.getByRole("button", { name: /save settings/i })
+ ).toBeDisabled();
+ });
+
+ it("should enable Save Settings button after a form field is changed", async () => {
+ const user = userEvent.setup();
+ await renderSettings({ accessToken: "test-token" });
+
+ expect(screen.getByRole("button", { name: /save settings/i })).toBeDisabled();
+
+ await user.click(screen.getByRole("switch"));
+
+ expect(screen.getByRole("button", { name: /save settings/i })).not.toBeDisabled();
+ });
+
+ it("should show an error alert when the mutation fails", async () => {
+ vi.mocked(useUpdateMCPSemanticFilterSettings).mockReturnValue({
+ mutate: mockMutate,
+ isPending: false,
+ error: new Error("Failed to update settings"),
+ } as any);
+ await renderSettings({ accessToken: "test-token" });
+ expect(screen.getByText("Could not update settings")).toBeInTheDocument();
+ expect(screen.getByText("Failed to update settings")).toBeInTheDocument();
+ });
+});
diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterTestPanel.test.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterTestPanel.test.tsx
new file mode 100644
index 000000000000..974a6a7bf044
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/MCPSemanticFilterTestPanel.test.tsx
@@ -0,0 +1,141 @@
+import React from "react";
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import { render, screen, fireEvent } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import MCPSemanticFilterTestPanel from "./MCPSemanticFilterTestPanel";
+import { TestResult } from "./semanticFilterTestUtils";
+
+vi.mock("@/components/common_components/ModelSelector", () => ({
+ default: ({ onChange, value, labelText, disabled }: any) => (
+
+
+
+
+ ),
+}));
+
+const buildProps = (
+ overrides: Partial> = {}
+) => ({
+ accessToken: "test-token",
+ testQuery: "",
+ setTestQuery: vi.fn(),
+ testModel: "gpt-4o",
+ setTestModel: vi.fn(),
+ isTesting: false,
+ onTest: vi.fn(),
+ filterEnabled: true,
+ testResult: null as TestResult | null,
+ curlCommand: "curl --location 'http://localhost:4000/v1/responses'",
+ ...overrides,
+});
+
+describe("MCPSemanticFilterTestPanel", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it("should render the Test Configuration card", () => {
+ render();
+ expect(screen.getByText("Test Configuration")).toBeInTheDocument();
+ });
+
+ it("should show the test query textarea", () => {
+ render();
+ expect(
+ screen.getByPlaceholderText(/enter a test query to see which tools/i)
+ ).toBeInTheDocument();
+ });
+
+ it("should call setTestQuery when user types in the query field", () => {
+ const mockSetTestQuery = vi.fn();
+ render();
+
+ const textarea = screen.getByPlaceholderText(/enter a test query to see which tools/i);
+ fireEvent.change(textarea, { target: { value: "find relevant tools" } });
+
+ expect(mockSetTestQuery).toHaveBeenCalledWith("find relevant tools");
+ });
+
+ it("should disable the Test Filter button when testQuery is empty", () => {
+ render();
+ expect(screen.getByRole("button", { name: /test filter/i })).toBeDisabled();
+ });
+
+ it("should disable the Test Filter button when filterEnabled is false", () => {
+ render(
+
+ );
+ expect(screen.getByRole("button", { name: /test filter/i })).toBeDisabled();
+ });
+
+ it("should enable the Test Filter button when testQuery is set and filter is enabled", () => {
+ render(
+
+ );
+ expect(screen.getByRole("button", { name: /test filter/i })).not.toBeDisabled();
+ });
+
+ it("should call onTest when the Test Filter button is clicked", async () => {
+ const mockOnTest = vi.fn();
+ const user = userEvent.setup();
+ render(
+
+ );
+
+ await user.click(screen.getByRole("button", { name: /test filter/i }));
+ expect(mockOnTest).toHaveBeenCalledOnce();
+ });
+
+ it("should show a warning when semantic filtering is disabled", () => {
+ render();
+ expect(screen.getByText("Semantic filtering is disabled")).toBeInTheDocument();
+ });
+
+ it("should not show the disabled warning when filterEnabled is true", () => {
+ render();
+ expect(screen.queryByText("Semantic filtering is disabled")).not.toBeInTheDocument();
+ });
+
+ it("should display test results when testResult is provided", () => {
+ const testResult: TestResult = {
+ totalTools: 10,
+ selectedTools: 3,
+ tools: ["wiki-fetch", "github-search", "slack-post"],
+ };
+ render();
+
+ expect(screen.getByText("3 tools selected")).toBeInTheDocument();
+ expect(screen.getByText("Filtered from 10 available tools")).toBeInTheDocument();
+ expect(screen.getByText("wiki-fetch")).toBeInTheDocument();
+ expect(screen.getByText("github-search")).toBeInTheDocument();
+ expect(screen.getByText("slack-post")).toBeInTheDocument();
+ });
+
+ it("should not render the results section when testResult is null", () => {
+ render();
+ expect(screen.queryByText("Results")).not.toBeInTheDocument();
+ });
+
+ it("should show the curl command in the API Usage tab", async () => {
+ const user = userEvent.setup();
+ const curlCommand = "curl --location 'http://localhost:4000/v1/responses' --header 'Authorization: Bearer sk-1234'";
+ render();
+
+ await user.click(screen.getByRole("tab", { name: "API Usage" }));
+
+ expect(screen.getByText(curlCommand)).toBeInTheDocument();
+ });
+});
diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/semanticFilterTestUtils.test.ts b/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/semanticFilterTestUtils.test.ts
new file mode 100644
index 000000000000..12acdf8b8bac
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/MCPSemanticFilterSettings/semanticFilterTestUtils.test.ts
@@ -0,0 +1,117 @@
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import { getCurlCommand, runSemanticFilterTest } from "./semanticFilterTestUtils";
+import { testMCPSemanticFilter } from "@/components/networking";
+import NotificationManager from "@/components/molecules/notifications_manager";
+
+vi.mock("@/components/networking", () => ({
+ testMCPSemanticFilter: vi.fn(),
+}));
+
+describe("getCurlCommand", () => {
+ it("should include the model name in the curl command", () => {
+ const result = getCurlCommand("gpt-4o", "test query");
+ expect(result).toContain('"gpt-4o"');
+ });
+
+ it("should include the query in the curl command", () => {
+ const result = getCurlCommand("gpt-4o", "find relevant files");
+ expect(result).toContain("find relevant files");
+ });
+
+ it("should use a placeholder when query is empty", () => {
+ const result = getCurlCommand("gpt-4o", "");
+ expect(result).toContain("Your query here");
+ });
+});
+
+describe("runSemanticFilterTest", () => {
+ const mockSetIsTesting = vi.fn();
+ const mockSetTestResult = vi.fn();
+ const baseArgs = {
+ accessToken: "test-token",
+ testModel: "gpt-4o",
+ testQuery: "find relevant files",
+ setIsTesting: mockSetIsTesting,
+ setTestResult: mockSetTestResult,
+ };
+
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it("should call NotificationManager.error and not set isTesting when testQuery is empty", async () => {
+ await runSemanticFilterTest({ ...baseArgs, testQuery: "" });
+ expect(NotificationManager.error).toHaveBeenCalledWith("Please enter a query and select a model");
+ expect(mockSetIsTesting).not.toHaveBeenCalled();
+ });
+
+ it("should call NotificationManager.error and not set isTesting when testModel is empty", async () => {
+ await runSemanticFilterTest({ ...baseArgs, testModel: "" });
+ expect(NotificationManager.error).toHaveBeenCalledWith("Please enter a query and select a model");
+ expect(mockSetIsTesting).not.toHaveBeenCalled();
+ });
+
+ it("should set isTesting to true then false around the API call", async () => {
+ vi.mocked(testMCPSemanticFilter).mockResolvedValueOnce({
+ data: {},
+ headers: { filter: "5->2", tools: "tool-a,tool-b" },
+ });
+
+ await runSemanticFilterTest(baseArgs);
+
+ expect(mockSetIsTesting).toHaveBeenNthCalledWith(1, true);
+ expect(mockSetIsTesting).toHaveBeenNthCalledWith(2, false);
+ });
+
+ it("should clear the previous test result before making a new request", async () => {
+ vi.mocked(testMCPSemanticFilter).mockResolvedValueOnce({
+ data: {},
+ headers: { filter: "5->2", tools: "tool-a,tool-b" },
+ });
+
+ await runSemanticFilterTest(baseArgs);
+
+ expect(mockSetTestResult).toHaveBeenNthCalledWith(1, null);
+ });
+
+ it("should set test result with parsed data on success", async () => {
+ vi.mocked(testMCPSemanticFilter).mockResolvedValueOnce({
+ data: {},
+ headers: { filter: "10->3", tools: "wiki,github,slack" },
+ });
+
+ await runSemanticFilterTest(baseArgs);
+
+ expect(mockSetTestResult).toHaveBeenCalledWith({
+ totalTools: 10,
+ selectedTools: 3,
+ tools: ["wiki", "github", "slack"],
+ });
+ expect(NotificationManager.success).toHaveBeenCalledWith(
+ "Semantic filter test completed successfully"
+ );
+ });
+
+ it("should show a warning when the filter header is missing", async () => {
+ vi.mocked(testMCPSemanticFilter).mockResolvedValueOnce({
+ data: {},
+ headers: { filter: null, tools: null },
+ });
+
+ await runSemanticFilterTest(baseArgs);
+
+ expect(NotificationManager.warning).toHaveBeenCalledWith(
+ "Semantic filter is not enabled or no tools were filtered"
+ );
+ expect(mockSetTestResult).not.toHaveBeenCalledWith(expect.objectContaining({ totalTools: expect.any(Number) }));
+ });
+
+ it("should show an error notification and finish testing when the API call fails", async () => {
+ vi.mocked(testMCPSemanticFilter).mockRejectedValueOnce(new Error("Network error"));
+
+ await runSemanticFilterTest(baseArgs);
+
+ expect(NotificationManager.error).toHaveBeenCalledWith("Failed to test semantic filter");
+ expect(mockSetIsTesting).toHaveBeenLastCalledWith(false);
+ });
+});
diff --git a/ui/litellm-dashboard/src/components/TeamSSOSettings.test.tsx b/ui/litellm-dashboard/src/components/TeamSSOSettings.test.tsx
index 935d26099cd7..ae93b118799d 100644
--- a/ui/litellm-dashboard/src/components/TeamSSOSettings.test.tsx
+++ b/ui/litellm-dashboard/src/components/TeamSSOSettings.test.tsx
@@ -9,36 +9,6 @@ import NotificationsManager from "./molecules/notifications_manager";
vi.mock("./networking");
-vi.mock("@tremor/react", async (importOriginal) => {
- const actual = await importOriginal();
- const React = await import("react");
- const Card = ({ children }: { children: React.ReactNode }) => React.createElement("div", { "data-testid": "card" }, children);
- Card.displayName = "Card";
- const Title = ({ children }: { children: React.ReactNode }) => React.createElement("h2", {}, children);
- Title.displayName = "Title";
- const Text = ({ children }: { children: React.ReactNode }) => React.createElement("span", {}, children);
- Text.displayName = "Text";
- const Divider = () => React.createElement("hr", {});
- Divider.displayName = "Divider";
- const TextInput = ({ value, onChange, placeholder, className }: any) =>
- React.createElement("input", {
- type: "text",
- value: value || "",
- onChange,
- placeholder,
- className,
- });
- TextInput.displayName = "TextInput";
- return {
- ...actual,
- Card,
- Title,
- Text,
- Divider,
- TextInput,
- };
-});
-
vi.mock("./common_components/budget_duration_dropdown", () => {
const BudgetDurationDropdown = ({ value, onChange }: { value: string | null; onChange: (value: string) => void }) => (