package integration_test import ( "crypto/tls" "fmt" "net/smtp" "os" "testing" "time" "github.com/juanatsap/cv-site/internal/services" ) // TestSMTPConnection tests that SMTP credentials are valid and connection works // This test requires valid SMTP credentials in environment variables // Run with: go test -v ./tests/integration/... -run TestSMTPConnection func TestSMTPConnection(t *testing.T) { // Skip in CI or when credentials aren't available host := os.Getenv("SMTP_HOST") port := os.Getenv("SMTP_PORT") user := os.Getenv("SMTP_USER") pass := os.Getenv("SMTP_PASSWORD") if host == "" || user == "" || pass == "" { t.Skip("Skipping SMTP test: SMTP credentials not configured") } addr := fmt.Sprintf("%s:%s", host, port) t.Run("TLS_Connection", func(t *testing.T) { tlsConfig := &tls.Config{ ServerName: host, MinVersion: tls.VersionTLS12, } if port == "465" { // Implicit SSL conn, err := tls.Dial("tcp", addr, tlsConfig) if err != nil { t.Fatalf("TLS dial failed: %v", err) } defer func() { _ = conn.Close() }() t.Log("TLS connection established (port 465 - implicit SSL)") } else { // STARTTLS client, err := smtp.Dial(addr) if err != nil { t.Fatalf("SMTP dial failed: %v", err) } defer func() { _ = client.Close() }() if err := client.StartTLS(tlsConfig); err != nil { t.Fatalf("STARTTLS failed: %v", err) } t.Log("TLS connection established (STARTTLS)") } }) t.Run("SMTP_Authentication", func(t *testing.T) { tlsConfig := &tls.Config{ ServerName: host, MinVersion: tls.VersionTLS12, } var client *smtp.Client var err error if port == "465" { // Implicit SSL - connect with TLS from the start conn, err := tls.Dial("tcp", addr, tlsConfig) if err != nil { t.Fatalf("TLS dial failed: %v", err) } defer func() { _ = conn.Close() }() client, err = smtp.NewClient(conn, host) if err != nil { t.Fatalf("SMTP client creation failed: %v", err) } } else { // STARTTLS client, err = smtp.Dial(addr) if err != nil { t.Fatalf("SMTP dial failed: %v", err) } if err := client.StartTLS(tlsConfig); err != nil { t.Fatalf("STARTTLS failed: %v", err) } } defer func() { _ = client.Close() }() auth := smtp.PlainAuth("", user, pass, host) if err := client.Auth(auth); err != nil { t.Fatalf("SMTP authentication failed: %v", err) } t.Log("SMTP authentication successful") _ = client.Quit() }) } // TestEmailServiceSend tests actual email sending // This will send a real test email - use sparingly // Run with: go test -v ./tests/integration/... -run TestEmailServiceSend func TestEmailServiceSend(t *testing.T) { // Skip in CI or when credentials aren't available host := os.Getenv("SMTP_HOST") port := os.Getenv("SMTP_PORT") user := os.Getenv("SMTP_USER") pass := os.Getenv("SMTP_PASSWORD") from := os.Getenv("SMTP_FROM_EMAIL") to := os.Getenv("CONTACT_EMAIL") if host == "" || user == "" || pass == "" { t.Skip("Skipping email send test: SMTP credentials not configured") } if from == "" { from = user } if to == "" { t.Skip("Skipping email send test: CONTACT_EMAIL not configured") } config := &services.EmailConfig{ SMTPHost: host, SMTPPort: port, SMTPUser: user, SMTPPassword: pass, FromEmail: from, ToEmail: to, } emailService := services.NewEmailService(config) testData := &services.ContactFormData{ Email: "test-sender@example.com", Name: "Integration Test", Company: "Test Suite", Subject: "Email Integration Test", Message: "This is an automated test email sent by the integration test suite. If you receive this, the email configuration is working correctly.", IP: "127.0.0.1", Time: time.Now(), } t.Run("SendContactForm", func(t *testing.T) { err := emailService.SendContactForm(testData) if err != nil { t.Fatalf("Failed to send email: %v", err) } t.Logf("Test email sent successfully to %s", to) }) } // TestEmailServiceValidation tests that the email service properly validates input func TestEmailServiceValidation(t *testing.T) { config := &services.EmailConfig{ SMTPHost: "smtp.test.com", SMTPPort: "465", SMTPUser: "test@test.com", SMTPPassword: "password", FromEmail: "from@test.com", ToEmail: "to@test.com", } emailService := services.NewEmailService(config) tests := []struct { name string data *services.ContactFormData wantErr bool errMsg string }{ { name: "valid data", data: &services.ContactFormData{ Email: "valid@example.com", Name: "Valid User", Message: "This is a valid message with more than 10 characters.", Time: time.Now(), }, wantErr: false, }, { name: "missing email", data: &services.ContactFormData{ Name: "No Email", Message: "This is a valid message.", Time: time.Now(), }, wantErr: true, errMsg: "email is required", }, { name: "invalid email format", data: &services.ContactFormData{ Email: "notanemail", Name: "Bad Email", Message: "This is a valid message.", Time: time.Now(), }, wantErr: true, errMsg: "invalid email format", }, { name: "missing message", data: &services.ContactFormData{ Email: "valid@example.com", Name: "No Message", Time: time.Now(), }, wantErr: true, errMsg: "message is required", }, { name: "message too short", data: &services.ContactFormData{ Email: "valid@example.com", Name: "Short Msg", Message: "Hi", Time: time.Now(), }, wantErr: true, errMsg: "message too short", }, { name: "email with newlines (header injection)", data: &services.ContactFormData{ Email: "test@example.com\nBcc: attacker@evil.com", Name: "Attacker", Message: "Trying to inject headers.", Time: time.Now(), }, wantErr: true, errMsg: "prohibited characters", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // We can't actually send since we don't have real SMTP // but we can test validation err := tt.data.Validate() if tt.wantErr { if err == nil { t.Errorf("expected error containing %q, got nil", tt.errMsg) } else if tt.errMsg != "" && !containsString(err.Error(), tt.errMsg) { t.Errorf("expected error containing %q, got %q", tt.errMsg, err.Error()) } } else { if err != nil { t.Errorf("unexpected error: %v", err) } } }) } _ = emailService // Avoid unused variable warning } func containsString(s, substr string) bool { return len(s) >= len(substr) && (s == substr || len(s) > 0 && containsStringHelper(s, substr)) } func containsStringHelper(s, substr string) bool { for i := 0; i <= len(s)-len(substr); i++ { if s[i:i+len(substr)] == substr { return true } } return false }