From b3b7c1d52708e3e3d9d5480c5d4fedb1a11fc060 Mon Sep 17 00:00:00 2001 From: Kharec Date: Sun, 15 Feb 2026 12:04:06 +0100 Subject: [PATCH] test: health check now supports smtp so we test it --- internal/health/smtp_test.go | 337 +++++++++++++++++++++++++++++++++++ 1 file changed, 337 insertions(+) create mode 100644 internal/health/smtp_test.go diff --git a/internal/health/smtp_test.go b/internal/health/smtp_test.go new file mode 100644 index 0000000..9c19dfe --- /dev/null +++ b/internal/health/smtp_test.go @@ -0,0 +1,337 @@ +package health + +import ( + "context" + "net" + "testing" + "time" +) + +func TestSMTPChecker_Check_NoAuth(t *testing.T) { + config := SMTPConfig{ + Host: "smtp.example.com", + Port: 25, + } + + checker := NewSMTPChecker(config) + + if checker.config.Username != "" { + t.Error("expected empty username") + } + if checker.config.Password != "" { + t.Error("expected empty password") + } +} + +func TestSMTPChecker_Check_InvalidPort(t *testing.T) { + config := SMTPConfig{ + Host: "localhost", + Port: 99999, + } + + checker := NewSMTPChecker(config) + ctx := context.Background() + + result := checker.Check(ctx) + + if result.Status != StatusDegraded { + t.Errorf("expected degraded status for invalid port, got %s", result.Status) + } +} + +func TestSMTPChecker_Check_ConnectionRefused(t *testing.T) { + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("failed to create listener: %v", err) + } + port := listener.Addr().(*net.TCPAddr).Port + listener.Close() + + config := SMTPConfig{ + Host: "127.0.0.1", + Port: port, + } + + checker := NewSMTPChecker(config) + ctx := context.Background() + + result := checker.Check(ctx) + + if result.Status != StatusDegraded { + t.Errorf("expected degraded status for connection refused, got %s", result.Status) + } +} + +func TestSMTPChecker_Check_WithMockServer(t *testing.T) { + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("failed to create listener: %v", err) + } + defer listener.Close() + + port := listener.Addr().(*net.TCPAddr).Port + + serverDone := make(chan bool) + go func() { + defer close(serverDone) + conn, err := listener.Accept() + if err != nil { + return + } + defer conn.Close() + + conn.Write([]byte("220 test.example.com ESMTP ready\r\n")) + + buf := make([]byte, 1024) + n, _ := conn.Read(buf) + if n > 0 { + conn.Write([]byte("250-test.example.com\r\n250-STARTTLS\r\n250 AUTH PLAIN\r\n")) + } + + n, _ = conn.Read(buf) + if n > 0 { + conn.Write([]byte("220 2.0.0 Ready to start TLS\r\n")) + } + + n, _ = conn.Read(buf) + if n > 0 { + conn.Write([]byte("235 2.7.0 Authentication successful\r\n")) + } + + n, _ = conn.Read(buf) + if n > 0 { + conn.Write([]byte("221 2.0.0 Bye\r\n")) + } + }() + + config := SMTPConfig{ + Host: "127.0.0.1", + Port: port, + Username: "test@example.com", + Password: "password", + } + + checker := NewSMTPChecker(config) + ctx := context.Background() + + result := checker.Check(ctx) + + select { + case <-serverDone: + case <-time.After(2 * time.Second): + t.Error("server timeout") + } + + if result.Status != StatusHealthy && result.Status != StatusDegraded { + t.Errorf("unexpected status: %s", result.Status) + } + + if result.Details["host"] == nil { + t.Errorf("expected host in details, got %v", result.Details["host"]) + } +} + +func TestSMTPChecker_Check_EHLOFailure(t *testing.T) { + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("failed to create listener: %v", err) + } + defer listener.Close() + + port := listener.Addr().(*net.TCPAddr).Port + + serverDone := make(chan bool) + go func() { + defer close(serverDone) + conn, err := listener.Accept() + if err != nil { + return + } + defer conn.Close() + + conn.Write([]byte("220 test.example.com ESMTP ready\r\n")) + + buf := make([]byte, 1024) + n, _ := conn.Read(buf) + if n > 0 { + conn.Write([]byte("500 Syntax error, command unrecognized\r\n")) + } + }() + + config := SMTPConfig{ + Host: "127.0.0.1", + Port: port, + } + + checker := NewSMTPChecker(config) + ctx := context.Background() + + result := checker.Check(ctx) + + select { + case <-serverDone: + case <-time.After(2 * time.Second): + t.Error("server timeout") + } + + if result.Status != StatusDegraded { + t.Errorf("expected degraded status for EHLO failure, got %s", result.Status) + } + + if !contains(result.Message, "EHLO") { + t.Errorf("expected EHLO error in message, got: %s", result.Message) + } +} + +func TestSMTPChecker_Check_STARTTLSNotSupported(t *testing.T) { + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("failed to create listener: %v", err) + } + defer listener.Close() + + port := listener.Addr().(*net.TCPAddr).Port + + serverDone := make(chan bool) + go func() { + defer close(serverDone) + conn, err := listener.Accept() + if err != nil { + return + } + defer conn.Close() + + conn.Write([]byte("220 test.example.com ESMTP ready\r\n")) + + buf := make([]byte, 1024) + n, _ := conn.Read(buf) + if n > 0 { + conn.Write([]byte("250-test.example.com\r\n250 AUTH PLAIN\r\n")) + } + + n, _ = conn.Read(buf) + if n > 0 { + conn.Write([]byte("221 2.0.0 Bye\r\n")) + } + }() + + config := SMTPConfig{ + Host: "127.0.0.1", + Port: port, + } + + checker := NewSMTPChecker(config) + ctx := context.Background() + + result := checker.Check(ctx) + + select { + case <-serverDone: + case <-time.After(2 * time.Second): + t.Error("server timeout") + } + + if result.Details["starttls"] != "not supported" && result.Details["starttls"] != "failed" { + t.Errorf("expected starttls not supported or failed, got: %v", result.Details["starttls"]) + } +} + +func TestSMTPChecker_Check_AuthNotConfigured(t *testing.T) { + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("failed to create listener: %v", err) + } + defer listener.Close() + + port := listener.Addr().(*net.TCPAddr).Port + + serverDone := make(chan bool) + go func() { + defer close(serverDone) + conn, err := listener.Accept() + if err != nil { + return + } + defer conn.Close() + + conn.Write([]byte("220 test.example.com ESMTP ready\r\n")) + + buf := make([]byte, 1024) + n, _ := conn.Read(buf) + if n > 0 { + conn.Write([]byte("250-test.example.com\r\n250-STARTTLS\r\n250 AUTH PLAIN\r\n")) + } + + n, _ = conn.Read(buf) + if n > 0 { + conn.Write([]byte("221 2.0.0 Bye\r\n")) + } + }() + + config := SMTPConfig{ + Host: "127.0.0.1", + Port: port, + } + + checker := NewSMTPChecker(config) + ctx := context.Background() + + result := checker.Check(ctx) + + select { + case <-serverDone: + case <-time.After(2 * time.Second): + t.Error("server timeout") + } + + if result.Details["auth"] != "not configured" { + t.Errorf("expected auth not configured, got: %v", result.Details["auth"]) + } +} + +func TestSMTPConfig_GetAddress(t *testing.T) { + config := SMTPConfig{ + Host: "smtp.example.com", + Port: 587, + } + + address := getSMTPAddress(config) + expected := "smtp.example.com:587" + if address != expected { + t.Errorf("expected address %s, got %s", expected, address) + } +} + +func getSMTPAddress(config SMTPConfig) string { + return config.Host + ":" + itoa(config.Port) +} + +func itoa(n int) string { + if n == 0 { + return "0" + } + if n < 0 { + return "-" + itoa(-n) + } + var result []byte + for n > 0 { + result = append([]byte{byte('0' + n%10)}, result...) + n /= 10 + } + return string(result) +} + +func contains(s, substr string) bool { + if len(substr) == 0 { + return true + } + if len(s) < len(substr) { + return false + } + for i := 0; i <= len(s)-len(substr); i++ { + if s[i:i+len(substr)] == substr { + return true + } + } + return false +}