Le dépôt n'avait aucun test, aucun linter, aucune vérification de types. Outillage - ESLint 9 (flat config) sur api + les deux frontends, Ruff sur le Python - tsc --checkJs sur l'API (pas de TypeScript, juste la vérification) - Vitest : 401 tests JS ; pytest : 43 tests Python - Tests de mutation (Stryker), deux profils : logique pure et API complète - Hook pre-push `npm run check` (~17 s) — le déploiement Coolify est sur webhook, c'est donc la seule porte de qualité avant la mise en ligne - Workflow Forgejo Actions prêt (inerte tant qu'aucun runner n'est enregistré) Sécurité - Injection SQL authentifiée dans resolve-conflict : `field` était interpolé dans le SET sans allowlist - timingSafeEqual levait sur un jeton multi-octets (500 au lieu de 401) - setErrorHandler écrasait tous les 4xx en 500 - .env.example : ADMIN_JWT_SECRET et ADMIN_SEED_* n'étaient documentés nulle part alors que leur absence casse toute connexion admin Annulation réelle des crawl_run (migration 014) - L'API posait status='error' sans que le crawler en sache rien : le process continuait, et son UPDATE final ne matchait plus (run réussi affiché en erreur) - Protocole coopératif : drapeau cancel_requested lu à chaque lot, le crawler écrit lui-même status='cancelled' Cohérence géographique (migration 014) - Le trigger 013 supprimait les band_locations sans purger le point dénormalisé - L'édition admin de lat/lon n'atteignait jamais band_locations : la carte ignorait la correction. Override step_order = -1, dans une transaction Corrections - limit/offset NaN → 500 au lieu de 400 - OPTIONS sans `return reply` (Fastify poursuivait le cycle de vie) - listen() sans catch, cast ::text en dur sur les colonnes numériques - /admin/api/logs ne renvoyait pas sa pagination - a11y : sélecteur de langue annoncé comme liste vide (role=option manquant) Nettoyage - apps/web/quizz-site supprimé (sans rapport avec le projet) - Code mort : openModal(), LANG_NAMES, double import, variables inutilisées - .dockerignore ajoutés ; node_modules racine n'était pas gitignoré Co-Authored-By: Claude <noreply@anthropic.com>
65 lines
2.3 KiB
JavaScript
65 lines
2.3 KiB
JavaScript
import Fastify from "fastify";
|
|
import cookie from "@fastify/cookie";
|
|
import { makeFakePool } from "./fakePool.js";
|
|
|
|
/**
|
|
* Applications de test partagées.
|
|
*
|
|
* Construire une app Fastify coûte ~14 ms (helmet + cookie + rate-limit +
|
|
* ready()). C'est négligeable une fois, mais les tests de mutation rejouent la
|
|
* suite des milliers de fois : à raison d'une construction par cas de test, ces
|
|
* 14 ms devenaient l'essentiel des 5 minutes du run.
|
|
*
|
|
* On construit donc l'app UNE fois par fichier, autour d'un pool reconfigurable
|
|
* (`pool.reset(handlers)`), et chaque test ne fait que reprogrammer le pool.
|
|
*
|
|
* Une app par fichier de test (et non une globale partagée) : les fichiers
|
|
* s'exécutent dans des workers séparés, et l'isolation reste totale puisque le
|
|
* seul état mutable est le pool, remis à zéro avant chaque cas.
|
|
*/
|
|
|
|
/** App n'exposant que les routes admin (plugin adminRoutes seul). */
|
|
export async function createAdminApp() {
|
|
const { default: adminRoutes } = await import("../../src/adminRoutes.js");
|
|
const pool = makeFakePool();
|
|
const app = Fastify({ logger: false });
|
|
await app.register(cookie);
|
|
await app.register(adminRoutes, { pool });
|
|
await app.ready();
|
|
return { app, pool };
|
|
}
|
|
|
|
/** App complète (routes publiques + admin), via la fabrique de production. */
|
|
export async function createFullApp(opts = {}) {
|
|
const { buildServer } = await import("../../src/app.js");
|
|
const pool = makeFakePool();
|
|
const app = await buildServer({
|
|
pool,
|
|
logger: false,
|
|
corsOrigins: ["https://metalfrom.eu"],
|
|
seedAdmin: false,
|
|
// L'app est partagée entre les cas du fichier : les plafonds de débit
|
|
// réels rendraient le 11e test rouge sans rapport avec ce qu'il vérifie.
|
|
// Les vraies valeurs sont testées dans rateLimit.test.js.
|
|
globalRateLimitMax: 1e9,
|
|
adminRateLimitMax: 1e9,
|
|
authRateLimitMax: 1e9,
|
|
...opts,
|
|
});
|
|
return { app, pool };
|
|
}
|
|
|
|
/**
|
|
* Quelques cas doivent construire leur propre app (options de fabrique
|
|
* différentes : jeton d'import absent, pool null…). Ils restent rares et
|
|
* assument le coût de construction.
|
|
*/
|
|
export async function createFullAppWith(opts) {
|
|
const { buildServer } = await import("../../src/app.js");
|
|
return buildServer({
|
|
logger: false,
|
|
corsOrigins: ["https://metalfrom.eu"],
|
|
seedAdmin: false,
|
|
...opts,
|
|
});
|
|
}
|