Adds user management

This commit is contained in:
2026-06-22 21:14:22 +02:00
parent 5331be4279
commit 6976eb4337
40 changed files with 3392 additions and 146 deletions
+104
View File
@@ -0,0 +1,104 @@
import { renderHook, waitFor, act } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { http, HttpResponse } from 'msw';
import { createElement } from 'react';
import { describe, expect, it } from 'vitest';
import { server } from '@/mocks/server';
import { API_BASE } from '@/mocks/auth/fixtures';
import { getMockUsers } from '@/mocks/users/handlers';
import { useUsers, useInviteUser, useChangeRole } from './useUsers';
function createWrapper() {
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
});
return ({ children }: { children: React.ReactNode }) =>
createElement(QueryClientProvider, { client: queryClient }, children);
}
const USERS_URL = `${API_BASE}/api/v1/Users`;
describe('useUsers', () => {
it('starts in loading state', () => {
const { result } = renderHook(() => useUsers(), { wrapper: createWrapper() });
expect(result.current.isPending).toBe(true);
});
it('returns the users list on success', async () => {
const { result } = renderHook(() => useUsers(), { wrapper: createWrapper() });
await waitFor(() => expect(result.current.isPending).toBe(false));
expect(result.current.isError).toBe(false);
expect(result.current.data).toHaveLength(getMockUsers().length);
expect(result.current.data?.[0].email).toBe(getMockUsers()[0].email);
});
it('enters error state on 5xx', async () => {
server.use(http.get(USERS_URL, () => HttpResponse.json({}, { status: 500 })));
const { result } = renderHook(() => useUsers(), { wrapper: createWrapper() });
await waitFor(() => expect(result.current.isPending).toBe(false));
expect(result.current.isError).toBe(true);
});
});
describe('useInviteUser', () => {
it('returns inviteLink on successful mutation', async () => {
const { result } = renderHook(() => useInviteUser(), { wrapper: createWrapper() });
await act(async () => {
await result.current.mutateAsync({ email: 'new@example.com', role: 'User' });
});
await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(result.current.data?.inviteLink).toContain('/invite/complete');
});
it('fails when server returns 400', async () => {
server.use(
http.post(`${USERS_URL}/invite`, () =>
HttpResponse.json({ detail: 'already exists' }, { status: 400 }),
),
);
const { result } = renderHook(() => useInviteUser(), { wrapper: createWrapper() });
await act(async () => {
await result.current.mutateAsync({ email: 'dup@example.com', role: 'User' }).catch(() => {});
});
await waitFor(() => expect(result.current.isError).toBe(true));
});
});
describe('useChangeRole', () => {
it('succeeds when server returns 200', async () => {
const { result } = renderHook(() => useChangeRole(), { wrapper: createWrapper() });
await act(async () => {
await result.current.mutateAsync({
userId: '22222222-2222-2222-2222-222222222222',
newRole: 'User',
});
});
await waitFor(() => expect(result.current.isSuccess).toBe(true));
});
it('fails when server returns 403', async () => {
server.use(
http.put(`${USERS_URL}/:userId/role`, () => new HttpResponse(null, { status: 403 })),
);
const { result } = renderHook(() => useChangeRole(), { wrapper: createWrapper() });
await act(async () => {
await result.current
.mutateAsync({ userId: 'any-id', newRole: 'Administrator' })
.catch(() => {});
});
await waitFor(() => expect(result.current.isError).toBe(true));
});
});