Les fonctionnalités ajoutées récemment n'étaient couvertes par aucun test, et deux utilitaires historiques ne l'avaient jamais été. - statutMonitoring : bornes du seuil testées de part et d'autre, là où une confusion entre < et <= se verrait. - dates : protège la correction du décalage de fuseau (toISOString basculait en UTC, le 1er janvier local devenait le 31 décembre). Les helpers sont sortis de Historique.jsx vers utils/dates.js, un fichier de composant ne pouvant pas exporter autre chose sans casser le rafraîchissement à chaud. - exportToCSV : échappement des virgules, guillemets et sauts de ligne, et conservation du zéro, qui est falsy alors que « 0 erreur » est justement l'information utile. Cet utilitaire sert quatre écrans et n'avait aucun test. - Dashboard : les menus ne proposent que les valeurs ayant des résultats, les catégories se restreignent au service choisi, et une catégorie devenue sans résultat est réinitialisée. Vérifié par mutation : casser la cascade fait bien échouer deux tests. - Admin : création, modification pré-remplie, confirmation avant suppression, remontée du refus 409 de l'API, désactivation et non suppression d'un monitoring, affichage des libellés à la place des identifiants. Corrige un défaut révélé par ces tests : referentielsConfig capturait les fonctions de l'API à l'import, figeant l'implémentation. Elles sont désormais résolues à l'appel, ce qui découple la configuration du module d'API.
175 lines
7.0 KiB
React
175 lines
7.0 KiB
React
/* É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(<Admin />);
|
||
|
||
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(<Admin />);
|
||
|
||
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(<Admin />);
|
||
|
||
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(<Admin />);
|
||
|
||
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(<Admin />);
|
||
|
||
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(<Admin />);
|
||
|
||
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(<Admin />);
|
||
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 <select> renvoie une chaîne ; l'API attend un entier. */
|
||
const creation = vi.spyOn(api, 'createMonitoring').mockResolvedValue({ id_monito: 9 });
|
||
const utilisateur = userEvent.setup();
|
||
render(<Admin />);
|
||
|
||
await screen.findByText('Administration');
|
||
await utilisateur.click(screen.getByRole('button', { name: /Ajouter/ }));
|
||
|
||
await utilisateur.type(screen.getByLabelText('Nom'), 'Nouveau monitoring');
|
||
await utilisateur.selectOptions(screen.getByLabelText('Service'), '2');
|
||
await utilisateur.selectOptions(screen.getByLabelText('Catégorie'), '1');
|
||
await utilisateur.type(screen.getByLabelText('Table source'), 'MONITO_TEST');
|
||
await utilisateur.type(screen.getByLabelText('Base source'), 'Sage');
|
||
await utilisateur.click(screen.getByRole('button', { name: 'Enregistrer' }));
|
||
|
||
await waitFor(() => {
|
||
expect(creation).toHaveBeenCalledWith({
|
||
monito_intitule: 'Nouveau monitoring',
|
||
id_service: 2,
|
||
id_categorie: 1,
|
||
table_source: 'MONITO_TEST',
|
||
bdd_source: 'Sage',
|
||
});
|
||
});
|
||
});
|
||
});
|