mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-08 22:21:35 +00:00
Adding unit test to expand unit testing coverage
This commit is contained in:
parent
c81ac6cf46
commit
bcbf8d1de3
4 changed files with 778 additions and 0 deletions
|
|
@ -0,0 +1,187 @@
|
|||
import { describe, expect, it, vi, beforeEach } from "vitest";
|
||||
import {
|
||||
convertImageToBase64,
|
||||
createChatMultimodalMessage,
|
||||
createChatDisplayMessage,
|
||||
shouldShowChatAttachedImage,
|
||||
} from "./ChatImageUtils";
|
||||
import { MessageType } from "./types";
|
||||
|
||||
describe("ChatImageUtils", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("convertImageToBase64", () => {
|
||||
it("should convert file to base64 data URI", async () => {
|
||||
const file = new File(["test content"], "test.png", { type: "image/png" });
|
||||
const result = await convertImageToBase64(file);
|
||||
expect(result).toMatch(/^data:image\/png;base64,/);
|
||||
});
|
||||
|
||||
it("should handle different file types", async () => {
|
||||
const jpegFile = new File(["jpeg content"], "test.jpg", { type: "image/jpeg" });
|
||||
const result = await convertImageToBase64(jpegFile);
|
||||
expect(result).toMatch(/^data:image\/jpeg;base64,/);
|
||||
});
|
||||
|
||||
it("should reject on file read error", async () => {
|
||||
const file = new File(["test"], "test.png", { type: "image/png" });
|
||||
const originalReadAsDataURL = FileReader.prototype.readAsDataURL;
|
||||
|
||||
FileReader.prototype.readAsDataURL = vi.fn(function (this: FileReader) {
|
||||
setTimeout(() => {
|
||||
if (this.onerror) {
|
||||
this.onerror(new Error("Read error") as any);
|
||||
}
|
||||
}, 0);
|
||||
});
|
||||
|
||||
await expect(convertImageToBase64(file)).rejects.toThrow();
|
||||
|
||||
FileReader.prototype.readAsDataURL = originalReadAsDataURL;
|
||||
});
|
||||
});
|
||||
|
||||
describe("createChatMultimodalMessage", () => {
|
||||
it("should create multimodal message with text and image", async () => {
|
||||
const file = new File(["test content"], "test.png", { type: "image/png" });
|
||||
const inputMessage = "What is in this image?";
|
||||
|
||||
const result = await createChatMultimodalMessage(inputMessage, file);
|
||||
|
||||
expect(result.role).toBe("user");
|
||||
expect(result.content).toHaveLength(2);
|
||||
expect(result.content[0]).toEqual({ type: "text", text: inputMessage });
|
||||
expect(result.content[1]).toMatchObject({
|
||||
type: "image_url",
|
||||
image_url: {
|
||||
url: expect.stringMatching(/^data:image\/png;base64,/),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should include base64 data URI in image_url", async () => {
|
||||
const file = new File(["test content"], "test.png", { type: "image/png" });
|
||||
const result = await createChatMultimodalMessage("test", file);
|
||||
|
||||
const imageContent = result.content[1];
|
||||
expect(imageContent.type).toBe("image_url");
|
||||
if ("image_url" in imageContent && imageContent.image_url) {
|
||||
expect(imageContent.image_url.url).toMatch(/^data:/);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("createChatDisplayMessage", () => {
|
||||
it("should create display message without file", () => {
|
||||
const result = createChatDisplayMessage("Hello world", false);
|
||||
|
||||
expect(result.role).toBe("user");
|
||||
expect(result.content).toBe("Hello world");
|
||||
expect(result.imagePreviewUrl).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should create display message with PDF file", () => {
|
||||
const filePreviewUrl = "blob:test-url";
|
||||
const result = createChatDisplayMessage("Read this", true, filePreviewUrl, "document.pdf");
|
||||
|
||||
expect(result.content).toBe("Read this [PDF attached]");
|
||||
expect(result.imagePreviewUrl).toBe(filePreviewUrl);
|
||||
});
|
||||
|
||||
it("should create display message with image file", () => {
|
||||
const filePreviewUrl = "blob:test-url";
|
||||
const result = createChatDisplayMessage("Look at this", true, filePreviewUrl, "photo.jpg");
|
||||
|
||||
expect(result.content).toBe("Look at this [Image attached]");
|
||||
expect(result.imagePreviewUrl).toBe(filePreviewUrl);
|
||||
});
|
||||
|
||||
it("should create display message with file but no fileName", () => {
|
||||
const filePreviewUrl = "blob:test-url";
|
||||
const result = createChatDisplayMessage("Check this", true, filePreviewUrl);
|
||||
|
||||
expect(result.content).toBe("Check this ");
|
||||
expect(result.imagePreviewUrl).toBe(filePreviewUrl);
|
||||
});
|
||||
|
||||
it("should create display message with file but no preview URL", () => {
|
||||
const result = createChatDisplayMessage("See this", true, undefined, "image.png");
|
||||
|
||||
expect(result.content).toBe("See this [Image attached]");
|
||||
expect(result.imagePreviewUrl).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("shouldShowChatAttachedImage", () => {
|
||||
it("should return true for user message with image attachment", () => {
|
||||
const message: MessageType = {
|
||||
role: "user",
|
||||
content: "Check this [Image attached]",
|
||||
imagePreviewUrl: "blob:test-url",
|
||||
};
|
||||
|
||||
expect(shouldShowChatAttachedImage(message)).toBe(true);
|
||||
});
|
||||
|
||||
it("should return true for user message with PDF attachment", () => {
|
||||
const message: MessageType = {
|
||||
role: "user",
|
||||
content: "Read this [PDF attached]",
|
||||
imagePreviewUrl: "blob:test-url",
|
||||
};
|
||||
|
||||
expect(shouldShowChatAttachedImage(message)).toBe(true);
|
||||
});
|
||||
|
||||
it("should return false for assistant message", () => {
|
||||
const message: MessageType = {
|
||||
role: "assistant",
|
||||
content: "Here is the image [Image attached]",
|
||||
imagePreviewUrl: "blob:test-url",
|
||||
};
|
||||
|
||||
expect(shouldShowChatAttachedImage(message)).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when content is not a string", () => {
|
||||
const message: MessageType = {
|
||||
role: "user",
|
||||
content: [{ type: "input_text", text: "test" }],
|
||||
imagePreviewUrl: "blob:test-url",
|
||||
};
|
||||
|
||||
expect(shouldShowChatAttachedImage(message)).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when content does not include attachment marker", () => {
|
||||
const message: MessageType = {
|
||||
role: "user",
|
||||
content: "Just regular text",
|
||||
imagePreviewUrl: "blob:test-url",
|
||||
};
|
||||
|
||||
expect(shouldShowChatAttachedImage(message)).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when imagePreviewUrl is missing", () => {
|
||||
const message: MessageType = {
|
||||
role: "user",
|
||||
content: "Check this [Image attached]",
|
||||
};
|
||||
|
||||
expect(shouldShowChatAttachedImage(message)).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when imagePreviewUrl is empty string", () => {
|
||||
const message: MessageType = {
|
||||
role: "user",
|
||||
content: "Check this [Image attached]",
|
||||
imagePreviewUrl: "",
|
||||
};
|
||||
|
||||
expect(shouldShowChatAttachedImage(message)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,326 @@
|
|||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
||||
import CodeInterpreterOutput from "./CodeInterpreterOutput";
|
||||
|
||||
vi.mock("@/components/networking", () => ({
|
||||
getProxyBaseUrl: vi.fn(() => "https://example.com"),
|
||||
}));
|
||||
|
||||
global.fetch = vi.fn();
|
||||
|
||||
describe("CodeInterpreterOutput", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
URL.createObjectURL = vi.fn((blob) => `blob:${blob}`);
|
||||
URL.revokeObjectURL = vi.fn();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("should render", () => {
|
||||
render(<CodeInterpreterOutput code="print('hello')" accessToken="test-token" />);
|
||||
|
||||
expect(screen.getByText("Python Code Executed")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display code in syntax highlighter", async () => {
|
||||
const user = userEvent.setup();
|
||||
const code = "print('hello world')";
|
||||
const { container } = render(<CodeInterpreterOutput code={code} accessToken="test-token" />);
|
||||
|
||||
expect(screen.getByText("Python Code Executed")).toBeInTheDocument();
|
||||
|
||||
const collapseHeader = screen.getByRole("button");
|
||||
await user.click(collapseHeader);
|
||||
|
||||
await waitFor(() => {
|
||||
const codeElement = container.querySelector("code.language-python");
|
||||
expect(codeElement).toBeInTheDocument();
|
||||
expect(codeElement?.textContent).toContain(code);
|
||||
});
|
||||
});
|
||||
|
||||
it("should fetch and display images from annotations", async () => {
|
||||
const mockBlob = new Blob(["image data"], { type: "image/png" });
|
||||
const mockResponse = {
|
||||
ok: true,
|
||||
blob: vi.fn().mockResolvedValue(mockBlob),
|
||||
};
|
||||
|
||||
(global.fetch as any).mockResolvedValue(mockResponse);
|
||||
|
||||
const annotations = [
|
||||
{
|
||||
type: "container_file_citation" as const,
|
||||
container_id: "container-1",
|
||||
file_id: "file-1",
|
||||
filename: "chart.png",
|
||||
start_index: 0,
|
||||
end_index: 10,
|
||||
},
|
||||
];
|
||||
|
||||
render(
|
||||
<CodeInterpreterOutput
|
||||
code="import matplotlib.pyplot as plt"
|
||||
annotations={annotations}
|
||||
accessToken="test-token"
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
"https://example.com/v1/containers/container-1/files/file-1/content",
|
||||
expect.objectContaining({
|
||||
headers: {
|
||||
Authorization: "Bearer test-token",
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("chart.png")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should show loading state while fetching images", async () => {
|
||||
const mockBlob = new Blob(["image data"], { type: "image/png" });
|
||||
let resolveBlob: (value: Blob) => void;
|
||||
const blobPromise = new Promise<Blob>((resolve) => {
|
||||
resolveBlob = resolve;
|
||||
});
|
||||
|
||||
const mockResponse = {
|
||||
ok: true,
|
||||
blob: vi.fn().mockReturnValue(blobPromise),
|
||||
};
|
||||
|
||||
(global.fetch as any).mockResolvedValue(mockResponse);
|
||||
|
||||
const annotations = [
|
||||
{
|
||||
type: "container_file_citation" as const,
|
||||
container_id: "container-1",
|
||||
file_id: "file-1",
|
||||
filename: "chart.png",
|
||||
start_index: 0,
|
||||
end_index: 10,
|
||||
},
|
||||
];
|
||||
|
||||
render(
|
||||
<CodeInterpreterOutput
|
||||
code="import matplotlib.pyplot as plt"
|
||||
annotations={annotations}
|
||||
accessToken="test-token"
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Loading image...")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
resolveBlob!(mockBlob);
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText("Loading image...")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle download for image files", async () => {
|
||||
const user = userEvent.setup();
|
||||
const mockBlob = new Blob(["image data"], { type: "image/png" });
|
||||
const mockResponse = {
|
||||
ok: true,
|
||||
blob: vi.fn().mockResolvedValue(mockBlob),
|
||||
};
|
||||
|
||||
(global.fetch as any).mockResolvedValue(mockResponse);
|
||||
|
||||
const annotations = [
|
||||
{
|
||||
type: "container_file_citation" as const,
|
||||
container_id: "container-1",
|
||||
file_id: "file-1",
|
||||
filename: "chart.png",
|
||||
start_index: 0,
|
||||
end_index: 10,
|
||||
},
|
||||
];
|
||||
|
||||
const createElementSpy = vi.spyOn(document, "createElement");
|
||||
const appendChildSpy = vi.spyOn(document.body, "appendChild");
|
||||
const removeChildSpy = vi.spyOn(document.body, "removeChild");
|
||||
|
||||
render(
|
||||
<CodeInterpreterOutput
|
||||
code="import matplotlib.pyplot as plt"
|
||||
annotations={annotations}
|
||||
accessToken="test-token"
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("chart.png")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const downloadButton = screen.getByText("Download");
|
||||
await user.click(downloadButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
"https://example.com/v1/containers/container-1/files/file-1/content",
|
||||
expect.objectContaining({
|
||||
headers: {
|
||||
Authorization: "Bearer test-token",
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
createElementSpy.mockRestore();
|
||||
appendChildSpy.mockRestore();
|
||||
removeChildSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("should handle download for non-image files", async () => {
|
||||
const user = userEvent.setup();
|
||||
const mockBlob = new Blob(["file data"], { type: "text/plain" });
|
||||
const mockResponse = {
|
||||
ok: true,
|
||||
blob: vi.fn().mockResolvedValue(mockBlob),
|
||||
};
|
||||
|
||||
(global.fetch as any).mockResolvedValue(mockResponse);
|
||||
|
||||
const annotations = [
|
||||
{
|
||||
type: "container_file_citation" as const,
|
||||
container_id: "container-1",
|
||||
file_id: "file-1",
|
||||
filename: "data.csv",
|
||||
start_index: 0,
|
||||
end_index: 10,
|
||||
},
|
||||
];
|
||||
|
||||
render(<CodeInterpreterOutput code="import pandas as pd" annotations={annotations} accessToken="test-token" />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("data.csv")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const downloadButton = screen.getByText("data.csv").closest("button");
|
||||
expect(downloadButton).toBeInTheDocument();
|
||||
if (downloadButton) {
|
||||
await user.click(downloadButton);
|
||||
}
|
||||
|
||||
await waitFor(() => {
|
||||
expect(global.fetch).toHaveBeenCalledWith(
|
||||
"https://example.com/v1/containers/container-1/files/file-1/content",
|
||||
expect.objectContaining({
|
||||
headers: {
|
||||
Authorization: "Bearer test-token",
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("should return null when no code and no annotations", () => {
|
||||
const { container } = render(<CodeInterpreterOutput accessToken="test-token" />);
|
||||
|
||||
expect(container.firstChild).toBeNull();
|
||||
});
|
||||
|
||||
it("should handle multiple image formats", async () => {
|
||||
const mockBlob = new Blob(["image data"], { type: "image/png" });
|
||||
const mockResponse = {
|
||||
ok: true,
|
||||
blob: vi.fn().mockResolvedValue(mockBlob),
|
||||
};
|
||||
|
||||
(global.fetch as any).mockResolvedValue(mockResponse);
|
||||
|
||||
const annotations = [
|
||||
{
|
||||
type: "container_file_citation" as const,
|
||||
container_id: "container-1",
|
||||
file_id: "file-1",
|
||||
filename: "image.png",
|
||||
start_index: 0,
|
||||
end_index: 10,
|
||||
},
|
||||
{
|
||||
type: "container_file_citation" as const,
|
||||
container_id: "container-1",
|
||||
file_id: "file-2",
|
||||
filename: "image.jpg",
|
||||
start_index: 0,
|
||||
end_index: 10,
|
||||
},
|
||||
{
|
||||
type: "container_file_citation" as const,
|
||||
container_id: "container-1",
|
||||
file_id: "file-3",
|
||||
filename: "image.jpeg",
|
||||
start_index: 0,
|
||||
end_index: 10,
|
||||
},
|
||||
{
|
||||
type: "container_file_citation" as const,
|
||||
container_id: "container-1",
|
||||
file_id: "file-4",
|
||||
filename: "image.gif",
|
||||
start_index: 0,
|
||||
end_index: 10,
|
||||
},
|
||||
];
|
||||
|
||||
render(
|
||||
<CodeInterpreterOutput
|
||||
code="import matplotlib.pyplot as plt"
|
||||
annotations={annotations}
|
||||
accessToken="test-token"
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(global.fetch).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle fetch errors gracefully", async () => {
|
||||
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
(global.fetch as any).mockRejectedValue(new Error("Network error"));
|
||||
|
||||
const annotations = [
|
||||
{
|
||||
type: "container_file_citation" as const,
|
||||
container_id: "container-1",
|
||||
file_id: "file-1",
|
||||
filename: "chart.png",
|
||||
start_index: 0,
|
||||
end_index: 10,
|
||||
},
|
||||
];
|
||||
|
||||
render(
|
||||
<CodeInterpreterOutput
|
||||
code="import matplotlib.pyplot as plt"
|
||||
annotations={annotations}
|
||||
accessToken="test-token"
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(consoleErrorSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
consoleErrorSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { UnifiedSelector } from "./UnifiedSelector";
|
||||
import { EndpointId, ENDPOINT_CONFIGS } from "../endpoint_config";
|
||||
|
||||
describe("UnifiedSelector", () => {
|
||||
it("should render", () => {
|
||||
const onChange = vi.fn();
|
||||
const options = [
|
||||
{ value: "option1", label: "Option 1" },
|
||||
{ value: "option2", label: "Option 2" },
|
||||
];
|
||||
const config = ENDPOINT_CONFIGS[EndpointId.CHAT_COMPLETIONS];
|
||||
|
||||
render(<UnifiedSelector value="" options={options} loading={false} config={config} onChange={onChange} />);
|
||||
|
||||
const select = screen.getByRole("combobox");
|
||||
expect(select).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display placeholder when not loading", () => {
|
||||
const onChange = vi.fn();
|
||||
const options = [{ value: "option1", label: "Option 1" }];
|
||||
const config = ENDPOINT_CONFIGS[EndpointId.CHAT_COMPLETIONS];
|
||||
|
||||
const { container } = render(
|
||||
<UnifiedSelector value="" options={options} loading={false} config={config} onChange={onChange} />,
|
||||
);
|
||||
|
||||
const placeholder = container.querySelector(".ant-select-selection-placeholder");
|
||||
expect(placeholder).toHaveTextContent(config.selectorPlaceholder);
|
||||
});
|
||||
|
||||
it("should display loading placeholder when loading", () => {
|
||||
const onChange = vi.fn();
|
||||
const options = [{ value: "option1", label: "Option 1" }];
|
||||
const config = ENDPOINT_CONFIGS[EndpointId.CHAT_COMPLETIONS];
|
||||
|
||||
const { container } = render(
|
||||
<UnifiedSelector value="" options={options} loading={true} config={config} onChange={onChange} />,
|
||||
);
|
||||
|
||||
const placeholder = container.querySelector(".ant-select-selection-placeholder");
|
||||
expect(placeholder).toHaveTextContent(`Loading ${config.selectorLabel.toLowerCase()}s...`);
|
||||
});
|
||||
|
||||
it("should call onChange when option is selected", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onChange = vi.fn();
|
||||
const options = [
|
||||
{ value: "option1", label: "Option 1" },
|
||||
{ value: "option2", label: "Option 2" },
|
||||
];
|
||||
const config = ENDPOINT_CONFIGS[EndpointId.CHAT_COMPLETIONS];
|
||||
|
||||
render(<UnifiedSelector value="" options={options} loading={false} config={config} onChange={onChange} />);
|
||||
|
||||
const select = screen.getByRole("combobox");
|
||||
await user.click(select);
|
||||
|
||||
await waitFor(() => {
|
||||
const option = screen.getByText("Option 1");
|
||||
expect(option).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const option = screen.getByText("Option 1");
|
||||
await user.click(option);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onChange).toHaveBeenCalled();
|
||||
});
|
||||
const callArgs = onChange.mock.calls[0];
|
||||
expect(callArgs[0]).toBe("option1");
|
||||
});
|
||||
|
||||
it("should display selected value", () => {
|
||||
const onChange = vi.fn();
|
||||
const options = [
|
||||
{ value: "option1", label: "Option 1" },
|
||||
{ value: "option2", label: "Option 2" },
|
||||
];
|
||||
const config = ENDPOINT_CONFIGS[EndpointId.CHAT_COMPLETIONS];
|
||||
|
||||
const { container } = render(
|
||||
<UnifiedSelector value="option1" options={options} loading={false} config={config} onChange={onChange} />,
|
||||
);
|
||||
|
||||
const selectedValue = container.querySelector(".ant-select-selection-item");
|
||||
expect(selectedValue).toHaveTextContent("Option 1");
|
||||
});
|
||||
|
||||
it("should filter options by search input", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onChange = vi.fn();
|
||||
const options = [
|
||||
{ value: "option1", label: "Option One" },
|
||||
{ value: "option2", label: "Option Two" },
|
||||
{ value: "option3", label: "Different" },
|
||||
];
|
||||
const config = ENDPOINT_CONFIGS[EndpointId.CHAT_COMPLETIONS];
|
||||
|
||||
render(<UnifiedSelector value="" options={options} loading={false} config={config} onChange={onChange} />);
|
||||
|
||||
const select = screen.getByRole("combobox");
|
||||
await user.click(select);
|
||||
await user.type(select, "One");
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Option One")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Option Two")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Different")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should show loading spinner in notFoundContent when loading", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onChange = vi.fn();
|
||||
const options: { value: string; label: string }[] = [];
|
||||
const config = ENDPOINT_CONFIGS[EndpointId.CHAT_COMPLETIONS];
|
||||
|
||||
render(<UnifiedSelector value="" options={options} loading={true} config={config} onChange={onChange} />);
|
||||
|
||||
const select = screen.getByRole("combobox");
|
||||
await user.click(select);
|
||||
|
||||
await waitFor(() => {
|
||||
const spin = document.querySelector(".ant-spin");
|
||||
expect(spin).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should show no options message when not loading and no options", async () => {
|
||||
const user = userEvent.setup();
|
||||
const onChange = vi.fn();
|
||||
const options: { value: string; label: string }[] = [];
|
||||
const config = ENDPOINT_CONFIGS[EndpointId.CHAT_COMPLETIONS];
|
||||
|
||||
render(<UnifiedSelector value="" options={options} loading={false} config={config} onChange={onChange} />);
|
||||
|
||||
const select = screen.getByRole("combobox");
|
||||
await user.click(select);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(`No ${config.selectorLabel.toLowerCase()}s available`)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should work with agent endpoint config", () => {
|
||||
const onChange = vi.fn();
|
||||
const options = [{ value: "agent1", label: "Agent One" }];
|
||||
const config = ENDPOINT_CONFIGS[EndpointId.A2A_AGENTS];
|
||||
|
||||
const { container } = render(
|
||||
<UnifiedSelector value="" options={options} loading={false} config={config} onChange={onChange} />,
|
||||
);
|
||||
|
||||
const placeholder = container.querySelector(".ant-select-selection-placeholder");
|
||||
expect(placeholder).toHaveTextContent(config.selectorPlaceholder);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
EndpointId,
|
||||
ENDPOINT_CONFIGS,
|
||||
getAvailableEndpoints,
|
||||
getEndpointConfig,
|
||||
isAgentEndpoint,
|
||||
isModelEndpoint,
|
||||
modelOptionsToSelectorOptions,
|
||||
agentOptionsToSelectorOptions,
|
||||
getSelectionFieldName,
|
||||
getComparisonSelection,
|
||||
hasValidSelection,
|
||||
} from "./endpoint_config";
|
||||
import { Agent } from "../llm_calls/fetch_agents";
|
||||
|
||||
describe("endpoint_config", () => {
|
||||
it("should export EndpointId constants", () => {
|
||||
expect(EndpointId.CHAT_COMPLETIONS).toBe("/v1/chat/completions");
|
||||
expect(EndpointId.A2A_AGENTS).toBe("/a2a");
|
||||
});
|
||||
|
||||
it("should have endpoint configs for all endpoint IDs", () => {
|
||||
expect(ENDPOINT_CONFIGS[EndpointId.CHAT_COMPLETIONS]).toBeDefined();
|
||||
expect(ENDPOINT_CONFIGS[EndpointId.A2A_AGENTS]).toBeDefined();
|
||||
expect(ENDPOINT_CONFIGS[EndpointId.CHAT_COMPLETIONS].selectorType).toBe("model");
|
||||
expect(ENDPOINT_CONFIGS[EndpointId.A2A_AGENTS].selectorType).toBe("agent");
|
||||
});
|
||||
|
||||
it("should get available endpoints", () => {
|
||||
const endpoints = getAvailableEndpoints();
|
||||
expect(endpoints).toHaveLength(2);
|
||||
expect(endpoints).toContainEqual({
|
||||
value: EndpointId.CHAT_COMPLETIONS,
|
||||
label: "/v1/chat/completions",
|
||||
});
|
||||
expect(endpoints).toContainEqual({
|
||||
value: EndpointId.A2A_AGENTS,
|
||||
label: "/a2a (Agents)",
|
||||
});
|
||||
});
|
||||
|
||||
it("should get endpoint config by ID", () => {
|
||||
const config = getEndpointConfig(EndpointId.CHAT_COMPLETIONS);
|
||||
expect(config.id).toBe(EndpointId.CHAT_COMPLETIONS);
|
||||
expect(config.selectorType).toBe("model");
|
||||
expect(config.selectorLabel).toBe("Model");
|
||||
});
|
||||
|
||||
it("should check if endpoint is agent endpoint", () => {
|
||||
expect(isAgentEndpoint(EndpointId.A2A_AGENTS)).toBe(true);
|
||||
expect(isAgentEndpoint(EndpointId.CHAT_COMPLETIONS)).toBe(false);
|
||||
});
|
||||
|
||||
it("should check if endpoint is model endpoint", () => {
|
||||
expect(isModelEndpoint(EndpointId.CHAT_COMPLETIONS)).toBe(true);
|
||||
expect(isModelEndpoint(EndpointId.A2A_AGENTS)).toBe(false);
|
||||
});
|
||||
|
||||
it("should convert model options to selector options", () => {
|
||||
const models = ["gpt-4", "gpt-3.5-turbo", "claude-3"];
|
||||
const options = modelOptionsToSelectorOptions(models);
|
||||
expect(options).toHaveLength(3);
|
||||
expect(options[0]).toEqual({ value: "gpt-4", label: "gpt-4" });
|
||||
expect(options[1]).toEqual({ value: "gpt-3.5-turbo", label: "gpt-3.5-turbo" });
|
||||
expect(options[2]).toEqual({ value: "claude-3", label: "claude-3" });
|
||||
});
|
||||
|
||||
it("should convert agent options to selector options", () => {
|
||||
const agents: Agent[] = [
|
||||
{ agent_id: "agent-1", agent_name: "Agent One" },
|
||||
{ agent_id: "agent-2", agent_name: "Agent Two" },
|
||||
{ agent_id: "agent-3", agent_name: undefined as any },
|
||||
];
|
||||
const options = agentOptionsToSelectorOptions(agents);
|
||||
expect(options).toHaveLength(3);
|
||||
expect(options[0]).toEqual({ value: "Agent One", label: "Agent One" });
|
||||
expect(options[1]).toEqual({ value: "Agent Two", label: "Agent Two" });
|
||||
expect(options[2]).toEqual({ value: undefined, label: "agent-3" });
|
||||
});
|
||||
|
||||
it("should get selection field name based on endpoint", () => {
|
||||
expect(getSelectionFieldName(EndpointId.CHAT_COMPLETIONS)).toBe("model");
|
||||
expect(getSelectionFieldName(EndpointId.A2A_AGENTS)).toBe("agent");
|
||||
});
|
||||
|
||||
it("should get comparison selection based on endpoint", () => {
|
||||
const comparison = { model: "gpt-4", agent: "agent-1" };
|
||||
expect(getComparisonSelection(comparison, EndpointId.CHAT_COMPLETIONS)).toBe("gpt-4");
|
||||
expect(getComparisonSelection(comparison, EndpointId.A2A_AGENTS)).toBe("agent-1");
|
||||
});
|
||||
|
||||
it("should check if comparison has valid selection", () => {
|
||||
const comparisonWithModel = { model: "gpt-4", agent: "" };
|
||||
const comparisonWithAgent = { model: "", agent: "agent-1" };
|
||||
const comparisonEmpty = { model: "", agent: "" };
|
||||
const comparisonWhitespace = { model: " ", agent: "" };
|
||||
|
||||
expect(hasValidSelection(comparisonWithModel, EndpointId.CHAT_COMPLETIONS)).toBe(true);
|
||||
expect(hasValidSelection(comparisonWithAgent, EndpointId.A2A_AGENTS)).toBe(true);
|
||||
expect(hasValidSelection(comparisonEmpty, EndpointId.CHAT_COMPLETIONS)).toBe(false);
|
||||
expect(hasValidSelection(comparisonWhitespace, EndpointId.CHAT_COMPLETIONS)).toBe(false);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue