diff --git a/msg_test.go b/msg_test.go index 7fcbd99..1d4cf58 100644 --- a/msg_test.go +++ b/msg_test.go @@ -5,44 +5,241 @@ package mail import ( - "bufio" - "bytes" "embed" - "errors" "fmt" - htpl "html/template" - "io" - "net/mail" - "os" - "sort" + "reflect" "strings" "testing" - ttpl "text/template" - "time" ) //go:embed README.md var efs embed.FS -// TestNewMsg tests the NewMsg method +/* +addrHeader: make(map[AddrHeader][]*mail.Address), +charset: CharsetUTF8, +encoding: EncodingQP, +genHeader: make(map[Header][]string), +preformHeader: make(map[Header]string), +mimever: MIME10, +*/ func TestNewMsg(t *testing.T) { - m := NewMsg() - var err error - if m.encoding != EncodingQP { - err = fmt.Errorf("default encoding is not Quoted-Prinable") - } - if m.charset != CharsetUTF8 { - err = fmt.Errorf("default charset is not UTF-8") - } + t.Run("create new message", func(t *testing.T) { + message := NewMsg() + if message == nil { + t.Fatal("message is nil") + } + if message.addrHeader == nil { + t.Errorf("address header map is nil") + } + if message.genHeader == nil { + t.Errorf("generic header map is nil") + } + if message.preformHeader == nil { + t.Errorf("preformatted header map is nil") + } + if message.charset != CharsetUTF8 { + t.Errorf("default charset for new Msg mismatch. Expected: %s, got: %s", CharsetUTF8, + message.charset) + } + if message.encoding != EncodingQP { + t.Errorf("default encoding for new Msg mismatch. Expected: %s, got: %s", EncodingQP, + message.encoding) + } + if message.mimever != MIME10 { + t.Errorf("default MIME version for new Msg mismatch. Expected: %s, got: %s", MIME10, + message.mimever) + } + if reflect.TypeOf(message.encoder).String() != "mime.WordEncoder" { + t.Errorf("default encoder for new Msg mismatch. Expected: %s, got: %s", "mime.WordEncoder", + reflect.TypeOf(message.encoder).String()) + } + if !strings.EqualFold(message.encoder.Encode(message.charset.String(), "ab12§$/"), + `=?UTF-8?q?ab12=C2=A7$/?=`) { + t.Errorf("default encoder for new Msg mismatch. QP encoded expected string: %s, got: %s", + `=?UTF-8?q?ab12=C2=A7$/?=`, message.encoder.Encode(message.charset.String(), "ab12§$/")) + } + }) + t.Run("new message with nil option", func(t *testing.T) { + message := NewMsg(nil) + if message == nil { + t.Fatal("message is nil") + } + }) + t.Run("new message with custom charsets", func(t *testing.T) { + tests := []struct { + name string + value Charset + want Charset + }{ + {"charset is UTF-7", CharsetUTF7, "UTF-7"}, + {"charset is UTF-8", CharsetUTF8, "UTF-8"}, + {"charset is US-ASCII", CharsetASCII, "US-ASCII"}, + {"charset is ISO-8859-1", CharsetISO88591, "ISO-8859-1"}, + {"charset is ISO-8859-2", CharsetISO88592, "ISO-8859-2"}, + {"charset is ISO-8859-3", CharsetISO88593, "ISO-8859-3"}, + {"charset is ISO-8859-4", CharsetISO88594, "ISO-8859-4"}, + {"charset is ISO-8859-5", CharsetISO88595, "ISO-8859-5"}, + {"charset is ISO-8859-6", CharsetISO88596, "ISO-8859-6"}, + {"charset is ISO-8859-7", CharsetISO88597, "ISO-8859-7"}, + {"charset is ISO-8859-9", CharsetISO88599, "ISO-8859-9"}, + {"charset is ISO-8859-13", CharsetISO885913, "ISO-8859-13"}, + {"charset is ISO-8859-14", CharsetISO885914, "ISO-8859-14"}, + {"charset is ISO-8859-15", CharsetISO885915, "ISO-8859-15"}, + {"charset is ISO-8859-16", CharsetISO885916, "ISO-8859-16"}, + {"charset is ISO-2022-JP", CharsetISO2022JP, "ISO-2022-JP"}, + {"charset is ISO-2022-KR", CharsetISO2022KR, "ISO-2022-KR"}, + {"charset is windows-1250", CharsetWindows1250, "windows-1250"}, + {"charset is windows-1251", CharsetWindows1251, "windows-1251"}, + {"charset is windows-1252", CharsetWindows1252, "windows-1252"}, + {"charset is windows-1255", CharsetWindows1255, "windows-1255"}, + {"charset is windows-1256", CharsetWindows1256, "windows-1256"}, + {"charset is KOI8-R", CharsetKOI8R, "KOI8-R"}, + {"charset is KOI8-U", CharsetKOI8U, "KOI8-U"}, + {"charset is Big5", CharsetBig5, "Big5"}, + {"charset is GB18030", CharsetGB18030, "GB18030"}, + {"charset is GB2312", CharsetGB2312, "GB2312"}, + {"charset is TIS-620", CharsetTIS620, "TIS-620"}, + {"charset is EUC-KR", CharsetEUCKR, "EUC-KR"}, + {"charset is Shift_JIS", CharsetShiftJIS, "Shift_JIS"}, + {"charset is GBK", CharsetGBK, "GBK"}, + {"charset is Unknown", CharsetUnknown, "Unknown"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + message := NewMsg(WithCharset(tt.value), nil) + if message == nil { + t.Fatal("message is nil") + } + if message.charset != tt.want { + t.Fatalf("NewMsg(WithCharset(%s)) failed. Expected charset: %s, got: %s", tt.value, tt.want, + message.charset) + } + }) + } + }) + t.Run("new message with custom encoding", func(t *testing.T) { + tests := []struct { + name string + value Encoding + want Encoding + }{ + {"encoding is Quoted-Printable", EncodingQP, "quoted-printable"}, + {"encoding is Base64", EncodingB64, "base64"}, + {"encoding is Unencoded 8-Bit", NoEncoding, "8bit"}, + {"encoding is US-ASCII 7-Bit", EncodingUSASCII, "7bit"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + message := NewMsg(WithEncoding(tt.value), nil) + if message == nil { + t.Fatal("message is nil") + } + if message.encoding != tt.want { + t.Errorf("NewMsg(WithEncoding(%s)) failed. Expected encoding: %s, got: %s", tt.value, + tt.want, message.encoding) + } + }) + } + }) + t.Run("new message with custom MIME version", func(t *testing.T) { + tests := []struct { + name string + value MIMEVersion + want MIMEVersion + }{ + {"MIME version: 1.0", MIME10, "1.0"}, + {"MIME version: 1.1", MIMEVersion("1.1"), "1.1"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + message := NewMsg(WithMIMEVersion(tt.value)) + if message == nil { + t.Fatal("message is nil") + } + if message.mimever != tt.want { + t.Errorf("NewMsg(WithMIMEVersion(%s)) failed. Expected MIME version: %s, got: %s", + tt.value, tt.want, message.mimever) + } + }) + } + }) + t.Run("new message with custom boundary", func(t *testing.T) { + tests := []struct { + name string + value string + }{ + {"boundary: test123", "test123"}, + {"boundary is empty", ""}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + message := NewMsg(WithBoundary(tt.value)) + if message == nil { + t.Fatal("message is nil") + } + if message.boundary != tt.value { + t.Errorf("NewMsg(WithBoundary(%s)) failed. Expected boundary: %s, got: %s", tt.value, + tt.value, message.boundary) + } + }) + } + }) + t.Run("new message with custom PGP type", func(t *testing.T) { + tests := []struct { + name string + value PGPType + }{ + {"Not a PGP encoded message", NoPGP}, + {"PGP encrypted message", PGPEncrypt}, + {"PGP signed message", PGPSignature}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + message := NewMsg(WithPGPType(tt.value)) + if message == nil { + t.Fatal("message is nil") + } + if message.pgptype != tt.value { + t.Errorf("NewMsg(WithPGPType(%d)) failed. Expected PGP type: %d, got: %d", tt.value, + tt.value, message.pgptype) + } + }) + } + }) + t.Run("new message with middleware: uppercase", func(t *testing.T) { + message := NewMsg() + if message == nil { + t.Fatal("message is nil") + } + if len(message.middlewares) != 0 { + t.Errorf("NewMsg() failed. Expected empty middlewares, got: %d", len(message.middlewares)) + } + message = NewMsg(WithMiddleware(uppercaseMiddleware{})) + if len(message.middlewares) != 1 { + t.Errorf("NewMsg(WithMiddleware(uppercaseMiddleware{})) failed. Expected 1 middleware, got: %d", + len(message.middlewares)) + } + message = NewMsg(WithMiddleware(uppercaseMiddleware{}), WithMiddleware(encodeMiddleware{})) + if len(message.middlewares) != 2 { + t.Errorf("NewMsg(WithMiddleware(uppercaseMiddleware{}),WithMiddleware(encodeMiddleware{})) "+ + "failed. Expected 2 middleware, got: %d", len(message.middlewares)) + } + }) + t.Run("new message without default user-agent", func(t *testing.T) { + message := NewMsg(WithNoDefaultUserAgent()) + if message == nil { + t.Fatal("message is nil") + } + if !message.noDefaultUserAgent { + t.Errorf("NewMsg(WithNoDefaultUserAgent()) failed. Expected noDefaultUserAgent to be true, got: %t", + message.noDefaultUserAgent) + } - if err != nil { - t.Errorf("NewMsg() failed: %s", err) - return - } + }) } -// TestNewMsgCharset tests WithCharset and Msg.SetCharset -func TestNewMsgCharset(t *testing.T) { +func TestMsg_SetCharset(t *testing.T) { tests := []struct { name string value Charset @@ -81,27 +278,37 @@ func TestNewMsgCharset(t *testing.T) { {"charset is GBK", CharsetGBK, "GBK"}, {"charset is Unknown", CharsetUnknown, "Unknown"}, } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - m := NewMsg(WithCharset(tt.value), nil) - if m.charset != tt.want { - t.Errorf("WithCharset() failed. Expected: %s, got: %s", tt.want, m.charset) - } - m.SetCharset(CharsetUTF8) - if m.charset != CharsetUTF8 { - t.Errorf("SetCharset() failed. Expected: %s, got: %s", CharsetUTF8, m.charset) - } - m.SetCharset(tt.value) - if m.charset != tt.want { - t.Errorf("SetCharset() failed. Expected: %s, got: %s", tt.want, m.charset) - } - }) - } + t.Run("SetCharset on new message", func(t *testing.T) { + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + message := NewMsg() + if message == nil { + t.Fatal("message is nil") + } + message.SetCharset(tt.value) + if message.charset != tt.want { + t.Errorf("failed to set charset. Expected: %s, got: %s", tt.want, message.charset) + } + }) + } + }) + t.Run("SetCharset to override WithCharset", func(t *testing.T) { + message := NewMsg(WithCharset(CharsetUTF7)) + if message == nil { + t.Fatal("message is nil") + } + if message.charset != CharsetUTF7 { + t.Errorf("failed to set charset on message creation. Expected: %s, got: %s", CharsetUTF7, + message.charset) + } + message.SetCharset(CharsetUTF8) + if message.charset != CharsetUTF8 { + t.Errorf("failed to set charset. Expected: %s, got: %s", CharsetUTF8, message.charset) + } + }) } -// TestNewMsgWithCharset tests WithEncoding and Msg.SetEncoding -func TestNewMsgWithEncoding(t *testing.T) { +func TestMsg_SetEncoding(t *testing.T) { tests := []struct { name string value Encoding @@ -110,105 +317,3194 @@ func TestNewMsgWithEncoding(t *testing.T) { {"encoding is Quoted-Printable", EncodingQP, "quoted-printable"}, {"encoding is Base64", EncodingB64, "base64"}, {"encoding is Unencoded 8-Bit", NoEncoding, "8bit"}, + {"encoding is US-ASCII 7-Bit", EncodingUSASCII, "7bit"}, } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - m := NewMsg(WithEncoding(tt.value)) - if m.encoding != tt.want { - t.Errorf("WithEncoding() failed. Expected: %s, got: %s", tt.want, m.encoding) - } - m.SetEncoding(NoEncoding) - if m.encoding != NoEncoding { - t.Errorf("SetEncoding() failed. Expected: %s, got: %s", NoEncoding, m.encoding) - } - m.SetEncoding(tt.want) - if m.encoding != tt.want { - t.Errorf("SetEncoding() failed. Expected: %s, got: %s", tt.want, m.encoding) - } - }) - } + t.Run("SetEncoding on new message", func(t *testing.T) { + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + message := NewMsg() + if message == nil { + t.Fatal("message is nil") + } + message.SetEncoding(tt.value) + if message.encoding != tt.want { + t.Errorf("failed to set encoding. Expected: %s, got: %s", tt.want, message.encoding) + } + }) + } + }) + t.Run("SetEncoding to override WithEncoding", func(t *testing.T) { + message := NewMsg(WithEncoding(EncodingUSASCII)) + if message == nil { + t.Fatal("message is nil") + } + if message.encoding != EncodingUSASCII { + t.Errorf("failed to set encoding on message creation. Expected: %s, got: %s", EncodingUSASCII, + message.encoding) + } + message.SetEncoding(EncodingB64) + if message.encoding != EncodingB64 { + t.Errorf("failed to set encoding. Expected: %s, got: %s", EncodingB64, message.encoding) + } + }) } -// TestNewMsgWithMIMEVersion tests WithMIMEVersion and Msg.SetMIMEVersion -func TestNewMsgWithMIMEVersion(t *testing.T) { - tests := []struct { - name string - value MIMEVersion - want MIMEVersion - }{ - {"MIME version is 1.0", MIME10, "1.0"}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - m := NewMsg(WithMIMEVersion(tt.value)) - if m.mimever != tt.want { - t.Errorf("WithMIMEVersion() failed. Expected: %s, got: %s", tt.want, m.mimever) - } - m.mimever = "" - m.SetMIMEVersion(tt.value) - if m.mimever != tt.want { - t.Errorf("SetMIMEVersion() failed. Expected: %s, got: %s", tt.want, m.mimever) - } - }) - } -} - -// TestNewMsgWithBoundary tests WithBoundary and Msg.SetBoundary -func TestNewMsgWithBoundary(t *testing.T) { +func TestMsg_SetBoundary(t *testing.T) { tests := []struct { name string value string }{ - {"boundary is test123", "test123"}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - m := NewMsg(WithBoundary(tt.value)) - if m.boundary != tt.value { - t.Errorf("WithBoundary() failed. Expected: %s, got: %s", tt.value, m.boundary) - } - m.boundary = "" - m.SetBoundary(tt.value) - if m.boundary != tt.value { - t.Errorf("SetBoundary() failed. Expected: %s, got: %s", tt.value, m.boundary) - } - }) + {"boundary: test123", "test123"}, + {"boundary is empty", ""}, } + t.Run("SetBoundary on new message", func(t *testing.T) { + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + message := NewMsg() + if message == nil { + t.Fatal("message is nil") + } + message.SetBoundary(tt.value) + if message.boundary != tt.value { + t.Errorf("failed to set boundary. Expected: %s, got: %s", tt.value, message.boundary) + } + }) + } + }) + t.Run("SetBoundary to override WithBoundary", func(t *testing.T) { + message := NewMsg(WithBoundary("123Test")) + if message == nil { + t.Fatal("message is nil") + } + if message.boundary != "123Test" { + t.Errorf("failed to set boundary on message creation. Expected: %s, got: %s", "123Test", + message.boundary) + } + message.SetBoundary("test123") + if message.boundary != "test123" { + t.Errorf("failed to set boundary. Expected: %s, got: %s", "test123", message.boundary) + } + }) } -// TestNewMsg_WithPGPType tests WithPGPType option -func TestNewMsg_WithPGPType(t *testing.T) { - tests := []struct { - name string - pt PGPType - hpt bool - }{ - {"Not a PGP encoded message", NoPGP, false}, - {"PGP encrypted message", PGPEncrypt, true}, - {"PGP signed message", PGPSignature, true}, +/* +// TestNewMsgWithMiddleware tests WithMiddleware + + func TestNewMsgWithMiddleware(t *testing.T) { + m := NewMsg() + if len(m.middlewares) != 0 { + t.Errorf("empty middlewares failed. m.middlewares expected to be: empty, got: %d middleware", len(m.middlewares)) + } + m = NewMsg(WithMiddleware(uppercaseMiddleware{})) + if len(m.middlewares) != 1 { + t.Errorf("empty middlewares failed. m.middlewares expected to be: 1, got: %d middleware", len(m.middlewares)) + } + m = NewMsg(WithMiddleware(uppercaseMiddleware{}), WithMiddleware(encodeMiddleware{})) + if len(m.middlewares) != 2 { + t.Errorf("empty middlewares failed. m.middlewares expected to be: 2, got: %d middleware", len(m.middlewares)) + } } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - m := NewMsg(WithPGPType(tt.pt)) - if m.pgptype != tt.pt { - t.Errorf("WithPGPType() failed. Expected: %d, got: %d", tt.pt, m.pgptype) +// TestApplyMiddlewares tests the applyMiddlewares for the Msg object + + func TestApplyMiddlewares(t *testing.T) { + tests := []struct { + name string + sub string + want string + }{ + {"normal subject", "This is a test subject", "THIS IS @ TEST SUBJECT"}, + {"subject with one middleware effect", "This is test subject", "THIS IS TEST SUBJECT"}, + {"subject with one middleware effect", "This is A test subject", "THIS IS A TEST SUBJECT"}, + } + m := NewMsg(WithMiddleware(encodeMiddleware{}), WithMiddleware(uppercaseMiddleware{})) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m.Subject(tt.sub) + if m.genHeader[HeaderSubject] == nil { + t.Errorf("Subject() method failed in applyMiddlewares() test. Generic header for subject is empty") + return + } + m = m.applyMiddlewares(m) + s, ok := m.genHeader[HeaderSubject] + if !ok { + t.Errorf("failed to get subject header") + } + if s[0] != tt.want { + t.Errorf("applyMiddlewares() method failed. Expected: %s, got: %s", tt.want, s[0]) + } + }) + } + } + +// TestMsg_SetGenHeader tests Msg.SetGenHeader + + func TestMsg_SetGenHeader(t *testing.T) { + tests := []struct { + name string + header Header + values []string + }{ + {"set subject", HeaderSubject, []string{"This is Subject"}}, + {"set content-language", HeaderContentLang, []string{"en", "de", "fr", "es"}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := NewMsg() + m.SetGenHeader(tt.header, tt.values...) + if m.genHeader[tt.header] == nil { + t.Errorf("SetGenHeader() failed. Tried to set header %s, but it is empty", tt.header) + return + } + for _, v := range tt.values { + found := false + for _, hv := range m.genHeader[tt.header] { + if hv == v { + found = true + } + } + if !found { + t.Errorf("SetGenHeader() failed. Value %s not found in header field", v) + } + } + }) + } + } + +// TestMsg_SetGenHeaderPreformatted tests Msg.SetGenHeaderPreformatted + + func TestMsg_SetGenHeaderPreformatted(t *testing.T) { + tests := []struct { + name string + header Header + value string + }{ + {"set subject", HeaderSubject, "This is Subject"}, + {"set content-language", HeaderContentLang, fmt.Sprintf("%s, %s, %s, %s", + "en", "de", "fr", "es")}, + {"set subject with newline", HeaderSubject, "This is Subject\r\n with 2nd line"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := &Msg{} + m.SetGenHeaderPreformatted(tt.header, tt.value) + m = NewMsg() + m.SetGenHeaderPreformatted(tt.header, tt.value) + if m.preformHeader[tt.header] == "" { + t.Errorf("SetGenHeaderPreformatted() failed. Tried to set header %s, but it is empty", tt.header) + } + if m.preformHeader[tt.header] != tt.value { + t.Errorf("SetGenHeaderPreformatted() failed. Expected: %q, got: %q", tt.value, + m.preformHeader[tt.header]) + } + buf := bytes.Buffer{} + _, err := m.WriteTo(&buf) + if err != nil { + t.Errorf("failed to write message to memory: %s", err) + return + } + if !strings.Contains(buf.String(), fmt.Sprintf("%s: %s%s", tt.header, tt.value, SingleNewLine)) { + t.Errorf("SetGenHeaderPreformatted() failed. Unable to find correctly formated header in " + + "mail message output") + } + }) + } + } + +// TestMsg_AddTo tests the Msg.AddTo method + + func TestMsg_AddTo(t *testing.T) { + a := []string{"address1@example.com", "address2@example.com"} + na := "address3@example.com" + m := NewMsg() + if err := m.To(a...); err != nil { + t.Errorf("failed to set TO addresses: %s", err) + return + } + if err := m.AddTo(na); err != nil { + t.Errorf("AddTo failed: %s", err) + return + } + + atf := false + for _, v := range m.addrHeader[HeaderTo] { + if v.Address == na { + atf = true } - m.pgptype = 99 - m.SetPGPType(tt.pt) - if m.pgptype != tt.pt { - t.Errorf("SetPGPType() failed. Expected: %d, got: %d", tt.pt, m.pgptype) + } + if !atf { + t.Errorf("AddTo() failed. Address %q not found in TO address slice.", na) + } + } + +// TestMsg_From tests the Msg.From and Msg.GetSender methods + + func TestMsg_From(t *testing.T) { + a := "toni@example.com" + n := "Toni Tester" + na := fmt.Sprintf(`"%s" <%s>`, n, a) + m := NewMsg() + + _, err := m.GetSender(false) + if err == nil { + t.Errorf("GetSender(false) without a set From address succeeded but was expected to fail") + return + } + + if err := m.From(na); err != nil { + t.Errorf("failed to set FROM addresses: %s", err) + return + } + gs, err := m.GetSender(false) + if err != nil { + t.Errorf("GetSender(false) failed: %s", err) + return + } + if gs != a { + t.Errorf("From() failed. Expected: %s, got: %s", a, gs) + return + } + + gs, err = m.GetSender(true) + if err != nil { + t.Errorf("GetSender(true) failed: %s", err) + return + } + if gs != na { + t.Errorf("From() failed. Expected: %s, got: %s", na, gs) + return + } + } + +// TestMsg_EnvelopeFrom tests the Msg.EnvelopeFrom and Msg.GetSender methods + + func TestMsg_EnvelopeFrom(t *testing.T) { + e := "envelope@example.com" + a := "toni@example.com" + n := "Toni Tester" + na := fmt.Sprintf(`"%s" <%s>`, n, a) + ne := fmt.Sprintf(`<%s>`, e) + m := NewMsg() + + _, err := m.GetSender(false) + if err == nil { + t.Errorf("GetSender(false) without a set envelope From address succeeded but was expected to fail") + return + } + + if err := m.EnvelopeFrom(e); err != nil { + t.Errorf("failed to set envelope FROM addresses: %s", err) + return + } + gs, err := m.GetSender(false) + if err != nil { + t.Errorf("GetSender(false) failed: %s", err) + return + } + if gs != e { + t.Errorf("From() failed. Expected: %s, got: %s", e, gs) + return + } + + if err := m.From(na); err != nil { + t.Errorf("failed to set FROM addresses: %s", err) + return + } + gs, err = m.GetSender(false) + if err != nil { + t.Errorf("GetSender(false) failed: %s", err) + return + } + if gs != e { + t.Errorf("From() failed. Expected: %s, got: %s", e, gs) + return + } + + gs, err = m.GetSender(true) + if err != nil { + t.Errorf("GetSender(true) failed: %s", err) + return + } + if gs != ne { + t.Errorf("From() failed. Expected: %s, got: %s", ne, gs) + return + } + m.Reset() + + if err := m.From(na); err != nil { + t.Errorf("failed to set FROM addresses: %s", err) + return + } + gs, err = m.GetSender(false) + if err != nil { + t.Errorf("GetSender(true) failed: %s", err) + return + } + if gs != a { + t.Errorf("From() failed. Expected: %s, got: %s", a, gs) + return + } + gs, err = m.GetSender(true) + if err != nil { + t.Errorf("GetSender(true) failed: %s", err) + return + } + if gs != na { + t.Errorf("From() failed. Expected: %s, got: %s", na, gs) + return + } + } + +// TestMsg_AddToFormat tests the Msg.AddToFormat method + + func TestMsg_AddToFormat(t *testing.T) { + a := []string{"address1@example.com", "address2@example.com"} + nn := "Toni Tester" + na := "address3@example.com" + w := `"Toni Tester" ` + m := NewMsg() + if err := m.To(a...); err != nil { + t.Errorf("failed to set TO addresses: %s", err) + return + } + if err := m.AddToFormat(nn, na); err != nil { + t.Errorf("AddToFormat failed: %s", err) + return + } + + atf := false + for _, v := range m.addrHeader[HeaderTo] { + if v.String() == w { + atf = true } - if m.hasPGPType() != tt.hpt { - t.Errorf("hasPGPType() failed. Expected %t, got: %t", tt.hpt, m.hasPGPType()) + } + if !atf { + t.Errorf("AddToFormat() failed. Address %q not found in TO address slice.", w) + } + } + +// TestMsg_ToIgnoreInvalid tests the Msg.ToIgnoreInvalid method + + func TestMsg_ToIgnoreInvalid(t *testing.T) { + a := []string{"address1@example.com", "address2@example.com"} + fa := []string{"address1@example.com", "address2@example.com", "failedaddress.com"} + m := NewMsg() + + m.ToIgnoreInvalid(a...) + l := len(m.addrHeader[HeaderTo]) + if l != len(a) { + t.Errorf("ToIgnoreInvalid() failed. Expected %d addresses, got: %d", len(a), l) + } + m.ToIgnoreInvalid(fa...) + l = len(m.addrHeader[HeaderTo]) + if l != len(fa)-1 { + t.Errorf("ToIgnoreInvalid() failed. Expected %d addresses, got: %d", len(fa)-1, l) + } + } + +// TestMsg_AddCc tests the Msg.AddCc method + + func TestMsg_AddCc(t *testing.T) { + a := []string{"address1@example.com", "address2@example.com"} + na := "address3@example.com" + m := NewMsg() + if err := m.Cc(a...); err != nil { + t.Errorf("failed to set CC addresses: %s", err) + return + } + if err := m.AddCc(na); err != nil { + t.Errorf("AddCc failed: %s", err) + return + } + + atf := false + for _, v := range m.addrHeader[HeaderCc] { + if v.Address == na { + atf = true } + } + if !atf { + t.Errorf("AddCc() failed. Address %q not found in CC address slice.", na) + } + } + +// TestMsg_AddCcFormat tests the Msg.AddCcFormat method + + func TestMsg_AddCcFormat(t *testing.T) { + a := []string{"address1@example.com", "address2@example.com"} + nn := "Toni Tester" + na := "address3@example.com" + w := `"Toni Tester" ` + m := NewMsg() + if err := m.Cc(a...); err != nil { + t.Errorf("failed to set CC addresses: %s", err) + return + } + if err := m.AddCcFormat(nn, na); err != nil { + t.Errorf("AddCcFormat failed: %s", err) + return + } + + atf := false + for _, v := range m.addrHeader[HeaderCc] { + if v.String() == w { + atf = true + } + } + if !atf { + t.Errorf("AddCcFormat() failed. Address %q not found in CC address slice.", w) + } + } + +// TestMsg_CcIgnoreInvalid tests the Msg.CcIgnoreInvalid method + + func TestMsg_CcIgnoreInvalid(t *testing.T) { + a := []string{"address1@example.com", "address2@example.com"} + fa := []string{"address1@example.com", "address2@example.com", "failedaddress.com"} + m := NewMsg() + + m.CcIgnoreInvalid(a...) + l := len(m.addrHeader[HeaderCc]) + if l != len(a) { + t.Errorf("CcIgnoreInvalid() failed. Expected %d addresses, got: %d", len(a), l) + } + m.CcIgnoreInvalid(fa...) + l = len(m.addrHeader[HeaderCc]) + if l != len(fa)-1 { + t.Errorf("CcIgnoreInvalid() failed. Expected %d addresses, got: %d", len(fa)-1, l) + } + } + +// TestMsg_AddBcc tests the Msg.AddBcc method + + func TestMsg_AddBcc(t *testing.T) { + a := []string{"address1@example.com", "address2@example.com"} + na := "address3@example.com" + m := NewMsg() + if err := m.Bcc(a...); err != nil { + t.Errorf("failed to set BCC addresses: %s", err) + return + } + if err := m.AddBcc(na); err != nil { + t.Errorf("AddBcc failed: %s", err) + return + } + + atf := false + for _, v := range m.addrHeader[HeaderBcc] { + if v.Address == na { + atf = true + } + } + if !atf { + t.Errorf("AddBcc() failed. Address %q not found in BCC address slice.", na) + } + } + +// TestMsg_AddBccFormat tests the Msg.AddBccFormat method + + func TestMsg_AddBccFormat(t *testing.T) { + a := []string{"address1@example.com", "address2@example.com"} + nn := "Toni Tester" + na := "address3@example.com" + w := `"Toni Tester" ` + m := NewMsg() + if err := m.Bcc(a...); err != nil { + t.Errorf("failed to set BCC addresses: %s", err) + return + } + if err := m.AddBccFormat(nn, na); err != nil { + t.Errorf("AddBccFormat failed: %s", err) + return + } + + atf := false + for _, v := range m.addrHeader[HeaderBcc] { + if v.String() == w { + atf = true + } + } + if !atf { + t.Errorf("AddBccFormat() failed. Address %q not found in BCC address slice.", w) + } + } + +// TestMsg_BccIgnoreInvalid tests the Msg.BccIgnoreInvalid method + + func TestMsg_BccIgnoreInvalid(t *testing.T) { + a := []string{"address1@example.com", "address2@example.com"} + fa := []string{"address1@example.com", "address2@example.com", "failedaddress.com"} + m := NewMsg() + + m.BccIgnoreInvalid(a...) + l := len(m.addrHeader[HeaderBcc]) + if l != len(a) { + t.Errorf("BccIgnoreInvalid() failed. Expected %d addresses, got: %d", len(a), l) + } + m.BccIgnoreInvalid(fa...) + l = len(m.addrHeader[HeaderBcc]) + if l != len(fa)-1 { + t.Errorf("BccIgnoreInvalid() failed. Expected %d addresses, got: %d", len(fa)-1, l) + } + } + +// TestMsg_SetBulk tests the Msg.SetBulk method + + func TestMsg_SetBulk(t *testing.T) { + m := NewMsg() + m.SetBulk() + if m.genHeader[HeaderPrecedence] == nil { + t.Errorf("SetBulk() failed. Precedence header is nil") + return + } + if m.genHeader[HeaderPrecedence][0] != "bulk" { + t.Errorf("SetBulk() failed. Expected Precedence header: %q, got: %q", "bulk", + m.genHeader[HeaderPrecedence][0]) + } + if m.genHeader[HeaderXAutoResponseSuppress] == nil { + t.Errorf("SetBulk() failed. X-Auto-Response-Suppress header is nil") + return + } + if m.genHeader[HeaderXAutoResponseSuppress][0] != "All" { + t.Errorf("SetBulk() failed. Expected X-Auto-Response-Suppress header: %q, got: %q", "All", + m.genHeader[HeaderXAutoResponseSuppress][0]) + } + } + +// TestMsg_SetDate tests the Msg.SetDate and Msg.SetDateWithValue method + + func TestMsg_SetDate(t *testing.T) { + m := NewMsg() + m.SetDate() + if m.genHeader[HeaderDate] == nil { + t.Errorf("SetDate() failed. Date header is nil") + return + } + d, ok := m.genHeader[HeaderDate] + if !ok { + t.Errorf("failed to get date header") + return + } + _, err := time.Parse(time.RFC1123Z, d[0]) + if err != nil { + t.Errorf("failed to parse time in date header: %s", err) + } + m.genHeader = nil + m.genHeader = make(map[Header][]string) + + now := time.Now() + m.SetDateWithValue(now) + if m.genHeader[HeaderDate] == nil { + t.Errorf("SetDateWithValue() failed. Date header is nil") + return + } + d, ok = m.genHeader[HeaderDate] + if !ok { + t.Errorf("failed to get date header") + return + } + pt, err := time.Parse(time.RFC1123Z, d[0]) + if err != nil { + t.Errorf("failed to parse time in date header: %s", err) + } + if pt.Unix() != now.Unix() { + t.Errorf("SetDateWithValue() failed. Expected time: %d, got: %d", now.Unix(), + pt.Unix()) + } + } + +// TestMsg_SetMessageIDWIthValue tests the Msg.SetMessageIDWithValue and Msg.SetMessageID methods + + func TestMsg_SetMessageIDWithValue(t *testing.T) { + m := NewMsg() + m.SetMessageID() + if m.genHeader[HeaderMessageID] == nil { + t.Errorf("SetMessageID() failed. MessageID header is nil") + return + } + if m.genHeader[HeaderMessageID][0] == "" { + t.Errorf("SetMessageID() failed. Expected value, got: empty") + return + } + if _, ok := m.genHeader[HeaderMessageID]; ok { + m.genHeader[HeaderMessageID] = nil + } + v := "This.is.a.message.id" + vf := "" + m.SetMessageIDWithValue(v) + if m.genHeader[HeaderMessageID] == nil { + t.Errorf("SetMessageIDWithValue() failed. MessageID header is nil") + return + } + if m.genHeader[HeaderMessageID][0] != vf { + t.Errorf("SetMessageIDWithValue() failed. Expected: %s, got: %s", vf, m.genHeader[HeaderMessageID][0]) + return + } + } + +// TestMsg_SetMessageIDRandomness tests the randomness of Msg.SetMessageID methods + + func TestMsg_SetMessageIDRandomness(t *testing.T) { + var mids []string + m := NewMsg() + for i := 0; i < 50_000; i++ { + m.SetMessageID() + mid := m.GetMessageID() + mids = append(mids, mid) + } + c := make(map[string]int) + for i := range mids { + c[mids[i]]++ + } + for k, v := range c { + if v > 1 { + t.Errorf("MessageID randomness not given. MessageID %q was generated %d times", k, v) + } + } + } + + func TestMsg_GetMessageID(t *testing.T) { + expected := "this.is.a.message.id" + msg := NewMsg() + msg.SetMessageIDWithValue(expected) + val := msg.GetMessageID() + if !strings.EqualFold(val, fmt.Sprintf("<%s>", expected)) { + t.Errorf("GetMessageID() failed. Expected: %s, got: %s", fmt.Sprintf("<%s>", expected), val) + } + msg.genHeader[HeaderMessageID] = nil + val = msg.GetMessageID() + if val != "" { + t.Errorf("GetMessageID() failed. Expected empty string, got: %s", val) + } + } + +// TestMsg_FromFormat tests the FromFormat and EnvelopeFrom methods for the Msg object + + func TestMsg_FromFormat(t *testing.T) { + tests := []struct { + tname string + name string + addr string + want string + fail bool + }{ + { + "valid name and addr", "Toni Tester", "tester@example.com", + `"Toni Tester" `, false, + }, + { + "no name with valid addr", "", "tester@example.com", + ``, false, + }, + { + "valid name with invalid addr", "Toni Tester", "@example.com", + ``, true, + }, + } + + m := NewMsg() + for _, tt := range tests { + t.Run(tt.tname, func(t *testing.T) { + if err := m.FromFormat(tt.name, tt.addr); err != nil && !tt.fail { + t.Errorf("failed to FromFormat(): %s", err) + return + } + if err := m.EnvelopeFromFormat(tt.name, tt.addr); err != nil && !tt.fail { + t.Errorf("failed to EnvelopeFromFormat(): %s", err) + return + } + + var fa *mail.Address + f, ok := m.addrHeader[HeaderFrom] + if ok && len(f) > 0 { + fa = f[0] + } + if (!ok || len(f) == 0) && !tt.fail { + t.Errorf(`valid from address expected, but "From:" field is empty`) + return + } + if tt.fail && len(f) > 0 { + t.Errorf("FromFormat() was supposed to failed but got value: %s", fa.String()) + return + } + + if !tt.fail && fa.String() != tt.want { + t.Errorf("wrong result for FromFormat(). Want: %s, got: %s", tt.want, fa.String()) + } + m.addrHeader[HeaderFrom] = nil + }) + } + } + + func TestMsg_GetRecipients(t *testing.T) { + a := []string{"to@example.com", "cc@example.com", "bcc@example.com"} + m := NewMsg() + + _, err := m.GetRecipients() + if err == nil { + t.Errorf("GetRecipients() succeeded but was expected to fail") + return + } + + if err := m.AddTo(a[0]); err != nil { + t.Errorf("AddTo() failed: %s", err) + return + } + if err := m.AddCc(a[1]); err != nil { + t.Errorf("AddCc() failed: %s", err) + return + } + if err := m.AddBcc(a[2]); err != nil { + t.Errorf("AddBcc() failed: %s", err) + return + } + + al, err := m.GetRecipients() + if err != nil { + t.Errorf("GetRecipients() failed: %s", err) + return + } + + tf, cf, bf := false, false, false + for _, r := range al { + if r == a[0] { + tf = true + } + if r == a[1] { + cf = true + } + if r == a[2] { + bf = true + } + } + if !tf { + t.Errorf("GetRecipients() failed. Expected to address %s but was not found", a[0]) + return + } + if !cf { + t.Errorf("GetRecipients() failed. Expected cc address %s but was not found", a[1]) + return + } + if !bf { + t.Errorf("GetRecipients() failed. Expected bcc address %s but was not found", a[2]) + return + } + } + +// TestMsg_ReplyTo tests the Msg.ReplyTo and Msg.ReplyToFormat methods + + func TestMsg_ReplyTo(t *testing.T) { + tests := []struct { + tname string + name string + addr string + want string + sf bool + }{ + { + "valid name and addr", "Toni Tester", "tester@example.com", + `"Toni Tester" `, false, + }, + { + "no name with valid addr", "", "tester@example.com", + ``, false, + }, + { + "valid name with invalid addr", "Toni Tester", "@example.com", + ``, true, + }, + } + m := NewMsg() + for _, tt := range tests { + t.Run(tt.tname, func(t *testing.T) { + if err := m.ReplyTo(tt.want); err != nil && !tt.sf { + t.Errorf("ReplyTo() method failed: %s", err) + } + if !tt.sf { + rt, ok := m.genHeader[HeaderReplyTo] + if !ok { + t.Errorf("ReplyTo() failed: ReplyTo generic header not set") + return + } + if len(rt) <= 0 { + t.Errorf("ReplyTo() failed: length of generic ReplyTo header is zero or less than zero") + return + } + if rt[0] != tt.want { + t.Errorf("ReplyTo() failed: expected value: %s, got: %s", tt.want, rt[0]) + } + } + m.genHeader = nil + m.genHeader = make(map[Header][]string) + if err := m.ReplyToFormat(tt.name, tt.addr); err != nil && !tt.sf { + t.Errorf("ReplyToFormat() method failed: %s", err) + } + if !tt.sf { + rt, ok := m.genHeader[HeaderReplyTo] + if !ok { + t.Errorf("ReplyTo() failed: ReplyTo generic header not set") + return + } + if len(rt) <= 0 { + t.Errorf("ReplyTo() failed: length of generic ReplyTo header is zero or less than zero") + return + } + if rt[0] != tt.want { + t.Errorf("ReplyTo() failed: expected value: %s, got: %s", tt.want, rt[0]) + } + } + m.genHeader = nil + m.genHeader = make(map[Header][]string) + }) + } + } + +// TestMsg_Subject tests the Msg.Subject method + + func TestMsg_Subject(t *testing.T) { + tests := []struct { + name string + sub string + want string + }{ + {"normal subject", "This is a test subject", "This is a test subject"}, + { + "subject with umlauts", "This is a test subject with umlauts: üäöß", + "=?UTF-8?q?This_is_a_test_subject_with_umlauts:_=C3=BC=C3=A4=C3=B6=C3=9F?=", + }, + { + "subject with emoji", "This is a test subject with emoji: 📧", + "=?UTF-8?q?This_is_a_test_subject_with_emoji:_=F0=9F=93=A7?=", + }, + } + m := NewMsg() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m.Subject(tt.sub) + s, ok := m.genHeader[HeaderSubject] + if !ok || len(s) <= 0 { + t.Errorf("Subject() method failed. Generic header for Subject is empty") + return + } + if s[0] != tt.want { + t.Errorf("Subject() method failed. Expected: %s, got: %s", tt.want, s[0]) + } + }) + } + } + +// TestMsg_SetImportance tests the Msg.SetImportance method + + func TestMsg_SetImportance(t *testing.T) { + tests := []struct { + name string + imp Importance + wantns string + xprio string + want string + sf bool + }{ + {"Importance: Non-Urgent", ImportanceNonUrgent, "0", "5", "non-urgent", false}, + {"Importance: Low", ImportanceLow, "0", "5", "low", false}, + {"Importance: Normal", ImportanceNormal, "", "", "", true}, + {"Importance: High", ImportanceHigh, "1", "1", "high", false}, + {"Importance: Urgent", ImportanceUrgent, "1", "1", "urgent", false}, + {"Importance: Unknown", 9, "", "", "", true}, + } + m := NewMsg() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m.SetImportance(tt.imp) + hi, ok := m.genHeader[HeaderImportance] + if (!ok || len(hi) <= 0) && !tt.sf { + t.Errorf("SetImportance() method failed. Generic header for Importance is empty") + } + hp, ok := m.genHeader[HeaderPriority] + if (!ok || len(hp) <= 0) && !tt.sf { + t.Errorf("SetImportance() method failed. Generic header for Priority is empty") + } + hx, ok := m.genHeader[HeaderXPriority] + if (!ok || len(hx) <= 0) && !tt.sf { + t.Errorf("SetImportance() method failed. Generic header for X-Priority is empty") + } + hm, ok := m.genHeader[HeaderXMSMailPriority] + if (!ok || len(hm) <= 0) && !tt.sf { + t.Errorf("SetImportance() method failed. Generic header for X-MS-XPriority is empty") + } + if !tt.sf { + if hi[0] != tt.want { + t.Errorf("SetImportance() method failed. Expected Imporance: %s, got: %s", tt.want, hi[0]) + } + if hp[0] != tt.wantns { + t.Errorf("SetImportance() method failed. Expected Priority: %s, got: %s", tt.want, hp[0]) + } + if hx[0] != tt.xprio { + t.Errorf("SetImportance() method failed. Expected X-Priority: %s, got: %s", tt.want, hx[0]) + } + if hm[0] != tt.wantns { + t.Errorf("SetImportance() method failed. Expected X-MS-Priority: %s, got: %s", tt.wantns, hm[0]) + } + } + m.genHeader = nil + m.genHeader = make(map[Header][]string) + }) + } + } + +// TestMsg_SetOrganization tests the Msg.SetOrganization method + + func TestMsg_SetOrganization(t *testing.T) { + tests := []struct { + name string + org string + }{ + {"Org: testcorp", "testcorp"}, + } + m := NewMsg() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m.SetOrganization(tt.org) + o, ok := m.genHeader[HeaderOrganization] + if !ok || len(o) <= 0 { + t.Errorf("SetOrganization() method failed. Generic header for Organization is empty") + return + } + if o[0] != tt.org { + t.Errorf("SetOrganization() method failed. Expected: %s, got: %s", tt.org, o[0]) + } + }) + } + } + +// TestMsg_SetUserAgent tests the Msg.SetUserAgent method + + func TestMsg_SetUserAgent(t *testing.T) { + tests := []struct { + name string + ua string + }{ + {"UA: Testmail 1.0", "Testmailer 1.0"}, + } + m := NewMsg() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m.SetUserAgent(tt.ua) + xm, ok := m.genHeader[HeaderXMailer] + if !ok || len(xm) <= 0 { + t.Errorf("SetUserAgent() method failed. Generic header for X-Mailer is empty") + return + } + ua, ok := m.genHeader[HeaderUserAgent] + if !ok || len(ua) <= 0 { + t.Errorf("SetUserAgent() method failed. Generic header for UserAgent is empty") + return + } + if xm[0] != tt.ua { + t.Errorf("SetUserAgent() method failed. Expected X-Mailer: %s, got: %s", tt.ua, xm[0]) + } + if ua[0] != tt.ua { + t.Errorf("SetUserAgent() method failed. Expected User-Agent: %s, got: %s", tt.ua, ua[0]) + } + }) + } + } + +// TestMsg_RequestMDN tests the different RequestMDN* related methods of Msg + + func TestMsg_RequestMDN(t *testing.T) { + n := "Toni Tester" + n2 := "Melanie Tester" + v := "toni.tester@example.com" + v2 := "melanie.tester@example.com" + iv := "testertest.tld" + vl := []string{v, v2} + m := NewMsg() + + // Single valid address + if err := m.RequestMDNTo(v); err != nil { + t.Errorf("RequestMDNTo with a single valid address failed: %s", err) + } + if val := m.genHeader[HeaderDispositionNotificationTo]; len(val) > 1 { + if val[0] != fmt.Sprintf("<%s>", v) { + t.Errorf("RequestMDNTo with a single valid address failed. Expected: %s, got: %s", v, + val[0]) + } + } + m.Reset() + + // Multiples valid addresses + if err := m.RequestMDNTo(vl...); err != nil { + t.Errorf("RequestMDNTo with a multiple valid address failed: %s", err) + } + if val := m.genHeader[HeaderDispositionNotificationTo]; len(val) > 0 { + if val[0] != fmt.Sprintf("<%s>", v) { + t.Errorf("RequestMDNTo with a multiple valid addresses failed. Expected 0: %s, got 0: %s", v, + val[0]) + } + } + if val := m.genHeader[HeaderDispositionNotificationTo]; len(val) > 1 { + if val[1] != fmt.Sprintf("<%s>", v2) { + t.Errorf("RequestMDNTo with a multiple valid addresses failed. Expected 1: %s, got 1: %s", v2, + val[1]) + } + } + m.Reset() + + // Invalid address + if err := m.RequestMDNTo(iv); err == nil { + t.Errorf("RequestMDNTo with an invalid address was supposed to failed, but didn't") + } + m.Reset() + + // Single valid addresses + AddTo + if err := m.RequestMDNTo(v); err != nil { + t.Errorf("RequestMDNTo with a single valid address failed: %s", err) + } + if err := m.RequestMDNAddTo(v2); err != nil { + t.Errorf("RequestMDNAddTo with a valid address failed: %s", err) + } + if val := m.genHeader[HeaderDispositionNotificationTo]; len(val) > 1 { + if val[1] != fmt.Sprintf("<%s>", v2) { + t.Errorf("RequestMDNTo with a multiple valid addresses failed. Expected 1: %s, got 1: %s", v2, + val[1]) + } + } + m.Reset() + + // Single valid address formated + AddToFromat + if err := m.RequestMDNToFormat(n, v); err != nil { + t.Errorf("RequestMDNToFormat with a single valid address failed: %s", err) + } + if val := m.genHeader[HeaderDispositionNotificationTo]; len(val) > 0 { + if val[0] != fmt.Sprintf(`"%s" <%s>`, n, v) { + t.Errorf(`RequestMDNToFormat with a single valid address failed. Expected: "%s" <%s>, got: %s`, n, v, + val[0]) + } + } + if err := m.RequestMDNAddToFormat(n2, v2); err != nil { + t.Errorf("RequestMDNAddToFormat with a valid address failed: %s", err) + } + if val := m.genHeader[HeaderDispositionNotificationTo]; len(val) > 1 { + if val[1] != fmt.Sprintf(`"%s" <%s>`, n2, v2) { + t.Errorf(`RequestMDNAddToFormat with a single valid address failed. Expected: "%s" <%s>, got: %s`, n2, v2, + val[1]) + } + } + m.Reset() + + // Invalid formated address + if err := m.RequestMDNToFormat(n, iv); err == nil { + t.Errorf("RequestMDNToFormat with an invalid address was supposed to failed, but didn't") + } + + // Invalid address AddTo + AddToFormat + if err := m.RequestMDNAddTo(iv); err == nil { + t.Errorf("RequestMDNAddTo with an invalid address was supposed to failed, but didn't") + } + if err := m.RequestMDNAddToFormat(n, iv); err == nil { + t.Errorf("RequestMDNAddToFormat with an invalid address was supposed to failed, but didn't") + } + } + +// TestMsg_SetBodyString tests the Msg.SetBodyString method + + func TestMsg_SetBodyString(t *testing.T) { + tests := []struct { + name string + ct ContentType + value string + want string + sf bool + }{ + {"Body: test", TypeTextPlain, "test", "test", false}, + { + "Body: with Umlauts", TypeTextHTML, "üäöß", + "üäöß", false, + }, + {"Body: with emoji", TypeTextPlain, "📧", "📧", false}, + } + m := NewMsg() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m.SetBodyString(tt.ct, tt.value) + if len(m.parts) != 1 { + t.Errorf("SetBodyString() failed: no mail parts found") + } + part := m.parts[0] + res := bytes.Buffer{} + if _, err := part.writeFunc(&res); err != nil && !tt.sf { + t.Errorf("WriteFunc of part failed: %s", err) + } + if res.String() != tt.want { + t.Errorf("SetBodyString() failed. Expecteding: %s, got: %s", tt.want, res.String()) + } + }) + } + } + +// TestMsg_AddAlternativeString tests the Msg.AddAlternativeString method + + func TestMsg_AddAlternativeString(t *testing.T) { + tests := []struct { + name string + value string + want string + sf bool + }{ + {"Body: test", "test", "test", false}, + {"Body: with Umlauts", "üäöß", "üäöß", false}, + {"Body: with emoji", "📧", "📧", false}, + } + m := NewMsg() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m.SetBodyString(TypeTextPlain, tt.value) + if len(m.parts) != 1 { + t.Errorf("AddAlternativeString() => SetBodyString() failed: no mail parts found") + } + m.AddAlternativeString(TypeTextHTML, tt.value) + if len(m.parts) != 2 { + t.Errorf("AddAlternativeString() failed: no alternative mail parts found") + } + apart := m.parts[1] + res := bytes.Buffer{} + if _, err := apart.writeFunc(&res); err != nil && !tt.sf { + t.Errorf("WriteFunc of part failed: %s", err) + } + if res.String() != tt.want { + t.Errorf("AddAlternativeString() failed. Expecteding: %s, got: %s", tt.want, res.String()) + } + }) + } + } + +// TestMsg_AttachFile tests the Msg.AttachFile and the WithFilename FileOption method + + func TestMsg_AttachFile(t *testing.T) { + tests := []struct { + name string + file string + fn string + sf bool + }{ + {"File: README.md", "README.md", "README.md", false}, + {"File: doc.go", "doc.go", "foo.go", false}, + {"File: nonexisting", "", "invalid.file", true}, + } + m := NewMsg() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m.AttachFile(tt.file, WithFileName(tt.fn), nil) + if len(m.attachments) != 1 && !tt.sf { + t.Errorf("AttachFile() failed. Number of attachments expected: %d, got: %d", 1, + len(m.attachments)) + return + } + if !tt.sf { + file := m.attachments[0] + if file == nil { + t.Errorf("AttachFile() failed. Attachment file pointer is nil") + return + } + if file.Name != tt.fn { + t.Errorf("AttachFile() failed. Filename of attachment expected: %s, got: %s", tt.fn, + file.Name) + } + buf := bytes.Buffer{} + if _, err := file.Writer(&buf); err != nil { + t.Errorf("failed to execute WriterFunc: %s", err) + return + } + } + m.Reset() + }) + } + } + +// TestMsg_GetAttachments tests the Msg.GetAttachments method + + func TestMsg_GetAttachments(t *testing.T) { + tests := []struct { + name string + files []string + }{ + {"File: README.md", []string{"README.md"}}, + {"File: doc.go", []string{"doc.go"}}, + {"File: README.md and doc.go", []string{"README.md", "doc.go"}}, + {"File: nonexisting", nil}, + } + m := NewMsg() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + for _, f := range tt.files { + m.AttachFile(f, WithFileName(f), nil) + } + if len(m.attachments) != len(tt.files) { + t.Errorf("AttachFile() failed. Number of attachments expected: %d, got: %d", len(tt.files), + len(m.attachments)) + return + } + ff := m.GetAttachments() + if len(m.attachments) != len(ff) { + t.Errorf("GetAttachments() failed. Number of attachments expected: %d, got: %d", len(m.attachments), + len(ff)) + return + } + var fn []string + for _, f := range ff { + fn = append(fn, f.Name) + } + sort.Strings(fn) + sort.Strings(tt.files) + for i, f := range tt.files { + if f != fn[i] { + t.Errorf("GetAttachments() failed. Attachment name expected: %s, got: %s", f, + fn[i]) + return + } + } + m.Reset() + }) + } + } + +// TestMsg_SetAttachments tests the Msg.GetAttachments method + + func TestMsg_SetAttachments(t *testing.T) { + tests := []struct { + name string + attachments []string + files []string + }{ + {"File: replace README.md with doc.go", []string{"README.md"}, []string{"doc.go"}}, + {"File: add README.md with doc.go ", []string{"doc.go"}, []string{"README.md", "doc.go"}}, + {"File: remove README.md and doc.go", []string{"README.md", "doc.go"}, nil}, + {"File: add README.md and doc.go", nil, []string{"README.md", "doc.go"}}, + } + m := NewMsg() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sort.Strings(tt.attachments) + sort.Strings(tt.files) + for _, a := range tt.attachments { + m.AttachFile(a, WithFileName(a), nil) + } + if len(m.attachments) != len(tt.attachments) { + t.Errorf("AttachFile() failed. Number of attachments expected: %d, got: %d", len(tt.files), + len(m.attachments)) + return + } + var files []*File + for _, f := range tt.files { + files = append(files, &File{Name: f}) + } + m.SetAttachments(files) + if len(m.attachments) != len(files) { + t.Errorf("SetAttachements() failed. Number of attachments expected: %d, got: %d", len(files), + len(m.attachments)) + return + } + for i, f := range tt.files { + if f != m.attachments[i].Name { + t.Errorf("SetAttachments() failed. Attachment name expected: %s, got: %s", f, + m.attachments[i].Name) + return + } + } + m.Reset() + }) + } + } + +// TestMsg_UnsetAllAttachments tests the Msg.UnsetAllAttachments method + + func TestMsg_UnsetAllAttachments(t *testing.T) { + tests := []struct { + name string + attachments []string + }{ + {"File: one file", []string{"README.md"}}, + {"File: two files", []string{"README.md", "doc.go"}}, + {"File: nil", nil}, + } + m := NewMsg() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var files []*File + for _, f := range tt.attachments { + files = append(files, &File{Name: f}) + } + m.SetAttachments(files) + + if len(m.attachments) != len(files) { + t.Errorf("SetAttachements() failed. Number of attachments expected: %d, got: %d", len(files), + len(m.attachments)) + return + } + m.UnsetAllAttachments() + if m.attachments != nil { + t.Errorf("UnsetAllAttachments() failed. The attachments file's pointer is not nil") + return + } + m.Reset() + }) + } + } + +// TestMsg_GetEmbeds tests the Msg.GetEmbeds method + + func TestMsg_GetEmbeds(t *testing.T) { + tests := []struct { + name string + files []string + }{ + {"File: README.md", []string{"README.md"}}, + {"File: doc.go", []string{"doc.go"}}, + {"File: README.md and doc.go", []string{"README.md", "doc.go"}}, + {"File: nonexisting", nil}, + } + m := NewMsg() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + for _, f := range tt.files { + m.EmbedFile(f, WithFileName(f), nil) + } + if len(m.embeds) != len(tt.files) { + t.Errorf("EmbedFile() failed. Number of embedded files expected: %d, got: %d", len(tt.files), + len(m.embeds)) + return + } + ff := m.GetEmbeds() + if len(m.embeds) != len(ff) { + t.Errorf("GetEmbeds() failed. Number of embedded files expected: %d, got: %d", len(m.embeds), + len(ff)) + return + } + var fn []string + for _, f := range ff { + fn = append(fn, f.Name) + } + sort.Strings(fn) + sort.Strings(tt.files) + for i, f := range tt.files { + if f != fn[i] { + t.Errorf("GetEmbeds() failed. Embedded file name expected: %s, got: %s", f, + fn[i]) + return + } + } + m.Reset() + }) + } + } + +// TestMsg_SetEmbeds tests the Msg.GetEmbeds method + + func TestMsg_SetEmbeds(t *testing.T) { + tests := []struct { + name string + embeds []string + files []string + }{ + {"File: replace README.md with doc.go", []string{"README.md"}, []string{"doc.go"}}, + {"File: add README.md with doc.go ", []string{"doc.go"}, []string{"README.md", "doc.go"}}, + {"File: remove README.md and doc.go", []string{"README.md", "doc.go"}, nil}, + {"File: add README.md and doc.go", nil, []string{"README.md", "doc.go"}}, + } + m := NewMsg() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sort.Strings(tt.embeds) + sort.Strings(tt.files) + for _, a := range tt.embeds { + m.EmbedFile(a, WithFileName(a), nil) + } + if len(m.embeds) != len(tt.embeds) { + t.Errorf("EmbedFile() failed. Number of embedded files expected: %d, got: %d", len(tt.files), + len(m.embeds)) + return + } + var files []*File + for _, f := range tt.files { + files = append(files, &File{Name: f}) + } + m.SetEmbeds(files) + if len(m.embeds) != len(files) { + t.Errorf("SetEmbeds() failed. Number of embedded files expected: %d, got: %d", len(files), + len(m.embeds)) + return + } + for i, f := range tt.files { + if f != m.embeds[i].Name { + t.Errorf("SetEmbeds() failed. Embedded file name expected: %s, got: %s", f, + m.embeds[i].Name) + return + } + } + m.Reset() + }) + } + } + +// TestMsg_UnsetAllEmbeds tests the Msg.TestMsg_UnsetAllEmbeds method + + func TestMsg_UnsetAllEmbeds(t *testing.T) { + tests := []struct { + name string + embeds []string + }{ + {"File: one file", []string{"README.md"}}, + {"File: two files", []string{"README.md", "doc.go"}}, + {"File: nil", nil}, + } + m := NewMsg() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var files []*File + for _, f := range tt.embeds { + files = append(files, &File{Name: f}) + } + m.SetEmbeds(files) + if len(m.embeds) != len(files) { + t.Errorf("SetEmbeds() failed. Number of embedded files expected: %d, got: %d", len(files), + len(m.embeds)) + return + } + m.UnsetAllEmbeds() + if m.embeds != nil { + t.Errorf("UnsetAllEmbeds() failed. The embeds file's point is not nil") + return + } + m.Reset() + }) + } + } + +// TestMsg_UnsetAllParts tests the Msg.TestMsg_UnsetAllParts method + + func TestMsg_UnsetAllParts(t *testing.T) { + tests := []struct { + name string + attachments []string + embeds []string + }{ + {"File: both is exist", []string{"README.md"}, []string{"doc.go"}}, + {"File: both is nil", nil, nil}, + {"File: attachment exist, embed nil", []string{"README.md"}, nil}, + {"File: attachment nil, embed exist", nil, []string{"README.md"}}, + } + m := NewMsg() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var attachments []*File + for _, f := range tt.attachments { + attachments = append(attachments, &File{Name: f}) + } + m.SetAttachments(attachments) + if len(m.attachments) != len(attachments) { + t.Errorf("SetAttachements() failed. Number of attachments files expected: %d, got: %d", + len(attachments), len(m.attachments)) + return + } + var embeds []*File + for _, f := range tt.embeds { + embeds = append(embeds, &File{Name: f}) + } + m.SetEmbeds(embeds) + if len(m.embeds) != len(embeds) { + t.Errorf("SetEmbeds() failed. Number of embedded files expected: %d, got: %d", len(embeds), + len(m.embeds)) + return + } + m.UnsetAllParts() + if m.attachments != nil { + t.Errorf("UnsetAllParts() failed. The attachments file's point is not nil") + return + } + if m.embeds != nil { + t.Errorf("UnsetAllParts() failed. The embeds file's point is not nil") + return + } + m.Reset() + }) + } + } + +// TestMsg_AttachFromEmbedFS tests the Msg.AttachFromEmbedFS and the WithFilename FileOption method + + func TestMsg_AttachFromEmbedFS(t *testing.T) { + tests := []struct { + name string + file string + fn string + sf bool + }{ + {"File: README.md", "README.md", "README.md", false}, + {"File: nonexisting", "", "invalid.file", true}, + } + m := NewMsg() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := m.AttachFromEmbedFS(tt.file, &efs, WithFileName(tt.fn)); err != nil && !tt.sf { + t.Errorf("AttachFromEmbedFS() failed: %s", err) + return + } + if len(m.attachments) != 1 && !tt.sf { + t.Errorf("AttachFile() failed. Number of attachments expected: %d, got: %d", 1, + len(m.attachments)) + return + } + if !tt.sf { + file := m.attachments[0] + if file == nil { + t.Errorf("AttachFile() failed. Attachment file pointer is nil") + return + } + if file.Name != tt.fn { + t.Errorf("AttachFile() failed. Filename of attachment expected: %s, got: %s", tt.fn, + file.Name) + } + buf := bytes.Buffer{} + if _, err := file.Writer(&buf); err != nil { + t.Errorf("failed to execute WriterFunc: %s", err) + return + } + } + m.Reset() + }) + } + } + +// TestMsg_AttachFileBrokenFunc tests WriterFunc of the Msg.AttachFile method + + func TestMsg_AttachFileBrokenFunc(t *testing.T) { + m := NewMsg() + m.AttachFile("README.md") + if len(m.attachments) != 1 { + t.Errorf("AttachFile() failed. Number of attachments expected: %d, got: %d", 1, + len(m.attachments)) + return + } + file := m.attachments[0] + if file == nil { + t.Errorf("AttachFile() failed. Attachment file pointer is nil") + return + } + file.Writer = func(io.Writer) (int64, error) { + return 0, fmt.Errorf("failing intentionally") + } + buf := bytes.Buffer{} + if _, err := file.Writer(&buf); err == nil { + t.Errorf("execute WriterFunc did not fail, but was expected to fail") + } + } + +// TestMsg_AttachReader tests the Msg.AttachReader method + + func TestMsg_AttachReader(t *testing.T) { + m := NewMsg() + ts := "This is a test string" + rbuf := bytes.Buffer{} + rbuf.WriteString(ts) + r := bufio.NewReader(&rbuf) + if err := m.AttachReader("testfile.txt", r); err != nil { + t.Errorf("AttachReader() failed. Expected no error, got: %s", err.Error()) + return + } + if len(m.attachments) != 1 { + t.Errorf("AttachReader() failed. Number of attachments expected: %d, got: %d", 1, + len(m.attachments)) + return + } + file := m.attachments[0] + if file == nil { + t.Errorf("AttachReader() failed. Attachment file pointer is nil") + return + } + if file.Name != "testfile.txt" { + t.Errorf("AttachReader() failed. Expected file name: %s, got: %s", "testfile.txt", + file.Name) + } + wbuf := bytes.Buffer{} + if _, err := file.Writer(&wbuf); err != nil { + t.Errorf("execute WriterFunc failed: %s", err) + } + if wbuf.String() != ts { + t.Errorf("AttachReader() failed. Expected string: %q, got: %q", ts, wbuf.String()) + } + } + +// TestMsg_EmbedFile tests the Msg.EmbedFile and the WithFilename FileOption method + + func TestMsg_EmbedFile(t *testing.T) { + tests := []struct { + name string + file string + fn string + sf bool + }{ + {"File: README.md", "README.md", "README.md", false}, + {"File: doc.go", "doc.go", "foo.go", false}, + {"File: nonexisting", "", "invalid.file", true}, + } + m := NewMsg() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m.EmbedFile(tt.file, WithFileName(tt.fn), nil) + if len(m.embeds) != 1 && !tt.sf { + t.Errorf("EmbedFile() failed. Number of embeds expected: %d, got: %d", 1, + len(m.embeds)) + return + } + if !tt.sf { + file := m.embeds[0] + if file == nil { + t.Errorf("EmbedFile() failed. Embedded file pointer is nil") + return + } + if file.Name != tt.fn { + t.Errorf("EmbedFile() failed. Filename of embeds expected: %s, got: %s", tt.fn, + file.Name) + } + buf := bytes.Buffer{} + if _, err := file.Writer(&buf); err != nil { + t.Errorf("failed to execute WriterFunc: %s", err) + return + } + } + m.Reset() + }) + } + } + +// TestMsg_EmbedFromEmbedFS tests the Msg.EmbedFromEmbedFS and the WithFilename FileOption method + + func TestMsg_EmbedFromEmbedFS(t *testing.T) { + tests := []struct { + name string + file string + fn string + sf bool + }{ + {"File: README.md", "README.md", "README.md", false}, + {"File: nonexisting", "", "invalid.file", true}, + } + m := NewMsg() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := m.EmbedFromEmbedFS(tt.file, &efs, WithFileName(tt.fn)); err != nil && !tt.sf { + t.Errorf("EmbedFromEmbedFS() failed: %s", err) + return + } + if len(m.embeds) != 1 && !tt.sf { + t.Errorf("EmbedFile() failed. Number of embeds expected: %d, got: %d", 1, + len(m.embeds)) + return + } + if !tt.sf { + file := m.embeds[0] + if file == nil { + t.Errorf("EmbedFile() failed. Embedded file pointer is nil") + return + } + if file.Name != tt.fn { + t.Errorf("EmbedFile() failed. Filename of embeds expected: %s, got: %s", tt.fn, + file.Name) + } + buf := bytes.Buffer{} + if _, err := file.Writer(&buf); err != nil { + t.Errorf("failed to execute WriterFunc: %s", err) + return + } + } + m.Reset() + }) + } + } + +// TestMsg_EmbedFileBrokenFunc tests WriterFunc of the Msg.EmbedFile method + + func TestMsg_EmbedFileBrokenFunc(t *testing.T) { + m := NewMsg() + m.EmbedFile("README.md") + if len(m.embeds) != 1 { + t.Errorf("EmbedFile() failed. Number of embeds expected: %d, got: %d", 1, + len(m.embeds)) + return + } + file := m.embeds[0] + if file == nil { + t.Errorf("EmbedFile() failed. Embedded file pointer is nil") + return + } + file.Writer = func(io.Writer) (int64, error) { + return 0, fmt.Errorf("failing intentionally") + } + buf := bytes.Buffer{} + if _, err := file.Writer(&buf); err == nil { + t.Errorf("execute WriterFunc did not fail, but was expected to fail") + } + } + +// TestMsg_EmbedReader tests the Msg.EmbedReader method + + func TestMsg_EmbedReader(t *testing.T) { + m := NewMsg() + ts := "This is a test string" + rbuf := bytes.Buffer{} + rbuf.WriteString(ts) + r := bufio.NewReader(&rbuf) + if err := m.EmbedReader("testfile.txt", r); err != nil { + t.Errorf("EmbedReader() failed. Expected no error, got: %s", err.Error()) + return + } + if len(m.embeds) != 1 { + t.Errorf("EmbedReader() failed. Number of embeds expected: %d, got: %d", 1, + len(m.embeds)) + return + } + file := m.embeds[0] + if file == nil { + t.Errorf("EmbedReader() failed. Embedded file pointer is nil") + return + } + if file.Name != "testfile.txt" { + t.Errorf("EmbedReader() failed. Expected file name: %s, got: %s", "testfile.txt", + file.Name) + } + wbuf := bytes.Buffer{} + if _, err := file.Writer(&wbuf); err != nil { + t.Errorf("execute WriterFunc failed: %s", err) + } + if wbuf.String() != ts { + t.Errorf("EmbedReader() failed. Expected string: %q, got: %q", ts, wbuf.String()) + } + } + +// TestMsg_hasAlt tests the hasAlt() method of the Msg + + func TestMsg_hasAlt(t *testing.T) { + m := NewMsg() + m.SetBodyString(TypeTextPlain, "Plain") + m.AddAlternativeString(TypeTextHTML, "HTML") + if !m.hasAlt() { + t.Errorf("mail has alternative parts but hasAlt() returned true") + } + } + +// TestMsg_hasRelated tests the hasRelated() method of the Msg + + func TestMsg_hasRelated(t *testing.T) { + m := NewMsg() + m.SetBodyString(TypeTextPlain, "Plain") + m.EmbedFile("README.md") + if !m.hasRelated() { + t.Errorf("mail has related parts but hasRelated() returned true") + } + } + +// TestMsg_hasMixed tests the hasMixed() method of the Msg + + func TestMsg_hasMixed(t *testing.T) { + m := NewMsg() + m.SetBodyString(TypeTextPlain, "Plain") + m.AttachFile("README.md") + if !m.hasMixed() { + t.Errorf("mail has mixed parts but hasMixed() returned true") + } + } + +// TestMsg_WriteTo tests the WriteTo() method of the Msg + + func TestMsg_WriteTo(t *testing.T) { + m := NewMsg() + m.SetBodyString(TypeTextPlain, "Plain") + wbuf := bytes.Buffer{} + n, err := m.WriteTo(&wbuf) + if err != nil { + t.Errorf("WriteTo() failed: %s", err) + return + } + if n != int64(wbuf.Len()) { + t.Errorf("WriteTo() failed: expected written byte length: %d, got: %d", n, wbuf.Len()) + } + } + +// TestMsg_WriteToSkipMiddleware tests the WriteTo() method of the Msg + + func TestMsg_WriteToSkipMiddleware(t *testing.T) { + m := NewMsg(WithMiddleware(encodeMiddleware{}), WithMiddleware(uppercaseMiddleware{})) + m.Subject("This is a test") + m.SetBodyString(TypeTextPlain, "Plain") + wbuf := bytes.Buffer{} + n, err := m.WriteToSkipMiddleware(&wbuf, "uppercase") + if err != nil { + t.Errorf("WriteToSkipMiddleware() failed: %s", err) + return + } + if n != int64(wbuf.Len()) { + t.Errorf("WriteToSkipMiddleware() failed: expected written byte length: %d, got: %d", n, wbuf.Len()) + } + if !strings.Contains(wbuf.String(), "Subject: This is @ test") { + t.Errorf("WriteToSkipMiddleware failed. Unable to find encoded subject") + } + + wbuf2 := bytes.Buffer{} + n, err = m.WriteTo(&wbuf2) + if err != nil { + t.Errorf("WriteTo() failed: %s", err) + return + } + if n != int64(wbuf2.Len()) { + t.Errorf("WriteTo() failed: expected written byte length: %d, got: %d", n, wbuf2.Len()) + } + if !strings.Contains(wbuf2.String(), "Subject: THIS IS @ TEST") { + t.Errorf("WriteToSkipMiddleware failed. Unable to find encoded and upperchase subject") + } + } + +// TestMsg_WriteTo_fails tests the WriteTo() method of the Msg but with a failing body writer function + + func TestMsg_WriteTo_fails(t *testing.T) { + m := NewMsg() + m.SetBodyWriter(TypeTextPlain, func(io.Writer) (int64, error) { + return 0, errors.New("failed") + }) + _, err := m.WriteTo(io.Discard) + if err == nil { + t.Errorf("WriteTo() with failing BodyWriter function was supposed to fail, but didn't") + return + } + + // NoEncoding handles the errors separately + m = NewMsg(WithEncoding(NoEncoding)) + m.SetBodyWriter(TypeTextPlain, func(io.Writer) (int64, error) { + return 0, errors.New("failed") + }) + _, err = m.WriteTo(io.Discard) + if err == nil { + t.Errorf("WriteTo() (no encoding) with failing BodyWriter function was supposed to fail, but didn't") + return + } + } + +// TestMsg_Write tests the Write() method of the Msg + + func TestMsg_Write(t *testing.T) { + m := NewMsg() + m.SetBodyString(TypeTextPlain, "Plain") + wbuf := bytes.Buffer{} + n, err := m.Write(&wbuf) + if err != nil { + t.Errorf("WriteTo() failed: %s", err) + return + } + if n != int64(wbuf.Len()) { + t.Errorf("WriteTo() failed: expected written byte length: %d, got: %d", n, wbuf.Len()) + } + } + +// TestMsg_WriteWithLongHeader tests the WriteTo() method of the Msg with a long header + + func TestMsg_WriteWithLongHeader(t *testing.T) { + m := NewMsg() + m.SetBodyString(TypeTextPlain, "Plain") + m.SetGenHeader(HeaderContentLang, "de", "en", "fr", "es", "xxxx", "yyyy", "de", "en", "fr", + "es", "xxxx", "yyyy", "de", "en", "fr", "es", "xxxx", "yyyy", "de", "en", "fr") + m.SetGenHeader(HeaderContentID, "XXXXXXXXXXXXXXX XXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXX", + "XXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXX") + wbuf := bytes.Buffer{} + n, err := m.WriteTo(&wbuf) + if err != nil { + t.Errorf("WriteTo() failed: %s", err) + return + } + if n != int64(wbuf.Len()) { + t.Errorf("WriteTo() failed: expected written byte length: %d, got: %d", n, wbuf.Len()) + } + } + +// TestMsg_WriteDiffEncoding tests the WriteTo() method of the Msg with different Encoding + + func TestMsg_WriteDiffEncoding(t *testing.T) { + tests := []struct { + name string + ct ContentType + en Encoding + alt bool + wa bool + we bool + }{ + {"Plain/QP/NoAlt/NoAttach/NoEmbed", TypeTextPlain, EncodingQP, false, false, false}, + {"Plain/B64/NoAlt/NoAttach/NoEmbed", TypeTextPlain, EncodingB64, false, false, false}, + {"Plain/No/NoAlt/NoAttach/NoEmbed", TypeTextPlain, NoEncoding, false, false, false}, + {"HTML/QP/NoAlt/NoAttach/NoEmbed", TypeTextHTML, EncodingQP, false, false, false}, + {"HTML/B64/NoAlt/NoAttach/NoEmbed", TypeTextHTML, EncodingB64, false, false, false}, + {"HTML/No/NoAlt/NoAttach/NoEmbed", TypeTextHTML, NoEncoding, false, false, false}, + {"Plain/QP/HTML/NoAttach/NoEmbed", TypeTextPlain, EncodingQP, true, false, false}, + {"Plain/B64/HTML/NoAttach/NoEmbed", TypeTextPlain, EncodingB64, true, false, false}, + {"Plain/No/HTML/NoAttach/NoEmbed", TypeTextPlain, NoEncoding, true, false, false}, + {"Plain/QP/NoAlt/Attach/NoEmbed", TypeTextPlain, EncodingQP, false, true, false}, + {"Plain/B64/NoAlt/Attach/NoEmbed", TypeTextPlain, EncodingB64, false, true, false}, + {"Plain/No/NoAlt/Attach/NoEmbed", TypeTextPlain, NoEncoding, false, true, false}, + {"Plain/QP/NoAlt/NoAttach/Embed", TypeTextPlain, EncodingQP, false, false, true}, + {"Plain/B64/NoAlt/NoAttach/Embed", TypeTextPlain, EncodingB64, false, false, true}, + {"Plain/No/NoAlt/NoAttach/Embed", TypeTextPlain, NoEncoding, false, false, true}, + {"Plain/QP/HTML/Attach/Embed", TypeTextPlain, EncodingQP, true, true, true}, + {"Plain/B64/HTML/Attach/Embed", TypeTextPlain, EncodingB64, true, true, true}, + {"Plain/No/HTML/Attach/Embed", TypeTextPlain, NoEncoding, true, true, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + m := NewMsg(WithEncoding(tt.en)) + m.SetBodyString(tt.ct, tt.name) + if tt.alt { + m.AddAlternativeString(TypeTextHTML, fmt.Sprintf("

