Files
Classeo/frontend/tests/unit/lib/features/subjects/api/deleteSubject.test.ts
Mathias STRASSER 86d00ce733 feat: Afficher les statistiques de notes par matière côté administration
L'admin doit pouvoir voir en un coup d'œil quelles matières sont
actives (notes saisies) pour décider lesquelles peuvent être supprimées
sans perte de données. Auparavant, la suppression d'une matière était
silencieuse : elle cascade-deletait évaluations et notes sans avertir.

La liste des matières affiche désormais les compteurs d'enseignants,
classes, évaluations et notes. La suppression déclenche une confirmation
explicite quand la matière contient des notes, avec récapitulatif des
volumes impactés, pour rendre l'action irréversible consciente.

Côté tests, un endpoint de seeding HTTP remplace les appels docker exec
dans les E2E (gain ~30-60s → 5-10s par test), et un trait partagé
factorise le SQL de seeding entre les deux suites fonctionnelles.
2026-04-21 15:37:25 +02:00

83 lines
3.0 KiB
TypeScript

import { describe, it, expect, vi } from 'vitest';
import { deleteSubject } from '$lib/features/subjects/api/deleteSubject';
const API = 'http://test.classeo.local:18000/api';
function makeResponse(status: number, body?: Record<string, unknown>): Response {
const init: ResponseInit = { status };
if (body !== undefined) {
init.headers = { 'Content-Type': 'application/json' };
}
return new Response(body !== undefined ? JSON.stringify(body) : null, init);
}
describe('deleteSubject', () => {
it('envoie DELETE sans confirm quand hasGrades=false', async () => {
const fetchFn = vi.fn().mockResolvedValueOnce(makeResponse(204));
const result = await deleteSubject({ id: 'abc', hasGrades: false }, fetchFn, API);
expect(fetchFn).toHaveBeenCalledWith(`${API}/subjects/abc`, { method: 'DELETE' });
expect(result).toEqual({ status: 'success' });
});
it('envoie DELETE sans confirm quand hasGrades=null (pour laisser le backend décider)', async () => {
const fetchFn = vi.fn().mockResolvedValueOnce(makeResponse(204));
await deleteSubject({ id: 'abc', hasGrades: null }, fetchFn, API);
expect(fetchFn).toHaveBeenCalledWith(`${API}/subjects/abc`, { method: 'DELETE' });
});
it('ajoute ?confirm=true quand hasGrades=true', async () => {
const fetchFn = vi.fn().mockResolvedValueOnce(makeResponse(204));
await deleteSubject({ id: 'abc', hasGrades: true }, fetchFn, API);
expect(fetchFn).toHaveBeenCalledWith(`${API}/subjects/abc?confirm=true`, { method: 'DELETE' });
});
it('retourne un status conflict avec le message backend sur 409', async () => {
const fetchFn = vi.fn().mockResolvedValueOnce(
makeResponse(409, {
'hydra:description':
'Cette matière est liée à 3 évaluation(s) et 12 note(s). Confirmez la suppression pour continuer.'
})
);
const result = await deleteSubject({ id: 'abc', hasGrades: null }, fetchFn, API);
expect(result).toEqual({
status: 'conflict',
message:
'Cette matière est liée à 3 évaluation(s) et 12 note(s). Confirmez la suppression pour continuer.'
});
});
it('retourne un status error sur autre code HTTP', async () => {
const fetchFn = vi
.fn()
.mockResolvedValueOnce(makeResponse(500, { detail: 'Internal server error' }));
const result = await deleteSubject({ id: 'abc', hasGrades: false }, fetchFn, API);
expect(result).toEqual({ status: 'error', message: 'Internal server error' });
});
it('fallback message si pas de payload JSON', async () => {
const fetchFn = vi.fn().mockResolvedValueOnce(makeResponse(403));
const result = await deleteSubject({ id: 'abc', hasGrades: false }, fetchFn, API);
expect(result).toEqual({ status: 'error', message: 'Erreur lors de la suppression (403)' });
});
it('extrait message depuis le champ `message`', async () => {
const fetchFn = vi.fn().mockResolvedValueOnce(makeResponse(409, { message: 'Conflit' }));
const result = await deleteSubject({ id: 'abc', hasGrades: null }, fetchFn, API);
expect(result).toEqual({ status: 'conflict', message: 'Conflit' });
});
});