Files
soft_usite/pkg/services/soporte_imap_flujo_test.go
T
Lizandro GuarnizoandClaude Opus 5 5875f9e36b fix(soporte): el buzón IMAP nunca se leía — la búsqueda pedía UIDs a una búsqueda por secuencia
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>
2026-08-17 19:20:27 -05:00

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