code.gitea.io/gitea@v1.22.3/tests/integration/incoming_email_test.go (about) 1 // Copyright 2023 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package integration 5 6 import ( 7 "io" 8 "net" 9 "net/smtp" 10 "net/url" 11 "strings" 12 "testing" 13 "time" 14 15 "code.gitea.io/gitea/models/db" 16 issues_model "code.gitea.io/gitea/models/issues" 17 "code.gitea.io/gitea/models/unittest" 18 user_model "code.gitea.io/gitea/models/user" 19 "code.gitea.io/gitea/modules/setting" 20 "code.gitea.io/gitea/services/mailer/incoming" 21 incoming_payload "code.gitea.io/gitea/services/mailer/incoming/payload" 22 token_service "code.gitea.io/gitea/services/mailer/token" 23 "code.gitea.io/gitea/tests" 24 25 "github.com/stretchr/testify/assert" 26 "gopkg.in/gomail.v2" 27 ) 28 29 func TestIncomingEmail(t *testing.T) { 30 onGiteaRun(t, func(t *testing.T, u *url.URL) { 31 user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) 32 issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) 33 34 t.Run("Payload", func(t *testing.T) { 35 defer tests.PrintCurrentTest(t)() 36 37 comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 1}) 38 39 _, err := incoming_payload.CreateReferencePayload(user) 40 assert.Error(t, err) 41 42 issuePayload, err := incoming_payload.CreateReferencePayload(issue) 43 assert.NoError(t, err) 44 commentPayload, err := incoming_payload.CreateReferencePayload(comment) 45 assert.NoError(t, err) 46 47 _, err = incoming_payload.GetReferenceFromPayload(db.DefaultContext, []byte{1, 2, 3}) 48 assert.Error(t, err) 49 50 ref, err := incoming_payload.GetReferenceFromPayload(db.DefaultContext, issuePayload) 51 assert.NoError(t, err) 52 assert.IsType(t, ref, new(issues_model.Issue)) 53 assert.EqualValues(t, issue.ID, ref.(*issues_model.Issue).ID) 54 55 ref, err = incoming_payload.GetReferenceFromPayload(db.DefaultContext, commentPayload) 56 assert.NoError(t, err) 57 assert.IsType(t, ref, new(issues_model.Comment)) 58 assert.EqualValues(t, comment.ID, ref.(*issues_model.Comment).ID) 59 }) 60 61 t.Run("Token", func(t *testing.T) { 62 defer tests.PrintCurrentTest(t)() 63 64 payload := []byte{1, 2, 3, 4, 5} 65 66 token, err := token_service.CreateToken(token_service.ReplyHandlerType, user, payload) 67 assert.NoError(t, err) 68 assert.NotEmpty(t, token) 69 70 ht, u, p, err := token_service.ExtractToken(db.DefaultContext, token) 71 assert.NoError(t, err) 72 assert.Equal(t, token_service.ReplyHandlerType, ht) 73 assert.Equal(t, user.ID, u.ID) 74 assert.Equal(t, payload, p) 75 }) 76 77 t.Run("Handler", func(t *testing.T) { 78 t.Run("Reply", func(t *testing.T) { 79 t.Run("Comment", func(t *testing.T) { 80 defer tests.PrintCurrentTest(t)() 81 82 handler := &incoming.ReplyHandler{} 83 84 payload, err := incoming_payload.CreateReferencePayload(issue) 85 assert.NoError(t, err) 86 87 assert.Error(t, handler.Handle(db.DefaultContext, &incoming.MailContent{}, nil, payload)) 88 assert.NoError(t, handler.Handle(db.DefaultContext, &incoming.MailContent{}, user, payload)) 89 90 content := &incoming.MailContent{ 91 Content: "reply by mail", 92 Attachments: []*incoming.Attachment{ 93 { 94 Name: "attachment.txt", 95 Content: []byte("test"), 96 }, 97 }, 98 } 99 100 assert.NoError(t, handler.Handle(db.DefaultContext, content, user, payload)) 101 102 comments, err := issues_model.FindComments(db.DefaultContext, &issues_model.FindCommentsOptions{ 103 IssueID: issue.ID, 104 Type: issues_model.CommentTypeComment, 105 }) 106 assert.NoError(t, err) 107 assert.NotEmpty(t, comments) 108 comment := comments[len(comments)-1] 109 assert.Equal(t, user.ID, comment.PosterID) 110 assert.Equal(t, content.Content, comment.Content) 111 assert.NoError(t, comment.LoadAttachments(db.DefaultContext)) 112 assert.Len(t, comment.Attachments, 1) 113 attachment := comment.Attachments[0] 114 assert.Equal(t, content.Attachments[0].Name, attachment.Name) 115 assert.EqualValues(t, 4, attachment.Size) 116 }) 117 118 t.Run("CodeComment", func(t *testing.T) { 119 defer tests.PrintCurrentTest(t)() 120 121 comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 6}) 122 issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID}) 123 124 handler := &incoming.ReplyHandler{} 125 content := &incoming.MailContent{ 126 Content: "code reply by mail", 127 Attachments: []*incoming.Attachment{ 128 { 129 Name: "attachment.txt", 130 Content: []byte("test"), 131 }, 132 }, 133 } 134 135 payload, err := incoming_payload.CreateReferencePayload(comment) 136 assert.NoError(t, err) 137 138 assert.NoError(t, handler.Handle(db.DefaultContext, content, user, payload)) 139 140 comments, err := issues_model.FindComments(db.DefaultContext, &issues_model.FindCommentsOptions{ 141 IssueID: issue.ID, 142 Type: issues_model.CommentTypeCode, 143 }) 144 assert.NoError(t, err) 145 assert.NotEmpty(t, comments) 146 comment = comments[len(comments)-1] 147 assert.Equal(t, user.ID, comment.PosterID) 148 assert.Equal(t, content.Content, comment.Content) 149 assert.NoError(t, comment.LoadAttachments(db.DefaultContext)) 150 assert.Len(t, comment.Attachments, 1) 151 attachment := comment.Attachments[0] 152 assert.Equal(t, content.Attachments[0].Name, attachment.Name) 153 assert.EqualValues(t, 4, attachment.Size) 154 }) 155 }) 156 157 t.Run("Unsubscribe", func(t *testing.T) { 158 defer tests.PrintCurrentTest(t)() 159 160 watching, err := issues_model.CheckIssueWatch(db.DefaultContext, user, issue) 161 assert.NoError(t, err) 162 assert.True(t, watching) 163 164 handler := &incoming.UnsubscribeHandler{} 165 166 content := &incoming.MailContent{ 167 Content: "unsub me", 168 } 169 170 payload, err := incoming_payload.CreateReferencePayload(issue) 171 assert.NoError(t, err) 172 173 assert.NoError(t, handler.Handle(db.DefaultContext, content, user, payload)) 174 175 watching, err = issues_model.CheckIssueWatch(db.DefaultContext, user, issue) 176 assert.NoError(t, err) 177 assert.False(t, watching) 178 }) 179 }) 180 181 if setting.IncomingEmail.Enabled { 182 // This test connects to the configured email server and is currently only enabled for MySql integration tests. 183 // It sends a reply to create a comment. If the comment is not detected after 10 seconds the test fails. 184 t.Run("IMAP", func(t *testing.T) { 185 defer tests.PrintCurrentTest(t)() 186 187 payload, err := incoming_payload.CreateReferencePayload(issue) 188 assert.NoError(t, err) 189 token, err := token_service.CreateToken(token_service.ReplyHandlerType, user, payload) 190 assert.NoError(t, err) 191 192 msg := gomail.NewMessage() 193 msg.SetHeader("To", strings.Replace(setting.IncomingEmail.ReplyToAddress, setting.IncomingEmail.TokenPlaceholder, token, 1)) 194 msg.SetHeader("From", user.Email) 195 msg.SetBody("text/plain", token) 196 err = gomail.Send(&smtpTestSender{}, msg) 197 assert.NoError(t, err) 198 199 assert.Eventually(t, func() bool { 200 comments, err := issues_model.FindComments(db.DefaultContext, &issues_model.FindCommentsOptions{ 201 IssueID: issue.ID, 202 Type: issues_model.CommentTypeComment, 203 }) 204 assert.NoError(t, err) 205 assert.NotEmpty(t, comments) 206 207 comment := comments[len(comments)-1] 208 209 return comment.PosterID == user.ID && comment.Content == token 210 }, 10*time.Second, 1*time.Second) 211 }) 212 } 213 }) 214 } 215 216 // A simple SMTP mail sender used for integration tests. 217 type smtpTestSender struct{} 218 219 func (s *smtpTestSender) Send(from string, to []string, msg io.WriterTo) error { 220 conn, err := net.Dial("tcp", net.JoinHostPort(setting.IncomingEmail.Host, "25")) 221 if err != nil { 222 return err 223 } 224 defer conn.Close() 225 226 client, err := smtp.NewClient(conn, setting.IncomingEmail.Host) 227 if err != nil { 228 return err 229 } 230 231 if err = client.Mail(from); err != nil { 232 return err 233 } 234 235 for _, rec := range to { 236 if err = client.Rcpt(rec); err != nil { 237 return err 238 } 239 } 240 241 w, err := client.Data() 242 if err != nil { 243 return err 244 } 245 if _, err := msg.WriteTo(w); err != nil { 246 return err 247 } 248 if err := w.Close(); err != nil { 249 return err 250 } 251 252 return client.Quit() 253 }