mirror of
https://github.com/MaksTinyWorkshop/_Assistant_Lead_Tech
synced 2026-06-28 01:53:40 +02:00
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>
This commit is contained in:
@@ -249,6 +249,160 @@ const notif = await waitForNotification({ type: 'X', recipientId: userId });
|
||||
- [ ] Timeout par défaut court (1500 ms)
|
||||
- [ ] Migration progressive — pas tous les tests d'un coup
|
||||
|
||||
### Pattern symétrique pour vérifier l'ABSENCE d'un side-effect
|
||||
|
||||
`waitForX` ne convient pas pour prouver qu'**aucun** event n'apparaît : le polling jusqu'au timeout ne distingue pas "aucun event" de "event arrivé après le timeout". Le remplacer par `setTimeout(100) + count()` souffre d'une double fragilité (trop court en CI lent → faux négatif si l'event fuyant arrive après ; trop long → temps gaspillé).
|
||||
|
||||
Le helper symétrique correct est un polling-borné **fail-fast** (`assertCountStable`) : il poll-vérifie que le compteur reste à la valeur attendue sur une fenêtre courte, et abandonne **dès** qu'un compteur surnuméraire est observé.
|
||||
|
||||
```typescript
|
||||
// __tests__/helpers/asyncWait.ts
|
||||
export const assertNotificationCountStable = async (
|
||||
query: { type: NotificationType; recipientId?: string },
|
||||
expected: number,
|
||||
options: { windowMs?: number; intervalMs?: number } = {},
|
||||
): Promise<number> => {
|
||||
const window = options.windowMs ?? 200;
|
||||
const interval = options.intervalMs ?? 50;
|
||||
const deadline = Date.now() + window;
|
||||
|
||||
let count = await prisma.notification.count({ where: query });
|
||||
if (count > expected) return count; // fail-fast immédiat
|
||||
|
||||
while (Date.now() < deadline) {
|
||||
await new Promise((r) => setTimeout(r, interval));
|
||||
count = await prisma.notification.count({ where: query });
|
||||
if (count > expected) return count; // fail-fast
|
||||
}
|
||||
return count;
|
||||
};
|
||||
```
|
||||
|
||||
```typescript
|
||||
// ❌ délai arbitraire — trop court en CI lent (faux négatif), trop long = temps perdu
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
expect(await prisma.notification.count({ where: { /* … */ } })).toBe(1);
|
||||
|
||||
// ✅ polling borné, fail-fast si une notif fuyante apparaît
|
||||
const count = await assertNotificationCountStable({ type: 'X', recipientId: userId }, 1);
|
||||
expect(count).toBe(1);
|
||||
```
|
||||
|
||||
Checklist (absence) :
|
||||
|
||||
- [ ] Fenêtre courte (200 ms par défaut — durée typique d'un fire-and-forget non-désiré, pas un timeout)
|
||||
- [ ] Filtre exhaustif — `recipientId` ou `targetId` au minimum
|
||||
- [ ] Le test affirme `count === expected` APRÈS le helper, pas pendant
|
||||
|
||||
Cas vécu : `PATCH/DELETE payments → aucune nouvelle notif` (`cotisationsPayments.test.ts`), 05-05-2026.
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-injection-dependance-hooks-module-level"></a>
|
||||
## Pattern : Injection de dépendance testable via hooks module-level `__setXForTests` / `__resetXForTests`
|
||||
|
||||
- Objectif : surcharger en test une dépendance d'un module de prod (getter de clés JWKS, sub-resolver DB, …) SANS polluer la signature publique (param de DI) ni recourir à `vi.mock` (fragile sur les imports transitifs, hoisting capricieux).
|
||||
- Contexte : module dont une dépendance interne doit varier en test mais dont la signature publique est un invariant ("signatures intouchables").
|
||||
- Quand l'utiliser : la dépendance est interne et `vi.mock` se révèle fragile (chaîne d'imports transitifs).
|
||||
- Quand l'éviter : la dépendance est déjà un paramètre explicite de la fonction (param-threading suffit) ; ou un `vi.mock` simple et stable fait l'affaire.
|
||||
- Avantage :
|
||||
- pas de param de DI dans le contrat public, pas de `vi.mock` fragile
|
||||
- bonus diagnostic : injecter un fake qui **throw si appelé** prouve qu'un chemin n'est PAS pris (ex. "le sub-resolver Keycloak ne doit pas être appelé quand le JWT-maison gagne")
|
||||
- Limites / vigilance :
|
||||
- **reset systématique dans `setupFile.ts` avant chaque fichier** (même garde-fou que les rate-limiters in-memory : un fichier qui injecte ne doit pas polluer le suivant → flakiness inter-fichiers évitée)
|
||||
- le défaut de PRODUCTION reste le comportement réel (ex. stub fail-closed `() => null` tant que la vraie impl n'existe pas)
|
||||
- Validé le : 14-06-2026
|
||||
- Contexte technique : Vitest — RL799_V2 (rate-limiters, puis Keycloak K1.1)
|
||||
|
||||
### Implémentation
|
||||
|
||||
```typescript
|
||||
// module de prod
|
||||
let active = productionDefault;
|
||||
|
||||
export function __setXForTests(v: typeof productionDefault): void { active = v; }
|
||||
export function __resetXForTests(): void { active = productionDefault; }
|
||||
```
|
||||
|
||||
```typescript
|
||||
// setupFile.ts — reset AVANT chaque fichier
|
||||
beforeEach(() => {
|
||||
__resetXForTests();
|
||||
__resetAllRateLimitersForTests();
|
||||
});
|
||||
```
|
||||
|
||||
Cas vécus : `__resetAllRateLimitersForTests` (`rateLimiter.ts`), puis `__setKeycloakKeyGetterForTests` / `__setSubResolverForTests` (K1.1).
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-e2e-db-based-nestjs-prisma-v7"></a>
|
||||
## Pattern : e2e DB-based NestJS + Prisma v7 (Jest + @swc/jest)
|
||||
|
||||
- Objectif : exécuter des e2e Jest contre une vraie base Postgres (sans mocker `PrismaClient`) dans un projet NestJS utilisant Prisma v7.
|
||||
- Contexte : Prisma v7 charge dynamiquement un runtime WASM (`await import('....mjs')`, `import.meta.url`) → incompatible avec la config Jest historique `ts-jest` + `moduleNameMapper` qui mock le client.
|
||||
- Quand l'utiliser : projet NestJS + Prisma v7 voulant des e2e avec persistance réelle (validation de contracts Zod réels, fixtures partagées avec le seed, intégrité cross-modules).
|
||||
- Quand l'éviter : suites qui n'ont pas besoin d'une vraie DB → garder le pipeline mocké (plus rapide, plus isolé).
|
||||
- Avantage :
|
||||
- exploite des builders typés contre une vraie base
|
||||
- pattern inter-projet : tout NestJS + Prisma v7 rencontre le même problème
|
||||
- Limites / vigilance :
|
||||
- garde-fou anti-truncate destructeur obligatoire (refuser si `DATABASE_URL` ne contient pas "test")
|
||||
- **PAS** de mock global `argon2` (sinon le hash est stub et les tests d'auth ne valident rien)
|
||||
- Validé le : 27-05-2026
|
||||
- Contexte technique : NestJS / Jest / @swc/jest / Prisma v7 / Postgres — app-alexandrie
|
||||
|
||||
### Config Jest e2e DB-based minimale
|
||||
|
||||
```json
|
||||
{
|
||||
"moduleFileExtensions": ["js", "mjs", "json", "ts"],
|
||||
"testRegex": ".e2e-db-spec.ts$",
|
||||
"setupFiles": ["<rootDir>/test-env-db.ts"],
|
||||
"transformIgnorePatterns": ["/node_modules/(?!(@prisma)/)"],
|
||||
"transform": {
|
||||
"^.+\\.(t|j|mj)s$": ["@swc/jest", {
|
||||
"jsc": {
|
||||
"target": "es2023",
|
||||
"parser": { "syntax": "typescript", "decorators": true, "dynamicImport": true },
|
||||
"transform": { "legacyDecorator": true, "decoratorMetadata": true },
|
||||
"keepClassNames": true
|
||||
},
|
||||
"module": { "type": "commonjs" }
|
||||
}]
|
||||
},
|
||||
"moduleNameMapper": { "^(\\.{1,2}/.*)\\.js$": "$1" }
|
||||
}
|
||||
```
|
||||
|
||||
Points-clés :
|
||||
|
||||
- **`@swc/jest`** au lieu de `ts-jest` : gère `import.meta.url`, `await import()` dynamique, et respecte `decoratorMetadata` (DI Nest).
|
||||
- **`transformIgnorePatterns`** ouvert pour `/node_modules/@prisma/` : les `.mjs` du runtime WASM doivent passer dans le transformer.
|
||||
- **`moduleNameMapper`** strip les extensions `.js` : Prisma v7 écrit ses imports en ESM strict (`./internal/class.js`), Jest CJS résout `.ts`.
|
||||
- **PAS** de `moduleNameMapper` mappant `PrismaClient` vers un mock (sinon on tape le mock, pas la vraie DB).
|
||||
|
||||
### Helper e2e avec garde-fou anti-truncate
|
||||
|
||||
```typescript
|
||||
// _helpers/e2e-db.ts
|
||||
export async function truncateE2EDb(prisma: PrismaClient): Promise<void> {
|
||||
const url = process.env.DATABASE_URL ?? '';
|
||||
if (!url.includes('test')) {
|
||||
throw new Error('[e2e-db] truncate refusé : DATABASE_URL ne contient pas "test"');
|
||||
}
|
||||
await prisma.$executeRawUnsafe(`TRUNCATE TABLE ${tables} RESTART IDENTITY CASCADE;`);
|
||||
}
|
||||
```
|
||||
|
||||
Infra (à provisionner une fois) : DB dédiée avec "test" dans le nom + `DATABASE_URL=...test pnpm exec prisma migrate deploy`.
|
||||
|
||||
### Cohabitation avec les e2e mockés historiques
|
||||
|
||||
- Garder le pipeline mocké pour les suites sans besoin de vraie DB.
|
||||
- Migrer progressivement vers le DB-based les e2e qui bénéficient d'une vraie persistance.
|
||||
- Convention de nommage : `*.e2e-spec.ts` (mockés) vs `*.e2e-db-spec.ts` (DB-based) — extension distinctive captée par les `testRegex` respectifs.
|
||||
|
||||
---
|
||||
|
||||
<a id="pattern-test-atomicite-transaction"></a>
|
||||
|
||||
Reference in New Issue
Block a user