fix: security tests with mock email sender and rate limit isolation

- Add EmailSender interface to allow mocking in tests
- Add IsInitialized() method to template.Manager for nil-safe checks
- Update contact handler to use interface and safe initialization checks
- Add mockEmailSender in security tests to avoid SMTP connection attempts
- Use unique IPs per test case to avoid rate limiting interference
This commit is contained in:
juanatsap
2025-12-02 13:49:54 +00:00
parent 41dbd77c2f
commit 3edeb5274d
3 changed files with 75 additions and 33 deletions
+35 -12
View File
@@ -15,6 +15,18 @@ import (
"github.com/juanatsap/cv-site/internal/templates"
)
// mockEmailSender is a mock implementation of handlers.EmailSender for testing
type mockEmailSender struct{}
func (m *mockEmailSender) SendContactForm(data *services.ContactFormData) error {
// Validate like the real service would
if err := data.Validate(); err != nil {
return fmt.Errorf("validation failed: %w", err)
}
// Always succeed - don't actually send
return nil
}
// setupTestServer creates a test server with the contact handler and security middleware
func setupTestServer(t *testing.T) http.Handler {
t.Helper()
@@ -22,18 +34,8 @@ func setupTestServer(t *testing.T) http.Handler {
// Create a minimal template manager for testing
tmpl := &templates.Manager{}
// Create a mock email service configuration
emailConfig := &services.EmailConfig{
SMTPHost: "localhost",
SMTPPort: "1025",
SMTPUser: "test",
SMTPPassword: "test",
FromEmail: "test@example.com",
ToEmail: "recipient@example.com",
}
// Create email service with mock config (won't actually send emails in tests)
emailService := services.NewEmailService(emailConfig)
// Create mock email service that doesn't actually send
emailService := &mockEmailSender{}
// Create the contact handler
contactHandler := handlers.NewContactHandler(tmpl, emailService)
@@ -306,36 +308,43 @@ func TestInputValidation_EmailFormat(t *testing.T) {
tests := []struct {
name string
email string
ip string // Use unique IPs to avoid rate limiting
wantCode int
}{
{
name: "valid email",
email: "test@example.com",
ip: "10.1.1.1",
wantCode: http.StatusOK,
},
{
name: "valid email with subdomain",
email: "user@mail.example.com",
ip: "10.1.1.2",
wantCode: http.StatusOK,
},
{
name: "invalid - no @",
email: "notanemail",
ip: "10.1.1.3",
wantCode: http.StatusBadRequest,
},
{
name: "invalid - no domain",
email: "test@",
ip: "10.1.1.4",
wantCode: http.StatusBadRequest,
},
{
name: "invalid - no TLD",
email: "test@example",
ip: "10.1.1.5",
wantCode: http.StatusBadRequest,
},
{
name: "empty email",
email: "",
ip: "10.1.1.6",
wantCode: http.StatusBadRequest,
},
}
@@ -356,6 +365,7 @@ func TestInputValidation_EmailFormat(t *testing.T) {
req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)")
req.Header.Set("Referer", "http://localhost:8080/")
req.Header.Set("HX-Request", "true")
req.Header.Set("X-Forwarded-For", tt.ip) // Use unique IP to avoid rate limiting
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
@@ -374,31 +384,37 @@ func TestInputValidation_MessageLength(t *testing.T) {
tests := []struct {
name string
message string
ip string // Use unique IPs to avoid rate limiting
wantCode int
}{
{
name: "valid message - minimum length",
message: "Short msg!",
ip: "10.2.1.1",
wantCode: http.StatusOK,
},
{
name: "valid message - normal length",
message: "This is a normal length message that should pass validation.",
ip: "10.2.1.2",
wantCode: http.StatusOK,
},
{
name: "valid message - maximum length",
message: strings.Repeat("a", 5000),
ip: "10.2.1.3",
wantCode: http.StatusOK,
},
{
name: "invalid - too long",
message: strings.Repeat("a", 5001),
ip: "10.2.1.4",
wantCode: http.StatusBadRequest,
},
{
name: "invalid - empty",
message: "",
ip: "10.2.1.5",
wantCode: http.StatusBadRequest,
},
}
@@ -419,6 +435,7 @@ func TestInputValidation_MessageLength(t *testing.T) {
req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)")
req.Header.Set("Referer", "http://localhost:8080/")
req.Header.Set("HX-Request", "true")
req.Header.Set("X-Forwarded-For", tt.ip) // Use unique IP to avoid rate limiting
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
@@ -438,30 +455,35 @@ func TestInputValidation_RequiredFields(t *testing.T) {
name string
email string
message string
ip string // Use unique IPs to avoid rate limiting
wantCode int
}{
{
name: "all required fields present",
email: "test@example.com",
message: "Valid message",
ip: "10.3.1.1",
wantCode: http.StatusOK,
},
{
name: "missing email",
email: "",
message: "Valid message",
ip: "10.3.1.2",
wantCode: http.StatusBadRequest,
},
{
name: "missing message",
email: "test@example.com",
message: "",
ip: "10.3.1.3",
wantCode: http.StatusBadRequest,
},
{
name: "both missing",
email: "",
message: "",
ip: "10.3.1.4",
wantCode: http.StatusBadRequest,
},
}
@@ -481,6 +503,7 @@ func TestInputValidation_RequiredFields(t *testing.T) {
req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)")
req.Header.Set("Referer", "http://localhost:8080/")
req.Header.Set("HX-Request", "true")
req.Header.Set("X-Forwarded-For", tt.ip) // Use unique IP to avoid rate limiting
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)