Usé c.Search, que devuelve números de secuencia, y después le pedí los UIDs al resultado. go-imap devuelve nil ahí, así que la lista salía siempre vacía: el cron conectaba bien, buscaba bien, y se iba sin leer nada ni escribir un solo error en el log. Va con UIDSearch. Con eso se arreglan las dos cosas: no leía y no creaba tickets. Además: - Fetch con Peek: el correo queda marcado como leído solo si se llegó a procesar. Antes, un fallo a mitad de camino lo perdía para siempre. - "Probar conexión" ahora informa cuántos mensajes hay en la carpeta y cuántos sin leer; "Revisar buzón ahora" informa cuántos encontró y cuántos convirtió en ticket, y avisa si la lectura está desactivada. - Un correo sin asunto abre ticket como "(sin asunto)" en vez de descartarse en silencio, y los descartes quedan en el log. Los tres tests nuevos corren contra un servidor IMAP en memoria: bajar los no leídos, que marcar como leído los saque de la próxima corrida, y que bajarlos sin procesarlos no los marque. El primero falla si se vuelve a poner Search. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
136 lines
3.6 KiB
Go
136 lines
3.6 KiB
Go
package services
|
|
|
|
import (
|
|
"io"
|
|
"net"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/emersion/go-imap/v2"
|
|
"github.com/emersion/go-imap/v2/imapclient"
|
|
"github.com/emersion/go-imap/v2/imapserver"
|
|
"github.com/emersion/go-imap/v2/imapserver/imapmemserver"
|
|
)
|
|
|
|
// servidorDePrueba levanta un IMAP en memoria con un correo sin leer en INBOX.
|
|
func servidorDePrueba(t *testing.T, mensajes ...string) (*imapclient.Client, io.Closer) {
|
|
t.Helper()
|
|
mem := imapmemserver.New()
|
|
user := imapmemserver.NewUser("soporte", "secreta")
|
|
if err := user.Create("INBOX", nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
mem.AddUser(user)
|
|
|
|
srv := imapserver.New(&imapserver.Options{
|
|
NewSession: func(conn *imapserver.Conn) (imapserver.Session, *imapserver.GreetingData, error) {
|
|
return mem.NewSession(), nil, nil
|
|
},
|
|
InsecureAuth: true,
|
|
})
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
go srv.Serve(ln)
|
|
|
|
c, err := imapclient.DialInsecure(ln.Addr().String(), nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := c.Login("soporte", "secreta").Wait(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, m := range mensajes {
|
|
ac := c.Append("INBOX", int64(len(m)), nil)
|
|
if _, err := ac.Write([]byte(m)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := ac.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := ac.Wait(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
return c, ln
|
|
}
|
|
|
|
const correoCrudo = "From: Juan <juan@cliente.com>\r\n" +
|
|
"Subject: No puedo entrar\r\n" +
|
|
"Message-Id: <uno@cliente.com>\r\n" +
|
|
"Content-Type: text/plain; charset=UTF-8\r\n" +
|
|
"\r\n" +
|
|
"La contrasena no me sirve.\r\n"
|
|
|
|
// Este test existe por un bug concreto: la búsqueda usaba Search (números de
|
|
// secuencia) y después pedía UIDs, así que la lista salía siempre vacía y el
|
|
// buzón no se leía nunca, sin un solo error en el log.
|
|
func TestBajarNoLeidosDevuelveLosCorreos(t *testing.T) {
|
|
c, ln := servidorDePrueba(t, correoCrudo)
|
|
defer ln.Close()
|
|
defer c.Close()
|
|
|
|
correos, err := BajarNoLeidos(c, "INBOX")
|
|
if err != nil {
|
|
t.Fatalf("BajarNoLeidos: %v", err)
|
|
}
|
|
if len(correos) != 1 {
|
|
t.Fatalf("esperaba 1 correo sin leer, hay %d", len(correos))
|
|
}
|
|
if correos[0].UID == 0 {
|
|
t.Error("el UID vino en cero: sin él no se puede marcar como leído")
|
|
}
|
|
if got := correos[0].Correo.Subject; got != "No puedo entrar" {
|
|
t.Errorf("Subject = %q", got)
|
|
}
|
|
if !strings.Contains(correos[0].Correo.Texto, "contrasena no me sirve") {
|
|
t.Errorf("Texto = %q", correos[0].Correo.Texto)
|
|
}
|
|
}
|
|
|
|
// Y este, por el otro lado del mismo problema: si marcar como leído no funciona,
|
|
// el mismo correo abre un ticket nuevo cada 2 minutos para siempre.
|
|
func TestMarcarLeidoSacaElCorreoDeLaProximaCorrida(t *testing.T) {
|
|
c, ln := servidorDePrueba(t, correoCrudo)
|
|
defer ln.Close()
|
|
defer c.Close()
|
|
|
|
correos, err := BajarNoLeidos(c, "INBOX")
|
|
if err != nil || len(correos) != 1 {
|
|
t.Fatalf("BajarNoLeidos: %v (%d correos)", err, len(correos))
|
|
}
|
|
if err := MarcarLeido(c, correos[0].UID); err != nil {
|
|
t.Fatalf("MarcarLeido: %v", err)
|
|
}
|
|
|
|
otra, err := BajarNoLeidos(c, "INBOX")
|
|
if err != nil {
|
|
t.Fatalf("segunda pasada: %v", err)
|
|
}
|
|
if len(otra) != 0 {
|
|
t.Errorf("el correo ya leído volvió a aparecer (%d)", len(otra))
|
|
}
|
|
}
|
|
|
|
// Peek: bajarlo sin procesarlo no lo debe marcar, o un fallo a mitad de camino
|
|
// perdería el correo para siempre.
|
|
func TestBajarNoLeidosNoMarcaSolo(t *testing.T) {
|
|
c, ln := servidorDePrueba(t, correoCrudo)
|
|
defer ln.Close()
|
|
defer c.Close()
|
|
|
|
if _, err := BajarNoLeidos(c, "INBOX"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
otra, err := BajarNoLeidos(c, "INBOX")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(otra) != 1 {
|
|
t.Errorf("el correo debería seguir sin leer, hay %d", len(otra))
|
|
}
|
|
}
|
|
|
|
var _ = imap.FlagSeen
|