En résumé
Linkr est vérifié par quatre niveaux de tests, du plus rapide au plus complet : les tests unitaires, lancés à chaque git push ; les parcours de bout en bout, qui pilotent un vrai navigateur ; la matrice de bases de données, qui passe chaque requête de l’application sur chaque moteur ; et les scénarios SPE, qui installent Linkr dans une simulation d’entrepôt hospitalier. Deux skills, /run-tests et /update-tests, choisissent les tests à lancer et les tiennent à jour.
Vue d’ensemble
| Niveau | Ce qu’il prouve | Commande | Durée | Quand |
|---|---|---|---|---|
| Unitaires | la logique pure : construction du SQL, échappement, import/export, routes et services de l’API | voir Tests unitaires | 2 à 5 min | à chaque git push |
| Bout en bout | les parcours d’un utilisateur, dans un vrai navigateur | npm run test:e2e | 1 à 2 min | à la demande, en relecture, avant une version |
| Matrice de bases | chaque requête donne la même réponse sur chaque moteur, et en combien de temps | npm run test:matrix | 1 min (×1) à 20 min (×50) | après un changement de SQL ou de connecteur, avant une version |
| Scénarios SPE | Linkr fonctionne derrière chaque variante de proxy, de réseau et de machine | npm run test:spe | ~3 min par scénario | après un changement de déploiement, avant une version |
Une campagne complète, avant une version, prend environ une heure. La référence côté développeurs est docs/testing.md, dans le dépôt.
Tests unitaires
Ils vérifient la logique pure, sans interface : construction des requêtes SQL, échappement, recherche floue, import et export des entités, mise en forme. Ce qui mérite un test unitaire — et ce qui n’en mérite pas, comme les composants d’interface qui changent souvent — est décrit dans docs/conventions.md.
- Frontend — à côté du code, en
*.test.tsdansapps/web/src/; lancés parnpm run test(Vitest). - API — dans
apps/api/tests/; lancés parnpm run test:api(pytest). Ils tournent en parallèle sur quatre processus, chaque test avec sa propre base SQLite, et aucun ne peut durer plus de deux minutes. - Paquets — chaque paquet de
packages/a ses tests :linkr-format(le format d’export),linkr-mcp(le serveur MCP),linkr-py(le client Python).
Au moment du push
Un hook — un script que git lance avant d’envoyer vos commits — exécute ces tests, mais seulement ceux de ce que le push modifie : un changement dans apps/api lance les tests de l’API, un changement dans apps/web les tests, le lint et le typage du frontend. Si l’un échoue, le push est bloqué. Le script est scripts/git-hooks/pre-push, installé par npm install ; LINKR_PREPUSH_ALL=1 git push lance tout. Les niveaux plus lents ne sont jamais dans le hook.
Parcours de bout en bout
Un parcours est un scénario d’utilisateur rejoué dans un vrai navigateur par Playwright : ouvrir la page d’accueil, chercher un concept, lancer une cohorte, afficher un tableau de bord… Il vérifie ce qu’un utilisateur verrait, et échoue aussi sur toute erreur JavaScript de la page.
client— la version navigateur seul (WASM), avec les données de démonstration, servie sur le port 4317. Les données doivent avoir été récupérées une fois avecnpm run data:fetch.server— une instance complète, frontend et API ; ce sont les scénarios SPE qui la fournissent. Le parcours commence par l’assistant d’installation, qui crée le premier compte : il faut donc une instance jetable.
Les parcours sont dans apps/web/e2e/, la configuration dans apps/web/playwright.config.ts. Ils trouvent les éléments par leur rôle et leur texte visible, jamais par une classe CSS : un changement de style ne les casse pas, un bouton renommé si. Après un échec, la trace et l’arbre d’accessibilité de la page sont conservés dans apps/web/test-results/ (npx playwright show-trace …).
Un bug connu reste visible
Un parcours qui reproduit un bug encore ouvert porte le tag @known-bug dans son titre : il est écarté des lancements ordinaires, et E2E_KNOWN_BUGS=1 le relance. Une fois le bug corrigé, on retire le tag — le parcours devient alors le test de non-régression.
Matrice de bases de données
Linkr lit des bases sur plusieurs moteurs — un dossier Parquet, un fichier DuckDB ou SQLite, un serveur PostgreSQL, MySQL ou Doris. La matrice vérifie que le SQL que l’application envoie vraiment donne la même réponse partout. Elle exécute les vrais générateurs de requêtes — dossier patient, vue d’ensemble, cohortes, comptages de concepts, catalogue — puis passe chaque requête sur chaque moteur, par le code de connexion de l’API, et compare les lignes à la référence : DuckDB sur le Parquet.
- différent — un moteur a répondu autrement : c’est toujours un bug, dans le SQL généré ou dans le connecteur ;
- erreur — un moteur a refusé le SQL de l’application ;
- les temps par requête et par moteur signalent les requêtes lentes.
Les données sont la démonstration publique MIMIC-IV au format OMOP (100 patients), clonées dans .cache/test-data/ au premier lancement. --scale N en fait N copies, sous de nouveaux identifiants, pour une base plus grosse — sur les moteurs de fichiers et PostgreSQL : MySQL ne sert qu’à vérifier le dialecte, ce que la base de taille normale suffit à faire.
npm run test:matrix # moteurs de fichiers, sur cette machine
npm run test:matrix -- --spe # + PostgreSQL et MySQL, dans la simulation SPE
npm run test:matrix -- --spe --machine tiny --scale 50 # une petite machine, une base ×50
Le code est dans tests/db-matrix/ et scripts/test-matrix.mjs. Le rapport, .cache/db-matrix/<machine>-x<échelle>/report.md, donne par moteur le nombre de requêtes identiques, différentes ou en erreur, le temps total et la requête la plus lente.
Scénarios SPE
Un SPE — environnement sécurisé de traitement des données — est l’espace isolé où un entrepôt de données hospitalier fait tourner Linkr pour un projet : pas d’internet, un proxy devant l’application, un miroir local des paquets. Le dépôt en contient une simulation, dans docker/spe-sim/. Chaque scénario part d’une installation vierge, la démarre sous une variante, lance un test de fumée — l’application répond-elle, sous le bon préfixe ? — puis, avec --e2e, les parcours server.
Les scénarios, dans docker/spe-sim/scenarios/ :
- default — le SPE tel que la plupart des hôpitaux le configurent : WebSockets transmis, préfixe retiré, miroir de paquets, délais généreux ;
- no-websockets — un proxy qui ne laisse pas passer les WebSockets : l’application se charge, et le terminal, le journal d’ETL en direct et les notifications échouent avec un message qui nomme le proxy ;
- short-timeout — un proxy qui coupe une connexion inactive au bout d’une minute : l’application le signale au lieu de rester bloquée ;
- small-uploads — l’envoi de fichiers limité à 1 Mo, la valeur par défaut de nginx : un envoi plus gros échoue avec une erreur que l’application affiche ;
- no-strip-prefix — un proxy qui transmet le chemin sans retirer le préfixe : l’application fonctionne comme avec le scénario par défaut ;
- offline — sans miroir de paquets : les environnements de projet ne peuvent rien installer, et le disent.
npm run test:spe -- default # un scénario
npm run test:spe -- --machine tiny --e2e # tous, sur une petite machine, avec les parcours
Le script est scripts/test-spe.mjs ; le rapport est .cache/spe/report-<machine>.md.
Profils de machine
Un Mac de développement rapide cache les requêtes lentes. Les profils de docker/spe-sim/machines/ — tiny, laptop, small-server, unlimited — limitent le processeur et la mémoire du conteneur de l’API et des bases ; DuckDB lit la limite de mémoire et s’y adapte. Comparez un lancement bridé avec unlimited avant de déclarer une requête lente. Les limites de la machine virtuelle de Docker Desktop restent le plafond de tous les profils.
Les tests instables
Un test qui échoue une fois sur cinq révèle une situation de compétition — deux opérations qui se doublent selon le hasard du moment —, dans le test ou dans l’application. Aucun niveau ne relance un test en échec : une instabilité apparaît comme un échec au lieu de se cacher. Tout répéter à chaque fois coûterait trop cher ; on répète là où ces situations naissent :
- après une modification du montage des bases, du chargement des données de démonstration, des tâches de fond, de l’annulation ou des WebSockets, relancez cinq fois les tests de ce domaine (
--repeat-each 5pour Playwright, une boucle pour pytest) ; - avant une version, relancez les parcours
clientcinq fois ; - un échec vu une seule fois s’examine — on ne relance pas jusqu’à ce qu’il passe.
Les skills
Deux skills de Claude Code, dans .claude/skills/, portent ces niveaux de tests.
/run-tests
Choisit les niveaux à lancer d’après ce que la modification touche — un générateur de SQL appelle la matrice, un fichier de docker/ les scénarios SPE, une page ou un store les parcours —, les lance, puis rend un verdict : un tableau par niveau avec les résultats et les durées, et chaque échec, en précisant s’il vient de la modification ou s’il était déjà là. Il prend aussi un argument : quick, e2e, matrix, spe ou full. /code-review l’appelle à la fin de chaque relecture.
/update-tests
Met les tests à jour après que le code a bougé : il repère ce qui a changé depuis le dernier audit, ajoute les tests manquants — tests unitaires, requêtes de la matrice, parcours, scénarios —, corrige ceux que le changement a rendus faux, retire ceux qui ne servent plus et revérifie les bugs connus. Chaque audit est consigné dans AUDIT-LOG.md, pour que le suivant reprenne là où celui-ci s’est arrêté. À lancer quand une fonctionnalité arrive sans tests, ou avant une version.
Pour aller plus loin
- Contribuer au code — l’installation depuis les sources, les branches, les worktrees et les autres skills.
- Modes de déploiement — pourquoi les parcours existent en version navigateur seul et en version serveur.
- Connecteurs de bases de données — les moteurs que la matrice vérifie.