mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-09 14:22:12 +00:00
Merge pull request #20462 from BerriAI/litellm_model_info_cost
[Fix] UI - Model Info Page: Fix Input and Output Labels
This commit is contained in:
@@ -1,59 +1,36 @@
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { render, waitFor } from "@testing-library/react";
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import React, { ReactNode } from "react";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import ModelInfoView from "./model_info_view";
|
||||
import NotificationsManager from "./molecules/notifications_manager";
|
||||
import * as networking from "./networking";
|
||||
|
||||
vi.mock("../../utils/dataUtils", () => ({
|
||||
copyToClipboard: vi.fn().mockResolvedValue(true),
|
||||
}));
|
||||
|
||||
vi.mock("./molecules/notifications_manager", () => ({
|
||||
default: {
|
||||
success: vi.fn(),
|
||||
error: vi.fn(),
|
||||
info: vi.fn(),
|
||||
fromBackend: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("./networking", () => ({
|
||||
modelInfoV1Call: vi.fn().mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
model_name: "GPT-4",
|
||||
litellm_params: {
|
||||
model: "gpt-4",
|
||||
api_base: "https://api.openai.com/v1",
|
||||
custom_llm_provider: "openai",
|
||||
},
|
||||
model_info: {
|
||||
id: "123",
|
||||
created_by: "123",
|
||||
db_model: true,
|
||||
input_cost_per_token: 0.00003,
|
||||
output_cost_per_token: 0.00006,
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
credentialGetCall: vi.fn().mockResolvedValue({
|
||||
credential_name: "test-credential",
|
||||
credential_values: {},
|
||||
credential_info: {},
|
||||
}),
|
||||
getGuardrailsList: vi.fn().mockResolvedValue({
|
||||
guardrails: [{ guardrail_name: "content_filter" }, { guardrail_name: "toxicity_filter" }],
|
||||
}),
|
||||
tagListCall: vi.fn().mockResolvedValue({
|
||||
test_tag: {
|
||||
name: "test_tag",
|
||||
description: "A test tag",
|
||||
},
|
||||
production_tag: {
|
||||
name: "production_tag",
|
||||
description: "Production ready models",
|
||||
},
|
||||
}),
|
||||
testConnectionRequest: vi.fn().mockResolvedValue({
|
||||
status: "success",
|
||||
}),
|
||||
modelPatchUpdateCall: vi.fn().mockResolvedValue({}),
|
||||
modelDeleteCall: vi.fn().mockResolvedValue({}),
|
||||
modelInfoV1Call: vi.fn(),
|
||||
credentialGetCall: vi.fn(),
|
||||
getGuardrailsList: vi.fn(),
|
||||
tagListCall: vi.fn(),
|
||||
testConnectionRequest: vi.fn(),
|
||||
modelPatchUpdateCall: vi.fn(),
|
||||
modelDeleteCall: vi.fn(),
|
||||
credentialCreateCall: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock the useModelsInfo hook since it uses React Query
|
||||
const mockUseModelsInfo = vi.fn();
|
||||
const mockUseModelHub = vi.fn();
|
||||
|
||||
@@ -62,12 +39,21 @@ vi.mock("@/app/(dashboard)/hooks/models/useModels", () => ({
|
||||
useModelHub: (...args: any[]) => mockUseModelHub(...args),
|
||||
}));
|
||||
|
||||
// Mock the useModelCostMap hook
|
||||
const mockUseModelCostMap = vi.fn();
|
||||
vi.mock("@/app/(dashboard)/hooks/models/useModelCostMap", () => ({
|
||||
useModelCostMap: (...args: any[]) => mockUseModelCostMap(...args),
|
||||
}));
|
||||
|
||||
const mockNotificationsManager = vi.mocked(NotificationsManager);
|
||||
const mockModelInfoV1Call = vi.mocked(networking.modelInfoV1Call);
|
||||
const mockCredentialGetCall = vi.mocked(networking.credentialGetCall);
|
||||
const mockGetGuardrailsList = vi.mocked(networking.getGuardrailsList);
|
||||
const mockTagListCall = vi.mocked(networking.tagListCall);
|
||||
const mockTestConnectionRequest = vi.mocked(networking.testConnectionRequest);
|
||||
const mockModelPatchUpdateCall = vi.mocked(networking.modelPatchUpdateCall);
|
||||
const mockModelDeleteCall = vi.mocked(networking.modelDeleteCall);
|
||||
const mockCredentialCreateCall = vi.mocked(networking.credentialCreateCall);
|
||||
|
||||
describe("ModelInfoView", () => {
|
||||
let queryClient: QueryClient;
|
||||
|
||||
@@ -88,6 +74,16 @@ describe("ModelInfoView", () => {
|
||||
},
|
||||
};
|
||||
|
||||
const DEFAULT_ADMIN_PROPS = {
|
||||
modelId: "123",
|
||||
onClose: vi.fn(),
|
||||
accessToken: "test-token",
|
||||
userID: "123",
|
||||
userRole: "Admin",
|
||||
onModelUpdate: vi.fn(),
|
||||
modelAccessGroups: ["group1", "group2"],
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
@@ -98,7 +94,6 @@ describe("ModelInfoView", () => {
|
||||
});
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Set up default mocks
|
||||
mockUseModelsInfo.mockReturnValue({
|
||||
data: {
|
||||
data: [defaultModelData],
|
||||
@@ -120,89 +115,170 @@ describe("ModelInfoView", () => {
|
||||
isLoading: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
mockModelInfoV1Call.mockResolvedValue({
|
||||
data: [defaultModelData],
|
||||
});
|
||||
|
||||
mockCredentialGetCall.mockResolvedValue({
|
||||
credential_name: "test-credential",
|
||||
credential_values: {},
|
||||
credential_info: {},
|
||||
});
|
||||
|
||||
mockGetGuardrailsList.mockResolvedValue({
|
||||
guardrails: [{ guardrail_name: "content_filter" }, { guardrail_name: "toxicity_filter" }],
|
||||
});
|
||||
|
||||
mockTagListCall.mockResolvedValue({
|
||||
test_tag: {
|
||||
name: "test_tag",
|
||||
description: "A test tag",
|
||||
models: [],
|
||||
created_at: "2024-01-01T00:00:00Z",
|
||||
updated_at: "2024-01-01T00:00:00Z",
|
||||
},
|
||||
production_tag: {
|
||||
name: "production_tag",
|
||||
description: "Production ready models",
|
||||
models: [],
|
||||
created_at: "2024-01-01T00:00:00Z",
|
||||
updated_at: "2024-01-01T00:00:00Z",
|
||||
},
|
||||
});
|
||||
|
||||
mockTestConnectionRequest.mockResolvedValue({
|
||||
status: "success",
|
||||
});
|
||||
|
||||
mockModelPatchUpdateCall.mockResolvedValue({});
|
||||
mockModelDeleteCall.mockResolvedValue({});
|
||||
mockCredentialCreateCall.mockResolvedValue({});
|
||||
});
|
||||
|
||||
const wrapper = ({ children }: { children: ReactNode }) =>
|
||||
React.createElement(QueryClientProvider, { client: queryClient }, children);
|
||||
|
||||
const DEFAULT_ADMIN_PROPS = {
|
||||
modelId: "123",
|
||||
onClose: () => {},
|
||||
accessToken: "123",
|
||||
userID: "123",
|
||||
userRole: "Admin",
|
||||
onModelUpdate: () => {},
|
||||
modelAccessGroups: [],
|
||||
};
|
||||
|
||||
describe("Edit Model", () => {
|
||||
it("should render the model info view", async () => {
|
||||
const { getByText } = render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
await waitFor(() => {
|
||||
expect(getByText("Model Settings")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should not render an edit settings button if the model is not a DB model", async () => {
|
||||
const nonDbModelData = {
|
||||
...defaultModelData,
|
||||
model_info: {
|
||||
...defaultModelData.model_info,
|
||||
db_model: false,
|
||||
},
|
||||
};
|
||||
|
||||
mockUseModelsInfo.mockReturnValue({
|
||||
data: {
|
||||
data: [nonDbModelData],
|
||||
},
|
||||
isLoading: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
const { queryByText } = render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
await waitFor(() => {
|
||||
expect(queryByText("Edit Settings")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should render tags in the edit model", async () => {
|
||||
const { getByText } = render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
await waitFor(() => {
|
||||
expect(getByText("Tags")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should render the litellm params in the edit model", async () => {
|
||||
const { getByText } = render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
await waitFor(() => {
|
||||
expect(getByText("LiteLLM Params")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should render a test connection button", async () => {
|
||||
const { getByTestId } = render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
it("should render", async () => {
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
await waitFor(() => {
|
||||
expect(getByTestId("test-connection-button")).toBeInTheDocument();
|
||||
expect(screen.getByText("Model Settings")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should render a reuse credentials button", async () => {
|
||||
const { getByTestId } = render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
it("should display loading state when model data is loading", () => {
|
||||
mockUseModelsInfo.mockReturnValue({
|
||||
data: null,
|
||||
isLoading: true,
|
||||
error: null,
|
||||
});
|
||||
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
expect(screen.getByText("Loading...")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display not found message when model data is not available", async () => {
|
||||
mockUseModelsInfo.mockReturnValue({
|
||||
data: {
|
||||
data: [],
|
||||
},
|
||||
isLoading: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
await waitFor(() => {
|
||||
expect(getByTestId("reuse-credentials-button")).toBeInTheDocument();
|
||||
expect(screen.getByText("Model not found")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should render a delete model button", async () => {
|
||||
const { getByTestId } = render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
it("should display model name in the header", async () => {
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
await waitFor(() => {
|
||||
expect(getByTestId("delete-model-button")).toBeInTheDocument();
|
||||
expect(screen.getByText(/Public Model Name:/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should render a disabled delete model button if the model is not a DB model", async () => {
|
||||
it("should display back button that calls onClose when clicked", async () => {
|
||||
const mockOnClose = vi.fn();
|
||||
const user = userEvent.setup();
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} onClose={mockOnClose} />, { wrapper });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Model Settings")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const backButton = screen.getByRole("button", { name: /back to models/i });
|
||||
await user.click(backButton);
|
||||
|
||||
expect(mockOnClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should display test connection button", async () => {
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("button", { name: /test connection/i })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should test connection when test connection button is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Model Settings")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const testButton = screen.getByRole("button", { name: /test connection/i });
|
||||
await user.click(testButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockTestConnectionRequest).toHaveBeenCalled();
|
||||
expect(mockNotificationsManager.success).toHaveBeenCalledWith("Connection test successful!");
|
||||
});
|
||||
});
|
||||
|
||||
it("should display error notification when connection test fails", async () => {
|
||||
const user = userEvent.setup();
|
||||
mockTestConnectionRequest.mockRejectedValue(new Error("Connection failed"));
|
||||
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Model Settings")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const testButton = screen.getByRole("button", { name: /test connection/i });
|
||||
await user.click(testButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockNotificationsManager.error).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("should display reuse credentials button for admin users", async () => {
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("button", { name: /re-use credentials/i })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should disable reuse credentials button for non-admin users", async () => {
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} userRole="User" />, { wrapper });
|
||||
await waitFor(() => {
|
||||
const button = screen.getByRole("button", { name: /re-use credentials/i });
|
||||
expect(button).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
it("should display delete model button", async () => {
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("button", { name: /delete model/i })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should disable delete button when model is not a DB model", async () => {
|
||||
const nonDbModelData = {
|
||||
...defaultModelData,
|
||||
model_info: {
|
||||
@@ -219,13 +295,14 @@ describe("ModelInfoView", () => {
|
||||
error: null,
|
||||
});
|
||||
|
||||
const { getByTestId } = render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
await waitFor(() => {
|
||||
expect(getByTestId("delete-model-button")).toBeDisabled();
|
||||
const deleteButton = screen.getByRole("button", { name: /delete model/i });
|
||||
expect(deleteButton).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
it("should render a disabled delete model button if the user is not an admin and model is not created by the user", async () => {
|
||||
it("should disable delete button when user is not admin and did not create the model", async () => {
|
||||
const nonCreatedByUserModelData = {
|
||||
...defaultModelData,
|
||||
model_info: {
|
||||
@@ -242,18 +319,177 @@ describe("ModelInfoView", () => {
|
||||
error: null,
|
||||
});
|
||||
|
||||
const NON_CREATED_BY_USER_ADMIN_PROPS = {
|
||||
...DEFAULT_ADMIN_PROPS,
|
||||
userRole: "User",
|
||||
};
|
||||
|
||||
const { getByTestId } = render(<ModelInfoView {...NON_CREATED_BY_USER_ADMIN_PROPS} />, { wrapper });
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} userRole="User" />, { wrapper });
|
||||
await waitFor(() => {
|
||||
expect(getByTestId("delete-model-button")).toBeDisabled();
|
||||
const deleteButton = screen.getByRole("button", { name: /delete model/i });
|
||||
expect(deleteButton).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
it("should render health check model field for wildcard routes", async () => {
|
||||
it("should display overview and raw JSON tabs", async () => {
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("tab", { name: /overview/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole("tab", { name: /raw json/i })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should display model information in overview tab", async () => {
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Provider")).toBeInTheDocument();
|
||||
expect(screen.getByText("LiteLLM Model")).toBeInTheDocument();
|
||||
expect(screen.getByText("Pricing")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should display edit settings button when user can edit model", async () => {
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("button", { name: /edit settings/i })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should not display edit settings button when model is not a DB model", async () => {
|
||||
const nonDbModelData = {
|
||||
...defaultModelData,
|
||||
model_info: {
|
||||
...defaultModelData.model_info,
|
||||
db_model: false,
|
||||
},
|
||||
};
|
||||
|
||||
mockUseModelsInfo.mockReturnValue({
|
||||
data: {
|
||||
data: [nonDbModelData],
|
||||
},
|
||||
isLoading: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole("button", { name: /edit settings/i })).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should enter edit mode when edit settings button is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("button", { name: /edit settings/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const editButton = screen.getByRole("button", { name: /edit settings/i });
|
||||
await user.click(editButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("button", { name: /save changes/i })).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: /cancel/i })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should display form fields in edit mode", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("button", { name: /edit settings/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const editButton = screen.getByRole("button", { name: /edit settings/i });
|
||||
await user.click(editButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByPlaceholderText("Enter model name")).toBeInTheDocument();
|
||||
expect(screen.getByPlaceholderText("Enter LiteLLM model name")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should allow editing model name in edit mode", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("button", { name: /edit settings/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const editButton = screen.getByRole("button", { name: /edit settings/i });
|
||||
await user.click(editButton);
|
||||
|
||||
const modelNameInput = await screen.findByPlaceholderText("Enter model name");
|
||||
await user.clear(modelNameInput);
|
||||
await user.type(modelNameInput, "Updated Model Name");
|
||||
|
||||
expect(modelNameInput).toHaveValue("Updated Model Name");
|
||||
});
|
||||
|
||||
it("should cancel editing when cancel button is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("button", { name: /edit settings/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const editButton = screen.getByRole("button", { name: /edit settings/i });
|
||||
await user.click(editButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("button", { name: /cancel/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const cancelButton = screen.getByRole("button", { name: /cancel/i });
|
||||
await user.click(cancelButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("button", { name: /edit settings/i })).toBeInTheDocument();
|
||||
expect(screen.queryByRole("button", { name: /save changes/i })).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should save model changes when save button is clicked", async () => {
|
||||
const user = userEvent.setup();
|
||||
const mockOnModelUpdate = vi.fn();
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} onModelUpdate={mockOnModelUpdate} />, { wrapper });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("button", { name: /edit settings/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const editButton = screen.getByRole("button", { name: /edit settings/i });
|
||||
await user.click(editButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("button", { name: /save changes/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const saveButton = screen.getByRole("button", { name: /save changes/i });
|
||||
await user.click(saveButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockModelPatchUpdateCall).toHaveBeenCalled();
|
||||
expect(mockNotificationsManager.success).toHaveBeenCalledWith("Model settings updated successfully");
|
||||
expect(mockOnModelUpdate).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("should display tags section", async () => {
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Tags")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should display LiteLLM Params section", async () => {
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("LiteLLM Params")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should display health check model field for wildcard models", async () => {
|
||||
const wildcardModelData = {
|
||||
...defaultModelData,
|
||||
litellm_params: {
|
||||
@@ -270,38 +506,71 @@ describe("ModelInfoView", () => {
|
||||
error: null,
|
||||
});
|
||||
|
||||
const { getByText } = render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
await waitFor(() => {
|
||||
expect(getByText("Model Settings")).toBeInTheDocument();
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(getByText("Health Check Model")).toBeInTheDocument();
|
||||
expect(screen.getByText("Health Check Model")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should not render health check model field for non-wildcard routes", async () => {
|
||||
const { queryByText } = render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
it("should not display health check model field for non-wildcard models", async () => {
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
await waitFor(() => {
|
||||
expect(queryByText("Model Settings")).toBeInTheDocument();
|
||||
});
|
||||
await waitFor(() => {
|
||||
expect(queryByText("Health Check Model")).not.toBeInTheDocument();
|
||||
expect(screen.getByText("Model Settings")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Health Check Model")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("View Model", () => {
|
||||
it("should render the model info view", async () => {
|
||||
const { getByText } = render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
await waitFor(() => {
|
||||
expect(getByText("Model Settings")).toBeInTheDocument();
|
||||
});
|
||||
it("should display edit auto router button for auto router models", async () => {
|
||||
const autoRouterModelData = {
|
||||
...defaultModelData,
|
||||
litellm_params: {
|
||||
...defaultModelData.litellm_params,
|
||||
auto_router_config: {},
|
||||
},
|
||||
};
|
||||
|
||||
mockUseModelsInfo.mockReturnValue({
|
||||
data: {
|
||||
data: [autoRouterModelData],
|
||||
},
|
||||
isLoading: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
it("should render tags in the view model", async () => {
|
||||
const { getByText } = render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
await waitFor(() => {
|
||||
expect(getByText("Tags")).toBeInTheDocument();
|
||||
});
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole("button", { name: /edit auto router/i })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it("should display model access groups field", async () => {
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Model Access Groups")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should display guardrails field", async () => {
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Guardrails")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should display pricing information", async () => {
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Input:/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Output:/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should display created at and created by information", async () => {
|
||||
render(<ModelInfoView {...DEFAULT_ADMIN_PROPS} />, { wrapper });
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Created At/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/Created By/)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -136,11 +136,9 @@ export default function ModelInfoView({
|
||||
|
||||
useEffect(() => {
|
||||
const getExistingCredential = async () => {
|
||||
console.log("accessToken, ", accessToken);
|
||||
if (!accessToken) return;
|
||||
if (usingExistingCredential) return;
|
||||
let existingCredentialResponse = await credentialGetCall(accessToken, null, modelId);
|
||||
console.log("existingCredentialResponse, ", existingCredentialResponse);
|
||||
setExistingCredential({
|
||||
credential_name: existingCredentialResponse["credential_name"],
|
||||
credential_values: existingCredentialResponse["credential_values"],
|
||||
@@ -153,7 +151,6 @@ export default function ModelInfoView({
|
||||
// Only fetch if we don't have modelData yet
|
||||
if (modelData) return;
|
||||
let modelInfoResponse = await modelInfoV1Call(accessToken, modelId);
|
||||
console.log("modelInfoResponse, ", modelInfoResponse);
|
||||
let specificModelData = modelInfoResponse.data[0];
|
||||
if (specificModelData && !specificModelData.litellm_model_name) {
|
||||
specificModelData = {
|
||||
@@ -201,7 +198,6 @@ export default function ModelInfoView({
|
||||
}, [accessToken, modelId]);
|
||||
|
||||
const handleReuseCredential = async (values: any) => {
|
||||
console.log("values, ", values);
|
||||
if (!accessToken) return;
|
||||
let credentialItem = {
|
||||
credential_name: values.credential_name,
|
||||
@@ -212,7 +208,6 @@ export default function ModelInfoView({
|
||||
};
|
||||
NotificationsManager.info("Storing credential..");
|
||||
let credentialResponse = await credentialCreateCall(accessToken, credentialItem);
|
||||
console.log("credentialResponse, ", credentialResponse);
|
||||
NotificationsManager.success("Credential stored successfully");
|
||||
};
|
||||
|
||||
@@ -221,8 +216,6 @@ export default function ModelInfoView({
|
||||
if (!accessToken) return;
|
||||
setIsSaving(true);
|
||||
|
||||
console.log("values.model_name, ", values.model_name);
|
||||
|
||||
// Parse LiteLLM extra params from JSON text area
|
||||
let parsedExtraParams: Record<string, any> = {};
|
||||
try {
|
||||
@@ -412,7 +405,6 @@ export default function ModelInfoView({
|
||||
}
|
||||
};
|
||||
const isWildcardModel = modelData.litellm_model_name.includes("*");
|
||||
console.log("isWildcardModel, ", isWildcardModel);
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
@@ -657,7 +649,7 @@ export default function ModelInfoView({
|
||||
? (localModelData.litellm_params?.input_cost_per_token * 1_000_000).toFixed(4)
|
||||
: localModelData?.model_info?.input_cost_per_token
|
||||
? (localModelData.model_info.input_cost_per_token * 1_000_000).toFixed(4)
|
||||
: null}
|
||||
: "Not Set"}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -674,7 +666,7 @@ export default function ModelInfoView({
|
||||
? (localModelData.litellm_params.output_cost_per_token * 1_000_000).toFixed(4)
|
||||
: localModelData?.model_info?.output_cost_per_token
|
||||
? (localModelData.model_info.output_cost_per_token * 1_000_000).toFixed(4)
|
||||
: null}
|
||||
: "Not Set"}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user