dana-medical-site/internal/notify/email_test.go
2026-10-14 13:33:13 +00:00

167 lines
4.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package notify
import (
"bufio"
"context"
"encoding/base64"
"net"
"strconv"
"strings"
"testing"
"time"
"dana/internal/domain"
)
// fakeSMTP — минимальный SMTP-сервер без TLS и авторизации: принимает одно письмо.
func fakeSMTP(t *testing.T) (host string, port int, got <-chan string) {
t.Helper()
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { ln.Close() })
ch := make(chan string, 1)
go func() {
conn, err := ln.Accept()
if err != nil {
return
}
defer conn.Close()
r := bufio.NewReader(conn)
w := func(s string) { conn.Write([]byte(s + "\r\n")) }
w("220 fake")
var transcript strings.Builder
inData := false
for {
line, err := r.ReadString('\n')
if err != nil {
return
}
if inData {
if line == ".\r\n" {
inData = false
w("250 queued")
continue
}
transcript.WriteString(line)
continue
}
cmd := strings.ToUpper(strings.TrimSpace(line))
switch {
case strings.HasPrefix(cmd, "EHLO"), strings.HasPrefix(cmd, "HELO"):
w("250 fake")
case strings.HasPrefix(cmd, "MAIL FROM"), strings.HasPrefix(cmd, "RCPT TO"):
transcript.WriteString(strings.TrimSpace(line) + "\n")
w("250 ok")
case cmd == "DATA":
inData = true
w("354 go")
case cmd == "QUIT":
w("221 bye")
ch <- transcript.String()
return
default:
w("250 ok")
}
}
}()
addr := ln.Addr().(*net.TCPAddr)
return "127.0.0.1", addr.Port, ch
}
func TestEmailLeadCreated(t *testing.T) {
host, port, got := fakeSMTP(t)
e := &Email{Host: host, Port: port, From: "DANA сайт <site@example.ru>", To: []string{"admin@example.ru"}, SiteURL: "https://danamedical.ru"}
lead := domain.LeadNotice{
ID: 42,
FormTitle: "Запись на приём",
Name: "Иван\r\nBcc: evil@example.com",
Phone: "+7 (928) 000-00-99",
Service: "Консультация педиатра",
Extra: map[string]any{"date": "2026-09-20", "time": "Утро, 08:00–12:00"},
Message: "Тестовая заявка",
PageURL: "/services/detskaya-klinika/priem-pediatra/",
CreatedAt: time.Date(2026, 9, 15, 9, 30, 0, 0, time.UTC),
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := e.LeadCreated(ctx, lead); err != nil {
t.Fatalf("LeadCreated: %v", err)
}
var transcript string
select {
case transcript = <-got:
case <-time.After(5 * time.Second):
t.Fatal("fake SMTP did not receive message")
}
if !strings.Contains(transcript, "MAIL FROM:<site@example.ru>") || !strings.Contains(transcript, "RCPT TO:<admin@example.ru>") {
t.Fatalf("envelope not as expected:\n%s", transcript)
}
// перевод строки в имени не должен стать заголовком письма
if strings.Contains(transcript, "\r\nBcc:") {
t.Fatalf("header injection:\n%s", transcript)
}
body := decodeBody(t, transcript)
for _, want := range []string{
"Новая заявка № 42",
"Время: 15.09.2026 12:30",
"Форма: Запись на приём",
"Имя: Иван Bcc: evil@example.com",
"Телефон: +7 (928) 000-00-99",
"Услуга: Консультация педиатра",
"Желаемая дата: 2026-09-20",
"Удобное время: Утро, 08:00–12:00",
"Тестовая заявка",
"Все заявки: https://danamedical.ru/admin/",
} {
if !strings.Contains(body, want) {
t.Errorf("body has no %q:\n%s", want, body)
}
}
}
func TestFormRecipientsOverrideDefault(t *testing.T) {
host, port, got := fakeSMTP(t)
e := &Email{Host: host, Port: port, From: "site@example.ru", To: []string{"default@example.ru"}}
lead := domain.LeadNotice{ID: 1, Recipients: []string{"doctors@example.ru", "не адрес"}}
if err := e.LeadCreated(context.Background(), lead); err != nil {
t.Fatal(err)
}
transcript := <-got
if !strings.Contains(transcript, "RCPT TO:<doctors@example.ru>") || strings.Contains(transcript, "default@example.ru") {
t.Fatalf("recipients of the form must replace defaults:\n%s", transcript)
}
}
func TestNoRecipients(t *testing.T) {
e := &Email{Host: "127.0.0.1", Port: 1, From: "site@example.ru"}
if err := e.LeadCreated(context.Background(), domain.LeadNotice{ID: 1}); err != errNoRecipients {
t.Fatalf("want errNoRecipients, got %v", err)
}
}
func decodeBody(t *testing.T, transcript string) string {
t.Helper()
parts := strings.SplitN(transcript, "\r\n\r\n", 2)
if len(parts) != 2 {
t.Fatalf("no body separator:\n%s", transcript)
}
raw, err := base64.StdEncoding.DecodeString(strings.ReplaceAll(parts[1], "\r\n", ""))
if err != nil {
t.Fatalf("decode body: %v", err)
}
return string(raw)
}
var _ = strconv.Itoa