import re from pathlib import Path from scripts.vault.generate_map import generer def _depot(tmp_path: Path) -> Path: fichiers = { "backend/core/domain/game.py": "import os\n", "frontend/src/hooks/useBx.ts": "export const useBx = () => 0;\n", "backend/api/animetix/containers/__init__.py": "", } for chemin, contenu in fichiers.items(): cible = tmp_path / chemin cible.parent.mkdir(parents=True, exist_ok=True) cible.write_text(contenu, encoding="utf-8", newline="\n") return tmp_path def test_une_note_par_paquet_plus_index_et_cablage(tmp_path): racine = _depot(tmp_path) sortie = racine / "vault" / "carte" generer(racine, sortie) noms = {chemin.name for chemin in sortie.glob("*.md")} assert "backend-core-domain.md" in noms assert "frontend-src-hooks.md" in noms assert "index.md" in noms assert "cablage-di.md" in noms def test_la_generation_est_deterministe(tmp_path): racine = _depot(tmp_path) sortie = racine / "vault" / "carte" generer(racine, sortie) premier = {c.name: c.read_bytes() for c in sorted(sortie.glob("*.md"))} generer(racine, sortie) second = {c.name: c.read_bytes() for c in sorted(sortie.glob("*.md"))} assert premier == second def test_une_note_orpheline_est_supprimee(tmp_path): racine = _depot(tmp_path) sortie = racine / "vault" / "carte" generer(racine, sortie) orpheline = sortie / "backend-paquet-disparu.md" orpheline.write_text( "reste d'une generation precedente\n", encoding="utf-8", newline="\n" ) generer(racine, sortie) assert not orpheline.exists() def test_les_fins_de_ligne_sont_en_lf(tmp_path): racine = _depot(tmp_path) sortie = racine / "vault" / "carte" generer(racine, sortie) assert b"\r\n" not in (sortie / "index.md").read_bytes() RACINE_DEPOT = Path(__file__).resolve().parents[2] _COMPTES = re.compile(r"\(\d+ l\.\)|\d+ fichiers, \d+ lignes") def _structure(note: Path) -> str: """Le contenu d'une note debarrasse de ses comptes de lignes. Sans ca la garde mordrait a CHAQUE commit touchant backend/ ou frontend/src, puisque chaque note porte le nombre de lignes de chaque fichier. Ce qui rouille vraiment, et qu'on veut voir, c'est la STRUCTURE : un repertoire sans note, ou une note qui liste un fichier disparu. """ return _COMPTES.sub("", note.read_text(encoding="utf-8")) def test_la_carte_couvre_la_structure_reelle_du_code(tmp_path): """vault/carte/ est genere : sans garde, il rouille en silence. Le verificateur de liens ne voit que les liens MORTS. Une note absente pour un repertoire neuf, ou une note listant des fichiers disparus, ne casse aucun lien et ne reveille donc personne. Mesure du 2026-08-22 : 4 repertoires sans aucune note — dont `api/social/` et `api/core/manga/`, devenus des paquets par un decoupage — suite entierement verte. """ attendu = tmp_path / "carte" generer(RACINE_DEPOT, attendu) reel = RACINE_DEPOT / "vault" / "carte" noms_attendus = {p.name for p in attendu.glob("*.md")} noms_reels = {p.name for p in reel.glob("*.md")} assert noms_reels == noms_attendus, ( f"notes manquantes : {sorted(noms_attendus - noms_reels)} | " f"notes orphelines : {sorted(noms_reels - noms_attendus)} — " "regenerer avec `python -m scripts.vault.generate_map`" ) divergentes = sorted( nom for nom in noms_attendus if _structure(reel / nom) != _structure(attendu / nom) ) assert divergentes == [], ( f"{len(divergentes)} note(s) ne listent plus les bons fichiers : " f"{divergentes[:5]} — regenerer avec " "`python -m scripts.vault.generate_map`" )