Aller au contenu
Sylvain Joffraud l4w
Retour

Harnais de tests bash pour une API PDF qu’on ne maîtrise pas

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 :

  1. POST l’endpoint de génération avec un XML de référence (versionné en git) ;
  2. vérifier le HTTP 200 + le magic number %PDF en tête du binaire ;
  3. extraire le texte (pdftotext -layout) et asserter le contenu métier (noms, dates, montants, libellés) ;
  4. 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

#bash #testing #api #pdf #integration #ci


Partager cet article :

Article précédent
Docker bind mount : la source absente crée un dossier vide
Article suivant
Coolify « Pre-deployment Command » : un exec, pas un shell