diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/agents/useAgents.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/agents/useAgents.test.ts new file mode 100644 index 0000000000..44fc6a9683 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/agents/useAgents.test.ts @@ -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(); + }); +}); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/credentials/useCredentials.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/credentials/useCredentials.test.ts new file mode 100644 index 0000000000..ee903628f0 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/credentials/useCredentials.test.ts @@ -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); + }); +}); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/customers/useCustomers.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/customers/useCustomers.test.ts new file mode 100644 index 0000000000..716d6f7539 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/customers/useCustomers.test.ts @@ -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(); + }); +}); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/guardrails/useGuardrails.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/guardrails/useGuardrails.test.ts index 48344a2167..d9e96a5308 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/guardrails/useGuardrails.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/guardrails/useGuardrails.test.ts @@ -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", () => ({ diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/keys/useKeys.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/keys/useKeys.test.ts new file mode 100644 index 0000000000..c4ffb7041a --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/keys/useKeys.test.ts @@ -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 + ); + }); +}); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/models/useModelCostMap.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/models/useModelCostMap.test.ts index 325503408c..f79ca33bc5 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/models/useModelCostMap.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/models/useModelCostMap.test.ts @@ -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 = { "gpt-3.5-turbo": { diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/organizations/useOrganizations.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/organizations/useOrganizations.test.ts index 2f13a68bab..66c005f37c 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/organizations/useOrganizations.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/organizations/useOrganizations.test.ts @@ -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", () => ({ diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/organizations/useOrganizations.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/organizations/useOrganizations.ts index 57c9c05765..27a946d112 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/organizations/useOrganizations.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/organizations/useOrganizations.ts @@ -6,8 +6,7 @@ import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; const organizationKeys = createQueryKeys("organizations"); export const useOrganizations = (): UseQueryResult => { - const { accessToken } = useAuthorized(); - const { userId, userRole } = useAuthorized(); + const { accessToken, userId, userRole } = useAuthorized(); return useQuery({ queryKey: organizationKeys.list({}), queryFn: async () => await organizationListCall(accessToken!), diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/providers/useProviderFields.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/providers/useProviderFields.test.ts index 952b52bb40..33242e0452 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/providers/useProviderFields.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/providers/useProviderFields.test.ts @@ -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[] = [ { diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/tags/useTags.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/tags/useTags.test.ts index 9ae9662359..a175133956 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/tags/useTags.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/tags/useTags.test.ts @@ -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", () => ({ diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/teams/useTeams.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/teams/useTeams.test.ts new file mode 100644 index 0000000000..91ffbcfafa --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/teams/useTeams.test.ts @@ -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); + }); +});