mirror of
https://github.com/tiennm99/litellm.git
synced 2026-07-20 12:17:34 +00:00
Merge pull request #18577 from BerriAI/litellm_ui_react_query_tests
[Refactor] UI - Test for React Query Hooks
This commit is contained in:
@@ -0,0 +1,332 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { renderHook, waitFor } from "@testing-library/react";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import React, { ReactNode } from "react";
|
||||
import { useAgents } from "./useAgents";
|
||||
import { getAgentsList } from "@/components/networking";
|
||||
import type { AgentsResponse, Agent } from "@/components/agents/types";
|
||||
|
||||
// Mock the networking function
|
||||
vi.mock("@/components/networking", () => ({
|
||||
getAgentsList: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock useAuthorized hook - we can override this in individual tests
|
||||
const mockUseAuthorized = vi.fn();
|
||||
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
|
||||
default: () => mockUseAuthorized(),
|
||||
}));
|
||||
|
||||
// Import actual roles instead of mocking them
|
||||
|
||||
// Mock data
|
||||
const mockAgents: Agent[] = [
|
||||
{
|
||||
agent_id: "agent-1",
|
||||
agent_name: "Test Agent 1",
|
||||
litellm_params: {
|
||||
model: "gpt-3.5-turbo",
|
||||
api_key: "test-key-1",
|
||||
},
|
||||
agent_card_params: {
|
||||
description: "A test agent for unit testing",
|
||||
},
|
||||
created_at: "2024-01-01T00:00:00Z",
|
||||
updated_at: "2024-01-01T00:00:00Z",
|
||||
created_by: "user-1",
|
||||
updated_by: "user-1",
|
||||
},
|
||||
{
|
||||
agent_id: "agent-2",
|
||||
agent_name: "Test Agent 2",
|
||||
litellm_params: {
|
||||
model: "claude-3",
|
||||
api_key: "test-key-2",
|
||||
},
|
||||
agent_card_params: {
|
||||
description: "Another test agent",
|
||||
},
|
||||
created_at: "2024-01-01T00:00:00Z",
|
||||
updated_at: "2024-01-01T00:00:00Z",
|
||||
created_by: "user-2",
|
||||
updated_by: "user-2",
|
||||
},
|
||||
];
|
||||
|
||||
const mockAgentsResponse: AgentsResponse = {
|
||||
agents: mockAgents,
|
||||
};
|
||||
|
||||
describe("useAgents", () => {
|
||||
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 agents data when query is successful", async () => {
|
||||
// Mock successful API call
|
||||
(getAgentsList as any).mockResolvedValue(mockAgentsResponse);
|
||||
|
||||
const { result } = renderHook(() => useAgents(), { 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(mockAgentsResponse);
|
||||
expect(result.current.error).toBeNull();
|
||||
expect(getAgentsList).toHaveBeenCalledWith("test-access-token");
|
||||
expect(getAgentsList).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should handle error when getAgentsList fails", async () => {
|
||||
const errorMessage = "Failed to fetch agents";
|
||||
const testError = new Error(errorMessage);
|
||||
|
||||
// Mock failed API call
|
||||
(getAgentsList as any).mockRejectedValue(testError);
|
||||
|
||||
const { result } = renderHook(() => useAgents(), { 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(getAgentsList).toHaveBeenCalledWith("test-access-token");
|
||||
expect(getAgentsList).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(() => useAgents(), { 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(getAgentsList).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not execute query when userRole is not an admin role", async () => {
|
||||
// Mock non-admin userRole
|
||||
mockUseAuthorized.mockReturnValue({
|
||||
accessToken: "test-access-token",
|
||||
userRole: "member", // Not in all_admin_roles
|
||||
userId: "test-user-id",
|
||||
token: "test-token",
|
||||
userEmail: "test@example.com",
|
||||
premiumUser: false,
|
||||
disabledPersonalKeyCreation: null,
|
||||
showSSOBanner: false,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useAgents(), { 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(getAgentsList).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not execute query when userRole is null", async () => {
|
||||
// Mock null userRole
|
||||
mockUseAuthorized.mockReturnValue({
|
||||
accessToken: "test-access-token",
|
||||
userRole: null,
|
||||
userId: "test-user-id",
|
||||
token: "test-token",
|
||||
userEmail: "test@example.com",
|
||||
premiumUser: false,
|
||||
disabledPersonalKeyCreation: null,
|
||||
showSSOBanner: false,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useAgents(), { 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(getAgentsList).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not execute query when userRole is empty string", async () => {
|
||||
// Mock empty string userRole
|
||||
mockUseAuthorized.mockReturnValue({
|
||||
accessToken: "test-access-token",
|
||||
userRole: "",
|
||||
userId: "test-user-id",
|
||||
token: "test-token",
|
||||
userEmail: "test@example.com",
|
||||
premiumUser: false,
|
||||
disabledPersonalKeyCreation: null,
|
||||
showSSOBanner: false,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useAgents(), { 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(getAgentsList).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not execute query when both accessToken and userRole are missing", async () => {
|
||||
// Mock both auth values missing
|
||||
mockUseAuthorized.mockReturnValue({
|
||||
accessToken: null,
|
||||
userRole: null,
|
||||
userId: "test-user-id",
|
||||
token: null,
|
||||
userEmail: "test@example.com",
|
||||
premiumUser: false,
|
||||
disabledPersonalKeyCreation: null,
|
||||
showSSOBanner: false,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useAgents(), { 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(getAgentsList).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should execute query when accessToken is present and userRole is Admin", async () => {
|
||||
// Mock successful API call
|
||||
(getAgentsList as any).mockResolvedValue(mockAgentsResponse);
|
||||
|
||||
// Ensure auth values are set (already done in beforeEach)
|
||||
const { result } = renderHook(() => useAgents(), { wrapper });
|
||||
|
||||
// Wait for query to execute
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
expect(getAgentsList).toHaveBeenCalledWith("test-access-token");
|
||||
expect(getAgentsList).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should execute query when accessToken is present and userRole is proxy_admin", async () => {
|
||||
// Mock successful API call
|
||||
(getAgentsList as any).mockResolvedValue(mockAgentsResponse);
|
||||
|
||||
// Mock proxy_admin role
|
||||
mockUseAuthorized.mockReturnValue({
|
||||
accessToken: "test-access-token",
|
||||
userRole: "proxy_admin",
|
||||
userId: "test-user-id",
|
||||
token: "test-token",
|
||||
userEmail: "test@example.com",
|
||||
premiumUser: false,
|
||||
disabledPersonalKeyCreation: null,
|
||||
showSSOBanner: false,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useAgents(), { wrapper });
|
||||
|
||||
// Wait for query to execute
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
expect(getAgentsList).toHaveBeenCalledWith("test-access-token");
|
||||
expect(getAgentsList).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should return empty agents array when API returns empty data", async () => {
|
||||
// Mock API returning empty agents array
|
||||
(getAgentsList as any).mockResolvedValue({ agents: [] });
|
||||
|
||||
const { result } = renderHook(() => useAgents(), { wrapper });
|
||||
|
||||
// Wait for success
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
expect(result.current.isSuccess).toBe(true);
|
||||
});
|
||||
|
||||
expect(result.current.data).toEqual({ agents: [] });
|
||||
expect(getAgentsList).toHaveBeenCalledWith("test-access-token");
|
||||
});
|
||||
|
||||
it("should handle network timeout error", async () => {
|
||||
const timeoutError = new Error("Network timeout");
|
||||
|
||||
// Mock network timeout
|
||||
(getAgentsList as any).mockRejectedValue(timeoutError);
|
||||
|
||||
const { result } = renderHook(() => useAgents(), { 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,194 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { renderHook, waitFor } from "@testing-library/react";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import React, { ReactNode } from "react";
|
||||
import { useCredentials } from "./useCredentials";
|
||||
import { credentialListCall, CredentialsResponse, CredentialItem } from "@/components/networking";
|
||||
|
||||
// Mock the networking function
|
||||
vi.mock("@/components/networking", () => ({
|
||||
credentialListCall: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock useAuthorized hook - we can override this in individual tests
|
||||
const mockUseAuthorized = vi.fn();
|
||||
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
|
||||
default: () => mockUseAuthorized(),
|
||||
}));
|
||||
|
||||
// Mock data
|
||||
const mockCredentialItems: CredentialItem[] = [
|
||||
{
|
||||
credential_name: "openai-api-key",
|
||||
credential_values: { api_key: "sk-test123" },
|
||||
credential_info: {
|
||||
custom_llm_provider: "openai",
|
||||
description: "OpenAI API Key for GPT models",
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
credential_name: "anthropic-api-key",
|
||||
credential_values: { api_key: "sk-ant-test456" },
|
||||
credential_info: {
|
||||
custom_llm_provider: "anthropic",
|
||||
description: "Anthropic API Key for Claude models",
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const mockCredentialsResponse: CredentialsResponse = {
|
||||
credentials: mockCredentialItems,
|
||||
};
|
||||
|
||||
describe("useCredentials", () => {
|
||||
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 credentials data when query is successful", async () => {
|
||||
// Mock successful API call
|
||||
(credentialListCall as any).mockResolvedValue(mockCredentialsResponse);
|
||||
|
||||
const { result } = renderHook(() => useCredentials(), { 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(mockCredentialsResponse);
|
||||
expect(result.current.error).toBeNull();
|
||||
expect(credentialListCall).toHaveBeenCalledWith("test-access-token");
|
||||
expect(credentialListCall).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should handle error when credentialListCall fails", async () => {
|
||||
const errorMessage = "Failed to fetch credentials";
|
||||
const testError = new Error(errorMessage);
|
||||
|
||||
// Mock failed API call
|
||||
(credentialListCall as any).mockRejectedValue(testError);
|
||||
|
||||
const { result } = renderHook(() => useCredentials(), { 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(credentialListCall).toHaveBeenCalledWith("test-access-token");
|
||||
expect(credentialListCall).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(() => useCredentials(), { 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(credentialListCall).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should return empty credentials array when API returns empty data", async () => {
|
||||
// Mock API returning empty credentials array
|
||||
(credentialListCall as any).mockResolvedValue({ credentials: [] });
|
||||
|
||||
const { result } = renderHook(() => useCredentials(), { wrapper });
|
||||
|
||||
// Wait for success
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
expect(result.current.isSuccess).toBe(true);
|
||||
});
|
||||
|
||||
expect(result.current.data).toEqual({ credentials: [] });
|
||||
expect(credentialListCall).toHaveBeenCalledWith("test-access-token");
|
||||
});
|
||||
|
||||
it("should handle network timeout error", async () => {
|
||||
const timeoutError = new Error("Network timeout");
|
||||
|
||||
// Mock network timeout
|
||||
(credentialListCall as any).mockRejectedValue(timeoutError);
|
||||
|
||||
const { result } = renderHook(() => useCredentials(), { wrapper });
|
||||
|
||||
// Wait for error
|
||||
await waitFor(() => {
|
||||
expect(result.current.isError).toBe(true);
|
||||
});
|
||||
|
||||
expect(result.current.error).toEqual(timeoutError);
|
||||
expect(result.current.data).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should execute query when accessToken is present", async () => {
|
||||
// Mock successful API call
|
||||
(credentialListCall as any).mockResolvedValue(mockCredentialsResponse);
|
||||
|
||||
// Ensure auth values are set (already done in beforeEach)
|
||||
const { result } = renderHook(() => useCredentials(), { wrapper });
|
||||
|
||||
// Wait for query to execute
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
expect(credentialListCall).toHaveBeenCalledWith("test-access-token");
|
||||
expect(credentialListCall).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,334 @@
|
||||
import { allEndUsersCall } 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 type { Customer, CustomersResponse } from "./useCustomers";
|
||||
import { useCustomers } from "./useCustomers";
|
||||
|
||||
// Mock the networking function
|
||||
vi.mock("@/components/networking", () => ({
|
||||
allEndUsersCall: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock useAuthorized hook - we can override this in individual tests
|
||||
const mockUseAuthorized = vi.fn();
|
||||
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
|
||||
default: () => mockUseAuthorized(),
|
||||
}));
|
||||
|
||||
// Import actual roles instead of mocking them
|
||||
|
||||
// Mock data
|
||||
const mockCustomers: Customer[] = [
|
||||
{
|
||||
user_id: "customer-1",
|
||||
alias: "Test Customer 1",
|
||||
spend: 150.5,
|
||||
blocked: false,
|
||||
allowed_model_region: "us-east-1",
|
||||
default_model: "gpt-3.5-turbo",
|
||||
budget_id: "budget-1",
|
||||
litellm_budget_table: {
|
||||
budget_id: "budget-1",
|
||||
max_budget: 1000,
|
||||
soft_budget: 800,
|
||||
max_parallel_requests: 10,
|
||||
tpm_limit: 1000,
|
||||
rpm_limit: 100,
|
||||
model_max_budget: { "gpt-4": 500 },
|
||||
budget_duration: "monthly",
|
||||
budget_reset_at: "2024-02-01T00:00:00Z",
|
||||
created_at: "2024-01-01T00:00:00Z",
|
||||
created_by: "admin-1",
|
||||
updated_at: "2024-01-01T00:00:00Z",
|
||||
updated_by: "admin-1",
|
||||
},
|
||||
},
|
||||
{
|
||||
user_id: "customer-2",
|
||||
alias: null,
|
||||
spend: 0,
|
||||
blocked: true,
|
||||
allowed_model_region: null,
|
||||
default_model: null,
|
||||
budget_id: null,
|
||||
litellm_budget_table: null,
|
||||
},
|
||||
];
|
||||
|
||||
const mockCustomersResponse: CustomersResponse = mockCustomers;
|
||||
|
||||
describe("useCustomers", () => {
|
||||
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 customers data when query is successful", async () => {
|
||||
// Mock successful API call
|
||||
(allEndUsersCall as any).mockResolvedValue(mockCustomersResponse);
|
||||
|
||||
const { result } = renderHook(() => useCustomers(), { 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(mockCustomersResponse);
|
||||
expect(result.current.error).toBeNull();
|
||||
expect(allEndUsersCall).toHaveBeenCalledWith("test-access-token");
|
||||
expect(allEndUsersCall).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should handle error when allEndUsersCall fails", async () => {
|
||||
const errorMessage = "Failed to fetch customers";
|
||||
const testError = new Error(errorMessage);
|
||||
|
||||
// Mock failed API call
|
||||
(allEndUsersCall as any).mockRejectedValue(testError);
|
||||
|
||||
const { result } = renderHook(() => useCustomers(), { 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(allEndUsersCall).toHaveBeenCalledWith("test-access-token");
|
||||
expect(allEndUsersCall).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(() => useCustomers(), { 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(allEndUsersCall).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not execute query when userRole is not an admin role", async () => {
|
||||
// Mock non-admin userRole
|
||||
mockUseAuthorized.mockReturnValue({
|
||||
accessToken: "test-access-token",
|
||||
userRole: "member", // Not in all_admin_roles
|
||||
userId: "test-user-id",
|
||||
token: "test-token",
|
||||
userEmail: "test@example.com",
|
||||
premiumUser: false,
|
||||
disabledPersonalKeyCreation: null,
|
||||
showSSOBanner: false,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useCustomers(), { 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(allEndUsersCall).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not execute query when userRole is null", async () => {
|
||||
// Mock null userRole
|
||||
mockUseAuthorized.mockReturnValue({
|
||||
accessToken: "test-access-token",
|
||||
userRole: null,
|
||||
userId: "test-user-id",
|
||||
token: "test-token",
|
||||
userEmail: "test@example.com",
|
||||
premiumUser: false,
|
||||
disabledPersonalKeyCreation: null,
|
||||
showSSOBanner: false,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useCustomers(), { 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(allEndUsersCall).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not execute query when userRole is empty string", async () => {
|
||||
// Mock empty string userRole
|
||||
mockUseAuthorized.mockReturnValue({
|
||||
accessToken: "test-access-token",
|
||||
userRole: "",
|
||||
userId: "test-user-id",
|
||||
token: "test-token",
|
||||
userEmail: "test@example.com",
|
||||
premiumUser: false,
|
||||
disabledPersonalKeyCreation: null,
|
||||
showSSOBanner: false,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useCustomers(), { 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(allEndUsersCall).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not execute query when both accessToken and userRole are missing", async () => {
|
||||
// Mock both auth values missing
|
||||
mockUseAuthorized.mockReturnValue({
|
||||
accessToken: null,
|
||||
userRole: null,
|
||||
userId: "test-user-id",
|
||||
token: null,
|
||||
userEmail: "test@example.com",
|
||||
premiumUser: false,
|
||||
disabledPersonalKeyCreation: null,
|
||||
showSSOBanner: false,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useCustomers(), { 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(allEndUsersCall).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should execute query when accessToken is present and userRole is Admin", async () => {
|
||||
// Mock successful API call
|
||||
(allEndUsersCall as any).mockResolvedValue(mockCustomersResponse);
|
||||
|
||||
// Ensure auth values are set (already done in beforeEach)
|
||||
const { result } = renderHook(() => useCustomers(), { wrapper });
|
||||
|
||||
// Wait for query to execute
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
expect(allEndUsersCall).toHaveBeenCalledWith("test-access-token");
|
||||
expect(allEndUsersCall).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should execute query when accessToken is present and userRole is proxy_admin", async () => {
|
||||
// Mock successful API call
|
||||
(allEndUsersCall as any).mockResolvedValue(mockCustomersResponse);
|
||||
|
||||
// Mock proxy_admin role
|
||||
mockUseAuthorized.mockReturnValue({
|
||||
accessToken: "test-access-token",
|
||||
userRole: "proxy_admin",
|
||||
userId: "test-user-id",
|
||||
token: "test-token",
|
||||
userEmail: "test@example.com",
|
||||
premiumUser: false,
|
||||
disabledPersonalKeyCreation: null,
|
||||
showSSOBanner: false,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useCustomers(), { wrapper });
|
||||
|
||||
// Wait for query to execute
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
expect(allEndUsersCall).toHaveBeenCalledWith("test-access-token");
|
||||
expect(allEndUsersCall).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should return empty customers array when API returns empty data", async () => {
|
||||
// Mock API returning empty customers array
|
||||
(allEndUsersCall as any).mockResolvedValue([]);
|
||||
|
||||
const { result } = renderHook(() => useCustomers(), { wrapper });
|
||||
|
||||
// Wait for success
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
expect(result.current.isSuccess).toBe(true);
|
||||
});
|
||||
|
||||
expect(result.current.data).toEqual([]);
|
||||
expect(allEndUsersCall).toHaveBeenCalledWith("test-access-token");
|
||||
});
|
||||
|
||||
it("should handle network timeout error", async () => {
|
||||
const timeoutError = new Error("Network timeout");
|
||||
|
||||
// Mock network timeout
|
||||
(allEndUsersCall as any).mockRejectedValue(timeoutError);
|
||||
|
||||
const { result } = renderHook(() => useCustomers(), { wrapper });
|
||||
|
||||
// Wait for error
|
||||
await waitFor(() => {
|
||||
expect(result.current.isError).toBe(true);
|
||||
});
|
||||
|
||||
expect(result.current.error).toEqual(timeoutError);
|
||||
expect(result.current.data).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -10,17 +10,6 @@ vi.mock("@/components/networking", () => ({
|
||||
getGuardrailsList: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock the queryKeysFactory - we'll mock the specific return value
|
||||
vi.mock("../common/queryKeysFactory", () => ({
|
||||
createQueryKeys: vi.fn((resource: string) => ({
|
||||
all: [resource],
|
||||
lists: () => [resource, "list"],
|
||||
list: (params?: any) => [resource, "list", { params }],
|
||||
details: () => [resource, "detail"],
|
||||
detail: (uid: string) => [resource, "detail", uid],
|
||||
})),
|
||||
}));
|
||||
|
||||
// Mock useAuthorized hook - we can override this in individual tests
|
||||
const mockUseAuthorized = vi.fn();
|
||||
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
|
||||
|
||||
@@ -0,0 +1,362 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { renderHook, waitFor } from "@testing-library/react";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import React, { ReactNode } from "react";
|
||||
import { useKeys } from "./useKeys";
|
||||
import { keyListCall } from "@/components/networking";
|
||||
import type { KeyResponse } from "@/components/key_team_helpers/key_list";
|
||||
|
||||
// Mock the networking function
|
||||
vi.mock("@/components/networking", () => ({
|
||||
keyListCall: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock useAuthorized hook - we can override this in individual tests
|
||||
const mockUseAuthorized = vi.fn();
|
||||
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
|
||||
default: () => mockUseAuthorized(),
|
||||
}));
|
||||
|
||||
// Mock data
|
||||
const mockKeys: KeyResponse[] = [
|
||||
{
|
||||
token: "sk-test-key-1",
|
||||
token_id: "key-1",
|
||||
key_name: "Test Key 1",
|
||||
key_alias: "test-key-1",
|
||||
spend: 10.5,
|
||||
max_budget: 100,
|
||||
expires: "2024-12-31T23:59:59Z",
|
||||
models: ["gpt-3.5-turbo"],
|
||||
aliases: {},
|
||||
config: {},
|
||||
user_id: "user-1",
|
||||
team_id: null,
|
||||
max_parallel_requests: 10,
|
||||
metadata: {},
|
||||
tpm_limit: 1000,
|
||||
rpm_limit: 100,
|
||||
duration: "30d",
|
||||
budget_duration: "1mo",
|
||||
budget_reset_at: "2024-02-01T00:00:00Z",
|
||||
allowed_cache_controls: [],
|
||||
allowed_routes: [],
|
||||
permissions: {},
|
||||
model_spend: { "gpt-3.5-turbo": 10.5 },
|
||||
model_max_budget: { "gpt-3.5-turbo": 100 },
|
||||
soft_budget_cooldown: false,
|
||||
blocked: false,
|
||||
litellm_budget_table: {},
|
||||
organization_id: null,
|
||||
created_at: "2024-01-01T00:00:00Z",
|
||||
updated_at: "2024-01-01T00:00:00Z",
|
||||
team_spend: 0,
|
||||
team_alias: "",
|
||||
team_tpm_limit: 0,
|
||||
team_rpm_limit: 0,
|
||||
team_max_budget: 0,
|
||||
team_models: [],
|
||||
team_blocked: false,
|
||||
soft_budget: 0,
|
||||
team_model_aliases: {},
|
||||
team_member_spend: 0,
|
||||
team_metadata: {},
|
||||
end_user_id: "",
|
||||
end_user_tpm_limit: 0,
|
||||
end_user_rpm_limit: 0,
|
||||
end_user_max_budget: 0,
|
||||
last_refreshed_at: 0,
|
||||
api_key: "",
|
||||
user_role: "user",
|
||||
rpm_limit_per_model: {},
|
||||
tpm_limit_per_model: {},
|
||||
user_tpm_limit: 0,
|
||||
user_rpm_limit: 0,
|
||||
user_email: "",
|
||||
},
|
||||
{
|
||||
token: "sk-test-key-2",
|
||||
token_id: "key-2",
|
||||
key_name: "Test Key 2",
|
||||
key_alias: "test-key-2",
|
||||
spend: 25.0,
|
||||
max_budget: 200,
|
||||
expires: "2024-12-31T23:59:59Z",
|
||||
models: ["claude-3"],
|
||||
aliases: {},
|
||||
config: {},
|
||||
user_id: "user-2",
|
||||
team_id: "team-1",
|
||||
max_parallel_requests: 5,
|
||||
metadata: {},
|
||||
tpm_limit: 500,
|
||||
rpm_limit: 50,
|
||||
duration: "30d",
|
||||
budget_duration: "1mo",
|
||||
budget_reset_at: "2024-02-01T00:00:00Z",
|
||||
allowed_cache_controls: [],
|
||||
allowed_routes: [],
|
||||
permissions: {},
|
||||
model_spend: { "claude-3": 25.0 },
|
||||
model_max_budget: { "claude-3": 200 },
|
||||
soft_budget_cooldown: false,
|
||||
blocked: false,
|
||||
litellm_budget_table: {},
|
||||
organization_id: null,
|
||||
created_at: "2024-01-01T00:00:00Z",
|
||||
updated_at: "2024-01-01T00:00:00Z",
|
||||
team_spend: 0,
|
||||
team_alias: "test-team",
|
||||
team_tpm_limit: 1000,
|
||||
team_rpm_limit: 100,
|
||||
team_max_budget: 500,
|
||||
team_models: ["claude-3"],
|
||||
team_blocked: false,
|
||||
soft_budget: 0,
|
||||
team_model_aliases: {},
|
||||
team_member_spend: 0,
|
||||
team_metadata: {},
|
||||
end_user_id: "",
|
||||
end_user_tpm_limit: 0,
|
||||
end_user_rpm_limit: 0,
|
||||
end_user_max_budget: 0,
|
||||
last_refreshed_at: 0,
|
||||
api_key: "",
|
||||
user_role: "user",
|
||||
rpm_limit_per_model: {},
|
||||
tpm_limit_per_model: {},
|
||||
user_tpm_limit: 0,
|
||||
user_rpm_limit: 0,
|
||||
user_email: "",
|
||||
},
|
||||
];
|
||||
|
||||
const mockKeysResponse = {
|
||||
keys: mockKeys,
|
||||
total_count: 2,
|
||||
current_page: 1,
|
||||
total_pages: 1,
|
||||
};
|
||||
|
||||
describe("useKeys", () => {
|
||||
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 keys data when query is successful", async () => {
|
||||
// Mock successful API call
|
||||
(keyListCall as any).mockResolvedValue(mockKeysResponse);
|
||||
|
||||
const { result } = renderHook(() => useKeys(1, 10), { 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(mockKeysResponse);
|
||||
expect(result.current.error).toBeNull();
|
||||
expect(keyListCall).toHaveBeenCalledWith(
|
||||
"test-access-token",
|
||||
null, // organizationID
|
||||
null, // teamID
|
||||
null, // selectedKeyAlias
|
||||
null, // userID
|
||||
null, // keyHash
|
||||
1, // page
|
||||
10, // pageSize
|
||||
);
|
||||
expect(keyListCall).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should handle error when keyListCall fails", async () => {
|
||||
const errorMessage = "Failed to fetch keys";
|
||||
const testError = new Error(errorMessage);
|
||||
|
||||
// Mock failed API call
|
||||
(keyListCall as any).mockRejectedValue(testError);
|
||||
|
||||
const { result } = renderHook(() => useKeys(1, 10), { 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(keyListCall).toHaveBeenCalledWith(
|
||||
"test-access-token",
|
||||
null, // organizationID
|
||||
null, // teamID
|
||||
null, // selectedKeyAlias
|
||||
null, // userID
|
||||
null, // keyHash
|
||||
1, // page
|
||||
10, // pageSize
|
||||
);
|
||||
expect(keyListCall).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(() => useKeys(1, 10), { 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(keyListCall).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should pass correct page and pageSize parameters to the API", async () => {
|
||||
// Mock successful API call
|
||||
(keyListCall as any).mockResolvedValue(mockKeysResponse);
|
||||
|
||||
const page = 2;
|
||||
const pageSize = 20;
|
||||
|
||||
const { result } = renderHook(() => useKeys(page, pageSize), { wrapper });
|
||||
|
||||
// Wait for success
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
expect(keyListCall).toHaveBeenCalledWith(
|
||||
"test-access-token",
|
||||
null, // organizationID
|
||||
null, // teamID
|
||||
null, // selectedKeyAlias
|
||||
null, // userID
|
||||
null, // keyHash
|
||||
page, // page
|
||||
pageSize, // pageSize
|
||||
);
|
||||
});
|
||||
|
||||
it("should return empty keys array when API returns empty data", async () => {
|
||||
// Mock API returning empty keys array
|
||||
const emptyResponse = {
|
||||
keys: [],
|
||||
total_count: 0,
|
||||
current_page: 1,
|
||||
total_pages: 0,
|
||||
};
|
||||
(keyListCall as any).mockResolvedValue(emptyResponse);
|
||||
|
||||
const { result } = renderHook(() => useKeys(1, 10), { wrapper });
|
||||
|
||||
// Wait for success
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
expect(result.current.isSuccess).toBe(true);
|
||||
});
|
||||
|
||||
expect(result.current.data).toEqual(emptyResponse);
|
||||
expect(keyListCall).toHaveBeenCalledWith(
|
||||
"test-access-token",
|
||||
null, // organizationID
|
||||
null, // teamID
|
||||
null, // selectedKeyAlias
|
||||
null, // userID
|
||||
null, // keyHash
|
||||
1, // page
|
||||
10, // pageSize
|
||||
);
|
||||
});
|
||||
|
||||
it("should handle network timeout error", async () => {
|
||||
const timeoutError = new Error("Network timeout");
|
||||
|
||||
// Mock network timeout
|
||||
(keyListCall as any).mockRejectedValue(timeoutError);
|
||||
|
||||
const { result } = renderHook(() => useKeys(1, 10), { wrapper });
|
||||
|
||||
// Wait for error
|
||||
await waitFor(() => {
|
||||
expect(result.current.isError).toBe(true);
|
||||
});
|
||||
|
||||
expect(result.current.error).toEqual(timeoutError);
|
||||
expect(result.current.data).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should handle pagination correctly", async () => {
|
||||
const paginatedResponse = {
|
||||
keys: [mockKeys[0]], // Only first key
|
||||
total_count: 15,
|
||||
current_page: 2,
|
||||
total_pages: 2,
|
||||
};
|
||||
(keyListCall as any).mockResolvedValue(paginatedResponse);
|
||||
|
||||
const { result } = renderHook(() => useKeys(2, 10), { wrapper });
|
||||
|
||||
// Wait for success
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
expect(result.current.data).toEqual(paginatedResponse);
|
||||
expect(keyListCall).toHaveBeenCalledWith(
|
||||
"test-access-token",
|
||||
null, // organizationID
|
||||
null, // teamID
|
||||
null, // selectedKeyAlias
|
||||
null, // userID
|
||||
null, // keyHash
|
||||
2, // page
|
||||
10, // pageSize
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -10,17 +10,6 @@ vi.mock("@/components/networking", () => ({
|
||||
modelCostMap: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock the queryKeysFactory - we'll mock the specific return value
|
||||
vi.mock("../common/queryKeysFactory", () => ({
|
||||
createQueryKeys: vi.fn((resource: string) => ({
|
||||
all: [resource],
|
||||
lists: () => [resource, "list"],
|
||||
list: (params?: any) => [resource, "list", { params }],
|
||||
details: () => [resource, "detail"],
|
||||
detail: (uid: string) => [resource, "detail", uid],
|
||||
})),
|
||||
}));
|
||||
|
||||
// Mock data
|
||||
const mockModelCostData: Record<string, any> = {
|
||||
"gpt-3.5-turbo": {
|
||||
|
||||
@@ -11,17 +11,6 @@ vi.mock("@/components/networking", () => ({
|
||||
organizationListCall: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock the queryKeysFactory - we'll mock the specific return value
|
||||
vi.mock("../common/queryKeysFactory", () => ({
|
||||
createQueryKeys: vi.fn((resource: string) => ({
|
||||
all: [resource],
|
||||
lists: () => [resource, "list"],
|
||||
list: (params?: any) => [resource, "list", { params }],
|
||||
details: () => [resource, "detail"],
|
||||
detail: (uid: string) => [resource, "detail", uid],
|
||||
})),
|
||||
}));
|
||||
|
||||
// Mock useAuthorized hook - we can override this in individual tests
|
||||
const mockUseAuthorized = vi.fn();
|
||||
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
|
||||
|
||||
@@ -6,8 +6,7 @@ import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
|
||||
const organizationKeys = createQueryKeys("organizations");
|
||||
|
||||
export const useOrganizations = (): UseQueryResult<Organization[]> => {
|
||||
const { accessToken } = useAuthorized();
|
||||
const { userId, userRole } = useAuthorized();
|
||||
const { accessToken, userId, userRole } = useAuthorized();
|
||||
return useQuery<Organization[]>({
|
||||
queryKey: organizationKeys.list({}),
|
||||
queryFn: async () => await organizationListCall(accessToken!),
|
||||
|
||||
@@ -11,17 +11,6 @@ vi.mock("@/components/networking", () => ({
|
||||
getProviderCreateMetadata: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock the queryKeysFactory - we'll mock the specific return value
|
||||
vi.mock("../common/queryKeysFactory", () => ({
|
||||
createQueryKeys: vi.fn((resource: string) => ({
|
||||
all: [resource],
|
||||
lists: () => [resource, "list"],
|
||||
list: (params?: any) => [resource, "list", { params }],
|
||||
details: () => [resource, "detail"],
|
||||
detail: (uid: string) => [resource, "detail", uid],
|
||||
})),
|
||||
}));
|
||||
|
||||
// Mock data
|
||||
const mockProviderFields: ProviderCreateInfo[] = [
|
||||
{
|
||||
|
||||
@@ -11,17 +11,6 @@ vi.mock("@/components/networking", () => ({
|
||||
tagListCall: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock the queryKeysFactory - we'll mock the specific return value
|
||||
vi.mock("../common/queryKeysFactory", () => ({
|
||||
createQueryKeys: vi.fn((resource: string) => ({
|
||||
all: [resource],
|
||||
lists: () => [resource, "list"],
|
||||
list: (params?: any) => [resource, "list", { params }],
|
||||
details: () => [resource, "detail"],
|
||||
detail: (uid: string) => [resource, "detail", uid],
|
||||
})),
|
||||
}));
|
||||
|
||||
// Mock useAuthorized hook - we can override this in individual tests
|
||||
const mockUseAuthorized = vi.fn();
|
||||
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { renderHook, waitFor } from "@testing-library/react";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import React, { ReactNode } from "react";
|
||||
import { useTeams } from "./useTeams";
|
||||
import { fetchTeams } from "@/app/(dashboard)/networking";
|
||||
import type { Team } from "@/components/key_team_helpers/key_list";
|
||||
|
||||
// Mock the networking function
|
||||
vi.mock("@/app/(dashboard)/networking", () => ({
|
||||
fetchTeams: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock useAuthorized hook - we can override this in individual tests
|
||||
const mockUseAuthorized = vi.fn();
|
||||
vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
|
||||
default: () => mockUseAuthorized(),
|
||||
}));
|
||||
|
||||
// Mock data
|
||||
const mockTeams: Team[] = [
|
||||
{
|
||||
team_id: "team-1",
|
||||
team_alias: "Test Team 1",
|
||||
models: ["gpt-3.5-turbo", "claude-3"],
|
||||
max_budget: 100.0,
|
||||
budget_duration: "monthly",
|
||||
tpm_limit: 1000,
|
||||
rpm_limit: 100,
|
||||
organization_id: "org-1",
|
||||
created_at: "2024-01-01T00:00:00Z",
|
||||
keys: [],
|
||||
members_with_roles: [],
|
||||
},
|
||||
{
|
||||
team_id: "team-2",
|
||||
team_alias: "Test Team 2",
|
||||
models: ["gpt-4"],
|
||||
max_budget: 200.0,
|
||||
budget_duration: "monthly",
|
||||
tpm_limit: 2000,
|
||||
rpm_limit: 200,
|
||||
organization_id: "org-1",
|
||||
created_at: "2024-01-02T00:00:00Z",
|
||||
keys: [],
|
||||
members_with_roles: [],
|
||||
},
|
||||
];
|
||||
|
||||
describe("useTeams", () => {
|
||||
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",
|
||||
userId: "test-user-id",
|
||||
userRole: "Admin",
|
||||
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 teams data when query is successful", async () => {
|
||||
// Mock successful API call
|
||||
(fetchTeams as any).mockResolvedValue(mockTeams);
|
||||
|
||||
const { result } = renderHook(() => useTeams(), { 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(mockTeams);
|
||||
expect(result.current.error).toBeNull();
|
||||
expect(fetchTeams).toHaveBeenCalledWith("test-access-token", "test-user-id", "Admin", null);
|
||||
expect(fetchTeams).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should handle error when fetchTeams fails", async () => {
|
||||
const errorMessage = "Failed to fetch teams";
|
||||
const testError = new Error(errorMessage);
|
||||
|
||||
// Mock failed API call
|
||||
(fetchTeams as any).mockRejectedValue(testError);
|
||||
|
||||
const { result } = renderHook(() => useTeams(), { 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(fetchTeams).toHaveBeenCalledWith("test-access-token", "test-user-id", "Admin", null);
|
||||
expect(fetchTeams).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should not execute query when accessToken is missing", async () => {
|
||||
// Mock missing accessToken
|
||||
mockUseAuthorized.mockReturnValue({
|
||||
accessToken: null,
|
||||
userId: "test-user-id",
|
||||
userRole: "Admin",
|
||||
token: null,
|
||||
userEmail: "test@example.com",
|
||||
premiumUser: false,
|
||||
disabledPersonalKeyCreation: null,
|
||||
showSSOBanner: false,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useTeams(), { 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(fetchTeams).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should not execute query when accessToken is empty string", async () => {
|
||||
// Mock empty string accessToken
|
||||
mockUseAuthorized.mockReturnValue({
|
||||
accessToken: "",
|
||||
userId: "test-user-id",
|
||||
userRole: "Admin",
|
||||
token: "",
|
||||
userEmail: "test@example.com",
|
||||
premiumUser: false,
|
||||
disabledPersonalKeyCreation: null,
|
||||
showSSOBanner: false,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useTeams(), { 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(fetchTeams).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should execute query when accessToken is present", async () => {
|
||||
// Mock successful API call
|
||||
(fetchTeams as any).mockResolvedValue(mockTeams);
|
||||
|
||||
// Ensure auth values are set (already done in beforeEach)
|
||||
const { result } = renderHook(() => useTeams(), { wrapper });
|
||||
|
||||
// Wait for query to execute
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
expect(fetchTeams).toHaveBeenCalledWith("test-access-token", "test-user-id", "Admin", null);
|
||||
expect(fetchTeams).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should return empty teams array when API returns empty data", async () => {
|
||||
// Mock API returning empty teams array
|
||||
(fetchTeams as any).mockResolvedValue([]);
|
||||
|
||||
const { result } = renderHook(() => useTeams(), { wrapper });
|
||||
|
||||
// Wait for success
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
expect(result.current.isSuccess).toBe(true);
|
||||
});
|
||||
|
||||
expect(result.current.data).toEqual([]);
|
||||
expect(fetchTeams).toHaveBeenCalledWith("test-access-token", "test-user-id", "Admin", null);
|
||||
});
|
||||
|
||||
it("should handle network timeout error", async () => {
|
||||
const timeoutError = new Error("Network timeout");
|
||||
|
||||
// Mock network timeout
|
||||
(fetchTeams as any).mockRejectedValue(timeoutError);
|
||||
|
||||
const { result } = renderHook(() => useTeams(), { wrapper });
|
||||
|
||||
// Wait for error
|
||||
await waitFor(() => {
|
||||
expect(result.current.isError).toBe(true);
|
||||
});
|
||||
|
||||
expect(result.current.error).toEqual(timeoutError);
|
||||
expect(result.current.data).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should pass userId and userRole to fetchTeams", async () => {
|
||||
// Mock successful API call
|
||||
(fetchTeams as any).mockResolvedValue(mockTeams);
|
||||
|
||||
// Mock specific userId and userRole
|
||||
mockUseAuthorized.mockReturnValue({
|
||||
accessToken: "test-access-token",
|
||||
userId: "custom-user-id",
|
||||
userRole: "member",
|
||||
token: "test-token",
|
||||
userEmail: "test@example.com",
|
||||
premiumUser: false,
|
||||
disabledPersonalKeyCreation: null,
|
||||
showSSOBanner: false,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useTeams(), { wrapper });
|
||||
|
||||
// Wait for query to execute
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
expect(fetchTeams).toHaveBeenCalledWith("test-access-token", "custom-user-id", "member", null);
|
||||
});
|
||||
|
||||
it("should handle null userId", async () => {
|
||||
// Mock successful API call
|
||||
(fetchTeams as any).mockResolvedValue(mockTeams);
|
||||
|
||||
// Mock null userId
|
||||
mockUseAuthorized.mockReturnValue({
|
||||
accessToken: "test-access-token",
|
||||
userId: null,
|
||||
userRole: "Admin",
|
||||
token: "test-token",
|
||||
userEmail: "test@example.com",
|
||||
premiumUser: false,
|
||||
disabledPersonalKeyCreation: null,
|
||||
showSSOBanner: false,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useTeams(), { wrapper });
|
||||
|
||||
// Wait for query to execute
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
expect(fetchTeams).toHaveBeenCalledWith("test-access-token", null, "Admin", null);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user