Step 9 inspected latest.json and stopped there. That proves the file is well formed, not that it installs: the TLS download, signature verification, installer execution and relaunch are exercised by no test at all — cargo check and cargo test only prove that code compiles. A regression there surfaces at update time, on a user's machine, and automatic updates are a Base+ feature. Written as a manual checklist rather than automation because it needs two real desktop environments. The value is in the preconditions, which are what make the difference between running the test and only appearing to: - Test from a machine still on the PREVIOUS version. check() compares release.version > current strictly, so testing on the machine that just built the release shows "up to date" and the checklist gets ticked as "nothing to update" — the silent skip it exists to prevent, now with a paper trail claiming it passed. - A Base+ key on each machine, with any activation.token from another machine removed. Auto-update is entitlement-gated: useUpdater dispatches NOT_ENTITLED before check() ever runs, and a token bound to a different machine_id silently resolves the edition back to Free. - A live polkit agent on Linux, and record which prompt actually appeared — install_deb cascades pkexec -> zenity/kdialog -> terminal sudo, so without an agent the app appears to hang instead of failing. The two target flows are written separately because they genuinely differ: on Windows downloadAndInstall never returns (the process exits and NSIS takes over, so readyToInstall/installing never render), while on Linux the app stays alive through a polkit prompt and a restart control. The doc states what the test does NOT prove: `tar` stays unexercised. Its vulnerable path is only reached by install_appimage and the macOS .app.tar.gz branch; our .deb goes through `pkexec dpkg -i` and our NSIS path launches the .exe. That corrects #315's own premise. The failure path is documented as an incident playbook rather than a "rollback", because it is not one. Republishing the previous latest.json stops propagation, but check()'s strict comparison means users already on the broken version are never offered the older one — the real fix is a vN+1. rpm is recorded as known-broken, not unverified, and tracked in #320. Resolves #315 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
7.4 KiB
QA — Cycle de mise à jour automatique
Checklist manuelle pour valider qu'une release est réellement installable par la mise à jour automatique, et pas seulement téléchargeable. À dérouler après chaque release qui touche tauri-plugin-updater, ses dépendances (reqwest/rustls-webpki, tar), release.yml, ou la configuration updater de tauri.conf.json.
Elle existe parce que la CI ne peut rien prouver ici : cargo check et cargo test prouvent que le chemin de mise à jour compile, jamais qu'il fonctionne. Une régression s'y manifeste chez l'utilisateur, au moment de la mise à jour — et la mise à jour automatique étant une fonctionnalité Base+, ce sont des utilisateurs payants.
Cette checklist tourne après que la release est publiée.
release.ymla déjà poussélatest.jsonvers le registre de paquets, qui est l'endpoint configuré danstauri.conf.json. Tout utilisateur Base+ qui clique sur « Vérifier les mises à jour » reçoit donc déjà la nouvelle version pendant que vous déroulez ceci. En cas d'échec, voir « Si ça casse » en bas — et lire cette section avant de commencer, pas pendant l'incident.
Prérequis
Ce sont les conditions sans lesquelles le test ne teste rien. Chacune a un mode d'échec où la checklist se coche « OK » sans avoir rien vérifié.
-
Une machine Windows (hôte ou VM) et une machine Linux de bureau. Les deux flux sont réellement différents, ils ne se substituent pas l'un à l'autre.
-
Sur la machine Linux, un agent polkit qui tourne. L'installation du
.debpasse parpkexec; sans agent, l'app retombe en cascade surzenity/kdialogpuis sur unsudodans un terminal inexistant, et paraît figée au lieu d'échouer. Noter plus bas quelle invite est réellement apparue : si ce n'est pas polkit, c'est un chemin de repli qui a été validé, pas le chemin nominal. -
La version précédente installée sur chaque machine, depuis les artefacts de la release précédente (
.debet-setup.exeattachés à la release Forgejo). Ne pas tester sur la machine qui vient de builder la nouvelle version.Sans ça,
check()comparerelease.version > version_courante, trouve faux, et l'UI affiche « à jour ». La checklist se coche alors comme « rien à mettre à jour » — soit exactement le saut silencieux qu'elle est censée éliminer, avec en prime une trace écrite affirmant qu'elle est passée. -
Une clé de licence Base ou Premium valide sur chaque machine. La mise à jour automatique est verrouillée par édition :
useUpdaterappellecheck_entitlement("auto-update")et bascule ennotEntitledavant même d'appelercheck(). Une installation Gratuite ne télécharge jamais rien, et les builds de release n'ont pas dedev-override. -
Sur chaque machine, supprimer tout
activation.tokenprovenant d'une autre machine (VM clonée, snapshot restauré) dans le répertoire de données de l'app. Unelicense.keyseule fonctionne — l'absence de token est un état de pré-activation toléré — mais un token émis pour un autremachine_idfait retomber l'édition en Gratuite, et le testeur voit une carte « non éligible » anodine plutôt qu'un échec.
Ce que ce test prouve — et ce qu'il ne prouve pas
À écrire explicitement, parce que la tentation est de croire qu'un cycle réussi couvre tout le chemin de mise à jour.
Prouvé sur les deux cibles : le téléchargement TLS (donc rustls-webpki), la vérification de signature minisign, l'exécution de l'installeur, et le redémarrage sur la nouvelle version.
Non prouvé : l'extraction d'archive, donc tar. Dans tauri-plugin-updater, tar n'est atteint que par install_appimage et la branche macOS .app.tar.gz. Aucun de nos bundles ne passe par là — le .deb fait pkexec dpkg -i, le NSIS écrit le .exe dans un répertoire temporaire et le lance. Tant qu'on ne livre pas d'AppImage, le chemin tar reste mort et aucune checklist manuelle ne le couvrira.
1. Windows (NSIS)
Point d'entrée : Paramètres → Systèmes → carte Mises à jour → « Vérifier les mises à jour ». Il n'y a aucune vérification automatique dans l'app — ce bouton et celui de la page d'erreur sont les deux seuls déclencheurs.
-
La carte passe en
checking, puis afficheavailableavec le numéro de la nouvelle version et les notes extraites du CHANGELOG. -
Cliquer sur télécharger → état
downloading, la progression avance (elle vient decontentLength, donc un serveur qui ne le renvoie pas se voit ici). -
L'installeur NSIS s'ouvre et l'application se ferme d'elle-même.
Attendu, et propre à Windows :
downloadAndInstallne rend jamais la main — le processus appelleexit(0)et laisse l'installeur prendre le relais. Les étatsreadyToInstalletinstallingne s'affichent jamais sur cette cible. Ne pas les attendre, ne pas les cocher. -
Terminer l'installation, relancer l'app, vérifier la version dans Paramètres → Systèmes.
-
Re-cliquer sur « Vérifier les mises à jour » →
upToDate.
2. Linux (.deb)
-
Même point d'entrée, mêmes états jusqu'à
downloading. -
Après le téléchargement, l'app reste vivante et passe en
readyToInstall. -
Une invite d'authentification apparaît. Noter laquelle : polkit (nominal), zenity/kdialog, ou rien du tout (repli
sudo→ l'app paraît figée).Invite observée :
________________ -
Authentifier → état
installing, puis le contrôle de redémarrage apparaît. -
Redémarrer, vérifier la version, re-vérifier →
upToDate.
3. RPM — cassé, connu
- Ne pas tester : ce chemin est cassé, pas seulement non vérifié.
latest.json ne porte qu'une entrée linux-x86_64, construite à partir du .deb (release.yml:104-122). Une installation rpm reçoit donc des octets deb, que Installer::Rpm rejette après vérification de la signature magique du payload. Les artefacts .rpm continuent pourtant d'être publiés comme assets de release. Suivi en #320.
Si ça casse
L'ordre compte, et la première action ne répare pas ce qu'on croit.
-
Republier le
latest.jsonprécédent surgeneric/simpl-resultat/latest(DELETEpuisPUT, avec lePACKAGE_TOKENqu'utiliserelease.yml). Le corps est récupérable depuis les assets de la release précédente — sa copie dans le registre a été détruite par leDELETEquerelease.ymlfait avant chaque envoi. -
Comprendre ce que ça arrête, et ce que ça n'arrête pas.
check()compare strictementrelease.version > version_courante. Republier vN-1 stoppe la propagation vers ceux qui n'ont pas encore cliqué. Les utilisateurs déjà passés à la vN cassée ne se verront jamais proposer vN-1 : ils sont bloqués dessus. -
Le vrai correctif est donc une vN+1, pas un retour en arrière. Le republication de vN-1 n'achète que du temps — et si elle reste en place, elle prive aussi les utilisateurs restés en vN-1 de tout chemin de mise à jour.
Trace
- Consigner le résultat en commentaire de la release Forgejo : cibles déroulées, versions de départ et d'arrivée, invite d'authentification observée sous Linux.
- Si la checklist n'a pas été déroulée, l'écrire explicitement avec la raison, plutôt que de laisser le silence. Une étape non tracée est indiscernable d'une étape passée — c'est le mode d'échec que cette page combat.