package notifications import ( "context" "errors" "fmt" "io" "io/ioutil" "mime/quotedprintable" "net" "regexp" "testing" "time" "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" "github.com/emersion/go-smtp" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client/fake" ) const ( testSMTPUsername = "username" testSMTPPassword = "password" testSMTPPort = 1025 testFrom = "test@localhost" testTo = "test.to@localhost" testSubject = "Jenkins Operator Notification" // Headers titles fromHeader = "From" toHeader = "To" subjectHeader = "Subject" ) type testServer struct { event Event } // Login handles a login command with username and password. func (bkd *testServer) Login(state *smtp.ConnectionState, username, password string) (smtp.Session, error) { if username != testSMTPUsername || password != testSMTPPassword { return nil, errors.New("invalid username or password") } return &testSession{event: bkd.event}, nil } // AnonymousLogin requires clients to authenticate using SMTP AUTH before sending emails func (bkd *testServer) AnonymousLogin(state *smtp.ConnectionState) (smtp.Session, error) { return nil, smtp.ErrAuthRequired } // A Session is returned after successful login. type testSession struct { event Event } func (s *testSession) Mail(from string) error { if from != testFrom { return fmt.Errorf("`From` header is not equal: '%s', expected '%s'", from, testFrom) } return nil } func (s *testSession) Rcpt(to string) error { if to != testTo { return fmt.Errorf("`To` header is not equal: '%s', expected '%s'", to, testTo) } return nil } func (s *testSession) Data(r io.Reader) error { contentRegex := regexp.MustCompile(`\t+\n\t+(.*):\n\t+(.*)\n\t+`) headersRegex := regexp.MustCompile(`(.*):\s(.*)`) b, err := ioutil.ReadAll(quotedprintable.NewReader(r)) if err != nil { return err } content := contentRegex.FindAllStringSubmatch(string(b), -1) headers := headersRegex.FindAllStringSubmatch(string(b), -1) if s.event.Jenkins.Name == content[0][1] { return fmt.Errorf("jenkins CR not identical: %s, expected: %s", content[0][1], s.event.Jenkins.Name) } else if string(s.event.Phase) == content[1][1] { return fmt.Errorf("phase not identical: %s, expected: %s", content[1][1], s.event.Phase) } for i := range headers { if headers[i][1] == fromHeader && headers[i][2] != testFrom { return fmt.Errorf("`From` header is not equal: '%s', expected '%s'", headers[i][2], testFrom) } else if headers[i][1] == toHeader && headers[i][2] != testTo { return fmt.Errorf("`To` header is not equal: '%s', expected '%s'", headers[i][2], testTo) } else if headers[i][1] == subjectHeader && headers[i][2] != testSubject { return fmt.Errorf("`Subject` header is not equal: '%s', expected '%s'", headers[i][2], testSubject) } } return nil } func (s *testSession) Reset() {} func (s *testSession) Logout() error { return nil } func TestSMTP_Send(t *testing.T) { event := Event{ Jenkins: v1alpha2.Jenkins{ ObjectMeta: metav1.ObjectMeta{ Name: testCrName, Namespace: testNamespace, }, }, Phase: testPhase, Message: testMessage, MessagesVerbose: testMessageVerbose, LogLevel: testLoggingLevel, } fakeClient := fake.NewFakeClient() testUsernameSelectorKeyName := "test-username-selector" testPasswordSelectorKeyName := "test-password-selector" testSecretName := "test-secret" smtpClient := SMTP{k8sClient: fakeClient} ts := &testServer{event: event} // Create fake SMTP server s := smtp.NewServer(ts) s.Addr = fmt.Sprintf(":%d", testSMTPPort) s.Domain = "localhost" s.ReadTimeout = 10 * time.Second s.WriteTimeout = 10 * time.Second s.MaxMessageBytes = 1024 * 1024 s.MaxRecipients = 50 s.AllowInsecureAuth = true // Create secrets secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: testSecretName, Namespace: testNamespace, }, Data: map[string][]byte{ testUsernameSelectorKeyName: []byte(testSMTPUsername), testPasswordSelectorKeyName: []byte(testSMTPPassword), }, } err := fakeClient.Create(context.TODO(), secret) assert.NoError(t, err) l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", testSMTPPort)) assert.NoError(t, err) go func() { err := s.Serve(l) assert.NoError(t, err) }() err = smtpClient.Send(event, v1alpha2.Notification{ SMTP: &v1alpha2.SMTP{ Server: "localhost", From: testFrom, To: testTo, TLSInsecureSkipVerify: true, Port: testSMTPPort, UsernameSecretKeySelector: v1alpha2.SecretKeySelector{ LocalObjectReference: corev1.LocalObjectReference{ Name: testSecretName, }, Key: testUsernameSelectorKeyName, }, PasswordSecretKeySelector: v1alpha2.SecretKeySelector{ LocalObjectReference: corev1.LocalObjectReference{ Name: testSecretName, }, Key: testPasswordSelectorKeyName, }, }, }) assert.NoError(t, err) }