%s

", tt.name)) + } + if tt.wa { + m.AttachFile("README.md") + } + if tt.we { + m.EmbedFile("README.md") + } + wbuf := bytes.Buffer{} + n, err := m.WriteTo(&wbuf) + if err != nil { + t.Errorf("WriteTo() failed: %s", err) + return + } + if n != int64(wbuf.Len()) { + t.Errorf("WriteTo() failed: expected written byte length: %d, got: %d", n, wbuf.Len()) + } + wbuf.Reset() + }) + } + } + +// TestMsg_appendFile tests the appendFile() method of the Msg + + func TestMsg_appendFile(t *testing.T) { + m := NewMsg() + var fl []*File + f := &File{ + Name: "file.txt", + } + fl = m.appendFile(fl, f, nil) + if len(fl) != 1 { + t.Errorf("appendFile() failed. Expected length: %d, got: %d", 1, len(fl)) + } + fl = m.appendFile(fl, f, nil) + if len(fl) != 2 { + t.Errorf("appendFile() failed. Expected length: %d, got: %d", 2, len(fl)) + } + } + +// TestMsg_multipleWrites tests multiple executions of WriteTo on the Msg + + func TestMsg_multipleWrites(t *testing.T) { + ts := "XXX_UNIQUE_STRING_XXX" + wbuf := bytes.Buffer{} + m := NewMsg() + m.SetBodyString(TypeTextPlain, ts) + + // First WriteTo() + _, err := m.WriteTo(&wbuf) + if err != nil { + t.Errorf("failed to write body to buffer: %s", err) + } + if !strings.Contains(wbuf.String(), ts) { + t.Errorf("first WriteTo() body does not contain unique string: %s", ts) + } + + // Second WriteTo() + wbuf.Reset() + _, err = m.WriteTo(&wbuf) + if err != nil { + t.Errorf("failed to write body to buffer: %s", err) + } + if !strings.Contains(wbuf.String(), ts) { + t.Errorf("second WriteTo() body does not contain unique string: %s", ts) + } + } + +// TestMsg_NewReader tests the Msg.NewReader method + + func TestMsg_NewReader(t *testing.T) { + m := NewMsg() + m.SetBodyString(TypeTextPlain, "TEST123") + mr := m.NewReader() + if mr == nil { + t.Errorf("NewReader failed: Reader is nil") + } + if mr.Error() != nil { + t.Errorf("NewReader failed: %s", mr.Error()) + } + } + +// TestMsg_NewReader_ioCopy tests the Msg.NewReader method using io.Copy + + func TestMsg_NewReader_ioCopy(t *testing.T) { + wbuf1 := bytes.Buffer{} + wbuf2 := bytes.Buffer{} + m := NewMsg() + m.SetBodyString(TypeTextPlain, "TEST123") + mr := m.NewReader() + if mr == nil { + t.Errorf("NewReader failed: Reader is nil") + } + + // First we use WriteTo to have something to compare to + _, err := m.WriteTo(&wbuf1) + if err != nil { + t.Errorf("failed to write body to buffer: %s", err) + } + + // Then we write to wbuf2 via io.Copy + n, err := io.Copy(&wbuf2, mr) + if err != nil { + t.Errorf("failed to use io.Copy on Reader: %s", err) + } + if n != int64(wbuf1.Len()) { + t.Errorf("message length of WriteTo and io.Copy differ. Expected: %d, got: %d", wbuf1.Len(), n) + } + if wbuf1.String() != wbuf2.String() { + t.Errorf("message content of WriteTo and io.Copy differ") + } + } + +// TestMsg_UpdateReader tests the Msg.UpdateReader method + + func TestMsg_UpdateReader(t *testing.T) { + m := NewMsg() + m.Subject("Subject-Run 1") + mr := m.NewReader() + if mr == nil { + t.Errorf("NewReader failed: Reader is nil") + } + wbuf1 := bytes.Buffer{} + _, err := io.Copy(&wbuf1, mr) + if err != nil { + t.Errorf("io.Copy on Reader failed: %s", err) + } + if !strings.Contains(wbuf1.String(), "Subject: Subject-Run 1") { + t.Errorf("io.Copy on Reader failed. Expected to find %q but string in Subject was not found", + "Subject-Run 1") + } + + m.Subject("Subject-Run 2") + m.UpdateReader(mr) + wbuf2 := bytes.Buffer{} + _, err = io.Copy(&wbuf2, mr) + if err != nil { + t.Errorf("2nd io.Copy on Reader failed: %s", err) + } + if !strings.Contains(wbuf2.String(), "Subject: Subject-Run 2") { + t.Errorf("io.Copy on Reader failed. Expected to find %q but string in Subject was not found", + "Subject-Run 2") + } + } + +// TestMsg_SetBodyTextTemplate tests the Msg.SetBodyTextTemplate method + + func TestMsg_SetBodyTextTemplate(t *testing.T) { + tests := []struct { + name string + tpl string + ph string + sf bool + }{ + {"normal text", "This is a {{.Placeholder}}", "TemplateTest", false}, + {"invalid tpl", "This is a {{ foo .Placeholder}}", "TemplateTest", true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data := struct { + Placeholder string + }{Placeholder: tt.ph} + tpl, err := ttpl.New("test").Parse(tt.tpl) + if err != nil && !tt.sf { + t.Errorf("failed to render template: %s", err) + return + } + m := NewMsg() + if err := m.SetBodyTextTemplate(tpl, data); err != nil && !tt.sf { + t.Errorf("failed to set template as body: %s", err) + } + + wbuf := bytes.Buffer{} + _, err = m.WriteTo(&wbuf) + if err != nil { + t.Errorf("failed to write body to buffer: %s", err) + } + if !strings.Contains(wbuf.String(), tt.ph) && !tt.sf { + t.Errorf("SetBodyTextTemplate failed: Body does not contain the expected tpl placeholder: %s", tt.ph) + } + m.Reset() + }) + } + } + +// TestMsg_SetBodyHTMLTemplate tests the Msg.SetBodyHTMLTemplate method + + func TestMsg_SetBodyHTMLTemplate(t *testing.T) { + tests := []struct { + name string + tpl string + ph string + ex string + sf bool + }{ + {"normal HTML", "

