6019f2aafa
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>
121 lines
2.8 KiB
Go
121 lines
2.8 KiB
Go
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")
|
|
}
|
|
})
|
|
}
|