315 lines
9.3 KiB
Go
315 lines
9.3 KiB
Go
// 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)
|
|
}
|
|
}
|
|
}
|