test: tests Go para funciones email SMTP
Tests para builders puros (build_html, build_text, with_attachment), template_render, smtp_connect y smtp_send con mock TCP server. Todos los tests pasan: 18 casos cubriendo pureza, inmutabilidad y envio SMTP. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
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")
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user