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.
83 lines
3.0 KiB
TypeScript
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' });
|
|
});
|
|
});
|