This is a {{.Placeholder}}

", "TemplateTest", "TemplateTest", false}, + { + "HTML with HTML", "

This is a {{.Placeholder}}

", "", + "<script>alert(1)</script>", false, + }, + {"invalid tpl", "

This is a {{ foo .Placeholder}}

", "TemplateTest", "", true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data := struct { + Placeholder string + }{Placeholder: tt.ph} + tpl, err := htpl.New("test").Parse(tt.tpl) + if err != nil && !tt.sf { + t.Errorf("failed to render template: %s", err) + return + } + m := NewMsg() + if err := m.SetBodyHTMLTemplate(tpl, data); err != nil && !tt.sf { + t.Errorf("failed to set template as body: %s", err) + } + + wbuf := bytes.Buffer{} + _, err = m.WriteTo(&wbuf) + if err != nil { + t.Errorf("failed to write body to buffer: %s", err) + } + if !strings.Contains(wbuf.String(), tt.ex) && !tt.sf { + t.Errorf("SetBodyTextTemplate failed: Body does not contain the expected tpl placeholder: %s", tt.ph) + } + m.Reset() + }) + } + } + +// TestMsg_AddAlternativeTextTemplate tests the Msg.AddAlternativeTextTemplate method + + func TestMsg_AddAlternativeTextTemplate(t *testing.T) { + tests := []struct { + name string + tpl string + ph string + sf bool + }{ + {"normal text", "This is a {{.Placeholder}}", "TemplateTest", false}, + {"invalid tpl", "This is a {{ foo .Placeholder}}", "TemplateTest", true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data := struct { + Placeholder string + }{Placeholder: tt.ph} + tpl, err := ttpl.New("test").Parse(tt.tpl) + if err != nil && !tt.sf { + t.Errorf("failed to render template: %s", err) + return + } + m := NewMsg() + m.SetBodyString(TypeTextHTML, "") + if err := m.AddAlternativeTextTemplate(tpl, data); err != nil && !tt.sf { + t.Errorf("failed to set template as alternative part: %s", err) + } + + wbuf := bytes.Buffer{} + _, err = m.WriteTo(&wbuf) + if err != nil { + t.Errorf("failed to write body to buffer: %s", err) + } + if !strings.Contains(wbuf.String(), tt.ph) && !tt.sf { + t.Errorf("SetBodyTextTemplate failed: Body does not contain the expected tpl placeholder: %s", tt.ph) + } + m.Reset() + }) + } + } + +// TestMsg_AddAlternativeHTMLTemplate tests the Msg.AddAlternativeHTMLTemplate method + + func TestMsg_AddAlternativeHTMLTemplate(t *testing.T) { + tests := []struct { + name string + tpl string + ph string + ex string + sf bool + }{ + {"normal HTML", "

