Pular para o conteúdo
Voltar ao blog

Como testamos o MailVault: mais de 600 testes, e-mails reais, servidores reais

6 min de leitura

E-mail é uma daquelas coisas que parecem simples na superfície e não são nem um pouco. Servidores IMAP se comportam de formas diferentes. A codificação MIME tem casos extremos mais antigos que a carreira da maioria dos desenvolvedores. Tokens OAuth2 expiram em horas inconvenientes. Anexos podem estar aninhados três níveis abaixo em mensagens multipart. Se você está construindo um cliente de e-mail, ou você testa de forma obsessiva ou entrega bugs.

Hoje o MailVault tem mais de 600 testes automatizados distribuídos em quatro camadas: testes unitários em Rust, testes unitários em JavaScript, testes de integração contra servidores IMAP reais e testes de ponta a ponta que dirigem o aplicativo de desktop de verdade. Veja como tudo se encaixa.

Camada 1: testes unitários em Rust (55 testes)

O núcleo do MailVault é escrito em Rust — todas as conexões IMAP, o envio por SMTP, a análise de MIME e o gerenciamento de tokens OAuth2 rodam nativamente, não por um sidecar em Node.js. A camada Rust tem 55 testes unitários cobrindo as funções mais difíceis de acertar.

Um bom exemplo é a compressão de intervalos de UID. Quando o MailVault precisa buscar os cabeçalhos dos UIDs [1, 2, 3, 5, 6, 10], ele os comprime na string de intervalo do IMAP "1:3,5:6,10" para diminuir o tamanho do comando. Os testes cobrem entradas vazias, UIDs isolados, intervalos consecutivos, lacunas, entradas fora de ordem, duplicatas, intervalos grandes e a operação inversa (expandir os intervalos de volta em UIDs individuais). Há também testes de ida e volta, que verificam se comprimir → expandir devolve o conjunto original. Isso importa porque os comandos UID FETCH em blocos quebram se os intervalos estiverem errados.

O módulo da API do Microsoft Graph tem os próprios testes para interpretar as respostas JSON do Graph no formato interno EmailHeader do MailVault, e os testes do módulo principal cobrem a leitura de nomes de arquivo Maildir, a codificação das marcas e a busca de arquivos por UID.

Camada 2: testes unitários em JavaScript (13 suítes)

O frontend tem 13 conjuntos de testes unitários dedicados rodando no Vitest, mais testes adicionais no nível do store. Eles cobrem a lógica que fica entre o backend em Rust e a interface em React:

A interpretação de e-mails é a área mais testada. O conjunto emailParser exercita a extração de correspondentes (interpretar "John Doe <[email protected]>" separando nome e endereço), a normalização de assuntos (remover todas as combinações de Re:, Fwd:, FW:, Re[N]:), a detecção de assinatura e a identificação do nível de citação. O algoritmo de encadeamento — uma versão simplificada da especificação de threading de Jamie Zawinski — tem testes para cadeias de In-Reply-To, para o percurso do cabeçalho References, para o agrupamento alternativo por assunto e para a detecção de ciclos em cabeçalhos malformados.

Outros conjuntos testam o pipeline em segundo plano (controle de concorrência, novas tentativas com recuo exponencial, ordem em cascata entre as contas ativa e em segundo plano), o tratamento de tokens OAuth2 (renovação antecipada antes do vencimento, eliminação de tentativas de renovação simultâneas), as otimizações de sincronização delta (a árvore de decisão de quatro níveis do CONDSTORE), o fluxo de carregamento rápido (cabeçalhos em cache exibidos na hora, com a sincronização em segundo plano preenchendo as mudanças), o carregamento resiliente (degradação suave quando as conexões IMAP caem no meio de uma busca) e as transições de estado por todo o store do Zustand.

Os próprios testes do store verificam o descarte no cache LRU (o MailVault guarda os corpos das mensagens em memória, com um limite configurável), a remoção de campos (tirar o rawSource e o conteúdo dos anexos antes de guardar em cache, para economizar memória) e a estimativa do tamanho do cache.

Camada 3: testes de integração contra servidores IMAP reais

Testes unitários com mocks só contam parte da história. Os testes de integração do MailVault se conectam a servidores IMAP reais com credenciais reais e enviam e-mails reais. Mantemos duas contas de teste dedicadas em um domínio privado, e toda execução de testes de integração conversa com essas contas por TLS.

O conjunto central email.test.js roda um ciclo completo: conectar por IMAP, enviar um e-mail por SMTP (usando o Nodemailer), esperar a chegada, buscar e interpretar a mensagem com o mailparser, verificar os cabeçalhos e o corpo e então apagar a mensagem de teste na limpeza. Isso pega problemas do mundo real que os mocks deixam passar — peculiaridades da negociação TLS, extensões IMAP específicas de cada servidor, diferenças de codificação.

O conjunto de otimizações de IMAP testa os recursos avançados de protocolo do MailVault contra o servidor real: a negociação do COMPRESS=DEFLATE (o servidor realmente aceita? a sessão comprimida funciona?), a sincronização delta com CONDSTORE (o CHANGEDSINCE devolve as marcas alteradas corretas?) e as respostas compactas do ESEARCH.

