Merge pull request #23773 from BerriAI/litellm_/reverent-panini

[Test] UI Dashboard - Add unit tests for 5 untested files
This commit is contained in:
yuneng-jiang 2026-03-16 14:42:16 -07:00 committed by GitHub
commit eba8df5235
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 231 additions and 0 deletions

View file

@ -0,0 +1,29 @@
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import HashicorpVaultEmptyPlaceholder from "./HashicorpVaultEmptyPlaceholder";
describe("HashicorpVaultEmptyPlaceholder", () => {
it("should render the empty state message and configure button", () => {
render(<HashicorpVaultEmptyPlaceholder onAdd={vi.fn()} />);
expect(screen.getByText("No Vault Configuration Found")).toBeInTheDocument();
expect(screen.getByRole("button", { name: /configure vault/i })).toBeInTheDocument();
});
it("should call onAdd when the configure button is clicked", async () => {
const onAdd = vi.fn();
const user = userEvent.setup();
render(<HashicorpVaultEmptyPlaceholder onAdd={onAdd} />);
await user.click(screen.getByRole("button", { name: /configure vault/i }));
expect(onAdd).toHaveBeenCalledOnce();
});
it("should display the description text about Vault purpose", () => {
render(<HashicorpVaultEmptyPlaceholder onAdd={vi.fn()} />);
expect(
screen.getByText(/Configure Hashicorp Vault to securely manage provider API keys/),
).toBeInTheDocument();
});
});

View file

