...

Source file src/github.com/go-mail/mail/message_test.go

Documentation: github.com/go-mail/mail

     1  package mail
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/base64"
     6  	"io"
     7  	"io/ioutil"
     8  	"path/filepath"
     9  	"regexp"
    10  	"strconv"
    11  	"strings"
    12  	"testing"
    13  	"time"
    14  )
    15  
    16  func init() {
    17  	now = func() time.Time {
    18  		return time.Date(2014, 06, 25, 17, 46, 0, 0, time.UTC)
    19  	}
    20  }
    21  
    22  type message struct {
    23  	from    string
    24  	to      []string
    25  	content string
    26  }
    27  
    28  func TestMessage(t *testing.T) {
    29  	m := NewMessage()
    30  	m.SetAddressHeader("From", "from@example.com", "Señor From")
    31  	m.SetHeader("To", m.FormatAddress("to@example.com", "Señor To"), "tobis@example.com")
    32  	m.SetAddressHeader("Cc", "cc@example.com", "A, B")
    33  	m.SetAddressHeader("X-To", "ccbis@example.com", "à, b")
    34  	m.SetDateHeader("X-Date", now())
    35  	m.SetHeader("X-Date-2", m.FormatDate(now()))
    36  	m.SetHeader("Subject", "¡Hola, señor!")
    37  	m.SetHeaders(map[string][]string{
    38  		"X-Headers": {"Test", "Café"},
    39  	})
    40  	m.SetBody("text/plain", "¡Hola, señor!")
    41  
    42  	want := &message{
    43  		from: "from@example.com",
    44  		to: []string{
    45  			"to@example.com",
    46  			"tobis@example.com",
    47  			"cc@example.com",
    48  		},
    49  		content: "From: =?UTF-8?q?Se=C3=B1or_From?= <from@example.com>\r\n" +
    50  			"To: =?UTF-8?q?Se=C3=B1or_To?= <to@example.com>, tobis@example.com\r\n" +
    51  			"Cc: \"A, B\" <cc@example.com>\r\n" +
    52  			"X-To: =?UTF-8?b?w6AsIGI=?= <ccbis@example.com>\r\n" +
    53  			"X-Date: Wed, 25 Jun 2014 17:46:00 +0000\r\n" +
    54  			"X-Date-2: Wed, 25 Jun 2014 17:46:00 +0000\r\n" +
    55  			"X-Headers: Test, =?UTF-8?q?Caf=C3=A9?=\r\n" +
    56  			"Subject: =?UTF-8?q?=C2=A1Hola,_se=C3=B1or!?=\r\n" +
    57  			"Content-Type: text/plain; charset=UTF-8\r\n" +
    58  			"Content-Transfer-Encoding: quoted-printable\r\n" +
    59  			"\r\n" +
    60  			"=C2=A1Hola, se=C3=B1or!",
    61  	}
    62  
    63  	testMessage(t, m, 0, want)
    64  }
    65  
    66  func TestCustomMessage(t *testing.T) {
    67  	m := NewMessage(SetCharset("ISO-8859-1"), SetEncoding(Base64))
    68  	m.SetHeaders(map[string][]string{
    69  		"From":    {"from@example.com"},
    70  		"To":      {"to@example.com"},
    71  		"Subject": {"Café"},
    72  	})
    73  	m.SetBody("text/html", "¡Hola, señor!")
    74  
    75  	want := &message{
    76  		from: "from@example.com",
    77  		to:   []string{"to@example.com"},
    78  		content: "From: from@example.com\r\n" +
    79  			"To: to@example.com\r\n" +
    80  			"Subject: =?ISO-8859-1?b?Q2Fmw6k=?=\r\n" +
    81  			"Content-Type: text/html; charset=ISO-8859-1\r\n" +
    82  			"Content-Transfer-Encoding: base64\r\n" +
    83  			"\r\n" +
    84  			"wqFIb2xhLCBzZcOxb3Ih",
    85  	}
    86  
    87  	testMessage(t, m, 0, want)
    88  }
    89  
    90  func TestUnencodedMessage(t *testing.T) {
    91  	m := NewMessage(SetEncoding(Unencoded))
    92  	m.SetHeaders(map[string][]string{
    93  		"From":    {"from@example.com"},
    94  		"To":      {"to@example.com"},
    95  		"Subject": {"Café"},
    96  	})
    97  	m.SetBody("text/html", "¡Hola, señor!")
    98  
    99  	want := &message{
   100  		from: "from@example.com",
   101  		to:   []string{"to@example.com"},
   102  		content: "From: from@example.com\r\n" +
   103  			"To: to@example.com\r\n" +
   104  			"Subject: =?UTF-8?q?Caf=C3=A9?=\r\n" +
   105  			"Content-Type: text/html; charset=UTF-8\r\n" +
   106  			"Content-Transfer-Encoding: 8bit\r\n" +
   107  			"\r\n" +
   108  			"¡Hola, señor!",
   109  	}
   110  
   111  	testMessage(t, m, 0, want)
   112  }
   113  
   114  func TestRecipients(t *testing.T) {
   115  	m := NewMessage()
   116  	m.SetHeaders(map[string][]string{
   117  		"From":    {"from@example.com"},
   118  		"To":      {"to@example.com"},
   119  		"Cc":      {"cc@example.com"},
   120  		"Bcc":     {"bcc1@example.com", "bcc2@example.com"},
   121  		"Subject": {"Hello!"},
   122  	})
   123  	m.SetBody("text/plain", "Test message")
   124  
   125  	want := &message{
   126  		from: "from@example.com",
   127  		to:   []string{"to@example.com", "cc@example.com", "bcc1@example.com", "bcc2@example.com"},
   128  		content: "From: from@example.com\r\n" +
   129  			"To: to@example.com\r\n" +
   130  			"Cc: cc@example.com\r\n" +
   131  			"Subject: Hello!\r\n" +
   132  			"Content-Type: text/plain; charset=UTF-8\r\n" +
   133  			"Content-Transfer-Encoding: quoted-printable\r\n" +
   134  			"\r\n" +
   135  			"Test message",
   136  	}
   137  
   138  	testMessage(t, m, 0, want)
   139  }
   140  
   141  func TestAlternative(t *testing.T) {
   142  	m := NewMessage()
   143  	m.SetHeader("From", "from@example.com")
   144  	m.SetHeader("To", "to@example.com")
   145  	m.SetBody("text/plain", "¡Hola, señor!")
   146  	m.AddAlternative("text/html", "¡<b>Hola</b>, <i>señor</i>!</h1>")
   147  
   148  	want := &message{
   149  		from: "from@example.com",
   150  		to:   []string{"to@example.com"},
   151  		content: "From: from@example.com\r\n" +
   152  			"To: to@example.com\r\n" +
   153  			"Content-Type: multipart/alternative;\r\n" +
   154  			" boundary=_BOUNDARY_1_\r\n" +
   155  			"\r\n" +
   156  			"--_BOUNDARY_1_\r\n" +
   157  			"Content-Type: text/plain; charset=UTF-8\r\n" +
   158  			"Content-Transfer-Encoding: quoted-printable\r\n" +
   159  			"\r\n" +
   160  			"=C2=A1Hola, se=C3=B1or!\r\n" +
   161  			"--_BOUNDARY_1_\r\n" +
   162  			"Content-Type: text/html; charset=UTF-8\r\n" +
   163  			"Content-Transfer-Encoding: quoted-printable\r\n" +
   164  			"\r\n" +
   165  			"=C2=A1<b>Hola</b>, <i>se=C3=B1or</i>!</h1>\r\n" +
   166  			"--_BOUNDARY_1_--\r\n",
   167  	}
   168  
   169  	testMessage(t, m, 1, want)
   170  }
   171  
   172  func TestPartSetting(t *testing.T) {
   173  	m := NewMessage()
   174  	m.SetHeader("From", "from@example.com")
   175  	m.SetHeader("To", "to@example.com")
   176  	m.SetBody("text/plain; format=flowed", "¡Hola, señor!", SetPartEncoding(Unencoded))
   177  	m.AddAlternative("text/html", "¡<b>Hola</b>, <i>señor</i>!</h1>")
   178  
   179  	want := &message{
   180  		from: "from@example.com",
   181  		to:   []string{"to@example.com"},
   182  		content: "From: from@example.com\r\n" +
   183  			"To: to@example.com\r\n" +
   184  			"Content-Type: multipart/alternative;\r\n" +
   185  			" boundary=_BOUNDARY_1_\r\n" +
   186  			"\r\n" +
   187  			"--_BOUNDARY_1_\r\n" +
   188  			"Content-Type: text/plain; format=flowed; charset=UTF-8\r\n" +
   189  			"Content-Transfer-Encoding: 8bit\r\n" +
   190  			"\r\n" +
   191  			"¡Hola, señor!\r\n" +
   192  			"--_BOUNDARY_1_\r\n" +
   193  			"Content-Type: text/html; charset=UTF-8\r\n" +
   194  			"Content-Transfer-Encoding: quoted-printable\r\n" +
   195  			"\r\n" +
   196  			"=C2=A1<b>Hola</b>, <i>se=C3=B1or</i>!</h1>\r\n" +
   197  			"--_BOUNDARY_1_--\r\n",
   198  	}
   199  
   200  	testMessage(t, m, 1, want)
   201  }
   202  
   203  func TestPartSettingWithCustomBoundary(t *testing.T) {
   204  	m := NewMessage()
   205  	m.SetBoundary("lalalaDaiMne3Ryblya")
   206  	m.SetHeader("From", "from@example.com")
   207  	m.SetHeader("To", "to@example.com")
   208  	m.SetBody("text/plain; format=flowed", "¡Hola, señor!", SetPartEncoding(Unencoded))
   209  	m.AddAlternative("text/html", "¡<b>Hola</b>, <i>señor</i>!</h1>")
   210  
   211  	want := &message{
   212  		from: "from@example.com",
   213  		to:   []string{"to@example.com"},
   214  		content: "From: from@example.com\r\n" +
   215  			"To: to@example.com\r\n" +
   216  			"Content-Type: multipart/alternative;\r\n" +
   217  			" boundary=lalalaDaiMne3Ryblya\r\n" +
   218  			"\r\n" +
   219  			"--lalalaDaiMne3Ryblya\r\n" +
   220  			"Content-Type: text/plain; format=flowed; charset=UTF-8\r\n" +
   221  			"Content-Transfer-Encoding: 8bit\r\n" +
   222  			"\r\n" +
   223  			"¡Hola, señor!\r\n" +
   224  			"--lalalaDaiMne3Ryblya\r\n" +
   225  			"Content-Type: text/html; charset=UTF-8\r\n" +
   226  			"Content-Transfer-Encoding: quoted-printable\r\n" +
   227  			"\r\n" +
   228  			"=C2=A1<b>Hola</b>, <i>se=C3=B1or</i>!</h1>\r\n" +
   229  			"--lalalaDaiMne3Ryblya--\r\n",
   230  	}
   231  
   232  	testMessage(t, m, 1, want)
   233  }
   234  
   235  func TestBodyWriter(t *testing.T) {
   236  	m := NewMessage()
   237  	m.SetHeader("From", "from@example.com")
   238  	m.SetHeader("To", "to@example.com")
   239  	m.SetBodyWriter("text/plain", func(w io.Writer) error {
   240  		_, err := w.Write([]byte("Test message"))
   241  		return err
   242  	})
   243  	m.AddAlternativeWriter("text/html", func(w io.Writer) error {
   244  		_, err := w.Write([]byte("Test HTML"))
   245  		return err
   246  	})
   247  
   248  	want := &message{
   249  		from: "from@example.com",
   250  		to:   []string{"to@example.com"},
   251  		content: "From: from@example.com\r\n" +
   252  			"To: to@example.com\r\n" +
   253  			"Content-Type: multipart/alternative;\r\n" +
   254  			" boundary=_BOUNDARY_1_\r\n" +
   255  			"\r\n" +
   256  			"--_BOUNDARY_1_\r\n" +
   257  			"Content-Type: text/plain; charset=UTF-8\r\n" +
   258  			"Content-Transfer-Encoding: quoted-printable\r\n" +
   259  			"\r\n" +
   260  			"Test message\r\n" +
   261  			"--_BOUNDARY_1_\r\n" +
   262  			"Content-Type: text/html; charset=UTF-8\r\n" +
   263  			"Content-Transfer-Encoding: quoted-printable\r\n" +
   264  			"\r\n" +
   265  			"Test HTML\r\n" +
   266  			"--_BOUNDARY_1_--\r\n",
   267  	}
   268  
   269  	testMessage(t, m, 1, want)
   270  }
   271  
   272  func TestAttachmentReader(t *testing.T) {
   273  	m := NewMessage()
   274  	m.SetHeader("From", "from@example.com")
   275  	m.SetHeader("To", "to@example.com")
   276  
   277  	var b bytes.Buffer
   278  	b.Write([]byte("Test file"))
   279  	m.AttachReader("file.txt", &b)
   280  
   281  	want := &message{
   282  		from: "from@example.com",
   283  		to:   []string{"to@example.com"},
   284  		content: "From: from@example.com\r\n" +
   285  			"To: to@example.com\r\n" +
   286  			"Content-Type: text/plain; charset=utf-8; name=\"file.txt\"\r\n" +
   287  			"Content-Disposition: attachment; filename=\"file.txt\"\r\n" +
   288  			"Content-Transfer-Encoding: base64\r\n" +
   289  			"\r\n" +
   290  			base64.StdEncoding.EncodeToString([]byte("Test file")),
   291  	}
   292  
   293  	testMessage(t, m, 0, want)
   294  }
   295  
   296  func TestAttachmentOnly(t *testing.T) {
   297  	m := NewMessage()
   298  	m.SetHeader("From", "from@example.com")
   299  	m.SetHeader("To", "to@example.com")
   300  	m.Attach(mockCopyFile("/tmp/test.pdf"))
   301  
   302  	want := &message{
   303  		from: "from@example.com",
   304  		to:   []string{"to@example.com"},
   305  		content: "From: from@example.com\r\n" +
   306  			"To: to@example.com\r\n" +
   307  			"Content-Type: application/pdf; name=\"test.pdf\"\r\n" +
   308  			"Content-Disposition: attachment; filename=\"test.pdf\"\r\n" +
   309  			"Content-Transfer-Encoding: base64\r\n" +
   310  			"\r\n" +
   311  			base64.StdEncoding.EncodeToString([]byte("Content of test.pdf")),
   312  	}
   313  
   314  	testMessage(t, m, 0, want)
   315  }
   316  
   317  func TestAttachment(t *testing.T) {
   318  	m := NewMessage()
   319  	m.SetHeader("From", "from@example.com")
   320  	m.SetHeader("To", "to@example.com")
   321  	m.SetBody("text/plain", "Test")
   322  	m.Attach(mockCopyFile("/tmp/test.pdf"))
   323  
   324  	want := &message{
   325  		from: "from@example.com",
   326  		to:   []string{"to@example.com"},
   327  		content: "From: from@example.com\r\n" +
   328  			"To: to@example.com\r\n" +
   329  			"Content-Type: multipart/mixed;\r\n" +
   330  			" boundary=_BOUNDARY_1_\r\n" +
   331  			"\r\n" +
   332  			"--_BOUNDARY_1_\r\n" +
   333  			"Content-Type: text/plain; charset=UTF-8\r\n" +
   334  			"Content-Transfer-Encoding: quoted-printable\r\n" +
   335  			"\r\n" +
   336  			"Test\r\n" +
   337  			"--_BOUNDARY_1_\r\n" +
   338  			"Content-Type: application/pdf; name=\"test.pdf\"\r\n" +
   339  			"Content-Disposition: attachment; filename=\"test.pdf\"\r\n" +
   340  			"Content-Transfer-Encoding: base64\r\n" +
   341  			"\r\n" +
   342  			base64.StdEncoding.EncodeToString([]byte("Content of test.pdf")) + "\r\n" +
   343  			"--_BOUNDARY_1_--\r\n",
   344  	}
   345  
   346  	testMessage(t, m, 1, want)
   347  }
   348  
   349  func TestRename(t *testing.T) {
   350  	m := NewMessage()
   351  	m.SetHeader("From", "from@example.com")
   352  	m.SetHeader("To", "to@example.com")
   353  	m.SetBody("text/plain", "Test")
   354  	name, copy := mockCopyFile("/tmp/test.pdf")
   355  	rename := Rename("another.pdf")
   356  	m.Attach(name, copy, rename)
   357  
   358  	want := &message{
   359  		from: "from@example.com",
   360  		to:   []string{"to@example.com"},
   361  		content: "From: from@example.com\r\n" +
   362  			"To: to@example.com\r\n" +
   363  			"Content-Type: multipart/mixed;\r\n" +
   364  			" boundary=_BOUNDARY_1_\r\n" +
   365  			"\r\n" +
   366  			"--_BOUNDARY_1_\r\n" +
   367  			"Content-Type: text/plain; charset=UTF-8\r\n" +
   368  			"Content-Transfer-Encoding: quoted-printable\r\n" +
   369  			"\r\n" +
   370  			"Test\r\n" +
   371  			"--_BOUNDARY_1_\r\n" +
   372  			"Content-Type: application/pdf; name=\"another.pdf\"\r\n" +
   373  			"Content-Disposition: attachment; filename=\"another.pdf\"\r\n" +
   374  			"Content-Transfer-Encoding: base64\r\n" +
   375  			"\r\n" +
   376  			base64.StdEncoding.EncodeToString([]byte("Content of test.pdf")) + "\r\n" +
   377  			"--_BOUNDARY_1_--\r\n",
   378  	}
   379  
   380  	testMessage(t, m, 1, want)
   381  }
   382  
   383  func TestAttachmentsOnly(t *testing.T) {
   384  	m := NewMessage()
   385  	m.SetHeader("From", "from@example.com")
   386  	m.SetHeader("To", "to@example.com")
   387  	m.Attach(mockCopyFile("/tmp/test.pdf"))
   388  	m.Attach(mockCopyFile("/tmp/test.zip"))
   389  
   390  	want := &message{
   391  		from: "from@example.com",
   392  		to:   []string{"to@example.com"},
   393  		content: "From: from@example.com\r\n" +
   394  			"To: to@example.com\r\n" +
   395  			"Content-Type: multipart/mixed;\r\n" +
   396  			" boundary=_BOUNDARY_1_\r\n" +
   397  			"\r\n" +
   398  			"--_BOUNDARY_1_\r\n" +
   399  			"Content-Type: application/pdf; name=\"test.pdf\"\r\n" +
   400  			"Content-Disposition: attachment; filename=\"test.pdf\"\r\n" +
   401  			"Content-Transfer-Encoding: base64\r\n" +
   402  			"\r\n" +
   403  			base64.StdEncoding.EncodeToString([]byte("Content of test.pdf")) + "\r\n" +
   404  			"--_BOUNDARY_1_\r\n" +
   405  			"Content-Type: application/zip; name=\"test.zip\"\r\n" +
   406  			"Content-Disposition: attachment; filename=\"test.zip\"\r\n" +
   407  			"Content-Transfer-Encoding: base64\r\n" +
   408  			"\r\n" +
   409  			base64.StdEncoding.EncodeToString([]byte("Content of test.zip")) + "\r\n" +
   410  			"--_BOUNDARY_1_--\r\n",
   411  	}
   412  
   413  	testMessage(t, m, 1, want)
   414  }
   415  
   416  func TestAttachments(t *testing.T) {
   417  	m := NewMessage()
   418  	m.SetHeader("From", "from@example.com")
   419  	m.SetHeader("To", "to@example.com")
   420  	m.SetBody("text/plain", "Test")
   421  	m.Attach(mockCopyFile("/tmp/test.pdf"))
   422  	m.Attach(mockCopyFile("/tmp/test.zip"))
   423  
   424  	want := &message{
   425  		from: "from@example.com",
   426  		to:   []string{"to@example.com"},
   427  		content: "From: from@example.com\r\n" +
   428  			"To: to@example.com\r\n" +
   429  			"Content-Type: multipart/mixed;\r\n" +
   430  			" boundary=_BOUNDARY_1_\r\n" +
   431  			"\r\n" +
   432  			"--_BOUNDARY_1_\r\n" +
   433  			"Content-Type: text/plain; charset=UTF-8\r\n" +
   434  			"Content-Transfer-Encoding: quoted-printable\r\n" +
   435  			"\r\n" +
   436  			"Test\r\n" +
   437  			"--_BOUNDARY_1_\r\n" +
   438  			"Content-Type: application/pdf; name=\"test.pdf\"\r\n" +
   439  			"Content-Disposition: attachment; filename=\"test.pdf\"\r\n" +
   440  			"Content-Transfer-Encoding: base64\r\n" +
   441  			"\r\n" +
   442  			base64.StdEncoding.EncodeToString([]byte("Content of test.pdf")) + "\r\n" +
   443  			"--_BOUNDARY_1_\r\n" +
   444  			"Content-Type: application/zip; name=\"test.zip\"\r\n" +
   445  			"Content-Disposition: attachment; filename=\"test.zip\"\r\n" +
   446  			"Content-Transfer-Encoding: base64\r\n" +
   447  			"\r\n" +
   448  			base64.StdEncoding.EncodeToString([]byte("Content of test.zip")) + "\r\n" +
   449  			"--_BOUNDARY_1_--\r\n",
   450  	}
   451  
   452  	testMessage(t, m, 1, want)
   453  }
   454  
   455  func TestEmbeddedReader(t *testing.T) {
   456  	m := NewMessage()
   457  	m.SetHeader("From", "from@example.com")
   458  	m.SetHeader("To", "to@example.com")
   459  
   460  	var b bytes.Buffer
   461  	b.Write([]byte("Test file"))
   462  	m.EmbedReader("file.txt", &b)
   463  
   464  	want := &message{
   465  		from: "from@example.com",
   466  		to:   []string{"to@example.com"},
   467  		content: "From: from@example.com\r\n" +
   468  			"To: to@example.com\r\n" +
   469  			"Content-Type: text/plain; charset=utf-8; name=\"file.txt\"\r\n" +
   470  			"Content-Transfer-Encoding: base64\r\n" +
   471  			"Content-Disposition: inline; filename=\"file.txt\"\r\n" +
   472  			"Content-ID: <file.txt>\r\n" +
   473  			"\r\n" +
   474  			base64.StdEncoding.EncodeToString([]byte("Test file")),
   475  	}
   476  
   477  	testMessage(t, m, 0, want)
   478  }
   479  
   480  func TestEmbedded(t *testing.T) {
   481  	m := NewMessage()
   482  	m.SetHeader("From", "from@example.com")
   483  	m.SetHeader("To", "to@example.com")
   484  	m.Embed(mockCopyFileWithHeader(m, "image1.jpg", map[string][]string{"Content-ID": {"<test-content-id>"}}))
   485  	m.Embed(mockCopyFile("image2.jpg"))
   486  	m.SetBody("text/plain", "Test")
   487  
   488  	want := &message{
   489  		from: "from@example.com",
   490  		to:   []string{"to@example.com"},
   491  		content: "From: from@example.com\r\n" +
   492  			"To: to@example.com\r\n" +
   493  			"Content-Type: multipart/related;\r\n" +
   494  			" boundary=_BOUNDARY_1_\r\n" +
   495  			"\r\n" +
   496  			"--_BOUNDARY_1_\r\n" +
   497  			"Content-Type: text/plain; charset=UTF-8\r\n" +
   498  			"Content-Transfer-Encoding: quoted-printable\r\n" +
   499  			"\r\n" +
   500  			"Test\r\n" +
   501  			"--_BOUNDARY_1_\r\n" +
   502  			"Content-Type: image/jpeg; name=\"image1.jpg\"\r\n" +
   503  			"Content-Disposition: inline; filename=\"image1.jpg\"\r\n" +
   504  			"Content-ID: <test-content-id>\r\n" +
   505  			"Content-Transfer-Encoding: base64\r\n" +
   506  			"\r\n" +
   507  			base64.StdEncoding.EncodeToString([]byte("Content of image1.jpg")) + "\r\n" +
   508  			"--_BOUNDARY_1_\r\n" +
   509  			"Content-Type: image/jpeg; name=\"image2.jpg\"\r\n" +
   510  			"Content-Disposition: inline; filename=\"image2.jpg\"\r\n" +
   511  			"Content-ID: <image2.jpg>\r\n" +
   512  			"Content-Transfer-Encoding: base64\r\n" +
   513  			"\r\n" +
   514  			base64.StdEncoding.EncodeToString([]byte("Content of image2.jpg")) + "\r\n" +
   515  			"--_BOUNDARY_1_--\r\n",
   516  	}
   517  
   518  	testMessage(t, m, 1, want)
   519  }
   520  
   521  func TestFullMessage(t *testing.T) {
   522  	m := NewMessage()
   523  	m.SetHeader("From", "from@example.com")
   524  	m.SetHeader("To", "to@example.com")
   525  	m.SetBody("text/plain", "¡Hola, señor!")
   526  	m.AddAlternative("text/html", "¡<b>Hola</b>, <i>señor</i>!</h1>")
   527  	m.Attach(mockCopyFile("test.pdf"))
   528  	m.Embed(mockCopyFile("image.jpg"))
   529  
   530  	want := &message{
   531  		from: "from@example.com",
   532  		to:   []string{"to@example.com"},
   533  		content: "From: from@example.com\r\n" +
   534  			"To: to@example.com\r\n" +
   535  			"Content-Type: multipart/mixed;\r\n" +
   536  			" boundary=_BOUNDARY_1_\r\n" +
   537  			"\r\n" +
   538  			"--_BOUNDARY_1_\r\n" +
   539  			"Content-Type: multipart/related;\r\n" +
   540  			" boundary=_BOUNDARY_2_\r\n" +
   541  			"\r\n" +
   542  			"--_BOUNDARY_2_\r\n" +
   543  			"Content-Type: multipart/alternative;\r\n" +
   544  			" boundary=_BOUNDARY_3_\r\n" +
   545  			"\r\n" +
   546  			"--_BOUNDARY_3_\r\n" +
   547  			"Content-Type: text/plain; charset=UTF-8\r\n" +
   548  			"Content-Transfer-Encoding: quoted-printable\r\n" +
   549  			"\r\n" +
   550  			"=C2=A1Hola, se=C3=B1or!\r\n" +
   551  			"--_BOUNDARY_3_\r\n" +
   552  			"Content-Type: text/html; charset=UTF-8\r\n" +
   553  			"Content-Transfer-Encoding: quoted-printable\r\n" +
   554  			"\r\n" +
   555  			"=C2=A1<b>Hola</b>, <i>se=C3=B1or</i>!</h1>\r\n" +
   556  			"--_BOUNDARY_3_--\r\n" +
   557  			"\r\n" +
   558  			"--_BOUNDARY_2_\r\n" +
   559  			"Content-Type: image/jpeg; name=\"image.jpg\"\r\n" +
   560  			"Content-Disposition: inline; filename=\"image.jpg\"\r\n" +
   561  			"Content-ID: <image.jpg>\r\n" +
   562  			"Content-Transfer-Encoding: base64\r\n" +
   563  			"\r\n" +
   564  			base64.StdEncoding.EncodeToString([]byte("Content of image.jpg")) + "\r\n" +
   565  			"--_BOUNDARY_2_--\r\n" +
   566  			"\r\n" +
   567  			"--_BOUNDARY_1_\r\n" +
   568  			"Content-Type: application/pdf; name=\"test.pdf\"\r\n" +
   569  			"Content-Disposition: attachment; filename=\"test.pdf\"\r\n" +
   570  			"Content-Transfer-Encoding: base64\r\n" +
   571  			"\r\n" +
   572  			base64.StdEncoding.EncodeToString([]byte("Content of test.pdf")) + "\r\n" +
   573  			"--_BOUNDARY_1_--\r\n",
   574  	}
   575  
   576  	testMessage(t, m, 3, want)
   577  
   578  	want = &message{
   579  		from: "from@example.com",
   580  		to:   []string{"to@example.com"},
   581  		content: "From: from@example.com\r\n" +
   582  			"To: to@example.com\r\n" +
   583  			"Content-Type: text/plain; charset=UTF-8\r\n" +
   584  			"Content-Transfer-Encoding: quoted-printable\r\n" +
   585  			"\r\n" +
   586  			"Test reset",
   587  	}
   588  	m.Reset()
   589  	m.SetHeader("From", "from@example.com")
   590  	m.SetHeader("To", "to@example.com")
   591  	m.SetBody("text/plain", "Test reset")
   592  	testMessage(t, m, 0, want)
   593  }
   594  
   595  func TestQpLineLength(t *testing.T) {
   596  	m := NewMessage()
   597  	m.SetHeader("From", "from@example.com")
   598  	m.SetHeader("To", "to@example.com")
   599  	m.SetBody("text/plain",
   600  		strings.Repeat("0", 76)+"\r\n"+
   601  			strings.Repeat("0", 75)+"à\r\n"+
   602  			strings.Repeat("0", 74)+"à\r\n"+
   603  			strings.Repeat("0", 73)+"à\r\n"+
   604  			strings.Repeat("0", 72)+"à\r\n"+
   605  			strings.Repeat("0", 75)+"\r\n"+
   606  			strings.Repeat("0", 76)+"\n")
   607  
   608  	want := &message{
   609  		from: "from@example.com",
   610  		to:   []string{"to@example.com"},
   611  		content: "From: from@example.com\r\n" +
   612  			"To: to@example.com\r\n" +
   613  			"Content-Type: text/plain; charset=UTF-8\r\n" +
   614  			"Content-Transfer-Encoding: quoted-printable\r\n" +
   615  			"\r\n" +
   616  			strings.Repeat("0", 75) + "=\r\n0\r\n" +
   617  			strings.Repeat("0", 75) + "=\r\n=C3=A0\r\n" +
   618  			strings.Repeat("0", 74) + "=\r\n=C3=A0\r\n" +
   619  			strings.Repeat("0", 73) + "=\r\n=C3=A0\r\n" +
   620  			strings.Repeat("0", 72) + "=C3=\r\n=A0\r\n" +
   621  			strings.Repeat("0", 75) + "\r\n" +
   622  			strings.Repeat("0", 75) + "=\r\n0\r\n",
   623  	}
   624  
   625  	testMessage(t, m, 0, want)
   626  }
   627  
   628  func TestBase64LineLength(t *testing.T) {
   629  	m := NewMessage(SetCharset("UTF-8"), SetEncoding(Base64))
   630  	m.SetHeader("From", "from@example.com")
   631  	m.SetHeader("To", "to@example.com")
   632  	m.SetBody("text/plain", strings.Repeat("0", 58))
   633  
   634  	want := &message{
   635  		from: "from@example.com",
   636  		to:   []string{"to@example.com"},
   637  		content: "From: from@example.com\r\n" +
   638  			"To: to@example.com\r\n" +
   639  			"Content-Type: text/plain; charset=UTF-8\r\n" +
   640  			"Content-Transfer-Encoding: base64\r\n" +
   641  			"\r\n" +
   642  			strings.Repeat("MDAw", 19) + "\r\nMA==",
   643  	}
   644  
   645  	testMessage(t, m, 0, want)
   646  }
   647  
   648  func TestEmptyName(t *testing.T) {
   649  	m := NewMessage()
   650  	m.SetAddressHeader("From", "from@example.com", "")
   651  
   652  	want := &message{
   653  		from:    "from@example.com",
   654  		content: "From: from@example.com\r\n",
   655  	}
   656  
   657  	testMessage(t, m, 0, want)
   658  }
   659  
   660  func TestEmptyHeader(t *testing.T) {
   661  	m := NewMessage()
   662  	m.SetHeaders(map[string][]string{
   663  		"From":    {"from@example.com"},
   664  		"X-Empty": nil,
   665  	})
   666  
   667  	want := &message{
   668  		from: "from@example.com",
   669  		content: "From: from@example.com\r\n" +
   670  			"X-Empty:\r\n",
   671  	}
   672  
   673  	testMessage(t, m, 0, want)
   674  }
   675  
   676  func testMessage(t *testing.T, m *Message, bCount int, want *message) {
   677  	err := Send(stubSendMail(t, bCount, want), m)
   678  	if err != nil {
   679  		t.Error(err)
   680  	}
   681  }
   682  
   683  func stubSendMail(t *testing.T, bCount int, want *message) SendFunc {
   684  	return func(from string, to []string, m io.WriterTo) error {
   685  		if from != want.from {
   686  			t.Fatalf("Invalid from, got %q, want %q", from, want.from)
   687  		}
   688  
   689  		if len(to) != len(want.to) {
   690  			t.Fatalf("Invalid recipient count, \ngot %d: %q\nwant %d: %q",
   691  				len(to), to,
   692  				len(want.to), want.to,
   693  			)
   694  		}
   695  		for i := range want.to {
   696  			if to[i] != want.to[i] {
   697  				t.Fatalf("Invalid recipient, got %q, want %q",
   698  					to[i], want.to[i],
   699  				)
   700  			}
   701  		}
   702  
   703  		buf := new(bytes.Buffer)
   704  		_, err := m.WriteTo(buf)
   705  		if err != nil {
   706  			t.Error(err)
   707  		}
   708  		got := buf.String()
   709  		wantMsg := string("MIME-Version: 1.0\r\n" +
   710  			"Date: Wed, 25 Jun 2014 17:46:00 +0000\r\n" +
   711  			want.content)
   712  		if bCount > 0 {
   713  			boundaries := getBoundaries(t, bCount, got)
   714  			for i, b := range boundaries {
   715  				wantMsg = strings.Replace(wantMsg, "_BOUNDARY_"+strconv.Itoa(i+1)+"_", b, -1)
   716  			}
   717  		}
   718  
   719  		compareBodies(t, got, wantMsg)
   720  
   721  		return nil
   722  	}
   723  }
   724  
   725  func compareBodies(t *testing.T, got, want string) {
   726  	// We cannot do a simple comparison since the ordering of headers' fields
   727  	// is random.
   728  	gotLines := strings.Split(got, "\r\n")
   729  	wantLines := strings.Split(want, "\r\n")
   730  
   731  	// We only test for too many lines, missing lines are tested after
   732  	if len(gotLines) > len(wantLines) {
   733  		t.Fatalf("Message has too many lines, \ngot %d:\n%s\nwant %d:\n%s", len(gotLines), got, len(wantLines), want)
   734  	}
   735  
   736  	isInHeader := true
   737  	headerStart := 0
   738  	for i, line := range wantLines {
   739  		if line == gotLines[i] {
   740  			if line == "" {
   741  				isInHeader = false
   742  			} else if !isInHeader && len(line) > 2 && line[:2] == "--" {
   743  				isInHeader = true
   744  				headerStart = i + 1
   745  			}
   746  			continue
   747  		}
   748  
   749  		if !isInHeader {
   750  			missingLine(t, line, got, want)
   751  		}
   752  
   753  		isMissing := true
   754  		for j := headerStart; j < len(gotLines); j++ {
   755  			if gotLines[j] == "" {
   756  				break
   757  			}
   758  			if gotLines[j] == line {
   759  				isMissing = false
   760  				break
   761  			}
   762  		}
   763  		if isMissing {
   764  			missingLine(t, line, got, want)
   765  		}
   766  	}
   767  }
   768  
   769  func missingLine(t *testing.T, line, got, want string) {
   770  	t.Fatalf("Missing line %q\ngot:\n%s\nwant:\n%s", line, got, want)
   771  }
   772  
   773  func getBoundaries(t *testing.T, count int, m string) []string {
   774  	if matches := boundaryRegExp.FindAllStringSubmatch(m, count); matches != nil {
   775  		boundaries := make([]string, count)
   776  		for i, match := range matches {
   777  			boundaries[i] = match[1]
   778  		}
   779  		return boundaries
   780  	}
   781  
   782  	t.Fatal("Boundary not found in body")
   783  	return []string{""}
   784  }
   785  
   786  var boundaryRegExp = regexp.MustCompile("boundary=(\\w+)")
   787  
   788  func mockCopyFile(name string) (string, FileSetting) {
   789  	return name, SetCopyFunc(func(w io.Writer) error {
   790  		_, err := w.Write([]byte("Content of " + filepath.Base(name)))
   791  		return err
   792  	})
   793  }
   794  
   795  func mockCopyFileWithHeader(m *Message, name string, h map[string][]string) (string, FileSetting, FileSetting) {
   796  	name, f := mockCopyFile(name)
   797  	return name, f, SetHeader(h)
   798  }
   799  
   800  func BenchmarkFull(b *testing.B) {
   801  	discardFunc := SendFunc(func(from string, to []string, m io.WriterTo) error {
   802  		_, err := m.WriteTo(ioutil.Discard)
   803  		return err
   804  	})
   805  
   806  	m := NewMessage()
   807  	b.ResetTimer()
   808  	for n := 0; n < b.N; n++ {
   809  		m.SetAddressHeader("From", "from@example.com", "Señor From")
   810  		m.SetHeaders(map[string][]string{
   811  			"To":      {"to@example.com"},
   812  			"Cc":      {"cc@example.com"},
   813  			"Bcc":     {"bcc1@example.com", "bcc2@example.com"},
   814  			"Subject": {"¡Hola, señor!"},
   815  		})
   816  		m.SetBody("text/plain", "¡Hola, señor!")
   817  		m.AddAlternative("text/html", "<p>¡Hola, señor!</p>")
   818  		m.Attach(mockCopyFile("benchmark.txt"))
   819  		m.Embed(mockCopyFile("benchmark.jpg"))
   820  
   821  		if err := Send(discardFunc, m); err != nil {
   822  			panic(err)
   823  		}
   824  		m.Reset()
   825  	}
   826  }
   827  

View as plain text