Aller au contenu
Retour au blog

Comment nous testons MailVault : plus de 600 tests, de vrais e-mails, de vrais serveurs

6 min de lecture

L'e-mail fait partie de ces choses qui paraissent simples en surface et ne le sont pas du tout. Les serveurs IMAP se comportent différemment. L'encodage MIME a des cas limites plus anciens que la carrière de la plupart des développeurs. Les jetons OAuth2 expirent au mauvais moment. Une pièce jointe peut être imbriquée à trois niveaux dans un message multipart. Quand on construit un client de messagerie, soit on teste de façon obsessionnelle, soit on livre des bugs.

MailVault compte aujourd'hui plus de 600 tests automatisés répartis sur quatre couches : tests unitaires Rust, tests unitaires JavaScript, tests d'intégration contre de vrais serveurs IMAP, et tests de bout en bout qui pilotent l'application de bureau réelle. Voici comment tout cela s'articule.

Couche 1 : tests unitaires Rust (55 tests)

Le cœur de MailVault est écrit en Rust — toutes les connexions IMAP, les envois SMTP, l'analyse MIME et la gestion des jetons OAuth2 s'exécutent nativement, pas via un sidecar Node.js. La couche Rust a 55 tests unitaires qui couvrent les fonctions les plus difficiles à écrire correctement.

Un bon exemple est la compression des plages d'UID. Quand MailVault doit récupérer les en-têtes des UID [1, 2, 3, 5, 6, 10], il les comprime en la chaîne de plages IMAP "1:3,5:6,10" pour réduire la taille de la commande. Les tests couvrent les entrées vides, les UID isolés, les plages consécutives, les trous, les entrées non triées, les doublons, les grandes plages et l'opération inverse (redéployer les plages en UID individuels). Il y a aussi des tests d'aller-retour qui vérifient que compress → expand rend bien l'ensemble d'origine. Tout cela compte, parce que les commandes UID FETCH découpées en lots cassent si les plages sont fausses.

Le module de l'API Microsoft Graph a ses propres tests, pour l'analyse des réponses JSON de Graph vers le format interne EmailHeader de MailVault, et les tests du module principal couvrent l'analyse des noms de fichiers Maildir, l'encodage des indicateurs et la recherche de fichier par UID.

Couche 2 : tests unitaires JavaScript (13 suites)

Le frontend a 13 suites de tests unitaires dédiées, qui tournent sur Vitest, plus des tests supplémentaires au niveau des stores. Elles couvrent la logique qui se trouve entre le backend Rust et l'interface React :

L'analyse des e-mails est le domaine le plus testé. La suite emailParser éprouve l'extraction des correspondants (analyser « John Doe <[email protected]> » en nom et adresse), la normalisation de l'objet (retirer toutes les combinaisons de Re:, Fwd:, FW:, Re[N]:), la détection des signatures et l'identification du niveau de citation. L'algorithme de regroupement en fils — une version simplifiée de la spécification de Jamie Zawinski — a des tests pour les chaînes In-Reply-To, le parcours de l'en-tête References, le regroupement de repli par objet et la détection de cycles pour les en-têtes malformés.

