81 lines
2.3 KiB
TypeScript
81 lines
2.3 KiB
TypeScript
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||
|
|
import { render, screen, waitFor } from '@testing-library/react';
|
||
|
|
import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
||
|
|
import { ProtectedRoute } from '@/components/ProtectedRoute';
|
||
|
|
import { ToastProvider } from '@/hooks/useToast';
|
||
|
|
import { AuthProvider } from '@/hooks/useAuth';
|
||
|
|
import { setToken } from '@/api/client';
|
||
|
|
import { ToastViewport } from '@/components/Toast';
|
||
|
|
|
||
|
|
// Mock the auth API so AuthProvider hydrates without network.
|
||
|
|
vi.mock('@/api/auth', () => ({
|
||
|
|
login: vi.fn(),
|
||
|
|
logout: vi.fn(),
|
||
|
|
fetchMe: vi.fn(),
|
||
|
|
}));
|
||
|
|
|
||
|
|
import { fetchMe } from '@/api/auth';
|
||
|
|
|
||
|
|
function setup() {
|
||
|
|
return render(
|
||
|
|
<MemoryRouter initialEntries={['/admin']}>
|
||
|
|
<ToastProvider>
|
||
|
|
<AuthProvider>
|
||
|
|
<Routes>
|
||
|
|
<Route path="/login" element={<div>LOGIN PAGE</div>} />
|
||
|
|
<Route path="/engagements" element={<div>ENGAGEMENTS</div>} />
|
||
|
|
<Route element={<ProtectedRoute roles={['admin']} />}>
|
||
|
|
<Route path="/admin" element={<div>ADMIN AREA</div>} />
|
||
|
|
</Route>
|
||
|
|
</Routes>
|
||
|
|
<ToastViewport />
|
||
|
|
</AuthProvider>
|
||
|
|
</ToastProvider>
|
||
|
|
</MemoryRouter>,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
describe('ProtectedRoute', () => {
|
||
|
|
beforeEach(() => {
|
||
|
|
localStorage.clear();
|
||
|
|
vi.mocked(fetchMe).mockReset();
|
||
|
|
});
|
||
|
|
|
||
|
|
afterEach(() => {
|
||
|
|
setToken(null);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('redirects unauthenticated users to /login', async () => {
|
||
|
|
// No token → unauthenticated, no /me call.
|
||
|
|
setup();
|
||
|
|
expect(await screen.findByText('LOGIN PAGE')).toBeInTheDocument();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('admins reach the admin page', async () => {
|
||
|
|
setToken('fake-token');
|
||
|
|
vi.mocked(fetchMe).mockResolvedValue({
|
||
|
|
id: 1,
|
||
|
|
username: 'alice',
|
||
|
|
role: 'admin',
|
||
|
|
created_at: '2026-01-01',
|
||
|
|
});
|
||
|
|
setup();
|
||
|
|
expect(await screen.findByText('ADMIN AREA')).toBeInTheDocument();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('non-admins get redirected and see an access denied toast', async () => {
|
||
|
|
setToken('fake-token');
|
||
|
|
vi.mocked(fetchMe).mockResolvedValue({
|
||
|
|
id: 2,
|
||
|
|
username: 'bob',
|
||
|
|
role: 'soc',
|
||
|
|
created_at: '2026-01-01',
|
||
|
|
});
|
||
|
|
setup();
|
||
|
|
expect(await screen.findByText('ENGAGEMENTS')).toBeInTheDocument();
|
||
|
|
await waitFor(() => {
|
||
|
|
expect(screen.getByTestId('toast')).toHaveTextContent('Accès refusé');
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|