Fixtures : le générateur de cartes SD¶
Les cartes SD de recette (arbres LogPR<serie>.txt + PaRecPR<serie>_THLog.csv + les *.wav)
pesaient autrefois plusieurs centaines de méga-octets et étaient faites à la main : impossibles à
committer (binaire lourd, dépôt public et forké) et non rejouables (rien ne garantissait de les
reconstruire à l'identique).
Où vivent les WAV, et pourquoi c'est à la racine¶
Les enregistreurs déposent leurs fichiers à la racine de la carte. Le produit accepte aussi un
sous-dossier bruts/ - InspecteurDossier regarde les deux - mais c'est la racine que le parc porte.
Les seize specs produisaient pourtant un bruts/, sans qu'aucune ne puisse en décider : le champ
n'existait pas. La disposition réelle n'était donc jouée par aucun cas de recette, et les parcours
filmés montraient une carte que le lecteur n'a pas sous les yeux (#5281).
Depuis, brutsDansUnSousDossier tranche, et son défaut est faux : une spec qui ne dit rien décrit
ce qu'un enregistreur produit. sd-prefixee est la seule à le poser à vrai, pour que la branche du
sous-dossier garde sa recette - c'est la carte de quelqu'un qui a organisé ses fichiers, et le choix
lui revenait naturellement.
On les décrit désormais par des specs déclaratives de quelques kilo-octets, sous
recette/fixtures/spec/, qu'un générateur déterministe matérialise sur disque. La spec est la
source de vérité ; l'arbre SD n'en est qu'un artefact reconstructible octet pour octet : le
générateur ne tire aucune date de l'horloge ni aucun octet aléatoire.
Le générateur vit en portée test (src/test/java/fr/univ_amu/iut/recette/) : l'application
distribuée n'embarque pas la fabrique de fixtures ; ce sont les tests qui fabriquent WAV/ZIP.
Le format de spec¶
Une spec est un fichier YAML. Tous les champs ont des valeurs par défaut raisonnables, donc une spec reste concise. Exemple commenté (le cas propre) :
fixture: sd-nominale # nom de la carte (= nom du sous-dossier généré)
but: "Cas propre : 6 wav, un seul enregistreur, une seule nuit."
journal: # journal du capteur LogPR<serie>.txt
present: true # false -> aucun journal (mode dégradé)
serie: "1925492" # n° de série déclaré (nomme aussi le fichier)
nuit: "2026-04-22" # date de la 1re ligne (fixe dateDebut pour l'analyseur)
sondePresente: true # ajoute la ligne « Sonde ... présente »
corrompu: false # true -> journal illisible (aucune série extractible)
appuiSurTouche: false # true -> « Wakeup by PINPUSH... Cpt 2 » au milieu de la nuit (#4981)
nuitInterrompue: false # true -> le journal s'arrête après le réveil, cycle jamais refermé (#5093)
sessions: # REDÉMARRAGES supplémentaires du capteur (#3898), facultatif
- nuit: "2026-04-25" # chacun repose ses paramètres après `nuit` ci-dessus
frequenceKhz: 256 # et peut annoncer une autre fréquence d'acquisition
thlog: # relevé climatique PaRecPR<serie>_THLog.csv
present: true
mesures: 6 # nombre de lignes de mesures déterministes
wav: # paramètres communs des WAV (RIFF mono 16 bits valide)
frequenceHz: 384000 # inscrite dans l'en-tête ; divisible par 10 (règle R10)
dureeSecondes: 1.5 # courte, pour des fixtures légères
zip: true # produit aussi sd-nominale.zip (chemin de décompression)
# brutsDansUnSousDossier: true # range les WAV dans bruts/ ; ABSENT = à la racine, ce que les
# enregistreurs déposent réellement (#5281)
enregistreurs: # un ou plusieurs enregistreurs de la carte
- serie: "1925492"
horodatages: # PaRecPR<serie>_<yyyyMMdd>_<HHmmss>.wav
- "20260422_203922"
- "20260422_210515"
# fauxWav: ["20260422_211500"] # octets non-WAV : rejetés à l'import
attendu: # contrat de recette (voir « Le garde-fou »)
aJournal: true
aReleve: true
journalLisible: true # false -> l'inspection doit échouer
plusieursEnregistreurs: false # bandeau « mélange »
incoherent: false # bandeau « incohérence »
nuits: 1 # nombre de nuits détectées
etatNommage: "BRUT" # BRUT, PREFIXE ou VIDE
rejets: 0 # > 0 déclenche la vérification d'import réel
completudes: [] # une par nuit, dans l'ordre : COMPLETE, TRONQUEE ou INCONNUE
Deux variantes utiles :
-
Préfixe (carte déjà nommée en session) : un bloc
prefixe:ajoute le préfixe R6Car<carre>-<annee>-Pass<passage>-<point>-devant chaque nom de brut. -
Horodatages génératifs (grosses cartes) : au lieu de lister chaque fichier, on décrit une série déterministe.
Les 12 cartes de recette¶
Chaque carte exerce une pathologie de l'assistant d'import (voir l'étape 5 de S2 · Importer une nuit).
| Carte | Ce qu'elle exerce | wav |
|---|---|---|
sd-nominale (+ .zip) |
cas propre ; l'archive exerce le chemin de décompression | 6 |
sd-melange |
deux enregistreurs dans le même dossier -> bandeau « mélange » | 6 |
sd-incoherente |
journal et wav en désaccord (série + date) -> bandeau « incohérence » | 3 |
sd-multi-nuits |
trois nuits sous un journal unique -> table des nuits | 6 |
sd-multi-configs |
deux nuits, capteur reconfiguré entre les deux (384 puis 256 kHz) -> chaque nuit doit recevoir les paramètres de sa session (#3460) | 4 |
sd-sans-journal |
aucun LogPR -> mode dégradé (import possible sans journal) |
3 |
sd-journal-corrompu |
LogPR illisible -> l'inspection échoue avec un message clair |
3 |
sd-prefixee |
bruts déjà préfixés Car... -> état de nommage PREFIXE. La seule carte rangée dans bruts/ |
3 |
sd-rejets |
un faux wav parmi huit valides -> l'import aboutit, zone des rejets | 9 |
sd-grosse |
soixante wav -> test de charge (progression, parallélisme, disque) | 60 |
sd-reveil-bouton |
un appui sur une touche au milieu de la nuit -> une nuit, complète, et non deux (#4981) | 5 |
sd-nuit-interrompue |
le journal ne se referme jamais -> nuit tronquée (#5093) | 3 |
Régénérer les cartes¶
Le générateur ne fait pas partie du binaire ; on le lance en portée test via un goal de confort (non lié à une phase, jamais exécuté en CI ni au packaging) :
env -u DISPLAY ./mvnw -q test-compile exec:java@generer-sd \
-Dexec.args="recette/fixtures/spec /chemin/vers/dest"
Chaque carte est écrite dans dest/<fixture>/ (et dest/<fixture>.zip si la spec le demande). Deux
exécutions produisent des octets identiques. Passer une seule spec au lieu du dossier ne génère
que cette carte.
Le garde-fou¶
Le bloc attendu de chaque spec est un contrat vérifié contre le code réel, pas contre une
liste tenue à la main. Deux tests (esprit cliquet, sans JavaFX) amarrent le générateur à la réalité :
GenerationCartesSDCliquetTest: pour chaque spec, génère la carte puis vérifie que l'inspection réelle (InspecteurDossier) constate la pathologie déclarée (mélange, incohérence, nombre de nuits, état de nommage, nombre d'originaux, journal illisible), plus le round-trip du zip viaExtracteurZip.GenerationCartesSDImportCliquetTest: pour toute spec à rejets (attendu.rejets > 0), lance un import réel headless (ServiceImportsur base SQLite jetable) et vérifiecompte(REJETE). C'est le seul cas de recette qui se joue à l'import et non à la seule inspection.
Ces tests deviennent rouges si le générateur cesse de produire la bonne carte ou si un détecteur
d'import change de comportement (AnalyseMelange, AnalyseCoherence, PartitionNuits,
AnalyseurLogPR, ServiceImport, ExtracteurZip).
Provoquer un refus de dépôt¶
Les cartes SD ci-dessus fournissent des données ; elles ne permettent pas de faire refuser un dépôt par la plateforme. Or c'est le seul endroit du produit où un refus est définitif : l'archive ne repartira pas telle quelle, la reprise cesse d'être offerte, et une reconnexion réarme les refus de droits mais jamais un contenu refusé (#3687, #3689). Sans levier, ces cases ne seraient pas rejouables.
Le stub réseau des E2E CLI sert aussi l'application : ConnexionModule#urlDeBase est le seul
endroit qui décide l'URL du client, et ce client est le singleton partagé par toutes les features.
Pointer VIGIECHIRO_URL sur le stub suffit donc à détourner l'IHM.
# 1. Le stub, avec le statut qu'on veut voir servir sur les routes de dépôt.
VIGIECHIRO_STUB_REFUS=403 python3 src/test/bats/stub_vigiechiro.py /tmp/port /tmp/journal 5 &
# 2. L'application, pointée dessus.
VIGIECHIRO_URL="http://127.0.0.1:$(cat /tmp/port)" ./mvnw javafx:run
| Statut | Ce qu'il fait jouer |
|---|---|
403 (ou 401) |
un refus de droits : réparable par une reconnexion, les unités se réarment |
422 (ou 400) |
un contenu refusé : rien ne le répare, et le message ne conseille pas la reconnexion |
0 ou absent |
aucun refus, comportement nominal |
Le refus ne porte que sur /fichiers et /multipart, et c'est délibéré : refuser partout
empêcherait de se connecter, donc d'atteindre le dépôt. Le cas à jouer est ce qui se passe après
le refus, pas le refus lui-même.
Les cas correspondants sont le bloc A5 de la session S4.
Où ça vit¶
recette/fixtures/spec/*.yaml: les specs (source de vérité, versionnées).src/test/java/fr/univ_amu/iut/recette/: le générateur (GenerateurCartesSD,FabriqueWav,LecteurSpec,SpecCarteSd) et les deux garde-fous.- Le rejeu déterministe complet (générer -> importer via la CLI -> comparer à un golden) prolonge ce socle : voir la section « Rejouer une campagne » de la méthode.
La décision de conception (specs déclaratives + générateur déterministe, portée test) est consignée dans l'ADR 0015.