/** * Tests for useAdmin hooks * Verifies admin statistics and list fetching functionality */ import { renderHook, waitFor, act } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { useAdminStats, useAdminUsers, useAdminOrganizations, useCreateUser, useUpdateUser, useDeleteUser, useActivateUser, useDeactivateUser, useBulkUserAction, } from '@/lib/api/hooks/useAdmin'; import { adminListUsers, adminListOrganizations, adminListSessions, adminCreateUser, adminUpdateUser, adminDeleteUser, adminActivateUser, adminDeactivateUser, adminBulkUserAction, } from '@/lib/api/client'; import { useAuth } from '@/lib/auth/AuthContext'; // Mock dependencies jest.mock('@/lib/api/client'); jest.mock('@/lib/auth/AuthContext'); const mockAdminListUsers = adminListUsers as jest.MockedFunction; const mockAdminListOrganizations = adminListOrganizations as jest.MockedFunction; const mockAdminListSessions = adminListSessions as jest.MockedFunction; const mockUseAuth = useAuth as jest.MockedFunction; describe('useAdmin hooks', () => { let queryClient: QueryClient; beforeEach(() => { queryClient = new QueryClient({ defaultOptions: { queries: { retry: false, }, }, }); jest.clearAllMocks(); }); const wrapper = ({ children }: { children: React.ReactNode }) => ( {children} ); describe('useAdminStats', () => { const mockUsersData = { data: { data: [ { is_active: true }, { is_active: true }, { is_active: false }, ], pagination: { total: 3, page: 1, limit: 10000 }, }, }; const mockOrgsData = { data: { pagination: { total: 5 }, }, }; const mockSessionsData = { data: { pagination: { total: 10 }, }, }; it('fetches and calculates stats when user is superuser', async () => { mockUseAuth.mockReturnValue({ user: { is_superuser: true } as any, isAuthenticated: true, isLoading: false, login: jest.fn(), logout: jest.fn(), }); mockAdminListUsers.mockResolvedValue(mockUsersData as any); mockAdminListOrganizations.mockResolvedValue(mockOrgsData as any); mockAdminListSessions.mockResolvedValue(mockSessionsData as any); const { result } = renderHook(() => useAdminStats(), { wrapper }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(result.current.data).toEqual({ totalUsers: 3, activeUsers: 2, totalOrganizations: 5, totalSessions: 10, }); expect(mockAdminListUsers).toHaveBeenCalledWith({ query: { page: 1, limit: 100 }, throwOnError: false, }); expect(mockAdminListOrganizations).toHaveBeenCalledWith({ query: { page: 1, limit: 100 }, throwOnError: false, }); expect(mockAdminListSessions).toHaveBeenCalledWith({ query: { page: 1, limit: 100 }, throwOnError: false, }); }); it('does not fetch when user is not superuser', async () => { mockUseAuth.mockReturnValue({ user: { is_superuser: false } as any, isAuthenticated: true, isLoading: false, login: jest.fn(), logout: jest.fn(), }); const { result } = renderHook(() => useAdminStats(), { wrapper }); expect(result.current.isLoading).toBe(false); expect(result.current.data).toBeUndefined(); expect(mockAdminListUsers).not.toHaveBeenCalled(); expect(mockAdminListOrganizations).not.toHaveBeenCalled(); }); it('does not fetch when user is null', async () => { mockUseAuth.mockReturnValue({ user: null, isAuthenticated: false, isLoading: false, login: jest.fn(), logout: jest.fn(), }); const { result } = renderHook(() => useAdminStats(), { wrapper }); expect(result.current.isLoading).toBe(false); expect(result.current.data).toBeUndefined(); expect(mockAdminListUsers).not.toHaveBeenCalled(); }); it('handles users API error', async () => { mockUseAuth.mockReturnValue({ user: { is_superuser: true } as any, isAuthenticated: true, isLoading: false, login: jest.fn(), logout: jest.fn(), }); mockAdminListUsers.mockResolvedValue({ error: 'Users fetch failed' } as any); const { result } = renderHook(() => useAdminStats(), { wrapper }); await waitFor(() => expect(result.current.isError).toBe(true)); expect(result.current.error).toBeDefined(); }); it('handles organizations API error', async () => { mockUseAuth.mockReturnValue({ user: { is_superuser: true } as any, isAuthenticated: true, isLoading: false, login: jest.fn(), logout: jest.fn(), }); mockAdminListUsers.mockResolvedValue(mockUsersData as any); mockAdminListOrganizations.mockResolvedValue({ error: 'Orgs fetch failed' } as any); const { result } = renderHook(() => useAdminStats(), { wrapper }); await waitFor(() => expect(result.current.isError).toBe(true)); expect(result.current.error).toBeDefined(); }); }); describe('useAdminUsers', () => { const mockResponse = { data: { data: [{ id: '1' }, { id: '2' }], pagination: { total: 2, page: 1, limit: 50 }, }, }; it('fetches users when user is superuser', async () => { mockUseAuth.mockReturnValue({ user: { is_superuser: true } as any, isAuthenticated: true, isLoading: false, login: jest.fn(), logout: jest.fn(), }); mockAdminListUsers.mockResolvedValue(mockResponse as any); const { result } = renderHook(() => useAdminUsers(), { wrapper }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(result.current.data).toEqual(mockResponse.data); expect(mockAdminListUsers).toHaveBeenCalledWith({ query: { page: 1, limit: 50 }, throwOnError: false, }); }); it('uses custom page and limit parameters', async () => { mockUseAuth.mockReturnValue({ user: { is_superuser: true } as any, isAuthenticated: true, isLoading: false, login: jest.fn(), logout: jest.fn(), }); mockAdminListUsers.mockResolvedValue(mockResponse as any); renderHook(() => useAdminUsers(2, 100), { wrapper }); await waitFor(() => { expect(mockAdminListUsers).toHaveBeenCalledWith({ query: { page: 2, limit: 100 }, throwOnError: false, }); }); }); it('does not fetch when user is not superuser', async () => { mockUseAuth.mockReturnValue({ user: { is_superuser: false } as any, isAuthenticated: true, isLoading: false, login: jest.fn(), logout: jest.fn(), }); const { result } = renderHook(() => useAdminUsers(), { wrapper }); expect(result.current.isLoading).toBe(false); expect(result.current.data).toBeUndefined(); expect(mockAdminListUsers).not.toHaveBeenCalled(); }); it('handles API error', async () => { mockUseAuth.mockReturnValue({ user: { is_superuser: true } as any, isAuthenticated: true, isLoading: false, login: jest.fn(), logout: jest.fn(), }); mockAdminListUsers.mockResolvedValue({ error: 'Fetch failed' } as any); const { result } = renderHook(() => useAdminUsers(), { wrapper }); await waitFor(() => expect(result.current.isError).toBe(true)); expect(result.current.error).toBeDefined(); }); it('passes search parameter to API', async () => { mockUseAuth.mockReturnValue({ user: { is_superuser: true } as any, isAuthenticated: true, isLoading: false, login: jest.fn(), logout: jest.fn(), }); mockAdminListUsers.mockResolvedValue(mockResponse as any); renderHook(() => useAdminUsers(1, 50, 'test@example.com'), { wrapper }); await waitFor(() => { expect(mockAdminListUsers).toHaveBeenCalledWith({ query: { page: 1, limit: 50, search: 'test@example.com' }, throwOnError: false, }); }); }); it('passes is_active filter parameter to API', async () => { mockUseAuth.mockReturnValue({ user: { is_superuser: true } as any, isAuthenticated: true, isLoading: false, login: jest.fn(), logout: jest.fn(), }); mockAdminListUsers.mockResolvedValue(mockResponse as any); renderHook(() => useAdminUsers(1, 50, null, true), { wrapper }); await waitFor(() => { expect(mockAdminListUsers).toHaveBeenCalledWith({ query: { page: 1, limit: 50, is_active: true }, throwOnError: false, }); }); }); it('passes is_superuser filter parameter to API', async () => { mockUseAuth.mockReturnValue({ user: { is_superuser: true } as any, isAuthenticated: true, isLoading: false, login: jest.fn(), logout: jest.fn(), }); mockAdminListUsers.mockResolvedValue(mockResponse as any); renderHook(() => useAdminUsers(1, 50, null, null, false), { wrapper }); await waitFor(() => { expect(mockAdminListUsers).toHaveBeenCalledWith({ query: { page: 1, limit: 50, is_superuser: false }, throwOnError: false, }); }); }); it('passes all filter parameters to API', async () => { mockUseAuth.mockReturnValue({ user: { is_superuser: true } as any, isAuthenticated: true, isLoading: false, login: jest.fn(), logout: jest.fn(), }); mockAdminListUsers.mockResolvedValue(mockResponse as any); renderHook(() => useAdminUsers(2, 20, 'admin', true, false), { wrapper }); await waitFor(() => { expect(mockAdminListUsers).toHaveBeenCalledWith({ query: { page: 2, limit: 20, search: 'admin', is_active: true, is_superuser: false }, throwOnError: false, }); }); }); it('excludes filter parameters when they are null', async () => { mockUseAuth.mockReturnValue({ user: { is_superuser: true } as any, isAuthenticated: true, isLoading: false, login: jest.fn(), logout: jest.fn(), }); mockAdminListUsers.mockResolvedValue(mockResponse as any); renderHook(() => useAdminUsers(1, 50, null, null, null), { wrapper }); await waitFor(() => { expect(mockAdminListUsers).toHaveBeenCalledWith({ query: { page: 1, limit: 50 }, throwOnError: false, }); }); }); }); describe('useAdminOrganizations', () => { const mockResponse = { data: { data: [{ id: '1' }, { id: '2' }], pagination: { total: 2, page: 1, limit: 50 }, }, }; it('fetches organizations when user is superuser', async () => { mockUseAuth.mockReturnValue({ user: { is_superuser: true } as any, isAuthenticated: true, isLoading: false, login: jest.fn(), logout: jest.fn(), }); mockAdminListOrganizations.mockResolvedValue(mockResponse as any); const { result } = renderHook(() => useAdminOrganizations(), { wrapper }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); expect(result.current.data).toEqual(mockResponse.data); expect(mockAdminListOrganizations).toHaveBeenCalledWith({ query: { page: 1, limit: 50 }, throwOnError: false, }); }); it('uses custom page and limit parameters', async () => { mockUseAuth.mockReturnValue({ user: { is_superuser: true } as any, isAuthenticated: true, isLoading: false, login: jest.fn(), logout: jest.fn(), }); mockAdminListOrganizations.mockResolvedValue(mockResponse as any); renderHook(() => useAdminOrganizations(3, 25), { wrapper }); await waitFor(() => { expect(mockAdminListOrganizations).toHaveBeenCalledWith({ query: { page: 3, limit: 25 }, throwOnError: false, }); }); }); it('does not fetch when user is not superuser', async () => { mockUseAuth.mockReturnValue({ user: { is_superuser: false } as any, isAuthenticated: true, isLoading: false, login: jest.fn(), logout: jest.fn(), }); const { result } = renderHook(() => useAdminOrganizations(), { wrapper }); expect(result.current.isLoading).toBe(false); expect(result.current.data).toBeUndefined(); expect(mockAdminListOrganizations).not.toHaveBeenCalled(); }); it('handles API error', async () => { mockUseAuth.mockReturnValue({ user: { is_superuser: true } as any, isAuthenticated: true, isLoading: false, login: jest.fn(), logout: jest.fn(), }); mockAdminListOrganizations.mockResolvedValue({ error: 'Fetch failed' } as any); const { result } = renderHook(() => useAdminOrganizations(), { wrapper }); await waitFor(() => expect(result.current.isError).toBe(true)); expect(result.current.error).toBeDefined(); }); }); describe('useCreateUser', () => { it('creates a user successfully', async () => { const mockCreateUser = adminCreateUser as jest.MockedFunction; mockCreateUser.mockResolvedValue({ data: { id: '1', email: 'newuser@example.com', first_name: 'New', last_name: 'User', is_active: true, is_superuser: false, created_at: '2025-01-01T00:00:00Z' }, } as any); const { result } = renderHook(() => useCreateUser(), { wrapper }); await act(async () => { await result.current.mutateAsync({ email: 'newuser@example.com', first_name: 'New', last_name: 'User', password: 'Password123', is_superuser: false, }); }); expect(mockCreateUser).toHaveBeenCalledWith({ body: { email: 'newuser@example.com', first_name: 'New', last_name: 'User', password: 'Password123', is_superuser: false, }, throwOnError: false, }); }); it('handles create error', async () => { const mockCreateUser = adminCreateUser as jest.MockedFunction; mockCreateUser.mockResolvedValue({ error: 'Create failed' } as any); const { result } = renderHook(() => useCreateUser(), { wrapper }); await expect( result.current.mutateAsync({ email: 'test@example.com', first_name: 'Test', password: 'Password123', is_superuser: false, }) ).rejects.toThrow('Failed to create user'); }); }); describe('useUpdateUser', () => { it('updates a user successfully', async () => { const mockUpdateUser = adminUpdateUser as jest.MockedFunction; mockUpdateUser.mockResolvedValue({ data: { id: '1', email: 'updated@example.com', first_name: 'Updated', last_name: 'User', is_active: true, is_superuser: false, created_at: '2025-01-01T00:00:00Z' }, } as any); const { result } = renderHook(() => useUpdateUser(), { wrapper }); await act(async () => { await result.current.mutateAsync({ userId: '1', userData: { first_name: 'Updated', }, }); }); expect(mockUpdateUser).toHaveBeenCalledWith({ path: { user_id: '1' }, body: { first_name: 'Updated', }, throwOnError: false, }); }); it('handles update error', async () => { const mockUpdateUser = adminUpdateUser as jest.MockedFunction; mockUpdateUser.mockResolvedValue({ error: 'Update failed' } as any); const { result } = renderHook(() => useUpdateUser(), { wrapper }); await expect( result.current.mutateAsync({ userId: '1', userData: { first_name: 'Test' }, }) ).rejects.toThrow('Failed to update user'); }); }); describe('useDeleteUser', () => { it('deletes a user successfully', async () => { const mockDeleteUser = adminDeleteUser as jest.MockedFunction; mockDeleteUser.mockResolvedValue({ data: { success: true } } as any); const { result } = renderHook(() => useDeleteUser(), { wrapper }); await act(async () => { await result.current.mutateAsync('1'); }); expect(mockDeleteUser).toHaveBeenCalledWith({ path: { user_id: '1' }, throwOnError: false, }); }); it('handles delete error', async () => { const mockDeleteUser = adminDeleteUser as jest.MockedFunction; mockDeleteUser.mockResolvedValue({ error: 'Delete failed' } as any); const { result } = renderHook(() => useDeleteUser(), { wrapper }); await expect(result.current.mutateAsync('1')).rejects.toThrow('Failed to delete user'); }); }); describe('useActivateUser', () => { it('activates a user successfully', async () => { const mockActivateUser = adminActivateUser as jest.MockedFunction; mockActivateUser.mockResolvedValue({ data: { success: true } } as any); const { result } = renderHook(() => useActivateUser(), { wrapper }); await act(async () => { await result.current.mutateAsync('1'); }); expect(mockActivateUser).toHaveBeenCalledWith({ path: { user_id: '1' }, throwOnError: false, }); }); it('handles activate error', async () => { const mockActivateUser = adminActivateUser as jest.MockedFunction; mockActivateUser.mockResolvedValue({ error: 'Activate failed' } as any); const { result } = renderHook(() => useActivateUser(), { wrapper }); await expect(result.current.mutateAsync('1')).rejects.toThrow('Failed to activate user'); }); }); describe('useDeactivateUser', () => { it('deactivates a user successfully', async () => { const mockDeactivateUser = adminDeactivateUser as jest.MockedFunction; mockDeactivateUser.mockResolvedValue({ data: { success: true } } as any); const { result } = renderHook(() => useDeactivateUser(), { wrapper }); await act(async () => { await result.current.mutateAsync('1'); }); expect(mockDeactivateUser).toHaveBeenCalledWith({ path: { user_id: '1' }, throwOnError: false, }); }); it('handles deactivate error', async () => { const mockDeactivateUser = adminDeactivateUser as jest.MockedFunction; mockDeactivateUser.mockResolvedValue({ error: 'Deactivate failed' } as any); const { result } = renderHook(() => useDeactivateUser(), { wrapper }); await expect(result.current.mutateAsync('1')).rejects.toThrow('Failed to deactivate user'); }); }); describe('useBulkUserAction', () => { it('performs bulk activate successfully', async () => { const mockBulkAction = adminBulkUserAction as jest.MockedFunction; mockBulkAction.mockResolvedValue({ data: { success: true, affected_count: 2 } } as any); const { result } = renderHook(() => useBulkUserAction(), { wrapper }); await act(async () => { await result.current.mutateAsync({ action: 'activate', userIds: ['1', '2'], }); }); expect(mockBulkAction).toHaveBeenCalledWith({ body: { action: 'activate', user_ids: ['1', '2'] }, throwOnError: false, }); }); it('performs bulk deactivate successfully', async () => { const mockBulkAction = adminBulkUserAction as jest.MockedFunction; mockBulkAction.mockResolvedValue({ data: { success: true, affected_count: 3 } } as any); const { result } = renderHook(() => useBulkUserAction(), { wrapper }); await act(async () => { await result.current.mutateAsync({ action: 'deactivate', userIds: ['1', '2', '3'], }); }); expect(mockBulkAction).toHaveBeenCalledWith({ body: { action: 'deactivate', user_ids: ['1', '2', '3'] }, throwOnError: false, }); }); it('performs bulk delete successfully', async () => { const mockBulkAction = adminBulkUserAction as jest.MockedFunction; mockBulkAction.mockResolvedValue({ data: { success: true, affected_count: 1 } } as any); const { result } = renderHook(() => useBulkUserAction(), { wrapper }); await act(async () => { await result.current.mutateAsync({ action: 'delete', userIds: ['1'], }); }); expect(mockBulkAction).toHaveBeenCalledWith({ body: { action: 'delete', user_ids: ['1'] }, throwOnError: false, }); }); it('handles bulk action error', async () => { const mockBulkAction = adminBulkUserAction as jest.MockedFunction; mockBulkAction.mockResolvedValue({ error: 'Bulk action failed' } as any); const { result } = renderHook(() => useBulkUserAction(), { wrapper }); await expect( result.current.mutateAsync({ action: 'activate', userIds: ['1', '2'], }) ).rejects.toThrow('Failed to perform bulk action'); }); }); });