This is a {{.Placeholder}}

", "TemplateTest", "TemplateTest", false}, + { + "HTML with HTML", "

This is a {{.Placeholder}}

", "", + "<script>alert(1)</script>", false, + }, + {"invalid tpl", "

This is a {{ foo .Placeholder}}

", "TemplateTest", "", true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data := struct { + Placeholder string + }{Placeholder: tt.ph} + tpl, err := htpl.New("test").Parse(tt.tpl) + if err != nil && !tt.sf { + t.Errorf("failed to render template: %s", err) + return + } + m := NewMsg() + m.SetBodyString(TypeTextPlain, "") + if err := m.AddAlternativeHTMLTemplate(tpl, data); err != nil && !tt.sf { + t.Errorf("failed to set template as alternative part: %s", err) + } + + wbuf := bytes.Buffer{} + _, err = m.WriteTo(&wbuf) + if err != nil { + t.Errorf("failed to write body to buffer: %s", err) + } + if !strings.Contains(wbuf.String(), tt.ex) && !tt.sf { + t.Errorf("SetBodyTextTemplate failed: Body does not contain the expected tpl placeholder: %s", tt.ph) + } + m.Reset() + }) + } + } + +// TestMsg_AttachTextTemplate tests the Msg.AttachTextTemplate method + + func TestMsg_AttachTextTemplate(t *testing.T) { + tests := []struct { + name string + tpl string + ph string + ex string + ac int + sf bool + }{ + { + "normal text", "This is a {{.Placeholder}}", "TemplateTest", + "VGhpcyBpcyBhIFRlbXBsYXRlVGVzdA==", 1, false, + }, + {"invalid tpl", "This is a {{ foo .Placeholder}}", "TemplateTest", "", 0, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data := struct { + Placeholder string + }{Placeholder: tt.ph} + tpl, err := ttpl.New("test").Parse(tt.tpl) + if err != nil && !tt.sf { + t.Errorf("failed to render template: %s", err) + return + } + m := NewMsg() + m.SetBodyString(TypeTextPlain, "This is the body") + if err := m.AttachTextTemplate("attachment.txt", tpl, data); err != nil && !tt.sf { + t.Errorf("failed to attach template: %s", err) + } + + wbuf := bytes.Buffer{} + _, err = m.WriteTo(&wbuf) + if err != nil { + t.Errorf("failed to write body to buffer: %s", err) + } + if !strings.Contains(wbuf.String(), tt.ex) && !tt.sf { + t.Errorf("SetBodyTextTemplate failed: Body does not contain the expected tpl placeholder: %s", tt.ph) + } + if len(m.attachments) != tt.ac { + t.Errorf("wrong number of attachments. Expected: %d, got: %d", tt.ac, len(m.attachments)) + } + m.Reset() + }) + } + } + +// TestMsg_AttachHTMLTemplate tests the Msg.AttachHTMLTemplate method + + func TestMsg_AttachHTMLTemplate(t *testing.T) { + tests := []struct { + name string + tpl string + ph string + ex string + ac int + sf bool + }{ + { + "normal HTML", "

This is a {{.Placeholder}}

", "TemplateTest", + "PHA+VGhpcyBpcyBhIFRlbXBsYXRlVGVzdDwvcD4=", 1, false, + }, + { + "HTML with HTML", "

This is a {{.Placeholder}}

", "", + "PHA+VGhpcyBpcyBhICZsdDtzY3JpcHQmZ3Q7YWxlcnQoMSkmbHQ7L3NjcmlwdCZndDs8L3A+", 1, false, + }, + {"invalid tpl", "

This is a {{ foo .Placeholder}}

", "TemplateTest", "", 0, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data := struct { + Placeholder string + }{Placeholder: tt.ph} + tpl, err := htpl.New("test").Parse(tt.tpl) + if err != nil && !tt.sf { + t.Errorf("failed to render template: %s", err) + return + } + m := NewMsg() + m.SetBodyString(TypeTextPlain, "") + if err := m.AttachHTMLTemplate("attachment.html", tpl, data); err != nil && !tt.sf { + t.Errorf("failed to set template as alternative part: %s", err) + } + + wbuf := bytes.Buffer{} + _, err = m.WriteTo(&wbuf) + if err != nil { + t.Errorf("failed to write body to buffer: %s", err) + } + if !strings.Contains(wbuf.String(), tt.ex) && !tt.sf { + t.Errorf("SetBodyTextTemplate failed: Body does not contain the expected tpl placeholder: %s", tt.ph) + } + if len(m.attachments) != tt.ac { + t.Errorf("wrong number of attachments. Expected: %d, got: %d", tt.ac, len(m.attachments)) + } + m.Reset() + }) + } + } + +// TestMsg_EmbedTextTemplate tests the Msg.EmbedTextTemplate method + + func TestMsg_EmbedTextTemplate(t *testing.T) { + tests := []struct { + name string + tpl string + ph string + ex string + ec int + sf bool + }{ + { + "normal text", "This is a {{.Placeholder}}", "TemplateTest", + "VGhpcyBpcyBhIFRlbXBsYXRlVGVzdA==", 1, false, + }, + {"invalid tpl", "This is a {{ foo .Placeholder}}", "TemplateTest", "", 0, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data := struct { + Placeholder string + }{Placeholder: tt.ph} + tpl, err := ttpl.New("test").Parse(tt.tpl) + if err != nil && !tt.sf { + t.Errorf("failed to render template: %s", err) + return + } + m := NewMsg() + m.SetBodyString(TypeTextPlain, "This is the body") + if err := m.EmbedTextTemplate("attachment.txt", tpl, data); err != nil && !tt.sf { + t.Errorf("failed to attach template: %s", err) + } + + wbuf := bytes.Buffer{} + _, err = m.WriteTo(&wbuf) + if err != nil { + t.Errorf("failed to write body to buffer: %s", err) + } + if !strings.Contains(wbuf.String(), tt.ex) && !tt.sf { + t.Errorf("SetBodyTextTemplate failed: Body does not contain the expected tpl placeholder: %s", tt.ph) + } + if len(m.embeds) != tt.ec { + t.Errorf("wrong number of attachments. Expected: %d, got: %d", tt.ec, len(m.attachments)) + } + m.Reset() + }) + } + } + +// TestMsg_EmbedHTMLTemplate tests the Msg.EmbedHTMLTemplate method + + func TestMsg_EmbedHTMLTemplate(t *testing.T) { + tests := []struct { + name string + tpl string + ph string + ex string + ec int + sf bool + }{ + { + "normal HTML", "

This is a {{.Placeholder}}

", "TemplateTest", + "PHA+VGhpcyBpcyBhIFRlbXBsYXRlVGVzdDwvcD4=", 1, false, + }, + { + "HTML with HTML", "

This is a {{.Placeholder}}

", "", + "PHA+VGhpcyBpcyBhICZsdDtzY3JpcHQmZ3Q7YWxlcnQoMSkmbHQ7L3NjcmlwdCZndDs8L3A+", 1, false, + }, + {"invalid tpl", "

This is a {{ foo .Placeholder}}