D'autres suites testent le pipeline d'arrière-plan (contrôle de la concurrence, réessai avec repli exponentiel, ordre de cascade entre le compte actif et les comptes en arrière-plan), la gestion des jetons OAuth2 (rafraîchissement anticipé avant expiration, déduplication des tentatives de rafraîchissement simultanées), les optimisations de synchronisation delta (l'arbre de décision CONDSTORE à quatre niveaux), le chargement rapide (en-têtes en cache affichés immédiatement, synchronisation en arrière-plan qui complète les changements), le chargement résilient (dégradation en douceur quand une connexion IMAP tombe en plein transfert) et les transitions d'état à travers le store Zustand.

Les tests du store, eux, vérifient l'éviction du cache LRU (MailVault garde les corps de message en mémoire, avec une limite configurable), le retrait de champs (supprimer rawSource et le contenu des pièces jointes avant la mise en cache, pour économiser de la mémoire) et l'estimation de la taille du cache.

Couche 3 : tests d'intégration contre de vrais serveurs IMAP

Des tests unitaires avec des simulacres ne disent qu'une partie de l'histoire. Les tests d'intégration de MailVault se connectent à de vrais serveurs IMAP avec de vrais identifiants et envoient de vrais e-mails. Nous maintenons deux comptes de test dédiés sur un domaine privé, et chaque exécution des tests d'intégration parle à ces comptes en TLS.

La suite principale email.test.js exécute un cycle complet : se connecter en IMAP, envoyer un e-mail en SMTP (avec Nodemailer), attendre son arrivée, le récupérer et l'analyser avec mailparser, vérifier les en-têtes et le corps, puis supprimer le message de test pendant le nettoyage. Cela attrape des problèmes réels que les mocks ratent — bizarreries de négociation TLS, extensions IMAP propres à un serveur, différences d'encodage.

La suite d'optimisations IMAP éprouve les fonctions avancées du protocole utilisées par MailVault contre le serveur réel : la négociation COMPRESS=DEFLATE (le serveur la prend-il vraiment en charge ? la session compressée fonctionne-t-elle ?), la synchronisation delta CONDSTORE (CHANGEDSINCE renvoie-t-il les bons indicateurs modifiés ?) et les réponses compactes ESEARCH.

La suite d'e-mails entre comptes éprouve les scénarios multi-comptes : envoyer du compte A au compte B, vérifier que le message apparaît dans la boîte de B, tester la propagation de l'état lu/non lu, et contrôler que l'algorithme de regroupement relie correctement la conversation entre les deux comptes.

Les 500 e-mails d'amorçage

Pour tester de façon réaliste, nous amorçons nos boîtes de test avec 500 e-mails — 250 dans chaque sens entre les deux comptes. Le script d'amorçage génère des e-mails aux objets réalistes (« Q4 Revenue Report — Final Numbers », « Re: Bug in checkout flow », « Invoice #4821 — Due January 15 ») et alterne cinq types MIME : texte brut, HTML, multipart/alternative, HTML avec une pièce jointe, et HTML avec plusieurs pièces jointes (CSV, JSON et PNG).

Environ un tiers des e-mails d'amorçage sont marqués non lus. Les pièces jointes comprennent des rapports PDF générés, des fichiers de données CSV, des métadonnées JSON et des images PNG. Cela nous donne une boîte qui ressemble et se comporte comme une vraie boîte de réception — types de contenu mélangés, états de lecture variés, conversations en fils, et assez de volume pour faire remonter les problèmes de performance.

Tests de charge

La suite mailbox-stress.test.js va plus loin : elle injecte 500 e-mails dans une même boîte, en passant par les cinq types MIME, puis vérifie que la couche IMAP de MailVault encaisse correctement le volume. Cela attrape les problèmes de UID FETCH découpé (nous scindons les récupérations en lots de 200 pour éviter les limites de longueur des commandes IMAP), de pagination et d'usage mémoire sous charge.

Couche 4 : tests de bout en bout dans l'application réelle

La dernière couche pilote la véritable application de bureau Tauri, avec WebdriverIO et le WebDriver de Tauri. L'application est compilée avec un drapeau de fonctionnalité webdriver, lancée dans une vraie fenêtre de bureau, et les tests interagissent avec elle comme le ferait un utilisateur — cliquer sur des boutons, taper dans des champs, attendre le chargement des e-mails.

Nous avons scindé les tests E2E en deux suites. La suite UI (6 fichiers de test) éprouve tout ce qui n'a pas besoin d'une connexion IMAP : panneaux de réglages, interactions avec la barre latérale, raccourcis clavier, mise en page et changement de vue, modèles de rédaction. Elle est rapide, parce qu'elle ne touche pas au réseau.

La suite connectée (11 fichiers de test) éprouve l'application complète avec de vrais comptes — dont un de plus de 17 000 e-mails. Ce n'est pas un banc d'essai synthétique : c'est une vraie boîte mail, avec des années de messages accumulés, des types MIME mélangés, des fils imbriqués et tous les cas tordus qu'entraînent des comptes de longue durée. Faire tourner les tests E2E sur une boîte de cette taille nous garde honnêtes sur la pagination, l'usage mémoire et la performance perçue à grande échelle. Ces scénarios comprennent :

Démarrage à froid — l'application se lance sans aucun cache, ajoute un compte, et l'on mesure le temps qu'il faut pour que les e-mails apparaissent. C'est un repère que nous suivons : les premiers e-mails visibles à partir d'un état complètement froid.

Gestion des comptes — ajouter des comptes, passer de l'un à l'autre, vérifier que l'état propre à chaque compte (dernière boîte sélectionnée, liste d'e-mails en cache) persiste correctement.

Liste et lecteur d'e-mails — faire défiler la liste, sélectionner des e-mails, vérifier que les corps se chargent correctement (depuis le cache, depuis Maildir, ou depuis IMAP en repli), contrôler que les e-mails voisins sont préchargés.

Rédaction, réponse et transfert — ouvrir la fenêtre de rédaction, répondre à un e-mail (en contrôlant que le corps cité et les destinataires sont bons), transférer en conservant les pièces jointes.

Recherche — éprouver la barre de recherche sur la boîte préremplie, en vérifiant la justesse des résultats et les performances.

Opérations sur les dossiers — déplacer des e-mails d'un dossier à l'autre, archiver, supprimer (ce qui passe par UID EXPUNGE, RFC 4315, pour ne retirer que le message visé).

Boîte de réception unifiée — éprouver la vue unifiée multi-comptes, qui fusionne les e-mails de tous les comptes en une seule liste chronologique.

Mesures de performance — temps de démarrage à froid, latence de chargement des e-mails, performance du regroupement en fils. Elles produisent un tableau de synthèse avec les temps p50, p95 et maximum, ce qui nous permet de repérer les régressions.

Chaque test E2E connecté a un garde-fou : une liste blanche de comptes de test approuvés. Si un test tente par accident de modifier un compte absent de cette liste, il échoue immédiatement. Cela empêche le code de test de toucher de vraies données d'utilisateur pendant le développement.

Pourquoi cela compte

MailVault manipule votre courrier. Ce sont des données personnelles et importantes. Quand vous ajoutez un compte, changez de dossier, archivez un message ou cherchez dans votre boîte, cela doit marcher — pas seulement dans le cas idéal, mais quand le serveur est lent, quand la connexion tombe, quand un e-mail a des en-têtes malformés, quand un jeton OAuth2 expire en pleine récupération.

L'approche à quatre couches nous donne de la confiance à chaque niveau : les tests Rust vérifient les primitives bas niveau, les tests unitaires JS vérifient la logique applicative, les tests d'intégration vérifient que de vrais serveurs se comportent comme prévu, et les tests E2E vérifient que l'ensemble fonctionne en tant qu'application de bureau. Quand nous publions une version, tous sont passés au vert.