You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

incoming_email_test.go 7.5KB

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