diff --git a/DataSentinel/src/__tests__/Admin.test.jsx b/DataSentinel/src/__tests__/Admin.test.jsx
new file mode 100644
index 0000000..b8865b8
--- /dev/null
+++ b/DataSentinel/src/__tests__/Admin.test.jsx
@@ -0,0 +1,174 @@
+/* Écran d'administration des référentiels.
+
+ Avant correction, les boutons Modifier / Désactiver n'appelaient rien
+ (console.log) et « Enregistrer » se contentait de fermer la modale. Ces
+ tests vérifient que chaque action atteint réellement l'API et que les
+ erreurs métier remontent à l'utilisateur. */
+import { describe, it, expect, beforeEach, vi } from 'vitest';
+import { render, screen, waitFor, within } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import Admin from '../pages/Admin/Admin';
+import * as api from '../services/api';
+
+const SERVICES = [
+ { id_service: 1, nom_service: 'Contrat' },
+ { id_service: 2, nom_service: 'Fournisseur' },
+];
+const CATEGORIES = [{ id_categorie: 1, intitule_categorie: 'Tiers-payeurs' }];
+const MONITORINGS = [{
+ id_monito: 1, monito_intitule: 'Erreurs tiers payeurs', id_service: 1,
+ id_categorie: 1, table_source: 'MONITO_TIERS_PAYEURS', bdd_source: 'Sage',
+}];
+
+function stubLectures() {
+ vi.spyOn(api, 'getServices').mockResolvedValue(SERVICES);
+ vi.spyOn(api, 'getCategories').mockResolvedValue(CATEGORIES);
+ vi.spyOn(api, 'getContacts').mockResolvedValue([]);
+ vi.spyOn(api, 'getMonitorings').mockResolvedValue(MONITORINGS);
+}
+
+async function ouvrirOnglet(utilisateur, nom) {
+ await utilisateur.click(screen.getByRole('button', { name: nom }));
+}
+
+describe('Admin — CRUD des référentiels', () => {
+ beforeEach(() => {
+ vi.restoreAllMocks();
+ stubLectures();
+ vi.spyOn(window, 'confirm').mockReturnValue(true);
+ });
+
+ it('crée un service via l’API et recharge la liste', async () => {
+ const creation = vi.spyOn(api, 'createService').mockResolvedValue({ id_service: 3 });
+ const utilisateur = userEvent.setup();
+ render();
+
+ await screen.findByText('Administration');
+ await ouvrirOnglet(utilisateur, 'Services');
+ await utilisateur.click(screen.getByRole('button', { name: /Ajouter/ }));
+
+ await utilisateur.type(screen.getByLabelText('Nom du service'), 'Comptabilité');
+ await utilisateur.click(screen.getByRole('button', { name: 'Enregistrer' }));
+
+ await waitFor(() => {
+ expect(creation).toHaveBeenCalledWith({ nom_service: 'Comptabilité' });
+ });
+ /* Rechargement après écriture : la liste doit être redemandée. */
+ expect(api.getServices).toHaveBeenCalledTimes(2);
+ });
+
+ it('pré-remplit le formulaire de modification avec la ligne choisie', async () => {
+ vi.spyOn(api, 'updateService').mockResolvedValue({});
+ const utilisateur = userEvent.setup();
+ render();
+
+ await screen.findByText('Administration');
+ await ouvrirOnglet(utilisateur, 'Services');
+
+ const ligne = screen.getByText('Fournisseur').closest('tr');
+ await utilisateur.click(within(ligne).getByRole('button', { name: /Modifier/ }));
+
+ expect(screen.getByLabelText('Nom du service')).toHaveValue('Fournisseur');
+ });
+
+ it('envoie l’identifiant de la ligne lors d’une modification', async () => {
+ const modification = vi.spyOn(api, 'updateService').mockResolvedValue({});
+ const utilisateur = userEvent.setup();
+ render();
+
+ await screen.findByText('Administration');
+ await ouvrirOnglet(utilisateur, 'Services');
+
+ const ligne = screen.getByText('Fournisseur').closest('tr');
+ await utilisateur.click(within(ligne).getByRole('button', { name: /Modifier/ }));
+ await utilisateur.click(screen.getByRole('button', { name: 'Enregistrer' }));
+
+ await waitFor(() => {
+ expect(modification).toHaveBeenCalledWith(2, { nom_service: 'Fournisseur' });
+ });
+ });
+
+ it('demande confirmation avant de supprimer, et n’appelle rien si on refuse', async () => {
+ window.confirm.mockReturnValue(false);
+ const suppression = vi.spyOn(api, 'deleteService').mockResolvedValue({});
+ const utilisateur = userEvent.setup();
+ render();
+
+ await screen.findByText('Administration');
+ await ouvrirOnglet(utilisateur, 'Services');
+
+ const ligne = screen.getByText('Contrat').closest('tr');
+ await utilisateur.click(within(ligne).getByRole('button', { name: /Supprimer/ }));
+
+ expect(window.confirm).toHaveBeenCalled();
+ expect(suppression).not.toHaveBeenCalled();
+ });
+
+ it('affiche le refus de l’API quand des enregistrements sont rattachés', async () => {
+ /* Cas réel : l'API renvoie 409 avec le décompte des rattachements. */
+ vi.spyOn(api, 'deleteService').mockRejectedValue(
+ new Error('Erreur 409: Suppression impossible : 5 monitoring(s) rattachés.'),
+ );
+ const utilisateur = userEvent.setup();
+ render();
+
+ await screen.findByText('Administration');
+ await ouvrirOnglet(utilisateur, 'Services');
+
+ const ligne = screen.getByText('Contrat').closest('tr');
+ await utilisateur.click(within(ligne).getByRole('button', { name: /Supprimer/ }));
+
+ expect(await screen.findByText(/5 monitoring\(s\) rattachés/)).toBeInTheDocument();
+ });
+
+ it('parle de désactivation, pas de suppression, pour un monitoring', async () => {
+ /* TABLE_FINAL référence le monitoring : il n'est jamais supprimé. */
+ const desactivation = vi.spyOn(api, 'desactiverMonitoring').mockResolvedValue({});
+ const utilisateur = userEvent.setup();
+ render();
+
+ await screen.findByText('Administration');
+
+ const ligne = screen.getByText('Erreurs tiers payeurs').closest('tr');
+ expect(within(ligne).getByRole('button', { name: /Désactiver/ })).toBeInTheDocument();
+
+ await utilisateur.click(within(ligne).getByRole('button', { name: /Désactiver/ }));
+ await waitFor(() => expect(desactivation).toHaveBeenCalledWith(1));
+ });
+
+ it('affiche le libellé du service plutôt que son identifiant brut', async () => {
+ render();
+ await screen.findByText('Administration');
+
+ const ligne = screen.getByText('Erreurs tiers payeurs').closest('tr');
+ /* id_service = 1 doit s'afficher « Contrat ». */
+ expect(within(ligne).getByText('Contrat')).toBeInTheDocument();
+ });
+
+ it('convertit en entier les identifiants issus des listes déroulantes', async () => {
+ /* Un