Come testiamo MailVault: oltre 600 test, email vere, server veri
La posta elettronica è una di quelle cose che in superficie sembrano semplici e non lo sono affatto. I server IMAP si comportano in modi diversi. La codifica MIME ha casi limite più vecchi della carriera di quasi tutti gli sviluppatori. I token OAuth2 scadono nei momenti peggiori. Gli allegati possono essere annidati tre livelli dentro messaggi multipart. Se costruisci un client di posta, o testi in modo ossessivo o rilasci bug.
MailVault ha oggi più di 600 test automatici distribuiti su quattro livelli: unit test in Rust, unit test in JavaScript, test di integrazione contro server IMAP reali e test end-to-end che pilotano la vera app desktop. Ecco come si incastra tutto.
Livello 1: unit test in Rust (55 test)
Il cuore di MailVault è scritto in Rust — tutte le connessioni IMAP, l'invio SMTP, l'analisi MIME e la gestione dei token OAuth2 girano in modo nativo, non attraverso un sidecar Node.js. Il livello Rust ha 55 unit test che coprono le funzioni più difficili da azzeccare.
Un buon esempio è la compressione degli intervalli di UID. Quando MailVault deve recuperare le intestazioni per gli UID [1, 2, 3, 5, 6, 10], li comprime nella stringa di intervalli IMAP "1:3,5:6,10" per ridurre al minimo la dimensione del comando. I test coprono input vuoti, UID singoli, intervalli consecutivi, buchi, input non ordinati, duplicati, intervalli grandi e l'operazione inversa (riespandere gli intervalli in singoli UID). Ci sono anche test di andata e ritorno che verificano che compress → expand restituisca l'insieme di partenza. Contano perché i comandi UID FETCH suddivisi in blocchi si rompono se gli intervalli sono sbagliati.
Il modulo della Microsoft Graph API ha i suoi test per l'analisi delle risposte JSON di Graph nel formato interno EmailHeader di MailVault, e i test del modulo principale coprono l'analisi dei nomi dei file Maildir, la codifica dei contrassegni e la ricerca dei file per UID.
Livello 2: unit test JavaScript (13 suite di test)
Il frontend ha 13 suite di unit test dedicate che girano su Vitest, più ulteriori test a livello di store. Coprono la logica che sta tra il backend Rust e l'interfaccia React:
L'analisi delle email è l'area testata più a fondo. La suite emailParser mette alla prova l'estrazione dei corrispondenti (analizzare "John Doe <[email protected]>" ricavandone nome e indirizzo), la normalizzazione dell'oggetto (togliere tutte le combinazioni di Re:, Fwd:, FW:, Re[N]:), il rilevamento della firma e l'identificazione dei livelli di citazione. L'algoritmo di threading — una versione semplificata della specifica di Jamie Zawinski — ha test per le catene In-Reply-To, per il percorso lungo l'intestazione References, per il raggruppamento di ripiego basato sull'oggetto e per il rilevamento dei cicli con intestazioni malformate.
Altre suite testano la pipeline in background (controllo della concorrenza, nuovi tentativi con backoff esponenziale, ordine a cascata tra account attivi e account in background), la gestione dei token OAuth2 (rinnovo anticipato prima della scadenza, deduplicazione dei rinnovi concorrenti), le ottimizzazioni della sincronizzazione delta (l'albero decisionale CONDSTORE a quattro livelli), il flusso di caricamento rapido (intestazioni in cache mostrate subito, sincronizzazione in background che riempie le differenze), il caricamento resiliente (degradazione controllata quando le connessioni IMAP cadono a metà recupero) e le transizioni di stato attraverso lo store Zustand.
I test dello store verificano a loro volta l'espulsione dalla cache LRU (MailVault tiene in memoria i corpi delle email con un limite configurabile), la rimozione dei campi (togliere rawSource e il contenuto degli allegati prima di mettere in cache, per risparmiare memoria) e la stima della dimensione della cache.
Livello 3: test di integrazione contro server IMAP reali
Gli unit test con i mock possono dirti solo fino a un certo punto. I test di integrazione di MailVault si collegano a server IMAP reali con credenziali reali e inviano email reali. Manteniamo due account di prova dedicati su un dominio privato, e ogni esecuzione dei test di integrazione parla con quegli account via TLS.
La suite principale email.test.js esegue un ciclo completo: si collega via IMAP, invia un'email via SMTP (usando Nodemailer), aspetta che arrivi, la recupera e la analizza con mailparser, verifica intestazioni e corpo, poi elimina il messaggio di prova durante la pulizia. Così si intercettano problemi del mondo reale che i mock si perdono — stranezze nella negoziazione TLS, estensioni IMAP specifiche di un server, differenze di codifica.
La suite sulle ottimizzazioni IMAP mette alla prova le funzioni di protocollo avanzate di MailVault contro il server reale: la negoziazione di COMPRESS=DEFLATE (il server la supporta davvero? la sessione compressa funziona?), la sincronizzazione delta CONDSTORE (CHANGEDSINCE restituisce i contrassegni cambiati giusti?) e le risposte compatte ESEARCH.
La suite sulle email tra account mette alla prova gli scenari con più account: inviare dall'account A all'account B, verificare che il messaggio compaia nella posta in arrivo di B, testare la propagazione dello stato letto/da leggere e controllare che l'algoritmo di threading colleghi correttamente la conversazione su entrambi gli account.
Il seed da 500 email
Per testare in modo realistico, riempiamo le caselle di prova con 500 email — 250 per ciascuna direzione tra i due account. Lo script di seeding genera email con oggetti realistici ("Report ricavi Q4 — numeri definitivi", "Re: bug nel flusso di checkout", "Fattura n. 4821 — scadenza 15 gennaio") e alterna cinque tipi MIME: testo semplice, HTML, multipart/alternative, HTML con un allegato e HTML con più allegati (CSV, JSON e PNG).
Circa un terzo delle email caricate è segnato come da leggere. Gli allegati comprendono report PDF generati, file di dati CSV, metadati JSON e immagini PNG. Questo ci dà una casella che sembra e si comporta come una casella vera — tipi di contenuto misti, stati di lettura vari, conversazioni in thread e volume sufficiente a far emergere i problemi di prestazioni.
Test di stress
La suite mailbox-stress.test.js va oltre: inietta 500 email in un'unica casella di posta (passando in rassegna tutti e cinque i tipi MIME), poi verifica che il livello IMAP di MailVault regga correttamente il volume. Così si intercettano i problemi con i comandi UID FETCH suddivisi in blocchi (dividiamo i recuperi in lotti da 200 per evitare i limiti di lunghezza dei comandi IMAP), con la paginazione e con l'uso della memoria sotto carico.
Livello 4: test end-to-end nell'app vera
L'ultimo livello guida la vera app desktop Tauri usando WebdriverIO con il WebDriver di Tauri. L'app viene compilata con un feature flag webdriver, avviata come vera finestra desktop, e i test ci interagiscono come farebbe una persona — facendo clic sui pulsanti, scrivendo nei campi, aspettando che le email si carichino.
Abbiamo diviso i test E2E in due suite. La suite UI (6 file di test) verifica tutto ciò che non richiede una connessione IMAP: i pannelli delle impostazioni, le interazioni con la barra laterale, le scorciatoie da tastiera, il layout e il cambio di vista, e i modelli di composizione. Girano in fretta perché non toccano la rete.
La suite connessa (11 file di test) verifica l'app completa con account reali — compreso uno con oltre 17.000 email. Non è un benchmark artificiale: è una casella di posta vera, con anni di messaggi accumulati, tipi MIME misti, thread annidati e tutti i casi limite disordinati che arrivano con gli account di posta di lunga data. Eseguire i test E2E su una casella di quella dimensione ci tiene onesti su paginazione, uso della memoria e prestazioni percepite su larga scala. Questi scenari comprendono:
Avvio a freddo — l'app parte senza cache, aggiunge un account e misura quanto ci mette a far comparire le email. È un parametro che teniamo d'occhio: le prime email visibili partendo da uno stato completamente freddo.
Gestione degli account — aggiungere account, passare dall'uno all'altro, verificare che lo stato di ciascuno (ultima casella selezionata, elenco delle email in cache) si conservi correttamente.
Elenco delle email e visualizzatore — scorrere l'elenco delle email, selezionarle, verificare che i corpi si carichino correttamente (dalla cache, dal Maildir o da IMAP come ripiego), controllare che le email adiacenti vengano precaricate.
Scrittura, risposta e inoltro — aprire la finestra di composizione, rispondere a un'email (controllando che il corpo citato e i destinatari siano corretti), inoltrare mantenendo gli allegati.
Ricerca — provare la barra di ricerca sulla casella di posta popolata, verificando l'esattezza dei risultati e le prestazioni.
Operazioni sulle cartelle — spostare le email tra cartelle, archiviare, eliminare (usando UID EXPUNGE dell'RFC 4315 per rimuovere solo il messaggio indicato).
Casella unificata — provare la vista unificata multi-account che unisce le email di tutti gli account in un unico elenco cronologico.
Misure di prestazione — tempi di avvio a freddo, latenza di caricamento delle email, prestazioni del threading. Producono una tabella riassuntiva con i tempi p50, p95 e massimi, così possiamo accorgerci delle regressioni.
Ogni test E2E connesso ha un meccanismo di sicurezza: una whitelist di account di prova approvati. Se un test prova per sbaglio a modificare un account che non è nella whitelist, fallisce subito. Così il codice di test non tocca mai dati reali di utenti durante lo sviluppo.
Perché tutto questo conta
MailVault ha in mano la tua posta. Sono dati personali e importanti. Quando aggiungi un account, cambi cartella, archivi un messaggio o cerchi nella casella, deve funzionare — non solo nel percorso ideale, ma anche quando il server è lento, quando la connessione cade, quando un'email ha intestazioni malformate, quando un token OAuth2 scade a metà recupero.
L'approccio a quattro livelli ci dà fiducia a ogni livello: i test Rust verificano le primitive di basso livello, gli unit test JS verificano la logica applicativa, i test di integrazione verificano che i server reali si comportino come ci aspettiamo e i test E2E verificano che il tutto funzioni come app desktop. Quando rilasciamo una versione, tutti questi test sono passati.