Files
MaksTinyWorkshop f1b783407a docs(knowledge): capitalisation backend — intégration du triage local (mai-juin 2026)
Triage et intégration des propositions backend du buffer 95_a_capitaliser.md
(lot local RL799_V2 + app-alexandrie, mai-juin 2026), distinct de la capitalisation
remote antérieure (triage 2026-05-02).

~73 entrées intégrées sur knowledge/backend/, dont :
- patterns/auth.md : série "membrane d'auth fédérée BFF/OIDC" (9 patterns) + jose algo whitelist
- patterns/prisma.md : recette fusionnée "Migration String/Int → enum" (backfill + Cas A/B/C),
  row réactivable, endpoint replace atomique, updateMany conditionnel, etc.
- risques/general.md : 19 risques (epoch s vs ms, keepAliveTimeout=0, upsert+filtre liste,
  fail-safe catch-all, retrait asymétrique front/back, anti-énumération rate-limit, etc.)
- patterns/general, async, nestjs, contracts, tests + risques/auth, contracts, prisma, redis, stripe, tests
- compléments d'entrées existantes (authorize-after-fetch, P3014, cursor opaque, DI swc, Stripe v20...)
- README patterns/risques mis à jour

Doublons internes corrigés en relecture (suppression-champ .map() → general seul ;
e2e DB-based → tests.md seul). Doublons hors backend / entrées projet / rejets non intégrés.
Source 95_a_capitaliser.md non purgée à ce stade (purge en fin de capitalisation complète).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 11:25:02 +02:00

13 KiB
Raw Permalink Blame History


title: Backend — Risques & vigilance : Tests domain: backend bucket: risques tags: [tests, vitest, isolation, env-vars, flakiness] applies_to: [analysis, implementation, review, debug] severity: high validated_on: 2026-05-02 source_projects: [RL799_V2]

Backend — Risques & vigilance : Tests

Extrait de la base de connaissance Lead_tech. Voir knowledge/backend/risques/README.md pour l'index complet.


vi.stubEnv sans restauration — fuite env vars inter-fichiers

Risques

  • Un test qui stub une env var dans beforeAll sans vi.unstubAllEnvs() en afterAll affecte silencieusement tous les fichiers exécutés après lui dans le même process
  • En séquentiel (maxWorkers: 1), l'ordre est déterministe et la fuite est invisible — la suite passe au vert
  • En passant à maxWorkers > 1, les env vars stubbées sont partagées entre workers → tests imprévisibles

Symptômes

  • Tests qui passent en isolation mais échouent dans la suite complète, ou inversement
  • Comportement d'un endpoint qui dépend d'une env définie dans un fichier qui n'a rien à voir
  • Migration de maxWorkers: 1 vers maxWorkers: 4 qui rouge la suite d'un coup

Bonnes pratiques / mitigations

beforeAll(() => {
  vi.stubEnv('RESEND_API_KEY', 'test-key');
});
afterAll(() => {
  vi.unstubAllEnvs();
});

// Variante encore plus robuste (isolation parfaite par test) :
beforeEach(() => { vi.stubEnv('X', 'y'); });
afterEach(() => { vi.unstubAllEnvs(); });
  • Détection : rg "vi\.stubEnv\(" __tests__ | wc -l doit être ≤ rg "vi\.unstubAllEnvs\(\)" __tests__ | wc -l regroupé par fichier

  • Avant toute migration vers maxWorkers > 1 : sweep complet stubEnv / unstubAllEnvs

  • Contexte technique : Vitest — RL799_V2 24-04-2026


maxWorkers: 1 masque les problèmes d'isolation

Risques

  • L'exécution séquentielle cache systématiquement tous les bugs d'isolation : vi.stubEnv non restaurée, mutations de seed non restaurées, deleteMany direct, compteurs globaux non resets
  • La CI actuelle ne peut pas détecter ces problèmes → faux sentiment de sécurité
  • Le jour où on veut paralléliser pour gagner du temps, on découvre une dizaine de bugs d'isolation simultanément

Symptômes

  • CI verte en maxWorkers: 1, rouge dès maxWorkers > 1
  • Tests "verts depuis 6 mois" qui rougent soudainement après un changement de config
  • Pas de détection possible avant le passage en parallèle

Bonnes pratiques / mitigations

  • Tester la parallélisation tôt, même si la suite est petite — passer maxWorkers: 2 force l'équipe à écrire des tests isolés

  • Si on hérite d'un projet en maxWorkers: 1, ne pas migrer d'un coup. Audit ciblé d'abord :

    • grep "vi\.stubEnv\(" / "vi\.unstubAllEnvs\(" / "deleteMany\(" / "TEST_USER\." pour repérer les patterns suspects
  • Ajouter un audit "hidden_by_serial_execution" en review de tests : lister les patterns qui marcheraient aujourd'hui mais casseraient en parallèle

  • Heuristique : projet > 200 tests → impératif de passer en parallèle (sinon CI > 15 min = friction dev majeure)

  • Contexte technique : Vitest — RL799_V2 24-04-2026


