Test de contrat GraphQL
Vue d'ensemble
Des tests qui se situent sur la jointure entre les couches et cassent lorsqu'un côté bouge sans le consentement de l'autre — tests-comme-contrôle appliqué aux frontières. La règle cardinale : vérifiez le contrat — la forme des données et la sémantique d'accès dont dépend le consommateur — jamais la chaîne de la requête elle-même. Un test qui casse sur une modification cosmétique de requête est un anti-patron de détection de changement ; un test qui casse lorsque la forme garantie ou la permission bouge est un contrôle.
§1 — Le contrat de requête GraphQL
- Chaque opération est écrite une seule fois comme document typé dans un emplacement connu, exportée pour être importée. L'UI l'importe ; le test importe le même artefact — jamais une copie retapée. Copier une requête dans un test tue silencieusement le contrat (modifier la requête UI ne casse plus le test). DRY devient porteur de charge.
- Il casse dans les deux directions, par construction :
- Dérive DB → UI, au moment de la compilation.
graphql-codegengénère des types TypeScript à partir du schéma live. Renommer ou supprimer un champ utilisé ettscéchoue sur chaque utilisation et le document partagé — la build casse avant qu'un utilisateur ne voie un panneau vide. C'est pourquoi le codegen contre le schéma live est obligatoire. - Dérive UI → DB / permission, au moment de l'exécution. Le document partagé s'exécute contre une base de test ensemencée via le chemin d'appel réel, sous un contexte de rôle, en vérifiant la forme retournée et le résultat d'accès — pas le texte de la requête.
- Dérive DB → UI, au moment de la compilation.
- Les tests de contrat et la matrice de rôles RLS sont le même harnais. Exécutez le document unique via PostGraphile en tant que rôle A (données attendues) et rôle B (refus/vide attendus). Un mécanisme prouve trois choses : la requête correspond toujours au schéma, les hypothèses UI tiennent toujours, le RLS applique toujours la frontière.
Exemple concret — comment un vrai bug est attrapé
- DB→UI : une migration supprime
equipment.is_active. Codegen regénère à partir du nouveau schéma ; le type généré perd le champ ;tscéchoue sur le document partagé et chaque composant qui l'utilise — attrapé à la build, avant le merge. Aucun test ne vérifie "texte de requête == X." - UI→DB : un développeur élargit la requête UI pour inclure
cancellation_reason(RLS l'expose uniquement aux rôles élevés). Le test runtime exécuté en tant que membre simple vérifie la forme visible pour le membre, obtient maintenant null/refus, et échoue — forçant "avions-nous l'intention d'exposer ceci ?"
§2 — Tests de contrat de route et de fumée
- Routes énumérées à partir d'une source unique. Chaque route a un test de fumée : s'affiche sans erreur + affiche un ensemble minimal d'éléments requis (un titre, un repère, un contrôle clé — pas une interaction détaillée).
- Ajouter/supprimer une route force un changement de test correspondant — une nouvelle URL est incomplète sans son test de fumée ; une URL supprimée supprime son test. Les URL n'apparaissent ni ne disparaissent silencieusement.
- Explicitement PAS des tests détaillés UI/interaction — délibérés, de portée limitée. La question est "cette URL fonctionne-t-elle et affiche-t-elle l'essentiel," rien de plus.
Application : graphql-codegen + tsc en CI (bloquant) pour la partie compilation ; tests runtime de contrat + tests de fumée de route en CI contre une base de données ensemencée.
Raisonnement complet : Article XV de la constitution, regroupé dans engineering-constitution/references/engineering-constitution.md.