Refactor and centralize user and pagination interfaces in useAdmin hook

- Unified `User` and `PaginationMeta` type definitions into `useAdmin` to improve maintainability and consistency.
- Updated affected components (`UserManagementContent`, `UserListTable`, `UserFormDialog`, `UserActionMenu`) to reference the centralized types.
- Enhanced test coverage for user-related hooks to include create, update, delete, activate, deactivate, and bulk actions.
This commit is contained in:
Felipe Cardoso
2025-11-06 12:49:46 +01:00
parent 91bc4f190d
commit f22f87250c
6 changed files with 316 additions and 49 deletions

View File

@@ -3,10 +3,29 @@
* Verifies admin statistics and list fetching functionality
*/
import { renderHook, waitFor } from '@testing-library/react';
import { renderHook, waitFor, act } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { useAdminStats, useAdminUsers, useAdminOrganizations } from '@/lib/api/hooks/useAdmin';
import { adminListUsers, adminListOrganizations } from '@/lib/api/client';
import {
useAdminStats,
useAdminUsers,
useAdminOrganizations,
useCreateUser,
useUpdateUser,
useDeleteUser,
useActivateUser,
useDeactivateUser,
useBulkUserAction,
} from '@/lib/api/hooks/useAdmin';
import {
adminListUsers,
adminListOrganizations,
adminCreateUser,
adminUpdateUser,
adminDeleteUser,
adminActivateUser,
adminDeactivateUser,
adminBulkUserAction,
} from '@/lib/api/client';
import { useAuth } from '@/lib/auth/AuthContext';
// Mock dependencies
@@ -79,12 +98,12 @@ describe('useAdmin hooks', () => {
});
expect(mockAdminListUsers).toHaveBeenCalledWith({
query: { page: 1, limit: 10000 },
query: { page: 1, limit: 100 },
throwOnError: false,
});
expect(mockAdminListOrganizations).toHaveBeenCalledWith({
query: { page: 1, limit: 10000 },
query: { page: 1, limit: 100 },
throwOnError: false,
});
});
@@ -327,4 +346,253 @@ describe('useAdmin hooks', () => {
expect(result.current.error).toBeDefined();
});
});
describe('useCreateUser', () => {
it('creates a user successfully', async () => {
const mockCreateUser = adminCreateUser as jest.MockedFunction<typeof adminCreateUser>;
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_active: true,
is_superuser: false,
});
});
expect(mockCreateUser).toHaveBeenCalledWith({
body: {
email: 'newuser@example.com',
first_name: 'New',
last_name: 'User',
password: 'Password123',
is_active: true,
is_superuser: false,
},
throwOnError: false,
});
});
it('handles create error', async () => {
const mockCreateUser = adminCreateUser as jest.MockedFunction<typeof adminCreateUser>;
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_active: true,
is_superuser: false,
})
).rejects.toThrow('Failed to create user');
});
});
describe('useUpdateUser', () => {
it('updates a user successfully', async () => {
const mockUpdateUser = adminUpdateUser as jest.MockedFunction<typeof adminUpdateUser>;
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: {
email: 'updated@example.com',
first_name: 'Updated',
},
});
});
expect(mockUpdateUser).toHaveBeenCalledWith({
path: { user_id: '1' },
body: {
email: 'updated@example.com',
first_name: 'Updated',
},
throwOnError: false,
});
});
it('handles update error', async () => {
const mockUpdateUser = adminUpdateUser as jest.MockedFunction<typeof adminUpdateUser>;
mockUpdateUser.mockResolvedValue({ error: 'Update failed' } as any);
const { result } = renderHook(() => useUpdateUser(), { wrapper });
await expect(
result.current.mutateAsync({
userId: '1',
userData: { email: 'test@example.com' },
})
).rejects.toThrow('Failed to update user');
});
});
describe('useDeleteUser', () => {
it('deletes a user successfully', async () => {
const mockDeleteUser = adminDeleteUser as jest.MockedFunction<typeof adminDeleteUser>;
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<typeof adminDeleteUser>;
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<typeof adminActivateUser>;
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<typeof adminActivateUser>;
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<typeof adminDeactivateUser>;
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<typeof adminDeactivateUser>;
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<typeof adminBulkUserAction>;
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<typeof adminBulkUserAction>;
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<typeof adminBulkUserAction>;
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<typeof adminBulkUserAction>;
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');
});
});
});