Some checks failed
go-generate-check / go-generate-check (push) Failing after 22s
493 lines
13 KiB
Go
493 lines
13 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"go.uber.org/mock/gomock"
|
|
"src.opensuse.org/autogits/common"
|
|
"src.opensuse.org/autogits/common/gitea-generated/models"
|
|
mock_common "src.opensuse.org/autogits/common/mock"
|
|
)
|
|
|
|
func TestProcessPR(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
|
|
mockGitea := mock_common.NewMockGitea(ctrl)
|
|
groupName := "testgroup"
|
|
|
|
bot := &ReviewBot{
|
|
gitea: mockGitea,
|
|
groupName: groupName,
|
|
}
|
|
bot.InitRegex(groupName)
|
|
|
|
org := "myorg"
|
|
repo := "myrepo"
|
|
prIndex := int64(1)
|
|
headSha := "abcdef123456"
|
|
|
|
pr := &models.PullRequest{
|
|
Index: prIndex,
|
|
URL: "http://gitea/pr/1",
|
|
State: "open",
|
|
Base: &models.PRBranchInfo{
|
|
Name: "main",
|
|
Repo: &models.Repository{
|
|
Name: repo,
|
|
Owner: &models.User{
|
|
UserName: org,
|
|
},
|
|
},
|
|
},
|
|
Head: &models.PRBranchInfo{
|
|
Sha: headSha,
|
|
},
|
|
User: &models.User{
|
|
UserName: "submitter",
|
|
},
|
|
RequestedReviewers: []*models.User{
|
|
{UserName: groupName},
|
|
},
|
|
}
|
|
|
|
prjConfig := &common.AutogitConfig{
|
|
GitProjectName: org + "/" + repo + "#main",
|
|
ReviewGroups: []*common.ReviewGroup{
|
|
{
|
|
Name: groupName,
|
|
Reviewers: []string{"reviewer1", "reviewer2"},
|
|
},
|
|
},
|
|
}
|
|
bot.configs = common.AutogitConfigs{prjConfig}
|
|
|
|
t.Run("Review not requested for group", func(t *testing.T) {
|
|
prNoRequest := *pr
|
|
prNoRequest.RequestedReviewers = nil
|
|
err := bot.ProcessPR(&prNoRequest)
|
|
if err != nil {
|
|
t.Errorf("Expected no error, got %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("PR is closed", func(t *testing.T) {
|
|
prClosed := *pr
|
|
prClosed.State = "closed"
|
|
err := bot.ProcessPR(&prClosed)
|
|
if err != nil {
|
|
t.Errorf("Expected no error, got %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("Successful Approval", func(t *testing.T) {
|
|
common.IsDryRun = false
|
|
mockGitea.EXPECT().GetPullRequestReviews(org, repo, prIndex).Return(nil, nil)
|
|
// reviewer1 approved in timeline
|
|
timeline := []*models.TimelineComment{
|
|
{
|
|
Type: common.TimelineCommentType_Comment,
|
|
User: &models.User{UserName: "reviewer1"},
|
|
Body: "@" + groupName + ": approve",
|
|
},
|
|
}
|
|
|
|
mockGitea.EXPECT().GetTimeline(org, repo, prIndex).Return(timeline, nil)
|
|
|
|
expectedText := "reviewer1 approved a review on behalf of " + groupName
|
|
mockGitea.EXPECT().AddReviewComment(pr, common.ReviewStateApproved, expectedText).Return(nil, nil)
|
|
mockGitea.EXPECT().UnrequestReview(org, repo, prIndex, gomock.Any()).Return(nil)
|
|
|
|
err := bot.ProcessPR(pr)
|
|
if err != nil {
|
|
t.Errorf("Expected nil error, got %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("Dry Run - No actions taken", func(t *testing.T) {
|
|
common.IsDryRun = true
|
|
mockGitea.EXPECT().GetPullRequestReviews(org, repo, prIndex).Return(nil, nil)
|
|
timeline := []*models.TimelineComment{
|
|
{
|
|
Type: common.TimelineCommentType_Comment,
|
|
User: &models.User{UserName: "reviewer1"},
|
|
Body: "@" + groupName + ": approve",
|
|
},
|
|
}
|
|
mockGitea.EXPECT().GetTimeline(org, repo, prIndex).Return(timeline, nil)
|
|
|
|
// No AddReviewComment or UnrequestReview should be called
|
|
err := bot.ProcessPR(pr)
|
|
if err != nil {
|
|
t.Errorf("Expected nil error, got %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("Approval already exists - No new comment", func(t *testing.T) {
|
|
common.IsDryRun = false
|
|
mockGitea.EXPECT().GetPullRequestReviews(org, repo, prIndex).Return(nil, nil)
|
|
|
|
approvalText := "reviewer1 approved a review on behalf of " + groupName
|
|
timeline := []*models.TimelineComment{
|
|
{
|
|
Type: common.TimelineCommentType_Review,
|
|
User: &models.User{UserName: groupName},
|
|
Body: approvalText,
|
|
},
|
|
{
|
|
Type: common.TimelineCommentType_Comment,
|
|
User: &models.User{UserName: "reviewer1"},
|
|
Body: "@" + groupName + ": approve",
|
|
},
|
|
{
|
|
Type: common.TimelineCommentType_Comment,
|
|
User: &models.User{UserName: groupName},
|
|
Body: "Help comment",
|
|
},
|
|
}
|
|
|
|
mockGitea.EXPECT().GetTimeline(org, repo, prIndex).Return(timeline, nil)
|
|
|
|
// No AddReviewComment, UnrequestReview, or AddComment should be called
|
|
err := bot.ProcessPR(pr)
|
|
if err != nil {
|
|
t.Errorf("Expected nil error, got %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("Rejection already exists - No new comment", func(t *testing.T) {
|
|
common.IsDryRun = false
|
|
mockGitea.EXPECT().GetPullRequestReviews(org, repo, prIndex).Return(nil, nil)
|
|
|
|
rejectionText := "reviewer1 requested changes on behalf of " + groupName + ". See http://gitea/comment/123"
|
|
timeline := []*models.TimelineComment{
|
|
{
|
|
Type: common.TimelineCommentType_Review,
|
|
User: &models.User{UserName: groupName},
|
|
Body: rejectionText,
|
|
},
|
|
{
|
|
Type: common.TimelineCommentType_Comment,
|
|
User: &models.User{UserName: "reviewer1"},
|
|
Body: "@" + groupName + ": decline",
|
|
HTMLURL: "http://gitea/comment/123",
|
|
},
|
|
{
|
|
Type: common.TimelineCommentType_Comment,
|
|
User: &models.User{UserName: groupName},
|
|
Body: "Help comment",
|
|
},
|
|
}
|
|
|
|
mockGitea.EXPECT().GetTimeline(org, repo, prIndex).Return(timeline, nil)
|
|
|
|
err := bot.ProcessPR(pr)
|
|
if err != nil {
|
|
t.Errorf("Expected nil error, got %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("Pending review - Help comment already exists", func(t *testing.T) {
|
|
common.IsDryRun = false
|
|
mockGitea.EXPECT().GetPullRequestReviews(org, repo, prIndex).Return(nil, nil)
|
|
|
|
timeline := []*models.TimelineComment{
|
|
{
|
|
Type: common.TimelineCommentType_Comment,
|
|
User: &models.User{UserName: groupName},
|
|
Body: "Some help comment",
|
|
},
|
|
}
|
|
|
|
mockGitea.EXPECT().GetTimeline(org, repo, prIndex).Return(timeline, nil)
|
|
|
|
// It will try to request reviews
|
|
mockGitea.EXPECT().RequestReviews(pr, "reviewer1", "reviewer2").Return(nil, nil)
|
|
|
|
// AddComment should NOT be called because bot already has a comment in timeline
|
|
err := bot.ProcessPR(pr)
|
|
if err != ReviewNotFinished {
|
|
t.Errorf("Expected ReviewNotFinished error, got %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("Submitter is group member - Excluded from review request", func(t *testing.T) {
|
|
common.IsDryRun = false
|
|
prSubmitterMember := *pr
|
|
prSubmitterMember.User = &models.User{UserName: "reviewer1"}
|
|
mockGitea.EXPECT().GetPullRequestReviews(org, repo, prIndex).Return(nil, nil)
|
|
mockGitea.EXPECT().GetTimeline(org, repo, prIndex).Return(nil, nil)
|
|
mockGitea.EXPECT().RequestReviews(&prSubmitterMember, "reviewer2").Return(nil, nil)
|
|
mockGitea.EXPECT().AddComment(&prSubmitterMember, gomock.Any()).Return(nil)
|
|
err := bot.ProcessPR(&prSubmitterMember)
|
|
if err != ReviewNotFinished {
|
|
t.Errorf("Expected ReviewNotFinished error, got %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("Successful Rejection", func(t *testing.T) {
|
|
common.IsDryRun = false
|
|
mockGitea.EXPECT().GetPullRequestReviews(org, repo, prIndex).Return(nil, nil)
|
|
timeline := []*models.TimelineComment{
|
|
{
|
|
Type: common.TimelineCommentType_Comment,
|
|
User: &models.User{UserName: "reviewer2"},
|
|
Body: "@" + groupName + ": decline",
|
|
HTMLURL: "http://gitea/comment/999",
|
|
},
|
|
}
|
|
mockGitea.EXPECT().GetTimeline(org, repo, prIndex).Return(timeline, nil)
|
|
expectedText := "reviewer2 requested changes on behalf of " + groupName + ". See http://gitea/comment/999"
|
|
mockGitea.EXPECT().AddReviewComment(pr, common.ReviewStateRequestChanges, expectedText).Return(nil, nil)
|
|
mockGitea.EXPECT().UnrequestReview(org, repo, prIndex, gomock.Any()).Return(nil)
|
|
err := bot.ProcessPR(pr)
|
|
if err != nil {
|
|
t.Errorf("Expected nil error, got %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("Config not found", func(t *testing.T) {
|
|
bot.configs = common.AutogitConfigs{}
|
|
err := bot.ProcessPR(pr)
|
|
if err == nil {
|
|
t.Error("Expected error when config is missing, got nil")
|
|
}
|
|
})
|
|
|
|
t.Run("Gitea error in GetPullRequestReviews", func(t *testing.T) {
|
|
bot.configs = common.AutogitConfigs{prjConfig}
|
|
mockGitea.EXPECT().GetPullRequestReviews(org, repo, prIndex).Return(nil, fmt.Errorf("gitea error"))
|
|
err := bot.ProcessPR(pr)
|
|
if err == nil {
|
|
t.Error("Expected error from gitea, got nil")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestProcessNotifications(t *testing.T) {
|
|
ctrl := gomock.NewController(t)
|
|
defer ctrl.Finish()
|
|
|
|
mockGitea := mock_common.NewMockGitea(ctrl)
|
|
groupName := "testgroup"
|
|
|
|
bot := &ReviewBot{
|
|
gitea: mockGitea,
|
|
groupName: groupName,
|
|
}
|
|
bot.InitRegex(groupName)
|
|
|
|
org := "myorg"
|
|
repo := "myrepo"
|
|
prIndex := int64(123)
|
|
notificationID := int64(456)
|
|
|
|
notification := &models.NotificationThread{
|
|
ID: notificationID,
|
|
Subject: &models.NotificationSubject{
|
|
URL: fmt.Sprintf("http://gitea/api/v1/repos/%s/%s/pulls/%d", org, repo, prIndex),
|
|
},
|
|
}
|
|
|
|
t.Run("Notification Success", func(t *testing.T) {
|
|
common.IsDryRun = false
|
|
pr := &models.PullRequest{
|
|
Index: prIndex,
|
|
Base: &models.PRBranchInfo{
|
|
Name: "main",
|
|
Repo: &models.Repository{
|
|
Name: repo,
|
|
Owner: &models.User{UserName: org},
|
|
},
|
|
},
|
|
|
|
Head: &models.PRBranchInfo{
|
|
Sha: "headsha",
|
|
Repo: &models.Repository{
|
|
Name: repo,
|
|
Owner: &models.User{UserName: org},
|
|
},
|
|
},
|
|
|
|
User: &models.User{UserName: "submitter"},
|
|
RequestedReviewers: []*models.User{{UserName: groupName}},
|
|
}
|
|
|
|
mockGitea.EXPECT().GetPullRequest(org, repo, prIndex).Return(pr, nil)
|
|
|
|
prjConfig := &common.AutogitConfig{
|
|
GitProjectName: org + "/" + repo + "#main",
|
|
ReviewGroups: []*common.ReviewGroup{{Name: groupName, Reviewers: []string{"r1"}}},
|
|
}
|
|
bot.configs = common.AutogitConfigs{prjConfig}
|
|
mockGitea.EXPECT().GetPullRequestReviews(org, repo, prIndex).Return(nil, nil)
|
|
timeline := []*models.TimelineComment{
|
|
{
|
|
Type: common.TimelineCommentType_Comment,
|
|
User: &models.User{UserName: "r1"},
|
|
Body: "@" + groupName + ": approve",
|
|
},
|
|
}
|
|
mockGitea.EXPECT().GetTimeline(org, repo, prIndex).Return(timeline, nil)
|
|
expectedText := "r1 approved a review on behalf of " + groupName
|
|
mockGitea.EXPECT().AddReviewComment(pr, common.ReviewStateApproved, expectedText).Return(nil, nil)
|
|
mockGitea.EXPECT().UnrequestReview(org, repo, prIndex, gomock.Any()).Return(nil)
|
|
|
|
mockGitea.EXPECT().SetNotificationRead(notificationID).Return(nil)
|
|
|
|
bot.ProcessNotifications(notification)
|
|
|
|
})
|
|
|
|
t.Run("Invalid Notification URL", func(t *testing.T) {
|
|
badNotification := &models.NotificationThread{
|
|
Subject: &models.NotificationSubject{
|
|
URL: "http://gitea/invalid/url",
|
|
},
|
|
}
|
|
bot.ProcessNotifications(badNotification)
|
|
})
|
|
|
|
t.Run("Gitea error in GetPullRequest", func(t *testing.T) {
|
|
mockGitea.EXPECT().GetPullRequest(org, repo, prIndex).Return(nil, fmt.Errorf("gitea error"))
|
|
bot.ProcessNotifications(notification)
|
|
})
|
|
}
|
|
|
|
func TestReviewApprovalCheck(t *testing.T) {
|
|
tests := []struct {
|
|
Name string
|
|
GroupName string
|
|
InString string
|
|
Approved bool
|
|
Rejected bool
|
|
}{
|
|
{
|
|
Name: "Empty String",
|
|
GroupName: "group",
|
|
InString: "",
|
|
},
|
|
{
|
|
Name: "Random Text",
|
|
GroupName: "group",
|
|
InString: "some things LGTM",
|
|
},
|
|
{
|
|
Name: "Group name with Random Text means disapproval",
|
|
GroupName: "group",
|
|
InString: "@group: some things LGTM",
|
|
Rejected: true,
|
|
},
|
|
{
|
|
Name: "Bad name with Approval",
|
|
GroupName: "group2",
|
|
InString: "@group: LGTM",
|
|
},
|
|
{
|
|
Name: "Bad name with Approval",
|
|
GroupName: "group2",
|
|
InString: "@group: LGTM",
|
|
},
|
|
{
|
|
Name: "LGTM approval",
|
|
GroupName: "group2",
|
|
InString: "@group2: LGTM",
|
|
Approved: true,
|
|
},
|
|
{
|
|
Name: "approval",
|
|
GroupName: "group2",
|
|
InString: "@group2: approved",
|
|
Approved: true,
|
|
},
|
|
{
|
|
Name: "approval",
|
|
GroupName: "group2",
|
|
InString: "@group2: approve",
|
|
Approved: true,
|
|
},
|
|
{
|
|
Name: "disapproval",
|
|
GroupName: "group2",
|
|
InString: "@group2: disapprove",
|
|
Rejected: true,
|
|
},
|
|
{
|
|
Name: "Whitespace before colon",
|
|
GroupName: "group",
|
|
InString: "@group : LGTM",
|
|
Approved: true,
|
|
},
|
|
{
|
|
Name: "No whitespace after colon",
|
|
GroupName: "group",
|
|
InString: "@group:LGTM",
|
|
Approved: true,
|
|
},
|
|
{
|
|
Name: "Leading and trailing whitespace on line",
|
|
GroupName: "group",
|
|
InString: " @group: LGTM ",
|
|
Approved: true,
|
|
},
|
|
{
|
|
Name: "Multiline: Approved on second line",
|
|
GroupName: "group",
|
|
InString: "Random noise\n@group: approved",
|
|
Approved: true,
|
|
},
|
|
{
|
|
Name: "Multiline: Multiple group mentions, first wins",
|
|
GroupName: "group",
|
|
InString: "@group: decline\n@group: approve",
|
|
Rejected: true,
|
|
},
|
|
{
|
|
Name: "Multiline: Approved on second line",
|
|
GroupName: "group",
|
|
InString: "noise\n@group: approve\nmore noise",
|
|
Approved: true,
|
|
},
|
|
{
|
|
Name: "Not at start of line (even with whitespace)",
|
|
GroupName: "group",
|
|
InString: "Hello @group: approve",
|
|
Approved: false,
|
|
},
|
|
{
|
|
Name: "Rejecting with reason",
|
|
GroupName: "group",
|
|
InString: "@group: decline because of X, Y and Z",
|
|
Rejected: true,
|
|
},
|
|
{
|
|
Name: "No colon after group",
|
|
GroupName: "group",
|
|
InString: "@group LGTM",
|
|
Approved: false,
|
|
Rejected: false,
|
|
},
|
|
{
|
|
Name: "Invalid char after group",
|
|
GroupName: "group",
|
|
InString: "@group! LGTM",
|
|
Approved: false,
|
|
Rejected: false,
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.Name, func(t *testing.T) {
|
|
bot := &ReviewBot{}
|
|
bot.InitRegex(test.GroupName)
|
|
|
|
if r := bot.ReviewAccepted(test.InString); r != test.Approved {
|
|
t.Error("ReviewAccepted() returned", r, "expecting", test.Approved)
|
|
}
|
|
if r := bot.ReviewRejected(test.InString); r != test.Rejected {
|
|
t.Error("ReviewRejected() returned", r, "expecting", test.Rejected)
|
|
}
|
|
})
|
|
}
|
|
}
|