package email import ( "testing" "bytes" "crypto/rand" "io" "io/ioutil" "mime" "mime/multipart" "net/mail" "net/smtp" "os" ) func TestEmailTextHtmlAttachment(t *testing.T) { e := NewEmail() e.From = "Jordan Wright " e.To = []string{"test@example.com"} e.Bcc = []string{"test_bcc@example.com"} e.Cc = []string{"test_cc@example.com"} e.Subject = "Awesome Subject" e.Text = "Text Body is, of course, supported!\n" e.HTML = "

Fancy Html is supported, too!

\n" e.Attach(bytes.NewBufferString("Rad attachement"), "rad.txt", "text/plain; charset=utf-8") raw, err := e.Bytes() if err != nil { t.Fatal("Failed to render message: ", e) } ioutil.WriteFile("golden", raw, os.ModePerm) msg, err := mail.ReadMessage(bytes.NewBuffer(raw)) if err != nil { t.Fatal("Could not parse rendered message: ", err) } expectedHeaders := map[string]string{ "To": "test@example.com", "From": "Jordan Wright ", "Cc": "test_cc@example.com", "Subject": "Awesome Subject", } for header, expected := range expectedHeaders { if val := msg.Header.Get(header); val != expected { t.Errorf("Wrong value for message header %s: %v != %v", header, expected, val) } } // Were the right headers set? ct := msg.Header.Get("Content-type") mt, params, err := mime.ParseMediaType(ct) if err != nil { t.Fatal("Content-type header is invalid: ", ct) } else if mt != "multipart/mixed" { t.Fatalf("Content-type expected \"multipart/mixed\", not %v", mt) } b := params["boundary"] if b == "" { t.Fatalf("Invalid or missing boundary parameter: ", b) } if len(params) != 1 { t.Fatal("Unexpected content-type parameters") } // Is the generated message parsable? mixed := multipart.NewReader(msg.Body, params["boundary"]) text, err := mixed.NextPart() if err != nil { t.Fatalf("Could not find text component of email: ", err) } // Does the text portion match what we expect? mt, params, err = mime.ParseMediaType(text.Header.Get("Content-type")) if err != nil { t.Fatal("Could not parse message's Content-Type") } else if mt != "multipart/alternative" { t.Fatal("Message missing multipart/alternative") } mpReader := multipart.NewReader(text, params["boundary"]) part, err := mpReader.NextPart() if err != nil { t.Fatal("Could not read plain text component of message: ", err) } plainText, err := ioutil.ReadAll(part) if err != nil { t.Fatal("Could not read plain text component of message: ", err) } if !bytes.Equal(plainText, []byte("Text Body is, of course, supported!\r\n")) { t.Fatalf("Plain text is broken: %#q", plainText) } // Check attachments. _, err = mixed.NextPart() if err != nil { t.Fatalf("Could not find attachemnt compoenent of email: ", err) } if _, err = mixed.NextPart(); err != io.EOF { t.Error("Expected only text and one attachement!") } } func ExampleGmail() { e := NewEmail() e.From = "Jordan Wright " e.To = []string{"test@example.com"} e.Bcc = []string{"test_bcc@example.com"} e.Cc = []string{"test_cc@example.com"} e.Subject = "Awesome Subject" e.Text = "Text Body is, of course, supported!" e.HTML = "

Fancy Html is supported, too!

" e.Send("smtp.gmail.com:587", smtp.PlainAuth("", e.From, "password123", "smtp.gmail.com")) } func ExampleAttach() { e := NewEmail() e.AttachFile("test.txt") } func Test_base64Wrap(t *testing.T) { file := "I'm a file long enough to force the function to wrap a\n" + "couple of lines, but I stop short of the end of one line and\n" + "have some padding dangling at the end." encoded := "SSdtIGEgZmlsZSBsb25nIGVub3VnaCB0byBmb3JjZSB0aGUgZnVuY3Rpb24gdG8gd3JhcCBhCmNv\r\n" + "dXBsZSBvZiBsaW5lcywgYnV0IEkgc3RvcCBzaG9ydCBvZiB0aGUgZW5kIG9mIG9uZSBsaW5lIGFu\r\n" + "ZApoYXZlIHNvbWUgcGFkZGluZyBkYW5nbGluZyBhdCB0aGUgZW5kLg==\r\n" var buf bytes.Buffer base64Wrap(&buf, []byte(file)) if !bytes.Equal(buf.Bytes(), []byte(encoded)) { t.Fatalf("Encoded file does not match expected: %#q != %#q", string(buf.Bytes()), encoded) } } func Test_quotedPrintEncode(t *testing.T) { var buf bytes.Buffer text := "Dear reader!\n\n" + "This is a test email to try and capture some of the corner cases that exist within\n" + "the quoted-printable encoding.\n" + "There are some wacky parts like =, and this input assumes UNIX line breaks so\r\n" + "it can come out a little weird. Also, we need to support unicode so here's a fish: 🐟\n" expected := "Dear reader!\r\n\r\n" + "This is a test email to try and capture some of the corner cases that exist=\r\n" + " within\r\n" + "the quoted-printable encoding.\r\n" + "There are some wacky parts like =3D, and this input assumes UNIX line break=\r\n" + "s so=0D\r\n" + "it can come out a little weird. Also, we need to support unicode so here's=\r\n" + " a fish: =F0=9F=90=9F\r\n" if err := quotePrintEncode(&buf, text); err != nil { t.Fatal("quotePrintEncode: ", err) } if s := buf.String(); s != expected { t.Errorf("quotedPrintEncode generated incorrect results: %#q != %#q", s, expected) } } func Benchmark_quotedPrintEncode(b *testing.B) { text := "Dear reader!\n\n" + "This is a test email to try and capture some of the corner cases that exist within\n" + "the quoted-printable encoding.\n" + "There are some wacky parts like =, and this input assumes UNIX line breaks so\r\n" + "it can come out a little weird. Also, we need to support unicode so here's a fish: 🐟\n" for i := 0; i <= b.N; i++ { if err := quotePrintEncode(ioutil.Discard, text); err != nil { panic(err) } } } func Benchmark_base64Wrap(b *testing.B) { // Reasonable base case; 128K random bytes file := make([]byte, 128*1024) if _, err := rand.Read(file); err != nil { panic(err) } for i := 0; i <= b.N; i++ { base64Wrap(ioutil.Discard, file) } }