@ -0,0 +1,77 @@
import { describe, it, expect, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import PageVisibilitySettings from "./PageVisibilitySettings";
vi.mock("@/components/page_utils", () => ({
getAvailablePages: () => [
{ page: "usage", label: "Usage", description: "View usage stats", group: "Analytics" },
{ page: "models", label: "Models", description: "Manage models", group: "Analytics" },
{ page: "keys", label: "API Keys", description: "Manage API keys", group: "Access" },
],
}));
describe("PageVisibilitySettings", () => {
it("should render the not-set tag when enabledPagesInternalUsers is null", () => {
render(
<PageVisibilitySettings
enabledPagesInternalUsers={null}
isUpdating={false}
onUpdate={vi.fn()}
/>,
);
expect(screen.getByText("Not set (all pages visible)")).toBeInTheDocument();
});
it("should show the selected page count tag when pages are configured", () => {
render(
<PageVisibilitySettings
enabledPagesInternalUsers={["usage", "keys"]}
isUpdating={false}
onUpdate={vi.fn()}
/>,
);
expect(screen.getByText("2 pages selected")).toBeInTheDocument();
});
it("should show singular 'page' when exactly one page is selected", () => {
render(
<PageVisibilitySettings
enabledPagesInternalUsers={["usage"]}
isUpdating={false}
onUpdate={vi.fn()}
/>,
);
expect(screen.getByText("1 page selected")).toBeInTheDocument();
});
it("should call onUpdate with null when reset button is clicked", async () => {
const onUpdate = vi.fn();
const user = userEvent.setup();
render(
<PageVisibilitySettings
enabledPagesInternalUsers={["usage"]}
isUpdating={false}
onUpdate={onUpdate}
/>,
);
// Expand the collapse panel first to reveal the reset button
await user.click(screen.getByRole("button", { name: /configure page visibility/i }));
await user.click(await screen.findByRole("button", { name: /reset to default/i }));
expect(onUpdate).toHaveBeenCalledWith({ enabled_ui_pages_internal_users: null });
});
it("should display the property description when provided", () => {
render(
<PageVisibilitySettings
enabledPagesInternalUsers={null}
enabledPagesPropertyDescription="Controls which pages are visible"
isUpdating={false}
onUpdate={vi.fn()}
/>,
);
expect(screen.getByText("Controls which pages are visible")).toBeInTheDocument();
});
});

View file

@ -0,0 +1,22 @@
import { describe, it, expect } from "vitest";
import { render, screen } from "@testing-library/react";
import { UiLoadingSpinner } from "./ui-loading-spinner";
describe("UiLoadingSpinner", () => {
it("should render an SVG element", () => {
render(<UiLoadingSpinner data-testid="spinner" />);
expect(screen.getByTestId("spinner")).toBeInTheDocument();
});
it("should apply custom className alongside default classes", () => {
render(<UiLoadingSpinner data-testid="spinner" className="text-red-500" />);
const svg = screen.getByTestId("spinner");
expect(svg).toHaveClass("text-red-500");
expect(svg).toHaveClass("animate-spin");
});
it("should spread additional SVG props onto the element", () => {
render(<UiLoadingSpinner data-testid="spinner" aria-label="Loading" />);
expect(screen.getByLabelText("Loading")).toBeInTheDocument();
});
});

View file

@ -0,0 +1,40 @@
import { describe, it, expect } from "vitest";
import { extractErrorMessage } from "./errorUtils";
describe("extractErrorMessage", () => {
it("should return the message from an Error instance", () => {
expect(extractErrorMessage(new Error("Something broke"))).toBe("Something broke");
});
it("should return detail when it is a string", () => {
expect(extractErrorMessage({ detail: "Not found" })).toBe("Not found");
});
it("should join msg fields from a FastAPI 422 detail array", () => {
const err = {
detail: [
{ msg: "field required", loc: ["body", "name"], type: "value_error" },
{ msg: "invalid type", loc: ["body", "age"], type: "type_error" },
],
};
expect(extractErrorMessage(err)).toBe("field required; invalid type");
});
it("should extract error from nested detail object", () => {
expect(extractErrorMessage({ detail: { error: "bad request" } })).toBe("bad request");
});
it("should fall back to message property on plain objects", () => {
expect(extractErrorMessage({ message: "fallback msg" })).toBe("fallback msg");
});
it("should JSON.stringify unknown object shapes", () => {
expect(extractErrorMessage({ foo: "bar" })).toBe('{"foo":"bar"}');
});
it("should stringify primitive non-object values", () => {
expect(extractErrorMessage(42)).toBe("42");
expect(extractErrorMessage(null)).toBe("null");
expect(extractErrorMessage(undefined)).toBe("undefined");
});
});

View file

@ -0,0 +1,63 @@
import { describe, it, expect } from "vitest";
import { classifyToolOp, groupToolsByCrud } from "./mcpToolCrudClassification";
describe("classifyToolOp", () => {
it("should classify read operations by name", () => {
expect(classifyToolOp("get-users")).toBe("read");
expect(classifyToolOp("list-items")).toBe("read");
expect(classifyToolOp("search documents")).toBe("read");
});
it("should classify delete operations by name", () => {
expect(classifyToolOp("delete-user")).toBe("delete");
expect(classifyToolOp("remove-item")).toBe("delete");
expect(classifyToolOp("purge-cache")).toBe("delete");
});
it("should classify create operations by name", () => {
expect(classifyToolOp("create-user")).toBe("create");
expect(classifyToolOp("add-item")).toBe("create");
expect(classifyToolOp("upload-file")).toBe("create");
});
it("should classify update operations by name", () => {
expect(classifyToolOp("update-settings")).toBe("update");
expect(classifyToolOp("edit-profile")).toBe("update");
expect(classifyToolOp("rename-file")).toBe("update");
});
it("should prioritize read over delete for names like get-removed-entries", () => {
expect(classifyToolOp("get-removed-entries")).toBe("read");
expect(classifyToolOp("list-deleted-items")).toBe("read");
});
it("should fall back to description when name is unrecognised", () => {
expect(classifyToolOp("mytool", "This will delete the record")).toBe("delete");
expect(classifyToolOp("mytool", "fetch data from the API")).toBe("read");
});
it("should return unknown when neither name nor description match", () => {
expect(classifyToolOp("my_tool")).toBe("unknown");
expect(classifyToolOp("my_tool", "does something")).toBe("unknown");
});
});
describe("groupToolsByCrud", () => {
it("should group tools into their CRUD categories", () => {
const tools = [
{ name: "get-user", description: "" },
{ name: "create-item", description: "" },
{ name: "delete-record", description: "" },
{ name: "update-settings", description: "" },
{ name: "mysteryop", description: "" },
];
const groups = groupToolsByCrud(tools);
expect(groups.read).toHaveLength(1);
expect(groups.create).toHaveLength(1);
expect(groups.delete).toHaveLength(1);
expect(groups.update).toHaveLength(1);
expect(groups.unknown).toHaveLength(1);
});
});