github.com/haalcala/mattermost-server-change-repo@v0.0.0-20210713015153-16753fbeee5f/services/mailservice/mail_test.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See LICENSE.txt for license information.
     3  
     4  package mailservice
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"io"
    10  	"io/ioutil"
    11  	"net"
    12  	"net/mail"
    13  	"net/smtp"
    14  	"os"
    15  	"strings"
    16  	"testing"
    17  	"time"
    18  
    19  	"github.com/stretchr/testify/assert"
    20  	"github.com/stretchr/testify/require"
    21  )
    22  
    23  func getConfig() *SMTPConfig {
    24  	server := os.Getenv("MM_EMAILSETTINGS_SMTPSERVER")
    25  	if server == "" {
    26  		server = "localhost"
    27  	}
    28  	port := os.Getenv("MM_EMAILSETTINGS_SMTPPORT")
    29  	if port == "" {
    30  		port = "10025"
    31  	}
    32  
    33  	return &SMTPConfig{
    34  		ConnectionSecurity:                "",
    35  		SkipServerCertificateVerification: false,
    36  		Hostname:                          "localhost",
    37  		ServerName:                        server,
    38  		Server:                            server,
    39  		Port:                              port,
    40  		ServerTimeout:                     10,
    41  		Username:                          "",
    42  		Password:                          "",
    43  		EnableSMTPAuth:                    false,
    44  		SendEmailNotifications:            true,
    45  		FeedbackName:                      "",
    46  		FeedbackEmail:                     "test@example.com",
    47  		ReplyToAddress:                    "test@example.com",
    48  	}
    49  }
    50  
    51  func TestMailConnectionFromConfig(t *testing.T) {
    52  	cfg := getConfig()
    53  
    54  	conn, err := ConnectToSMTPServer(cfg)
    55  	require.NoError(t, err, "Should connect to the SMTP Server %v", err)
    56  
    57  	_, err = NewSMTPClient(context.Background(), conn, cfg)
    58  
    59  	require.NoError(t, err, "Should get new SMTP client")
    60  
    61  	cfg.Server = "wrongServer"
    62  	cfg.Port = "553"
    63  
    64  	_, err = ConnectToSMTPServer(cfg)
    65  
    66  	require.Error(t, err, "Should not connect to the SMTP Server")
    67  }
    68  
    69  func TestMailConnectionAdvanced(t *testing.T) {
    70  	cfg := getConfig()
    71  
    72  	conn, err := ConnectToSMTPServerAdvanced(cfg)
    73  	require.NoError(t, err, "Should connect to the SMTP Server")
    74  	defer conn.Close()
    75  
    76  	_, err2 := NewSMTPClientAdvanced(context.Background(), conn, cfg)
    77  	require.NoError(t, err2, "Should get new SMTP client")
    78  
    79  	l, err3 := net.Listen("tcp", "localhost:") // emulate nc -l <random-port>
    80  	require.NoError(t, err3, "Should've open a network socket and listen")
    81  	defer l.Close()
    82  	cfg.Server = strings.Split(l.Addr().String(), ":")[0]
    83  	cfg.Port = strings.Split(l.Addr().String(), ":")[1]
    84  	cfg.ServerTimeout = 1
    85  
    86  	conn2, err := ConnectToSMTPServerAdvanced(cfg)
    87  	require.NoError(t, err, "Should connect to the SMTP Server")
    88  	defer conn2.Close()
    89  
    90  	ctx := context.Background()
    91  	ctx, cancel := context.WithTimeout(ctx, time.Second)
    92  	defer cancel()
    93  
    94  	_, err4 := NewSMTPClientAdvanced(
    95  		ctx,
    96  		conn2,
    97  		cfg,
    98  	)
    99  	require.Error(t, err4, "Should get a timeout get while creating a new SMTP client")
   100  	assert.Contains(t, err4.Error(), "unable to connect to the SMTP server")
   101  
   102  	cfg.Server = "wrongServer"
   103  	cfg.Port = "553"
   104  
   105  	_, err5 := ConnectToSMTPServerAdvanced(cfg)
   106  	require.Error(t, err5, "Should not connect to the SMTP Server")
   107  }
   108  
   109  func TestSendMailUsingConfig(t *testing.T) {
   110  	cfg := getConfig()
   111  
   112  	var emailTo = "test@example.com"
   113  	var emailSubject = "Testing this email"
   114  	var emailBody = "This is a test from autobot"
   115  	var emailCC = "test@example.com"
   116  
   117  	//Delete all the messages before check the sample email
   118  	DeleteMailBox(emailTo)
   119  
   120  	err2 := SendMailUsingConfig(emailTo, emailSubject, emailBody, cfg, true, emailCC)
   121  	require.NoError(t, err2, "Should connect to the SMTP Server")
   122  
   123  	//Check if the email was send to the right email address
   124  	var resultsMailbox JSONMessageHeaderInbucket
   125  	err3 := RetryInbucket(5, func() error {
   126  		var err error
   127  		resultsMailbox, err = GetMailBox(emailTo)
   128  		return err
   129  	})
   130  	if err3 != nil {
   131  		t.Log(err3)
   132  		t.Log("No email was received, maybe due load on the server. Skipping this verification")
   133  	} else {
   134  		if len(resultsMailbox) > 0 {
   135  			require.Contains(t, resultsMailbox[0].To[0], emailTo, "Wrong To: recipient")
   136  			resultsEmail, err := GetMessageFromMailbox(emailTo, resultsMailbox[0].ID)
   137  			require.NoError(t, err, "Could not get message from mailbox")
   138  			require.Contains(t, emailBody, resultsEmail.Body.Text, "Wrong received message %s", resultsEmail.Body.Text)
   139  		}
   140  	}
   141  }
   142  
   143  func TestSendMailWithEmbeddedFilesUsingConfig(t *testing.T) {
   144  	cfg := getConfig()
   145  
   146  	var emailTo = "test@example.com"
   147  	var emailSubject = "Testing this email"
   148  	var emailBody = "This is a test from autobot"
   149  	var emailCC = "test@example.com"
   150  
   151  	//Delete all the messages before check the sample email
   152  	DeleteMailBox(emailTo)
   153  
   154  	embeddedFiles := map[string]io.Reader{
   155  		"test1.png": bytes.NewReader([]byte("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")),
   156  		"test2.png": bytes.NewReader([]byte("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")),
   157  	}
   158  	err2 := SendMailWithEmbeddedFilesUsingConfig(emailTo, emailSubject, emailBody, embeddedFiles, cfg, true, emailCC)
   159  	require.NoError(t, err2, "Should connect to the SMTP Server")
   160  
   161  	//Check if the email was send to the right email address
   162  	var resultsMailbox JSONMessageHeaderInbucket
   163  	err3 := RetryInbucket(5, func() error {
   164  		var err error
   165  		resultsMailbox, err = GetMailBox(emailTo)
   166  		return err
   167  	})
   168  	if err3 != nil {
   169  		t.Log(err3)
   170  		t.Log("No email was received, maybe due load on the server. Skipping this verification")
   171  	} else {
   172  		if len(resultsMailbox) > 0 {
   173  			require.Contains(t, resultsMailbox[0].To[0], emailTo, "Wrong To: recipient")
   174  			resultsEmail, err := GetMessageFromMailbox(emailTo, resultsMailbox[0].ID)
   175  			require.NoError(t, err, "Could not get message from mailbox")
   176  			require.Contains(t, emailBody, resultsEmail.Body.Text, "Wrong received message %s", resultsEmail.Body.Text)
   177  			// Usign the message size because the inbucket API doesn't return embedded attachments through the API
   178  			require.Greater(t, resultsEmail.Size, 1500, "the file size should be more because the embedded attachemtns")
   179  		}
   180  	}
   181  }
   182  
   183  func TestSendMailUsingConfigAdvanced(t *testing.T) {
   184  	cfg := getConfig()
   185  
   186  	//Delete all the messages before check the sample email
   187  	DeleteMailBox("test2@example.com")
   188  
   189  	// create two files with the same name that will both be attached to the email
   190  	file1, err := ioutil.TempFile("", "*")
   191  	require.NoError(t, err)
   192  	defer os.Remove(file1.Name())
   193  	file1.Write([]byte("hello world"))
   194  	file1.Close()
   195  	file2, err := ioutil.TempFile("", "*")
   196  
   197  	require.NoError(t, err)
   198  	defer os.Remove(file2.Name())
   199  	file2.Write([]byte("foo bar"))
   200  	file2.Close()
   201  
   202  	embeddedFiles := map[string]io.Reader{
   203  		"test": bytes.NewReader([]byte("test data")),
   204  	}
   205  
   206  	headers := make(map[string]string)
   207  	headers["TestHeader"] = "TestValue"
   208  
   209  	mail := mailData{
   210  		mimeTo:        "test@example.com",
   211  		smtpTo:        "test2@example.com",
   212  		from:          mail.Address{Name: "Nobody", Address: "nobody@mattermost.com"},
   213  		replyTo:       mail.Address{Name: "ReplyTo", Address: "reply_to@mattermost.com"},
   214  		subject:       "Testing this email",
   215  		htmlBody:      "This is a test from autobot",
   216  		embeddedFiles: embeddedFiles,
   217  		mimeHeaders:   headers,
   218  	}
   219  
   220  	err = sendMailUsingConfigAdvanced(mail, cfg, true)
   221  	require.NoError(t, err, "Should connect to the STMP Server: %v", err)
   222  
   223  	//Check if the email was send to the right email address
   224  	var resultsMailbox JSONMessageHeaderInbucket
   225  	err = RetryInbucket(5, func() error {
   226  		var mailErr error
   227  		resultsMailbox, mailErr = GetMailBox(mail.smtpTo)
   228  		return mailErr
   229  	})
   230  	require.NoError(t, err, "No emails found for address %s. error: %v", mail.smtpTo, err)
   231  	require.NotEqual(t, len(resultsMailbox), 0)
   232  
   233  	require.Contains(t, resultsMailbox[0].To[0], mail.mimeTo, "Wrong To recipient")
   234  
   235  	resultsEmail, err := GetMessageFromMailbox(mail.smtpTo, resultsMailbox[0].ID)
   236  	require.NoError(t, err)
   237  
   238  	require.Contains(t, mail.htmlBody, resultsEmail.Body.Text, "Wrong received message")
   239  
   240  	// verify that the To header of the email message is set to the MIME recipient, even though we got it out of the SMTP recipient's email inbox
   241  	assert.Equal(t, mail.mimeTo, resultsEmail.Header["To"][0])
   242  
   243  	// verify that the MIME from address is correct - unfortunately, we can't verify the SMTP from address
   244  	assert.Equal(t, mail.from.String(), resultsEmail.Header["From"][0])
   245  
   246  	// check that the custom mime headers came through - header case seems to get mutated
   247  	assert.Equal(t, "TestValue", resultsEmail.Header["Testheader"][0])
   248  }
   249  
   250  func TestAuthMethods(t *testing.T) {
   251  	auth := &authChooser{
   252  		config: &SMTPConfig{
   253  			Username:   "test",
   254  			Password:   "fakepass",
   255  			ServerName: "fakeserver",
   256  			Server:     "fakeserver",
   257  			Port:       "25",
   258  		},
   259  	}
   260  	tests := []struct {
   261  		desc   string
   262  		server *smtp.ServerInfo
   263  		err    string
   264  	}{
   265  		{
   266  			desc:   "auth PLAIN success",
   267  			server: &smtp.ServerInfo{Name: "fakeserver:25", Auth: []string{"PLAIN"}, TLS: true},
   268  		},
   269  		{
   270  			desc:   "auth PLAIN unencrypted connection fail",
   271  			server: &smtp.ServerInfo{Name: "fakeserver:25", Auth: []string{"PLAIN"}, TLS: false},
   272  			err:    "unencrypted connection",
   273  		},
   274  		{
   275  			desc:   "auth PLAIN wrong host name",
   276  			server: &smtp.ServerInfo{Name: "wrongServer:999", Auth: []string{"PLAIN"}, TLS: true},
   277  			err:    "wrong host name",
   278  		},
   279  		{
   280  			desc:   "auth LOGIN success",
   281  			server: &smtp.ServerInfo{Name: "fakeserver:25", Auth: []string{"LOGIN"}, TLS: true},
   282  		},
   283  		{
   284  			desc:   "auth LOGIN unencrypted connection fail",
   285  			server: &smtp.ServerInfo{Name: "wrongServer:999", Auth: []string{"LOGIN"}, TLS: true},
   286  			err:    "wrong host name",
   287  		},
   288  		{
   289  			desc:   "auth LOGIN wrong host name",
   290  			server: &smtp.ServerInfo{Name: "fakeserver:25", Auth: []string{"LOGIN"}, TLS: false},
   291  			err:    "unencrypted connection",
   292  		},
   293  	}
   294  
   295  	for i, test := range tests {
   296  		t.Run(test.desc, func(t *testing.T) {
   297  			_, _, err := auth.Start(test.server)
   298  			got := ""
   299  			if err != nil {
   300  				got = err.Error()
   301  			}
   302  			assert.True(t, got == test.err, "%d. got error = %q; want %q", i, got, test.err)
   303  		})
   304  	}
   305  }
   306  
   307  type mockMailer struct {
   308  	data []byte
   309  }
   310  
   311  func (m *mockMailer) Mail(string) error             { return nil }
   312  func (m *mockMailer) Rcpt(string) error             { return nil }
   313  func (m *mockMailer) Data() (io.WriteCloser, error) { return m, nil }
   314  func (m *mockMailer) Write(p []byte) (int, error) {
   315  	m.data = append(m.data, p...)
   316  	return len(p), nil
   317  }
   318  func (m *mockMailer) Close() error { return nil }
   319  
   320  func TestSendMail(t *testing.T) {
   321  	dir, err := ioutil.TempDir(".", "mail-test-")
   322  	require.NoError(t, err)
   323  	defer os.RemoveAll(dir)
   324  	mocm := &mockMailer{}
   325  
   326  	testCases := map[string]struct {
   327  		replyTo     mail.Address
   328  		contains    string
   329  		notContains string
   330  	}{
   331  		"adds reply-to header": {
   332  			mail.Address{Address: "foo@test.com"},
   333  			"\r\nReply-To: <foo@test.com>\r\n",
   334  			"",
   335  		},
   336  		"doesn't add reply-to header": {
   337  			mail.Address{},
   338  			"",
   339  			"\r\nReply-To:",
   340  		},
   341  	}
   342  
   343  	for testName, tc := range testCases {
   344  		t.Run(testName, func(t *testing.T) {
   345  			mail := mailData{"", "", mail.Address{}, "", tc.replyTo, "", "", nil, nil}
   346  			err = SendMail(mocm, mail, time.Now())
   347  			require.NoError(t, err)
   348  			if tc.contains != "" {
   349  				require.Contains(t, string(mocm.data), tc.contains)
   350  			}
   351  			if tc.notContains != "" {
   352  				require.NotContains(t, string(mocm.data), tc.notContains)
   353  			}
   354  			mocm.data = []byte{}
   355  		})
   356  	}
   357  }