Files
_Assistant_Lead_Tech/knowledge/backend/risques/tests.md
T
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

244 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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.
---
<a id="risque-vi-stubenv-sans-restauration"></a>
## `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
```typescript
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
---
<a id="risque-maxworkers-1-masque-isolation"></a>
## `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
---
<a id="risque-flakiness-inter-fichiers-db-partagee"></a>
## 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** :
```bash
# 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
---
<a id="risque-test-non-regression-rbac-guards-reels"></a>
## 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
---
<a id="risque-prefixe-fixture-unique-par-fichier"></a>
## 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
---
<a id="risque-test-collision-fichier-versionne"></a>
## 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 status``D`), 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
---
<a id="risque-test-singleton-module-level-env"></a>
## 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
---
<a id="risque-test-rate-limit-rang-hardcode"></a>
## 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