Initial commit
This commit is contained in:
304
tests/integration/auth-integration.test.tsx
Normal file
304
tests/integration/auth-integration.test.tsx
Normal file
@@ -0,0 +1,304 @@
|
||||
/**
|
||||
* 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();
|
||||
});
|
||||
});
|
||||
168
tests/manual/auth-integration-checklist.md
Normal file
168
tests/manual/auth-integration-checklist.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# Checklist de Tests d'Intégration Keycloak
|
||||
|
||||
## 🔧 Prérequis
|
||||
- [ ] Keycloak est démarré sur `https://security.lions.dev`
|
||||
- [ ] Le realm `btpxpress` est configuré
|
||||
- [ ] Les clients `btpxpress-frontend` et `btpxpress-backend` sont créés
|
||||
- [ ] L'application frontend est démarrée sur `http://localhost:3000`
|
||||
- [ ] L'application backend est démarrée sur `http://localhost:8080`
|
||||
|
||||
## 🚀 Tests d'Authentification
|
||||
|
||||
### 1. Accès Initial
|
||||
- [ ] Aller sur `http://localhost:3000`
|
||||
- [ ] Vérifier que la page d'accueil s'affiche (page publique)
|
||||
- [ ] Cliquer sur un lien vers une page protégée (ex: Dashboard)
|
||||
- [ ] Vérifier la redirection vers `/auth/login`
|
||||
|
||||
### 2. Page de Connexion
|
||||
- [ ] Vérifier que la page de connexion s'affiche correctement
|
||||
- [ ] Vérifier le bouton "Se connecter avec Keycloak"
|
||||
- [ ] Cliquer sur le bouton de connexion
|
||||
- [ ] Vérifier la redirection vers Keycloak
|
||||
|
||||
### 3. Authentification Keycloak
|
||||
- [ ] Vérifier que la page de connexion Keycloak s'affiche
|
||||
- [ ] Se connecter avec un utilisateur valide
|
||||
- [ ] Vérifier la redirection vers l'application
|
||||
- [ ] Vérifier l'affichage du dashboard ou de la page demandée
|
||||
|
||||
### 4. État Authentifié
|
||||
- [ ] Vérifier que le menu utilisateur affiche les informations correctes
|
||||
- [ ] Vérifier que le nom d'utilisateur est affiché
|
||||
- [ ] Vérifier que l'email est affiché
|
||||
- [ ] Vérifier que le rôle est affiché
|
||||
|
||||
## 🔒 Tests d'Autorisation
|
||||
|
||||
### 5. Protection des Routes
|
||||
- [ ] Accéder à `/dashboard` - doit être accessible si authentifié
|
||||
- [ ] Accéder à `/chantiers` - doit être accessible si authentifié
|
||||
- [ ] Accéder à `/admin/utilisateurs` - doit être accessible seulement pour ADMIN/SUPER_ADMIN
|
||||
- [ ] Accéder à `/admin/roles` - doit être accessible seulement pour ADMIN/SUPER_ADMIN
|
||||
|
||||
### 6. Gestion des Rôles
|
||||
- [ ] Se connecter avec un utilisateur USER
|
||||
- [ ] Essayer d'accéder à `/admin/utilisateurs`
|
||||
- [ ] Vérifier la redirection vers `/auth/forbidden`
|
||||
- [ ] Vérifier l'affichage de la page d'accès interdit
|
||||
|
||||
### 7. Menu Dynamique
|
||||
- [ ] Vérifier que le menu affiche seulement les éléments autorisés
|
||||
- [ ] Se connecter avec différents rôles et vérifier les menus
|
||||
- [ ] Vérifier que les éléments admin ne s'affichent que pour les admins
|
||||
|
||||
## 🔄 Tests de Session
|
||||
|
||||
### 8. Rafraîchissement de Token
|
||||
- [ ] Rester connecté pendant plus de 5 minutes
|
||||
- [ ] Effectuer une action (ex: naviguer vers une nouvelle page)
|
||||
- [ ] Vérifier que le token est rafraîchi automatiquement
|
||||
- [ ] Vérifier qu'aucune déconnexion forcée n'a lieu
|
||||
|
||||
### 9. Expiration de Session
|
||||
- [ ] Laisser la session expirer (configurer un timeout court si nécessaire)
|
||||
- [ ] Essayer d'effectuer une action
|
||||
- [ ] Vérifier la redirection vers la page de connexion
|
||||
|
||||
### 10. Déconnexion
|
||||
- [ ] Cliquer sur "Se déconnecter" dans le menu utilisateur
|
||||
- [ ] Vérifier la redirection vers `/auth/logout`
|
||||
- [ ] Vérifier l'affichage de la page de déconnexion
|
||||
- [ ] Vérifier la redirection automatique vers l'accueil
|
||||
- [ ] Essayer d'accéder à une page protégée
|
||||
- [ ] Vérifier la redirection vers la connexion
|
||||
|
||||
## 🌐 Tests API
|
||||
|
||||
### 11. Intégration Backend
|
||||
- [ ] Ouvrir les outils de développement (F12)
|
||||
- [ ] Aller dans l'onglet Network
|
||||
- [ ] Effectuer une action qui appelle l'API (ex: charger la liste des chantiers)
|
||||
- [ ] Vérifier que les requêtes contiennent l'en-tête `Authorization: Bearer <token>`
|
||||
- [ ] Vérifier que les réponses sont correctes (200 OK)
|
||||
|
||||
### 12. Gestion des Erreurs API
|
||||
- [ ] Simuler une erreur 401 (token expiré)
|
||||
- [ ] Vérifier que l'application tente de rafraîchir le token
|
||||
- [ ] Si le rafraîchissement échoue, vérifier la redirection vers la connexion
|
||||
|
||||
## 🔧 Tests de Compatibilité
|
||||
|
||||
### 13. Rétrocompatibilité
|
||||
- [ ] Vérifier que l'ancien système JWT ne cause pas d'erreurs
|
||||
- [ ] Vérifier que les anciens tokens sont ignorés
|
||||
- [ ] Vérifier que le nettoyage des anciens tokens fonctionne
|
||||
|
||||
### 14. Navigation
|
||||
- [ ] Tester la navigation entre les pages
|
||||
- [ ] Vérifier que l'état d'authentification est maintenu
|
||||
- [ ] Tester le bouton "Retour" du navigateur
|
||||
- [ ] Tester le rafraîchissement de page (F5)
|
||||
|
||||
## 🚨 Tests d'Erreur
|
||||
|
||||
### 15. Keycloak Indisponible
|
||||
- [ ] Arrêter Keycloak temporairement
|
||||
- [ ] Essayer de se connecter
|
||||
- [ ] Vérifier l'affichage d'un message d'erreur approprié
|
||||
|
||||
### 16. Configuration Incorrecte
|
||||
- [ ] Modifier temporairement la configuration Keycloak (mauvais realm)
|
||||
- [ ] Vérifier la gestion des erreurs de configuration
|
||||
- [ ] Restaurer la configuration correcte
|
||||
|
||||
## 📱 Tests Multi-Navigateurs
|
||||
|
||||
### 17. Compatibilité Navigateurs
|
||||
- [ ] Tester sur Chrome
|
||||
- [ ] Tester sur Firefox
|
||||
- [ ] Tester sur Safari (si disponible)
|
||||
- [ ] Tester sur Edge
|
||||
|
||||
### 18. Mode Incognito
|
||||
- [ ] Tester l'authentification en mode incognito
|
||||
- [ ] Vérifier que la session ne persiste pas après fermeture
|
||||
|
||||
## ✅ Validation Finale
|
||||
|
||||
### 19. Workflow Complet
|
||||
- [ ] Effectuer un workflow complet : connexion → navigation → actions → déconnexion
|
||||
- [ ] Vérifier qu'aucune erreur n'apparaît dans la console
|
||||
- [ ] Vérifier que les performances sont acceptables
|
||||
|
||||
### 20. Documentation
|
||||
- [ ] Vérifier que la documentation est à jour
|
||||
- [ ] Vérifier que les variables d'environnement sont documentées
|
||||
- [ ] Vérifier que les instructions de déploiement sont correctes
|
||||
|
||||
## 📊 Résultats
|
||||
|
||||
| Test | Statut | Notes |
|
||||
|------|--------|-------|
|
||||
| Accès Initial | ⏳ | |
|
||||
| Page de Connexion | ⏳ | |
|
||||
| Authentification Keycloak | ⏳ | |
|
||||
| État Authentifié | ⏳ | |
|
||||
| Protection des Routes | ⏳ | |
|
||||
| Gestion des Rôles | ⏳ | |
|
||||
| Menu Dynamique | ⏳ | |
|
||||
| Rafraîchissement de Token | ⏳ | |
|
||||
| Expiration de Session | ⏳ | |
|
||||
| Déconnexion | ⏳ | |
|
||||
| Intégration Backend | ⏳ | |
|
||||
| Gestion des Erreurs API | ⏳ | |
|
||||
| Rétrocompatibilité | ⏳ | |
|
||||
| Navigation | ⏳ | |
|
||||
| Keycloak Indisponible | ⏳ | |
|
||||
| Configuration Incorrecte | ⏳ | |
|
||||
| Compatibilité Navigateurs | ⏳ | |
|
||||
| Mode Incognito | ⏳ | |
|
||||
| Workflow Complet | ⏳ | |
|
||||
| Documentation | ⏳ | |
|
||||
|
||||
**Légende :**
|
||||
- ✅ Réussi
|
||||
- ❌ Échec
|
||||
- ⏳ En attente
|
||||
- ⚠️ Attention requise
|
||||
Reference in New Issue
Block a user