Plusieurs modèles de documents PDF (mandat de prélèvement SEPA, bulletin d’adhésion, fiche de conseil, mandat de résiliation) à intégrer dans un parcours de souscription. Ces PDF sont produits par une API d’éditique d’un prestataire externe : on lui POST un XML, elle renvoie un PDF. On ne maîtrise ni le code de l’API, ni le moteur de rendu, ni le calendrier de ses re-builds.
La question : comment garantir qu’une nouvelle livraison du prestataire ne casse pas ce qui marchait ? À chaque re-déploiement chez eux, un champ peut disparaître, un bloc se vider, le HTTP 200 devenir 500. Sans contrat exécutable, on le découvre… quand un utilisateur le découvre.
Le besoin : un contrat exécutable, pas un framework
On a écrit un script bash de tests d’intégration. Pas de framework, pas de dépendance lourde : curl pour appeler, pdftotext (poppler-utils) pour extraire le texte, grep -F pour asserter. L’idée tient en quatre étapes par modèle :
- POST l’endpoint de génération avec un XML de référence (versionné en git) ;
- vérifier le HTTP 200 + le magic number
%PDFen tête du binaire ; - extraire le texte (
pdftotext -layout) et asserter le contenu métier (noms, dates, montants, libellés) ; - rejouer un cas de régression précis (un bloc qui se vidait quand on omettait un champ optionnel).
La brique d’appel, isolée dans un helper :
# compose_pdf <template> <xml_path> <out_pdf>
# POST le XML, stocke le PDF + le texte extrait, echo le code HTTP
compose_pdf() {
local tpl="$1" xml="$2" out="$3"
local http
http=$(curl -sS -o "$out" -w "%{http_code}" --max-time 20 \
-X POST "$ENDPOINT?templateName=$tpl" \
-H 'Accept: application/pdf' \
-F "dataFile=@$xml;type=application/xml" 2>/dev/null || echo "000")
# Si c'est bien un PDF, on extrait le texte pour les assertions de contenu
if [ "$(head -c4 "$out" 2>/dev/null)" = "%PDF" ] && command -v pdftotext >/dev/null; then
pdftotext -layout "$out" "${out%.pdf}.txt" 2>/dev/null || true
fi
echo "$http"
}
Les assertions : lisibles, comptées, fixed-string
Une petite lib d’assertions partagée maintient des compteurs et logue en couleur. Le point clé : grep -qF (fixed-string, pas regex). On cherche une chaîne métier exacte, pas un motif — c’est plus lisible, plus facile à débugger, et ça n’explose pas sur un caractère spécial dans un nom propre.
# assert_contains <name> <file> <pattern fixed-string>
assert_contains() {
local name="$1" file="$2" pat="$3"
TESTS_TOTAL=$((TESTS_TOTAL + 1))
if [ -f "$file" ] && grep -qF -- "$pat" "$file"; then
TESTS_PASS=$((TESTS_PASS + 1))
log_pass "$name"
else
TESTS_FAIL=$((TESTS_FAIL + 1))
log_fail "$name (motif absent : '$pat')"
fi
}
Un cas de test ressemble alors à de la prose vérifiable :
log_section "MANDAT_PRELEVEMENT_SEPA"
http=$(compose_pdf "MANDAT_PRELEVEMENT_SEPA" "$DIR/mandat_sepa.xml" "$OUT/sepa.pdf")
assert_eq "HTTP 200" "200" "$http"
assert_eq "magic PDF" "%PDF" "$(head -c4 "$OUT/sepa.pdf")"
assert_contains "IBAN présent" "$OUT/sepa.txt" "FR76"
assert_contains "BIC présent" "$OUT/sepa.txt" "AGRIFRPP"
assert_contains "titulaire" "$OUT/sepa.txt" "Jean Dupont"
assert_contains "adresse simplifiée" "$OUT/sepa.txt" "12 rue des Lilas 44000 Nantes"
# Régression : ce bloc se vidait quand l'adresse du titulaire était omise
assert_contains "bloc adresse non vide" "$OUT/sepa.txt" "Fait à Nantes"
Le total des assertions s’affiche en fin de run (avec la liste nominative des échecs), et le script exit 1 si quoi que ce soit casse — donc il s’enchaîne dans une CI sans glue supplémentaire.
Ce que ça a donné
57 assertions sur 4 modèles + 1 cas de régression. Un re-build chez le prestataire ? On relance le script, et 30 secondes plus tard on sait précisément ce qui a changé — pas « le PDF a l’air différent » mais « l’assertion IBAN présent sur le mandat SEPA est tombée ». La dernière régression en date a été détectée 3 minutes après que le prestataire ait poussé son fix : on a relancé, vu vert, confirmé.
Le détour pdftotext mérite un mot : un PDF est une boîte noire binaire, mais pdftotext -layout en extrait le texte en préservant grossièrement la mise en page. On passe d’un artefact non testable à un fichier texte sur lequel grep redevient roi. C’est suffisant pour vérifier que les données métier sont là — on ne teste pas le pixel, on teste le contenu.
Lessons learned
- Quand vous consommez une API que vous ne maîtrisez pas, écrivez vos tests d’intégration dès la première livraison. Le fichier de référence + les assertions, c’est votre contrat avec le prestataire. Sans lui, chaque re-build est une roulette russe.
- Bash + curl +
pdftotext+grep -Fsuffisent largement. Pas besoin d’un framework de test pour valider une API HTTP qui crache des PDF. La barrière à l’entrée minuscule fait qu’on l’écrit vraiment, au lieu de remettre à plus tard. - Préférez les assertions fixed-string aux regex.
grep -qF "FR76..."est lisible par un non-dev, ne casse pas sur un caractère spécial, et le message d’échec dit exactement quelle chaîne manque. - Un PDF se teste via son texte extrait.
pdftotext -layouttransforme un binaire opaque en ciblegrep-able. On vérifie la présence des données métier, pas le rendu graphique — c’est ce qui régresse le plus souvent. - Versionnez les XML de référence ET les PDF de sortie attendus en git. Le diff d’un PDF n’est pas lisible, mais le diff du
.txtextrait l’est : il montre noir sur blanc ce qu’une livraison a changé. - Faites
exit 1en cas d’échec et comptez les assertions. Un harnais qui retourne un code de sortie propre s’intègre dans n’importe quelle CI sans effort, et le compteur (« 57/57 ») donne une métrique de confiance immédiate.
#bash #testing #api #pdf #integration #ci