ultisuite-backend/internal/mail/imap/attachments_test.go
R3D347HR4Y bb5be669c1 Implement IMAP sync pipeline with rules and webhook support
- Introduced a new sync pipeline for IMAP that integrates a rules engine and webhook execution.
- Enhanced the `SyncWorker` to support attachment management and folder synchronization.
- Added functionality to detect special folder types (Sent, Drafts, Trash, Archive, Spam) during sync.
- Implemented a database schema for tracking rule executions and their outcomes.
- Created unit tests for the new rules engine and webhook execution logic.
- Updated migration scripts to accommodate new database structures for rule executions and folder states.
- Enhanced error handling and logging throughout the sync process for better observability.
2026-05-22 17:38:39 +02:00

158 lines
4.0 KiB
Go

package imap
import (
"encoding/base64"
"strings"
"testing"
)
func TestExtractAttachments_plainAttachment(t *testing.T) {
pdfData := []byte("%PDF-1.4\n")
raw := buildMultipartMessage(t, "mixed", []mimePart{
{
contentType: "text/plain",
body: []byte("Hello world"),
},
{
contentType: "application/pdf; name=\"doc.pdf\"",
disposition: "attachment; filename=\"doc.pdf\"",
body: pdfData,
transferEnc: "base64",
},
})
attachments, err := ExtractAttachments(raw)
if err != nil {
t.Fatalf("ExtractAttachments() error = %v", err)
}
if len(attachments) != 1 {
t.Fatalf("len(attachments) = %d, want 1", len(attachments))
}
att := attachments[0]
if att.Filename != "doc.pdf" {
t.Fatalf("Filename = %q, want doc.pdf", att.Filename)
}
if att.ContentType != "application/pdf" {
t.Fatalf("ContentType = %q, want application/pdf", att.ContentType)
}
if att.IsInline {
t.Fatal("IsInline = true, want false")
}
if att.ContentID != "" {
t.Fatalf("ContentID = %q, want empty", att.ContentID)
}
if string(att.Data) != string(pdfData) {
t.Fatalf("Data = %q, want %q", att.Data, pdfData)
}
}
func TestExtractAttachments_inlineWithCID(t *testing.T) {
pngData := []byte{0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n'}
raw := buildMultipartMessage(t, "related", []mimePart{
{
contentType: "text/html",
body: []byte(`<html><body><img src="cid:logo@cid"></body></html>`),
},
{
contentType: "image/png; name=\"logo.png\"",
disposition: "inline; filename=\"logo.png\"",
contentID: "<logo@cid>",
body: pngData,
transferEnc: "base64",
},
})
attachments, err := ExtractAttachments(raw)
if err != nil {
t.Fatalf("ExtractAttachments() error = %v", err)
}
if len(attachments) != 1 {
t.Fatalf("len(attachments) = %d, want 1", len(attachments))
}
att := attachments[0]
if att.Filename != "logo.png" {
t.Fatalf("Filename = %q, want logo.png", att.Filename)
}
if att.ContentType != "image/png" {
t.Fatalf("ContentType = %q, want image/png", att.ContentType)
}
if !att.IsInline {
t.Fatal("IsInline = false, want true")
}
if att.ContentID != "logo@cid" {
t.Fatalf("ContentID = %q, want logo@cid", att.ContentID)
}
if string(att.Data) != string(pngData) {
t.Fatalf("Data = %q, want %q", att.Data, pngData)
}
}
func TestExtractAttachments_skipsBodyParts(t *testing.T) {
raw := buildMultipartMessage(t, "alternative", []mimePart{
{
contentType: "text/plain",
body: []byte("plain body"),
},
{
contentType: "text/html",
body: []byte("<p>html body</p>"),
},
})
attachments, err := ExtractAttachments(raw)
if err != nil {
t.Fatalf("ExtractAttachments() error = %v", err)
}
if len(attachments) != 0 {
t.Fatalf("len(attachments) = %d, want 0", len(attachments))
}
}
type mimePart struct {
contentType string
disposition string
contentID string
transferEnc string
body []byte
}
func buildMultipartMessage(t *testing.T, subtype string, parts []mimePart) []byte {
t.Helper()
const boundary = "test-boundary"
var b strings.Builder
b.WriteString("From: sender@example.com\r\n")
b.WriteString("To: recipient@example.com\r\n")
b.WriteString("Subject: attachment test\r\n")
b.WriteString("MIME-Version: 1.0\r\n")
b.WriteString("Content-Type: multipart/" + subtype + "; boundary=\"" + boundary + "\"\r\n")
b.WriteString("\r\n")
for _, part := range parts {
b.WriteString("--" + boundary + "\r\n")
b.WriteString("Content-Type: " + part.contentType + "\r\n")
if part.disposition != "" {
b.WriteString("Content-Disposition: " + part.disposition + "\r\n")
}
if part.contentID != "" {
b.WriteString("Content-ID: " + part.contentID + "\r\n")
}
if part.transferEnc != "" {
b.WriteString("Content-Transfer-Encoding: " + part.transferEnc + "\r\n")
}
b.WriteString("\r\n")
if part.transferEnc == "base64" {
b.WriteString(base64.StdEncoding.EncodeToString(part.body))
} else {
b.Write(part.body)
}
b.WriteString("\r\n")
}
b.WriteString("--" + boundary + "--\r\n")
return []byte(b.String())
}