package infra import ( "bufio" "fmt" "net" "strings" "testing" ) // mockSMTPServer starts a minimal SMTP server on a random port that greets // and accepts any commands. Returns the listener address and a function to stop it. func mockSMTPServer(t *testing.T) (addr string, stop func()) { t.Helper() ln, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("mock smtp listen: %v", err) } go func() { for { conn, err := ln.Accept() if err != nil { return } go handleMockSMTP(conn) } }() return ln.Addr().String(), func() { ln.Close() } } // handleMockSMTP is a minimal SMTP session handler (no TLS, no auth required). func handleMockSMTP(conn net.Conn) { defer conn.Close() w := bufio.NewWriter(conn) r := bufio.NewReader(conn) fmt.Fprintf(w, "220 mock SMTP ready\r\n") w.Flush() for { line, err := r.ReadString('\n') if err != nil { return } line = strings.TrimSpace(line) upper := strings.ToUpper(line) switch { case strings.HasPrefix(upper, "EHLO"), strings.HasPrefix(upper, "HELO"): fmt.Fprintf(w, "250 mock\r\n") case strings.HasPrefix(upper, "MAIL FROM"): fmt.Fprintf(w, "250 OK\r\n") case strings.HasPrefix(upper, "RCPT TO"): fmt.Fprintf(w, "250 OK\r\n") case strings.HasPrefix(upper, "DATA"): fmt.Fprintf(w, "354 End with .\r\n") w.Flush() // read until ".\r\n" for { dl, derr := r.ReadString('\n') if derr != nil { return } if strings.TrimSpace(dl) == "." { break } } fmt.Fprintf(w, "250 OK\r\n") case strings.HasPrefix(upper, "QUIT"): fmt.Fprintf(w, "221 Bye\r\n") w.Flush() return default: fmt.Fprintf(w, "502 Command not recognized\r\n") } w.Flush() } } func TestSMTPConnect(t *testing.T) { t.Run("conecta sin cifrado a servidor mock", func(t *testing.T) { addr, stop := mockSMTPServer(t) defer stop() host, portStr, _ := net.SplitHostPort(addr) port := 0 fmt.Sscanf(portStr, "%d", &port) cfg := SMTPConfig{ Host: host, Port: port, TLSMode: "", // no encryption } client, err := SMTPConnect(cfg) if err != nil { t.Fatalf("SMTPConnect: %v", err) } client.Quit() }) t.Run("error si el servidor no existe", func(t *testing.T) { // Use a port that is listening but immediately closes (RST) — we start // and immediately close a listener so the port is known-closed. ln2, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { t.Fatalf("setup: %v", err) } h2, p2str, _ := net.SplitHostPort(ln2.Addr().String()) ln2.Close() // now nothing listens — connections get refused p2 := 0 fmt.Sscanf(p2str, "%d", &p2) cfg := SMTPConfig{ Host: h2, Port: p2, TLSMode: "", } _, connErr := SMTPConnect(cfg) if connErr == nil { t.Error("expected error for refused connection, got nil") } }) }