// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) // SPDX-License-Identifier: MIT //go:build linux || freebsd package email import ( "strings" "testing" "sourcedock.dev/petrbalvin/nuntius/pkg/contactform" ) func TestComposeContactWithoutService(t *testing.T) { req := contactform.Request{ Name: "Alice", Email: "alice@example.com", Message: "I have a question about your services.", } b := compose("from@example.com", "to@example.com", req, "MyForm", "contact") s := string(b) if !strings.Contains(s, "Subject: [nuntius/MyForm] Contact form submission") { t.Errorf("expected subject with form name only, got body:\n%s", s) } if !strings.Contains(s, "From: from@example.com") { t.Error("expected From header") } if !strings.Contains(s, "To: to@example.com") { t.Error("expected To header") } if !strings.Contains(s, "Reply-To: alice@example.com") { t.Error("expected Reply-To header") } if !strings.Contains(s, "MIME-Version: 1.0") { t.Error("expected MIME-Version header") } if !strings.Contains(s, "Content-Type: text/plain; charset=UTF-8") { t.Error("expected text/plain part") } if !strings.Contains(s, "Content-Type: text/html; charset=UTF-8") { t.Error("expected text/html part") } if !strings.Contains(s, "Content-Type: multipart/alternative") { t.Error("expected multipart/alternative content type") } if !strings.Contains(s, "I have a question about your services.") { t.Error("expected message content in body") } if !strings.Contains(s, "Contact Form Submission:") { t.Error("expected contact form plain-text header") } } func TestComposeContactWithService(t *testing.T) { req := contactform.Request{ Name: "Bob", Email: "bob@example.com", Service: "architecture", Message: "I would like a consultation.", } b := compose("from@e.com", "to@e.com", req, "ContactForm", "contact") s := string(b) if !strings.Contains(s, "Subject: [nuntius/ContactForm][architecture] Contact form submission") { t.Errorf("expected subject with service tag, got body:\n%s", s) } if !strings.Contains(s, "Service interest: architecture") { t.Error("expected service interest in plain text") } if !strings.Contains(s, "Content-Type: text/plain") { t.Error("expected text/plain part") } if !strings.Contains(s, "Content-Type: text/html") { t.Error("expected text/html part") } if !strings.Contains(s, "bob@example.com") { t.Error("expected submitter email in body") } } func TestComposeFeedback(t *testing.T) { req := contactform.Request{ Name: "Carol", Email: "carol@example.com", Message: "Great platform, but can you add dark mode?", } b := compose("sender@h.com", "recv@h.com", req, "FeedbackForm", "feedback") s := string(b) if !strings.Contains(s, "Subject: [nuntius/FeedbackForm] New feedback") { t.Errorf("expected feedback subject, got body:\n%s", s) } if !strings.Contains(s, "New Feedback:") { t.Error("expected feedback plain-text header") } if !strings.Contains(s, "From: Carol ") { t.Error("expected From line in plain text") } if !strings.Contains(s, "Content-Type: text/plain") { t.Error("expected text/plain part") } if !strings.Contains(s, "Content-Type: text/html") { t.Error("expected text/html part") } } func TestComposeNewsletter(t *testing.T) { req := contactform.Request{ Email: "subscriber@example.com", } b := compose("news@h.com", "owner@h.com", req, "NewsletterSignup", "newsletter") s := string(b) if !strings.Contains(s, "Subject: [nuntius/NewsletterSignup] New newsletter subscriber") { t.Errorf("expected newsletter subject, got body:\n%s", s) } if !strings.Contains(s, "New Newsletter Subscriber:") { t.Error("expected newsletter plain-text header") } if !strings.Contains(s, "Email: subscriber@example.com") { t.Error("expected subscriber email in plain text") } if !strings.Contains(s, "Content-Type: text/plain") { t.Error("expected text/plain part") } if !strings.Contains(s, "Content-Type: text/html") { t.Error("expected text/html part") } } func TestComposeGeneric(t *testing.T) { req := contactform.Request{ Name: "Dave", Email: "dave@example.com", Message: "Generic inquiry.", } b := compose("g@h.com", "g@h.com", req, "GenericForm", "generic") s := string(b) if !strings.Contains(s, "Subject: [nuntius/GenericForm] New submission") { t.Errorf("expected generic subject, got body:\n%s", s) } if !strings.Contains(s, "New Submission:") { t.Error("expected generic plain-text header") } if !strings.Contains(s, "Content-Type: text/plain") { t.Error("expected text/plain part") } if !strings.Contains(s, "Content-Type: text/html") { t.Error("expected text/html part") } } func TestComposeEmptyFormNameAndEmail(t *testing.T) { req := contactform.Request{ Name: "", Email: "", Message: "", } b := compose("", "", req, "", "generic") s := string(b) if !strings.Contains(s, "Subject: [nuntius/] New submission") { t.Errorf("expected subject with empty form name, got body:\n%s", s) } if !strings.Contains(s, "MIME-Version: 1.0") { t.Error("expected MIME-Version header even with empty fields") } if !strings.Contains(s, "Content-Type: text/plain") { t.Error("expected text/plain part even with empty fields") } if !strings.Contains(s, "Content-Type: text/html") { t.Error("expected text/html part even with empty fields") } } func TestComposeLongMessage(t *testing.T) { longMsg := strings.Repeat("Lorem ipsum dolor sit amet. ", 200) req := contactform.Request{ Name: "Eve", Email: "eve@example.com", Message: longMsg, } b := compose("x@y.com", "z@y.com", req, "LongForm", "feedback") s := string(b) if !strings.Contains(s, longMsg) { t.Error("expected long message content in body") } // Verify it's in both text and html parts by checking after the respective // content-type boundaries. textIdx := strings.Index(s, "Content-Type: text/plain") htmlIdx := strings.Index(s, "Content-Type: text/html") if textIdx == -1 || htmlIdx == -1 { t.Fatal("expected both text/plain and text/html parts") } textPart := s[textIdx:htmlIdx] htmlPart := s[htmlIdx:] if !strings.Contains(textPart, longMsg) { t.Error("expected long message in text/plain part") } if !strings.Contains(htmlPart, longMsg) { t.Error("expected long message in text/html part") } } func TestComposeSpecialCharacters(t *testing.T) { specialMsg := "Café résumé — déjà vu\nLine\twith\ttabs\n€uro sign © 2026" req := contactform.Request{ Name: "Renée", Email: "renée@example.com", Message: specialMsg, } b := compose("ñ@c.com", "ö@c.com", req, "SpaForm", "contact") s := string(b) if !strings.Contains(s, "Café résumé — déjà vu") { t.Error("expected accented characters to survive round-trip") } if !strings.Contains(s, "€uro sign © 2026") { t.Error("expected special symbols to survive round-trip") } if !strings.Contains(s, "Renée") { t.Error("expected accented name in body") } if !strings.Contains(s, "MIME-Version: 1.0") { t.Error("expected MIME-Version header") } } func TestComposeUnknownFormTypeFallsBackToContact(t *testing.T) { // Unknown form type should default to the contact template. req := contactform.Request{ Name: "Fallback", Email: "fallback@example.com", Message: "Does this work?", } b := compose("a@b.com", "c@b.com", req, "UnknownForm", "nonexistent") s := string(b) if !strings.Contains(s, "Subject: [nuntius/UnknownForm] Contact form submission") { t.Errorf("expected contact fallback subject, got body:\n%s", s) } if !strings.Contains(s, "Content-Type: text/plain") { t.Error("expected text/plain part in fallback") } if !strings.Contains(s, "Content-Type: text/html") { t.Error("expected text/html part in fallback") } } func TestComposeAllHeadersPresent(t *testing.T) { req := contactform.Request{ Name: "Test", Email: "test@example.com", Message: "Checking headers.", } b := compose("from@x.com", "to@x.com", req, "HeaderForm", "contact") s := string(b) required := []string{ "From: from@x.com", "To: to@x.com", "Subject:", "Date:", "Reply-To: test@example.com", "MIME-Version: 1.0", "Content-Type: multipart/alternative", } for _, h := range required { if !strings.Contains(s, h) { t.Errorf("expected header %q in message", h) } } } func TestComposeMultipartBoundary(t *testing.T) { req := contactform.Request{ Name: "Boundary", Email: "boundary@example.com", Message: "Boundary test.", } b := compose("from@t.com", "to@t.com", req, "BoundForm", "contact") s := string(b) if !strings.Contains(s, "--nuntius-boundary") { t.Error("expected nuntius-boundary separator") } // Should have opening boundary, middle boundary, closing boundary. count := strings.Count(s, "--nuntius-boundary") if count < 3 { t.Errorf("expected at least 3 boundary markers, got %d", count) } if !strings.Contains(s, "--nuntius-boundary--") { t.Error("expected closing boundary marker") } } func TestComposeDeliveredByFooter(t *testing.T) { req := contactform.Request{ Name: "Footer", Email: "footer@example.com", Message: "Footer check.", } for _, ft := range []string{"contact", "feedback", "newsletter", "generic"} { b := compose("f@t.com", "t@t.com", req, "FooterForm", ft) s := string(b) if !strings.Contains(s, "Delivered by nuntius") { t.Errorf("form type %q: expected 'Delivered by nuntius' footer in plain text", ft) } } }