`downloadAndInstall` is one call that downloads AND installs (updater.rs:723-729), and the `Finished` event is a no-op in useUpdater.ts:119-121. `READY_TO_INSTALL` is therefore dispatched only after `dpkg -i` returns, so the pkexec prompt opens while the card still reads "Téléchargement en cours…". The checklist told the tester to tick `readyToInstall` before the prompt. That ordering neutralized the polkit precondition the page exists to enforce: with no agent the app hangs in `downloading`, not in `readyToInstall`, so a tester following the checklist files "the download stalls" instead of "no polkit agent" — the exact false trace this page is written to prevent. Also states plainly that at `readyToInstall` the .deb is already on disk and the button only calls `relaunch()`; the label misleads on this target. Three smaller corrections from the same review: - The update triggers are four manual sites, not two: UpdateCard idle / upToDate refresh / error retry, plus ErrorPage.tsx:82 — which only detects and offers no download path. The load-bearing claim, "no automatic check in the app", was already right. - "Si ça casse" now spells out that DELETE and PUT hit different API prefixes (/api/v1/packages/ vs /api/packages/, release.yml:217-219, which carries a comment about exactly this). Replaying both against one URL 404s at the worst possible moment. - The SKILL.md changelog entry moves back into date order. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
9.1 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 — sur l'écran de téléchargement, pas surreadyToInstall(voir §2, c'est le piège qui produit le mauvais rapport de bug). 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 : les quatre déclencheurs sont manuels — ce bouton, l'icône rafraîchir de l'état upToDate, le « réessayer » de l'état error, et celui de la page d'erreur, qui détecte seulement et n'offre aucun chemin de téléchargement.
-
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. -
Pendant que la carte affiche encore « Téléchargement en cours… », une invite d'authentification apparaît. Noter laquelle : polkit (nominal), zenity/kdialog, ou rien du tout (repli
sudo→ l'app reste figée sur l'écran de téléchargement).Invite observée :
________________C'est bien le moment nominal, et c'est contre-intuitif :
downloadAndInstalltélécharge et installe dans le même appel (updater.rs:723-729), et l'événementFinishedest un no-op côté UI (useUpdater.ts:119-121). L'écran reste donc surdownloadingpendant tout ledpkg -i. Un testeur qui guette l'invite aprèsreadyToInstallne la verra jamais arriver au bon moment : il rapportera « le téléchargement bloque » là où le vrai diagnostic est « pas d'agent polkit ». -
Authentifier → l'app reste vivante et passe en
readyToInstall(« Mise à jour prête à installer » + bouton « Installer et redémarrer »).À ce stade le
.debest déjà installé sur le disque :installAndRestartne fait querelaunch(). Le libellé du bouton ment sur cette cible — qui s'arrête là croit que rien n'a été installé, et qui voit la version changer après le clic attribue l'installation au clic. -
Cliquer → état
installing, l'app redémarre. -
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.Les deux verbes ne visent pas le même préfixe d'API, et
release.yml:217-219porte un commentaire explicite là-dessus — le piège est déjà tombé une fois :DELETE {serveur}/api/v1/packages/{owner}/generic/simpl-resultat/latest PUT {serveur}/api/packages/{owner}/generic/simpl-resultat/latestRejouer les deux sur la même URL donne un 404 sur l'un des deux, au moment où on peut le moins se permettre de le débugger.
-
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.