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
+34 -21
View File
@@ -11,14 +11,20 @@ import (
"github.com/juanatsap/cv-site/internal/templates"
)
// EmailSender is an interface for sending contact form emails
// This allows for easy mocking in tests
type EmailSender interface {
SendContactForm(data *services.ContactFormData) error
}
// ContactHandler handles contact form submissions
type ContactHandler struct {
templates *templates.Manager
emailService *services.EmailService
emailService EmailSender
}
// NewContactHandler creates a new contact handler
func NewContactHandler(tmpl *templates.Manager, emailService *services.EmailService) *ContactHandler {
func NewContactHandler(tmpl *templates.Manager, emailService EmailSender) *ContactHandler {
return &ContactHandler{
templates: tmpl,
emailService: emailService,
@@ -139,23 +145,28 @@ func (h *ContactHandler) renderSuccess(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusOK)
// Fallback HTML for when templates aren't available (e.g., in tests)
fallbackHTML := `<div class="alert alert-success">
<h3>Message Sent!</h3>
<p>Thank you for your message. I'll get back to you soon.</p>
</div>`
// Check if templates are properly initialized
if !h.templates.IsInitialized() {
_, _ = w.Write([]byte(fallbackHTML))
return
}
tmpl, err := h.templates.Render("contact-success")
if err != nil {
log.Printf("ERROR loading success template: %v", err)
// Fallback to simple HTML
_, _ = w.Write([]byte(`<div class="alert alert-success">
<h3>Message Sent!</h3>
<p>Thank you for your message. I'll get back to you soon.</p>
</div>`))
_, _ = w.Write([]byte(fallbackHTML))
return
}
if err := tmpl.Execute(w, nil); err != nil {
log.Printf("ERROR rendering success template: %v", err)
_, _ = w.Write([]byte(`<div class="alert alert-success">
<h3>Message Sent!</h3>
<p>Thank you for your message. I'll get back to you soon.</p>
</div>`))
log.Printf("ERROR rendering error template: %v", err)
_, _ = w.Write([]byte(fallbackHTML))
}
}
@@ -164,6 +175,15 @@ func (h *ContactHandler) renderError(w http.ResponseWriter, r *http.Request, mes
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusBadRequest)
// Fallback HTML for when templates aren't available (e.g., in tests)
fallbackHTML := `<div class="alert alert-error"><h3>Error</h3><p>` + message + `</p></div>`
// Check if templates are properly initialized
if !h.templates.IsInitialized() {
_, _ = w.Write([]byte(fallbackHTML))
return
}
data := map[string]interface{}{
"Message": message,
}
@@ -171,20 +191,13 @@ func (h *ContactHandler) renderError(w http.ResponseWriter, r *http.Request, mes
tmpl, err := h.templates.Render("contact-error")
if err != nil {
log.Printf("ERROR loading error template: %v", err)
// Fallback to simple HTML
_, _ = w.Write([]byte(`<div class="alert alert-error">
<h3>Error</h3>
<p>` + message + `</p>
</div>`))
_, _ = w.Write([]byte(fallbackHTML))
return
}
if err := tmpl.Execute(w, data); err != nil {
log.Printf("ERROR rendering error template: %v", err)
_, _ = w.Write([]byte(`<div class="alert alert-error">
<h3>Error</h3>
<p>` + message + `</p>
</div>`))
_, _ = w.Write([]byte(fallbackHTML))
}
}
+6
View File
@@ -17,6 +17,12 @@ type Manager struct {
mu sync.RWMutex
}
// IsInitialized returns true if the template manager has been properly initialized
// with a config. Empty Manager structs (e.g., in tests) will return false.
func (m *Manager) IsInitialized() bool {
return m != nil && m.config != nil
}
// NewManager creates a new template manager
func NewManager(cfg *config.TemplateConfig) (*Manager, error) {
m := &Manager{