O conjunto de e-mails entre contas testa cenários com várias contas: enviar da conta A para a conta B, verificar se a mensagem aparece na caixa de entrada da B, testar a propagação do estado de lido/não lido e conferir se o algoritmo de encadeamento liga a conversa corretamente nas duas contas.

A semente de 500 e-mails

Para testar de forma realista, semeamos nossas caixas postais de teste com 500 e-mails — 250 em cada direção entre as duas contas. O script de semeadura gera e-mails com assuntos realistas ("Relatório de receita do Q4 — números finais", "Re: bug no fluxo de checkout", "Fatura nº 4821 — vence em 15 de janeiro") e alterna entre cinco tipos MIME: texto simples, HTML, multipart/alternative, HTML com um anexo e HTML com vários anexos (CSV, JSON e PNG).

Cerca de um terço dos e-mails semeados fica marcado como não lido. Os anexos incluem relatórios em PDF gerados, arquivos de dados em CSV, metadados em JSON e imagens PNG. Isso nos dá uma caixa postal que se parece e se comporta como uma caixa de entrada de verdade — tipos de conteúdo misturados, estados de leitura variados, conversas encadeadas e volume suficiente para revelar problemas de desempenho.

Testes de estresse

O conjunto mailbox-stress.test.js vai além: ele injeta 500 e-mails em uma única caixa postal (passando por todos os cinco tipos MIME) e então testa se a camada IMAP do MailVault dá conta do volume. Isso pega problemas com o UID FETCH em blocos (dividimos as buscas em lotes de 200 para não esbarrar nos limites de comprimento de comando do IMAP), com a paginação e com o uso de memória sob carga.

Camada 4: testes de ponta a ponta no aplicativo real

A última camada dirige o aplicativo de desktop Tauri de verdade, usando o WebdriverIO com o WebDriver do Tauri. O aplicativo é compilado com uma flag de recurso webdriver, aberto como uma janela de desktop real, e os testes interagem com ele do mesmo jeito que uma pessoa faria — clicando em botões, digitando em campos, esperando os e-mails carregarem.

Dividimos os testes E2E em dois conjuntos. O conjunto de interface (6 arquivos de teste) testa tudo o que não precisa de conexão IMAP: painéis de configurações, interações com a barra lateral, atalhos de teclado, layout e troca de visão, e modelos de composição. Eles rodam rápido porque não tocam na rede.

O conjunto conectado (11 arquivos de teste) testa o aplicativo inteiro com contas reais — incluindo uma com mais de 17.000 e-mails. Isso não é um benchmark sintético; é uma caixa postal real, com anos de mensagens acumuladas, tipos MIME variados, conversas aninhadas e toda a bagunça de casos extremos que vem com contas de e-mail antigas. Rodar testes E2E contra uma caixa postal desse tamanho nos mantém honestos quanto à paginação, ao uso de memória e ao desempenho percebido em escala. Esses cenários incluem:

Início a frio — o aplicativo abre sem nenhum cache, adiciona uma conta e mede quanto tempo os e-mails levam para aparecer. É uma medida que acompanhamos: os primeiros e-mails visíveis a partir de um estado completamente frio.

Gerenciamento de contas — adicionar contas, trocar entre elas, conferir se o estado de cada conta (última caixa postal selecionada, lista de e-mails em cache) persiste corretamente.

Lista de e-mails e visualizador — rolar a lista de e-mails, selecionar mensagens, conferir se os corpos carregam certo (do cache, do Maildir ou do IMAP como alternativa) e verificar se os e-mails vizinhos são buscados antecipadamente.

Escrever, responder e encaminhar — abrir a janela de composição, responder a um e-mail (conferindo se o corpo citado e os destinatários estão certos) e encaminhar preservando os anexos.

Busca — testar a barra de busca na caixa postal preparada, conferindo a precisão dos resultados e o desempenho.

Operações de pasta — mover e-mails entre pastas, arquivar, apagar (o que usa o UID EXPUNGE da RFC 4315 para remover só a mensagem escolhida).

Caixa de entrada unificada — testar a visão unificada de várias contas, que junta os e-mails de todas elas em uma única lista cronológica.

Medições de desempenho — tempo de início a frio, latência de carregamento dos e-mails, desempenho do encadeamento. Elas produzem uma tabela-resumo com os tempos p50, p95 e máximo, para pegarmos regressões.

Todo teste E2E conectado tem um mecanismo de segurança: uma lista de contas de teste aprovadas. Se um teste tentar por acidente modificar uma conta que não está na lista, ele falha na hora. Isso impede que o código de teste toque em dados reais de usuários durante o desenvolvimento.

Por que isso importa

O MailVault cuida do seu e-mail. São dados pessoais e importantes. Quando você adiciona uma conta, troca de pasta, arquiva uma mensagem ou busca na caixa de entrada, tem que funcionar — não só no caminho feliz, mas quando o servidor está lento, quando a conexão cai, quando um e-mail tem cabeçalhos malformados, quando um token OAuth2 expira no meio da busca.

A abordagem em quatro camadas nos dá confiança em todos os níveis: os testes em Rust verificam as primitivas de baixo nível, os testes unitários em JS verificam a lógica da aplicação, os testes de integração verificam que os servidores reais se comportam como esperamos, e os testes E2E verificam que o conjunto funciona como aplicativo de desktop. Quando lançamos uma versão, todos eles passaram.