", "TemplateTest", "", 0, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + data := struct { + Placeholder string + }{Placeholder: tt.ph} + tpl, err := htpl.New("test").Parse(tt.tpl) + if err != nil && !tt.sf { + t.Errorf("failed to render template: %s", err) + return + } + m := NewMsg() + m.SetBodyString(TypeTextPlain, "") + if err := m.EmbedHTMLTemplate("attachment.html", tpl, data); err != nil && !tt.sf { + t.Errorf("failed to set template as alternative part: %s", err) + } + + wbuf := bytes.Buffer{} + _, err = m.WriteTo(&wbuf) + if err != nil { + t.Errorf("failed to write body to buffer: %s", err) + } + if !strings.Contains(wbuf.String(), tt.ex) && !tt.sf { + t.Errorf("SetBodyTextTemplate failed: Body does not contain the expected tpl placeholder: %s", tt.ph) + } + if len(m.embeds) != tt.ec { + t.Errorf("wrong number of attachments. Expected: %d, got: %d", tt.ec, len(m.attachments)) + } + m.Reset() + }) + } + } + +// TestMsg_WriteToTempFile will test the output to temporary files + + func TestMsg_WriteToTempFile(t *testing.T) { + m := NewMsg() + _ = m.From("Toni Tester ") + _ = m.To("Ellenor Tester ") + m.SetBodyString(TypeTextPlain, "This is a test") + f, err := m.WriteToTempFile() + if err != nil { + t.Errorf("failed to write message to temporary output file: %s", err) + } + _ = os.Remove(f) + } + +// TestMsg_WriteToFile will test the output to a file + + func TestMsg_WriteToFile(t *testing.T) { + f, err := os.CreateTemp("", "go-mail-test_*.eml") + if err != nil { + t.Errorf("failed to create temporary output file: %s", err) + } + defer func() { + _ = f.Close() + _ = os.Remove(f.Name()) + }() + + m := NewMsg() + _ = m.From("Toni Tester ") + _ = m.To("Ellenor Tester ") + m.SetBodyString(TypeTextPlain, "This is a test") + if err := m.WriteToFile(f.Name()); err != nil { + t.Errorf("failed to write to output file: %s", err) + } + fi, err := os.Stat(f.Name()) + if err != nil { + t.Errorf("failed to stat output file: %s", err) + } + if fi == nil { + t.Errorf("received empty file handle") + return + } + if fi.Size() <= 0 { + t.Errorf("output file is expected to contain data but its size is zero") + } + } + +// TestMsg_GetGenHeader will test the GetGenHeader method of the Msg + + func TestMsg_GetGenHeader(t *testing.T) { + m := NewMsg() + m.Subject("this is a test") + sa := m.GetGenHeader(HeaderSubject) + if len(sa) <= 0 { + t.Errorf("GetGenHeader on subject failed. Got empty slice") + return + } + if sa[0] == "" { + t.Errorf("GetGenHeader on subject failed. Got empty value") + } + if sa[0] != "this is a test" { + t.Errorf("GetGenHeader on subject failed. Expected: %q, got: %q", "this is a test", sa[0]) + } + } + +// TestMsg_GetAddrHeader will test the Msg.GetAddrHeader method + + func TestMsg_GetAddrHeader(t *testing.T) { + m := NewMsg() + if err := m.FromFormat("Toni Sender", "sender@example.com"); err != nil { + t.Errorf("failed to set FROM address: %s", err) + } + if err := m.AddToFormat("Toni To", "to@example.com"); err != nil { + t.Errorf("failed to set TO address: %s", err) + } + if err := m.AddCcFormat("Toni Cc", "cc@example.com"); err != nil { + t.Errorf("failed to set CC address: %s", err) + } + if err := m.AddBccFormat("Toni Bcc", "bcc@example.com"); err != nil { + t.Errorf("failed to set BCC address: %s", err) + } + fh := m.GetAddrHeader(HeaderFrom) + if len(fh) <= 0 { + t.Errorf("GetAddrHeader on FROM failed. Got empty slice") + return + } + if fh[0].String() == "" { + t.Errorf("GetAddrHeader on FROM failed. Got empty value") + } + if fh[0].String() != `"Toni Sender" ` { + t.Errorf("GetAddrHeader on FROM failed. Expected: %q, got: %q", + `"Toni Sender" "`, fh[0].String()) + } + th := m.GetAddrHeader(HeaderTo) + if len(th) <= 0 { + t.Errorf("GetAddrHeader on TO failed. Got empty slice") + return + } + if th[0].String() == "" { + t.Errorf("GetAddrHeader on TO failed. Got empty value") + } + if th[0].String() != `"Toni To" ` { + t.Errorf("GetAddrHeader on TO failed. Expected: %q, got: %q", + `"Toni To" "`, th[0].String()) + } + ch := m.GetAddrHeader(HeaderCc) + if len(ch) <= 0 { + t.Errorf("GetAddrHeader on CC failed. Got empty slice") + return + } + if ch[0].String() == "" { + t.Errorf("GetAddrHeader on CC failed. Got empty value") + } + if ch[0].String() != `"Toni Cc" ` { + t.Errorf("GetAddrHeader on CC failed. Expected: %q, got: %q", + `"Toni Cc" "`, ch[0].String()) + } + bh := m.GetAddrHeader(HeaderBcc) + if len(bh) <= 0 { + t.Errorf("GetAddrHeader on BCC failed. Got empty slice") + return + } + if bh[0].String() == "" { + t.Errorf("GetAddrHeader on BCC failed. Got empty value") + } + if bh[0].String() != `"Toni Bcc" ` { + t.Errorf("GetAddrHeader on BCC failed. Expected: %q, got: %q", + `"Toni Bcc" "`, bh[0].String()) + } + } + +// TestMsg_GetFrom will test the Msg.GetFrom method + + func TestMsg_GetFrom(t *testing.T) { + m := NewMsg() + if err := m.FromFormat("Toni Sender", "sender@example.com"); err != nil { + t.Errorf("failed to set FROM address: %s", err) + } + fh := m.GetFrom() + if len(fh) <= 0 { + t.Errorf("GetFrom failed. Got empty slice") + return + } + if fh[0].String() == "" { + t.Errorf("GetFrom failed. Got empty value") + } + if fh[0].String() != `"Toni Sender" ` { + t.Errorf("GetFrom failed. Expected: %q, got: %q", + `"Toni Sender" "`, fh[0].String()) + } + } + +// TestMsg_GetFromString will test the Msg.GetFromString method + + func TestMsg_GetFromString(t *testing.T) { + m := NewMsg() + if err := m.FromFormat("Toni Sender", "sender@example.com"); err != nil { + t.Errorf("failed to set FROM address: %s", err) + } + fh := m.GetFromString() + if len(fh) <= 0 { + t.Errorf("GetFromString failed. Got empty slice") + return + } + if fh[0] == "" { + t.Errorf("GetFromString failed. Got empty value") + } + if fh[0] != `"Toni Sender" ` { + t.Errorf("GetFromString failed. Expected: %q, got: %q", + `"Toni Sender" "`, fh[0]) + } + } + +// TestMsg_GetTo will test the Msg.GetTo method + + func TestMsg_GetTo(t *testing.T) { + m := NewMsg() + if err := m.AddToFormat("Toni To", "to@example.com"); err != nil { + t.Errorf("failed to set TO address: %s", err) + } + fh := m.GetTo() + if len(fh) <= 0 { + t.Errorf("GetTo failed. Got empty slice") + return + } + if fh[0].String() == "" { + t.Errorf("GetTo failed. Got empty value") + } + if fh[0].String() != `"Toni To" ` { + t.Errorf("GetTo failed. Expected: %q, got: %q", + `"Toni To" "`, fh[0].String()) + } + } + +// TestMsg_GetToString will test the Msg.GetToString method + + func TestMsg_GetToString(t *testing.T) { + m := NewMsg() + if err := m.AddToFormat("Toni To", "to@example.com"); err != nil { + t.Errorf("failed to set TO address: %s", err) + } + fh := m.GetToString() + if len(fh) <= 0 { + t.Errorf("GetToString failed. Got empty slice") + return + } + if fh[0] == "" { + t.Errorf("GetToString failed. Got empty value") + } + if fh[0] != `"Toni To" ` { + t.Errorf("GetToString failed. Expected: %q, got: %q", + `"Toni To" "`, fh[0]) + } + } + +// TestMsg_GetCc will test the Msg.GetCc method + + func TestMsg_GetCc(t *testing.T) { + m := NewMsg() + if err := m.AddCcFormat("Toni Cc", "cc@example.com"); err != nil { + t.Errorf("failed to set TO address: %s", err) + } + fh := m.GetCc() + if len(fh) <= 0 { + t.Errorf("GetCc failed. Got empty slice") + return + } + if fh[0].String() == "" { + t.Errorf("GetCc failed. Got empty value") + } + if fh[0].String() != `"Toni Cc" ` { + t.Errorf("GetCc failed. Expected: %q, got: %q", + `"Toni Cc" "`, fh[0].String()) + } + } + +// TestMsg_GetCcString will test the Msg.GetCcString method + + func TestMsg_GetCcString(t *testing.T) { + m := NewMsg() + if err := m.AddCcFormat("Toni Cc", "cc@example.com"); err != nil { + t.Errorf("failed to set TO address: %s", err) + } + fh := m.GetCcString() + if len(fh) <= 0 { + t.Errorf("GetCcString failed. Got empty slice") + return + } + if fh[0] == "" { + t.Errorf("GetCcString failed. Got empty value") + } + if fh[0] != `"Toni Cc" ` { + t.Errorf("GetCcString failed. Expected: %q, got: %q", + `"Toni Cc" "`, fh[0]) + } + } + +// TestMsg_GetBcc will test the Msg.GetBcc method + + func TestMsg_GetBcc(t *testing.T) { + m := NewMsg() + if err := m.AddBccFormat("Toni Bcc", "bcc@example.com"); err != nil { + t.Errorf("failed to set TO address: %s", err) + } + fh := m.GetBcc() + if len(fh) <= 0 { + t.Errorf("GetBcc failed. Got empty slice") + return + } + if fh[0].String() == "" { + t.Errorf("GetBcc failed. Got empty value") + } + if fh[0].String() != `"Toni Bcc" ` { + t.Errorf("GetBcc failed. Expected: %q, got: %q", + `"Toni Cc" "`, fh[0].String()) + } + } + +// TestMsg_GetBccString will test the Msg.GetBccString method + + func TestMsg_GetBccString(t *testing.T) { + m := NewMsg() + if err := m.AddBccFormat("Toni Bcc", "bcc@example.com"); err != nil { + t.Errorf("failed to set TO address: %s", err) + } + fh := m.GetBccString() + if len(fh) <= 0 { + t.Errorf("GetBccString failed. Got empty slice") + return + } + if fh[0] == "" { + t.Errorf("GetBccString failed. Got empty value") + } + if fh[0] != `"Toni Bcc" ` { + t.Errorf("GetBccString failed. Expected: %q, got: %q", + `"Toni Cc" "`, fh[0]) + } + } + +// TestMsg_GetBoundary will test the Msg.GetBoundary method + + func TestMsg_GetBoundary(t *testing.T) { + b := "random_boundary_string" + m := NewMsg() + if boundary := m.GetBoundary(); boundary != "" { + t.Errorf("GetBoundary failed. Expected empty string, but got: %s", boundary) + } + m = NewMsg(WithBoundary(b)) + if boundary := m.GetBoundary(); boundary != b { + t.Errorf("GetBoundary failed. Expected boundary: %s, got: %s", b, boundary) + } + } + +// TestMsg_AttachEmbedReader_consecutive tests the Msg.AttachReader and Msg.EmbedReader +// methods with consecutive calls to Msg.WriteTo to make sure the attachments are not +// lost (see Github issue #110) + + func TestMsg_AttachEmbedReader_consecutive(t *testing.T) { + ts1 := "This is a test string" + ts2 := "Another test string" + m := NewMsg() + if err := m.AttachReader("attachment.txt", bytes.NewBufferString(ts1)); err != nil { + t.Errorf("AttachReader() failed. Expected no error, got: %s", err.Error()) + return + } + if err := m.EmbedReader("embedded.txt", bytes.NewBufferString(ts2)); err != nil { + t.Errorf("EmbedReader() failed. Expected no error, got: %s", err.Error()) + return + } + obuf1 := &bytes.Buffer{} + obuf2 := &bytes.Buffer{} + _, err := m.WriteTo(obuf1) + if err != nil { + t.Errorf("WriteTo to first output buffer failed: %s", err) + } + _, err = m.WriteTo(obuf2) + if err != nil { + t.Errorf("WriteTo to second output buffer failed: %s", err) + } + if !strings.Contains(obuf1.String(), "VGhpcyBpcyBhIHRlc3Qgc3RyaW5n") { + t.Errorf("Expected file attachment string not found in first output buffer") + } + if !strings.Contains(obuf2.String(), "VGhpcyBpcyBhIHRlc3Qgc3RyaW5n") { + t.Errorf("Expected file attachment string not found in second output buffer") + } + if !strings.Contains(obuf1.String(), "QW5vdGhlciB0ZXN0IHN0cmluZw==") { + t.Errorf("Expected embedded file string not found in first output buffer") + } + if !strings.Contains(obuf2.String(), "QW5vdGhlciB0ZXN0IHN0cmluZw==") { + t.Errorf("Expected embded file string not found in second output buffer") + } + } + +// TestMsg_AttachEmbedReadSeeker_consecutive tests the Msg.AttachReadSeeker and +// Msg.EmbedReadSeeker methods with consecutive calls to Msg.WriteTo to make +// sure the attachments are not lost (see Github issue #110) + + func TestMsg_AttachEmbedReadSeeker_consecutive(t *testing.T) { + ts1 := []byte("This is a test string") + ts2 := []byte("Another test string") + m := NewMsg() + m.AttachReadSeeker("attachment.txt", bytes.NewReader(ts1)) + m.EmbedReadSeeker("embedded.txt", bytes.NewReader(ts2)) + obuf1 := &bytes.Buffer{} + obuf2 := &bytes.Buffer{} + _, err := m.WriteTo(obuf1) + if err != nil { + t.Errorf("WriteTo to first output buffer failed: %s", err) + } + _, err = m.WriteTo(obuf2) + if err != nil { + t.Errorf("WriteTo to second output buffer failed: %s", err) + } + if !strings.Contains(obuf1.String(), "VGhpcyBpcyBhIHRlc3Qgc3RyaW5n") { + t.Errorf("Expected file attachment string not found in first output buffer") + } + if !strings.Contains(obuf2.String(), "VGhpcyBpcyBhIHRlc3Qgc3RyaW5n") { + t.Errorf("Expected file attachment string not found in second output buffer") + } + if !strings.Contains(obuf1.String(), "QW5vdGhlciB0ZXN0IHN0cmluZw==") { + t.Errorf("Expected embedded file string not found in first output buffer") + } + if !strings.Contains(obuf2.String(), "QW5vdGhlciB0ZXN0IHN0cmluZw==") { + t.Errorf("Expected embded file string not found in second output buffer") + } + } + +// TestMsg_AttachReadSeeker tests the Msg.AttachReadSeeker method + + func TestMsg_AttachReadSeeker(t *testing.T) { + m := NewMsg() + ts := []byte("This is a test string") + r := bytes.NewReader(ts) + m.AttachReadSeeker("testfile.txt", r) + if len(m.attachments) != 1 { + t.Errorf("AttachReadSeeker() failed. Number of attachments expected: %d, got: %d", 1, + len(m.attachments)) + return + } + file := m.attachments[0] + if file == nil { + t.Errorf("AttachReadSeeker() failed. Attachment file pointer is nil") + return + } + if file.Name != "testfile.txt" { + t.Errorf("AttachReadSeeker() failed. Expected file name: %s, got: %s", "testfile.txt", + file.Name) + } + wbuf := bytes.Buffer{} + if _, err := file.Writer(&wbuf); err != nil { + t.Errorf("execute WriterFunc failed: %s", err) + } + if wbuf.String() != string(ts) { + t.Errorf("AttachReadSeeker() failed. Expected string: %q, got: %q", ts, wbuf.String()) + } + } + +// TestMsg_EmbedReadSeeker tests the Msg.EmbedReadSeeker method + + func TestMsg_EmbedReadSeeker(t *testing.T) { + m := NewMsg() + ts := []byte("This is a test string") + r := bytes.NewReader(ts) + m.EmbedReadSeeker("testfile.txt", r) + if len(m.embeds) != 1 { + t.Errorf("EmbedReadSeeker() failed. Number of attachments expected: %d, got: %d", 1, + len(m.embeds)) + return + } + file := m.embeds[0] + if file == nil { + t.Errorf("EmbedReadSeeker() failed. Embedded file pointer is nil") + return + } + if file.Name != "testfile.txt" { + t.Errorf("EmbedReadSeeker() failed. Expected file name: %s, got: %s", "testfile.txt", + file.Name) + } + wbuf := bytes.Buffer{} + if _, err := file.Writer(&wbuf); err != nil { + t.Errorf("execute WriterFunc failed: %s", err) + } + if wbuf.String() != string(ts) { + t.Errorf("EmbedReadSeeker() failed. Expected string: %q, got: %q", ts, wbuf.String()) + } + } + +// TestMsg_ToFromString tests Msg.ToFromString in different scenarios + + func TestMsg_ToFromString(t *testing.T) { + tests := []struct { + n string + v string + w []*mail.Address + sf bool + }{ + {"valid single address", "test@test.com", []*mail.Address{ + {Name: "", Address: "test@test.com"}, + }, false}, + { + "valid multiple addresses", "test@test.com,test2@example.com", + []*mail.Address{ + {Name: "", Address: "test@test.com"}, + {Name: "", Address: "test2@example.com"}, + }, + false, + }, + { + "valid multiple addresses with space and name", + `test@test.com, "Toni Tester" `, + []*mail.Address{ + {Name: "", Address: "test@test.com"}, + {Name: "Toni Tester", Address: "test2@example.com"}, + }, + false, + }, + { + "invalid and valid multiple addresses", "test@test.com,test2#example.com", nil, + true, + }, + } + + for _, tt := range tests { + t.Run(tt.n, func(t *testing.T) { + m := NewMsg() + if err := m.ToFromString(tt.v); err != nil && !tt.sf { + t.Errorf("Msg.ToFromString failed: %s", err) + return + } + mto := m.GetTo() + if len(mto) != len(tt.w) { + t.Errorf("Msg.ToFromString failed, expected len: %d, got: %d", len(tt.w), + len(mto)) + return + } + for i := range mto { + w := tt.w[i] + g := mto[i] + if w.String() != g.String() { + t.Errorf("Msg.ToFromString failed, expected address: %s, got: %s", + w.String(), g.String()) + } + } + }) + } + } + +// TestMsg_CcFromString tests Msg.CcFromString in different scenarios + + func TestMsg_CcFromString(t *testing.T) { + tests := []struct { + n string + v string + w []*mail.Address + sf bool + }{ + {"valid single address", "test@test.com", []*mail.Address{ + {Name: "", Address: "test@test.com"}, + }, false}, + { + "valid multiple addresses", "test@test.com,test2@example.com", + []*mail.Address{ + {Name: "", Address: "test@test.com"}, + {Name: "", Address: "test2@example.com"}, + }, + false, + }, + { + "valid multiple addresses with space and name", + `test@test.com, "Toni Tester" `, + []*mail.Address{ + {Name: "", Address: "test@test.com"}, + {Name: "Toni Tester", Address: "test2@example.com"}, + }, + false, + }, + { + "invalid and valid multiple addresses", "test@test.com,test2#example.com", nil, + true, + }, + } + + for _, tt := range tests { + t.Run(tt.n, func(t *testing.T) { + m := NewMsg() + if err := m.CcFromString(tt.v); err != nil && !tt.sf { + t.Errorf("Msg.CcFromString failed: %s", err) + return + } + mto := m.GetCc() + if len(mto) != len(tt.w) { + t.Errorf("Msg.CcFromString failed, expected len: %d, got: %d", len(tt.w), + len(mto)) + return + } + for i := range mto { + w := tt.w[i] + g := mto[i] + if w.String() != g.String() { + t.Errorf("Msg.CcFromString failed, expected address: %s, got: %s", + w.String(), g.String()) + } + } + }) + } + } + +// TestMsg_BccFromString tests Msg.BccFromString in different scenarios + + func TestMsg_BccFromString(t *testing.T) { + tests := []struct { + n string + v string + w []*mail.Address + sf bool + }{ + {"valid single address", "test@test.com", []*mail.Address{ + {Name: "", Address: "test@test.com"}, + }, false}, + { + "valid multiple addresses", "test@test.com,test2@example.com", + []*mail.Address{ + {Name: "", Address: "test@test.com"}, + {Name: "", Address: "test2@example.com"}, + }, + false, + }, + { + "valid multiple addresses with space and name", + `test@test.com, "Toni Tester" `, + []*mail.Address{ + {Name: "", Address: "test@test.com"}, + {Name: "Toni Tester", Address: "test2@example.com"}, + }, + false, + }, + { + "invalid and valid multiple addresses", "test@test.com,test2#example.com", nil, + true, + }, + } + + for _, tt := range tests { + t.Run(tt.n, func(t *testing.T) { + m := NewMsg() + if err := m.BccFromString(tt.v); err != nil && !tt.sf { + t.Errorf("Msg.BccFromString failed: %s", err) + return + } + mto := m.GetBcc() + if len(mto) != len(tt.w) { + t.Errorf("Msg.BccFromString failed, expected len: %d, got: %d", len(tt.w), + len(mto)) + return + } + for i := range mto { + w := tt.w[i] + g := mto[i] + if w.String() != g.String() { + t.Errorf("Msg.BccFromString failed, expected address: %s, got: %s", + w.String(), g.String()) + } + } + }) + } + } + +// TestMsg_checkUserAgent tests the checkUserAgent method of the Msg + + func TestMsg_checkUserAgent(t *testing.T) { + tests := []struct { + name string + noDefaultUserAgent bool + genHeader map[Header][]string + wantUserAgent string + sf bool + }{ + { + name: "check default user agent", + noDefaultUserAgent: false, + wantUserAgent: fmt.Sprintf("go-mail v%s // https://github.com/wneessen/go-mail", VERSION), + sf: false, + }, + { + name: "check no default user agent", + noDefaultUserAgent: true, + wantUserAgent: "", + sf: true, + }, + { + name: "check if ua and xm is already set", + noDefaultUserAgent: false, + genHeader: map[Header][]string{ + HeaderUserAgent: {"custom UA"}, + HeaderXMailer: {"custom XM"}, + }, + wantUserAgent: "custom UA", + sf: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + msg := &Msg{ + noDefaultUserAgent: tt.noDefaultUserAgent, + genHeader: tt.genHeader, + } + msg.checkUserAgent() + gotUserAgent := "" + if val, ok := msg.genHeader[HeaderUserAgent]; ok { + gotUserAgent = val[0] // Assuming the first one is the needed value + } + if gotUserAgent != tt.wantUserAgent && !tt.sf { + t.Errorf("UserAgent got = %v, want = %v", gotUserAgent, tt.wantUserAgent) + } + }) + } + } + +// TestNewMsgWithMIMEVersion tests WithMIMEVersion and Msg.SetMIMEVersion + + func TestNewMsgWithNoDefaultUserAgent(t *testing.T) { + m := NewMsg(WithNoDefaultUserAgent()) + if m.noDefaultUserAgent != true { + t.Errorf("WithNoDefaultUserAgent() failed. Expected: %t, got: %t", true, false) + } + } + +// Fuzzing tests + + func FuzzMsg_Subject(f *testing.F) { + f.Add("Testsubject") + f.Add("") + f.Add("This is a longer test subject.") + f.Add("Let's add some umlauts: üäöß") + f.Add("Or even emojis: ☝️💪👍") + f.Fuzz(func(t *testing.T, data string) { + m := NewMsg() + m.Subject(data) + m.Reset() }) } -} + func FuzzMsg_From(f *testing.F) { + f.Add("Toni Tester ") + f.Add("") + f.Add("mail@server.com") + f.Fuzz(func(t *testing.T, data string) { + m := NewMsg() + if err := m.From(data); err != nil && + !strings.Contains(err.Error(), "failed to parse mail address") { + t.Errorf("failed set set FROM address: %s", err) + } + m.Reset() + }) + } +*/ type uppercaseMiddleware struct{} func (mw uppercaseMiddleware) Handle(m *Msg) *Msg { @@ -244,3032 +3540,3 @@ func (mw encodeMiddleware) Handle(m *Msg) *Msg { func (mw encodeMiddleware) Type() MiddlewareType { return "encode" } - -// TestNewMsgWithMiddleware tests WithMiddleware -func TestNewMsgWithMiddleware(t *testing.T) { - m := NewMsg() - if len(m.middlewares) != 0 { - t.Errorf("empty middlewares failed. m.middlewares expected to be: empty, got: %d middleware", len(m.middlewares)) - } - m = NewMsg(WithMiddleware(uppercaseMiddleware{})) - if len(m.middlewares) != 1 { - t.Errorf("empty middlewares failed. m.middlewares expected to be: 1, got: %d middleware", len(m.middlewares)) - } - m = NewMsg(WithMiddleware(uppercaseMiddleware{}), WithMiddleware(encodeMiddleware{})) - if len(m.middlewares) != 2 { - t.Errorf("empty middlewares failed. m.middlewares expected to be: 2, got: %d middleware", len(m.middlewares)) - } -} - -// TestApplyMiddlewares tests the applyMiddlewares for the Msg object -func TestApplyMiddlewares(t *testing.T) { - tests := []struct { - name string - sub string - want string - }{ - {"normal subject", "This is a test subject", "THIS IS @ TEST SUBJECT"}, - {"subject with one middleware effect", "This is test subject", "THIS IS TEST SUBJECT"}, - {"subject with one middleware effect", "This is A test subject", "THIS IS A TEST SUBJECT"}, - } - m := NewMsg(WithMiddleware(encodeMiddleware{}), WithMiddleware(uppercaseMiddleware{})) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - m.Subject(tt.sub) - if m.genHeader[HeaderSubject] == nil { - t.Errorf("Subject() method failed in applyMiddlewares() test. Generic header for subject is empty") - return - } - m = m.applyMiddlewares(m) - s, ok := m.genHeader[HeaderSubject] - if !ok { - t.Errorf("failed to get subject header") - } - if s[0] != tt.want { - t.Errorf("applyMiddlewares() method failed. Expected: %s, got: %s", tt.want, s[0]) - } - }) - } -} - -// TestMsg_SetGenHeader tests Msg.SetGenHeader -func TestMsg_SetGenHeader(t *testing.T) { - tests := []struct { - name string - header Header - values []string - }{ - {"set subject", HeaderSubject, []string{"This is Subject"}}, - {"set content-language", HeaderContentLang, []string{"en", "de", "fr", "es"}}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - m := NewMsg() - m.SetGenHeader(tt.header, tt.values...) - if m.genHeader[tt.header] == nil { - t.Errorf("SetGenHeader() failed. Tried to set header %s, but it is empty", tt.header) - return - } - for _, v := range tt.values { - found := false - for _, hv := range m.genHeader[tt.header] { - if hv == v { - found = true - } - } - if !found { - t.Errorf("SetGenHeader() failed. Value %s not found in header field", v) - } - } - }) - } -} - -// TestMsg_SetGenHeaderPreformatted tests Msg.SetGenHeaderPreformatted -func TestMsg_SetGenHeaderPreformatted(t *testing.T) { - tests := []struct { - name string - header Header - value string - }{ - {"set subject", HeaderSubject, "This is Subject"}, - {"set content-language", HeaderContentLang, fmt.Sprintf("%s, %s, %s, %s", - "en", "de", "fr", "es")}, - {"set subject with newline", HeaderSubject, "This is Subject\r\n with 2nd line"}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - m := &Msg{} - m.SetGenHeaderPreformatted(tt.header, tt.value) - m = NewMsg() - m.SetGenHeaderPreformatted(tt.header, tt.value) - if m.preformHeader[tt.header] == "" { - t.Errorf("SetGenHeaderPreformatted() failed. Tried to set header %s, but it is empty", tt.header) - } - if m.preformHeader[tt.header] != tt.value { - t.Errorf("SetGenHeaderPreformatted() failed. Expected: %q, got: %q", tt.value, - m.preformHeader[tt.header]) - } - buf := bytes.Buffer{} - _, err := m.WriteTo(&buf) - if err != nil { - t.Errorf("failed to write message to memory: %s", err) - return - } - if !strings.Contains(buf.String(), fmt.Sprintf("%s: %s%s", tt.header, tt.value, SingleNewLine)) { - t.Errorf("SetGenHeaderPreformatted() failed. Unable to find correctly formated header in " + - "mail message output") - } - }) - } -} - -// TestMsg_AddTo tests the Msg.AddTo method -func TestMsg_AddTo(t *testing.T) { - a := []string{"address1@example.com", "address2@example.com"} - na := "address3@example.com" - m := NewMsg() - if err := m.To(a...); err != nil { - t.Errorf("failed to set TO addresses: %s", err) - return - } - if err := m.AddTo(na); err != nil { - t.Errorf("AddTo failed: %s", err) - return - } - - atf := false - for _, v := range m.addrHeader[HeaderTo] { - if v.Address == na { - atf = true - } - } - if !atf { - t.Errorf("AddTo() failed. Address %q not found in TO address slice.", na) - } -} - -// TestMsg_From tests the Msg.From and Msg.GetSender methods -func TestMsg_From(t *testing.T) { - a := "toni@example.com" - n := "Toni Tester" - na := fmt.Sprintf(`"%s" <%s>`, n, a) - m := NewMsg() - - _, err := m.GetSender(false) - if err == nil { - t.Errorf("GetSender(false) without a set From address succeeded but was expected to fail") - return - } - - if err := m.From(na); err != nil { - t.Errorf("failed to set FROM addresses: %s", err) - return - } - gs, err := m.GetSender(false) - if err != nil { - t.Errorf("GetSender(false) failed: %s", err) - return - } - if gs != a { - t.Errorf("From() failed. Expected: %s, got: %s", a, gs) - return - } - - gs, err = m.GetSender(true) - if err != nil { - t.Errorf("GetSender(true) failed: %s", err) - return - } - if gs != na { - t.Errorf("From() failed. Expected: %s, got: %s", na, gs) - return - } -} - -// TestMsg_EnvelopeFrom tests the Msg.EnvelopeFrom and Msg.GetSender methods -func TestMsg_EnvelopeFrom(t *testing.T) { - e := "envelope@example.com" - a := "toni@example.com" - n := "Toni Tester" - na := fmt.Sprintf(`"%s" <%s>`, n, a) - ne := fmt.Sprintf(`<%s>`, e) - m := NewMsg() - - _, err := m.GetSender(false) - if err == nil { - t.Errorf("GetSender(false) without a set envelope From address succeeded but was expected to fail") - return - } - - if err := m.EnvelopeFrom(e); err != nil { - t.Errorf("failed to set envelope FROM addresses: %s", err) - return - } - gs, err := m.GetSender(false) - if err != nil { - t.Errorf("GetSender(false) failed: %s", err) - return - } - if gs != e { - t.Errorf("From() failed. Expected: %s, got: %s", e, gs) - return - } - - if err := m.From(na); err != nil { - t.Errorf("failed to set FROM addresses: %s", err) - return - } - gs, err = m.GetSender(false) - if err != nil { - t.Errorf("GetSender(false) failed: %s", err) - return - } - if gs != e { - t.Errorf("From() failed. Expected: %s, got: %s", e, gs) - return - } - - gs, err = m.GetSender(true) - if err != nil { - t.Errorf("GetSender(true) failed: %s", err) - return - } - if gs != ne { - t.Errorf("From() failed. Expected: %s, got: %s", ne, gs) - return - } - m.Reset() - - if err := m.From(na); err != nil { - t.Errorf("failed to set FROM addresses: %s", err) - return - } - gs, err = m.GetSender(false) - if err != nil { - t.Errorf("GetSender(true) failed: %s", err) - return - } - if gs != a { - t.Errorf("From() failed. Expected: %s, got: %s", a, gs) - return - } - gs, err = m.GetSender(true) - if err != nil { - t.Errorf("GetSender(true) failed: %s", err) - return - } - if gs != na { - t.Errorf("From() failed. Expected: %s, got: %s", na, gs) - return - } -} - -// TestMsg_AddToFormat tests the Msg.AddToFormat method -func TestMsg_AddToFormat(t *testing.T) { - a := []string{"address1@example.com", "address2@example.com"} - nn := "Toni Tester" - na := "address3@example.com" - w := `"Toni Tester" ` - m := NewMsg() - if err := m.To(a...); err != nil { - t.Errorf("failed to set TO addresses: %s", err) - return - } - if err := m.AddToFormat(nn, na); err != nil { - t.Errorf("AddToFormat failed: %s", err) - return - } - - atf := false - for _, v := range m.addrHeader[HeaderTo] { - if v.String() == w { - atf = true - } - } - if !atf { - t.Errorf("AddToFormat() failed. Address %q not found in TO address slice.", w) - } -} - -// TestMsg_ToIgnoreInvalid tests the Msg.ToIgnoreInvalid method -func TestMsg_ToIgnoreInvalid(t *testing.T) { - a := []string{"address1@example.com", "address2@example.com"} - fa := []string{"address1@example.com", "address2@example.com", "failedaddress.com"} - m := NewMsg() - - m.ToIgnoreInvalid(a...) - l := len(m.addrHeader[HeaderTo]) - if l != len(a) { - t.Errorf("ToIgnoreInvalid() failed. Expected %d addresses, got: %d", len(a), l) - } - m.ToIgnoreInvalid(fa...) - l = len(m.addrHeader[HeaderTo]) - if l != len(fa)-1 { - t.Errorf("ToIgnoreInvalid() failed. Expected %d addresses, got: %d", len(fa)-1, l) - } -} - -// TestMsg_AddCc tests the Msg.AddCc method -func TestMsg_AddCc(t *testing.T) { - a := []string{"address1@example.com", "address2@example.com"} - na := "address3@example.com" - m := NewMsg() - if err := m.Cc(a...); err != nil { - t.Errorf("failed to set CC addresses: %s", err) - return - } - if err := m.AddCc(na); err != nil { - t.Errorf("AddCc failed: %s", err) - return - } - - atf := false - for _, v := range m.addrHeader[HeaderCc] { - if v.Address == na { - atf = true - } - } - if !atf { - t.Errorf("AddCc() failed. Address %q not found in CC address slice.", na) - } -} - -// TestMsg_AddCcFormat tests the Msg.AddCcFormat method -func TestMsg_AddCcFormat(t *testing.T) { - a := []string{"address1@example.com", "address2@example.com"} - nn := "Toni Tester" - na := "address3@example.com" - w := `"Toni Tester" ` - m := NewMsg() - if err := m.Cc(a...); err != nil { - t.Errorf("failed to set CC addresses: %s", err) - return - } - if err := m.AddCcFormat(nn, na); err != nil { - t.Errorf("AddCcFormat failed: %s", err) - return - } - - atf := false - for _, v := range m.addrHeader[HeaderCc] { - if v.String() == w { - atf = true - } - } - if !atf { - t.Errorf("AddCcFormat() failed. Address %q not found in CC address slice.", w) - } -} - -// TestMsg_CcIgnoreInvalid tests the Msg.CcIgnoreInvalid method -func TestMsg_CcIgnoreInvalid(t *testing.T) { - a := []string{"address1@example.com", "address2@example.com"} - fa := []string{"address1@example.com", "address2@example.com", "failedaddress.com"} - m := NewMsg() - - m.CcIgnoreInvalid(a...) - l := len(m.addrHeader[HeaderCc]) - if l != len(a) { - t.Errorf("CcIgnoreInvalid() failed. Expected %d addresses, got: %d", len(a), l) - } - m.CcIgnoreInvalid(fa...) - l = len(m.addrHeader[HeaderCc]) - if l != len(fa)-1 { - t.Errorf("CcIgnoreInvalid() failed. Expected %d addresses, got: %d", len(fa)-1, l) - } -} - -// TestMsg_AddBcc tests the Msg.AddBcc method -func TestMsg_AddBcc(t *testing.T) { - a := []string{"address1@example.com", "address2@example.com"} - na := "address3@example.com" - m := NewMsg() - if err := m.Bcc(a...); err != nil { - t.Errorf("failed to set BCC addresses: %s", err) - return - } - if err := m.AddBcc(na); err != nil { - t.Errorf("AddBcc failed: %s", err) - return - } - - atf := false - for _, v := range m.addrHeader[HeaderBcc] { - if v.Address == na { - atf = true - } - } - if !atf { - t.Errorf("AddBcc() failed. Address %q not found in BCC address slice.", na) - } -} - -// TestMsg_AddBccFormat tests the Msg.AddBccFormat method -func TestMsg_AddBccFormat(t *testing.T) { - a := []string{"address1@example.com", "address2@example.com"} - nn := "Toni Tester" - na := "address3@example.com" - w := `"Toni Tester" ` - m := NewMsg() - if err := m.Bcc(a...); err != nil { - t.Errorf("failed to set BCC addresses: %s", err) - return - } - if err := m.AddBccFormat(nn, na); err != nil { - t.Errorf("AddBccFormat failed: %s", err) - return - } - - atf := false - for _, v := range m.addrHeader[HeaderBcc] { - if v.String() == w { - atf = true - } - } - if !atf { - t.Errorf("AddBccFormat() failed. Address %q not found in BCC address slice.", w) - } -} - -// TestMsg_BccIgnoreInvalid tests the Msg.BccIgnoreInvalid method -func TestMsg_BccIgnoreInvalid(t *testing.T) { - a := []string{"address1@example.com", "address2@example.com"} - fa := []string{"address1@example.com", "address2@example.com", "failedaddress.com"} - m := NewMsg() - - m.BccIgnoreInvalid(a...) - l := len(m.addrHeader[HeaderBcc]) - if l != len(a) { - t.Errorf("BccIgnoreInvalid() failed. Expected %d addresses, got: %d", len(a), l) - } - m.BccIgnoreInvalid(fa...) - l = len(m.addrHeader[HeaderBcc]) - if l != len(fa)-1 { - t.Errorf("BccIgnoreInvalid() failed. Expected %d addresses, got: %d", len(fa)-1, l) - } -} - -// TestMsg_SetBulk tests the Msg.SetBulk method -func TestMsg_SetBulk(t *testing.T) { - m := NewMsg() - m.SetBulk() - if m.genHeader[HeaderPrecedence] == nil { - t.Errorf("SetBulk() failed. Precedence header is nil") - return - } - if m.genHeader[HeaderPrecedence][0] != "bulk" { - t.Errorf("SetBulk() failed. Expected Precedence header: %q, got: %q", "bulk", - m.genHeader[HeaderPrecedence][0]) - } - if m.genHeader[HeaderXAutoResponseSuppress] == nil { - t.Errorf("SetBulk() failed. X-Auto-Response-Suppress header is nil") - return - } - if m.genHeader[HeaderXAutoResponseSuppress][0] != "All" { - t.Errorf("SetBulk() failed. Expected X-Auto-Response-Suppress header: %q, got: %q", "All", - m.genHeader[HeaderXAutoResponseSuppress][0]) - } -} - -// TestMsg_SetDate tests the Msg.SetDate and Msg.SetDateWithValue method -func TestMsg_SetDate(t *testing.T) { - m := NewMsg() - m.SetDate() - if m.genHeader[HeaderDate] == nil { - t.Errorf("SetDate() failed. Date header is nil") - return - } - d, ok := m.genHeader[HeaderDate] - if !ok { - t.Errorf("failed to get date header") - return - } - _, err := time.Parse(time.RFC1123Z, d[0]) - if err != nil { - t.Errorf("failed to parse time in date header: %s", err) - } - m.genHeader = nil - m.genHeader = make(map[Header][]string) - - now := time.Now() - m.SetDateWithValue(now) - if m.genHeader[HeaderDate] == nil { - t.Errorf("SetDateWithValue() failed. Date header is nil") - return - } - d, ok = m.genHeader[HeaderDate] - if !ok { - t.Errorf("failed to get date header") - return - } - pt, err := time.Parse(time.RFC1123Z, d[0]) - if err != nil { - t.Errorf("failed to parse time in date header: %s", err) - } - if pt.Unix() != now.Unix() { - t.Errorf("SetDateWithValue() failed. Expected time: %d, got: %d", now.Unix(), - pt.Unix()) - } -} - -// TestMsg_SetMessageIDWIthValue tests the Msg.SetMessageIDWithValue and Msg.SetMessageID methods -func TestMsg_SetMessageIDWithValue(t *testing.T) { - m := NewMsg() - m.SetMessageID() - if m.genHeader[HeaderMessageID] == nil { - t.Errorf("SetMessageID() failed. MessageID header is nil") - return - } - if m.genHeader[HeaderMessageID][0] == "" { - t.Errorf("SetMessageID() failed. Expected value, got: empty") - return - } - if _, ok := m.genHeader[HeaderMessageID]; ok { - m.genHeader[HeaderMessageID] = nil - } - v := "This.is.a.message.id" - vf := "" - m.SetMessageIDWithValue(v) - if m.genHeader[HeaderMessageID] == nil { - t.Errorf("SetMessageIDWithValue() failed. MessageID header is nil") - return - } - if m.genHeader[HeaderMessageID][0] != vf { - t.Errorf("SetMessageIDWithValue() failed. Expected: %s, got: %s", vf, m.genHeader[HeaderMessageID][0]) - return - } -} - -// TestMsg_SetMessageIDRandomness tests the randomness of Msg.SetMessageID methods -func TestMsg_SetMessageIDRandomness(t *testing.T) { - var mids []string - m := NewMsg() - for i := 0; i < 50_000; i++ { - m.SetMessageID() - mid := m.GetMessageID() - mids = append(mids, mid) - } - c := make(map[string]int) - for i := range mids { - c[mids[i]]++ - } - for k, v := range c { - if v > 1 { - t.Errorf("MessageID randomness not given. MessageID %q was generated %d times", k, v) - } - } -} - -func TestMsg_GetMessageID(t *testing.T) { - expected := "this.is.a.message.id" - msg := NewMsg() - msg.SetMessageIDWithValue(expected) - val := msg.GetMessageID() - if !strings.EqualFold(val, fmt.Sprintf("<%s>", expected)) { - t.Errorf("GetMessageID() failed. Expected: %s, got: %s", fmt.Sprintf("<%s>", expected), val) - } - msg.genHeader[HeaderMessageID] = nil - val = msg.GetMessageID() - if val != "" { - t.Errorf("GetMessageID() failed. Expected empty string, got: %s", val) - } -} - -// TestMsg_FromFormat tests the FromFormat and EnvelopeFrom methods for the Msg object -func TestMsg_FromFormat(t *testing.T) { - tests := []struct { - tname string - name string - addr string - want string - fail bool - }{ - { - "valid name and addr", "Toni Tester", "tester@example.com", - `"Toni Tester" `, false, - }, - { - "no name with valid addr", "", "tester@example.com", - ``, false, - }, - { - "valid name with invalid addr", "Toni Tester", "@example.com", - ``, true, - }, - } - - m := NewMsg() - for _, tt := range tests { - t.Run(tt.tname, func(t *testing.T) { - if err := m.FromFormat(tt.name, tt.addr); err != nil && !tt.fail { - t.Errorf("failed to FromFormat(): %s", err) - return - } - if err := m.EnvelopeFromFormat(tt.name, tt.addr); err != nil && !tt.fail { - t.Errorf("failed to EnvelopeFromFormat(): %s", err) - return - } - - var fa *mail.Address - f, ok := m.addrHeader[HeaderFrom] - if ok && len(f) > 0 { - fa = f[0] - } - if (!ok || len(f) == 0) && !tt.fail { - t.Errorf(`valid from address expected, but "From:" field is empty`) - return - } - if tt.fail && len(f) > 0 { - t.Errorf("FromFormat() was supposed to failed but got value: %s", fa.String()) - return - } - - if !tt.fail && fa.String() != tt.want { - t.Errorf("wrong result for FromFormat(). Want: %s, got: %s", tt.want, fa.String()) - } - m.addrHeader[HeaderFrom] = nil - }) - } -} - -func TestMsg_GetRecipients(t *testing.T) { - a := []string{"to@example.com", "cc@example.com", "bcc@example.com"} - m := NewMsg() - - _, err := m.GetRecipients() - if err == nil { - t.Errorf("GetRecipients() succeeded but was expected to fail") - return - } - - if err := m.AddTo(a[0]); err != nil { - t.Errorf("AddTo() failed: %s", err) - return - } - if err := m.AddCc(a[1]); err != nil { - t.Errorf("AddCc() failed: %s", err) - return - } - if err := m.AddBcc(a[2]); err != nil { - t.Errorf("AddBcc() failed: %s", err) - return - } - - al, err := m.GetRecipients() - if err != nil { - t.Errorf("GetRecipients() failed: %s", err) - return - } - - tf, cf, bf := false, false, false - for _, r := range al { - if r == a[0] { - tf = true - } - if r == a[1] { - cf = true - } - if r == a[2] { - bf = true - } - } - if !tf { - t.Errorf("GetRecipients() failed. Expected to address %s but was not found", a[0]) - return - } - if !cf { - t.Errorf("GetRecipients() failed. Expected cc address %s but was not found", a[1]) - return - } - if !bf { - t.Errorf("GetRecipients() failed. Expected bcc address %s but was not found", a[2]) - return - } -} - -// TestMsg_ReplyTo tests the Msg.ReplyTo and Msg.ReplyToFormat methods -func TestMsg_ReplyTo(t *testing.T) { - tests := []struct { - tname string - name string - addr string - want string - sf bool - }{ - { - "valid name and addr", "Toni Tester", "tester@example.com", - `"Toni Tester" `, false, - }, - { - "no name with valid addr", "", "tester@example.com", - ``, false, - }, - { - "valid name with invalid addr", "Toni Tester", "@example.com", - ``, true, - }, - } - m := NewMsg() - for _, tt := range tests { - t.Run(tt.tname, func(t *testing.T) { - if err := m.ReplyTo(tt.want); err != nil && !tt.sf { - t.Errorf("ReplyTo() method failed: %s", err) - } - if !tt.sf { - rt, ok := m.genHeader[HeaderReplyTo] - if !ok { - t.Errorf("ReplyTo() failed: ReplyTo generic header not set") - return - } - if len(rt) <= 0 { - t.Errorf("ReplyTo() failed: length of generic ReplyTo header is zero or less than zero") - return - } - if rt[0] != tt.want { - t.Errorf("ReplyTo() failed: expected value: %s, got: %s", tt.want, rt[0]) - } - } - m.genHeader = nil - m.genHeader = make(map[Header][]string) - if err := m.ReplyToFormat(tt.name, tt.addr); err != nil && !tt.sf { - t.Errorf("ReplyToFormat() method failed: %s", err) - } - if !tt.sf { - rt, ok := m.genHeader[HeaderReplyTo] - if !ok { - t.Errorf("ReplyTo() failed: ReplyTo generic header not set") - return - } - if len(rt) <= 0 { - t.Errorf("ReplyTo() failed: length of generic ReplyTo header is zero or less than zero") - return - } - if rt[0] != tt.want { - t.Errorf("ReplyTo() failed: expected value: %s, got: %s", tt.want, rt[0]) - } - } - m.genHeader = nil - m.genHeader = make(map[Header][]string) - }) - } -} - -// TestMsg_Subject tests the Msg.Subject method -func TestMsg_Subject(t *testing.T) { - tests := []struct { - name string - sub string - want string - }{ - {"normal subject", "This is a test subject", "This is a test subject"}, - { - "subject with umlauts", "This is a test subject with umlauts: üäöß", - "=?UTF-8?q?This_is_a_test_subject_with_umlauts:_=C3=BC=C3=A4=C3=B6=C3=9F?=", - }, - { - "subject with emoji", "This is a test subject with emoji: 📧", - "=?UTF-8?q?This_is_a_test_subject_with_emoji:_=F0=9F=93=A7?=", - }, - } - m := NewMsg() - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - m.Subject(tt.sub) - s, ok := m.genHeader[HeaderSubject] - if !ok || len(s) <= 0 { - t.Errorf("Subject() method failed. Generic header for Subject is empty") - return - } - if s[0] != tt.want { - t.Errorf("Subject() method failed. Expected: %s, got: %s", tt.want, s[0]) - } - }) - } -} - -// TestMsg_SetImportance tests the Msg.SetImportance method -func TestMsg_SetImportance(t *testing.T) { - tests := []struct { - name string - imp Importance - wantns string - xprio string - want string - sf bool - }{ - {"Importance: Non-Urgent", ImportanceNonUrgent, "0", "5", "non-urgent", false}, - {"Importance: Low", ImportanceLow, "0", "5", "low", false}, - {"Importance: Normal", ImportanceNormal, "", "", "", true}, - {"Importance: High", ImportanceHigh, "1", "1", "high", false}, - {"Importance: Urgent", ImportanceUrgent, "1", "1", "urgent", false}, - {"Importance: Unknown", 9, "", "", "", true}, - } - m := NewMsg() - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - m.SetImportance(tt.imp) - hi, ok := m.genHeader[HeaderImportance] - if (!ok || len(hi) <= 0) && !tt.sf { - t.Errorf("SetImportance() method failed. Generic header for Importance is empty") - } - hp, ok := m.genHeader[HeaderPriority] - if (!ok || len(hp) <= 0) && !tt.sf { - t.Errorf("SetImportance() method failed. Generic header for Priority is empty") - } - hx, ok := m.genHeader[HeaderXPriority] - if (!ok || len(hx) <= 0) && !tt.sf { - t.Errorf("SetImportance() method failed. Generic header for X-Priority is empty") - } - hm, ok := m.genHeader[HeaderXMSMailPriority] - if (!ok || len(hm) <= 0) && !tt.sf { - t.Errorf("SetImportance() method failed. Generic header for X-MS-XPriority is empty") - } - if !tt.sf { - if hi[0] != tt.want { - t.Errorf("SetImportance() method failed. Expected Imporance: %s, got: %s", tt.want, hi[0]) - } - if hp[0] != tt.wantns { - t.Errorf("SetImportance() method failed. Expected Priority: %s, got: %s", tt.want, hp[0]) - } - if hx[0] != tt.xprio { - t.Errorf("SetImportance() method failed. Expected X-Priority: %s, got: %s", tt.want, hx[0]) - } - if hm[0] != tt.wantns { - t.Errorf("SetImportance() method failed. Expected X-MS-Priority: %s, got: %s", tt.wantns, hm[0]) - } - } - m.genHeader = nil - m.genHeader = make(map[Header][]string) - }) - } -} - -// TestMsg_SetOrganization tests the Msg.SetOrganization method -func TestMsg_SetOrganization(t *testing.T) { - tests := []struct { - name string - org string - }{ - {"Org: testcorp", "testcorp"}, - } - m := NewMsg() - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - m.SetOrganization(tt.org) - o, ok := m.genHeader[HeaderOrganization] - if !ok || len(o) <= 0 { - t.Errorf("SetOrganization() method failed. Generic header for Organization is empty") - return - } - if o[0] != tt.org { - t.Errorf("SetOrganization() method failed. Expected: %s, got: %s", tt.org, o[0]) - } - }) - } -} - -// TestMsg_SetUserAgent tests the Msg.SetUserAgent method -func TestMsg_SetUserAgent(t *testing.T) { - tests := []struct { - name string - ua string - }{ - {"UA: Testmail 1.0", "Testmailer 1.0"}, - } - m := NewMsg() - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - m.SetUserAgent(tt.ua) - xm, ok := m.genHeader[HeaderXMailer] - if !ok || len(xm) <= 0 { - t.Errorf("SetUserAgent() method failed. Generic header for X-Mailer is empty") - return - } - ua, ok := m.genHeader[HeaderUserAgent] - if !ok || len(ua) <= 0 { - t.Errorf("SetUserAgent() method failed. Generic header for UserAgent is empty") - return - } - if xm[0] != tt.ua { - t.Errorf("SetUserAgent() method failed. Expected X-Mailer: %s, got: %s", tt.ua, xm[0]) - } - if ua[0] != tt.ua { - t.Errorf("SetUserAgent() method failed. Expected User-Agent: %s, got: %s", tt.ua, ua[0]) - } - }) - } -} - -// TestMsg_RequestMDN tests the different RequestMDN* related methods of Msg -func TestMsg_RequestMDN(t *testing.T) { - n := "Toni Tester" - n2 := "Melanie Tester" - v := "toni.tester@example.com" - v2 := "melanie.tester@example.com" - iv := "testertest.tld" - vl := []string{v, v2} - m := NewMsg() - - // Single valid address - if err := m.RequestMDNTo(v); err != nil { - t.Errorf("RequestMDNTo with a single valid address failed: %s", err) - } - if val := m.genHeader[HeaderDispositionNotificationTo]; len(val) > 1 { - if val[0] != fmt.Sprintf("<%s>", v) { - t.Errorf("RequestMDNTo with a single valid address failed. Expected: %s, got: %s", v, - val[0]) - } - } - m.Reset() - - // Multiples valid addresses - if err := m.RequestMDNTo(vl...); err != nil { - t.Errorf("RequestMDNTo with a multiple valid address failed: %s", err) - } - if val := m.genHeader[HeaderDispositionNotificationTo]; len(val) > 0 { - if val[0] != fmt.Sprintf("<%s>", v) { - t.Errorf("RequestMDNTo with a multiple valid addresses failed. Expected 0: %s, got 0: %s", v, - val[0]) - } - } - if val := m.genHeader[HeaderDispositionNotificationTo]; len(val) > 1 { - if val[1] != fmt.Sprintf("<%s>", v2) { - t.Errorf("RequestMDNTo with a multiple valid addresses failed. Expected 1: %s, got 1: %s", v2, - val[1]) - } - } - m.Reset() - - // Invalid address - if err := m.RequestMDNTo(iv); err == nil { - t.Errorf("RequestMDNTo with an invalid address was supposed to failed, but didn't") - } - m.Reset() - - // Single valid addresses + AddTo - if err := m.RequestMDNTo(v); err != nil { - t.Errorf("RequestMDNTo with a single valid address failed: %s", err) - } - if err := m.RequestMDNAddTo(v2); err != nil { - t.Errorf("RequestMDNAddTo with a valid address failed: %s", err) - } - if val := m.genHeader[HeaderDispositionNotificationTo]; len(val) > 1 { - if val[1] != fmt.Sprintf("<%s>", v2) { - t.Errorf("RequestMDNTo with a multiple valid addresses failed. Expected 1: %s, got 1: %s", v2, - val[1]) - } - } - m.Reset() - - // Single valid address formated + AddToFromat - if err := m.RequestMDNToFormat(n, v); err != nil { - t.Errorf("RequestMDNToFormat with a single valid address failed: %s", err) - } - if val := m.genHeader[HeaderDispositionNotificationTo]; len(val) > 0 { - if val[0] != fmt.Sprintf(`"%s" <%s>`, n, v) { - t.Errorf(`RequestMDNToFormat with a single valid address failed. Expected: "%s" <%s>, got: %s`, n, v, - val[0]) - } - } - if err := m.RequestMDNAddToFormat(n2, v2); err != nil { - t.Errorf("RequestMDNAddToFormat with a valid address failed: %s", err) - } - if val := m.genHeader[HeaderDispositionNotificationTo]; len(val) > 1 { - if val[1] != fmt.Sprintf(`"%s" <%s>`, n2, v2) { - t.Errorf(`RequestMDNAddToFormat with a single valid address failed. Expected: "%s" <%s>, got: %s`, n2, v2, - val[1]) - } - } - m.Reset() - - // Invalid formated address - if err := m.RequestMDNToFormat(n, iv); err == nil { - t.Errorf("RequestMDNToFormat with an invalid address was supposed to failed, but didn't") - } - - // Invalid address AddTo + AddToFormat - if err := m.RequestMDNAddTo(iv); err == nil { - t.Errorf("RequestMDNAddTo with an invalid address was supposed to failed, but didn't") - } - if err := m.RequestMDNAddToFormat(n, iv); err == nil { - t.Errorf("RequestMDNAddToFormat with an invalid address was supposed to failed, but didn't") - } -} - -// TestMsg_SetBodyString tests the Msg.SetBodyString method -func TestMsg_SetBodyString(t *testing.T) { - tests := []struct { - name string - ct ContentType - value string - want string - sf bool - }{ - {"Body: test", TypeTextPlain, "test", "test", false}, - { - "Body: with Umlauts", TypeTextHTML, "üäöß", - "üäöß", false, - }, - {"Body: with emoji", TypeTextPlain, "📧", "📧", false}, - } - m := NewMsg() - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - m.SetBodyString(tt.ct, tt.value) - if len(m.parts) != 1 { - t.Errorf("SetBodyString() failed: no mail parts found") - } - part := m.parts[0] - res := bytes.Buffer{} - if _, err := part.writeFunc(&res); err != nil && !tt.sf { - t.Errorf("WriteFunc of part failed: %s", err) - } - if res.String() != tt.want { - t.Errorf("SetBodyString() failed. Expecteding: %s, got: %s", tt.want, res.String()) - } - }) - } -} - -// TestMsg_AddAlternativeString tests the Msg.AddAlternativeString method -func TestMsg_AddAlternativeString(t *testing.T) { - tests := []struct { - name string - value string - want string - sf bool - }{ - {"Body: test", "test", "test", false}, - {"Body: with Umlauts", "üäöß", "üäöß", false}, - {"Body: with emoji", "📧", "📧", false}, - } - m := NewMsg() - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - m.SetBodyString(TypeTextPlain, tt.value) - if len(m.parts) != 1 { - t.Errorf("AddAlternativeString() => SetBodyString() failed: no mail parts found") - } - m.AddAlternativeString(TypeTextHTML, tt.value) - if len(m.parts) != 2 { - t.Errorf("AddAlternativeString() failed: no alternative mail parts found") - } - apart := m.parts[1] - res := bytes.Buffer{} - if _, err := apart.writeFunc(&res); err != nil && !tt.sf { - t.Errorf("WriteFunc of part failed: %s", err) - } - if res.String() != tt.want { - t.Errorf("AddAlternativeString() failed. Expecteding: %s, got: %s", tt.want, res.String()) - } - }) - } -} - -// TestMsg_AttachFile tests the Msg.AttachFile and the WithFilename FileOption method -func TestMsg_AttachFile(t *testing.T) { - tests := []struct { - name string - file string - fn string - sf bool - }{ - {"File: README.md", "README.md", "README.md", false}, - {"File: doc.go", "doc.go", "foo.go", false}, - {"File: nonexisting", "", "invalid.file", true}, - } - m := NewMsg() - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - m.AttachFile(tt.file, WithFileName(tt.fn), nil) - if len(m.attachments) != 1 && !tt.sf { - t.Errorf("AttachFile() failed. Number of attachments expected: %d, got: %d", 1, - len(m.attachments)) - return - } - if !tt.sf { - file := m.attachments[0] - if file == nil { - t.Errorf("AttachFile() failed. Attachment file pointer is nil") - return - } - if file.Name != tt.fn { - t.Errorf("AttachFile() failed. Filename of attachment expected: %s, got: %s", tt.fn, - file.Name) - } - buf := bytes.Buffer{} - if _, err := file.Writer(&buf); err != nil { - t.Errorf("failed to execute WriterFunc: %s", err) - return - } - } - m.Reset() - }) - } -} - -// TestMsg_GetAttachments tests the Msg.GetAttachments method -func TestMsg_GetAttachments(t *testing.T) { - tests := []struct { - name string - files []string - }{ - {"File: README.md", []string{"README.md"}}, - {"File: doc.go", []string{"doc.go"}}, - {"File: README.md and doc.go", []string{"README.md", "doc.go"}}, - {"File: nonexisting", nil}, - } - m := NewMsg() - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - for _, f := range tt.files { - m.AttachFile(f, WithFileName(f), nil) - } - if len(m.attachments) != len(tt.files) { - t.Errorf("AttachFile() failed. Number of attachments expected: %d, got: %d", len(tt.files), - len(m.attachments)) - return - } - ff := m.GetAttachments() - if len(m.attachments) != len(ff) { - t.Errorf("GetAttachments() failed. Number of attachments expected: %d, got: %d", len(m.attachments), - len(ff)) - return - } - var fn []string - for _, f := range ff { - fn = append(fn, f.Name) - } - sort.Strings(fn) - sort.Strings(tt.files) - for i, f := range tt.files { - if f != fn[i] { - t.Errorf("GetAttachments() failed. Attachment name expected: %s, got: %s", f, - fn[i]) - return - } - } - m.Reset() - }) - } -} - -// TestMsg_SetAttachments tests the Msg.GetAttachments method -func TestMsg_SetAttachments(t *testing.T) { - tests := []struct { - name string - attachments []string - files []string - }{ - {"File: replace README.md with doc.go", []string{"README.md"}, []string{"doc.go"}}, - {"File: add README.md with doc.go ", []string{"doc.go"}, []string{"README.md", "doc.go"}}, - {"File: remove README.md and doc.go", []string{"README.md", "doc.go"}, nil}, - {"File: add README.md and doc.go", nil, []string{"README.md", "doc.go"}}, - } - m := NewMsg() - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - sort.Strings(tt.attachments) - sort.Strings(tt.files) - for _, a := range tt.attachments { - m.AttachFile(a, WithFileName(a), nil) - } - if len(m.attachments) != len(tt.attachments) { - t.Errorf("AttachFile() failed. Number of attachments expected: %d, got: %d", len(tt.files), - len(m.attachments)) - return - } - var files []*File - for _, f := range tt.files { - files = append(files, &File{Name: f}) - } - m.SetAttachments(files) - if len(m.attachments) != len(files) { - t.Errorf("SetAttachements() failed. Number of attachments expected: %d, got: %d", len(files), - len(m.attachments)) - return - } - for i, f := range tt.files { - if f != m.attachments[i].Name { - t.Errorf("SetAttachments() failed. Attachment name expected: %s, got: %s", f, - m.attachments[i].Name) - return - } - } - m.Reset() - }) - } -} - -// TestMsg_UnsetAllAttachments tests the Msg.UnsetAllAttachments method -func TestMsg_UnsetAllAttachments(t *testing.T) { - tests := []struct { - name string - attachments []string - }{ - {"File: one file", []string{"README.md"}}, - {"File: two files", []string{"README.md", "doc.go"}}, - {"File: nil", nil}, - } - m := NewMsg() - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var files []*File - for _, f := range tt.attachments { - files = append(files, &File{Name: f}) - } - m.SetAttachments(files) - - if len(m.attachments) != len(files) { - t.Errorf("SetAttachements() failed. Number of attachments expected: %d, got: %d", len(files), - len(m.attachments)) - return - } - m.UnsetAllAttachments() - if m.attachments != nil { - t.Errorf("UnsetAllAttachments() failed. The attachments file's pointer is not nil") - return - } - m.Reset() - }) - } -} - -// TestMsg_GetEmbeds tests the Msg.GetEmbeds method -func TestMsg_GetEmbeds(t *testing.T) { - tests := []struct { - name string - files []string - }{ - {"File: README.md", []string{"README.md"}}, - {"File: doc.go", []string{"doc.go"}}, - {"File: README.md and doc.go", []string{"README.md", "doc.go"}}, - {"File: nonexisting", nil}, - } - m := NewMsg() - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - for _, f := range tt.files { - m.EmbedFile(f, WithFileName(f), nil) - } - if len(m.embeds) != len(tt.files) { - t.Errorf("EmbedFile() failed. Number of embedded files expected: %d, got: %d", len(tt.files), - len(m.embeds)) - return - } - ff := m.GetEmbeds() - if len(m.embeds) != len(ff) { - t.Errorf("GetEmbeds() failed. Number of embedded files expected: %d, got: %d", len(m.embeds), - len(ff)) - return - } - var fn []string - for _, f := range ff { - fn = append(fn, f.Name) - } - sort.Strings(fn) - sort.Strings(tt.files) - for i, f := range tt.files { - if f != fn[i] { - t.Errorf("GetEmbeds() failed. Embedded file name expected: %s, got: %s", f, - fn[i]) - return - } - } - m.Reset() - }) - } -} - -// TestMsg_SetEmbeds tests the Msg.GetEmbeds method -func TestMsg_SetEmbeds(t *testing.T) { - tests := []struct { - name string - embeds []string - files []string - }{ - {"File: replace README.md with doc.go", []string{"README.md"}, []string{"doc.go"}}, - {"File: add README.md with doc.go ", []string{"doc.go"}, []string{"README.md", "doc.go"}}, - {"File: remove README.md and doc.go", []string{"README.md", "doc.go"}, nil}, - {"File: add README.md and doc.go", nil, []string{"README.md", "doc.go"}}, - } - m := NewMsg() - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - sort.Strings(tt.embeds) - sort.Strings(tt.files) - for _, a := range tt.embeds { - m.EmbedFile(a, WithFileName(a), nil) - } - if len(m.embeds) != len(tt.embeds) { - t.Errorf("EmbedFile() failed. Number of embedded files expected: %d, got: %d", len(tt.files), - len(m.embeds)) - return - } - var files []*File - for _, f := range tt.files { - files = append(files, &File{Name: f}) - } - m.SetEmbeds(files) - if len(m.embeds) != len(files) { - t.Errorf("SetEmbeds() failed. Number of embedded files expected: %d, got: %d", len(files), - len(m.embeds)) - return - } - for i, f := range tt.files { - if f != m.embeds[i].Name { - t.Errorf("SetEmbeds() failed. Embedded file name expected: %s, got: %s", f, - m.embeds[i].Name) - return - } - } - m.Reset() - }) - } -} - -// TestMsg_UnsetAllEmbeds tests the Msg.TestMsg_UnsetAllEmbeds method -func TestMsg_UnsetAllEmbeds(t *testing.T) { - tests := []struct { - name string - embeds []string - }{ - {"File: one file", []string{"README.md"}}, - {"File: two files", []string{"README.md", "doc.go"}}, - {"File: nil", nil}, - } - m := NewMsg() - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var files []*File - for _, f := range tt.embeds { - files = append(files, &File{Name: f}) - } - m.SetEmbeds(files) - if len(m.embeds) != len(files) { - t.Errorf("SetEmbeds() failed. Number of embedded files expected: %d, got: %d", len(files), - len(m.embeds)) - return - } - m.UnsetAllEmbeds() - if m.embeds != nil { - t.Errorf("UnsetAllEmbeds() failed. The embeds file's point is not nil") - return - } - m.Reset() - }) - } -} - -// TestMsg_UnsetAllParts tests the Msg.TestMsg_UnsetAllParts method -func TestMsg_UnsetAllParts(t *testing.T) { - tests := []struct { - name string - attachments []string - embeds []string - }{ - {"File: both is exist", []string{"README.md"}, []string{"doc.go"}}, - {"File: both is nil", nil, nil}, - {"File: attachment exist, embed nil", []string{"README.md"}, nil}, - {"File: attachment nil, embed exist", nil, []string{"README.md"}}, - } - m := NewMsg() - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - var attachments []*File - for _, f := range tt.attachments { - attachments = append(attachments, &File{Name: f}) - } - m.SetAttachments(attachments) - if len(m.attachments) != len(attachments) { - t.Errorf("SetAttachements() failed. Number of attachments files expected: %d, got: %d", - len(attachments), len(m.attachments)) - return - } - var embeds []*File - for _, f := range tt.embeds { - embeds = append(embeds, &File{Name: f}) - } - m.SetEmbeds(embeds) - if len(m.embeds) != len(embeds) { - t.Errorf("SetEmbeds() failed. Number of embedded files expected: %d, got: %d", len(embeds), - len(m.embeds)) - return - } - m.UnsetAllParts() - if m.attachments != nil { - t.Errorf("UnsetAllParts() failed. The attachments file's point is not nil") - return - } - if m.embeds != nil { - t.Errorf("UnsetAllParts() failed. The embeds file's point is not nil") - return - } - m.Reset() - }) - } -} - -// TestMsg_AttachFromEmbedFS tests the Msg.AttachFromEmbedFS and the WithFilename FileOption method -func TestMsg_AttachFromEmbedFS(t *testing.T) { - tests := []struct { - name string - file string - fn string - sf bool - }{ - {"File: README.md", "README.md", "README.md", false}, - {"File: nonexisting", "", "invalid.file", true}, - } - m := NewMsg() - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := m.AttachFromEmbedFS(tt.file, &efs, WithFileName(tt.fn)); err != nil && !tt.sf { - t.Errorf("AttachFromEmbedFS() failed: %s", err) - return - } - if len(m.attachments) != 1 && !tt.sf { - t.Errorf("AttachFile() failed. Number of attachments expected: %d, got: %d", 1, - len(m.attachments)) - return - } - if !tt.sf { - file := m.attachments[0] - if file == nil { - t.Errorf("AttachFile() failed. Attachment file pointer is nil") - return - } - if file.Name != tt.fn { - t.Errorf("AttachFile() failed. Filename of attachment expected: %s, got: %s", tt.fn, - file.Name) - } - buf := bytes.Buffer{} - if _, err := file.Writer(&buf); err != nil { - t.Errorf("failed to execute WriterFunc: %s", err) - return - } - } - m.Reset() - }) - } -} - -// TestMsg_AttachFileBrokenFunc tests WriterFunc of the Msg.AttachFile method -func TestMsg_AttachFileBrokenFunc(t *testing.T) { - m := NewMsg() - m.AttachFile("README.md") - if len(m.attachments) != 1 { - t.Errorf("AttachFile() failed. Number of attachments expected: %d, got: %d", 1, - len(m.attachments)) - return - } - file := m.attachments[0] - if file == nil { - t.Errorf("AttachFile() failed. Attachment file pointer is nil") - return - } - file.Writer = func(io.Writer) (int64, error) { - return 0, fmt.Errorf("failing intentionally") - } - buf := bytes.Buffer{} - if _, err := file.Writer(&buf); err == nil { - t.Errorf("execute WriterFunc did not fail, but was expected to fail") - } -} - -// TestMsg_AttachReader tests the Msg.AttachReader method -func TestMsg_AttachReader(t *testing.T) { - m := NewMsg() - ts := "This is a test string" - rbuf := bytes.Buffer{} - rbuf.WriteString(ts) - r := bufio.NewReader(&rbuf) - if err := m.AttachReader("testfile.txt", r); err != nil { - t.Errorf("AttachReader() failed. Expected no error, got: %s", err.Error()) - return - } - if len(m.attachments) != 1 { - t.Errorf("AttachReader() failed. Number of attachments expected: %d, got: %d", 1, - len(m.attachments)) - return - } - file := m.attachments[0] - if file == nil { - t.Errorf("AttachReader() failed. Attachment file pointer is nil") - return - } - if file.Name != "testfile.txt" { - t.Errorf("AttachReader() failed. Expected file name: %s, got: %s", "testfile.txt", - file.Name) - } - wbuf := bytes.Buffer{} - if _, err := file.Writer(&wbuf); err != nil { - t.Errorf("execute WriterFunc failed: %s", err) - } - if wbuf.String() != ts { - t.Errorf("AttachReader() failed. Expected string: %q, got: %q", ts, wbuf.String()) - } -} - -// TestMsg_EmbedFile tests the Msg.EmbedFile and the WithFilename FileOption method -func TestMsg_EmbedFile(t *testing.T) { - tests := []struct { - name string - file string - fn string - sf bool - }{ - {"File: README.md", "README.md", "README.md", false}, - {"File: doc.go", "doc.go", "foo.go", false}, - {"File: nonexisting", "", "invalid.file", true}, - } - m := NewMsg() - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - m.EmbedFile(tt.file, WithFileName(tt.fn), nil) - if len(m.embeds) != 1 && !tt.sf { - t.Errorf("EmbedFile() failed. Number of embeds expected: %d, got: %d", 1, - len(m.embeds)) - return - } - if !tt.sf { - file := m.embeds[0] - if file == nil { - t.Errorf("EmbedFile() failed. Embedded file pointer is nil") - return - } - if file.Name != tt.fn { - t.Errorf("EmbedFile() failed. Filename of embeds expected: %s, got: %s", tt.fn, - file.Name) - } - buf := bytes.Buffer{} - if _, err := file.Writer(&buf); err != nil { - t.Errorf("failed to execute WriterFunc: %s", err) - return - } - } - m.Reset() - }) - } -} - -// TestMsg_EmbedFromEmbedFS tests the Msg.EmbedFromEmbedFS and the WithFilename FileOption method -func TestMsg_EmbedFromEmbedFS(t *testing.T) { - tests := []struct { - name string - file string - fn string - sf bool - }{ - {"File: README.md", "README.md", "README.md", false}, - {"File: nonexisting", "", "invalid.file", true}, - } - m := NewMsg() - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := m.EmbedFromEmbedFS(tt.file, &efs, WithFileName(tt.fn)); err != nil && !tt.sf { - t.Errorf("EmbedFromEmbedFS() failed: %s", err) - return - } - if len(m.embeds) != 1 && !tt.sf { - t.Errorf("EmbedFile() failed. Number of embeds expected: %d, got: %d", 1, - len(m.embeds)) - return - } - if !tt.sf { - file := m.embeds[0] - if file == nil { - t.Errorf("EmbedFile() failed. Embedded file pointer is nil") - return - } - if file.Name != tt.fn { - t.Errorf("EmbedFile() failed. Filename of embeds expected: %s, got: %s", tt.fn, - file.Name) - } - buf := bytes.Buffer{} - if _, err := file.Writer(&buf); err != nil { - t.Errorf("failed to execute WriterFunc: %s", err) - return - } - } - m.Reset() - }) - } -} - -// TestMsg_EmbedFileBrokenFunc tests WriterFunc of the Msg.EmbedFile method -func TestMsg_EmbedFileBrokenFunc(t *testing.T) { - m := NewMsg() - m.EmbedFile("README.md") - if len(m.embeds) != 1 { - t.Errorf("EmbedFile() failed. Number of embeds expected: %d, got: %d", 1, - len(m.embeds)) - return - } - file := m.embeds[0] - if file == nil { - t.Errorf("EmbedFile() failed. Embedded file pointer is nil") - return - } - file.Writer = func(io.Writer) (int64, error) { - return 0, fmt.Errorf("failing intentionally") - } - buf := bytes.Buffer{} - if _, err := file.Writer(&buf); err == nil { - t.Errorf("execute WriterFunc did not fail, but was expected to fail") - } -} - -// TestMsg_EmbedReader tests the Msg.EmbedReader method -func TestMsg_EmbedReader(t *testing.T) { - m := NewMsg() - ts := "This is a test string" - rbuf := bytes.Buffer{} - rbuf.WriteString(ts) - r := bufio.NewReader(&rbuf) - if err := m.EmbedReader("testfile.txt", r); err != nil { - t.Errorf("EmbedReader() failed. Expected no error, got: %s", err.Error()) - return - } - if len(m.embeds) != 1 { - t.Errorf("EmbedReader() failed. Number of embeds expected: %d, got: %d", 1, - len(m.embeds)) - return - } - file := m.embeds[0] - if file == nil { - t.Errorf("EmbedReader() failed. Embedded file pointer is nil") - return - } - if file.Name != "testfile.txt" { - t.Errorf("EmbedReader() failed. Expected file name: %s, got: %s", "testfile.txt", - file.Name) - } - wbuf := bytes.Buffer{} - if _, err := file.Writer(&wbuf); err != nil { - t.Errorf("execute WriterFunc failed: %s", err) - } - if wbuf.String() != ts { - t.Errorf("EmbedReader() failed. Expected string: %q, got: %q", ts, wbuf.String()) - } -} - -// TestMsg_hasAlt tests the hasAlt() method of the Msg -func TestMsg_hasAlt(t *testing.T) { - m := NewMsg() - m.SetBodyString(TypeTextPlain, "Plain") - m.AddAlternativeString(TypeTextHTML, "HTML") - if !m.hasAlt() { - t.Errorf("mail has alternative parts but hasAlt() returned true") - } -} - -// TestMsg_hasRelated tests the hasRelated() method of the Msg -func TestMsg_hasRelated(t *testing.T) { - m := NewMsg() - m.SetBodyString(TypeTextPlain, "Plain") - m.EmbedFile("README.md") - if !m.hasRelated() { - t.Errorf("mail has related parts but hasRelated() returned true") - } -} - -// TestMsg_hasMixed tests the hasMixed() method of the Msg -func TestMsg_hasMixed(t *testing.T) { - m := NewMsg() - m.SetBodyString(TypeTextPlain, "Plain") - m.AttachFile("README.md") - if !m.hasMixed() { - t.Errorf("mail has mixed parts but hasMixed() returned true") - } -} - -// TestMsg_WriteTo tests the WriteTo() method of the Msg -func TestMsg_WriteTo(t *testing.T) { - m := NewMsg() - m.SetBodyString(TypeTextPlain, "Plain") - wbuf := bytes.Buffer{} - n, err := m.WriteTo(&wbuf) - if err != nil { - t.Errorf("WriteTo() failed: %s", err) - return - } - if n != int64(wbuf.Len()) { - t.Errorf("WriteTo() failed: expected written byte length: %d, got: %d", n, wbuf.Len()) - } -} - -// TestMsg_WriteToSkipMiddleware tests the WriteTo() method of the Msg -func TestMsg_WriteToSkipMiddleware(t *testing.T) { - m := NewMsg(WithMiddleware(encodeMiddleware{}), WithMiddleware(uppercaseMiddleware{})) - m.Subject("This is a test") - m.SetBodyString(TypeTextPlain, "Plain") - wbuf := bytes.Buffer{} - n, err := m.WriteToSkipMiddleware(&wbuf, "uppercase") - if err != nil { - t.Errorf("WriteToSkipMiddleware() failed: %s", err) - return - } - if n != int64(wbuf.Len()) { - t.Errorf("WriteToSkipMiddleware() failed: expected written byte length: %d, got: %d", n, wbuf.Len()) - } - if !strings.Contains(wbuf.String(), "Subject: This is @ test") { - t.Errorf("WriteToSkipMiddleware failed. Unable to find encoded subject") - } - - wbuf2 := bytes.Buffer{} - n, err = m.WriteTo(&wbuf2) - if err != nil { - t.Errorf("WriteTo() failed: %s", err) - return - } - if n != int64(wbuf2.Len()) { - t.Errorf("WriteTo() failed: expected written byte length: %d, got: %d", n, wbuf2.Len()) - } - if !strings.Contains(wbuf2.String(), "Subject: THIS IS @ TEST") { - t.Errorf("WriteToSkipMiddleware failed. Unable to find encoded and upperchase subject") - } -} - -// TestMsg_WriteTo_fails tests the WriteTo() method of the Msg but with a failing body writer function -func TestMsg_WriteTo_fails(t *testing.T) { - m := NewMsg() - m.SetBodyWriter(TypeTextPlain, func(io.Writer) (int64, error) { - return 0, errors.New("failed") - }) - _, err := m.WriteTo(io.Discard) - if err == nil { - t.Errorf("WriteTo() with failing BodyWriter function was supposed to fail, but didn't") - return - } - - // NoEncoding handles the errors separately - m = NewMsg(WithEncoding(NoEncoding)) - m.SetBodyWriter(TypeTextPlain, func(io.Writer) (int64, error) { - return 0, errors.New("failed") - }) - _, err = m.WriteTo(io.Discard) - if err == nil { - t.Errorf("WriteTo() (no encoding) with failing BodyWriter function was supposed to fail, but didn't") - return - } -} - -// TestMsg_Write tests the Write() method of the Msg -func TestMsg_Write(t *testing.T) { - m := NewMsg() - m.SetBodyString(TypeTextPlain, "Plain") - wbuf := bytes.Buffer{} - n, err := m.Write(&wbuf) - if err != nil { - t.Errorf("WriteTo() failed: %s", err) - return - } - if n != int64(wbuf.Len()) { - t.Errorf("WriteTo() failed: expected written byte length: %d, got: %d", n, wbuf.Len()) - } -} - -// TestMsg_WriteWithLongHeader tests the WriteTo() method of the Msg with a long header -func TestMsg_WriteWithLongHeader(t *testing.T) { - m := NewMsg() - m.SetBodyString(TypeTextPlain, "Plain") - m.SetGenHeader(HeaderContentLang, "de", "en", "fr", "es", "xxxx", "yyyy", "de", "en", "fr", - "es", "xxxx", "yyyy", "de", "en", "fr", "es", "xxxx", "yyyy", "de", "en", "fr") - m.SetGenHeader(HeaderContentID, "XXXXXXXXXXXXXXX XXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXX", - "XXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXX") - wbuf := bytes.Buffer{} - n, err := m.WriteTo(&wbuf) - if err != nil { - t.Errorf("WriteTo() failed: %s", err) - return - } - if n != int64(wbuf.Len()) { - t.Errorf("WriteTo() failed: expected written byte length: %d, got: %d", n, wbuf.Len()) - } -} - -// TestMsg_WriteDiffEncoding tests the WriteTo() method of the Msg with different Encoding -func TestMsg_WriteDiffEncoding(t *testing.T) { - tests := []struct { - name string - ct ContentType - en Encoding - alt bool - wa bool - we bool - }{ - {"Plain/QP/NoAlt/NoAttach/NoEmbed", TypeTextPlain, EncodingQP, false, false, false}, - {"Plain/B64/NoAlt/NoAttach/NoEmbed", TypeTextPlain, EncodingB64, false, false, false}, - {"Plain/No/NoAlt/NoAttach/NoEmbed", TypeTextPlain, NoEncoding, false, false, false}, - {"HTML/QP/NoAlt/NoAttach/NoEmbed", TypeTextHTML, EncodingQP, false, false, false}, - {"HTML/B64/NoAlt/NoAttach/NoEmbed", TypeTextHTML, EncodingB64, false, false, false}, - {"HTML/No/NoAlt/NoAttach/NoEmbed", TypeTextHTML, NoEncoding, false, false, false}, - {"Plain/QP/HTML/NoAttach/NoEmbed", TypeTextPlain, EncodingQP, true, false, false}, - {"Plain/B64/HTML/NoAttach/NoEmbed", TypeTextPlain, EncodingB64, true, false, false}, - {"Plain/No/HTML/NoAttach/NoEmbed", TypeTextPlain, NoEncoding, true, false, false}, - {"Plain/QP/NoAlt/Attach/NoEmbed", TypeTextPlain, EncodingQP, false, true, false}, - {"Plain/B64/NoAlt/Attach/NoEmbed", TypeTextPlain, EncodingB64, false, true, false}, - {"Plain/No/NoAlt/Attach/NoEmbed", TypeTextPlain, NoEncoding, false, true, false}, - {"Plain/QP/NoAlt/NoAttach/Embed", TypeTextPlain, EncodingQP, false, false, true}, - {"Plain/B64/NoAlt/NoAttach/Embed", TypeTextPlain, EncodingB64, false, false, true}, - {"Plain/No/NoAlt/NoAttach/Embed", TypeTextPlain, NoEncoding, false, false, true}, - {"Plain/QP/HTML/Attach/Embed", TypeTextPlain, EncodingQP, true, true, true}, - {"Plain/B64/HTML/Attach/Embed", TypeTextPlain, EncodingB64, true, true, true}, - {"Plain/No/HTML/Attach/Embed", TypeTextPlain, NoEncoding, true, true, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - m := NewMsg(WithEncoding(tt.en)) - m.SetBodyString(tt.ct, tt.name) - if tt.alt { - m.AddAlternativeString(TypeTextHTML, fmt.Sprintf("

