gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  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. "net/url"
  9. "strings"
  10. "testing"
  11. "time"
  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. sender_service "code.gitea.io/gitea/services/mailer/sender"
  19. token_service "code.gitea.io/gitea/services/mailer/token"
  20. "code.gitea.io/gitea/tests"
  21. "github.com/stretchr/testify/assert"
  22. )
  23. func TestIncomingEmail(t *testing.T) {
  24. onGiteaRun(t, func(t *testing.T, u *url.URL) {
  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(t.Context(), []byte{1, 2, 3})
  37. assert.Error(t, err)
  38. ref, err := incoming_payload.GetReferenceFromPayload(t.Context(), issuePayload)
  39. assert.NoError(t, err)
  40. assert.IsType(t, ref, new(issues_model.Issue))
  41. assert.Equal(t, issue.ID, ref.(*issues_model.Issue).ID)
  42. ref, err = incoming_payload.GetReferenceFromPayload(t.Context(), commentPayload)
  43. assert.NoError(t, err)
  44. assert.IsType(t, ref, new(issues_model.Comment))
  45. assert.Equal(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(t.Context(), 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(t.Context(), &incoming.MailContent{}, nil, payload))
  67. assert.NoError(t, handler.Handle(t.Context(), &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(t.Context(), content, user, payload))
  78. comments, err := issues_model.FindComments(t.Context(), &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(t.Context()))
  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(t.Context(), content, user, payload))
  110. comments, err := issues_model.FindComments(t.Context(), &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(t.Context()))
  120. assert.Len(t, comment.Attachments, 1)
  121. attachment := comment.Attachments[0]
  122. assert.Equal(t, content.Attachments[0].Name, attachment.Name)
  123. assert.EqualValues(t, 4, attachment.Size)
  124. })
  125. })
  126. t.Run("Unsubscribe", func(t *testing.T) {
  127. defer tests.PrintCurrentTest(t)()
  128. watching, err := issues_model.CheckIssueWatch(t.Context(), user, issue)
  129. assert.NoError(t, err)
  130. assert.True(t, watching)
  131. handler := &incoming.UnsubscribeHandler{}
  132. content := &incoming.MailContent{
  133. Content: "unsub me",
  134. }
  135. payload, err := incoming_payload.CreateReferencePayload(issue)
  136. assert.NoError(t, err)
  137. assert.NoError(t, handler.Handle(t.Context(), content, user, payload))
  138. watching, err = issues_model.CheckIssueWatch(t.Context(), user, issue)
  139. assert.NoError(t, err)
  140. assert.False(t, watching)
  141. })
  142. })
  143. if setting.IncomingEmail.Enabled {
  144. // This test connects to the configured email server and is currently only enabled for MySql integration tests.
  145. // It sends a reply to create a comment. If the comment is not detected after 10 seconds the test fails.
  146. t.Run("IMAP", func(t *testing.T) {
  147. defer tests.PrintCurrentTest(t)()
  148. payload, err := incoming_payload.CreateReferencePayload(issue)
  149. assert.NoError(t, err)
  150. token, err := token_service.CreateToken(token_service.ReplyHandlerType, user, payload)
  151. assert.NoError(t, err)
  152. msg := sender_service.NewMessageFrom(
  153. strings.Replace(setting.IncomingEmail.ReplyToAddress, setting.IncomingEmail.TokenPlaceholder, token, 1),
  154. "",
  155. user.Email,
  156. "",
  157. token,
  158. )
  159. err = sender_service.Send(&smtpTestSender{}, msg)
  160. assert.NoError(t, err)
  161. assert.Eventually(t, func() bool {
  162. comments, err := issues_model.FindComments(t.Context(), &issues_model.FindCommentsOptions{
  163. IssueID: issue.ID,
  164. Type: issues_model.CommentTypeComment,
  165. })
  166. assert.NoError(t, err)
  167. assert.NotEmpty(t, comments)
  168. comment := comments[len(comments)-1]
  169. return comment.PosterID == user.ID && comment.Content == token
  170. }, 10*time.Second, 1*time.Second)
  171. })
  172. }
  173. })
  174. }
  175. // A simple SMTP mail sender used for integration tests.
  176. type smtpTestSender struct{}
  177. func (s *smtpTestSender) Send(from string, to []string, msg io.WriterTo) error {
  178. conn, err := net.Dial("tcp", net.JoinHostPort(setting.IncomingEmail.Host, "25"))
  179. if err != nil {
  180. return err
  181. }
  182. defer conn.Close()
  183. client, err := smtp.NewClient(conn, setting.IncomingEmail.Host)
  184. if err != nil {
  185. return err
  186. }
  187. if err = client.Mail(from); err != nil {
  188. return err
  189. }
  190. for _, rec := range to {
  191. if err = client.Rcpt(rec); err != nil {
  192. return err
  193. }
  194. }
  195. w, err := client.Data()
  196. if err != nil {
  197. return err
  198. }
  199. if _, err := msg.WriteTo(w); err != nil {
  200. return err
  201. }
  202. if err := w.Close(); err != nil {
  203. return err
  204. }
  205. return client.Quit()
  206. }