Add *testing.T to simpleSMTPServer and logging improvements

Pass `*testing.T` to `simpleSMTPServer` for enhanced test logging and helper methods. This allows better integration with the testing framework, converting standard log outputs to `t.Logf` for improved test diagnostics and error reporting.
This commit is contained in:
Winni Neessen 2024-10-24 00:11:58 +02:00
parent 7f3cd8dc38
commit 2710250baa
Signed by: wneessen
GPG key ID: 385AC9889632126E

View file

@ -1138,7 +1138,7 @@ func TestClient_SetDebugLog(t *testing.T) {
serverPort := int(TestServerPortBase + PortAdder.Load()) serverPort := int(TestServerPortBase + PortAdder.Load())
featureSet := "250-AUTH PLAIN\r\n250-8BITMIME\r\n250-DSN\r\n250 SMTPUTF8" featureSet := "250-AUTH PLAIN\r\n250-8BITMIME\r\n250-DSN\r\n250 SMTPUTF8"
go func() { go func() {
if err := simpleSMTPServer(ctx, &serverProps{FeatureSet: featureSet, ListenPort: serverPort}); err != nil { if err := simpleSMTPServer(ctx, t, &serverProps{FeatureSet: featureSet, ListenPort: serverPort}); err != nil {
t.Errorf("failed to start test server: %s", err) t.Errorf("failed to start test server: %s", err)
return return
} }
@ -1527,7 +1527,7 @@ func TestClient_Close(t *testing.T) {
serverPort := int(TestServerPortBase + PortAdder.Load()) serverPort := int(TestServerPortBase + PortAdder.Load())
featureSet := "250-AUTH PLAIN\r\n250-8BITMIME\r\n250-DSN\r\n250 SMTPUTF8" featureSet := "250-AUTH PLAIN\r\n250-8BITMIME\r\n250-DSN\r\n250 SMTPUTF8"
go func() { go func() {
if err := simpleSMTPServer(ctx, &serverProps{ if err := simpleSMTPServer(ctx, t, &serverProps{
FeatureSet: featureSet, FeatureSet: featureSet,
ListenPort: serverPort, ListenPort: serverPort,
}); err != nil { }); err != nil {
@ -1561,7 +1561,7 @@ func TestClient_Close(t *testing.T) {
serverPort := int(TestServerPortBase + PortAdder.Load()) serverPort := int(TestServerPortBase + PortAdder.Load())
featureSet := "250-AUTH PLAIN\r\n250-8BITMIME\r\n250-DSN\r\n250 SMTPUTF8" featureSet := "250-AUTH PLAIN\r\n250-8BITMIME\r\n250-DSN\r\n250 SMTPUTF8"
go func() { go func() {
if err := simpleSMTPServer(ctx, &serverProps{ if err := simpleSMTPServer(ctx, t, &serverProps{
FeatureSet: featureSet, FeatureSet: featureSet,
ListenPort: serverPort, ListenPort: serverPort,
}); err != nil { }); err != nil {
@ -1598,7 +1598,7 @@ func TestClient_Close(t *testing.T) {
serverPort := int(TestServerPortBase + PortAdder.Load()) serverPort := int(TestServerPortBase + PortAdder.Load())
featureSet := "250-AUTH PLAIN\r\n250-8BITMIME\r\n250-DSN\r\n250 SMTPUTF8" featureSet := "250-AUTH PLAIN\r\n250-8BITMIME\r\n250-DSN\r\n250 SMTPUTF8"
go func() { go func() {
if err := simpleSMTPServer(ctx, &serverProps{ if err := simpleSMTPServer(ctx, t, &serverProps{
FailOnQuit: true, FailOnQuit: true,
FeatureSet: featureSet, FeatureSet: featureSet,
ListenPort: serverPort, ListenPort: serverPort,
@ -1635,7 +1635,7 @@ func TestClient_DialWithContext(t *testing.T) {
serverPort := int(TestServerPortBase + PortAdder.Load()) serverPort := int(TestServerPortBase + PortAdder.Load())
featureSet := "250-AUTH PLAIN\r\n250-8BITMIME\r\n250-DSN\r\n250 SMTPUTF8" featureSet := "250-AUTH PLAIN\r\n250-8BITMIME\r\n250-DSN\r\n250 SMTPUTF8"
go func() { go func() {
if err := simpleSMTPServer(ctx, &serverProps{FeatureSet: featureSet, ListenPort: serverPort}); err != nil { if err := simpleSMTPServer(ctx, t, &serverProps{FeatureSet: featureSet, ListenPort: serverPort}); err != nil {
t.Errorf("failed to start test server: %s", err) t.Errorf("failed to start test server: %s", err)
return return
} }
@ -1788,7 +1788,7 @@ func TestClient_DialWithContext(t *testing.T) {
failServerPort := int(TestServerPortBase + PortAdder.Load()) failServerPort := int(TestServerPortBase + PortAdder.Load())
failFeatureSet := "250-AUTH PLAIN\r\n250-8BITMIME\r\n250-DSN\r\n250 SMTPUTF8" failFeatureSet := "250-AUTH PLAIN\r\n250-8BITMIME\r\n250-DSN\r\n250 SMTPUTF8"
go func() { go func() {
if err := simpleSMTPServer(ctxFail, &serverProps{ if err := simpleSMTPServer(ctxFail, t, &serverProps{
FailOnHelo: true, FailOnHelo: true,
FeatureSet: failFeatureSet, FeatureSet: failFeatureSet,
ListenPort: failServerPort, ListenPort: failServerPort,
@ -1836,7 +1836,7 @@ func TestClient_DialWithContext(t *testing.T) {
tlsServerPort := int(TestServerPortBase + PortAdder.Load()) tlsServerPort := int(TestServerPortBase + PortAdder.Load())
tlsFeatureSet := "250-AUTH PLAIN\r\n250-8BITMIME\r\n250-DSN\r\n250-STARTTLS\r\n250 SMTPUTF8" tlsFeatureSet := "250-AUTH PLAIN\r\n250-8BITMIME\r\n250-DSN\r\n250-STARTTLS\r\n250 SMTPUTF8"
go func() { go func() {
if err := simpleSMTPServer(ctxTLS, &serverProps{ if err := simpleSMTPServer(ctxTLS, t, &serverProps{
FeatureSet: tlsFeatureSet, FeatureSet: tlsFeatureSet,
ListenPort: tlsServerPort, ListenPort: tlsServerPort,
}); err != nil { }); err != nil {
@ -1866,7 +1866,7 @@ func TestClient_DialWithContext(t *testing.T) {
tlsServerPort := int(TestServerPortBase + PortAdder.Load()) tlsServerPort := int(TestServerPortBase + PortAdder.Load())
tlsFeatureSet := "250-AUTH PLAIN\r\n250-8BITMIME\r\n250-DSN\r\n250 SMTPUTF8" tlsFeatureSet := "250-AUTH PLAIN\r\n250-8BITMIME\r\n250-DSN\r\n250 SMTPUTF8"
go func() { go func() {
if err := simpleSMTPServer(ctxTLS, &serverProps{ if err := simpleSMTPServer(ctxTLS, t, &serverProps{
FeatureSet: tlsFeatureSet, FeatureSet: tlsFeatureSet,
ListenPort: tlsServerPort, ListenPort: tlsServerPort,
}); err != nil { }); err != nil {
@ -1896,7 +1896,7 @@ func TestClient_DialWithContext(t *testing.T) {
sslServerPort := int(TestServerPortBase + PortAdder.Load()) sslServerPort := int(TestServerPortBase + PortAdder.Load())
sslFeatureSet := "250-AUTH PLAIN\r\n250-8BITMIME\r\n250-DSN\r\n250 SMTPUTF8" sslFeatureSet := "250-AUTH PLAIN\r\n250-8BITMIME\r\n250-DSN\r\n250 SMTPUTF8"
go func() { go func() {
if err := simpleSMTPServer(ctxSSL, &serverProps{ if err := simpleSMTPServer(ctxSSL, t, &serverProps{
SSLListener: true, SSLListener: true,
FeatureSet: sslFeatureSet, FeatureSet: sslFeatureSet,
ListenPort: sslServerPort, ListenPort: sslServerPort,
@ -3771,7 +3771,8 @@ type serverProps struct {
// simpleSMTPServer starts a simple TCP server that resonds to SMTP commands. // simpleSMTPServer starts a simple TCP server that resonds to SMTP commands.
// The provided featureSet represents in what the server responds to EHLO command // The provided featureSet represents in what the server responds to EHLO command
// failReset controls if a RSET succeeds // failReset controls if a RSET succeeds
func simpleSMTPServer(ctx context.Context, props *serverProps) error { func simpleSMTPServer(ctx context.Context, t *testing.T, props *serverProps) error {
t.Helper()
if props == nil { if props == nil {
return fmt.Errorf("no server properties provided") return fmt.Errorf("no server properties provided")
} }
@ -3796,8 +3797,7 @@ func simpleSMTPServer(ctx context.Context, props *serverProps) error {
defer func() { defer func() {
if err := listener.Close(); err != nil { if err := listener.Close(); err != nil {
fmt.Printf("unable to close listener: %s\n", err) t.Logf("failed to close listener: %s", err)
os.Exit(1)
} }
}() }()
@ -3814,37 +3814,37 @@ func simpleSMTPServer(ctx context.Context, props *serverProps) error {
} }
return fmt.Errorf("unable to accept connection: %w", err) return fmt.Errorf("unable to accept connection: %w", err)
} }
handleTestServerConnection(connection, props) handleTestServerConnection(connection, t, props)
} }
} }
} }
func handleTestServerConnection(connection net.Conn, props *serverProps) { func handleTestServerConnection(connection net.Conn, t *testing.T, props *serverProps) {
defer func() { t.Helper()
t.Cleanup(func() {
if err := connection.Close(); err != nil { if err := connection.Close(); err != nil {
fmt.Printf("unable to close connection: %s\n", err) t.Logf("failed to close connection: %s", err)
} }
}() })
reader := bufio.NewReader(connection) reader := bufio.NewReader(connection)
writer := bufio.NewWriter(connection) writer := bufio.NewWriter(connection)
writeLine := func(data string) error { writeLine := func(data string) {
_, err := writer.WriteString(data + "\r\n") _, err := writer.WriteString(data + "\r\n")
if err != nil { if err != nil {
return fmt.Errorf("unable to write line: %w", err) t.Logf("failed to write line: %s", err)
}
if err = writer.Flush(); err != nil {
t.Logf("failed to flush writer: %s", err)
} }
return writer.Flush()
} }
writeOK := func() { writeOK := func() {
_ = writeLine("250 2.0.0 OK") writeLine("250 2.0.0 OK")
} }
if !props.IsTLS { if !props.IsTLS {
if err := writeLine("220 go-mail test server ready ESMTP"); err != nil { writeLine("220 go-mail test server ready ESMTP")
fmt.Printf("unable to write to client: %s\n", err)
return
}
} }
for { for {
@ -3859,23 +3859,22 @@ func handleTestServerConnection(connection net.Conn, props *serverProps) {
switch { switch {
case strings.HasPrefix(data, "EHLO"), strings.HasPrefix(data, "HELO"): case strings.HasPrefix(data, "EHLO"), strings.HasPrefix(data, "HELO"):
if len(strings.Split(data, " ")) != 2 { if len(strings.Split(data, " ")) != 2 {
_ = writeLine("501 Syntax: EHLO hostname") writeLine("501 Syntax: EHLO hostname")
break break
} }
if props.FailOnHelo { if props.FailOnHelo {
_ = writeLine("500 5.5.2 Error: fail on HELO") writeLine("500 5.5.2 Error: fail on HELO")
break break
} }
if err = writeLine("250-localhost.localdomain\r\n" + props.FeatureSet); err != nil { writeLine("250-localhost.localdomain\r\n" + props.FeatureSet)
break break
}
case strings.HasPrefix(data, "MAIL FROM:"): case strings.HasPrefix(data, "MAIL FROM:"):
from := strings.TrimPrefix(data, "MAIL FROM:") from := strings.TrimPrefix(data, "MAIL FROM:")
from = strings.ReplaceAll(from, "BODY=8BITMIME", "") from = strings.ReplaceAll(from, "BODY=8BITMIME", "")
from = strings.ReplaceAll(from, "SMTPUTF8", "") from = strings.ReplaceAll(from, "SMTPUTF8", "")
from = strings.TrimSpace(from) from = strings.TrimSpace(from)
if !strings.EqualFold(from, "<valid-from@domain.tld>") { if !strings.EqualFold(from, "<valid-from@domain.tld>") {
_ = writeLine(fmt.Sprintf("503 5.1.2 Invalid from: %s", from)) writeLine(fmt.Sprintf("503 5.1.2 Invalid from: %s", from))
break break
} }
writeOK() writeOK()
@ -3883,24 +3882,24 @@ func handleTestServerConnection(connection net.Conn, props *serverProps) {
to := strings.TrimPrefix(data, "RCPT TO:") to := strings.TrimPrefix(data, "RCPT TO:")
to = strings.TrimSpace(to) to = strings.TrimSpace(to)
if !strings.EqualFold(to, "<valid-to@domain.tld>") { if !strings.EqualFold(to, "<valid-to@domain.tld>") {
_ = writeLine(fmt.Sprintf("500 5.1.2 Invalid to: %s", to)) writeLine(fmt.Sprintf("500 5.1.2 Invalid to: %s", to))
break break
} }
writeOK() writeOK()
case strings.HasPrefix(data, "AUTH XOAUTH2"): case strings.HasPrefix(data, "AUTH XOAUTH2"):
auth := strings.TrimPrefix(data, "AUTH XOAUTH2 ") auth := strings.TrimPrefix(data, "AUTH XOAUTH2 ")
if !strings.EqualFold(auth, "dXNlcj11c2VyAWF1dGg9QmVhcmVyIHRva2VuAQE=") { if !strings.EqualFold(auth, "dXNlcj11c2VyAWF1dGg9QmVhcmVyIHRva2VuAQE=") {
_ = writeLine("535 5.7.8 Error: authentication failed") writeLine("535 5.7.8 Error: authentication failed")
break break
} }
_ = writeLine("235 2.7.0 Authentication successful") writeLine("235 2.7.0 Authentication successful")
case strings.HasPrefix(data, "AUTH PLAIN"): case strings.HasPrefix(data, "AUTH PLAIN"):
auth := strings.TrimPrefix(data, "AUTH PLAIN ") auth := strings.TrimPrefix(data, "AUTH PLAIN ")
if !strings.EqualFold(auth, "AHRvbmlAdGVzdGVyLmNvbQBWM3J5UzNjcjN0Kw==") { if !strings.EqualFold(auth, "AHRvbmlAdGVzdGVyLmNvbQBWM3J5UzNjcjN0Kw==") {
_ = writeLine("535 5.7.8 Error: authentication failed") writeLine("535 5.7.8 Error: authentication failed")
break break
} }
_ = writeLine("235 2.7.0 Authentication successful") writeLine("235 2.7.0 Authentication successful")
case strings.HasPrefix(data, "AUTH LOGIN"): case strings.HasPrefix(data, "AUTH LOGIN"):
var username, password string var username, password string
userResp := "VXNlcm5hbWU6" userResp := "VXNlcm5hbWU6"
@ -3921,7 +3920,7 @@ func handleTestServerConnection(connection net.Conn, props *serverProps) {
userResp = "" userResp = ""
passResp = "" passResp = ""
} }
_ = writeLine("334 " + userResp) writeLine("334 " + userResp)
ddata, derr := reader.ReadString('\n') ddata, derr := reader.ReadString('\n')
if derr != nil { if derr != nil {
@ -3930,7 +3929,7 @@ func handleTestServerConnection(connection net.Conn, props *serverProps) {
} }
ddata = strings.TrimSpace(ddata) ddata = strings.TrimSpace(ddata)
username = ddata username = ddata
_ = writeLine("334 " + passResp) writeLine("334 " + passResp)
ddata, derr = reader.ReadString('\n') ddata, derr = reader.ReadString('\n')
if derr != nil { if derr != nil {
@ -3942,29 +3941,29 @@ func handleTestServerConnection(connection net.Conn, props *serverProps) {
if !strings.EqualFold(username, "dG9uaUB0ZXN0ZXIuY29t") || if !strings.EqualFold(username, "dG9uaUB0ZXN0ZXIuY29t") ||
!strings.EqualFold(password, "VjNyeVMzY3IzdCs=") { !strings.EqualFold(password, "VjNyeVMzY3IzdCs=") {
_ = writeLine("535 5.7.8 Error: authentication failed") writeLine("535 5.7.8 Error: authentication failed")
break break
} }
_ = writeLine("235 2.7.0 Authentication successful") writeLine("235 2.7.0 Authentication successful")
case strings.EqualFold(data, "DATA"): case strings.EqualFold(data, "DATA"):
_ = writeLine("354 End data with <CR><LF>.<CR><LF>") writeLine("354 End data with <CR><LF>.<CR><LF>")
for { for {
ddata, derr := reader.ReadString('\n') ddata, derr := reader.ReadString('\n')
if derr != nil { if derr != nil {
fmt.Printf("failed to read DATA data from connection: %s\n", derr) t.Logf("failed to read data from connection: %s", derr)
break break
} }
ddata = strings.TrimSpace(ddata) ddata = strings.TrimSpace(ddata)
if strings.EqualFold(ddata, "DATA write should fail") { if strings.EqualFold(ddata, "DATA write should fail") {
_ = writeLine("500 5.0.0 Error during DATA transmission") writeLine("500 5.0.0 Error during DATA transmission")
break break
} }
if ddata == "." { if ddata == "." {
if strings.Contains(datastring, "DATA close should fail") { if strings.Contains(datastring, "DATA close should fail") {
_ = writeLine("500 5.0.0 Error during DATA closing") writeLine("500 5.0.0 Error during DATA closing")
break break
} }
_ = writeLine("250 2.0.0 Ok: queued as 1234567890") writeLine("250 2.0.0 Ok: queued as 1234567890")
break break
} }
datastring += ddata + "\n" datastring += ddata + "\n"
@ -3974,34 +3973,34 @@ func handleTestServerConnection(connection net.Conn, props *serverProps) {
writeOK() writeOK()
case strings.EqualFold(data, "rset"): case strings.EqualFold(data, "rset"):
if props.FailOnReset { if props.FailOnReset {
_ = writeLine("500 5.1.2 Error: reset failed") writeLine("500 5.1.2 Error: reset failed")
break break
} }
writeOK() writeOK()
case strings.EqualFold(data, "quit"): case strings.EqualFold(data, "quit"):
if props.FailOnQuit { if props.FailOnQuit {
_ = writeLine("500 5.1.2 Error: quit failed") writeLine("500 5.1.2 Error: quit failed")
break break
} }
_ = writeLine("221 2.0.0 Bye") writeLine("221 2.0.0 Bye")
return return
case strings.EqualFold(data, "starttls"): case strings.EqualFold(data, "starttls"):
if props.FailOnSTARTTLS { if props.FailOnSTARTTLS {
_ = writeLine("500 5.1.2 Error: starttls failed") writeLine("500 5.1.2 Error: starttls failed")
break break
} }
keypair, err := tls.X509KeyPair(localhostCert, localhostKey) keypair, err := tls.X509KeyPair(localhostCert, localhostKey)
if err != nil { if err != nil {
_ = writeLine("500 5.1.2 Error: starttls failed - " + err.Error()) writeLine("500 5.1.2 Error: starttls failed - " + err.Error())
break break
} }
_ = writeLine("220 Ready to start TLS") writeLine("220 Ready to start TLS")
tlsConfig := &tls.Config{Certificates: []tls.Certificate{keypair}} tlsConfig := &tls.Config{Certificates: []tls.Certificate{keypair}}
connection = tls.Server(connection, tlsConfig) connection = tls.Server(connection, tlsConfig)
props.IsTLS = true props.IsTLS = true
handleTestServerConnection(connection, props) handleTestServerConnection(connection, t, props)
default: default:
_ = writeLine("500 5.5.2 Error: bad syntax") writeLine("500 5.5.2 Error: bad syntax")
} }
} }
} }