Les chemins qui échouent
Vérifié contre le produit le .
Un refus réussi est un succès de test. Cette page vérifie que le produit bloque ce qu’il doit bloquer — et c’est ce qu’un client demandera de voir avant les fonctionnalités.
| Domaine | transverse — les huit familles de refus du produit |
| Durée | ~25 min pour les huit familles |
| Niveau | complet |
| Étiquette | manuel — trois familles exigent deux sessions simultanées, et la famille « permissions » suppose de comparer deux écrans du même produit vus par deux rôles |
Ce que chaque cas vérifie
Section intitulée « Ce que chaque cas vérifie »Trois choses, et il faut les trois. Un refus qui ne satisfait que les deux premières est un défaut.
- L’action est bloquée — rien ne s’est écrit.
- Le message est compréhensible — il nomme la cause, il ne dit pas « interdit ». Le texte exact est cité dans chaque cas.
- L’état n’a pas changé — c’est la vérification qu’on oublie, et la seule qui prouve qu’un refus n’a pas laissé de dégât derrière lui.
Formulez les résultats positivement. « Le produit refuse et affiche “Quota atteint : 4 h réservées cette semaine sur 4 h autorisées.” » n’est pas un échec : c’est la garantie qui tient.
Préparation
Section intitulée « Préparation »Socle B pour les familles 1 à 5 et 7, socle A pour la famille 3, le montage étant sur la préparation commune. La famille 6 est autonome, la famille 8 exige deux navigateurs.
1. Quotas
Section intitulée « 1. Quotas »Les quotas Une limite chiffrée qu'un profil ne peut pas dépasser — heures par semaine, réservations simultanées, durée maximale d'une demande. se posent sur le profil « Demandeur », depuis Administration → Règles de réservation. Les tests se jouent avec bob.
| # | Ce qu’on tente | Le produit refuse et affiche | L’état après |
|---|---|---|---|
| 1.1 | Réserver 1 h de plus après avoir consommé un quota de 2 h | « Quota atteint : 2 h réservées cette semaine sur 2 h autorisées. » | aucune réservation créée ; les 2 h précédentes sont intactes |
| 1.2 | Réserver au-delà de l’horizon autorisé | « Réservation trop lointaine : J+30 demandé, maximum J+7. » | rien créé |
| 1.3 | Réserver plus long que la durée maximale | « Durée trop longue : 120 min demandées, maximum 60 min. » | rien créé |
| 1.4 | Réserver hors de la plage horaire autorisée | « Réservations autorisées uniquement entre 08:00 et 12:00. » | rien créé |
À vérifier en plus : la même semaine, la réservation initiale est toujours « Confirmée ». Un refus de quota ne consomme rien et n’annule rien.
Étiquette : automatisable. Couvert par apps/api/src/booking-rules/booking-rules.e2e.spec.ts.
Traçabilité : STORY-012, V61.
2. Conflits d’occupation
Section intitulée « 2. Conflits d’occupation »| # | Ce qu’on tente | Le produit refuse et affiche | L’état après |
|---|---|---|---|
| 2.1 | Réserver une salle déjà réservée sur le même créneau | « Cette salle vient d’être prise. Alternatives sur le même créneau : » suivi de salles cliquables | la réservation existante est intacte ; aucune seconde réservation |
| 2.2 | Poser une séance sur une salle déjà occupée | « Salle occupée : B-201 · 09:45 – 11:15 » | la grille n’a pas bougé — il n’y a pas de mise à jour optimiste |
| 2.3 | Poser une séance là où l’enseignant a déjà cours | « Enseignant déjà en cours : Hugo Martin · 09:45 – 11:15 » | idem |
| 2.4 | Poser une séance là où le groupe a déjà cours | « Groupe déjà en cours : L1 A · 09:45 – 11:15 » | idem |
| 2.5 | Réserver une salle couverte par une indisponibilité | la salle n’apparaît pas dans les résultats ; avec la case gestionnaire, elle est grisée avec « Indisponible — Réfection du sol » et son bouton « Réserver » est inactif | rien créé |
| 2.6 | Réserver pendant une période gelée | « Période gelée “Vacances de Noël” : aucune réservation possible sur cette période. » | rien créé |
Le point de fond : séances, réservations et indisponibilités partagent une seule vérité d’occupation. Un brouillon occupe la salle exactement comme une réservation confirmée — le cas 2.2 le prouve même sur une séance non publiée.
Étiquette : manuel pour 2.1 (deux navigateurs), automatisable pour les autres. Couvert par
apps/api/src/occupancy/occupancy.e2e.spec.ts et course-placement.e2e.spec.ts.
Traçabilité : STORY-013, STORY-016, STORY-018.
3. Contraintes dures
Section intitulée « 3. Contraintes dures »Socle A. Ces refus se lisent sur la grille avant même d’être tentés : une cellule rendue impossible par une contrainte dure Une règle qui ne se négocie pas : tant qu'elle est violée, le placement est impossible — une salle déjà prise, un enseignant absent. est hachurée et ne porte aucun chiffre.
| # | Ce qu’on tente | Le produit refuse et affiche | L’état après |
|---|---|---|---|
| 3.1 | Placer un cours le mercredi, quand son enseignant est indisponible | la cellule est hachurée ; le survol donne la raison, et le placement n’est pas proposé | rien créé |
| 3.2 | Forcer une salle sans l’équipement requis, depuis le détail du créneau | la salle est non sélectionnable, avec son motif : « A-101 — Salle TP — exclue : Équipement requis manquant » | rien créé |
| 3.3 | Placer un cours dont la fenêtre déborde la plage d’ouverture | « Dépasse les heures d’ouverture (08:00 – 20:00) » | rien créé |
| 3.4 | Ouvrir le détail d’une cellule entièrement exclue | toutes les raisons sont listées, chacune avec « Bloque 14 salle(s) candidate(s) », plus « Créneau exclu : les contraintes souples ne sont pas évaluées. » | — |
La différence à faire constater : une contrainte souple ne refuse rien. Elle fait baisser le score, et le placement reste possible. Un testeur qui cherche un refus sur une contrainte souple cherchera longtemps.
Étiquette : manuel — la lecture d’une cellule hachurée est visuelle. Couvert partiellement par
apps/api/src/scoring/scoring.e2e.spec.ts. Traçabilité : STORY-021, STORY-023, STORY-026.
4. Permissions
Section intitulée « 4. Permissions »Le protocole de cette famille est différent des autres, et c’est le point à comprendre.
Le produit filtre la navigation ; il ne la désactive pas. Il n’y a ni entrée grisée, ni cadenas : ce qui n’est pas permis n’existe pas à l’écran.
On ne cherche donc pas un élément désactivé : on constate une absence.
Ce qui suppose de savoir à quoi ressemble l’écran quand on y a droit. Comparez deux sessions, ou relevez la liste des entrées avec un administrateur avant de vous connecter avec le rôle restreint.
| # | Ce qu’on tente | Ce qu’on doit constater | L’état après |
|---|---|---|---|
| 4.1 | Se connecter avec un compte sans planning.edit, chercher « Planification » |
l’entrée est absente du menu — pas grisée | |
| 4.2 | Ouvrir /planning par l’adresse directe |
redirection vers l’accueil, sans message de refus | rien |
| 4.3 | Compte sans planning.solve sur la grille |
le bouton « Placement optimisé » est absent de la barre d’actions | rien |
| 4.4 | Compte sans planning.publish avec des changements en attente |
le bandeau « … changement(s) non publié(s) sur cette semaine. » est là, sans bouton à sa droite | rien |
| 4.5 | Compte sans audit.read ouvrant /admin/audit |
redirection vers l’accueil | rien |
| 4.6 | Comparer « Vos accès » sur l’accueil entre deux rôles | les cartes diffèrent — c’est la liste exhaustive des droits de chacun | — |
Étiquette : manuel — la comparaison entre deux rôles ne s’automatise pas honnêtement côté
interface. Couvert côté serveur par apps/api/src/rbac/rbac.e2e.spec.ts.
Traçabilité : STORY-004, V60.
5. Portée
Section intitulée « 5. Portée »C’est le refus le plus surprenant du produit, et il mérite d’être joué lentement.
Un rôle limité au campus A ne donne aucun droit sur une ressource du campus B — cela, tout le monde l’attend. Ce qu’on n’attend pas :
Un rôle limité au campus A ne donne aucun droit sur une ressource dont le campus n’est pas renseigné.
Dans le doute, la réponse est non. Une donnée manquante n’est pas un doute favorable.
| # | Ce qu’on tente | Ce qu’on doit constater | L’état après |
|---|---|---|---|
| 5.1 | dave, « Valideur A » limité au campus A, ouvre Validations avec une demande sur le campus A | il la voit | — |
| 5.2 | Une demande est déposée sur une salle du campus B | dave ne la voit pas ; alice, sans périmètre, la voit | — |
| 5.3 | Comparer le compteur de dave et celui d’alice | ils diffèrent — chacun compte ce qui le concerne | — |
| 5.4 | Une ressource dont le campus n’est pas renseigné | dave ne la voit pas non plus — c’est le cas fail-closed | — |
| 5.5 | Ajouter à dave un second rôle sans périmètre portant la même permission | il voit désormais tout : le cumul est permissif, le périmètre du premier rôle est contourné | — |
Étiquette : manuel. Couvert côté serveur par apps/api/src/rbac/rbac.e2e.spec.ts et
apps/api/src/approvals/approvals.e2e.spec.ts. Traçabilité : STORY-004.
6. Isolation entre établissements
Section intitulée « 6. Isolation entre établissements »Ce cas a son scénario dédié, autonome et présentable devant un client : Isolation multi-établissements.
Il n’est pas recopié ici. Retenez seulement les deux formes de refus qu’il éprouve :
- la ressource est absente — une recherche ne renvoie rien de l’autre établissement ;
- la ressource est introuvable — ouvrir l’adresse directe d’une ressource de l’autre établissement donne « Salle introuvable. », et non un refus d’accès. Le produit ne confirme pas l’existence de ce qu’il protège.
Étiquette : manuel. Traçabilité : STORY-002, STORY-013.
7. Validation
Section intitulée « 7. Validation »Les refus de formulaire, qui bloquent avant tout envoi.
| # | Ce qu’on tente | Le produit refuse et affiche | L’état après |
|---|---|---|---|
| 7.1 | Une heure de fin antérieure à l’heure de début, en réservation | « L’heure de fin doit être après l’heure de début. », et aucune recherche n’est lancée | rien |
| 7.2 | Une année académique dont la fin précède le début | « La date de fin doit être postérieure à la date de début. » | rien créé |
| 7.3 | Une période hors des dates de l’année | « La période doit être comprise dans les dates de l’année académique. » | rien créé |
| 7.4 | Une ouverture postérieure à la fermeture | « L’heure d’ouverture doit précéder l’heure de fermeture. » | rien enregistré |
| 7.5 | Une durée de séance de 5 minutes | « La durée doit être comprise entre 15 et 480 minutes. » | rien créé |
| 7.6 | Un JSON invalide dans les caractéristiques d’une salle | « JSON invalide : corrigez les caractéristiques avant d’enregistrer. » | la fiche n’est pas enregistrée |
| 7.7 | Un rôle personnalisé sans aucune permission | « Sélectionnez au moins une permission. » | rien créé |
| 7.8 | Deux mots de passe différents | « Les deux mots de passe ne correspondent pas. » | mot de passe inchangé |
| 7.9 | Un seuil de contrainte hors bornes | « Valeur hors bornes : corrigez avant d’enregistrer. » | profil inchangé |
| 7.10 | Une capacité non numérique dans un import | « « Capacité » doit être un nombre entier valide. » | ligne rejetée, rien d’écrit |
Les jetons expirés, qui sont une validation d’un autre ordre :
| # | Ce qu’on tente | Le produit refuse et affiche |
|---|---|---|
| 7.11 | Rouvrir un lien de réinitialisation déjà utilisé | « Ce lien de réinitialisation est invalide ou a expiré. » |
| 7.12 | Rouvrir une invitation déjà acceptée, annulée, ou vieille de plus de sept jours | « Cette invitation est invalide, expirée ou a déjà été utilisée. » — le même message dans les trois cas, et c’est délibéré |
Étiquette : automatisable. Traçabilité : STORY-008, STORY-011, STORY-021.
8. Concurrence
Section intitulée « 8. Concurrence »Deux navigateurs. Le comportement de chaque cas a été vérifié, il n’y a rien d’indéterminé ici.
| # | Ce qu’on tente | Ce qui se passe | L’état après |
|---|---|---|---|
| 8.1 | alice et bob réservent la même salle sur le même créneau, presque en même temps | un seul gagne ; le perdant lit « Cette salle vient d’être prise. Alternatives sur le même créneau : » avec des salles cliquables | une seule réservation existe |
| 8.2 | Le perdant clique une alternative | le formulaire se re-pré-remplit sur cette salle | — |
| 8.3 | Un administrateur ouvre le récapitulatif de publication, un planificateur pose une séance de plus sur le même périmètre, l’administrateur confirme | « Le planning a changé depuis l’aperçu — récapitulatif actualisé, merci de revalider. » | rien n’a été publié |
| 8.4 | Un calcul d’optimisation tourne ; quelqu’un déplace une séance sur les mêmes salles ; on tente d’appliquer la suggestion | « Le planning a bougé pendant le calcul : la suggestion est périmée. Relancez le calcul. » | aucune séance créée |
| 8.5 | Deux personnes lancent un calcul sur le même établissement | la seconde lit « Un calcul est déjà en cours pour l’établissement. » | un seul calcul tourne |
| 8.6 | Deux approbateurs traitent la même demande | la seconde action donne « L’action a échoué. Réessayez. » ; la demande a déjà quitté la file | une seule décision enregistrée |
Le refus différé, qui n’a pas d’acteur humain :
| # | Ce qu’on tente | Ce qui se passe | Le délai |
|---|---|---|---|
| 8.7 | Laisser une demande de réservation non traitée jusqu’au début de son créneau | elle passe à « Expirée », sans motif, et la salle est libérée | le contrôle passe toutes les 60 secondes, valeur écrite en dur : comptez jusqu’à une minute après l’heure du créneau |
Étiquette : manuel. Traçabilité : STORY-014, STORY-016, STORY-017, STORY-030,
STORY-032.
Checklist
Section intitulée « Checklist »- Quota dépassé → « Quota atteint : 2 h réservées cette semaine sur 2 h autorisées. », et la réservation initiale est intacte
- Horizon dépassé → « Réservation trop lointaine : J+30 demandé, maximum J+7. »
- Durée dépassée → « Durée trop longue : 120 min demandées, maximum 60 min. »
- Salle déjà occupée → « Salle occupée : B-201 · 09:45 – 11:15 » et la grille n’a pas bougé
- Enseignant occupé → « Enseignant déjà en cours : … » ; groupe occupé → « Groupe déjà en cours : … »
- Salle indisponible → absente des résultats, ou grisée avec « Réserver » inactif
- Période gelée → « Période gelée “…” : aucune réservation possible sur cette période. »
- Contrainte dure → cellule hachurée, sans chiffre, raison nommée au survol
- Salle sans équipement requis → non sélectionnable, avec son motif
- Fenêtre hors ouverture → « Dépasse les heures d’ouverture (08:00 – 20:00) »
- Sans permission → l’entrée du menu est absente, pas grisée
- Adresse directe → redirection vers l’accueil (sauf les trois exceptions notées)
- Sans
planning.publish→ le bandeau de changements est là, sans bouton - Rôle limité au campus A → ne voit pas le campus B, ni une ressource sans campus
- Second rôle sans périmètre → il voit tout : le cumul est permissif
- Adresse d’une ressource d’un autre établissement → « Salle introuvable. »
- Dates incohérentes, durée hors bornes, JSON invalide, rôle sans permission → chaque message cité s’affiche, rien n’est enregistré
- Lien de réinitialisation réutilisé → « Ce lien de réinitialisation est invalide ou a expiré. »
- Invitation morte → « Cette invitation est invalide, expirée ou a déjà été utilisée. »
- Course à deux sur une salle → « Cette salle vient d’être prise. … », une seule réservation
- Publication concurrente → « Le planning a changé depuis l’aperçu … », rien n’est publié
- Suggestion périmée → « Le planning a bougé pendant le calcul : la suggestion est périmée. … »
- Second calcul → « Un calcul est déjà en cours pour l’établissement. »
- Demande non traitée → « Expirée » dans la minute qui suit l’heure du créneau, salle libérée
Ce que cette page n’est pas
Section intitulée « Ce que cette page n’est pas »- Ce n’est pas la liste des messages d’erreur du produit. Les états particuliers de chaque écran sont décrits dans la référence, écran par écran ; ici on les vérifie.
- Ce n’est pas la page des pièges. Un refus attendu est une garantie ; un piège est une perte de temps. Les seconds sont sur Les pièges connus.
Pages liées
Section intitulée « Pages liées »- Les pièges connus — ce qui fait perdre du temps sans être un refus légitime.
- Isolation multi-établissements — la famille 6, en entier.
- Le gabarit d’un scénario — le format, les comptes, les ports.
- Placement d'une séance et Règles de réservation — les deux écrans qui produisent le plus de refus.