%s

", tt.name)) - } - if tt.wa { - m.AttachFile("README.md") - } - if tt.we { - m.EmbedFile("README.md") - } - wbuf := bytes.Buffer{} - n, err := m.WriteTo(&wbuf) - if err != nil { - t.Errorf("WriteTo() failed: %s", err) - return - } - if n != int64(wbuf.Len()) { - t.Errorf("WriteTo() failed: expected written byte length: %d, got: %d", n, wbuf.Len()) - } - wbuf.Reset() - }) - } -} - -// TestMsg_appendFile tests the appendFile() method of the Msg -func TestMsg_appendFile(t *testing.T) { - m := NewMsg() - var fl []*File - f := &File{ - Name: "file.txt", - } - fl = m.appendFile(fl, f, nil) - if len(fl) != 1 { - t.Errorf("appendFile() failed. Expected length: %d, got: %d", 1, len(fl)) - } - fl = m.appendFile(fl, f, nil) - if len(fl) != 2 { - t.Errorf("appendFile() failed. Expected length: %d, got: %d", 2, len(fl)) - } -} - -// TestMsg_multipleWrites tests multiple executions of WriteTo on the Msg -func TestMsg_multipleWrites(t *testing.T) { - ts := "XXX_UNIQUE_STRING_XXX" - wbuf := bytes.Buffer{} - m := NewMsg() - m.SetBodyString(TypeTextPlain, ts) - - // First WriteTo() - _, err := m.WriteTo(&wbuf) - if err != nil { - t.Errorf("failed to write body to buffer: %s", err) - } - if !strings.Contains(wbuf.String(), ts) { - t.Errorf("first WriteTo() body does not contain unique string: %s", ts) - } - - // Second WriteTo() - wbuf.Reset() - _, err = m.WriteTo(&wbuf) - if err != nil { - t.Errorf("failed to write body to buffer: %s", err) - } - if !strings.Contains(wbuf.String(), ts) { - t.Errorf("second WriteTo() body does not contain unique string: %s", ts) - } -} - -// TestMsg_NewReader tests the Msg.NewReader method -func TestMsg_NewReader(t *testing.T) { - m := NewMsg() - m.SetBodyString(TypeTextPlain, "TEST123") - mr := m.NewReader() - if mr == nil { - t.Errorf("NewReader failed: Reader is nil") - } - if mr.Error() != nil { - t.Errorf("NewReader failed: %s", mr.Error()) - } -} - -// TestMsg_NewReader_ioCopy tests the Msg.NewReader method using io.Copy -func TestMsg_NewReader_ioCopy(t *testing.T) { - wbuf1 := bytes.Buffer{} - wbuf2 := bytes.Buffer{} - m := NewMsg() - m.SetBodyString(TypeTextPlain, "TEST123") - mr := m.NewReader() - if mr == nil { - t.Errorf("NewReader failed: Reader is nil") - } - - // First we use WriteTo to have something to compare to - _, err := m.WriteTo(&wbuf1) - if err != nil { - t.Errorf("failed to write body to buffer: %s", err) - } - - // Then we write to wbuf2 via io.Copy - n, err := io.Copy(&wbuf2, mr) - if err != nil { - t.Errorf("failed to use io.Copy on Reader: %s", err) - } - if n != int64(wbuf1.Len()) { - t.Errorf("message length of WriteTo and io.Copy differ. Expected: %d, got: %d", wbuf1.Len(), n) - } - if wbuf1.String() != wbuf2.String() { - t.Errorf("message content of WriteTo and io.Copy differ") - } -} - -// TestMsg_UpdateReader tests the Msg.UpdateReader method -func TestMsg_UpdateReader(t *testing.T) { - m := NewMsg() - m.Subject("Subject-Run 1") - mr := m.NewReader() - if mr == nil { - t.Errorf("NewReader failed: Reader is nil") - } - wbuf1 := bytes.Buffer{} - _, err := io.Copy(&wbuf1, mr) - if err != nil { - t.Errorf("io.Copy on Reader failed: %s", err) - } - if !strings.Contains(wbuf1.String(), "Subject: Subject-Run 1") { - t.Errorf("io.Copy on Reader failed. Expected to find %q but string in Subject was not found", - "Subject-Run 1") - } - - m.Subject("Subject-Run 2") - m.UpdateReader(mr) - wbuf2 := bytes.Buffer{} - _, err = io.Copy(&wbuf2, mr) - if err != nil { - t.Errorf("2nd io.Copy on Reader failed: %s", err) - } - if !strings.Contains(wbuf2.String(), "Subject: Subject-Run 2") { - t.Errorf("io.Copy on Reader failed. Expected to find %q but string in Subject was not found", - "Subject-Run 2") - } -} - -// TestMsg_SetBodyTextTemplate tests the Msg.SetBodyTextTemplate method -func TestMsg_SetBodyTextTemplate(t *testing.T) { - tests := []struct { - name string - tpl string - ph string - sf bool - }{ - {"normal text", "This is a {{.Placeholder}}", "TemplateTest", false}, - {"invalid tpl", "This is a {{ foo .Placeholder}}", "TemplateTest", true}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - data := struct { - Placeholder string - }{Placeholder: tt.ph} - tpl, err := ttpl.New("test").Parse(tt.tpl) - if err != nil && !tt.sf { - t.Errorf("failed to render template: %s", err) - return - } - m := NewMsg() - if err := m.SetBodyTextTemplate(tpl, data); err != nil && !tt.sf { - t.Errorf("failed to set template as body: %s", err) - } - - wbuf := bytes.Buffer{} - _, err = m.WriteTo(&wbuf) - if err != nil { - t.Errorf("failed to write body to buffer: %s", err) - } - if !strings.Contains(wbuf.String(), tt.ph) && !tt.sf { - t.Errorf("SetBodyTextTemplate failed: Body does not contain the expected tpl placeholder: %s", tt.ph) - } - m.Reset() - }) - } -} - -// TestMsg_SetBodyHTMLTemplate tests the Msg.SetBodyHTMLTemplate method -func TestMsg_SetBodyHTMLTemplate(t *testing.T) { - tests := []struct { - name string - tpl string - ph string - ex string - sf bool - }{ - {"normal HTML", "

