305 lines
8.2 KiB
TypeScript
305 lines
8.2 KiB
TypeScript
/**
|
|
* Tests d'intégration pour l'authentification Keycloak
|
|
* Vérifie que l'intégration fonctionne correctement avec l'application existante
|
|
*/
|
|
|
|
import React from 'react';
|
|
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
|
import { useRouter } from 'next/navigation';
|
|
import { AuthProvider, useAuth } from '../../contexts/AuthContext';
|
|
import ProtectedRoute from '../../components/auth/ProtectedRoute';
|
|
import ProtectedLayout from '../../components/ProtectedLayout';
|
|
|
|
// Mock Next.js router
|
|
jest.mock('next/navigation', () => ({
|
|
useRouter: jest.fn(),
|
|
useSearchParams: jest.fn(() => ({
|
|
get: jest.fn(() => null)
|
|
}))
|
|
}));
|
|
|
|
// Mock Keycloak
|
|
const mockKeycloak = {
|
|
init: jest.fn(() => Promise.resolve(false)),
|
|
login: jest.fn(),
|
|
logout: jest.fn(),
|
|
updateToken: jest.fn(() => Promise.resolve(false)),
|
|
authenticated: false,
|
|
token: null,
|
|
refreshToken: null,
|
|
tokenParsed: null,
|
|
onTokenExpired: null,
|
|
onAuthRefreshSuccess: null,
|
|
onAuthRefreshError: null,
|
|
onAuthLogout: null,
|
|
};
|
|
|
|
jest.mock('../../config/keycloak', () => ({
|
|
keycloak: mockKeycloak,
|
|
keycloakInitOptions: {},
|
|
KEYCLOAK_TIMEOUTS: {
|
|
TOKEN_REFRESH_BEFORE_EXPIRY: 30,
|
|
SESSION_CHECK_INTERVAL: 60,
|
|
SILENT_CHECK_SSO_TIMEOUT: 5000,
|
|
},
|
|
RoleUtils: {
|
|
hasRole: jest.fn(() => false),
|
|
hasPermission: jest.fn(() => false),
|
|
hasAnyRole: jest.fn(() => false),
|
|
getHighestRole: jest.fn(() => 'USER'),
|
|
}
|
|
}));
|
|
|
|
// Mock PrimeReact components
|
|
jest.mock('primereact/api', () => ({
|
|
PrimeReactProvider: ({ children }: { children: React.ReactNode }) => <div>{children}</div>
|
|
}));
|
|
|
|
// Mock Layout components
|
|
jest.mock('../../layout/context/layoutcontext', () => ({
|
|
LayoutProvider: ({ children }: { children: React.ReactNode }) => <div>{children}</div>
|
|
}));
|
|
|
|
const mockPush = jest.fn();
|
|
const mockBack = jest.fn();
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
(useRouter as jest.Mock).mockReturnValue({
|
|
push: mockPush,
|
|
back: mockBack,
|
|
pathname: '/dashboard',
|
|
query: {},
|
|
});
|
|
});
|
|
|
|
// Composant de test pour vérifier l'authentification
|
|
const TestAuthComponent = () => {
|
|
const { isAuthenticated, isLoading, user, login, logout } = useAuth();
|
|
|
|
if (isLoading) {
|
|
return <div data-testid="loading">Chargement...</div>;
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<div data-testid="auth-status">
|
|
{isAuthenticated ? 'Authentifié' : 'Non authentifié'}
|
|
</div>
|
|
{user && (
|
|
<div data-testid="user-info">
|
|
{user.username} - {user.email}
|
|
</div>
|
|
)}
|
|
<button data-testid="login-btn" onClick={login}>
|
|
Se connecter
|
|
</button>
|
|
<button data-testid="logout-btn" onClick={logout}>
|
|
Se déconnecter
|
|
</button>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
// Composant de test pour vérifier la protection des routes
|
|
const TestProtectedComponent = () => {
|
|
return <div data-testid="protected-content">Contenu protégé</div>;
|
|
};
|
|
|
|
describe('Intégration Authentification Keycloak', () => {
|
|
describe('AuthProvider', () => {
|
|
it('devrait initialiser Keycloak au montage', async () => {
|
|
render(
|
|
<AuthProvider>
|
|
<TestAuthComponent />
|
|
</AuthProvider>
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(mockKeycloak.init).toHaveBeenCalledWith(expect.any(Object));
|
|
});
|
|
});
|
|
|
|
it('devrait afficher le statut de chargement initial', () => {
|
|
render(
|
|
<AuthProvider>
|
|
<TestAuthComponent />
|
|
</AuthProvider>
|
|
);
|
|
|
|
expect(screen.getByTestId('loading')).toBeInTheDocument();
|
|
});
|
|
|
|
it('devrait afficher le statut non authentifié après initialisation', async () => {
|
|
mockKeycloak.authenticated = false;
|
|
|
|
render(
|
|
<AuthProvider>
|
|
<TestAuthComponent />
|
|
</AuthProvider>
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('auth-status')).toHaveTextContent('Non authentifié');
|
|
});
|
|
});
|
|
|
|
it('devrait appeler keycloak.login lors du clic sur connexion', async () => {
|
|
mockKeycloak.authenticated = false;
|
|
|
|
render(
|
|
<AuthProvider>
|
|
<TestAuthComponent />
|
|
</AuthProvider>
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('login-btn')).toBeInTheDocument();
|
|
});
|
|
|
|
fireEvent.click(screen.getByTestId('login-btn'));
|
|
expect(mockKeycloak.login).toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('ProtectedRoute', () => {
|
|
it('devrait rediriger vers login si non authentifié', async () => {
|
|
mockKeycloak.authenticated = false;
|
|
|
|
render(
|
|
<AuthProvider>
|
|
<ProtectedRoute>
|
|
<TestProtectedComponent />
|
|
</ProtectedRoute>
|
|
</AuthProvider>
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(mockPush).toHaveBeenCalledWith(expect.stringContaining('/auth/login'));
|
|
});
|
|
});
|
|
|
|
it('devrait afficher le contenu si authentifié', async () => {
|
|
mockKeycloak.authenticated = true;
|
|
mockKeycloak.tokenParsed = {
|
|
preferred_username: 'testuser',
|
|
email: 'test@example.com',
|
|
realm_access: { roles: ['USER'] }
|
|
};
|
|
|
|
render(
|
|
<AuthProvider>
|
|
<ProtectedRoute>
|
|
<TestProtectedComponent />
|
|
</ProtectedRoute>
|
|
</AuthProvider>
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('protected-content')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it('devrait rediriger vers forbidden si rôle insuffisant', async () => {
|
|
mockKeycloak.authenticated = true;
|
|
mockKeycloak.tokenParsed = {
|
|
preferred_username: 'testuser',
|
|
email: 'test@example.com',
|
|
realm_access: { roles: ['USER'] }
|
|
};
|
|
|
|
render(
|
|
<AuthProvider>
|
|
<ProtectedRoute requiredRoles={['ADMIN']}>
|
|
<TestProtectedComponent />
|
|
</ProtectedRoute>
|
|
</AuthProvider>
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(mockPush).toHaveBeenCalledWith('/auth/forbidden');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('ProtectedLayout', () => {
|
|
it('devrait protéger le layout principal', async () => {
|
|
mockKeycloak.authenticated = false;
|
|
|
|
render(
|
|
<AuthProvider>
|
|
<ProtectedLayout>
|
|
<div data-testid="layout-content">Contenu du layout</div>
|
|
</ProtectedLayout>
|
|
</AuthProvider>
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(mockPush).toHaveBeenCalledWith(expect.stringContaining('/auth/login'));
|
|
});
|
|
});
|
|
|
|
it('devrait afficher le contenu du layout si authentifié', async () => {
|
|
mockKeycloak.authenticated = true;
|
|
mockKeycloak.tokenParsed = {
|
|
preferred_username: 'testuser',
|
|
email: 'test@example.com',
|
|
realm_access: { roles: ['USER'] }
|
|
};
|
|
|
|
render(
|
|
<AuthProvider>
|
|
<ProtectedLayout>
|
|
<div data-testid="layout-content">Contenu du layout</div>
|
|
</ProtectedLayout>
|
|
</AuthProvider>
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('layout-content')).toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Gestion des erreurs', () => {
|
|
it('devrait gérer les erreurs d\'initialisation Keycloak', async () => {
|
|
mockKeycloak.init.mockRejectedValueOnce(new Error('Erreur Keycloak'));
|
|
|
|
render(
|
|
<AuthProvider>
|
|
<TestAuthComponent />
|
|
</AuthProvider>
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByTestId('auth-status')).toHaveTextContent('Non authentifié');
|
|
});
|
|
});
|
|
|
|
it('devrait gérer les erreurs de rafraîchissement de token', async () => {
|
|
mockKeycloak.authenticated = true;
|
|
mockKeycloak.updateToken.mockRejectedValueOnce(new Error('Token expiré'));
|
|
|
|
render(
|
|
<AuthProvider>
|
|
<TestAuthComponent />
|
|
</AuthProvider>
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(mockKeycloak.logout).toHaveBeenCalled();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Intégration avec les services API', () => {
|
|
it('devrait ajouter le token Keycloak aux requêtes API', async () => {
|
|
// Ce test nécessiterait de mocker axios et de vérifier que les interceptors
|
|
// ajoutent correctement le token Keycloak aux en-têtes
|
|
// Pour l'instant, nous vérifions juste que les services sont importables
|
|
const { ApiService } = await import('../../services/ApiService');
|
|
expect(ApiService).toBeDefined();
|
|
});
|
|
});
|