Développeur
Tester vos factures dans GitLab CI et GitHub Actions
Un job de votre pipeline envoie vos factures d'essai au validateur à chaque modification de votre code. Il devient rouge quand l'une d'elles empire, pas pour un échec déjà connu. Votre projet peut être écrit dans n'importe quel langage : voici les jobs prêts à copier, et la façon de s'en servir au quotidien.
Ce que le job attrape
Le job installe la commande valide-facture, puis valide chaque fichier .pdf et
.xml du dossier tests/factures : format du fichier, règles de la norme
EN 16931 et contrôles de la réforme française. Il compare ensuite chaque résultat à un fichier de
référence, versionné dans votre dépôt, qui photographie les résultats que vous avez acceptés. Son code de
sortie dit quoi faire dans la demande de fusion :
-
0: aucune facture n'a empiré par rapport à la référence. Une facture déjà non conforme, connue de la référence, ne rend pas le job rouge. -
1: une facture que la référence ne connaît pas encore est en échec. Corrigez la facture, ou le code qui la produit. -
2: une régression. Une facture a empiré, ou a disparu du dossier. Lisez la cause probable écrite sous la ligne du fichier : votre code a changé, ou ce sont les règles du validateur. -
3: le test n'a pas pu se faire (clé absente ou refusée, quota atteint, service indisponible). Ce code ne dit rien de vos factures : relancez le job une fois la cause levée.
Un job dans un autre langage que votre projet
Chaque job d'un pipeline choisit son image Docker. Celui de cette page tourne sur une image qui contient
PHP et Composer, et il y installe la commande à chaque exécution. PHP tourne dans le job, pas dans votre
projet : votre dépôt n'a besoin ni de composer.json, ni de PHP sur vos postes, et le
reste de votre pipeline ne change pas.
Le job lit les factures du dossier tests/factures : versionnées dans votre dépôt, ou
produites par un job précédent de votre pipeline, qui les lui transmet.
Sans PHP du tout, le script de la page
Valider une facture avec curl donne les codes
0, 1 et 3 dans un job, mais sans fichier de référence ni rapport
JUnit.
Le jeu de factures d'essai
- Déterministe. Produisez chaque facture avec une horloge figée, des numéros fixes et aucun identifiant aléatoire. La référence garde l'empreinte de chaque fichier : un fichier regénéré à l'identique garde la sienne, et devant une régression la commande peut dire si la cause est votre code ou les règles du validateur.
- Petit et représentatif. Un fichier par type de document et par particularité de votre facturation (avoir, remise, exonération, autre devise) suffit. Chaque fichier coûte une validation à chaque exécution.
- Jamais une facture réelle d'un client. Elle porte des données personnelles et commerciales, et elle resterait dans l'historique de votre dépôt.
La référence
Produisez-la une première fois depuis votre poste, à la racine du dépôt. Docker suffit, sans PHP
installé ; la commande s'écrit pour un shell POSIX (Linux, macOS, ou WSL sous Windows). Exportez
d'abord votre clé API dans VALIDE_FACTURE_API_KEY, par exemple avec
read -rs VALIDE_FACTURE_API_KEY && export VALIDE_FACTURE_API_KEY :
docker run --rm --user "$(id -u):$(id -g)" -e VALIDE_FACTURE_API_KEY -v "$PWD":/projet -w /projet composer:2 sh -c '
composer global config allow-plugins.php-http/discovery true &&
composer global require logalysm/valide-facture:^1.0 --no-interaction --no-progress &&
"$(composer global config bin-dir --absolute)/valide-facture" baseline update tests/factures'
git add valide-facture.baseline.json
-e VALIDE_FACTURE_API_KEY, sans valeur, transmet la clé de votre shell au conteneur sans
l'écrire dans la commande ni dans votre historique. --user fait écrire
valide-facture.baseline.json à votre nom : sans lui, sur Linux, le fichier appartient à
root. Commitez la référence avec les factures qu'elle décrit.
Relancez la même commande après chaque changement de résultat que vous acceptez : une facture corrigée, que le job signale « amélioré », ou un cas ajouté au jeu d'essai. Commitez la nouvelle référence avec le changement qui l'explique. Ne la regénérez pas pour faire passer une régression : elle en effacerait la seule trace.
Ajouté à la commande check du job, --strict fait échouer toute différence avec
la référence, y compris une amélioration ou un fichier nouveau. Il convient quand la référence doit
toujours décrire exactement le jeu d'essai.
GitLab CI
Ajoutez ce job à votre .gitlab-ci.yml :
valide-facture:
stage: test
image: composer:2
script:
- composer global config allow-plugins.php-http/discovery true
- composer global require logalysm/valide-facture:^1.0 --no-interaction --no-progress
- export PATH="$(composer global config bin-dir --absolute):$PATH"
# Les factures de tests/factures sont produites par votre code, ou versionnées.
- valide-facture check --baseline valide-facture.baseline.json tests/factures --junit rapport-junit.xml
artifacts:
when: always
reports:
junit: rapport-junit.xml
La première ligne de script autorise le plugin php-http/discovery, qui installe
le client HTTP de la commande. Sans elle, Composer bloque le plugin en mode non interactif, et chaque
fichier sort en 3.
Déclarez la clé en variable CI/CD du projet (Settings > CI/CD > Variables), nommée
VALIDE_FACTURE_API_KEY, en visibilité Masked. Laissez Protect variable
décoché, sauf si le job ne tourne que sur des branches protégées : une variable protégée n'est
transmise qu'à leurs pipelines, et ailleurs le job sort en 3, clé absente.
when: always envoie le rapport même quand le job échoue ; par défaut, GitLab ne
l'envoie qu'en cas de succès. Le rapport s'affiche dans l'onglet Tests du pipeline et dans la
demande de fusion, avec un cas de test par facture.
GitHub Actions
Le même contrôle, dans .github/workflows/valide-facture.yml :
name: valide-facture
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
jobs:
valide-facture:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: shivammathur/setup-php@eb7c497e18156a6bbabfef1d3a82760b9eda3962 # 2.40.0
with:
php-version: '8.5' # toute version prise en charge par la commande : 8.3 ou plus récente
- run: |
composer global config allow-plugins.php-http/discovery true
composer global require logalysm/valide-facture:^1.0 --no-interaction --no-progress
composer global config bin-dir --absolute >> "$GITHUB_PATH"
# Les factures de tests/factures sont produites par votre code, ou versionnées.
- run: valide-facture check --baseline valide-facture.baseline.json tests/factures --junit rapport-junit.xml
env:
VALIDE_FACTURE_API_KEY: ${{ secrets.VALIDE_FACTURE_API_KEY }}
- uses: mikepenz/action-junit-report@6ef3fdcfcd5f0b5d14df3e659ef19b8c6820f93d # v6.6.1
if: ${{ !cancelled() }}
with:
report_paths: rapport-junit.xml
annotate_only: true
Chaque étape run est un shell neuf : la troisième étape inscrit le dossier de la
commande dans $GITHUB_PATH, que GitHub ajoute au chemin des étapes suivantes.
Déclarez la clé en secret du dépôt (Settings > Secrets and variables > Actions), nommé
VALIDE_FACTURE_API_KEY. Seule l'étape qui lance check la reçoit. Sur une pull
request ouverte depuis un fork, GitHub ne transmet pas les secrets : le job sort en 3,
clé absente. N'utilisez pas pull_request_target pour le contourner : il donnerait le
secret au code du fork.
Le workflow tourne sur les push de main et sur les pull requests : une branche qui a
une pull request ouverte n'est pas validée deux fois à chaque push, ni sa consommation de quota
doublée.
GitHub n'affiche pas de rapport JUnit de lui-même. La dernière étape le lit et l'écrit dans le résumé du
job, même quand la commande a échoué : mikepenz/action-junit-report est une action
tierce, sous licence Apache-2.0, que vous pouvez remplacer par tout lecteur de JUnit. Avec
annotate_only: true, elle ne crée pas de check run, et le droit
contents: read lui suffit. Chaque action est épinglée par l'empreinte de son commit :
une étiquette peut être déplacée, une empreinte non.
Ce que vous faites tourner dans votre pipeline
^1.0 installe la dernière version 1 de la commande, jamais une version 2, qui
pourrait changer les codes de sortie ou le format de la référence. Les dépendances de la commande, elles,
sont résolues à chaque exécution : une version publiée entre deux pipelines entre dans votre job sans
que vous l'ayez relue. L'image composer:2 suit de même les versions de PHP et de Composer.
Pour figer davantage, installez la commande depuis un composer.json dédié au job, rangé
dans un sous-dossier, et versionnez son composer.lock : composer install
rejoue alors exactement les mêmes versions. Épinglez l'image par son empreinte de la même façon.
Ce que coûte une exécution
Chaque fichier validé consomme une unité de quota, à chaque exécution du pipeline ; la production de la référence aussi. Dix factures d'essai et vingt pipelines dans le mois consomment deux cents unités. Un refus d'entrée (fichier vide, trop volumineux, ni PDF ni XML) et une indisponibilité du service n'en consomment pas. Une clé refusée ou un quota atteint arrêtent la commande : les fichiers suivants ne sont pas envoyés.
Aller plus loin
La liste complète des refus du service est sur la page des guides du développeur, et chaque code de règle qu'un rapport peut citer a sa page parmi les codes d'erreur du validateur. Pour voir le rapport complet d'une facture avant de l'ajouter au jeu d'essai, déposez-la dans le validateur.
Portée de cette page
Ces jobs testent vos factures d'essai, pas celles que vous émettez. Le verdict porte sur la conformité technique du document au format et aux règles appliquées par le validateur ; il constitue une aide au diagnostic, et ne présume pas de l'acceptation de la facture par la plateforme de votre destinataire.
Développeur