This is a {{.Placeholder}}

", "TemplateTest", "TemplateTest", false}, - { - "HTML with HTML", "

This is a {{.Placeholder}}

", "", - "<script>alert(1)</script>", false, - }, - {"invalid tpl", "

This is a {{ foo .Placeholder}}

", "TemplateTest", "", true}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - data := struct { - Placeholder string - }{Placeholder: tt.ph} - tpl, err := htpl.New("test").Parse(tt.tpl) - if err != nil && !tt.sf { - t.Errorf("failed to render template: %s", err) - return - } - m := NewMsg() - if err := m.SetBodyHTMLTemplate(tpl, data); err != nil && !tt.sf { - t.Errorf("failed to set template as body: %s", err) - } - - wbuf := bytes.Buffer{} - _, err = m.WriteTo(&wbuf) - if err != nil { - t.Errorf("failed to write body to buffer: %s", err) - } - if !strings.Contains(wbuf.String(), tt.ex) && !tt.sf { - t.Errorf("SetBodyTextTemplate failed: Body does not contain the expected tpl placeholder: %s", tt.ph) - } - m.Reset() - }) - } -} - -// TestMsg_AddAlternativeTextTemplate tests the Msg.AddAlternativeTextTemplate method -func TestMsg_AddAlternativeTextTemplate(t *testing.T) { - tests := []struct { - name string - tpl string - ph string - sf bool - }{ - {"normal text", "This is a {{.Placeholder}}", "TemplateTest", false}, - {"invalid tpl", "This is a {{ foo .Placeholder}}", "TemplateTest", true}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - data := struct { - Placeholder string - }{Placeholder: tt.ph} - tpl, err := ttpl.New("test").Parse(tt.tpl) - if err != nil && !tt.sf { - t.Errorf("failed to render template: %s", err) - return - } - m := NewMsg() - m.SetBodyString(TypeTextHTML, "") - if err := m.AddAlternativeTextTemplate(tpl, data); err != nil && !tt.sf { - t.Errorf("failed to set template as alternative part: %s", err) - } - - wbuf := bytes.Buffer{} - _, err = m.WriteTo(&wbuf) - if err != nil { - t.Errorf("failed to write body to buffer: %s", err) - } - if !strings.Contains(wbuf.String(), tt.ph) && !tt.sf { - t.Errorf("SetBodyTextTemplate failed: Body does not contain the expected tpl placeholder: %s", tt.ph) - } - m.Reset() - }) - } -} - -// TestMsg_AddAlternativeHTMLTemplate tests the Msg.AddAlternativeHTMLTemplate method -func TestMsg_AddAlternativeHTMLTemplate(t *testing.T) { - tests := []struct { - name string - tpl string - ph string - ex string - sf bool - }{ - {"normal HTML", "

This is a {{.Placeholder}}

", "TemplateTest", "TemplateTest", false}, - { - "HTML with HTML", "

This is a {{.Placeholder}}

", "", - "<script>alert(1)</script>", false, - }, - {"invalid tpl", "

This is a {{ foo .Placeholder}}

", "TemplateTest", "", true}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - data := struct { - Placeholder string - }{Placeholder: tt.ph} - tpl, err := htpl.New("test").Parse(tt.tpl) - if err != nil && !tt.sf { - t.Errorf("failed to render template: %s", err) - return - } - m := NewMsg() - m.SetBodyString(TypeTextPlain, "") - if err := m.AddAlternativeHTMLTemplate(tpl, data); err != nil && !tt.sf { - t.Errorf("failed to set template as alternative part: %s", err) - } - - wbuf := bytes.Buffer{} - _, err = m.WriteTo(&wbuf) - if err != nil { - t.Errorf("failed to write body to buffer: %s", err) - } - if !strings.Contains(wbuf.String(), tt.ex) && !tt.sf { - t.Errorf("SetBodyTextTemplate failed: Body does not contain the expected tpl placeholder: %s", tt.ph) - } - m.Reset() - }) - } -} - -// TestMsg_AttachTextTemplate tests the Msg.AttachTextTemplate method -func TestMsg_AttachTextTemplate(t *testing.T) { - tests := []struct { - name string - tpl string - ph string - ex string - ac int - sf bool - }{ - { - "normal text", "This is a {{.Placeholder}}", "TemplateTest", - "VGhpcyBpcyBhIFRlbXBsYXRlVGVzdA==", 1, false, - }, - {"invalid tpl", "This is a {{ foo .Placeholder}}", "TemplateTest", "", 0, true}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - data := struct { - Placeholder string - }{Placeholder: tt.ph} - tpl, err := ttpl.New("test").Parse(tt.tpl) - if err != nil && !tt.sf { - t.Errorf("failed to render template: %s", err) - return - } - m := NewMsg() - m.SetBodyString(TypeTextPlain, "This is the body") - if err := m.AttachTextTemplate("attachment.txt", tpl, data); err != nil && !tt.sf { - t.Errorf("failed to attach template: %s", err) - } - - wbuf := bytes.Buffer{} - _, err = m.WriteTo(&wbuf) - if err != nil { - t.Errorf("failed to write body to buffer: %s", err) - } - if !strings.Contains(wbuf.String(), tt.ex) && !tt.sf { - t.Errorf("SetBodyTextTemplate failed: Body does not contain the expected tpl placeholder: %s", tt.ph) - } - if len(m.attachments) != tt.ac { - t.Errorf("wrong number of attachments. Expected: %d, got: %d", tt.ac, len(m.attachments)) - } - m.Reset() - }) - } -} - -// TestMsg_AttachHTMLTemplate tests the Msg.AttachHTMLTemplate method -func TestMsg_AttachHTMLTemplate(t *testing.T) { - tests := []struct { - name string - tpl string - ph string - ex string - ac int - sf bool - }{ - { - "normal HTML", "

This is a {{.Placeholder}}

", "TemplateTest", - "PHA+VGhpcyBpcyBhIFRlbXBsYXRlVGVzdDwvcD4=", 1, false, - }, - { - "HTML with HTML", "

This is a {{.Placeholder}}

", "", - "PHA+VGhpcyBpcyBhICZsdDtzY3JpcHQmZ3Q7YWxlcnQoMSkmbHQ7L3NjcmlwdCZndDs8L3A+", 1, false, - }, - {"invalid tpl", "

This is a {{ foo .Placeholder}}

