2
1
forked from adamm/autogits
Files
autogits/common/pr_test.go

1038 lines
40 KiB
Go

package common_test
import (
"errors"
"fmt"
"os"
"os/exec"
"path"
"slices"
"strings"
"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 reviewsToTimeline(reviews []*models.PullReview) []*models.TimelineComment {
timeline := make([]*models.TimelineComment, len(reviews))
for idx, review := range reviews {
if review.ID == 0 {
review.ID = int64(idx) + 100
}
timeline[idx] = &models.TimelineComment{
Type: common.TimelineCommentType_Review,
ReviewID: review.ID,
}
}
return timeline
}
func TestPR(t *testing.T) {
baseConfig := common.AutogitConfig{
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
Branch: "branch",
Organization: "foo",
GitProjectName: "barPrj",
}
type prdata struct {
pr *models.PullRequest
pr_err error
reviews []*models.PullReview
timeline []*models.TimelineComment
review_error error
}
tests := []struct {
name string
data []prdata
api_error string
resLen int
reviewed bool
consistentSet bool
prjGitPRIndex int
reviewSetFetcher func(*mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error)
}{
{
name: "Error fetching PullRequest",
data: []prdata{
{pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "opened"}, pr_err: errors.New("Missing PR")},
},
prjGitPRIndex: -1,
},
{
name: "Error fetching PullRequest in PrjGit",
data: []prdata{
{pr: &models.PullRequest{Body: "PR: foo/barPrj#22", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "opened"}, pr_err: errors.New("missing PR")},
{pr: &models.PullRequest{Body: "", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, State: "opened"}},
},
},
{
name: "Error fetching prjgit",
data: []prdata{
{pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "opened"}},
},
resLen: 1,
prjGitPRIndex: -1,
},
{
name: "Review set is consistent",
data: []prdata{
{pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "opened"}},
{pr: &models.PullRequest{Body: "PR: test/repo#42", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, State: "opened"}},
},
resLen: 2,
prjGitPRIndex: 1,
consistentSet: true,
},
{
name: "Review set is consistent: 1pkg",
data: []prdata{
{pr: &models.PullRequest{Body: "PR: foo/barPrj#22", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "opened"}},
{pr: &models.PullRequest{Body: "PR: test/repo#42", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, State: "opened"}},
},
resLen: 2,
prjGitPRIndex: 1,
consistentSet: true,
},
{
name: "Review set is consistent: 2pkg",
data: []prdata{
{pr: &models.PullRequest{Body: "some desc", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "opened"}},
{pr: &models.PullRequest{Body: "PR: test/repo#42\nPR: test/repo2#41", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, State: "opened"}},
{pr: &models.PullRequest{Body: "some other desc\nPR: foo/fer#33", Index: 41, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo2", Owner: &models.User{UserName: "test"}}}, State: "opened"}},
},
resLen: 3,
prjGitPRIndex: 1,
consistentSet: true,
},
{
name: "Review set of prjgit PR is consistent",
data: []prdata{
{
pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: common.Bot_BuildReview}, State: common.ReviewStateApproved},
},
},
},
resLen: 1,
prjGitPRIndex: 0,
consistentSet: true,
reviewed: true,
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &baseConfig)
},
},
{
name: "Review set is consistent: 2pkg",
data: []prdata{
{pr: &models.PullRequest{Body: "PR: foo/barPrj#222", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "opened"}},
{pr: &models.PullRequest{Body: "PR: test/repo2#41", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, State: "opened"}},
{pr: &models.PullRequest{Body: "PR: test/repo#42\nPR: test/repo2#41", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, State: "opened"}},
{pr: &models.PullRequest{Body: "PR: foo/barPrj#20", Index: 41, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo2", Owner: &models.User{UserName: "test"}}}, State: "opened"}},
},
resLen: 3,
prjGitPRIndex: 2,
consistentSet: true,
},
{
name: "WIP PR is not approved",
data: []prdata{
{
pr: &models.PullRequest{Body: "", Title: "WIP: some title", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: common.Bot_BuildReview}, State: common.ReviewStateApproved},
},
},
},
resLen: 1,
prjGitPRIndex: 0,
consistentSet: true,
reviewed: false,
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &baseConfig)
},
},
{
name: "Manual review is missing",
data: []prdata{
{
pr: &models.PullRequest{Body: "PR: foo/repo#20", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: common.Bot_BuildReview}, State: common.ReviewStateApproved},
},
},
{
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
},
},
},
resLen: 2,
prjGitPRIndex: 0,
consistentSet: true,
reviewed: false,
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &common.AutogitConfig{
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
Branch: "branch",
Organization: "foo",
GitProjectName: "barPrj",
ManualMergeOnly: true,
})
},
},
{
name: "Manual review is done, via PrjGit",
data: []prdata{
{
pr: &models.PullRequest{Body: "PR: foo/repo#20", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
reviews: []*models.PullReview{
{Body: "merge ok", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: common.Bot_BuildReview}, State: common.ReviewStateApproved},
},
},
{
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
},
},
},
resLen: 2,
prjGitPRIndex: 0,
consistentSet: true,
reviewed: true,
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &common.AutogitConfig{
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
Branch: "branch",
Organization: "foo",
GitProjectName: "barPrj",
ManualMergeOnly: true,
})
},
},
{
name: "Manual review is done, via PrjGit",
data: []prdata{
{
pr: &models.PullRequest{Body: "PR: foo/repo#20", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
reviews: []*models.PullReview{
{Body: "merge ok", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: common.Bot_BuildReview}, State: common.ReviewStateApproved},
},
},
{
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
},
},
},
resLen: 2,
prjGitPRIndex: 0,
consistentSet: true,
reviewed: true,
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &common.AutogitConfig{
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
Branch: "branch",
Organization: "foo",
GitProjectName: "barPrj",
ManualMergeOnly: true,
ManualMergeProject: true,
})
},
},
{
name: "Manual review is not done, via PrjGit",
data: []prdata{
{
pr: &models.PullRequest{Body: "PR: foo/repo#20", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
reviews: []*models.PullReview{
{Body: "merge ok", User: &models.User{UserName: "notm2"}, State: common.ReviewStateApproved},
{Body: "merge not ok", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: common.Bot_BuildReview}, State: common.ReviewStateApproved},
},
},
{
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
},
},
},
resLen: 2,
prjGitPRIndex: 0,
consistentSet: true,
reviewed: false,
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &common.AutogitConfig{
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
Branch: "branch",
Organization: "foo",
GitProjectName: "barPrj",
ManualMergeOnly: true,
ManualMergeProject: true,
})
},
},
{
name: "Manual review is done via PackageGit",
data: []prdata{
{
pr: &models.PullRequest{Body: "PR: foo/repo#20", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: common.Bot_BuildReview}, State: common.ReviewStateApproved},
},
},
{
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
{Body: "Merge ok", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
},
},
},
resLen: 2,
prjGitPRIndex: 0,
consistentSet: true,
reviewed: true,
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &common.AutogitConfig{
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
Branch: "branch",
Organization: "foo",
GitProjectName: "barPrj",
ManualMergeOnly: true,
})
},
},
{
name: "Manual review done via PkgGits",
data: []prdata{
{
pr: &models.PullRequest{Body: "PR: foo/repo#20\nPR: foo/repo#21", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: common.Bot_BuildReview}, State: common.ReviewStateApproved},
},
},
{
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
{Body: "Merge OK!", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
},
},
{
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 21, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
{Body: "merge ok", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
},
},
},
resLen: 3,
prjGitPRIndex: 0,
consistentSet: true,
reviewed: true,
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &common.AutogitConfig{
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
Branch: "branch",
Organization: "foo",
GitProjectName: "barPrj",
ManualMergeOnly: true,
})
},
},
{
name: "Manual review done via PkgGits not allowed",
data: []prdata{
{
pr: &models.PullRequest{Body: "PR: foo/repo#20\nPR: foo/repo#21", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: common.Bot_BuildReview}, State: common.ReviewStateApproved},
},
},
{
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
{Body: "Merge OK!", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
},
},
{
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 21, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
{Body: "merge ok", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
},
},
},
resLen: 3,
prjGitPRIndex: 0,
consistentSet: true,
reviewed: false,
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &common.AutogitConfig{
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
Branch: "branch",
Organization: "foo",
GitProjectName: "barPrj",
ManualMergeOnly: true,
ManualMergeProject: true,
})
},
},
{
name: "Manual review is is missing on one PR",
data: []prdata{
{
pr: &models.PullRequest{Body: "PR: foo/repo#20\nPR: foo/repo#21", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: common.Bot_BuildReview}, State: common.ReviewStateApproved},
},
},
{
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
},
},
{
pr: &models.PullRequest{Body: "PR: foo/barPrj#42", Index: 21, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
},
},
},
resLen: 3,
prjGitPRIndex: 0,
consistentSet: true,
reviewed: false,
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &common.AutogitConfig{
Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
Branch: "branch",
Organization: "foo",
GitProjectName: "barPrj",
ManualMergeOnly: true,
})
},
},
{
name: "PR is approved with negative optional review",
data: []prdata{
{
pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "opened"},
reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "super2"}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: common.Bot_BuildReview}, State: common.ReviewStateApproved},
{Body: "LGTM", User: &models.User{UserName: "bot"}, State: common.ReviewStateRequestChanges},
},
},
},
resLen: 1,
prjGitPRIndex: 0,
consistentSet: true,
reviewed: true,
reviewSetFetcher: func(mock *mock_common.MockGiteaPRTimelineFetcher) (*common.PRSet, error) {
config := common.AutogitConfig{
Reviewers: []string{"+super1", "*super2", "m1", "-m2", "~*bot"},
Branch: "branch",
Organization: "foo",
GitProjectName: "barPrj",
}
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &config)
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctl := gomock.NewController(t)
pr_mock := mock_common.NewMockGiteaPRTimelineFetcher(ctl)
review_mock := mock_common.NewMockGiteaPRChecker(ctl)
// reviewer_mock := mock_common.NewMockGiteaReviewRequester(ctl)
if test.reviewSetFetcher == nil { // if we are fetching the prjgit directly, the these mocks are not called
if test.prjGitPRIndex >= 0 {
pr_mock.EXPECT().GetPullRequest(baseConfig.Organization, baseConfig.GitProjectName, test.prjGitPRIndex).
Return(test.data[test.prjGitPRIndex].pr, test.data[test.prjGitPRIndex].pr_err)
} else if test.prjGitPRIndex < 0 {
// no prjgit PR
pr_mock.EXPECT().GetPullRequest(baseConfig.Organization, baseConfig.GitProjectName, gomock.Any()).
Return(nil, nil)
}
}
var test_err error
for _, data := range test.data {
pr_mock.EXPECT().GetPullRequest(data.pr.Base.Repo.Owner.UserName, data.pr.Base.Repo.Name, data.pr.Index).Return(data.pr, data.pr_err).AnyTimes()
if data.pr_err != nil {
test_err = data.pr_err
}
review_mock.EXPECT().GetPullRequestReviews(data.pr.Base.Repo.Owner.UserName, data.pr.Base.Repo.Name, data.pr.Index).Return(data.reviews, data.review_error).AnyTimes()
if data.timeline == nil {
data.timeline = reviewsToTimeline(data.reviews)
}
review_mock.EXPECT().GetTimeline(data.pr.Base.Repo.Owner.UserName, data.pr.Base.Repo.Name, data.pr.Index).Return(data.timeline, nil).AnyTimes()
}
var res *common.PRSet
var err error
if test.reviewSetFetcher != nil {
res, err = test.reviewSetFetcher(pr_mock)
} else {
res, err = common.FetchPRSet("test", pr_mock, "test", "repo", 42, &baseConfig)
}
if err == nil {
if test_err != nil {
t.Fatal("Expected", test_err, "but got", err)
}
} else {
if res != nil {
t.Fatal("error but got ReviewSet?")
}
if test.api_error != "" {
if err.Error() != test.api_error {
t.Fatal("expected", test.api_error, "but got", err)
}
} else if test_err != err {
t.Fatal("expected", test_err, "but got", err)
}
return
}
if test.resLen != len(res.PRs) {
t.Error("expected result len", test.resLen, "but got", len(res.PRs))
}
PrjGitPR, err := res.GetPrjGitPR()
if test.prjGitPRIndex < 0 {
if err == nil {
t.Error("expected error, but nothing")
}
}
pr_found := false
if test.prjGitPRIndex >= 0 {
for i := range test.data {
if PrjGitPR.PR == test.data[i].pr && i == test.prjGitPRIndex {
t.Log("found at index", i)
pr_found = true
}
}
if !pr_found {
t.Error("Cannot find expected PrjGit location in PR set", PrjGitPR)
}
} else {
if PrjGitPR != nil {
t.Log("Expected prjgit not found, but found?", PrjGitPR)
}
}
if isConsistent := res.IsConsistent(); isConsistent != test.consistentSet {
t.Error("IsConsistent() returned unexpected:", isConsistent)
}
/*
if err := res.AssignReviewers(reviewer_mock); err != nil {
t.Error("expected no errors assigning reviewers:", err)
}
*/
maintainers := mock_common.NewMockMaintainershipData(ctl)
maintainers.EXPECT().ListPackageMaintainers(gomock.Any()).Return([]string{}).AnyTimes()
maintainers.EXPECT().ListProjectMaintainers().Return([]string{}).AnyTimes()
maintainers.EXPECT().IsApproved(gomock.Any(), gomock.Any(), gomock.Any()).Return(true).AnyTimes()
if isApproved := res.IsApproved(review_mock, maintainers); isApproved != test.reviewed {
t.Error("expected reviewed to be NOT", isApproved)
}
})
}
}
func TestPRAssignReviewers(t *testing.T) {
tests := []struct {
name string
config common.AutogitConfig
reviewers []struct {
org, repo string
num int64
reviewer string
}
pkgReviews []*models.PullReview
pkgTimeline []*models.TimelineComment
prjReviews []*models.PullReview
prjTimeline []*models.TimelineComment
expectedReviewerCall [2][]string
}{
{
name: "No reviewers",
config: common.AutogitConfig{
GitProjectName: "repo",
Organization: "org",
Branch: "main",
Reviewers: []string{},
},
expectedReviewerCall: [2][]string{{"autogits_obs_staging_bot"}, {"prjmaintainer", "pkgmaintainer"}},
},
{
name: "One project reviewer only",
config: common.AutogitConfig{
GitProjectName: "repo",
Organization: "org",
Branch: "main",
Reviewers: []string{"-user1"},
},
expectedReviewerCall: [2][]string{{"user1", "autogits_obs_staging_bot"}, {"prjmaintainer", "pkgmaintainer"}},
},
{
name: "One project reviewer and one pkg reviewer only",
config: common.AutogitConfig{
GitProjectName: "repo",
Organization: "org",
Branch: "main",
Reviewers: []string{"-user1", "user2"},
},
expectedReviewerCall: [2][]string{{"user1", "autogits_obs_staging_bot"}, {"user2", "prjmaintainer", "pkgmaintainer"}},
},
{
name: "No need to get reviews of submitter",
config: common.AutogitConfig{
GitProjectName: "repo",
Organization: "org",
Branch: "main",
Reviewers: []string{"-user1", "submitter"},
},
expectedReviewerCall: [2][]string{{"user1", "autogits_obs_staging_bot"}, {"prjmaintainer", "pkgmaintainer"}},
},
{
name: "Reviews are done",
config: common.AutogitConfig{
GitProjectName: "repo",
Organization: "org",
Branch: "main",
Reviewers: []string{"-user1", "user2"},
},
pkgReviews: []*models.PullReview{
{
State: common.ReviewStateApproved,
User: &models.User{UserName: "user2"},
},
{
State: common.ReviewStateApproved,
User: &models.User{UserName: "pkgmaintainer"},
},
{
State: common.ReviewStatePending,
User: &models.User{UserName: "prjmaintainer"},
},
},
prjReviews: []*models.PullReview{
{
State: common.ReviewStateRequestChanges,
User: &models.User{UserName: "user1"},
},
{
State: common.ReviewStateRequestReview,
User: &models.User{UserName: "autogits_obs_staging_bot"},
},
},
expectedReviewerCall: [2][]string{},
},
{
name: "Stale review is not done, re-request it",
config: common.AutogitConfig{
GitProjectName: "repo",
Organization: "org",
Branch: "main",
Reviewers: []string{"-user1", "user2"},
},
pkgReviews: []*models.PullReview{
{
State: common.ReviewStateApproved,
User: &models.User{UserName: "user2"},
},
{
State: common.ReviewStatePending,
User: &models.User{UserName: "prjmaintainer"},
},
},
prjReviews: []*models.PullReview{
{
State: common.ReviewStateRequestChanges,
User: &models.User{UserName: "user1"},
Stale: true,
},
{
State: common.ReviewStateRequestReview,
Stale: true,
User: &models.User{UserName: "autogits_obs_staging_bot"},
},
},
expectedReviewerCall: [2][]string{{"user1", "autogits_obs_staging_bot"}, {"pkgmaintainer"}},
},
{
name: "Stale optional review is not done, re-request it",
config: common.AutogitConfig{
GitProjectName: "repo",
Organization: "org",
Branch: "main",
Reviewers: []string{"-user1", "user2", "~bot"},
},
pkgReviews: []*models.PullReview{
{
State: common.ReviewStateApproved,
User: &models.User{UserName: "bot"},
Stale: true,
},
{
State: common.ReviewStateApproved,
User: &models.User{UserName: "user2"},
},
{
State: common.ReviewStatePending,
User: &models.User{UserName: "prjmaintainer"},
},
},
prjReviews: []*models.PullReview{
{
State: common.ReviewStateRequestChanges,
User: &models.User{UserName: "user1"},
Stale: true,
},
{
State: common.ReviewStateRequestReview,
Stale: true,
User: &models.User{UserName: "autogits_obs_staging_bot"},
},
},
expectedReviewerCall: [2][]string{{"user1", "autogits_obs_staging_bot"}, {"pkgmaintainer", "bot"}},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctl := gomock.NewController(t)
pr_mock := mock_common.NewMockGiteaPRTimelineFetcher(ctl)
review_mock := mock_common.NewMockGiteaReviewFetcherAndRequester(ctl)
maintainership_mock := mock_common.NewMockMaintainershipData(ctl)
if test.pkgTimeline == nil {
test.pkgTimeline = reviewsToTimeline(test.pkgReviews)
}
if test.prjTimeline == nil {
test.prjTimeline = reviewsToTimeline(test.prjReviews)
}
pr_mock.EXPECT().GetPullRequest("other", "pkgrepo", int64(1)).Return(&models.PullRequest{
Body: "Some description is here",
User: &models.User{UserName: "submitter"},
RequestedReviewers: []*models.User{},
Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "pkgrepo", Owner: &models.User{UserName: "other"}}},
Head: &models.PRBranchInfo{},
Index: 1,
}, nil)
review_mock.EXPECT().GetPullRequestReviews("other", "pkgrepo", int64(1)).Return(test.pkgReviews, nil)
review_mock.EXPECT().GetTimeline("other", "pkgrepo", int64(1)).Return(test.pkgTimeline, nil)
pr_mock.EXPECT().GetPullRequest("org", "repo", int64(1)).Return(&models.PullRequest{
Body: fmt.Sprintf(common.PrPattern, "other", "pkgrepo", 1),
User: &models.User{UserName: "bot1"},
RequestedReviewers: []*models.User{{UserName: "main_reviewer"}},
Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "org"}}},
Head: &models.PRBranchInfo{},
Index: 42,
}, nil)
review_mock.EXPECT().GetPullRequestReviews("org", "repo", int64(42)).Return(test.prjReviews, nil)
review_mock.EXPECT().GetTimeline("org", "repo", int64(42)).Return(test.prjTimeline, nil)
maintainership_mock.EXPECT().ListProjectMaintainers().Return([]string{"prjmaintainer"}).AnyTimes()
maintainership_mock.EXPECT().ListPackageMaintainers("pkgrepo").Return([]string{"pkgmaintainer"}).AnyTimes()
prs, _ := common.FetchPRSet("test", pr_mock, "other", "pkgrepo", int64(1), &test.config)
if len(prs.PRs) != 2 {
t.Fatal("PRs not fetched")
}
for _, pr := range prs.PRs {
r := test.expectedReviewerCall[0]
if !prs.IsPrjGitPR(pr.PR) {
r = test.expectedReviewerCall[1]
}
slices.Sort(r)
for _, reviewer := range r {
review_mock.EXPECT().RequestReviews(pr.PR, reviewer).Return(nil, nil)
}
}
prs.AssignReviewers(review_mock, maintainership_mock)
})
}
prjgit_tests := []struct {
name string
config common.AutogitConfig
reviewers []struct {
org, repo string
num int64
reviewer string
}
prjReviews []*models.PullReview
expectedReviewerCall [2][]string
}{
{
name: "PrjMaintainers in prjgit review when not part of pkg set",
config: common.AutogitConfig{
GitProjectName: "repo",
Organization: "org",
Branch: "main",
Reviewers: []string{},
},
expectedReviewerCall: [2][]string{{"autogits_obs_staging_bot", "prjmaintainer"}},
},
}
for _, test := range prjgit_tests {
t.Run(test.name, func(t *testing.T) {
ctl := gomock.NewController(t)
pr_mock := mock_common.NewMockGiteaPRTimelineFetcher(ctl)
review_mock := mock_common.NewMockGiteaReviewFetcherAndRequester(ctl)
maintainership_mock := mock_common.NewMockMaintainershipData(ctl)
pr_mock.EXPECT().GetPullRequest("org", "repo", int64(1)).Return(&models.PullRequest{
Body: "Some description is here",
User: &models.User{UserName: "submitter"},
RequestedReviewers: []*models.User{},
Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "org"}}},
Head: &models.PRBranchInfo{},
Index: 1,
}, nil)
review_mock.EXPECT().GetPullRequestReviews("org", "repo", int64(1)).Return(test.prjReviews, nil)
review_mock.EXPECT().GetTimeline("org", "repo", int64(1)).Return(nil, nil)
maintainership_mock.EXPECT().ListProjectMaintainers().Return([]string{"prjmaintainer"}).AnyTimes()
prs, _ := common.FetchPRSet("test", pr_mock, "org", "repo", int64(1), &test.config)
if len(prs.PRs) != 1 {
t.Fatal("PRs not fetched")
}
for _, pr := range prs.PRs {
r := test.expectedReviewerCall[0]
if !prs.IsPrjGitPR(pr.PR) {
t.Fatal("only prjgit pr here")
}
for _, reviewer := range r {
review_mock.EXPECT().RequestReviews(pr.PR, reviewer).Return(nil, nil)
}
}
prs.AssignReviewers(review_mock, maintainership_mock)
})
}
}
func TestPRMerge(t *testing.T) {
repoDir := t.TempDir()
cwd, _ := os.Getwd()
cmd := exec.Command(path.Join(cwd, "test_repo_setup.sh"))
cmd.Dir = repoDir
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatal(string(out))
}
common.ExtraGitParams = []string{
"GIT_CONFIG_COUNT=1",
"GIT_CONFIG_KEY_0=protocol.file.allow",
"GIT_CONFIG_VALUE_0=always",
"GIT_AUTHOR_NAME=testname",
"GIT_AUTHOR_EMAIL=test@suse.com",
"GIT_AUTHOR_DATE='2005-04-07T22:13:13'",
"GIT_COMMITTER_NAME=testname",
"GIT_COMMITTER_EMAIL=test@suse.com",
"GIT_COMMITTER_DATE='2005-04-07T22:13:13'",
}
config := &common.AutogitConfig{
Organization: "org",
GitProjectName: "prj",
}
tests := []struct {
name string
pr *models.PullRequest
mergeError string
}{
{
name: "Merge base not merged in main",
pr: &models.PullRequest{
Base: &models.PRBranchInfo{
Sha: "e8b0de43d757c96a9d2c7101f4bff404e322f53a1fa4041fb85d646110c38ad4", // "base_add_b1"
Repo: &models.Repository{
Name: "prj",
Owner: &models.User{
UserName: "org",
},
SSHURL: "file://" + path.Join(repoDir, "prjgit"),
},
},
Head: &models.PRBranchInfo{
Sha: "88584433de1c917c1d773f62b82381848d882491940b5e9b427a540aa9057d9a", // "base_add_b2"
},
},
mergeError: "Aborting merge",
},
{
name: "Merge conflict in modules, auto-resolved",
pr: &models.PullRequest{
Base: &models.PRBranchInfo{
Sha: "4fbd1026b2d7462ebe9229a49100c11f1ad6555520a21ba515122d8bc41328a8",
Repo: &models.Repository{
Name: "prj",
Owner: &models.User{
UserName: "org",
},
SSHURL: "file://" + path.Join(cmd.Dir, "prjgit"),
},
},
Head: &models.PRBranchInfo{
Sha: "88584433de1c917c1d773f62b82381848d882491940b5e9b427a540aa9057d9a", // "base_add_b2"
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctl := gomock.NewController(t)
mock := mock_common.NewMockGiteaPRTimelineFetcher(ctl)
reviewUnrequestMock := mock_common.NewMockGiteaReviewUnrequester(ctl)
reviewUnrequestMock.EXPECT().UnrequestReview(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
testDir := t.TempDir()
t.Log("dir:", testDir)
mock.EXPECT().GetPullRequest("org", "prj", int64(1)).Return(test.pr, nil)
set, err := common.FetchPRSet("test", mock, "org", "prj", 1, config)
if err != nil {
t.Fatal(err)
}
gh, _ := common.AllocateGitWorkTree(testDir, "", "")
git, err := gh.CreateGitHandler("org")
err = set.Merge(reviewUnrequestMock, git)
if err != nil && (test.mergeError == "" || (len(test.mergeError) > 0 && !strings.Contains(err.Error(), test.mergeError))) {
os.CopyFS("/tmp/upstream", os.DirFS(repoDir))
os.CopyFS("/tmp/out", os.DirFS(testDir))
t.Fatal(err)
}
})
}
}
func TestPRChanges(t *testing.T) {
tests := []struct {
name string
PRs []*models.PullRequest
PrjPRs *models.PullRequest
}{
{
name: "Pkg PR is closed",
PRs: []*models.PullRequest{
{
Base: &models.PRBranchInfo{Repo: &models.Repository{Owner: &models.User{UserName: "org"}, Name: "repo"}},
Index: 42,
State: "merged",
},
},
PrjPRs: &models.PullRequest{
Title: "some PR",
Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "prjgit", Owner: &models.User{UserName: "org"}}},
Body: "PR: org/repo#42",
State: "opened",
},
},
}
config := common.AutogitConfig{
Branch: "main",
GitProjectName: "org/prjgit#branch",
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctl := gomock.NewController(t)
mock_fetcher := mock_common.NewMockGiteaPRTimelineFetcher(ctl)
mock_fetcher.EXPECT().GetPullRequest("org", "prjgit", int64(42)).Return(test.PrjPRs, nil)
for _, pr := range test.PRs {
mock_fetcher.EXPECT().GetPullRequest(pr.Base.Repo.Owner.UserName, pr.Base.Repo.Name, pr.Index).Return(pr, nil)
}
PRs, err := common.FetchPRSet("user", mock_fetcher, "org", "repo", 42, &config)
if err != nil {
t.Fatal(err)
}
if PRs.IsConsistent() {
t.Fatal("Inconsistent set!")
}
})
}
}