# Cahier de recette utilisateur — Lot L0 « Déblocage »

**Périmètre livré** : PR [#11](https://github.com/AAZTEKDEV/EFEKTIVACADEMIE-BACK/pull/11)
(sécurité P0, mergée) + PR [#95](https://github.com/AAZTEKDEV/EFEKTIVACADEMIE-BACK/pull/95)
(environnement reproductible + correctifs de compteurs).
**Environnement** : staging (`development` déployé). **Testeur** : Enguerran (ou délégué).
**Convention** : cocher ☐ → ✅ si conforme, ❌ sinon, et noter le constat en dessous du scénario.

## Comptes de test

| Rôle | Compte | Remarque |
|---|---|---|
| Apprenant A | `learner@mail.com` / `password` | inscrit à au moins 1 cours avec quiz |
| Apprenant B | second compte apprenant | inscrit à un cours différent, avec certificat obtenu |
| Manager M1 / M2 | deux managers avec des périmètres distincts | pour la recette des compteurs |
| Admin | `admin@mail.com` / `password` | |

> Sur staging, remplacer par les comptes de démonstration équivalents. Ne JAMAIS dérouler
> ce cahier en production.

---

## S1 · Seuil de réussite des quiz (EA-065 partie 1, issue #10)

**Avant correctif : un quiz était validé même à 0 %.**

- ☐ **S1.1** Apprenant A ouvre un quiz dont le seuil est 80 %. Répondre volontairement faux
  (score < 80 %). → La validation est **refusée**, le message indique le score obtenu,
  le seuil à atteindre et la possibilité de refaire le quiz. Le quiz n'apparaît PAS validé.
- ☐ **S1.2** Refaire le quiz avec un score ≥ seuil. → Le quiz est validé normalement.
- ☐ **S1.3** Échouer le dernier quiz d'un cours dont tout le reste est terminé. → **Aucun
  certificat n'est généré** tant que le quiz n'est pas réussi.
- ☐ **S1.4** (Admin) Modifier le seuil d'un quiz (ex. 50 %) puis re-tester S1.1 avec un score
  entre 50 et 80 %. → La nouvelle valeur fait foi.

## S2 · Certificats — étanchéité entre apprenants (EA-002, issue #9)

**Avant correctif : n'importe quel apprenant pouvait télécharger les certificats de tous.**

- ☐ **S2.1** Apprenant A télécharge son propre certificat depuis son espace. → OK.
- ☐ **S2.2** Apprenant A rejoue l'URL de téléchargement en remplaçant l'identifiant par celui
  d'un certificat de l'apprenant B (énumération d'ids). → **404 systématique**, jamais le
  fichier d'un autre.
- ☐ **S2.3** Identifiant inexistant (ex. 999999). → **404 propre** (pas d'erreur 500).
- ☐ **S2.4** (Manager) Téléchargement groupé des certificats sans sélectionner de cours. →
  Le ZIP contient uniquement les certificats du périmètre du manager (issus de la base),
  pas de fichiers orphelins du disque.

## S3 · Escalade de privilèges (PR #11, 3ᵉ volet)

- ☐ **S3.1** Apprenant A tente de modifier son profil en injectant un champ de rôle/statut
  protégé dans la requête (rejouer la requête de mise à jour de profil avec `role`/champs
  admin ajoutés). → Les champs protégés sont **ignorés ou refusés** ; le rôle ne change pas.
- ☐ **S3.2** Vérifier après coup (Admin > utilisateurs) que le rôle de l'apprenant est inchangé.

## S4 · Compteurs et reporting managérial (EA-009, issue #18)

**Jeu de données requis : 2 managers M1/M2 avec des cours et apprenants distincts.**

- ☐ **S4.1** M1 consulte les statistiques d'un de SES cours. → Chiffres cohérents avec son
  périmètre uniquement (M2 ne voit pas les mêmes).
- ☐ **S4.2** Comparer le « taux de complétion » affiché avec la réalité (un apprenant ayant
  terminé 2 fois le même cours ne compte qu'une fois — plus de double comptage).
- ☐ **S4.3** Graphique mensuel (« month-wise ») de M1 ≠ celui de M2 : chacun est filtré sur
  son périmètre.
- ☐ **S4.4** Se connecter avec un compte `sub_manager` puis `manager_general`. → Les tuiles
  du tableau de bord latéral correspondent au bon rôle (avant correctif, elles recevaient le
  payload « director »).

## S5 · Pagination paramétrable (EA-014, issue #23)

- ☐ **S5.1** (Admin) Liste des utilisateurs avec `per_page=50`. → 50 lignes par page.
- ☐ **S5.2** `per_page=5000` ou valeur absurde. → Borné (la liste retombe sur une borne max
  raisonnable, pas de time-out).
- ☐ **S5.3** Sans paramètre. → Comportement par défaut inchangé (20).

## S6 · Quiz autonomes — notification (EA-083, issue #24)

- ☐ **S6.1** Publier un quiz autonome rattaché à un cours. → Seuls les **inscrits du cours**
  reçoivent la notification (vérifier qu'un apprenant non inscrit ne reçoit rien).
- ☐ **S6.2** La publication répond immédiatement (envoi en file d'attente, pas de time-out).
- ☐ **S6.3** Re-publier / re-sauvegarder le même quiz. → **Aucun second envoi** (déclenchement
  unique).

## S7 · Complétion — garde-fous (EA-015, issue #25)

- ☐ **S7.1** Terminer un cours normalement. → Une seule ligne de complétion, un seul
  certificat (pas de doublon même en rejouant l'action).
- ☐ **S7.2** (Admin) Créer un cours vide (0 leçon, 0 quiz) et y inscrire l'apprenant A. →
  **Aucun certificat** n'est généré automatiquement.

---

## Hors recette utilisateur (vérifié côté dev)

- Environnement local reproductible : `migrate:fresh --seed` passe (documenté dans
  `09_ENVIRONNEMENT_LOCAL.md`).
- Issue #97 (5 tests `cta_url` en échec) : dette de tests, pas d'effet utilisateur.

| Synthèse | ✅ | ❌ | Commentaire |
|---|---|---|---|
| S1 Seuil quiz | | | |
| S2 IDOR certificats | | | |
| S3 Escalade privilèges | | | |
| S4 Compteurs | | | |
| S5 Pagination | | | |
| S6 Quiz autonomes | | | |
| S7 Garde-fous complétion | | | |

---

## Résultats — recette LOCALE du 07/08/2026 (intégration L0+L1+L2, base de démo)

Déroulée par Claude sur l'environnement local (API + base), en préfiguration de la
recette staging. **À rejouer sur staging pour valider officiellement.**

| Synthèse | Résultat | Constat |
|---|---|---|
| S1 Seuil quiz | ✅ 4/4 | Refus 422 sous le seuil avec score + seuil + invitation à refaire ; validation ≥ seuil ; le seuil modifié (manager) fait foi. ⚠ **Message en anglais** (« Score of 0%… ») → à franciser. |
| S2 IDOR certificats | ✅ 4/4 | 200 sur son certificat, **404** sur celui d'autrui et sur id inconnu (plus de 500) ; ZIP manager construit depuis la base (contenu exact). |
| S3 Escalade privilèges | ✅ | Manager → création d'un compte admin refusée (403 « not allowed to assign this role »), aucun compte créé. |
| S4 Compteurs | ✅ | Périmètre = **apprenants assignés (`parent_id`)**, restreint pour sub_manager/manager_general (1 apprenant → 1/1/1 vs global 3) ; graphique mensuel et taux cohérents ; payload sidebar conforme au rôle (bug `break` corrigé). ⚠ Un « manager » simple voit le global — modèle du correctif #95, à confirmer comme modèle cible avant le cockpit L4. |
| S5 Pagination | ✅ | Défaut 20 ; `per_page=50` respecté ; 5000 → plafonné à 100 ; valeur invalide → plancher 5. |
| S6 Quiz autonomes | ✅ (partiel) | `standalone_notified_at` en place ; ciblage limité aux inscrits couvert par test automatisé vert. **Envoi réel en file à revalider sur staging** (vraies boîtes mail). |
| S7 Garde-fous complétion | ✅ | Doublon `completed_courses` bloqué par contrainte unique (erreur 1062 constatée) ; cours vide sans certificat + génération sur cours avec contenu : tests verts. |

Notes de fixture locale : 7 questions du seed n'avaient aucune option (complétées pour
rendre les quiz jouables) ; les questions `mcq_multiple` attendent `attempted_option.ids`
(tableau) — à garder en tête pour la recette staging.
