Add missing comment reply handling (#32050)

Fixes #31937

- Add missing comment reply handling
- Use `onGiteaRun` in the test because the fixtures are not present
otherwise (did this behaviour change?)

Compare without whitespaces.
This commit is contained in:
KN4CK3R 2024-09-17 22:56:26 +02:00 committed by GitHub
parent 8cdf4e29c4
commit 55f1fcf0ad
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 186 additions and 180 deletions

View File

@ -82,43 +82,40 @@ func (h *ReplyHandler) Handle(ctx context.Context, content *MailContent, doer *u
return nil return nil
} }
attachmentIDs := make([]string, 0, len(content.Attachments))
if setting.Attachment.Enabled {
for _, attachment := range content.Attachments {
a, err := attachment_service.UploadAttachment(ctx, bytes.NewReader(attachment.Content), setting.Attachment.AllowedTypes, int64(len(attachment.Content)), &repo_model.Attachment{
Name: attachment.Name,
UploaderID: doer.ID,
RepoID: issue.Repo.ID,
})
if err != nil {
if upload.IsErrFileTypeForbidden(err) {
log.Info("Skipping disallowed attachment type: %s", attachment.Name)
continue
}
return err
}
attachmentIDs = append(attachmentIDs, a.UUID)
}
}
if content.Content == "" && len(attachmentIDs) == 0 {
return nil
}
switch r := ref.(type) { switch r := ref.(type) {
case *issues_model.Issue: case *issues_model.Issue:
attachmentIDs := make([]string, 0, len(content.Attachments)) _, err := issue_service.CreateIssueComment(ctx, doer, issue.Repo, issue, content.Content, attachmentIDs)
if setting.Attachment.Enabled {
for _, attachment := range content.Attachments {
a, err := attachment_service.UploadAttachment(ctx, bytes.NewReader(attachment.Content), setting.Attachment.AllowedTypes, int64(len(attachment.Content)), &repo_model.Attachment{
Name: attachment.Name,
UploaderID: doer.ID,
RepoID: issue.Repo.ID,
})
if err != nil {
if upload.IsErrFileTypeForbidden(err) {
log.Info("Skipping disallowed attachment type: %s", attachment.Name)
continue
}
return err
}
attachmentIDs = append(attachmentIDs, a.UUID)
}
}
if content.Content == "" && len(attachmentIDs) == 0 {
return nil
}
_, err = issue_service.CreateIssueComment(ctx, doer, issue.Repo, issue, content.Content, attachmentIDs)
if err != nil { if err != nil {
return fmt.Errorf("CreateIssueComment failed: %w", err) return fmt.Errorf("CreateIssueComment failed: %w", err)
} }
case *issues_model.Comment: case *issues_model.Comment:
comment := r comment := r
if content.Content == "" { switch comment.Type {
return nil case issues_model.CommentTypeCode:
}
if comment.Type == issues_model.CommentTypeCode {
_, err := pull_service.CreateCodeComment( _, err := pull_service.CreateCodeComment(
ctx, ctx,
doer, doer,
@ -130,11 +127,16 @@ func (h *ReplyHandler) Handle(ctx context.Context, content *MailContent, doer *u
false, // not pending review but a single review false, // not pending review but a single review
comment.ReviewID, comment.ReviewID,
"", "",
nil, attachmentIDs,
) )
if err != nil { if err != nil {
return fmt.Errorf("CreateCodeComment failed: %w", err) return fmt.Errorf("CreateCodeComment failed: %w", err)
} }
default:
_, err := issue_service.CreateIssueComment(ctx, doer, issue.Repo, issue, content.Content, attachmentIDs)
if err != nil {
return fmt.Errorf("CreateIssueComment failed: %w", err)
}
} }
} }
return nil return nil

View File

@ -7,6 +7,7 @@ import (
"io" "io"
"net" "net"
"net/smtp" "net/smtp"
"net/url"
"strings" "strings"
"testing" "testing"
"time" "time"
@ -26,187 +27,190 @@ import (
) )
func TestIncomingEmail(t *testing.T) { func TestIncomingEmail(t *testing.T) {
defer tests.PrepareTestEnv(t)() onGiteaRun(t, func(t *testing.T, u *url.URL) {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1})
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) t.Run("Payload", func(t *testing.T) {
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 1}) defer tests.PrintCurrentTest(t)()
t.Run("Payload", func(t *testing.T) { comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 1})
defer tests.PrintCurrentTest(t)()
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 1}) _, err := incoming_payload.CreateReferencePayload(user)
assert.Error(t, err)
_, err := incoming_payload.CreateReferencePayload(user) issuePayload, err := incoming_payload.CreateReferencePayload(issue)
assert.Error(t, err) assert.NoError(t, err)
commentPayload, err := incoming_payload.CreateReferencePayload(comment)
assert.NoError(t, err)
issuePayload, err := incoming_payload.CreateReferencePayload(issue) _, err = incoming_payload.GetReferenceFromPayload(db.DefaultContext, []byte{1, 2, 3})
assert.NoError(t, err) assert.Error(t, err)
commentPayload, err := incoming_payload.CreateReferencePayload(comment)
assert.NoError(t, err)
_, err = incoming_payload.GetReferenceFromPayload(db.DefaultContext, []byte{1, 2, 3}) ref, err := incoming_payload.GetReferenceFromPayload(db.DefaultContext, issuePayload)
assert.Error(t, err) assert.NoError(t, err)
assert.IsType(t, ref, new(issues_model.Issue))
assert.EqualValues(t, issue.ID, ref.(*issues_model.Issue).ID)
ref, err := incoming_payload.GetReferenceFromPayload(db.DefaultContext, issuePayload) ref, err = incoming_payload.GetReferenceFromPayload(db.DefaultContext, commentPayload)
assert.NoError(t, err) assert.NoError(t, err)
assert.IsType(t, ref, new(issues_model.Issue)) assert.IsType(t, ref, new(issues_model.Comment))
assert.EqualValues(t, issue.ID, ref.(*issues_model.Issue).ID) assert.EqualValues(t, comment.ID, ref.(*issues_model.Comment).ID)
})
ref, err = incoming_payload.GetReferenceFromPayload(db.DefaultContext, commentPayload) t.Run("Token", func(t *testing.T) {
assert.NoError(t, err) defer tests.PrintCurrentTest(t)()
assert.IsType(t, ref, new(issues_model.Comment))
assert.EqualValues(t, comment.ID, ref.(*issues_model.Comment).ID)
})
t.Run("Token", func(t *testing.T) { payload := []byte{1, 2, 3, 4, 5}
defer tests.PrintCurrentTest(t)()
payload := []byte{1, 2, 3, 4, 5} token, err := token_service.CreateToken(token_service.ReplyHandlerType, user, payload)
assert.NoError(t, err)
assert.NotEmpty(t, token)
token, err := token_service.CreateToken(token_service.ReplyHandlerType, user, payload) ht, u, p, err := token_service.ExtractToken(db.DefaultContext, token)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, token) assert.Equal(t, token_service.ReplyHandlerType, ht)
assert.Equal(t, user.ID, u.ID)
assert.Equal(t, payload, p)
})
ht, u, p, err := token_service.ExtractToken(db.DefaultContext, token) t.Run("Handler", func(t *testing.T) {
assert.NoError(t, err) t.Run("Reply", func(t *testing.T) {
assert.Equal(t, token_service.ReplyHandlerType, ht) t.Run("Comment", func(t *testing.T) {
assert.Equal(t, user.ID, u.ID) defer tests.PrintCurrentTest(t)()
assert.Equal(t, payload, p)
})
t.Run("Handler", func(t *testing.T) { handler := &incoming.ReplyHandler{}
t.Run("Reply", func(t *testing.T) {
t.Run("Comment", func(t *testing.T) { payload, err := incoming_payload.CreateReferencePayload(issue)
assert.NoError(t, err)
assert.Error(t, handler.Handle(db.DefaultContext, &incoming.MailContent{}, nil, payload))
assert.NoError(t, handler.Handle(db.DefaultContext, &incoming.MailContent{}, user, payload))
content := &incoming.MailContent{
Content: "reply by mail",
Attachments: []*incoming.Attachment{
{
Name: "attachment.txt",
Content: []byte("test"),
},
},
}
assert.NoError(t, handler.Handle(db.DefaultContext, content, user, payload))
comments, err := issues_model.FindComments(db.DefaultContext, &issues_model.FindCommentsOptions{
IssueID: issue.ID,
Type: issues_model.CommentTypeComment,
})
assert.NoError(t, err)
assert.NotEmpty(t, comments)
comment := comments[len(comments)-1]
assert.Equal(t, user.ID, comment.PosterID)
assert.Equal(t, content.Content, comment.Content)
assert.NoError(t, comment.LoadAttachments(db.DefaultContext))
assert.Len(t, comment.Attachments, 1)
attachment := comment.Attachments[0]
assert.Equal(t, content.Attachments[0].Name, attachment.Name)
assert.EqualValues(t, 4, attachment.Size)
})
t.Run("CodeComment", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 6})
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID})
handler := &incoming.ReplyHandler{}
content := &incoming.MailContent{
Content: "code reply by mail",
Attachments: []*incoming.Attachment{
{
Name: "attachment.txt",
Content: []byte("test"),
},
},
}
payload, err := incoming_payload.CreateReferencePayload(comment)
assert.NoError(t, err)
assert.NoError(t, handler.Handle(db.DefaultContext, content, user, payload))
comments, err := issues_model.FindComments(db.DefaultContext, &issues_model.FindCommentsOptions{
IssueID: issue.ID,
Type: issues_model.CommentTypeCode,
})
assert.NoError(t, err)
assert.NotEmpty(t, comments)
comment = comments[len(comments)-1]
assert.Equal(t, user.ID, comment.PosterID)
assert.Equal(t, content.Content, comment.Content)
assert.NoError(t, comment.LoadAttachments(db.DefaultContext))
assert.Len(t, comment.Attachments, 1)
attachment := comment.Attachments[0]
assert.Equal(t, content.Attachments[0].Name, attachment.Name)
assert.EqualValues(t, 4, attachment.Size)
})
})
t.Run("Unsubscribe", func(t *testing.T) {
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()
handler := &incoming.ReplyHandler{} watching, err := issues_model.CheckIssueWatch(db.DefaultContext, user, issue)
assert.NoError(t, err)
assert.True(t, watching)
handler := &incoming.UnsubscribeHandler{}
content := &incoming.MailContent{
Content: "unsub me",
}
payload, err := incoming_payload.CreateReferencePayload(issue) payload, err := incoming_payload.CreateReferencePayload(issue)
assert.NoError(t, err) assert.NoError(t, err)
assert.Error(t, handler.Handle(db.DefaultContext, &incoming.MailContent{}, nil, payload))
assert.NoError(t, handler.Handle(db.DefaultContext, &incoming.MailContent{}, user, payload))
content := &incoming.MailContent{
Content: "reply by mail",
Attachments: []*incoming.Attachment{
{
Name: "attachment.txt",
Content: []byte("test"),
},
},
}
assert.NoError(t, handler.Handle(db.DefaultContext, content, user, payload)) assert.NoError(t, handler.Handle(db.DefaultContext, content, user, payload))
comments, err := issues_model.FindComments(db.DefaultContext, &issues_model.FindCommentsOptions{ watching, err = issues_model.CheckIssueWatch(db.DefaultContext, user, issue)
IssueID: issue.ID,
Type: issues_model.CommentTypeComment,
})
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, comments) assert.False(t, watching)
comment := comments[len(comments)-1]
assert.Equal(t, user.ID, comment.PosterID)
assert.Equal(t, content.Content, comment.Content)
assert.NoError(t, comment.LoadAttachments(db.DefaultContext))
assert.Len(t, comment.Attachments, 1)
attachment := comment.Attachments[0]
assert.Equal(t, content.Attachments[0].Name, attachment.Name)
assert.EqualValues(t, 4, attachment.Size)
}) })
})
t.Run("CodeComment", func(t *testing.T) { if setting.IncomingEmail.Enabled {
// This test connects to the configured email server and is currently only enabled for MySql integration tests.
// It sends a reply to create a comment. If the comment is not detected after 10 seconds the test fails.
t.Run("IMAP", func(t *testing.T) {
defer tests.PrintCurrentTest(t)() defer tests.PrintCurrentTest(t)()
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 6}) payload, err := incoming_payload.CreateReferencePayload(issue)
issue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: comment.IssueID}) assert.NoError(t, err)
token, err := token_service.CreateToken(token_service.ReplyHandlerType, user, payload)
handler := &incoming.ReplyHandler{}
content := &incoming.MailContent{
Content: "code reply by mail",
Attachments: []*incoming.Attachment{
{
Name: "attachment.txt",
Content: []byte("test"),
},
},
}
payload, err := incoming_payload.CreateReferencePayload(comment)
assert.NoError(t, err) assert.NoError(t, err)
assert.NoError(t, handler.Handle(db.DefaultContext, content, user, payload)) msg := gomail.NewMessage()
msg.SetHeader("To", strings.Replace(setting.IncomingEmail.ReplyToAddress, setting.IncomingEmail.TokenPlaceholder, token, 1))
comments, err := issues_model.FindComments(db.DefaultContext, &issues_model.FindCommentsOptions{ msg.SetHeader("From", user.Email)
IssueID: issue.ID, msg.SetBody("text/plain", token)
Type: issues_model.CommentTypeCode, err = gomail.Send(&smtpTestSender{}, msg)
})
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, comments)
comment = comments[len(comments)-1] assert.Eventually(t, func() bool {
assert.Equal(t, user.ID, comment.PosterID) comments, err := issues_model.FindComments(db.DefaultContext, &issues_model.FindCommentsOptions{
assert.Equal(t, content.Content, comment.Content) IssueID: issue.ID,
assert.NoError(t, comment.LoadAttachments(db.DefaultContext)) Type: issues_model.CommentTypeComment,
assert.Empty(t, comment.Attachments) })
assert.NoError(t, err)
assert.NotEmpty(t, comments)
comment := comments[len(comments)-1]
return comment.PosterID == user.ID && comment.Content == token
}, 10*time.Second, 1*time.Second)
}) })
}) }
t.Run("Unsubscribe", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
watching, err := issues_model.CheckIssueWatch(db.DefaultContext, user, issue)
assert.NoError(t, err)
assert.True(t, watching)
handler := &incoming.UnsubscribeHandler{}
content := &incoming.MailContent{
Content: "unsub me",
}
payload, err := incoming_payload.CreateReferencePayload(issue)
assert.NoError(t, err)
assert.NoError(t, handler.Handle(db.DefaultContext, content, user, payload))
watching, err = issues_model.CheckIssueWatch(db.DefaultContext, user, issue)
assert.NoError(t, err)
assert.False(t, watching)
})
}) })
if setting.IncomingEmail.Enabled {
// This test connects to the configured email server and is currently only enabled for MySql integration tests.
// It sends a reply to create a comment. If the comment is not detected after 10 seconds the test fails.
t.Run("IMAP", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
payload, err := incoming_payload.CreateReferencePayload(issue)
assert.NoError(t, err)
token, err := token_service.CreateToken(token_service.ReplyHandlerType, user, payload)
assert.NoError(t, err)
msg := gomail.NewMessage()
msg.SetHeader("To", strings.Replace(setting.IncomingEmail.ReplyToAddress, setting.IncomingEmail.TokenPlaceholder, token, 1))
msg.SetHeader("From", user.Email)
msg.SetBody("text/plain", token)
err = gomail.Send(&smtpTestSender{}, msg)
assert.NoError(t, err)
assert.Eventually(t, func() bool {
comments, err := issues_model.FindComments(db.DefaultContext, &issues_model.FindCommentsOptions{
IssueID: issue.ID,
Type: issues_model.CommentTypeComment,
})
assert.NoError(t, err)
assert.NotEmpty(t, comments)
comment := comments[len(comments)-1]
return comment.PosterID == user.ID && comment.Content == token
}, 10*time.Second, 1*time.Second)
})
}
} }
// A simple SMTP mail sender used for integration tests. // A simple SMTP mail sender used for integration tests.