test: vitest setup + auth/login/cookie banner tests
This commit is contained in:
@@ -7,7 +7,9 @@
|
|||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:watch": "vitest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lucide-react": "^1.8.0",
|
"lucide-react": "^1.8.0",
|
||||||
@@ -25,6 +27,11 @@
|
|||||||
"eslint-plugin-react-hooks": "^7.1.1",
|
"eslint-plugin-react-hooks": "^7.1.1",
|
||||||
"eslint-plugin-react-refresh": "^0.5.2",
|
"eslint-plugin-react-refresh": "^0.5.2",
|
||||||
"globals": "^17.5.0",
|
"globals": "^17.5.0",
|
||||||
"vite": "^8.0.10"
|
"vite": "^8.0.10",
|
||||||
|
"vitest": "^4.1.9",
|
||||||
|
"jsdom": "^29.1.1",
|
||||||
|
"@testing-library/react": "^16.3.2",
|
||||||
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
|
"@testing-library/user-event": "^14.6.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+753
-4
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,49 @@
|
|||||||
|
/* Tests du contexte d'authentification : login / logout */
|
||||||
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||||
|
import { renderHook, act } from '@testing-library/react';
|
||||||
|
import { AuthProvider, useAuth } from '../context/AuthContext';
|
||||||
|
import * as api from '../services/api';
|
||||||
|
|
||||||
|
describe('AuthContext', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
localStorage.clear();
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
function wrapper({ children }) {
|
||||||
|
return <AuthProvider>{children}</AuthProvider>;
|
||||||
|
}
|
||||||
|
|
||||||
|
it('login renseigne l\'utilisateur, logout le vide', async () => {
|
||||||
|
const fakeUser = { username: 'bob', role: 'Consultant' };
|
||||||
|
vi.spyOn(api, 'login').mockResolvedValue(fakeUser);
|
||||||
|
const clearSpy = vi.spyOn(api, 'clearToken').mockImplementation(() => {});
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useAuth(), { wrapper });
|
||||||
|
|
||||||
|
expect(result.current.user).toBeNull();
|
||||||
|
expect(result.current.isAuthenticated).toBe(false);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
await result.current.login('bob', 'pwd');
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.current.user).toEqual(fakeUser);
|
||||||
|
expect(result.current.isAuthenticated).toBe(true);
|
||||||
|
/* L'utilisateur est persisté dans le localStorage */
|
||||||
|
expect(JSON.parse(localStorage.getItem('ds_user'))).toEqual(fakeUser);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
result.current.logout();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.current.user).toBeNull();
|
||||||
|
expect(result.current.isAuthenticated).toBe(false);
|
||||||
|
expect(localStorage.getItem('ds_user')).toBeNull();
|
||||||
|
expect(clearSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/* Tests du bandeau de consentement aux cookies (RGPD) */
|
||||||
|
import { describe, it, expect, beforeEach } from 'vitest';
|
||||||
|
import { render, screen, fireEvent } from '@testing-library/react';
|
||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
import CookieBanner from '../components/common/CookieBanner';
|
||||||
|
|
||||||
|
function renderBanner() {
|
||||||
|
return render(
|
||||||
|
<MemoryRouter>
|
||||||
|
<CookieBanner />
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('CookieBanner', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
localStorage.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('s\'affiche tant qu\'aucun consentement n\'est enregistré', () => {
|
||||||
|
renderBanner();
|
||||||
|
expect(screen.getByRole('dialog', { name: /Consentement aux cookies/ })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: 'Tout accepter' })).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('button', { name: 'Refuser' })).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('se masque et persiste le choix après "Tout accepter"', () => {
|
||||||
|
renderBanner();
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Tout accepter' }));
|
||||||
|
|
||||||
|
/* Le bandeau disparaît */
|
||||||
|
expect(screen.queryByRole('dialog', { name: /Consentement aux cookies/ })).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
/* La décision est persistée au format attendu */
|
||||||
|
const stored = JSON.parse(localStorage.getItem('ds_cookies_consent'));
|
||||||
|
expect(stored.choice).toBe('accept');
|
||||||
|
expect(stored.version).toBe(1);
|
||||||
|
expect(typeof stored.date).toBe('string');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ne s\'affiche pas si un consentement existe déjà', () => {
|
||||||
|
localStorage.setItem('ds_cookies_consent', JSON.stringify({ choice: 'refuse', date: '2026-01-01', version: 1 }));
|
||||||
|
renderBanner();
|
||||||
|
expect(screen.queryByRole('dialog', { name: /Consentement aux cookies/ })).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/* Tests de la page de connexion : validation du captcha anti-bot */
|
||||||
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||||
|
import { render, screen, fireEvent } from '@testing-library/react';
|
||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
import Login from '../pages/Login/Login';
|
||||||
|
import { AuthProvider } from '../context/AuthContext';
|
||||||
|
import * as api from '../services/api';
|
||||||
|
|
||||||
|
function renderLogin() {
|
||||||
|
return render(
|
||||||
|
<MemoryRouter>
|
||||||
|
<AuthProvider>
|
||||||
|
<Login />
|
||||||
|
</AuthProvider>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Login - captcha', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
localStorage.clear();
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('affiche une erreur et n\'appelle pas l\'API si le captcha est faux', async () => {
|
||||||
|
const loginSpy = vi.spyOn(api, 'login').mockResolvedValue({ username: 'x', role: 'Admin' });
|
||||||
|
|
||||||
|
renderLogin();
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByLabelText('Identifiant'), { target: { value: 'alice' } });
|
||||||
|
fireEvent.change(screen.getByLabelText('Mot de passe'), { target: { value: 'pwd' } });
|
||||||
|
/* Réponse volontairement absurde, jamais égale à x + y (max 20) */
|
||||||
|
fireEvent.change(screen.getByLabelText(/combien font/i), { target: { value: '9999' } });
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: /Se connecter/ }));
|
||||||
|
|
||||||
|
expect(await screen.findByText('Réponse au captcha incorrecte.')).toBeInTheDocument();
|
||||||
|
expect(loginSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
/* Tests du service API : authentification et gestion du token JWT */
|
||||||
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||||
|
import { login, getServices, getToken, setToken } from '../services/api';
|
||||||
|
|
||||||
|
describe('api.login', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
localStorage.clear();
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('stocke le token et envoie un corps urlencoded', async () => {
|
||||||
|
const fetchMock = vi.fn().mockResolvedValue({
|
||||||
|
ok: true,
|
||||||
|
status: 200,
|
||||||
|
json: async () => ({
|
||||||
|
access_token: 'jwt-abc-123',
|
||||||
|
token_type: 'bearer',
|
||||||
|
user: { username: 'alice', role: 'Admin' },
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
vi.stubGlobal('fetch', fetchMock);
|
||||||
|
|
||||||
|
const user = await login('alice', 's3cret');
|
||||||
|
|
||||||
|
/* Le token est persisté */
|
||||||
|
expect(getToken()).toBe('jwt-abc-123');
|
||||||
|
/* L'utilisateur retourné provient de la réponse */
|
||||||
|
expect(user).toEqual({ username: 'alice', role: 'Admin' });
|
||||||
|
|
||||||
|
/* Vérifie l'URL, la méthode et l'en-tête de contenu */
|
||||||
|
const [url, options] = fetchMock.mock.calls[0];
|
||||||
|
expect(url).toMatch(/\/auth\/login$/);
|
||||||
|
expect(options.method).toBe('POST');
|
||||||
|
expect(options.headers['Content-Type']).toBe('application/x-www-form-urlencoded');
|
||||||
|
|
||||||
|
/* Le corps urlencoded contient bien username et password */
|
||||||
|
const params = new URLSearchParams(options.body);
|
||||||
|
expect(params.get('username')).toBe('alice');
|
||||||
|
expect(params.get('password')).toBe('s3cret');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('lève "Identifiants invalides" sur une réponse 401', async () => {
|
||||||
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
|
||||||
|
ok: false,
|
||||||
|
status: 401,
|
||||||
|
text: async () => 'Unauthorized',
|
||||||
|
}));
|
||||||
|
|
||||||
|
await expect(login('alice', 'wrong')).rejects.toThrow('Identifiants invalides');
|
||||||
|
expect(getToken()).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('fetchApi (401)', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
localStorage.clear();
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('purge le token lorsqu\'une requête renvoie 401', async () => {
|
||||||
|
setToken('expired-token');
|
||||||
|
expect(getToken()).toBe('expired-token');
|
||||||
|
|
||||||
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
|
||||||
|
ok: false,
|
||||||
|
status: 401,
|
||||||
|
text: async () => 'Unauthorized',
|
||||||
|
}));
|
||||||
|
|
||||||
|
await expect(getServices()).rejects.toThrow();
|
||||||
|
/* Le token doit avoir été supprimé par handleUnauthorized */
|
||||||
|
expect(getToken()).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
/* Configuration globale des tests : matchers jest-dom + nettoyage du localStorage */
|
||||||
|
import '@testing-library/jest-dom';
|
||||||
|
import { afterEach } from 'vitest';
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
localStorage.clear();
|
||||||
|
});
|
||||||
@@ -6,4 +6,9 @@ export default defineConfig({
|
|||||||
plugins: [
|
plugins: [
|
||||||
react(),
|
react(),
|
||||||
],
|
],
|
||||||
|
test: {
|
||||||
|
environment: 'jsdom',
|
||||||
|
globals: true,
|
||||||
|
setupFiles: './src/test/setup.js',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user