mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-08 03:08:45 +00:00
Merge pull request #18601 from BerriAI/litellm_ui_test_coverage_xd
[Infra] UI - Adding Unit Tests to Increase Coverage
This commit is contained in:
commit
dacfe153ba
5 changed files with 1403 additions and 0 deletions
|
|
@ -0,0 +1,185 @@
|
|||
import { getUiSettings } from "@/components/networking";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { renderHook, waitFor } from "@testing-library/react";
|
||||
import React, { ReactNode } from "react";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { useUISettings } from "./useUISettings";
|
||||
|
||||
// Mock the networking function
|
||||
vi.mock("@/components/networking", () => ({
|
||||
getUiSettings: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock useAuthorized hook - we can override this in individual tests
|
||||
const mockUseAuthorized = vi.fn();
|
||||
vi.mock("../useAuthorized", () => ({
|
||||
default: () => mockUseAuthorized(),
|
||||
}));
|
||||
|
||||
// Mock data
|
||||
const mockUISettings: Record<string, any> = {
|
||||
theme: "dark",
|
||||
language: "en",
|
||||
notifications: true,
|
||||
dashboard_layout: "compact",
|
||||
api_keys_visible: false,
|
||||
};
|
||||
|
||||
describe("useUISettings", () => {
|
||||
let queryClient: QueryClient;
|
||||
|
||||
beforeEach(() => {
|
||||
queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Reset all mocks
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Set default mock for useAuthorized (enabled state)
|
||||
mockUseAuthorized.mockReturnValue({
|
||||
accessToken: "test-access-token",
|
||||
userRole: "Admin",
|
||||
userId: "test-user-id",
|
||||
token: "test-token",
|
||||
userEmail: "test@example.com",
|
||||
premiumUser: false,
|
||||
disabledPersonalKeyCreation: null,
|
||||
showSSOBanner: false,
|
||||
});
|
||||
});
|
||||
|
||||
const wrapper = ({ children }: { children: ReactNode }) =>
|
||||
React.createElement(QueryClientProvider, { client: queryClient }, children);
|
||||
|
||||
it("should return UI settings data when query is successful", async () => {
|
||||
// Mock successful API call
|
||||
(getUiSettings as any).mockResolvedValue(mockUISettings);
|
||||
|
||||
const { result } = renderHook(() => useUISettings(), { wrapper });
|
||||
|
||||
// Initially loading
|
||||
expect(result.current.isLoading).toBe(true);
|
||||
expect(result.current.data).toBeUndefined();
|
||||
|
||||
// Wait for success
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
expect(result.current.isSuccess).toBe(true);
|
||||
});
|
||||
|
||||
expect(result.current.data).toEqual(mockUISettings);
|
||||
expect(result.current.error).toBeNull();
|
||||
expect(getUiSettings).toHaveBeenCalledWith("test-access-token");
|
||||
expect(getUiSettings).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should handle error when getUiSettings fails", async () => {
|
||||
const errorMessage = "Failed to fetch UI settings";
|
||||
const testError = new Error(errorMessage);
|
||||
|
||||
// Mock failed API call
|
||||
(getUiSettings as any).mockRejectedValue(testError);
|
||||
|
||||
const { result } = renderHook(() => useUISettings(), { wrapper });
|
||||
|
||||
// Initially loading
|
||||
expect(result.current.isLoading).toBe(true);
|
||||
|
||||
// Wait for error
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
expect(result.current.isError).toBe(true);
|
||||
});
|
||||
|
||||
expect(result.current.error).toEqual(testError);
|
||||
expect(result.current.data).toBeUndefined();
|
||||
expect(getUiSettings).toHaveBeenCalledWith("test-access-token");
|
||||
expect(getUiSettings).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should not execute query when accessToken is missing", async () => {
|
||||
// Mock missing accessToken
|
||||
mockUseAuthorized.mockReturnValue({
|
||||
accessToken: null,
|
||||
userRole: "Admin",
|
||||
userId: "test-user-id",
|
||||
token: null,
|
||||
userEmail: "test@example.com",
|
||||
premiumUser: false,
|
||||
disabledPersonalKeyCreation: null,
|
||||
showSSOBanner: false,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useUISettings(), { wrapper });
|
||||
|
||||
// Query should not execute
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
expect(result.current.data).toBeUndefined();
|
||||
expect(result.current.isFetched).toBe(false);
|
||||
|
||||
// API should not be called
|
||||
expect(getUiSettings).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not execute query when accessToken is empty string", async () => {
|
||||
// Mock empty accessToken
|
||||
mockUseAuthorized.mockReturnValue({
|
||||
accessToken: "",
|
||||
userRole: "Admin",
|
||||
userId: "test-user-id",
|
||||
token: "",
|
||||
userEmail: "test@example.com",
|
||||
premiumUser: false,
|
||||
disabledPersonalKeyCreation: null,
|
||||
showSSOBanner: false,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useUISettings(), { wrapper });
|
||||
|
||||
// Query should not execute
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
expect(result.current.data).toBeUndefined();
|
||||
expect(result.current.isFetched).toBe(false);
|
||||
|
||||
// API should not be called
|
||||
expect(getUiSettings).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should return empty object when API returns empty settings", async () => {
|
||||
// Mock API returning empty object
|
||||
(getUiSettings as any).mockResolvedValue({});
|
||||
|
||||
const { result } = renderHook(() => useUISettings(), { wrapper });
|
||||
|
||||
// Wait for success
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
expect(result.current.isSuccess).toBe(true);
|
||||
});
|
||||
|
||||
expect(result.current.data).toEqual({});
|
||||
expect(getUiSettings).toHaveBeenCalledWith("test-access-token");
|
||||
});
|
||||
|
||||
it("should handle network timeout error", async () => {
|
||||
const timeoutError = new Error("Network timeout");
|
||||
|
||||
// Mock network timeout
|
||||
(getUiSettings as any).mockRejectedValue(timeoutError);
|
||||
|
||||
const { result } = renderHook(() => useUISettings(), { wrapper });
|
||||
|
||||
// Wait for error
|
||||
await waitFor(() => {
|
||||
expect(result.current.isError).toBe(true);
|
||||
});
|
||||
|
||||
expect(result.current.error).toEqual(timeoutError);
|
||||
expect(result.current.data).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,219 @@
|
|||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { ModelGroup } from "../llm_calls/fetch_models";
|
||||
import { determineEndpointType } from "./EndpointUtils";
|
||||
import { EndpointType } from "./mode_endpoint_mapping";
|
||||
|
||||
// Mock the getEndpointType function
|
||||
vi.mock("./mode_endpoint_mapping", () => ({
|
||||
EndpointType: {
|
||||
IMAGE: "image",
|
||||
VIDEO: "video",
|
||||
CHAT: "chat",
|
||||
RESPONSES: "responses",
|
||||
IMAGE_EDITS: "image_edits",
|
||||
ANTHROPIC_MESSAGES: "anthropic_messages",
|
||||
EMBEDDINGS: "embeddings",
|
||||
SPEECH: "speech",
|
||||
TRANSCRIPTION: "transcription",
|
||||
A2A_AGENTS: "a2a_agents",
|
||||
},
|
||||
getEndpointType: vi.fn(),
|
||||
ModelMode: {
|
||||
AUDIO_SPEECH: "audio_speech",
|
||||
AUDIO_TRANSCRIPTION: "audio_transcription",
|
||||
IMAGE_GENERATION: "image_generation",
|
||||
VIDEO_GENERATION: "video_generation",
|
||||
CHAT: "chat",
|
||||
RESPONSES: "responses",
|
||||
IMAGE_EDITS: "image_edits",
|
||||
ANTHROPIC_MESSAGES: "anthropic_messages",
|
||||
EMBEDDING: "embedding",
|
||||
},
|
||||
}));
|
||||
|
||||
// Import the mocked function
|
||||
import { getEndpointType } from "./mode_endpoint_mapping";
|
||||
|
||||
describe("determineEndpointType", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("should return the correct endpoint type when model is found and has a valid mode", () => {
|
||||
const mockModelInfo: ModelGroup[] = [
|
||||
{
|
||||
model_group: "gpt-3.5-turbo",
|
||||
mode: "chat",
|
||||
},
|
||||
{
|
||||
model_group: "dall-e-3",
|
||||
mode: "image_generation",
|
||||
},
|
||||
];
|
||||
|
||||
// Mock getEndpointType to return IMAGE for image_generation mode
|
||||
vi.mocked(getEndpointType).mockReturnValue(EndpointType.IMAGE);
|
||||
|
||||
const result = determineEndpointType("dall-e-3", mockModelInfo);
|
||||
|
||||
expect(getEndpointType).toHaveBeenCalledWith("image_generation");
|
||||
expect(result).toBe(EndpointType.IMAGE);
|
||||
});
|
||||
|
||||
it("should return CHAT endpoint type when model is found but has no mode", () => {
|
||||
const mockModelInfo: ModelGroup[] = [
|
||||
{
|
||||
model_group: "gpt-3.5-turbo",
|
||||
// No mode property
|
||||
},
|
||||
];
|
||||
|
||||
const result = determineEndpointType("gpt-3.5-turbo", mockModelInfo);
|
||||
|
||||
expect(getEndpointType).not.toHaveBeenCalled();
|
||||
expect(result).toBe(EndpointType.CHAT);
|
||||
});
|
||||
|
||||
it("should return CHAT endpoint type when model is not found in modelInfo", () => {
|
||||
const mockModelInfo: ModelGroup[] = [
|
||||
{
|
||||
model_group: "gpt-3.5-turbo",
|
||||
mode: "chat",
|
||||
},
|
||||
];
|
||||
|
||||
const result = determineEndpointType("non-existent-model", mockModelInfo);
|
||||
|
||||
expect(getEndpointType).not.toHaveBeenCalled();
|
||||
expect(result).toBe(EndpointType.CHAT);
|
||||
});
|
||||
|
||||
it("should return CHAT endpoint type when modelInfo array is empty", () => {
|
||||
const mockModelInfo: ModelGroup[] = [];
|
||||
|
||||
const result = determineEndpointType("any-model", mockModelInfo);
|
||||
|
||||
expect(getEndpointType).not.toHaveBeenCalled();
|
||||
expect(result).toBe(EndpointType.CHAT);
|
||||
});
|
||||
|
||||
it("should handle different mode types correctly", () => {
|
||||
const mockModelInfo: ModelGroup[] = [
|
||||
{
|
||||
model_group: "tts-model",
|
||||
mode: "audio_speech",
|
||||
},
|
||||
{
|
||||
model_group: "whisper-model",
|
||||
mode: "audio_transcription",
|
||||
},
|
||||
{
|
||||
model_group: "embedding-model",
|
||||
mode: "embedding",
|
||||
},
|
||||
{
|
||||
model_group: "video-model",
|
||||
mode: "video_generation",
|
||||
},
|
||||
];
|
||||
|
||||
// Test speech mode
|
||||
vi.mocked(getEndpointType).mockReturnValueOnce(EndpointType.SPEECH);
|
||||
const speechResult = determineEndpointType("tts-model", mockModelInfo);
|
||||
expect(getEndpointType).toHaveBeenCalledWith("audio_speech");
|
||||
expect(speechResult).toBe(EndpointType.SPEECH);
|
||||
|
||||
// Reset mock for next test
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Test transcription mode
|
||||
vi.mocked(getEndpointType).mockReturnValueOnce(EndpointType.TRANSCRIPTION);
|
||||
const transcriptionResult = determineEndpointType("whisper-model", mockModelInfo);
|
||||
expect(getEndpointType).toHaveBeenCalledWith("audio_transcription");
|
||||
expect(transcriptionResult).toBe(EndpointType.TRANSCRIPTION);
|
||||
|
||||
// Reset mock for next test
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Test embedding mode
|
||||
vi.mocked(getEndpointType).mockReturnValueOnce(EndpointType.EMBEDDINGS);
|
||||
const embeddingResult = determineEndpointType("embedding-model", mockModelInfo);
|
||||
expect(getEndpointType).toHaveBeenCalledWith("embedding");
|
||||
expect(embeddingResult).toBe(EndpointType.EMBEDDINGS);
|
||||
|
||||
// Reset mock for next test
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Test video mode
|
||||
vi.mocked(getEndpointType).mockReturnValueOnce(EndpointType.VIDEO);
|
||||
const videoResult = determineEndpointType("video-model", mockModelInfo);
|
||||
expect(getEndpointType).toHaveBeenCalledWith("video_generation");
|
||||
expect(videoResult).toBe(EndpointType.VIDEO);
|
||||
});
|
||||
|
||||
it("should prioritize the first matching model when there are duplicates", () => {
|
||||
const mockModelInfo: ModelGroup[] = [
|
||||
{
|
||||
model_group: "gpt-3.5-turbo",
|
||||
mode: "chat",
|
||||
},
|
||||
{
|
||||
model_group: "gpt-3.5-turbo",
|
||||
mode: "image_generation", // Different mode for same model name
|
||||
},
|
||||
];
|
||||
|
||||
vi.mocked(getEndpointType).mockReturnValue(EndpointType.CHAT);
|
||||
|
||||
const result = determineEndpointType("gpt-3.5-turbo", mockModelInfo);
|
||||
|
||||
expect(getEndpointType).toHaveBeenCalledWith("chat");
|
||||
expect(result).toBe(EndpointType.CHAT);
|
||||
});
|
||||
|
||||
it("should handle models with undefined mode property explicitly set", () => {
|
||||
const mockModelInfo: ModelGroup[] = [
|
||||
{
|
||||
model_group: "test-model",
|
||||
mode: undefined,
|
||||
},
|
||||
];
|
||||
|
||||
const result = determineEndpointType("test-model", mockModelInfo);
|
||||
|
||||
expect(getEndpointType).not.toHaveBeenCalled();
|
||||
expect(result).toBe(EndpointType.CHAT);
|
||||
});
|
||||
|
||||
it("should handle models with empty string mode", () => {
|
||||
const mockModelInfo: ModelGroup[] = [
|
||||
{
|
||||
model_group: "test-model",
|
||||
mode: "",
|
||||
},
|
||||
];
|
||||
|
||||
const result = determineEndpointType("test-model", mockModelInfo);
|
||||
|
||||
// Empty string is falsy, so getEndpointType should not be called
|
||||
expect(getEndpointType).not.toHaveBeenCalled();
|
||||
expect(result).toBe(EndpointType.CHAT);
|
||||
});
|
||||
|
||||
it("should handle case-sensitive model group matching", () => {
|
||||
const mockModelInfo: ModelGroup[] = [
|
||||
{
|
||||
model_group: "GPT-3.5-TURBO",
|
||||
mode: "chat",
|
||||
},
|
||||
];
|
||||
|
||||
vi.mocked(getEndpointType).mockReturnValue(EndpointType.CHAT);
|
||||
|
||||
// Test with different case - should not match
|
||||
const result = determineEndpointType("gpt-3.5-turbo", mockModelInfo);
|
||||
|
||||
expect(getEndpointType).not.toHaveBeenCalled();
|
||||
expect(result).toBe(EndpointType.CHAT);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
import { act, fireEvent, render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import ToolsCard from "./ToolsCard";
|
||||
import { Tool } from "./types";
|
||||
|
||||
describe("ToolsCard", () => {
|
||||
const mockTools: Tool[] = [
|
||||
{
|
||||
name: "Calculator",
|
||||
description: "Performs mathematical calculations",
|
||||
json: '{"type": "function", "function": {"name": "calculate"}}',
|
||||
},
|
||||
{
|
||||
name: "Weather API",
|
||||
description: "Gets current weather information",
|
||||
json: '{"type": "function", "function": {"name": "get_weather"}}',
|
||||
},
|
||||
];
|
||||
|
||||
const defaultProps = {
|
||||
tools: [] as Tool[],
|
||||
onAddTool: vi.fn(),
|
||||
onEditTool: vi.fn(),
|
||||
onRemoveTool: vi.fn(),
|
||||
};
|
||||
|
||||
it("should render the component", () => {
|
||||
render(<ToolsCard {...defaultProps} />);
|
||||
expect(screen.getByText("Tools")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display no tools message when tools array is empty", () => {
|
||||
render(<ToolsCard {...defaultProps} />);
|
||||
expect(screen.getByText("No tools added")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should render tools when provided", () => {
|
||||
render(<ToolsCard {...defaultProps} tools={mockTools} />);
|
||||
|
||||
expect(screen.getByText("Calculator")).toBeInTheDocument();
|
||||
expect(screen.getByText("Performs mathematical calculations")).toBeInTheDocument();
|
||||
expect(screen.getByText("Weather API")).toBeInTheDocument();
|
||||
expect(screen.getByText("Gets current weather information")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should call onAddTool when Add button is clicked", () => {
|
||||
const mockOnAddTool = vi.fn();
|
||||
render(<ToolsCard {...defaultProps} onAddTool={mockOnAddTool} />);
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByRole("button", { name: /add/i }));
|
||||
});
|
||||
|
||||
expect(mockOnAddTool).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should call onEditTool with correct index when Edit button is clicked", () => {
|
||||
const mockOnEditTool = vi.fn();
|
||||
render(<ToolsCard {...defaultProps} tools={mockTools} onEditTool={mockOnEditTool} />);
|
||||
|
||||
const editButtons = screen.getAllByText("Edit");
|
||||
act(() => {
|
||||
fireEvent.click(editButtons[0]);
|
||||
});
|
||||
|
||||
expect(mockOnEditTool).toHaveBeenCalledWith(0);
|
||||
expect(mockOnEditTool).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should call onRemoveTool with correct index when remove button is clicked", () => {
|
||||
const mockOnRemoveTool = vi.fn();
|
||||
render(<ToolsCard {...defaultProps} tools={mockTools} onRemoveTool={mockOnRemoveTool} />);
|
||||
|
||||
const removeButtons = screen.getAllByRole("button", { name: "" });
|
||||
act(() => {
|
||||
fireEvent.click(removeButtons[0]);
|
||||
});
|
||||
|
||||
expect(mockOnRemoveTool).toHaveBeenCalledWith(0);
|
||||
expect(mockOnRemoveTool).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,473 @@
|
|||
import { describe, it, expect, vi, beforeEach, afterEach, type Mock } from "vitest";
|
||||
import { render, screen, fireEvent, act, waitFor } from "@testing-library/react";
|
||||
import VersionHistorySidePanel from "./VersionHistorySidePanel";
|
||||
import { getPromptVersions } from "../../networking";
|
||||
import type { PromptSpec } from "../../networking";
|
||||
|
||||
// Mock the networking function
|
||||
vi.mock("../../networking", () => ({
|
||||
getPromptVersions: vi.fn(),
|
||||
}));
|
||||
|
||||
const mockGetPromptVersions = getPromptVersions as Mock;
|
||||
|
||||
// Mock Ant Design components that might need special handling
|
||||
vi.mock("antd", async () => {
|
||||
const actual = await vi.importActual("antd");
|
||||
return {
|
||||
...actual,
|
||||
Drawer: ({ children, title, onClose, open, width, placement, mask, maskClosable }: any) => (
|
||||
<div
|
||||
data-testid="drawer"
|
||||
data-open={open}
|
||||
data-title={title}
|
||||
data-mask={String(mask)}
|
||||
data-maskclosable={String(maskClosable)}
|
||||
>
|
||||
<div data-testid="drawer-header">{title}</div>
|
||||
<button data-testid="drawer-close" onClick={onClose}>
|
||||
×
|
||||
</button>
|
||||
<div data-testid="drawer-content">{children}</div>
|
||||
</div>
|
||||
),
|
||||
List: ({ children, dataSource, renderItem }: any) => (
|
||||
<div data-testid="list">{dataSource?.map((item: any, index: number) => renderItem(item, index))}</div>
|
||||
),
|
||||
Skeleton: ({ active }: any) => (
|
||||
<div data-testid="skeleton" data-active={active}>
|
||||
Loading...
|
||||
</div>
|
||||
),
|
||||
Tag: ({ children, color, className }: any) => (
|
||||
<span data-testid="tag" data-color={color} className={className}>
|
||||
{children}
|
||||
</span>
|
||||
),
|
||||
Typography: {
|
||||
Text: ({ children, type, className }: any) => (
|
||||
<span data-testid="text" data-type={type} className={className}>
|
||||
{children}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe("VersionHistorySidePanel", () => {
|
||||
// Mock data
|
||||
const mockPromptVersions: PromptSpec[] = [
|
||||
{
|
||||
prompt_id: "test-prompt.v2",
|
||||
litellm_params: { prompt_id: "test-prompt.v2" },
|
||||
prompt_info: { prompt_type: "db" },
|
||||
version: 2,
|
||||
created_at: "2024-01-15T10:30:00Z",
|
||||
},
|
||||
{
|
||||
prompt_id: "test-prompt.v1",
|
||||
litellm_params: { prompt_id: "test-prompt.v1" },
|
||||
prompt_info: { prompt_type: "db" },
|
||||
version: 1,
|
||||
created_at: "2024-01-10T09:00:00Z",
|
||||
},
|
||||
{
|
||||
prompt_id: "test-prompt.v3",
|
||||
litellm_params: { prompt_id: "test-prompt.v3" },
|
||||
prompt_info: { prompt_type: "config" },
|
||||
version: 3,
|
||||
created_at: "2024-01-20T14:15:00Z",
|
||||
},
|
||||
];
|
||||
|
||||
const mockPromptVersionsWithoutExplicitVersion = [
|
||||
{
|
||||
prompt_id: "test-prompt.v2",
|
||||
litellm_params: { prompt_id: "test-prompt.v2" },
|
||||
prompt_info: { prompt_type: "db" },
|
||||
created_at: "2024-01-15T10:30:00Z",
|
||||
},
|
||||
{
|
||||
prompt_id: "test-prompt.v1",
|
||||
litellm_params: { prompt_id: "test-prompt.v1" },
|
||||
prompt_info: { prompt_type: "db" },
|
||||
created_at: "2024-01-10T09:00:00Z",
|
||||
},
|
||||
];
|
||||
|
||||
const defaultProps = {
|
||||
isOpen: true,
|
||||
onClose: vi.fn(),
|
||||
accessToken: "test-token",
|
||||
promptId: "test-prompt.v2",
|
||||
activeVersionId: "test-prompt.v2",
|
||||
onSelectVersion: vi.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
// Mock successful response by default
|
||||
mockGetPromptVersions.mockResolvedValue({
|
||||
prompts: mockPromptVersions,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllTimers();
|
||||
});
|
||||
|
||||
describe("Component Rendering", () => {
|
||||
it("should render the component with drawer", async () => {
|
||||
await act(async () => {
|
||||
render(<VersionHistorySidePanel {...defaultProps} />);
|
||||
});
|
||||
expect(screen.getByTestId("drawer")).toBeInTheDocument();
|
||||
expect(screen.getByText("Version History")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should not render when isOpen is false", async () => {
|
||||
await act(async () => {
|
||||
render(<VersionHistorySidePanel {...defaultProps} isOpen={false} />);
|
||||
});
|
||||
// The drawer should still be rendered but with open=false
|
||||
const drawer = screen.getByTestId("drawer");
|
||||
expect(drawer).toHaveAttribute("data-open", "false");
|
||||
});
|
||||
|
||||
it("should show loading skeleton initially", async () => {
|
||||
// Mock a delayed response to show loading state
|
||||
mockGetPromptVersions.mockImplementationOnce(
|
||||
() => new Promise((resolve) => setTimeout(() => resolve({ prompts: mockPromptVersions }), 100)),
|
||||
);
|
||||
|
||||
render(<VersionHistorySidePanel {...defaultProps} />);
|
||||
expect(screen.getByTestId("skeleton")).toBeInTheDocument();
|
||||
|
||||
// Wait for loading to complete
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId("skeleton")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should show empty state when no versions are available", async () => {
|
||||
mockGetPromptVersions.mockResolvedValueOnce({ prompts: [] });
|
||||
|
||||
render(<VersionHistorySidePanel {...defaultProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("No version history available.")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should render version list when data is loaded", async () => {
|
||||
render(<VersionHistorySidePanel {...defaultProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("v2")).toBeInTheDocument();
|
||||
expect(screen.getByText("v1")).toBeInTheDocument();
|
||||
expect(screen.getByText("v3")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Check that Latest tag is shown for the first item
|
||||
const latestTags = screen.getAllByText("Latest");
|
||||
expect(latestTags.length).toBeGreaterThan(0);
|
||||
|
||||
// Check Active tag is shown for the active version
|
||||
expect(screen.getByText("Active")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Version Selection and Highlighting", () => {
|
||||
it("should highlight the active version correctly", async () => {
|
||||
render(<VersionHistorySidePanel {...defaultProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
const versionItems = screen.getAllByTestId("tag");
|
||||
// Should have Active tag for the selected version
|
||||
expect(screen.getByText("Active")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should highlight the latest version when no activeVersionId is provided", async () => {
|
||||
render(<VersionHistorySidePanel {...defaultProps} activeVersionId={undefined} />);
|
||||
|
||||
await waitFor(() => {
|
||||
const latestTags = screen.getAllByText("Latest");
|
||||
expect(latestTags.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it("should call onSelectVersion when a version is clicked", async () => {
|
||||
const mockOnSelectVersion = vi.fn();
|
||||
render(<VersionHistorySidePanel {...defaultProps} onSelectVersion={mockOnSelectVersion} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("v1")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const versionItem = screen.getByText("v1").closest("div");
|
||||
expect(versionItem).toBeInTheDocument();
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(versionItem!);
|
||||
});
|
||||
|
||||
expect(mockOnSelectVersion).toHaveBeenCalledWith(mockPromptVersions[1]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Version Number Extraction", () => {
|
||||
it("should extract version from explicit version field", async () => {
|
||||
render(<VersionHistorySidePanel {...defaultProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("v2")).toBeInTheDocument();
|
||||
expect(screen.getByText("v3")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should extract version from prompt_id with .v suffix", async () => {
|
||||
mockGetPromptVersions.mockResolvedValueOnce({
|
||||
prompts: mockPromptVersionsWithoutExplicitVersion,
|
||||
});
|
||||
|
||||
render(<VersionHistorySidePanel {...defaultProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("v2")).toBeInTheDocument();
|
||||
expect(screen.getByText("v1")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should extract version from prompt_id with _v suffix", async () => {
|
||||
const versionsWithUnderscore = [
|
||||
{
|
||||
prompt_id: "test-prompt_v2",
|
||||
litellm_params: { prompt_id: "test-prompt_v2" },
|
||||
prompt_info: { prompt_type: "db" },
|
||||
created_at: "2024-01-15T10:30:00Z",
|
||||
},
|
||||
];
|
||||
|
||||
mockGetPromptVersions.mockResolvedValueOnce({
|
||||
prompts: versionsWithUnderscore,
|
||||
});
|
||||
|
||||
render(<VersionHistorySidePanel {...defaultProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("v2")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should default to v1 when no version info is available", async () => {
|
||||
const versionWithoutVersionInfo = [
|
||||
{
|
||||
prompt_id: "test-prompt",
|
||||
litellm_params: { prompt_id: "test-prompt" },
|
||||
prompt_info: { prompt_type: "db" },
|
||||
created_at: "2024-01-15T10:30:00Z",
|
||||
},
|
||||
];
|
||||
|
||||
mockGetPromptVersions.mockResolvedValueOnce({
|
||||
prompts: versionWithoutVersionInfo,
|
||||
});
|
||||
|
||||
render(<VersionHistorySidePanel {...defaultProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("v1")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Date Formatting", () => {
|
||||
it("should format dates correctly", async () => {
|
||||
render(<VersionHistorySidePanel {...defaultProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
// Check that dates are displayed (format: YYYY-MM-DD HH:MM:SS)
|
||||
const dateElements = screen.getAllByTestId("text");
|
||||
const dateText = dateElements.find((el) => el.textContent?.match(/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/));
|
||||
expect(dateText).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
it("should show dash for missing dates", async () => {
|
||||
const versionsWithoutDates = [
|
||||
{
|
||||
prompt_id: "test-prompt.v1",
|
||||
litellm_params: { prompt_id: "test-prompt.v1" },
|
||||
prompt_info: { prompt_type: "db" },
|
||||
version: 1,
|
||||
},
|
||||
];
|
||||
|
||||
mockGetPromptVersions.mockResolvedValueOnce({
|
||||
prompts: versionsWithoutDates,
|
||||
});
|
||||
|
||||
render(<VersionHistorySidePanel {...defaultProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("-")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Prompt Type Display", () => {
|
||||
it("should show 'Saved to Database' for db prompts", async () => {
|
||||
render(<VersionHistorySidePanel {...defaultProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
const dbTexts = screen.getAllByText("Saved to Database");
|
||||
expect(dbTexts.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
it("should show 'Config Prompt' for config prompts", async () => {
|
||||
render(<VersionHistorySidePanel {...defaultProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Config Prompt")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Network Calls and Data Fetching", () => {
|
||||
it("should call getPromptVersions with correct parameters", async () => {
|
||||
render(<VersionHistorySidePanel {...defaultProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getPromptVersions).toHaveBeenCalledWith("test-token", "test-prompt");
|
||||
});
|
||||
});
|
||||
|
||||
it("should strip .v suffix from promptId when fetching versions", async () => {
|
||||
render(<VersionHistorySidePanel {...defaultProps} promptId="test-prompt.v3" />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getPromptVersions).toHaveBeenCalledWith("test-token", "test-prompt");
|
||||
});
|
||||
});
|
||||
|
||||
it("should not fetch versions when isOpen is false", () => {
|
||||
render(<VersionHistorySidePanel {...defaultProps} isOpen={false} />);
|
||||
|
||||
expect(getPromptVersions).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not fetch versions when accessToken is null", () => {
|
||||
render(<VersionHistorySidePanel {...defaultProps} accessToken={null} />);
|
||||
|
||||
expect(getPromptVersions).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not fetch versions when promptId is not provided", () => {
|
||||
render(<VersionHistorySidePanel {...defaultProps} promptId="" />);
|
||||
|
||||
expect(getPromptVersions).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should refetch versions when props change", async () => {
|
||||
const { rerender } = render(<VersionHistorySidePanel {...defaultProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getPromptVersions).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
rerender(<VersionHistorySidePanel {...defaultProps} promptId="different-prompt.v1" />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getPromptVersions).toHaveBeenCalledTimes(2);
|
||||
expect(getPromptVersions).toHaveBeenCalledWith("test-token", "different-prompt");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Error Handling", () => {
|
||||
it("should handle network errors gracefully", async () => {
|
||||
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||
mockGetPromptVersions.mockRejectedValueOnce(new Error("Network error"));
|
||||
|
||||
render(<VersionHistorySidePanel {...defaultProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(consoleSpy).toHaveBeenCalledWith("Error fetching prompt versions:", expect.any(Error));
|
||||
});
|
||||
|
||||
// Should show empty state when there's an error
|
||||
expect(screen.getByText("No version history available.")).toBeInTheDocument();
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe("User Interactions", () => {
|
||||
it("should call onClose when close button is clicked", () => {
|
||||
const mockOnClose = vi.fn();
|
||||
render(<VersionHistorySidePanel {...defaultProps} onClose={mockOnClose} />);
|
||||
|
||||
const drawer = screen.getByTestId("drawer");
|
||||
act(() => {
|
||||
fireEvent.click(drawer); // Simulate close action
|
||||
});
|
||||
|
||||
// Note: This test assumes the drawer handles close events.
|
||||
// In a real scenario, you'd test the actual close trigger.
|
||||
});
|
||||
|
||||
it("should prevent interaction with main content when drawer is open", () => {
|
||||
render(<VersionHistorySidePanel {...defaultProps} />);
|
||||
|
||||
const drawer = screen.getByTestId("drawer");
|
||||
// The mask and maskClosable props are passed as boolean false to disable them
|
||||
expect(drawer).toHaveAttribute("data-mask", "false");
|
||||
expect(drawer).toHaveAttribute("data-maskclosable", "false");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Edge Cases", () => {
|
||||
it("should handle activeVersionId with .v suffix correctly", async () => {
|
||||
render(<VersionHistorySidePanel {...defaultProps} activeVersionId="test-prompt.v1" />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Active")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should handle activeVersionId with _v suffix correctly", async () => {
|
||||
const versionsWithUnderscore = [
|
||||
{
|
||||
prompt_id: "test-prompt_v2",
|
||||
litellm_params: { prompt_id: "test-prompt_v2" },
|
||||
prompt_info: { prompt_type: "db" },
|
||||
version: 2,
|
||||
created_at: "2024-01-15T10:30:00Z",
|
||||
},
|
||||
];
|
||||
|
||||
mockGetPromptVersions.mockResolvedValueOnce({
|
||||
prompts: versionsWithUnderscore,
|
||||
});
|
||||
|
||||
render(<VersionHistorySidePanel {...defaultProps} activeVersionId="test-prompt_v2" />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Active")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should sort versions correctly with version field", async () => {
|
||||
// The component doesn't explicitly sort, but we can verify the order from the API response
|
||||
render(<VersionHistorySidePanel {...defaultProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
const versionElements = screen.getAllByTestId("tag");
|
||||
// Verify versions are displayed as they come from the API
|
||||
expect(screen.getByText("v2")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,444 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { PromptType } from "./types";
|
||||
import {
|
||||
convertToDotPrompt,
|
||||
extractVariables,
|
||||
getVersionNumber,
|
||||
parseExistingPrompt,
|
||||
stripVersionFromPromptId,
|
||||
} from "./utils";
|
||||
|
||||
describe("extractVariables", () => {
|
||||
it("should extract variables from messages", () => {
|
||||
const prompt: PromptType = {
|
||||
name: "test",
|
||||
model: "gpt-4",
|
||||
config: {},
|
||||
tools: [],
|
||||
developerMessage: "",
|
||||
messages: [
|
||||
{ role: "user", content: "Hello {{name}}, how are you?" },
|
||||
{ role: "assistant", content: "I am fine {{name}}" },
|
||||
],
|
||||
};
|
||||
|
||||
const result = extractVariables(prompt);
|
||||
expect(result).toEqual(["name"]);
|
||||
});
|
||||
|
||||
it("should extract variables from developer message", () => {
|
||||
const prompt: PromptType = {
|
||||
name: "test",
|
||||
model: "gpt-4",
|
||||
config: {},
|
||||
tools: [],
|
||||
developerMessage: "You are {{role}} assistant",
|
||||
messages: [{ role: "user", content: "Hello" }],
|
||||
};
|
||||
|
||||
const result = extractVariables(prompt);
|
||||
expect(result).toEqual(["role"]);
|
||||
});
|
||||
|
||||
it("should extract variables from both messages and developer message", () => {
|
||||
const prompt: PromptType = {
|
||||
name: "test",
|
||||
model: "gpt-4",
|
||||
config: {},
|
||||
tools: [],
|
||||
developerMessage: "You are {{role}} assistant",
|
||||
messages: [
|
||||
{ role: "user", content: "Hello {{name}}" },
|
||||
{ role: "assistant", content: "Hi {{name}}, I am {{role}}" },
|
||||
],
|
||||
};
|
||||
|
||||
const result = extractVariables(prompt);
|
||||
expect(result.sort()).toEqual(["name", "role"].sort());
|
||||
});
|
||||
|
||||
it("should return empty array when no variables present", () => {
|
||||
const prompt: PromptType = {
|
||||
name: "test",
|
||||
model: "gpt-4",
|
||||
config: {},
|
||||
tools: [],
|
||||
developerMessage: "You are an assistant",
|
||||
messages: [{ role: "user", content: "Hello world" }],
|
||||
};
|
||||
|
||||
const result = extractVariables(prompt);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it("should handle duplicate variables", () => {
|
||||
const prompt: PromptType = {
|
||||
name: "test",
|
||||
model: "gpt-4",
|
||||
config: {},
|
||||
tools: [],
|
||||
developerMessage: "",
|
||||
messages: [
|
||||
{ role: "user", content: "Hello {{name}}" },
|
||||
{ role: "assistant", content: "Hi {{name}} again" },
|
||||
],
|
||||
};
|
||||
|
||||
const result = extractVariables(prompt);
|
||||
expect(result).toEqual(["name"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("convertToDotPrompt", () => {
|
||||
it("should convert basic prompt to dot prompt format", () => {
|
||||
const prompt: PromptType = {
|
||||
name: "test",
|
||||
model: "gpt-4",
|
||||
config: {},
|
||||
tools: [],
|
||||
developerMessage: "",
|
||||
messages: [{ role: "user", content: "Hello world" }],
|
||||
};
|
||||
|
||||
const result = convertToDotPrompt(prompt);
|
||||
expect(result).toContain("---");
|
||||
expect(result).toContain("model: gpt-4");
|
||||
expect(result).toContain("input:");
|
||||
expect(result).toContain("schema:");
|
||||
expect(result).toContain("output:");
|
||||
expect(result).toContain("format: text");
|
||||
expect(result).toContain("User: Hello world");
|
||||
});
|
||||
|
||||
it("should include config parameters when set", () => {
|
||||
const prompt: PromptType = {
|
||||
name: "test",
|
||||
model: "gpt-4",
|
||||
config: {
|
||||
temperature: 0.7,
|
||||
max_tokens: 100,
|
||||
top_p: 0.9,
|
||||
},
|
||||
tools: [],
|
||||
developerMessage: "",
|
||||
messages: [{ role: "user", content: "Hello" }],
|
||||
};
|
||||
|
||||
const result = convertToDotPrompt(prompt);
|
||||
expect(result).toContain("temperature: 0.7");
|
||||
expect(result).toContain("max_tokens: 100");
|
||||
expect(result).toContain("top_p: 0.9");
|
||||
});
|
||||
|
||||
it("should include input schema with variables", () => {
|
||||
const prompt: PromptType = {
|
||||
name: "test",
|
||||
model: "gpt-4",
|
||||
config: {},
|
||||
tools: [],
|
||||
developerMessage: "",
|
||||
messages: [{ role: "user", content: "Hello {{name}}" }],
|
||||
};
|
||||
|
||||
const result = convertToDotPrompt(prompt);
|
||||
expect(result).toContain("input:");
|
||||
expect(result).toContain("schema:");
|
||||
expect(result).toContain("name: string");
|
||||
});
|
||||
|
||||
it("should include developer message when present", () => {
|
||||
const prompt: PromptType = {
|
||||
name: "test",
|
||||
model: "gpt-4",
|
||||
config: {},
|
||||
tools: [],
|
||||
developerMessage: "You are a helpful assistant",
|
||||
messages: [{ role: "user", content: "Hello" }],
|
||||
};
|
||||
|
||||
const result = convertToDotPrompt(prompt);
|
||||
expect(result).toContain("Developer: You are a helpful assistant");
|
||||
});
|
||||
|
||||
it("should include tools when present", () => {
|
||||
const prompt: PromptType = {
|
||||
name: "test",
|
||||
model: "gpt-4",
|
||||
config: {},
|
||||
tools: [
|
||||
{
|
||||
name: "get_weather",
|
||||
description: "Get weather information",
|
||||
json: '{"type": "function", "function": {"name": "get_weather"}}',
|
||||
},
|
||||
],
|
||||
developerMessage: "",
|
||||
messages: [{ role: "user", content: "Hello" }],
|
||||
};
|
||||
|
||||
const result = convertToDotPrompt(prompt);
|
||||
expect(result).toContain("tools:");
|
||||
expect(result).toContain('{"type":"function","function":{"name":"get_weather"}}');
|
||||
});
|
||||
|
||||
it("should handle multiple messages with different roles", () => {
|
||||
const prompt: PromptType = {
|
||||
name: "test",
|
||||
model: "gpt-4",
|
||||
config: {},
|
||||
tools: [],
|
||||
developerMessage: "",
|
||||
messages: [
|
||||
{ role: "user", content: "Hello" },
|
||||
{ role: "assistant", content: "Hi there" },
|
||||
{ role: "user", content: "How are you?" },
|
||||
],
|
||||
};
|
||||
|
||||
const result = convertToDotPrompt(prompt);
|
||||
expect(result).toContain("User: Hello");
|
||||
expect(result).toContain("Assistant: Hi there");
|
||||
expect(result).toContain("User: How are you?");
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseExistingPrompt", () => {
|
||||
it("should parse basic dotprompt content", () => {
|
||||
const apiResponse = {
|
||||
prompt_spec: {
|
||||
litellm_params: {
|
||||
dotprompt_content: `---
|
||||
model: gpt-4
|
||||
input:
|
||||
schema:
|
||||
output:
|
||||
format: text
|
||||
---
|
||||
|
||||
User: Hello world`,
|
||||
},
|
||||
prompt_id: "test-prompt",
|
||||
},
|
||||
};
|
||||
|
||||
const result = parseExistingPrompt(apiResponse);
|
||||
expect(result.name).toBe("test-prompt");
|
||||
expect(result.model).toBe("gpt-4");
|
||||
expect(result.messages).toEqual([{ role: "user", content: "Hello world" }]);
|
||||
});
|
||||
|
||||
it("should parse with config parameters", () => {
|
||||
const apiResponse = {
|
||||
prompt_spec: {
|
||||
litellm_params: {
|
||||
dotprompt_content: `---
|
||||
model: gpt-4
|
||||
temperature: 0.7
|
||||
max_tokens: 100
|
||||
top_p: 0.9
|
||||
input:
|
||||
schema:
|
||||
output:
|
||||
format: text
|
||||
---
|
||||
|
||||
User: Hello`,
|
||||
},
|
||||
prompt_id: "test-prompt",
|
||||
},
|
||||
};
|
||||
|
||||
const result = parseExistingPrompt(apiResponse);
|
||||
expect(result.config.temperature).toBe(0.7);
|
||||
expect(result.config.max_tokens).toBe(100);
|
||||
expect(result.config.top_p).toBe(0.9);
|
||||
});
|
||||
|
||||
it("should parse with developer message", () => {
|
||||
const apiResponse = {
|
||||
prompt_spec: {
|
||||
litellm_params: {
|
||||
dotprompt_content: `---
|
||||
model: gpt-4
|
||||
input:
|
||||
schema:
|
||||
output:
|
||||
format: text
|
||||
---
|
||||
|
||||
Developer: You are a helpful assistant
|
||||
|
||||
User: Hello`,
|
||||
},
|
||||
prompt_id: "test-prompt",
|
||||
},
|
||||
};
|
||||
|
||||
const result = parseExistingPrompt(apiResponse);
|
||||
expect(result.developerMessage).toBe("You are a helpful assistant");
|
||||
});
|
||||
|
||||
it("should parse multiple messages", () => {
|
||||
const apiResponse = {
|
||||
prompt_spec: {
|
||||
litellm_params: {
|
||||
dotprompt_content: `---
|
||||
model: gpt-4
|
||||
input:
|
||||
schema:
|
||||
output:
|
||||
format: text
|
||||
---
|
||||
|
||||
User: Hello
|
||||
How are you?
|
||||
|
||||
Assistant: I am fine
|
||||
Thank you for asking
|
||||
|
||||
User: Great!`,
|
||||
},
|
||||
prompt_id: "test-prompt",
|
||||
},
|
||||
};
|
||||
|
||||
const result = parseExistingPrompt(apiResponse);
|
||||
expect(result.messages).toEqual([
|
||||
{ role: "user", content: "Hello\nHow are you?" },
|
||||
{ role: "assistant", content: "I am fine\nThank you for asking" },
|
||||
{ role: "user", content: "Great!" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("should handle prompt with version suffix", () => {
|
||||
const apiResponse = {
|
||||
prompt_spec: {
|
||||
litellm_params: {
|
||||
dotprompt_content: `---
|
||||
model: gpt-4
|
||||
input:
|
||||
schema:
|
||||
output:
|
||||
format: text
|
||||
---
|
||||
|
||||
User: Hello`,
|
||||
},
|
||||
prompt_id: "test-prompt.v2",
|
||||
},
|
||||
};
|
||||
|
||||
const result = parseExistingPrompt(apiResponse);
|
||||
expect(result.name).toBe("test-prompt");
|
||||
});
|
||||
|
||||
it("should throw error when no dotprompt_content", () => {
|
||||
const apiResponse = {
|
||||
prompt_spec: {
|
||||
litellm_params: {},
|
||||
},
|
||||
};
|
||||
|
||||
expect(() => parseExistingPrompt(apiResponse)).toThrow("No dotprompt_content found in API response");
|
||||
});
|
||||
|
||||
it("should throw error for invalid dotprompt format", () => {
|
||||
const apiResponse = {
|
||||
prompt_spec: {
|
||||
litellm_params: {
|
||||
dotprompt_content: "invalid format",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(() => parseExistingPrompt(apiResponse)).toThrow("Invalid dotprompt format");
|
||||
});
|
||||
|
||||
it("should provide default values when parsing fails", () => {
|
||||
const apiResponse = {
|
||||
prompt_spec: {
|
||||
litellm_params: {
|
||||
dotprompt_content: `---
|
||||
model: gpt-4
|
||||
input:
|
||||
schema:
|
||||
output:
|
||||
format: text
|
||||
---
|
||||
|
||||
`,
|
||||
},
|
||||
prompt_id: "test-prompt",
|
||||
},
|
||||
};
|
||||
|
||||
const result = parseExistingPrompt(apiResponse);
|
||||
expect(result.messages).toEqual([
|
||||
{ role: "user", content: "Enter task specifics. Use {{template_variables}} for dynamic inputs" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getVersionNumber", () => {
|
||||
it("should return '1' for undefined promptId", () => {
|
||||
const result = getVersionNumber(undefined);
|
||||
expect(result).toBe("1");
|
||||
});
|
||||
|
||||
it("should return '1' for promptId without version", () => {
|
||||
const result = getVersionNumber("test-prompt");
|
||||
expect(result).toBe("1");
|
||||
});
|
||||
|
||||
it("should extract version with dot separator", () => {
|
||||
const result = getVersionNumber("test-prompt.v2");
|
||||
expect(result).toBe("2");
|
||||
});
|
||||
|
||||
it("should extract version with underscore separator", () => {
|
||||
const result = getVersionNumber("test-prompt_v3");
|
||||
expect(result).toBe("3");
|
||||
});
|
||||
|
||||
it("should extract version with hyphen separator", () => {
|
||||
const result = getVersionNumber("test-prompt-v4");
|
||||
expect(result).toBe("4");
|
||||
});
|
||||
|
||||
it("should extract multi-digit version", () => {
|
||||
const result = getVersionNumber("test-prompt.v123");
|
||||
expect(result).toBe("123");
|
||||
});
|
||||
});
|
||||
|
||||
describe("stripVersionFromPromptId", () => {
|
||||
it("should return empty string for undefined promptId", () => {
|
||||
const result = stripVersionFromPromptId(undefined);
|
||||
expect(result).toBe("");
|
||||
});
|
||||
|
||||
it("should return promptId unchanged when no version present", () => {
|
||||
const result = stripVersionFromPromptId("test-prompt");
|
||||
expect(result).toBe("test-prompt");
|
||||
});
|
||||
|
||||
it("should strip version with dot separator", () => {
|
||||
const result = stripVersionFromPromptId("test-prompt.v2");
|
||||
expect(result).toBe("test-prompt");
|
||||
});
|
||||
|
||||
it("should strip version with underscore separator", () => {
|
||||
const result = stripVersionFromPromptId("test-prompt_v3");
|
||||
expect(result).toBe("test-prompt");
|
||||
});
|
||||
|
||||
it("should strip version with hyphen separator", () => {
|
||||
const result = stripVersionFromPromptId("test-prompt-v4");
|
||||
expect(result).toBe("test-prompt");
|
||||
});
|
||||
|
||||
it("should strip multi-digit version", () => {
|
||||
const result = stripVersionFromPromptId("test-prompt.v123");
|
||||
expect(result).toBe("test-prompt");
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue