Compare commits
2 Commits
52c964abd2
...
b3b7c1d527
| Author | SHA1 | Date | |
|---|---|---|---|
| b3b7c1d527 | |||
| 4c1caa44dd |
@@ -10,7 +10,6 @@ import (
|
|||||||
"goyco/internal/middleware"
|
"goyco/internal/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockDBMonitor is a mock implementation of DBMonitor for testing
|
|
||||||
type MockDBMonitor struct {
|
type MockDBMonitor struct {
|
||||||
stats middleware.DBStats
|
stats middleware.DBStats
|
||||||
}
|
}
|
||||||
@@ -20,11 +19,9 @@ func (m *MockDBMonitor) GetStats() middleware.DBStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockDBMonitor) LogQuery(query string, duration time.Duration, err error) {
|
func (m *MockDBMonitor) LogQuery(query string, duration time.Duration, err error) {
|
||||||
// Mock implementation - does nothing
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockDBMonitor) LogSlowQuery(query string, duration time.Duration, threshold time.Duration) {
|
func (m *MockDBMonitor) LogSlowQuery(query string, duration time.Duration, threshold time.Duration) {
|
||||||
// Mock implementation - does nothing
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestStatusConstants(t *testing.T) {
|
func TestStatusConstants(t *testing.T) {
|
||||||
@@ -90,10 +87,8 @@ func TestDatabaseChecker_Name(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDatabaseChecker_Check(t *testing.T) {
|
func TestDatabaseChecker_Check(t *testing.T) {
|
||||||
// Create an in-memory database using the default driver
|
|
||||||
db, err := sql.Open("sqlite", ":memory:")
|
db, err := sql.Open("sqlite", ":memory:")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Fallback: create a nil db and just verify the checker structure
|
|
||||||
t.Logf("Could not open test database: %v", err)
|
t.Logf("Could not open test database: %v", err)
|
||||||
t.Skip("Skipping database-dependent test")
|
t.Skip("Skipping database-dependent test")
|
||||||
}
|
}
|
||||||
@@ -115,8 +110,6 @@ func TestDatabaseChecker_Check(t *testing.T) {
|
|||||||
|
|
||||||
result := checker.Check(ctx)
|
result := checker.Check(ctx)
|
||||||
|
|
||||||
// Note: The actual result depends on database availability
|
|
||||||
// We just verify the structure is correct
|
|
||||||
if result.Timestamp.IsZero() {
|
if result.Timestamp.IsZero() {
|
||||||
t.Error("Expected non-zero timestamp")
|
t.Error("Expected non-zero timestamp")
|
||||||
}
|
}
|
||||||
@@ -125,7 +118,6 @@ func TestDatabaseChecker_Check(t *testing.T) {
|
|||||||
t.Error("Expected ping_time in details")
|
t.Error("Expected ping_time in details")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only check stats if status is healthy
|
|
||||||
if result.Status == StatusHealthy {
|
if result.Status == StatusHealthy {
|
||||||
stats, ok := result.Details["stats"].(map[string]any)
|
stats, ok := result.Details["stats"].(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -139,11 +131,9 @@ func TestDatabaseChecker_Check(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDatabaseChecker_Check_Unhealthy(t *testing.T) {
|
func TestDatabaseChecker_Check_Unhealthy(t *testing.T) {
|
||||||
// Test with a nil database connection
|
|
||||||
checker := NewDatabaseChecker(nil, nil)
|
checker := NewDatabaseChecker(nil, nil)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// This should panic or return an error result
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
t.Logf("Got expected panic with nil db: %v", r)
|
t.Logf("Got expected panic with nil db: %v", r)
|
||||||
@@ -152,65 +142,12 @@ func TestDatabaseChecker_Check_Unhealthy(t *testing.T) {
|
|||||||
|
|
||||||
result := checker.Check(ctx)
|
result := checker.Check(ctx)
|
||||||
|
|
||||||
// If no panic, we should get an unhealthy status
|
|
||||||
if result.Status != StatusUnhealthy {
|
if result.Status != StatusUnhealthy {
|
||||||
t.Logf("Expected unhealthy status for nil db, got %s", result.Status)
|
t.Logf("Expected unhealthy status for nil db, got %s", result.Status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSMTPChecker_Name(t *testing.T) {
|
|
||||||
checker := &SMTPChecker{}
|
|
||||||
if checker.Name() != "smtp" {
|
|
||||||
t.Errorf("Expected name 'smtp', got %s", checker.Name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSMTPChecker_Check_InvalidHost(t *testing.T) {
|
|
||||||
config := SMTPConfig{
|
|
||||||
Host: "invalid.host.that.does.not.exist",
|
|
||||||
Port: 587,
|
|
||||||
}
|
|
||||||
|
|
||||||
checker := NewSMTPChecker(config)
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
result := checker.Check(ctx)
|
|
||||||
|
|
||||||
if result.Status != StatusDegraded {
|
|
||||||
t.Errorf("Expected degraded status for invalid host, got %s", result.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.Message == "" {
|
|
||||||
t.Error("Expected error message for connection failure")
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.Details["host"] != "invalid.host.that.does.not.exist:587" {
|
|
||||||
t.Errorf("Expected host in details, got %v", result.Details["host"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSMTPChecker_Check_WithAuth(t *testing.T) {
|
|
||||||
config := SMTPConfig{
|
|
||||||
Host: "smtp.example.com",
|
|
||||||
Port: 587,
|
|
||||||
Username: "test@example.com",
|
|
||||||
Password: "password",
|
|
||||||
From: "noreply@example.com",
|
|
||||||
}
|
|
||||||
|
|
||||||
checker := NewSMTPChecker(config)
|
|
||||||
|
|
||||||
// Just verify the configuration is stored correctly
|
|
||||||
if checker.config.Host != "smtp.example.com" {
|
|
||||||
t.Errorf("Expected host to be 'smtp.example.com', got %s", checker.config.Host)
|
|
||||||
}
|
|
||||||
if checker.config.Port != 587 {
|
|
||||||
t.Errorf("Expected port to be 587, got %d", checker.config.Port)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCompositeChecker(t *testing.T) {
|
func TestCompositeChecker(t *testing.T) {
|
||||||
// Create a composite checker with test checkers
|
|
||||||
checker1 := &mockChecker{
|
checker1 := &mockChecker{
|
||||||
name: "service1",
|
name: "service1",
|
||||||
status: StatusHealthy,
|
status: StatusHealthy,
|
||||||
@@ -276,7 +213,6 @@ func TestCompositeChecker_CheckWithVersion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// mockChecker is a test implementation of the Checker interface
|
|
||||||
type mockChecker struct {
|
type mockChecker struct {
|
||||||
name string
|
name string
|
||||||
status Status
|
status Status
|
||||||
|
|||||||
337
internal/health/smtp_test.go
Normal file
337
internal/health/smtp_test.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user