email_test.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. package email
  2. import (
  3. "testing"
  4. "bytes"
  5. "crypto/rand"
  6. "io"
  7. "io/ioutil"
  8. "mime"
  9. "mime/multipart"
  10. "net/mail"
  11. "net/smtp"
  12. )
  13. func TestEmailTextHtmlAttachment(t *testing.T) {
  14. e := NewEmail()
  15. e.From = "Jordan Wright <test@example.com>"
  16. e.To = []string{"test@example.com"}
  17. e.Bcc = []string{"test_bcc@example.com"}
  18. e.Cc = []string{"test_cc@example.com"}
  19. e.Subject = "Awesome Subject"
  20. e.Text = []byte("Text Body is, of course, supported!\n")
  21. e.HTML = []byte("<h1>Fancy Html is supported, too!</h1>\n")
  22. e.Attach(bytes.NewBufferString("Rad attachement"), "rad.txt", "text/plain; charset=utf-8")
  23. raw, err := e.Bytes()
  24. if err != nil {
  25. t.Fatal("Failed to render message: ", e)
  26. }
  27. msg, err := mail.ReadMessage(bytes.NewBuffer(raw))
  28. if err != nil {
  29. t.Fatal("Could not parse rendered message: ", err)
  30. }
  31. expectedHeaders := map[string]string{
  32. "To": "test@example.com",
  33. "From": "Jordan Wright <test@example.com>",
  34. "Cc": "test_cc@example.com",
  35. "Subject": "Awesome Subject",
  36. }
  37. for header, expected := range expectedHeaders {
  38. if val := msg.Header.Get(header); val != expected {
  39. t.Errorf("Wrong value for message header %s: %v != %v", header, expected, val)
  40. }
  41. }
  42. // Were the right headers set?
  43. ct := msg.Header.Get("Content-type")
  44. mt, params, err := mime.ParseMediaType(ct)
  45. if err != nil {
  46. t.Fatal("Content-type header is invalid: ", ct)
  47. } else if mt != "multipart/mixed" {
  48. t.Fatalf("Content-type expected \"multipart/mixed\", not %v", mt)
  49. }
  50. b := params["boundary"]
  51. if b == "" {
  52. t.Fatalf("Invalid or missing boundary parameter: ", b)
  53. }
  54. if len(params) != 1 {
  55. t.Fatal("Unexpected content-type parameters")
  56. }
  57. // Is the generated message parsable?
  58. mixed := multipart.NewReader(msg.Body, params["boundary"])
  59. text, err := mixed.NextPart()
  60. if err != nil {
  61. t.Fatalf("Could not find text component of email: ", err)
  62. }
  63. // Does the text portion match what we expect?
  64. mt, params, err = mime.ParseMediaType(text.Header.Get("Content-type"))
  65. if err != nil {
  66. t.Fatal("Could not parse message's Content-Type")
  67. } else if mt != "multipart/alternative" {
  68. t.Fatal("Message missing multipart/alternative")
  69. }
  70. mpReader := multipart.NewReader(text, params["boundary"])
  71. part, err := mpReader.NextPart()
  72. if err != nil {
  73. t.Fatal("Could not read plain text component of message: ", err)
  74. }
  75. plainText, err := ioutil.ReadAll(part)
  76. if err != nil {
  77. t.Fatal("Could not read plain text component of message: ", err)
  78. }
  79. if !bytes.Equal(plainText, []byte("Text Body is, of course, supported!\r\n")) {
  80. t.Fatalf("Plain text is broken: %#q", plainText)
  81. }
  82. // Check attachments.
  83. _, err = mixed.NextPart()
  84. if err != nil {
  85. t.Fatalf("Could not find attachemnt compoenent of email: ", err)
  86. }
  87. if _, err = mixed.NextPart(); err != io.EOF {
  88. t.Error("Expected only text and one attachement!")
  89. }
  90. }
  91. func ExampleGmail() {
  92. e := NewEmail()
  93. e.From = "Jordan Wright <test@gmail.com>"
  94. e.To = []string{"test@example.com"}
  95. e.Bcc = []string{"test_bcc@example.com"}
  96. e.Cc = []string{"test_cc@example.com"}
  97. e.Subject = "Awesome Subject"
  98. e.Text = []byte("Text Body is, of course, supported!\n")
  99. e.HTML = []byte("<h1>Fancy Html is supported, too!</h1>\n")
  100. e.Send("smtp.gmail.com:587", smtp.PlainAuth("", e.From, "password123", "smtp.gmail.com"))
  101. }
  102. func ExampleAttach() {
  103. e := NewEmail()
  104. e.AttachFile("test.txt")
  105. }
  106. func Test_base64Wrap(t *testing.T) {
  107. file := "I'm a file long enough to force the function to wrap a\n" +
  108. "couple of lines, but I stop short of the end of one line and\n" +
  109. "have some padding dangling at the end."
  110. encoded := "SSdtIGEgZmlsZSBsb25nIGVub3VnaCB0byBmb3JjZSB0aGUgZnVuY3Rpb24gdG8gd3JhcCBhCmNv\r\n" +
  111. "dXBsZSBvZiBsaW5lcywgYnV0IEkgc3RvcCBzaG9ydCBvZiB0aGUgZW5kIG9mIG9uZSBsaW5lIGFu\r\n" +
  112. "ZApoYXZlIHNvbWUgcGFkZGluZyBkYW5nbGluZyBhdCB0aGUgZW5kLg==\r\n"
  113. var buf bytes.Buffer
  114. base64Wrap(&buf, []byte(file))
  115. if !bytes.Equal(buf.Bytes(), []byte(encoded)) {
  116. t.Fatalf("Encoded file does not match expected: %#q != %#q", string(buf.Bytes()), encoded)
  117. }
  118. }
  119. func Test_quotedPrintEncode(t *testing.T) {
  120. var buf bytes.Buffer
  121. text := []byte("Dear reader!\n\n" +
  122. "This is a test email to try and capture some of the corner cases that exist within\n" +
  123. "the quoted-printable encoding.\n" +
  124. "There are some wacky parts like =, and this input assumes UNIX line breaks so\r\n" +
  125. "it can come out a little weird. Also, we need to support unicode so here's a fish: 🐟\n")
  126. expected := []byte("Dear reader!\r\n\r\n" +
  127. "This is a test email to try and capture some of the corner cases that exist=\r\n" +
  128. " within\r\n" +
  129. "the quoted-printable encoding.\r\n" +
  130. "There are some wacky parts like =3D, and this input assumes UNIX line break=\r\n" +
  131. "s so=0D\r\n" +
  132. "it can come out a little weird. Also, we need to support unicode so here's=\r\n" +
  133. " a fish: =F0=9F=90=9F\r\n")
  134. if err := quotePrintEncode(&buf, text); err != nil {
  135. t.Fatal("quotePrintEncode: ", err)
  136. }
  137. if b := buf.Bytes(); !bytes.Equal(b, expected) {
  138. t.Errorf("quotedPrintEncode generated incorrect results: %#q != %#q", b, expected)
  139. }
  140. }
  141. func Benchmark_quotedPrintEncode(b *testing.B) {
  142. text := []byte("Dear reader!\n\n" +
  143. "This is a test email to try and capture some of the corner cases that exist within\n" +
  144. "the quoted-printable encoding.\n" +
  145. "There are some wacky parts like =, and this input assumes UNIX line breaks so\r\n" +
  146. "it can come out a little weird. Also, we need to support unicode so here's a fish: 🐟\n")
  147. for i := 0; i <= b.N; i++ {
  148. if err := quotePrintEncode(ioutil.Discard, text); err != nil {
  149. panic(err)
  150. }
  151. }
  152. }
  153. func Benchmark_base64Wrap(b *testing.B) {
  154. // Reasonable base case; 128K random bytes
  155. file := make([]byte, 128*1024)
  156. if _, err := rand.Read(file); err != nil {
  157. panic(err)
  158. }
  159. for i := 0; i <= b.N; i++ {
  160. base64Wrap(ioutil.Discard, file)
  161. }
  162. }