367 lines
8.9 KiB
Go
367 lines
8.9 KiB
Go
|
|
package smtp
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"context"
|
|||
|
|
"strings"
|
|||
|
|
"testing"
|
|||
|
|
|
|||
|
|
"code.yun.ink/pkg/mailx/interfaces"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// 测试内容类型检测
|
|||
|
|
func TestContentTypeDetection(t *testing.T) {
|
|||
|
|
smtpClient := NewSmtp()
|
|||
|
|
|
|||
|
|
tests := []struct {
|
|||
|
|
name string
|
|||
|
|
body string
|
|||
|
|
expected interfaces.ContentType
|
|||
|
|
}{
|
|||
|
|
{
|
|||
|
|
name: "HTML with html tag",
|
|||
|
|
body: "<html><body>Test</body></html>",
|
|||
|
|
expected: interfaces.ContentTypeHTML,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "HTML with div tag",
|
|||
|
|
body: "<div>Test content</div>",
|
|||
|
|
expected: interfaces.ContentTypeHTML,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "HTML with img tag",
|
|||
|
|
body: "Check this image: <img src='test.jpg'>",
|
|||
|
|
expected: interfaces.ContentTypeHTML,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "Plain text",
|
|||
|
|
body: "This is plain text without any HTML tags.",
|
|||
|
|
expected: interfaces.ContentTypeText,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "Text with angle brackets",
|
|||
|
|
body: "Price: 100 < 200, Quality: A > B",
|
|||
|
|
expected: interfaces.ContentTypeText,
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tt := range tests {
|
|||
|
|
t.Run(tt.name, func(t *testing.T) {
|
|||
|
|
result := smtpClient.detectContentType(tt.body)
|
|||
|
|
if result != tt.expected {
|
|||
|
|
t.Errorf("Expected %s, got %s for body: %s", tt.expected, result, tt.body)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 测试邮件头构建
|
|||
|
|
func TestBuildHeaders(t *testing.T) {
|
|||
|
|
smtpClient := NewSmtp()
|
|||
|
|
smtpClient.SetOption(context.Background(), interfaces.SetSmtp(&interfaces.EmailConfigDataSmtp{}))
|
|||
|
|
boundary := "test-boundary"
|
|||
|
|
|
|||
|
|
tests := []struct {
|
|||
|
|
name string
|
|||
|
|
message interfaces.Message
|
|||
|
|
expected string
|
|||
|
|
}{
|
|||
|
|
{
|
|||
|
|
name: "HTML with inline images",
|
|||
|
|
message: interfaces.Message{
|
|||
|
|
Subject: "Test Subject",
|
|||
|
|
InlineImage: []interfaces.Attachment{{Name: "test.jpg"}},
|
|||
|
|
},
|
|||
|
|
expected: "multipart/related",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "HTML with attachments",
|
|||
|
|
message: interfaces.Message{
|
|||
|
|
Subject: "Test Subject",
|
|||
|
|
Attachment: []interfaces.Attachment{{Name: "test.pdf"}},
|
|||
|
|
},
|
|||
|
|
expected: "multipart/mixed",
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "Simple message",
|
|||
|
|
message: interfaces.Message{
|
|||
|
|
Subject: "Test Subject",
|
|||
|
|
},
|
|||
|
|
expected: "multipart/alternative",
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tt := range tests {
|
|||
|
|
t.Run(tt.name, func(t *testing.T) {
|
|||
|
|
headers := smtpClient.buildHeaders(tt.message, boundary)
|
|||
|
|
contentType := headers["Content-Type"]
|
|||
|
|
if !strings.Contains(contentType, tt.expected) {
|
|||
|
|
t.Errorf("Expected Content-Type to contain %s, got %s", tt.expected, contentType)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 测试附件类型处理
|
|||
|
|
func TestAttachmentTypes(t *testing.T) {
|
|||
|
|
tests := []struct {
|
|||
|
|
name string
|
|||
|
|
attachment interfaces.Attachment
|
|||
|
|
expectCID bool
|
|||
|
|
}{
|
|||
|
|
{
|
|||
|
|
name: "File attachment",
|
|||
|
|
attachment: interfaces.Attachment{
|
|||
|
|
Name: "document.pdf",
|
|||
|
|
Type: interfaces.AttachmentTypeFile,
|
|||
|
|
},
|
|||
|
|
expectCID: false,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "Inline image",
|
|||
|
|
attachment: interfaces.Attachment{
|
|||
|
|
Name: "image.jpg",
|
|||
|
|
Type: interfaces.AttachmentTypeInline,
|
|||
|
|
CID: "test-image",
|
|||
|
|
},
|
|||
|
|
expectCID: true,
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tt := range tests {
|
|||
|
|
t.Run(tt.name, func(t *testing.T) {
|
|||
|
|
if tt.expectCID && tt.attachment.CID == "" {
|
|||
|
|
t.Error("Expected CID for inline attachment")
|
|||
|
|
}
|
|||
|
|
if !tt.expectCID && tt.attachment.Type == interfaces.AttachmentTypeFile {
|
|||
|
|
// File attachments should not have CID
|
|||
|
|
if tt.attachment.CID != "" {
|
|||
|
|
t.Error("File attachment should not have CID")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 测试消息验证
|
|||
|
|
func TestMessageValidation2(t *testing.T) {
|
|||
|
|
smtpClient := NewSmtp()
|
|||
|
|
ctx := context.Background()
|
|||
|
|
|
|||
|
|
// 配置SMTP客户端
|
|||
|
|
_, err := smtpClient.SetOption(ctx, func(opt *interfaces.Options) {
|
|||
|
|
opt.Smtp = &interfaces.EmailConfigDataSmtp{
|
|||
|
|
Host: "smtp.example.com",
|
|||
|
|
Port: "587",
|
|||
|
|
Username: "test@example.com",
|
|||
|
|
Password: "password",
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
if err != nil {
|
|||
|
|
t.Fatalf("Failed to configure SMTP: %v", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
tests := []struct {
|
|||
|
|
name string
|
|||
|
|
message interfaces.Message
|
|||
|
|
wantErr bool
|
|||
|
|
}{
|
|||
|
|
{
|
|||
|
|
name: "Valid HTML message",
|
|||
|
|
message: interfaces.Message{
|
|||
|
|
To: []string{"test@example.com"},
|
|||
|
|
Subject: "Test",
|
|||
|
|
Body: "<h1>Test</h1>",
|
|||
|
|
BodyType: interfaces.ContentTypeHTML,
|
|||
|
|
},
|
|||
|
|
wantErr: false,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "Valid text message",
|
|||
|
|
message: interfaces.Message{
|
|||
|
|
To: []string{"test@example.com"},
|
|||
|
|
Subject: "Test",
|
|||
|
|
Body: "Plain text",
|
|||
|
|
BodyType: interfaces.ContentTypeText,
|
|||
|
|
},
|
|||
|
|
wantErr: false,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "Message with inline images",
|
|||
|
|
message: interfaces.Message{
|
|||
|
|
To: []string{"test@example.com"},
|
|||
|
|
Subject: "Test",
|
|||
|
|
Body: "<img src='cid:test'>",
|
|||
|
|
InlineImage: []interfaces.Attachment{
|
|||
|
|
{
|
|||
|
|
Name: "test.jpg",
|
|||
|
|
CID: "test",
|
|||
|
|
Type: interfaces.AttachmentTypeInline,
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
wantErr: false,
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
name: "Empty recipients",
|
|||
|
|
message: interfaces.Message{
|
|||
|
|
Subject: "Test",
|
|||
|
|
Body: "Test body",
|
|||
|
|
},
|
|||
|
|
wantErr: true,
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for _, tt := range tests {
|
|||
|
|
t.Run(tt.name, func(t *testing.T) {
|
|||
|
|
err := smtpClient.validateMessage(tt.message)
|
|||
|
|
if (err != nil) != tt.wantErr {
|
|||
|
|
t.Errorf("validateMessage() error = %v, wantErr %v", err, tt.wantErr)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 测试多部分邮件构建
|
|||
|
|
func TestMultipartEmailConstruction2(t *testing.T) {
|
|||
|
|
smtpClient := NewSmtp()
|
|||
|
|
ctx := context.Background()
|
|||
|
|
|
|||
|
|
_, err := smtpClient.SetOption(ctx, func(opt *interfaces.Options) {
|
|||
|
|
opt.Smtp = &interfaces.EmailConfigDataSmtp{
|
|||
|
|
Host: "smtp.example.com",
|
|||
|
|
Port: "587",
|
|||
|
|
Username: "test@example.com",
|
|||
|
|
Password: "password",
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
if err != nil {
|
|||
|
|
t.Fatalf("Failed to configure SMTP: %v", err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
message := interfaces.Message{
|
|||
|
|
To: []string{"recipient@example.com"},
|
|||
|
|
Subject: "Multipart Test",
|
|||
|
|
BodyType: interfaces.ContentTypeHTML,
|
|||
|
|
Body: "<h1>HTML Content</h1><img src='cid:test-img'>",
|
|||
|
|
TextBody: "Plain text version",
|
|||
|
|
InlineImage: []interfaces.Attachment{
|
|||
|
|
{
|
|||
|
|
Data: []byte("fake-image-data"),
|
|||
|
|
ContentType: "image/jpeg",
|
|||
|
|
Name: "test.jpg",
|
|||
|
|
CID: "test-img",
|
|||
|
|
Type: interfaces.AttachmentTypeInline,
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
Attachment: []interfaces.Attachment{
|
|||
|
|
{
|
|||
|
|
Data: []byte("fake-pdf-data"),
|
|||
|
|
ContentType: "application/pdf",
|
|||
|
|
Name: "document.pdf",
|
|||
|
|
Type: interfaces.AttachmentTypeFile,
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 测试邮件构建(不实际发送)
|
|||
|
|
boundary := "test-boundary"
|
|||
|
|
headers := smtpClient.buildHeaders(message, boundary)
|
|||
|
|
|
|||
|
|
// 验证Content-Type
|
|||
|
|
contentType := headers["Content-Type"]
|
|||
|
|
if !strings.Contains(contentType, "multipart/related") {
|
|||
|
|
t.Errorf("Expected multipart/related for message with inline images, got %s", contentType)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 验证必要的头部字段
|
|||
|
|
requiredHeaders := []string{"From", "To", "Subject", "Date", "MIME-Version", "Content-Type"}
|
|||
|
|
for _, header := range requiredHeaders {
|
|||
|
|
if _, exists := headers[header]; !exists {
|
|||
|
|
t.Errorf("Missing required header: %s", header)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 测试字节数据附件
|
|||
|
|
func TestByteDataAttachment(t *testing.T) {
|
|||
|
|
attachment := interfaces.Attachment{
|
|||
|
|
Data: []byte("test file content"),
|
|||
|
|
ContentType: "text/plain",
|
|||
|
|
Name: "test.txt",
|
|||
|
|
Type: interfaces.AttachmentTypeFile,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if len(attachment.Data) == 0 {
|
|||
|
|
t.Error("Attachment data should not be empty")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if attachment.ContentType != "text/plain" {
|
|||
|
|
t.Errorf("Expected content type text/plain, got %s", attachment.ContentType)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 基准测试:HTML邮件构建性能
|
|||
|
|
func BenchmarkHTMLEmailConstruction(b *testing.B) {
|
|||
|
|
smtpClient := NewSmtp()
|
|||
|
|
ctx := context.Background()
|
|||
|
|
|
|||
|
|
smtpClient.SetOption(ctx, func(opt *interfaces.Options) {
|
|||
|
|
opt.Smtp = &interfaces.EmailConfigDataSmtp{
|
|||
|
|
Host: "smtp.example.com",
|
|||
|
|
Port: "587",
|
|||
|
|
Username: "test@example.com",
|
|||
|
|
Password: "password",
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
message := interfaces.Message{
|
|||
|
|
To: []string{"test@example.com"},
|
|||
|
|
Subject: "Benchmark Test",
|
|||
|
|
BodyType: interfaces.ContentTypeHTML,
|
|||
|
|
Body: "<h1>Benchmark</h1><p>Performance test content</p>",
|
|||
|
|
TextBody: "Benchmark - Performance test content",
|
|||
|
|
InlineImage: []interfaces.Attachment{
|
|||
|
|
{
|
|||
|
|
Data: make([]byte, 1024), // 1KB fake image
|
|||
|
|
ContentType: "image/jpeg",
|
|||
|
|
Name: "test.jpg",
|
|||
|
|
CID: "test-img",
|
|||
|
|
Type: interfaces.AttachmentTypeInline,
|
|||
|
|
},
|
|||
|
|
},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
b.ResetTimer()
|
|||
|
|
for i := 0; i < b.N; i++ {
|
|||
|
|
boundary := "benchmark-boundary"
|
|||
|
|
headers := smtpClient.buildHeaders(message, boundary)
|
|||
|
|
_ = headers
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 测试大附件处理
|
|||
|
|
func TestLargeAttachmentHandling(t *testing.T) {
|
|||
|
|
// 创建大附件数据(模拟)
|
|||
|
|
largeData := make([]byte, 1024*1024) // 1MB
|
|||
|
|
for i := range largeData {
|
|||
|
|
largeData[i] = byte(i % 256)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
attachment := interfaces.Attachment{
|
|||
|
|
Data: largeData,
|
|||
|
|
ContentType: "application/octet-stream",
|
|||
|
|
Name: "large_file.bin",
|
|||
|
|
Type: interfaces.AttachmentTypeFile,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if len(attachment.Data) != 1024*1024 {
|
|||
|
|
t.Errorf("Expected 1MB data, got %d bytes", len(attachment.Data))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 测试是否超过限制(25MB)
|
|||
|
|
maxSize := 25 * 1024 * 1024
|
|||
|
|
if len(attachment.Data) > maxSize {
|
|||
|
|
t.Errorf("Attachment size %d exceeds limit %d", len(attachment.Data), maxSize)
|
|||
|
|
}
|
|||
|
|
}
|