Files
nuntius/internal/email/sender_test.go
T

315 lines
9.3 KiB
Go
Raw Normal View History

// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (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 <carol@example.com>") {
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)
}
}
}