", "TemplateTest", "", 0, true}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - data := struct { - Placeholder string - }{Placeholder: tt.ph} - tpl, err := htpl.New("test").Parse(tt.tpl) - if err != nil && !tt.sf { - t.Errorf("failed to render template: %s", err) - return - } - m := NewMsg() - m.SetBodyString(TypeTextPlain, "") - if err := m.AttachHTMLTemplate("attachment.html", tpl, data); err != nil && !tt.sf { - t.Errorf("failed to set template as alternative part: %s", err) - } - - wbuf := bytes.Buffer{} - _, err = m.WriteTo(&wbuf) - if err != nil { - t.Errorf("failed to write body to buffer: %s", err) - } - if !strings.Contains(wbuf.String(), tt.ex) && !tt.sf { - t.Errorf("SetBodyTextTemplate failed: Body does not contain the expected tpl placeholder: %s", tt.ph) - } - if len(m.attachments) != tt.ac { - t.Errorf("wrong number of attachments. Expected: %d, got: %d", tt.ac, len(m.attachments)) - } - m.Reset() - }) - } -} - -// TestMsg_EmbedTextTemplate tests the Msg.EmbedTextTemplate method -func TestMsg_EmbedTextTemplate(t *testing.T) { - tests := []struct { - name string - tpl string - ph string - ex string - ec int - sf bool - }{ - { - "normal text", "This is a {{.Placeholder}}", "TemplateTest", - "VGhpcyBpcyBhIFRlbXBsYXRlVGVzdA==", 1, false, - }, - {"invalid tpl", "This is a {{ foo .Placeholder}}", "TemplateTest", "", 0, true}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - data := struct { - Placeholder string - }{Placeholder: tt.ph} - tpl, err := ttpl.New("test").Parse(tt.tpl) - if err != nil && !tt.sf { - t.Errorf("failed to render template: %s", err) - return - } - m := NewMsg() - m.SetBodyString(TypeTextPlain, "This is the body") - if err := m.EmbedTextTemplate("attachment.txt", tpl, data); err != nil && !tt.sf { - t.Errorf("failed to attach template: %s", err) - } - - wbuf := bytes.Buffer{} - _, err = m.WriteTo(&wbuf) - if err != nil { - t.Errorf("failed to write body to buffer: %s", err) - } - if !strings.Contains(wbuf.String(), tt.ex) && !tt.sf { - t.Errorf("SetBodyTextTemplate failed: Body does not contain the expected tpl placeholder: %s", tt.ph) - } - if len(m.embeds) != tt.ec { - t.Errorf("wrong number of attachments. Expected: %d, got: %d", tt.ec, len(m.attachments)) - } - m.Reset() - }) - } -} - -// TestMsg_EmbedHTMLTemplate tests the Msg.EmbedHTMLTemplate method -func TestMsg_EmbedHTMLTemplate(t *testing.T) { - tests := []struct { - name string - tpl string - ph string - ex string - ec int - sf bool - }{ - { - "normal HTML", "

This is a {{.Placeholder}}

", "TemplateTest", - "PHA+VGhpcyBpcyBhIFRlbXBsYXRlVGVzdDwvcD4=", 1, false, - }, - { - "HTML with HTML", "

This is a {{.Placeholder}}

", "", - "PHA+VGhpcyBpcyBhICZsdDtzY3JpcHQmZ3Q7YWxlcnQoMSkmbHQ7L3NjcmlwdCZndDs8L3A+", 1, false, - }, - {"invalid tpl", "

This is a {{ foo .Placeholder}}

", "TemplateTest", "", 0, true}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - data := struct { - Placeholder string - }{Placeholder: tt.ph} - tpl, err := htpl.New("test").Parse(tt.tpl) - if err != nil && !tt.sf { - t.Errorf("failed to render template: %s", err) - return - } - m := NewMsg() - m.SetBodyString(TypeTextPlain, "") - if err := m.EmbedHTMLTemplate("attachment.html", tpl, data); err != nil && !tt.sf { - t.Errorf("failed to set template as alternative part: %s", err) - } - - wbuf := bytes.Buffer{} - _, err = m.WriteTo(&wbuf) - if err != nil { - t.Errorf("failed to write body to buffer: %s", err) - } - if !strings.Contains(wbuf.String(), tt.ex) && !tt.sf { - t.Errorf("SetBodyTextTemplate failed: Body does not contain the expected tpl placeholder: %s", tt.ph) - } - if len(m.embeds) != tt.ec { - t.Errorf("wrong number of attachments. Expected: %d, got: %d", tt.ec, len(m.attachments)) - } - m.Reset() - }) - } -} - -// TestMsg_WriteToTempFile will test the output to temporary files -func TestMsg_WriteToTempFile(t *testing.T) { - m := NewMsg() - _ = m.From("Toni Tester ") - _ = m.To("Ellenor Tester ") - m.SetBodyString(TypeTextPlain, "This is a test") - f, err := m.WriteToTempFile() - if err != nil { - t.Errorf("failed to write message to temporary output file: %s", err) - } - _ = os.Remove(f) -} - -// TestMsg_WriteToFile will test the output to a file -func TestMsg_WriteToFile(t *testing.T) { - f, err := os.CreateTemp("", "go-mail-test_*.eml") - if err != nil { - t.Errorf("failed to create temporary output file: %s", err) - } - defer func() { - _ = f.Close() - _ = os.Remove(f.Name()) - }() - - m := NewMsg() - _ = m.From("Toni Tester ") - _ = m.To("Ellenor Tester ") - m.SetBodyString(TypeTextPlain, "This is a test") - if err := m.WriteToFile(f.Name()); err != nil { - t.Errorf("failed to write to output file: %s", err) - } - fi, err := os.Stat(f.Name()) - if err != nil { - t.Errorf("failed to stat output file: %s", err) - } - if fi == nil { - t.Errorf("received empty file handle") - return - } - if fi.Size() <= 0 { - t.Errorf("output file is expected to contain data but its size is zero") - } -} - -// TestMsg_GetGenHeader will test the GetGenHeader method of the Msg -func TestMsg_GetGenHeader(t *testing.T) { - m := NewMsg() - m.Subject("this is a test") - sa := m.GetGenHeader(HeaderSubject) - if len(sa) <= 0 { - t.Errorf("GetGenHeader on subject failed. Got empty slice") - return - } - if sa[0] == "" { - t.Errorf("GetGenHeader on subject failed. Got empty value") - } - if sa[0] != "this is a test" { - t.Errorf("GetGenHeader on subject failed. Expected: %q, got: %q", "this is a test", sa[0]) - } -} - -// TestMsg_GetAddrHeader will test the Msg.GetAddrHeader method -func TestMsg_GetAddrHeader(t *testing.T) { - m := NewMsg() - if err := m.FromFormat("Toni Sender", "sender@example.com"); err != nil { - t.Errorf("failed to set FROM address: %s", err) - } - if err := m.AddToFormat("Toni To", "to@example.com"); err != nil { - t.Errorf("failed to set TO address: %s", err) - } - if err := m.AddCcFormat("Toni Cc", "cc@example.com"); err != nil { - t.Errorf("failed to set CC address: %s", err) - } - if err := m.AddBccFormat("Toni Bcc", "bcc@example.com"); err != nil { - t.Errorf("failed to set BCC address: %s", err) - } - fh := m.GetAddrHeader(HeaderFrom) - if len(fh) <= 0 { - t.Errorf("GetAddrHeader on FROM failed. Got empty slice") - return - } - if fh[0].String() == "" { - t.Errorf("GetAddrHeader on FROM failed. Got empty value") - } - if fh[0].String() != `"Toni Sender" ` { - t.Errorf("GetAddrHeader on FROM failed. Expected: %q, got: %q", - `"Toni Sender" "`, fh[0].String()) - } - th := m.GetAddrHeader(HeaderTo) - if len(th) <= 0 { - t.Errorf("GetAddrHeader on TO failed. Got empty slice") - return - } - if th[0].String() == "" { - t.Errorf("GetAddrHeader on TO failed. Got empty value") - } - if th[0].String() != `"Toni To" ` { - t.Errorf("GetAddrHeader on TO failed. Expected: %q, got: %q", - `"Toni To" "`, th[0].String()) - } - ch := m.GetAddrHeader(HeaderCc) - if len(ch) <= 0 { - t.Errorf("GetAddrHeader on CC failed. Got empty slice") - return - } - if ch[0].String() == "" { - t.Errorf("GetAddrHeader on CC failed. Got empty value") - } - if ch[0].String() != `"Toni Cc" ` { - t.Errorf("GetAddrHeader on CC failed. Expected: %q, got: %q", - `"Toni Cc" "`, ch[0].String()) - } - bh := m.GetAddrHeader(HeaderBcc) - if len(bh) <= 0 { - t.Errorf("GetAddrHeader on BCC failed. Got empty slice") - return - } - if bh[0].String() == "" { - t.Errorf("GetAddrHeader on BCC failed. Got empty value") - } - if bh[0].String() != `"Toni Bcc" ` { - t.Errorf("GetAddrHeader on BCC failed. Expected: %q, got: %q", - `"Toni Bcc" "`, bh[0].String()) - } -} - -// TestMsg_GetFrom will test the Msg.GetFrom method -func TestMsg_GetFrom(t *testing.T) { - m := NewMsg() - if err := m.FromFormat("Toni Sender", "sender@example.com"); err != nil { - t.Errorf("failed to set FROM address: %s", err) - } - fh := m.GetFrom() - if len(fh) <= 0 { - t.Errorf("GetFrom failed. Got empty slice") - return - } - if fh[0].String() == "" { - t.Errorf("GetFrom failed. Got empty value") - } - if fh[0].String() != `"Toni Sender" ` { - t.Errorf("GetFrom failed. Expected: %q, got: %q", - `"Toni Sender" "`, fh[0].String()) - } -} - -// TestMsg_GetFromString will test the Msg.GetFromString method -func TestMsg_GetFromString(t *testing.T) { - m := NewMsg() - if err := m.FromFormat("Toni Sender", "sender@example.com"); err != nil { - t.Errorf("failed to set FROM address: %s", err) - } - fh := m.GetFromString() - if len(fh) <= 0 { - t.Errorf("GetFromString failed. Got empty slice") - return - } - if fh[0] == "" { - t.Errorf("GetFromString failed. Got empty value") - } - if fh[0] != `"Toni Sender" ` { - t.Errorf("GetFromString failed. Expected: %q, got: %q", - `"Toni Sender" "`, fh[0]) - } -} - -// TestMsg_GetTo will test the Msg.GetTo method -func TestMsg_GetTo(t *testing.T) { - m := NewMsg() - if err := m.AddToFormat("Toni To", "to@example.com"); err != nil { - t.Errorf("failed to set TO address: %s", err) - } - fh := m.GetTo() - if len(fh) <= 0 { - t.Errorf("GetTo failed. Got empty slice") - return - } - if fh[0].String() == "" { - t.Errorf("GetTo failed. Got empty value") - } - if fh[0].String() != `"Toni To" ` { - t.Errorf("GetTo failed. Expected: %q, got: %q", - `"Toni To" "`, fh[0].String()) - } -} - -// TestMsg_GetToString will test the Msg.GetToString method -func TestMsg_GetToString(t *testing.T) { - m := NewMsg() - if err := m.AddToFormat("Toni To", "to@example.com"); err != nil { - t.Errorf("failed to set TO address: %s", err) - } - fh := m.GetToString() - if len(fh) <= 0 { - t.Errorf("GetToString failed. Got empty slice") - return - } - if fh[0] == "" { - t.Errorf("GetToString failed. Got empty value") - } - if fh[0] != `"Toni To" ` { - t.Errorf("GetToString failed. Expected: %q, got: %q", - `"Toni To" "`, fh[0]) - } -} - -// TestMsg_GetCc will test the Msg.GetCc method -func TestMsg_GetCc(t *testing.T) { - m := NewMsg() - if err := m.AddCcFormat("Toni Cc", "cc@example.com"); err != nil { - t.Errorf("failed to set TO address: %s", err) - } - fh := m.GetCc() - if len(fh) <= 0 { - t.Errorf("GetCc failed. Got empty slice") - return - } - if fh[0].String() == "" { - t.Errorf("GetCc failed. Got empty value") - } - if fh[0].String() != `"Toni Cc" ` { - t.Errorf("GetCc failed. Expected: %q, got: %q", - `"Toni Cc" "`, fh[0].String()) - } -} - -// TestMsg_GetCcString will test the Msg.GetCcString method -func TestMsg_GetCcString(t *testing.T) { - m := NewMsg() - if err := m.AddCcFormat("Toni Cc", "cc@example.com"); err != nil { - t.Errorf("failed to set TO address: %s", err) - } - fh := m.GetCcString() - if len(fh) <= 0 { - t.Errorf("GetCcString failed. Got empty slice") - return - } - if fh[0] == "" { - t.Errorf("GetCcString failed. Got empty value") - } - if fh[0] != `"Toni Cc" ` { - t.Errorf("GetCcString failed. Expected: %q, got: %q", - `"Toni Cc" "`, fh[0]) - } -} - -// TestMsg_GetBcc will test the Msg.GetBcc method -func TestMsg_GetBcc(t *testing.T) { - m := NewMsg() - if err := m.AddBccFormat("Toni Bcc", "bcc@example.com"); err != nil { - t.Errorf("failed to set TO address: %s", err) - } - fh := m.GetBcc() - if len(fh) <= 0 { - t.Errorf("GetBcc failed. Got empty slice") - return - } - if fh[0].String() == "" { - t.Errorf("GetBcc failed. Got empty value") - } - if fh[0].String() != `"Toni Bcc" ` { - t.Errorf("GetBcc failed. Expected: %q, got: %q", - `"Toni Cc" "`, fh[0].String()) - } -} - -// TestMsg_GetBccString will test the Msg.GetBccString method -func TestMsg_GetBccString(t *testing.T) { - m := NewMsg() - if err := m.AddBccFormat("Toni Bcc", "bcc@example.com"); err != nil { - t.Errorf("failed to set TO address: %s", err) - } - fh := m.GetBccString() - if len(fh) <= 0 { - t.Errorf("GetBccString failed. Got empty slice") - return - } - if fh[0] == "" { - t.Errorf("GetBccString failed. Got empty value") - } - if fh[0] != `"Toni Bcc" ` { - t.Errorf("GetBccString failed. Expected: %q, got: %q", - `"Toni Cc" "`, fh[0]) - } -} - -// TestMsg_GetBoundary will test the Msg.GetBoundary method -func TestMsg_GetBoundary(t *testing.T) { - b := "random_boundary_string" - m := NewMsg() - if boundary := m.GetBoundary(); boundary != "" { - t.Errorf("GetBoundary failed. Expected empty string, but got: %s", boundary) - } - m = NewMsg(WithBoundary(b)) - if boundary := m.GetBoundary(); boundary != b { - t.Errorf("GetBoundary failed. Expected boundary: %s, got: %s", b, boundary) - } -} - -// TestMsg_AttachEmbedReader_consecutive tests the Msg.AttachReader and Msg.EmbedReader -// methods with consecutive calls to Msg.WriteTo to make sure the attachments are not -// lost (see Github issue #110) -func TestMsg_AttachEmbedReader_consecutive(t *testing.T) { - ts1 := "This is a test string" - ts2 := "Another test string" - m := NewMsg() - if err := m.AttachReader("attachment.txt", bytes.NewBufferString(ts1)); err != nil { - t.Errorf("AttachReader() failed. Expected no error, got: %s", err.Error()) - return - } - if err := m.EmbedReader("embedded.txt", bytes.NewBufferString(ts2)); err != nil { - t.Errorf("EmbedReader() failed. Expected no error, got: %s", err.Error()) - return - } - obuf1 := &bytes.Buffer{} - obuf2 := &bytes.Buffer{} - _, err := m.WriteTo(obuf1) - if err != nil { - t.Errorf("WriteTo to first output buffer failed: %s", err) - } - _, err = m.WriteTo(obuf2) - if err != nil { - t.Errorf("WriteTo to second output buffer failed: %s", err) - } - if !strings.Contains(obuf1.String(), "VGhpcyBpcyBhIHRlc3Qgc3RyaW5n") { - t.Errorf("Expected file attachment string not found in first output buffer") - } - if !strings.Contains(obuf2.String(), "VGhpcyBpcyBhIHRlc3Qgc3RyaW5n") { - t.Errorf("Expected file attachment string not found in second output buffer") - } - if !strings.Contains(obuf1.String(), "QW5vdGhlciB0ZXN0IHN0cmluZw==") { - t.Errorf("Expected embedded file string not found in first output buffer") - } - if !strings.Contains(obuf2.String(), "QW5vdGhlciB0ZXN0IHN0cmluZw==") { - t.Errorf("Expected embded file string not found in second output buffer") - } -} - -// TestMsg_AttachEmbedReadSeeker_consecutive tests the Msg.AttachReadSeeker and -// Msg.EmbedReadSeeker methods with consecutive calls to Msg.WriteTo to make -// sure the attachments are not lost (see Github issue #110) -func TestMsg_AttachEmbedReadSeeker_consecutive(t *testing.T) { - ts1 := []byte("This is a test string") - ts2 := []byte("Another test string") - m := NewMsg() - m.AttachReadSeeker("attachment.txt", bytes.NewReader(ts1)) - m.EmbedReadSeeker("embedded.txt", bytes.NewReader(ts2)) - obuf1 := &bytes.Buffer{} - obuf2 := &bytes.Buffer{} - _, err := m.WriteTo(obuf1) - if err != nil { - t.Errorf("WriteTo to first output buffer failed: %s", err) - } - _, err = m.WriteTo(obuf2) - if err != nil { - t.Errorf("WriteTo to second output buffer failed: %s", err) - } - if !strings.Contains(obuf1.String(), "VGhpcyBpcyBhIHRlc3Qgc3RyaW5n") { - t.Errorf("Expected file attachment string not found in first output buffer") - } - if !strings.Contains(obuf2.String(), "VGhpcyBpcyBhIHRlc3Qgc3RyaW5n") { - t.Errorf("Expected file attachment string not found in second output buffer") - } - if !strings.Contains(obuf1.String(), "QW5vdGhlciB0ZXN0IHN0cmluZw==") { - t.Errorf("Expected embedded file string not found in first output buffer") - } - if !strings.Contains(obuf2.String(), "QW5vdGhlciB0ZXN0IHN0cmluZw==") { - t.Errorf("Expected embded file string not found in second output buffer") - } -} - -// TestMsg_AttachReadSeeker tests the Msg.AttachReadSeeker method -func TestMsg_AttachReadSeeker(t *testing.T) { - m := NewMsg() - ts := []byte("This is a test string") - r := bytes.NewReader(ts) - m.AttachReadSeeker("testfile.txt", r) - if len(m.attachments) != 1 { - t.Errorf("AttachReadSeeker() failed. Number of attachments expected: %d, got: %d", 1, - len(m.attachments)) - return - } - file := m.attachments[0] - if file == nil { - t.Errorf("AttachReadSeeker() failed. Attachment file pointer is nil") - return - } - if file.Name != "testfile.txt" { - t.Errorf("AttachReadSeeker() failed. Expected file name: %s, got: %s", "testfile.txt", - file.Name) - } - wbuf := bytes.Buffer{} - if _, err := file.Writer(&wbuf); err != nil { - t.Errorf("execute WriterFunc failed: %s", err) - } - if wbuf.String() != string(ts) { - t.Errorf("AttachReadSeeker() failed. Expected string: %q, got: %q", ts, wbuf.String()) - } -} - -// TestMsg_EmbedReadSeeker tests the Msg.EmbedReadSeeker method -func TestMsg_EmbedReadSeeker(t *testing.T) { - m := NewMsg() - ts := []byte("This is a test string") - r := bytes.NewReader(ts) - m.EmbedReadSeeker("testfile.txt", r) - if len(m.embeds) != 1 { - t.Errorf("EmbedReadSeeker() failed. Number of attachments expected: %d, got: %d", 1, - len(m.embeds)) - return - } - file := m.embeds[0] - if file == nil { - t.Errorf("EmbedReadSeeker() failed. Embedded file pointer is nil") - return - } - if file.Name != "testfile.txt" { - t.Errorf("EmbedReadSeeker() failed. Expected file name: %s, got: %s", "testfile.txt", - file.Name) - } - wbuf := bytes.Buffer{} - if _, err := file.Writer(&wbuf); err != nil { - t.Errorf("execute WriterFunc failed: %s", err) - } - if wbuf.String() != string(ts) { - t.Errorf("EmbedReadSeeker() failed. Expected string: %q, got: %q", ts, wbuf.String()) - } -} - -// TestMsg_ToFromString tests Msg.ToFromString in different scenarios -func TestMsg_ToFromString(t *testing.T) { - tests := []struct { - n string - v string - w []*mail.Address - sf bool - }{ - {"valid single address", "test@test.com", []*mail.Address{ - {Name: "", Address: "test@test.com"}, - }, false}, - { - "valid multiple addresses", "test@test.com,test2@example.com", - []*mail.Address{ - {Name: "", Address: "test@test.com"}, - {Name: "", Address: "test2@example.com"}, - }, - false, - }, - { - "valid multiple addresses with space and name", - `test@test.com, "Toni Tester" `, - []*mail.Address{ - {Name: "", Address: "test@test.com"}, - {Name: "Toni Tester", Address: "test2@example.com"}, - }, - false, - }, - { - "invalid and valid multiple addresses", "test@test.com,test2#example.com", nil, - true, - }, - } - - for _, tt := range tests { - t.Run(tt.n, func(t *testing.T) { - m := NewMsg() - if err := m.ToFromString(tt.v); err != nil && !tt.sf { - t.Errorf("Msg.ToFromString failed: %s", err) - return - } - mto := m.GetTo() - if len(mto) != len(tt.w) { - t.Errorf("Msg.ToFromString failed, expected len: %d, got: %d", len(tt.w), - len(mto)) - return - } - for i := range mto { - w := tt.w[i] - g := mto[i] - if w.String() != g.String() { - t.Errorf("Msg.ToFromString failed, expected address: %s, got: %s", - w.String(), g.String()) - } - } - }) - } -} - -// TestMsg_CcFromString tests Msg.CcFromString in different scenarios -func TestMsg_CcFromString(t *testing.T) { - tests := []struct { - n string - v string - w []*mail.Address - sf bool - }{ - {"valid single address", "test@test.com", []*mail.Address{ - {Name: "", Address: "test@test.com"}, - }, false}, - { - "valid multiple addresses", "test@test.com,test2@example.com", - []*mail.Address{ - {Name: "", Address: "test@test.com"}, - {Name: "", Address: "test2@example.com"}, - }, - false, - }, - { - "valid multiple addresses with space and name", - `test@test.com, "Toni Tester" `, - []*mail.Address{ - {Name: "", Address: "test@test.com"}, - {Name: "Toni Tester", Address: "test2@example.com"}, - }, - false, - }, - { - "invalid and valid multiple addresses", "test@test.com,test2#example.com", nil, - true, - }, - } - - for _, tt := range tests { - t.Run(tt.n, func(t *testing.T) { - m := NewMsg() - if err := m.CcFromString(tt.v); err != nil && !tt.sf { - t.Errorf("Msg.CcFromString failed: %s", err) - return - } - mto := m.GetCc() - if len(mto) != len(tt.w) { - t.Errorf("Msg.CcFromString failed, expected len: %d, got: %d", len(tt.w), - len(mto)) - return - } - for i := range mto { - w := tt.w[i] - g := mto[i] - if w.String() != g.String() { - t.Errorf("Msg.CcFromString failed, expected address: %s, got: %s", - w.String(), g.String()) - } - } - }) - } -} - -// TestMsg_BccFromString tests Msg.BccFromString in different scenarios -func TestMsg_BccFromString(t *testing.T) { - tests := []struct { - n string - v string - w []*mail.Address - sf bool - }{ - {"valid single address", "test@test.com", []*mail.Address{ - {Name: "", Address: "test@test.com"}, - }, false}, - { - "valid multiple addresses", "test@test.com,test2@example.com", - []*mail.Address{ - {Name: "", Address: "test@test.com"}, - {Name: "", Address: "test2@example.com"}, - }, - false, - }, - { - "valid multiple addresses with space and name", - `test@test.com, "Toni Tester" `, - []*mail.Address{ - {Name: "", Address: "test@test.com"}, - {Name: "Toni Tester", Address: "test2@example.com"}, - }, - false, - }, - { - "invalid and valid multiple addresses", "test@test.com,test2#example.com", nil, - true, - }, - } - - for _, tt := range tests { - t.Run(tt.n, func(t *testing.T) { - m := NewMsg() - if err := m.BccFromString(tt.v); err != nil && !tt.sf { - t.Errorf("Msg.BccFromString failed: %s", err) - return - } - mto := m.GetBcc() - if len(mto) != len(tt.w) { - t.Errorf("Msg.BccFromString failed, expected len: %d, got: %d", len(tt.w), - len(mto)) - return - } - for i := range mto { - w := tt.w[i] - g := mto[i] - if w.String() != g.String() { - t.Errorf("Msg.BccFromString failed, expected address: %s, got: %s", - w.String(), g.String()) - } - } - }) - } -} - -// TestMsg_checkUserAgent tests the checkUserAgent method of the Msg -func TestMsg_checkUserAgent(t *testing.T) { - tests := []struct { - name string - noDefaultUserAgent bool - genHeader map[Header][]string - wantUserAgent string - sf bool - }{ - { - name: "check default user agent", - noDefaultUserAgent: false, - wantUserAgent: fmt.Sprintf("go-mail v%s // https://github.com/wneessen/go-mail", VERSION), - sf: false, - }, - { - name: "check no default user agent", - noDefaultUserAgent: true, - wantUserAgent: "", - sf: true, - }, - { - name: "check if ua and xm is already set", - noDefaultUserAgent: false, - genHeader: map[Header][]string{ - HeaderUserAgent: {"custom UA"}, - HeaderXMailer: {"custom XM"}, - }, - wantUserAgent: "custom UA", - sf: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - msg := &Msg{ - noDefaultUserAgent: tt.noDefaultUserAgent, - genHeader: tt.genHeader, - } - msg.checkUserAgent() - gotUserAgent := "" - if val, ok := msg.genHeader[HeaderUserAgent]; ok { - gotUserAgent = val[0] // Assuming the first one is the needed value - } - if gotUserAgent != tt.wantUserAgent && !tt.sf { - t.Errorf("UserAgent got = %v, want = %v", gotUserAgent, tt.wantUserAgent) - } - }) - } -} - -// TestNewMsgWithMIMEVersion tests WithMIMEVersion and Msg.SetMIMEVersion -func TestNewMsgWithNoDefaultUserAgent(t *testing.T) { - m := NewMsg(WithNoDefaultUserAgent()) - if m.noDefaultUserAgent != true { - t.Errorf("WithNoDefaultUserAgent() failed. Expected: %t, got: %t", true, false) - } -} - -// Fuzzing tests -func FuzzMsg_Subject(f *testing.F) { - f.Add("Testsubject") - f.Add("") - f.Add("This is a longer test subject.") - f.Add("Let's add some umlauts: üäöß") - f.Add("Or even emojis: ☝️💪👍") - f.Fuzz(func(t *testing.T, data string) { - m := NewMsg() - m.Subject(data) - m.Reset() - }) -} - -func FuzzMsg_From(f *testing.F) { - f.Add("Toni Tester ") - f.Add("") - f.Add("mail@server.com") - f.Fuzz(func(t *testing.T, data string) { - m := NewMsg() - if err := m.From(data); err != nil && - !strings.Contains(err.Error(), "failed to parse mail address") { - t.Errorf("failed set set FROM address: %s", err) - } - m.Reset() - }) -}