package email import ( "sync" "testing" "time" "github.com/stretchr/testify/assert" ) func TestSend(t *testing.T) { config := &Config{ SMTPHost: "smtp.example.com", SMTPPort: 587, FromEmail: "test@example.com", } message := &Message{ To: []string{"recipient@example.com"}, Subject: "Test", Body: "Test body", } // This will fail to connect (no real server), but should validate inputs err := Send(config, message) // We expect an error because there's no real SMTP server // But it should be a connection error, not a validation error assert.Error(t, err) assert.Contains(t, err.Error(), "dial") } func TestSendValidation(t *testing.T) { tests := []struct { name string config *Config message *Message wantErr bool errMsg string }{ { name: "nil config", config: nil, message: &Message{To: []string{"test@example.com"}, Subject: "Test", Body: "Test"}, wantErr: true, errMsg: "configuration is required", }, { name: "nil message", config: &Config{SMTPHost: "smtp.example.com", SMTPPort: 587, FromEmail: "from@example.com"}, message: nil, wantErr: true, errMsg: "message is required", }, { name: "invalid config", config: &Config{ SMTPHost: "", SMTPPort: 587, }, message: &Message{To: []string{"test@example.com"}, Subject: "Test", Body: "Test"}, wantErr: true, errMsg: "invalid email configuration", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := Send(tt.config, tt.message) if tt.wantErr { assert.Error(t, err) assert.Contains(t, err.Error(), tt.errMsg) } }) } } func TestSendAsync(t *testing.T) { config := &Config{ SMTPHost: "smtp.example.com", SMTPPort: 587, FromEmail: "test@example.com", } message := &Message{ To: []string{"recipient@example.com"}, Subject: "Test Async", Body: "Test async body", } // SendAsync should not block start := time.Now() SendAsync(config, message) duration := time.Since(start) // Should return almost immediately (< 100ms) assert.Less(t, duration, 100*time.Millisecond) // Give goroutine time to start time.Sleep(50 * time.Millisecond) } func TestSendAsyncConcurrent(t *testing.T) { config := &Config{ SMTPHost: "smtp.example.com", SMTPPort: 587, FromEmail: "test@example.com", } // Send multiple emails concurrently var wg sync.WaitGroup count := 5 for i := 0; i < count; i++ { wg.Add(1) go func() { defer wg.Done() message := &Message{ To: []string{"recipient@example.com"}, Subject: "Concurrent Test", Body: "Test body", } SendAsync(config, message) }() } // Should complete without deadlock done := make(chan bool) go func() { wg.Wait() done <- true }() select { case <-done: // Success case <-time.After(1 * time.Second): t.Fatal("SendAsync calls did not complete in time") } }