Flakiness inter-fichiers vitest avec DB partagée

Risques

  • Un fichier de tests laisse des artefacts résiduels en DB que le fichier suivant ne s'attend pas à voir : audits orphelins, notifications, entries seed mutées, rate-limiters non resets
  • Le pattern se "résout" au 2e run par chance (le beforeEach finit par nettoyer par effet de bord), donnant une fausse confiance
  • En CI, un retry automatique masque la vraie cause

Symptômes

  • 2-4 tests rouges au 1er run, vert au 2e run sans aucune modification
  • vitest run <fichier-isolé> vert, suite complète rouge
  • Compteurs count({ type: 'X' }) qui tombent sur des résidus d'anciens tests

Bonnes pratiques / mitigations

Diagnostic :

# 1. Run isolé sur le fichier suspect
pnpm -C apps/api test mon-fichier
# 2. 2 runs consécutifs de la suite complète
pnpm -C apps/api test && pnpm -C apps/api test
# Si 1er rouge / 2e vert → flakiness inter-fichiers

Stratégies par horizon :

  • Court terme : accepter comme dette connue si le 2e run est stable, documenter dans le commit message
  • Moyen terme : identifier le fichier qui pollue, ajouter le cleanup manquant dans son afterEach
  • Long terme : DB-per-worker ou transactions + rollback (chantier d'infra dédié, voir knowledge/backend/patterns/tests.md pattern template database)

À ne PAS faire :

  • Ajouter des setTimeout pour "attendre que ça se stabilise"
  • Wrapper les assertions dans des try/catch silencieux
  • Marquer les tests .skip

Heuristique gravité :

  • 1 fail intermittent toutes les 5 runs : acceptable temporairement

  • 1+ fail systématique au 1er run, vert au 2e : à diagnostiquer mais pas urgent

  • Fails aléatoires différents à chaque run : urgent (state corruption)

  • Contexte technique : Vitest / Prisma — RL799_V2 25-04-2026


Test de non-régression d'accès (RBAC) qui ré-encode la table de rôles au lieu d'invoquer les guards réels

Risques

  • Un test "filet anti-régression" donne un faux sentiment de sécurité s'il teste une projection inerte de la règle au lieu de la règle appliquée
  • Cas vécu RL799 : un test "snapshot d'accès" conçu comme "pour chaque rôle, quels sets de rôles le couvrent" ne dépendait QUE du fichier de définition des sets — il restait vert même si un guard ou un call-site changeait (le vrai risque d'une refonte RBAC). La revue adversariale l'a qualifié de "faux filet"

Symptômes

  • Le test ne peut PAS échouer si la régression qu'il prétend protéger se produit
  • Question de détection : "ce test peut-il échouer si la régression que je crains arrive ?" — si non, c'est un faux filet

Bonnes pratiques / mitigations

  • Un test censé protéger contre un changement X doit exercer le chemin où X s'applique RÉELLEMENT, pas une reformulation parallèle de la règle

  • Correctif type : matrice (rôle représentatif × handler représentatif), token forgé par rôle, appel du handler/guard réel, assertion 200/403

  • Ne jamais ré-encoder la table de rôles dans le test : invoquer les guards/handlers de prod

  • Contexte technique : RBAC / API HTTP — RL799_V2


Préfixe de fixture de test partagé entre fichiers — cleanup qui efface la fixture d'un voisin

Risques

  • Deux fichiers de tests écrivant dans la même table avec le MÊME préfixe (RI-TEST-), dont l'un fait un filet deleteMany({ ref: { startsWith: 'RI-TEST-' } }) en afterEach
  • Vitest pouvant entrelacer deux fichiers sur un même worker, ce filet peut effacer la fixture VIVANTE de l'autre fichier → flakiness intermittente non déterministe
  • Bug LATENT de profil "iceberg" : les deux fichiers passent en isolation et même ensemble la plupart du temps, fail sporadique en CI (ex. 404 sur le merge) difficile à diagnostiquer

Symptômes

  • 404 / "introuvable" sporadique sur une fixture que le test croyait avoir créée
  • Deux fichiers grep-ables sur le même littéral de préfixe

Bonnes pratiques / mitigations

  • Préfixe de fixture = UNIQUE par fichier, jamais par domaine/table : RI-CRUD- vs RI-MERGE-, pas RI-TEST- partagé

  • Règle générale : un cleanup de test ne doit JAMAIS supprimer au-delà de ce que CE fichier a créé — ni rm une racine disque partagée, ni deleteMany un pattern qu'un autre fichier peut matcher, ni réutiliser un id du seed

  • Détection : grep -rln "<PREFIX>" <test-dir> doit retourner UN seul fichier par préfixe

  • Contexte technique : Vitest / Prisma — RL799_V2 22-06-2026


Test qui écrit/supprime un fichier collisionnant avec un artefact versionné

Risques

  • Un test pose une fixture via writeFile puis la supprime dans son finally, mais en ciblant le nom d'un fichier SEED VERSIONNÉ dans git (ex. seed-planches-architecture-apprenti.pdf)
  • À chaque run de la suite, le fichier seed disparaît du working tree (git statusD), polluant tous les diffs et risquant d'être commité par erreur

Symptômes

  • Un fichier suivi par git réapparaît en D (supprimé) dans git status après l'exécution d'une suite de tests, sans qu'aucun code applicatif ne le touche
  • Déroutant : le coupable est un test, pas le code en cours de dev

Bonnes pratiques / mitigations

  • Les fixtures posées sur disque portent un nom JETABLE non versionné (préfixe _fixture-, tmp-, ou sous-dossier __fixtures__/ git-ignoré), JAMAIS le nom d'un fichier seed/asset commité

  • Vérifier : git ls-files <dir> liste les fichiers versionnés — aucun nom de fixture de test ne doit y figurer

  • Détection rapide : si git status montre une suppression D inattendue d'un seed/asset après un run de tests, grep le nom exact dans __tests__/ → le test qui le référence avec un rm/removeFixture en finally est le coupable

  • Contexte technique : Vitest / filesystem — RL799_V2 23-06-2026


Test consommant un singleton module-level dépendant de l'env — passe "par accident" selon l'ordre des fichiers

Risques

  • Un test qui consomme un singleton module-level configuré par l'environnement (transport SMTP, client API, pool DB mémoïsé) sans stubber son propre env ET reset le singleton passe "par accident" selon l'ordre d'exécution des fichiers
  • Le singleton (let transport = null mémoïsé au 1er getTransport()) est partagé entre fichiers d'un même worker : un fichier A pose SMTP_HOST + construit le transport, un fichier B qui ne configure RIEN réutilise le transport de A → B "marche" tant que A tourne avant lui
  • Corollaire : un reset de singleton (__resetXxxForTests) placé dans le SETUP GLOBAL via import statique court-circuite les vi.mock des autres fichiers — l'import charge le module (et sa chaîne, ex. smtpTransport → nodemailer) AVANT que les vi.mock propres à chaque fichier soient hoistés → module figé sur la vraie dépendance

Symptômes

  • Test vert en local, rouge en CI (ou l'inverse) sans changement de code ; sentCount === 0 au lieu de N, ou appel réseau réel malgré un mock
  • Ajouter un reset "utile" au setup global casse des dizaines de tests sans rapport ('failed' !== 'sent')

Bonnes pratiques / mitigations

  • Tout fichier qui exerce le singleton doit être AUTO-SUFFISANT : stubber l'env requis dans beforeAll (vi.stubEnv('SMTP_HOST', …) + vi.unstubAllEnvs() en afterAll) ET reset le singleton dans beforeEach (__resetXxxForTests()), au POINT D'USAGE (où son propre vi.mock est actif), jamais dans le setup global

  • Ne jamais s'appuyer sur l'état laissé par un fichier voisin

  • Un reset de singleton va dans le beforeEach du/des fichier(s) qui en ont besoin, PAS dans le setup global ; si vraiment transverse, l'importer en différé (await import('@/lib/...')) au point d'usage

  • Le dryRun/mode test d'un service ne dérive PAS forcément de NODE_ENV === 'test' — vérifier la VRAIE condition (souvent une var dédiée, ex. MAIL_DRY_RUN === 'true')

  • Après tout changement touchant le fichier de setup, rejouer la suite COMPLÈTE (effet iceberg)

  • Contexte technique : Vitest — RL799_V2 23-06-2026


Test de rate-limit qui hardcode le rang exact de la requête bloquée

Risques

  • Un test qui hardcode "la Ne requête déclenche le 429" casse silencieusement quand un flag d'environnement (E2E) relève la limite
  • La limite effective dépend d'un flag (process.env.E2E === '1' ? 1000 : 20), mais le test fige le nombre d'itérations

Symptômes

  • for (i=0;i<21;i++) ...; expect(last.status).toBe(429) passe en local (limite=20) mais casse sous E2E=1 (limite relevée à 1000) — jamais de 429 atteint, rouge en CI E2E
  • À l'inverse, un test calibré sur la limite E2E serait trop lent/inutile en local

Bonnes pratiques / mitigations

  • Boucler jusqu'au PREMIER 429 avec un plafond de sécurité couvrant le régime le plus permissif (SAFETY_CAP > limite E2E)

  • Asserter (a) qu'un 429 a bien été atteint avant le plafond, (b) qu'au moins une requête est passée avant le blocage

  • Le test reste correct quelle que soit la limite effective et survit à un ajustement de capacité

  • Alternative : exposer la limite (getter) et dériver le nombre d'itérations — mais boucler-jusqu'au-429 évite de changer l'API de prod pour un test

  • Contexte technique : rate-limit / API HTTP — RL799_V2 23-06-2026