Aller au contenu

Leçons — câblage de @kanmaber/auth-backend dans jobPipeline

Contexte

Câblage réel du package partagé @kanmaber/auth-backend (Shared/packages/auth-backend, distribué en .tgz local, pas un registre npm) dans jobPipeline — container DI dédié, routeur Express dédié, seed ProfilUtilisateur. Deux bugs réels ont été trouvés et corrigés uniquement grâce à un test end-to-end réel (curl sur les endpoints /api/auth/register et /api/auth/connexion), après que le typecheck et le smoke-test d'assemblage DI soient passés sans rien détecter. Suit le pattern DDD/Clean Architecture documenté dans shared/architecture/guide-architecture-ddd (use case en 5 étapes, repository dédié par agrégat).

Contenu

#cle-etrangere Leçon 1 — une valeur hardcodée utilisée comme clé étrangère est une bombe à retardement silencieuse

InscrireUtilisateurUseCase (dans le package) hardcodait idRole: 'role_observateur', valeur utilisée directement comme profilId (FK) par le repository Prisma — sans lookup, sans validation, sans documentation nulle part (ni README, ni script de seed livré, ni commentaire). Résultat : la première inscription réelle a crashé avec une violation de contrainte FK Prisma brute (utilisateurs_profilId_fkey), un message technique opaque sans rapport avec la cause réelle.

Ça a passé le typecheck (string valide), ça a passé le smoke test (qui n'assemble que des objets DI, jamais une vraie requête DB) — détecté seulement au premier curl réel.

Correction : résolution dynamique via une clé métier stable et unique (ProfilUtilisateur.code, déjà @unique dans le schéma), avec une nouvelle méthode obtenirProfilParCode(code) ajoutée à IUtilisateurRepository (pas de nouveau repository dédié — empreinte minimale, cohérent avec la règle "pas d'abstraction prématurée"), et une erreur métier explicite (PROFIL_PAR_DEFAUT_INTROUVABLE) si le seed est absent, au lieu de laisser remonter l'erreur Prisma brute. Le code par défaut est configurable via AuthConfig.DEFAULT_ROLE_CODE (AUTH_DEFAULT_ROLE_CODE en env, défaut 'UTILISATEUR').

Règle générale : dans un package partagé, toute valeur qui référence une donnée censée exister côté consommateur (seed, config par défaut, rôle par défaut...) doit être résolue par une clé métier lisible, jamais un id technique arbitraire hardcodé ; et accompagnée d'une erreur claire si absente, jamais d'un crash technique opaque.

#npm Leçon 2 — npm install sur une dépendance file:...tgz peut silencieusement garder l'ancien code

Après correction du package, rebuild (npm run build) et regénération du tarball (npm pack), un rm -rf node_modules/@kanmaber/auth-backend && npm install dans jobPipeline a bien réécrit le fichier (nouveau timestamp) mais avec l'ancien contenu. Aucune erreur, aucun avertissement.

Cause : package-lock.json retient un hash d'intégrité calculé à la première installation. Tant que le chemin et le numéro de version de la dépendance file: restent identiques, npm fait confiance à son cache local (~/.npm/_cacache) au lieu de relire réellement les octets du .tgz — même si ce fichier a physiquement changé entre-temps.

Option rejetée : pointer la dépendance vers le dossier source plutôt que le .tgz (file:../../Shared/packages/auth-backend, sans nom de fichier). npm crée alors un symlink, et Node résout les imports internes du package (ex. @prisma/client) via le node_modules du dossier du package plutôt que celui du consommateur. Vérifié concrètement : ce node_modules contenait un client Prisma généré depuis le schéma fragment du package (communeCode, updatedAt sans @updatedAt), différent du schéma fusionné réel de jobPipeline (telephone, titre, @updatedAt correct) — un bug de désynchronisation silencieux bien pire que le problème de cache initial.

Solution retenue : bumper la version du package à chaque modification (1.0.01.0.1) avant npm pack, mettre à jour la référence file:...tgz côté consommateur, supprimer l'ancien .tgz. Un nouveau numéro de version force npm à traiter le contenu comme neuf, sans passer par le cache. Vérifié : après bump, npm install seul (sans extraction manuelle du tarball) a correctement repris le nouveau code.

Règle générale pour tout package interne file: + tarball local : - toujours bumper la version avant npm pack, même en dev interne ; supprimer l'ancien .tgz pour éviter toute ambiguïté ; - ne pas pointer vers le dossier source tant que le package a son propre node_modules avec des dépendances qui pourraient diverger de celles du consommateur (notamment @prisma/client, généré depuis un schéma) ; - après tout rebuild, vérifier explicitement (grep sur une chaîne caractéristique du changement) que le contenu installé correspond bien à la nouvelle version — ne jamais supposer que npm install a marché.

Leçon 3 (rappel) — le typecheck et les smoke tests ne prouvent pas qu'une fonctionnalité marche

Les deux bugs ci-dessus, plus des champs Zod requis non documentés (confirmationMotDePasse, accepteCGU) dans le payload d'inscription, sont passés inaperçus jusqu'au premier vrai test curl de bout en bout. Confirme la pratique déjà en usage : tester le comportement réel avant de considérer un câblage terminé, pas seulement la compilation.

Décision / Conclusion

  • Option C retenue pour la résolution du rôle par défaut : lookup par code via une méthode ajoutée à IUtilisateurRepository existant (pas de nouveau repository dédié).
  • Distribution du package file:...tgz conservée telle quelle (pas de passage au dossier symlinké) — le risque de désynchronisation Prisma l'emporte sur le confort de développement.
  • Discipline adoptée : bump de version systématique à chaque modification du package avant repack.

À faire

  • Checklist réutilisable pour tout futur package Shared/packages/* :
  • valeur de référence hardcodée → résolue par clé métier, jamais un id fixe ;
  • le package documente (README/seed) toute donnée supposée côté consommateur ;
  • version bumpée à chaque modif avant npm pack, ancien .tgz supprimé ;
  • après npm install, vérifier par grep que le contenu correspond à la nouvelle version ;
  • test end-to-end réel (curl/navigateur) avant de considérer le câblage terminé.
  • Problème SMTP restant à traiter (rejet 550 SPAM lors de l'envoi de l'email de vérification) — voir veille-financement/configuration-email-deliverabilite pour un cas très similaire déjà rencontré (rejet 550, migration vers Cloudflare Email Routing / Brevo).

Liens connexes