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>
41 lines
1.6 KiB
TOML
41 lines
1.6 KiB
TOML
[tool.ruff]
|
|
# Linting Python (équivalent d'ESLint côté crawler/geocoder).
|
|
line-length = 110
|
|
target-version = "py312"
|
|
exclude = [".git", "node_modules", "__pycache__"]
|
|
|
|
[tool.ruff.lint]
|
|
select = [
|
|
"E", # pycodestyle
|
|
"F", # pyflakes — imports/variables inutilisés, noms indéfinis
|
|
"I", # isort — ordre des imports
|
|
"B", # bugbear — pièges classiques (mutable default, boucle+except…)
|
|
"UP", # pyupgrade
|
|
"S", # bandit — analyse de sécurité (SQLi par f-string, subprocess, secrets…)
|
|
"C4", # comprehensions
|
|
]
|
|
ignore = [
|
|
"E501", # longueur gérée par le formateur
|
|
"S101", # `assert` : légitime dans les tests
|
|
]
|
|
|
|
[tool.ruff.lint.per-file-ignores]
|
|
# Les requêtes SQL sont construites avec des placeholders psycopg2 (%s) ;
|
|
# les rares f-strings ne portent que des noms de colonnes issus d'allowlists.
|
|
"apps/crawler/src/db.py" = ["S608"]
|
|
"apps/geocoder/src/*.py" = ["S608"]
|
|
"**/tests/*.py" = ["S105", "S106"]
|
|
# `random` sert au jitter de politesse entre deux requêtes HTTP, pas à générer
|
|
# des secrets : S311 (générateur non cryptographique) ne s'applique pas.
|
|
"apps/crawler/src/polite.py" = ["S311"]
|
|
"apps/geocoder/src/worker.py" = ["S311", "S608"]
|
|
# md5 sert d'empreinte de contenu pour détecter les pages inchangées,
|
|
# jamais de primitive de sécurité.
|
|
"apps/crawler/src/scraper_band.py" = ["S324"]
|
|
|
|
[tool.pytest.ini_options]
|
|
# Chaque app est testée depuis sa propre racine (`rootdir`), d'où le chemin relatif
|
|
# `src.` dans les imports de test. Voir le script `test:py` du package.json.
|
|
testpaths = ["tests"]
|
|
python_files = ["test_*.py"]
|
|
addopts = "-q --strict-markers"
|