diff options
author | KN4CK3R <admin@oldschoolhack.me> | 2023-01-14 16:57:10 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-01-14 23:57:10 +0800 |
commit | fc037b4b825f0501a1489e10d7c822435d825cb7 (patch) | |
tree | 551590b5ec197d8efca8b7bc3a9acc5961637d9d /services/mailer/mail_test.go | |
parent | 20e3ffd2085d7066b3206809dfae7b6ebd59cb5d (diff) | |
download | gitea-fc037b4b825f0501a1489e10d7c822435d825cb7.tar.gz gitea-fc037b4b825f0501a1489e10d7c822435d825cb7.zip |
Add support for incoming emails (#22056)
closes #13585
fixes #9067
fixes #2386
ref #6226
ref #6219
fixes #745
This PR adds support to process incoming emails to perform actions.
Currently I added handling of replies and unsubscribing from
issues/pulls. In contrast to #13585 the IMAP IDLE command is used
instead of polling which results (in my opinion 😉) in cleaner code.
Procedure:
- When sending an issue/pull reply email, a token is generated which is
present in the Reply-To and References header.
- IMAP IDLE waits until a new email arrives
- The token tells which action should be performed
A possible signature and/or reply gets stripped from the content.
I added a new service to the drone pipeline to test the receiving of
incoming mails. If we keep this in, we may test our outgoing emails too
in future.
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Diffstat (limited to 'services/mailer/mail_test.go')
-rw-r--r-- | services/mailer/mail_test.go | 57 |
1 files changed, 30 insertions, 27 deletions
diff --git a/services/mailer/mail_test.go b/services/mailer/mail_test.go index 6ed4fed9bd..64f2f740ca 100644 --- a/services/mailer/mail_test.go +++ b/services/mailer/mail_test.go @@ -8,6 +8,7 @@ import ( "context" "fmt" "html/template" + "regexp" "strings" "testing" texttmpl "text/template" @@ -66,6 +67,9 @@ func prepareMailerTest(t *testing.T) (doer *user_model.User, repo *repo_model.Re func TestComposeIssueCommentMessage(t *testing.T) { doer, _, issue, comment := prepareMailerTest(t) + setting.IncomingEmail.Enabled = true + defer func() { setting.IncomingEmail.Enabled = false }() + subjectTemplates = texttmpl.Must(texttmpl.New("issue/comment").Parse(subjectTpl)) bodyTemplates = template.Must(template.New("issue/comment").Parse(bodyTpl)) @@ -78,18 +82,20 @@ func TestComposeIssueCommentMessage(t *testing.T) { assert.NoError(t, err) assert.Len(t, msgs, 2) gomailMsg := msgs[0].ToMessage() - mailto := gomailMsg.GetHeader("To") - subject := gomailMsg.GetHeader("Subject") - messageID := gomailMsg.GetHeader("Message-ID") - inReplyTo := gomailMsg.GetHeader("In-Reply-To") - references := gomailMsg.GetHeader("References") - - assert.Len(t, mailto, 1, "exactly one recipient is expected in the To field") - assert.Equal(t, "Re: ", subject[0][:4], "Comment reply subject should contain Re:") - assert.Equal(t, "Re: [user2/repo1] @user2 #1 - issue1", subject[0]) - assert.Equal(t, "<user2/repo1/issues/1@localhost>", inReplyTo[0], "In-Reply-To header doesn't match") - assert.Equal(t, "<user2/repo1/issues/1@localhost>", references[0], "References header doesn't match") - assert.Equal(t, "<user2/repo1/issues/1/comment/2@localhost>", messageID[0], "Message-ID header doesn't match") + replyTo := gomailMsg.GetHeader("Reply-To")[0] + subject := gomailMsg.GetHeader("Subject")[0] + + assert.Len(t, gomailMsg.GetHeader("To"), 1, "exactly one recipient is expected in the To field") + tokenRegex := regexp.MustCompile(`\Aincoming\+(.+)@localhost\z`) + assert.Regexp(t, tokenRegex, replyTo) + token := tokenRegex.FindAllStringSubmatch(replyTo, 1)[0][1] + assert.Equal(t, "Re: ", subject[:4], "Comment reply subject should contain Re:") + assert.Equal(t, "Re: [user2/repo1] @user2 #1 - issue1", subject) + assert.Equal(t, "<user2/repo1/issues/1@localhost>", gomailMsg.GetHeader("In-Reply-To")[0], "In-Reply-To header doesn't match") + assert.ElementsMatch(t, []string{"<user2/repo1/issues/1@localhost>", "<reply-" + token + "@localhost>"}, gomailMsg.GetHeader("References"), "References header doesn't match") + assert.Equal(t, "<user2/repo1/issues/1/comment/2@localhost>", gomailMsg.GetHeader("Message-ID")[0], "Message-ID header doesn't match") + assert.Equal(t, "<mailto:"+replyTo+">", gomailMsg.GetHeader("List-Post")[0]) + assert.Len(t, gomailMsg.GetHeader("List-Unsubscribe"), 2) // url + mailto } func TestComposeIssueMessage(t *testing.T) { @@ -119,6 +125,8 @@ func TestComposeIssueMessage(t *testing.T) { assert.Equal(t, "<user2/repo1/issues/1@localhost>", inReplyTo[0], "In-Reply-To header doesn't match") assert.Equal(t, "<user2/repo1/issues/1@localhost>", references[0], "References header doesn't match") assert.Equal(t, "<user2/repo1/issues/1@localhost>", messageID[0], "Message-ID header doesn't match") + assert.Empty(t, gomailMsg.GetHeader("List-Post")) // incoming mail feature disabled + assert.Len(t, gomailMsg.GetHeader("List-Unsubscribe"), 1) // url without mailto } func TestTemplateSelection(t *testing.T) { @@ -238,7 +246,6 @@ func TestGenerateAdditionalHeaders(t *testing.T) { expected := map[string]string{ "List-ID": "user2/repo1 <repo1.user2.localhost>", "List-Archive": "<https://try.gitea.io/user2/repo1>", - "List-Unsubscribe": "https://try.gitea.io/user2/repo1/issues/1", "X-Gitea-Reason": "dummy-reason", "X-Gitea-Sender": "< U<se>r Tw<o > ><", "X-Gitea-Recipient": "Test", @@ -271,7 +278,6 @@ func Test_createReference(t *testing.T) { name string args args prefix string - suffix string }{ { name: "Open Issue", @@ -279,7 +285,7 @@ func Test_createReference(t *testing.T) { issue: issue, actionType: activities_model.ActionCreateIssue, }, - prefix: fmt.Sprintf("%s/issues/%d@%s", issue.Repo.FullName(), issue.Index, setting.Domain), + prefix: fmt.Sprintf("<%s/issues/%d@%s>", issue.Repo.FullName(), issue.Index, setting.Domain), }, { name: "Open Pull", @@ -287,7 +293,7 @@ func Test_createReference(t *testing.T) { issue: pullIssue, actionType: activities_model.ActionCreatePullRequest, }, - prefix: fmt.Sprintf("%s/pulls/%d@%s", issue.Repo.FullName(), issue.Index, setting.Domain), + prefix: fmt.Sprintf("<%s/pulls/%d@%s>", issue.Repo.FullName(), issue.Index, setting.Domain), }, { name: "Comment Issue", @@ -296,7 +302,7 @@ func Test_createReference(t *testing.T) { comment: comment, actionType: activities_model.ActionCommentIssue, }, - prefix: fmt.Sprintf("%s/issues/%d/comment/%d@%s", issue.Repo.FullName(), issue.Index, comment.ID, setting.Domain), + prefix: fmt.Sprintf("<%s/issues/%d/comment/%d@%s>", issue.Repo.FullName(), issue.Index, comment.ID, setting.Domain), }, { name: "Comment Pull", @@ -305,7 +311,7 @@ func Test_createReference(t *testing.T) { comment: comment, actionType: activities_model.ActionCommentPull, }, - prefix: fmt.Sprintf("%s/pulls/%d/comment/%d@%s", issue.Repo.FullName(), issue.Index, comment.ID, setting.Domain), + prefix: fmt.Sprintf("<%s/pulls/%d/comment/%d@%s>", issue.Repo.FullName(), issue.Index, comment.ID, setting.Domain), }, { name: "Close Issue", @@ -313,7 +319,7 @@ func Test_createReference(t *testing.T) { issue: issue, actionType: activities_model.ActionCloseIssue, }, - prefix: fmt.Sprintf("%s/issues/%d/close/", issue.Repo.FullName(), issue.Index), + prefix: fmt.Sprintf("<%s/issues/%d/close/", issue.Repo.FullName(), issue.Index), }, { name: "Close Pull", @@ -321,7 +327,7 @@ func Test_createReference(t *testing.T) { issue: pullIssue, actionType: activities_model.ActionClosePullRequest, }, - prefix: fmt.Sprintf("%s/pulls/%d/close/", issue.Repo.FullName(), issue.Index), + prefix: fmt.Sprintf("<%s/pulls/%d/close/", issue.Repo.FullName(), issue.Index), }, { name: "Reopen Issue", @@ -329,7 +335,7 @@ func Test_createReference(t *testing.T) { issue: issue, actionType: activities_model.ActionReopenIssue, }, - prefix: fmt.Sprintf("%s/issues/%d/reopen/", issue.Repo.FullName(), issue.Index), + prefix: fmt.Sprintf("<%s/issues/%d/reopen/", issue.Repo.FullName(), issue.Index), }, { name: "Reopen Pull", @@ -337,7 +343,7 @@ func Test_createReference(t *testing.T) { issue: pullIssue, actionType: activities_model.ActionReopenPullRequest, }, - prefix: fmt.Sprintf("%s/pulls/%d/reopen/", issue.Repo.FullName(), issue.Index), + prefix: fmt.Sprintf("<%s/pulls/%d/reopen/", issue.Repo.FullName(), issue.Index), }, { name: "Merge Pull", @@ -345,7 +351,7 @@ func Test_createReference(t *testing.T) { issue: pullIssue, actionType: activities_model.ActionMergePullRequest, }, - prefix: fmt.Sprintf("%s/pulls/%d/merge/", issue.Repo.FullName(), issue.Index), + prefix: fmt.Sprintf("<%s/pulls/%d/merge/", issue.Repo.FullName(), issue.Index), }, { name: "Ready Pull", @@ -353,7 +359,7 @@ func Test_createReference(t *testing.T) { issue: pullIssue, actionType: activities_model.ActionPullRequestReadyForReview, }, - prefix: fmt.Sprintf("%s/pulls/%d/ready/", issue.Repo.FullName(), issue.Index), + prefix: fmt.Sprintf("<%s/pulls/%d/ready/", issue.Repo.FullName(), issue.Index), }, } for _, tt := range tests { @@ -362,9 +368,6 @@ func Test_createReference(t *testing.T) { if !strings.HasPrefix(got, tt.prefix) { t.Errorf("createReference() = %v, want %v", got, tt.prefix) } - if !strings.HasSuffix(got, tt.suffix) { - t.Errorf("createReference() = %v, want %v", got, tt.prefix) - } }) } } |