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 TestCockpit(t *testing.T) { common.SetLoggingLevel(common.LogLevelDebug) gitea := common.AllocateGiteaTransport("https://src.opensuse.org") tl, err := gitea.GetTimeline("cockpit", "cockpit", 29) if err != nil { t.Fatal("Fail to timeline", err) } t.Log(tl) r, err := common.FetchGiteaReviews(gitea, []string{}, "cockpit", "cockpit", 29) if err != nil { t.Fatal("Error:", err) } t.Error(r) } */ 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) { return baseConfig := common.AutogitConfig{ Reviewers: []string{"+super1", "*super2", "m1", "-m2"}, Branch: "branch", Organization: "foo", GitProjectName: "foo/barPrj#master", } 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.MockGiteaPRTimelineReviewFetcher) (*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.MockGiteaPRTimelineReviewFetcher) (*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.MockGiteaPRTimelineReviewFetcher) (*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.MockGiteaPRTimelineReviewFetcher) (*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.MockGiteaPRTimelineReviewFetcher) (*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.MockGiteaPRTimelineReviewFetcher) (*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.MockGiteaPRTimelineReviewFetcher) (*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.MockGiteaPRTimelineReviewFetcher) (*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.MockGiteaPRTimelineReviewFetcher) (*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.MockGiteaPRTimelineReviewFetcher) (*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.MockGiteaPRTimelineReviewFetcher) (*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.MockGiteaPRTimelineReviewFetcher) (*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.NewMockGiteaPRTimelineReviewFetcher(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(), gomock.Any()).Return([]string{}).AnyTimes() maintainers.EXPECT().ListProjectMaintainers(gomock.Any()).Return([]string{}).AnyTimes() maintainers.EXPECT().IsApproved(gomock.Any(), 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 TestFindMissingAndExtraReviewers(t *testing.T) { tests := []struct { name string reviewers []struct { org, repo string num int64 reviewer string } prset *common.PRSet maintainers common.MaintainershipData noAutoStaging bool expected_missing_reviewers [][]string expected_extra_reviewers [][]string }{ { name: "No reviewers", prset: &common.PRSet{ PRs: []*common.PRInfo{ { PR: &models.PullRequest{ User: &models.User{UserName: "foo"}, Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}}, }, Reviews: &common.PRReviews{}, }, }, Config: &common.AutogitConfig{ GitProjectName: "prg/repo#main", Organization: "org", Branch: "main", Reviewers: []string{}, }, }, maintainers: &common.MaintainershipMap{Data: map[string][]string{}}, }, { name: "One project reviewer only", prset: &common.PRSet{ PRs: []*common.PRInfo{ { PR: &models.PullRequest{ User: &models.User{UserName: "foo"}, Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}}, }, Reviews: &common.PRReviews{}, }, { PR: &models.PullRequest{ User: &models.User{UserName: "foo"}, Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "prg"}}}, }, Reviews: &common.PRReviews{}, }, }, Config: &common.AutogitConfig{ GitProjectName: "prg/repo#main", Organization: "org", Branch: "main", Reviewers: []string{"-user1"}, }, }, maintainers: &common.MaintainershipMap{Data: map[string][]string{}}, expected_missing_reviewers: [][]string{ []string{}, []string{"autogits_obs_staging_bot", "user1"}, }, }, { name: "One project reviewer only and no auto staging", noAutoStaging: true, prset: &common.PRSet{ PRs: []*common.PRInfo{ { PR: &models.PullRequest{ User: &models.User{UserName: "foo"}, Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}}, }, Reviews: &common.PRReviews{}, }, { PR: &models.PullRequest{ User: &models.User{UserName: "foo"}, Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "prg"}}}, }, Reviews: &common.PRReviews{}, }, }, Config: &common.AutogitConfig{ GitProjectName: "prg/repo#main", Organization: "org", Branch: "main", Reviewers: []string{"-user1"}, }, }, maintainers: &common.MaintainershipMap{Data: map[string][]string{}}, expected_missing_reviewers: [][]string{ nil, {"user1"}, }, }, { name: "One project reviewer and one pkg reviewer only", prset: &common.PRSet{ PRs: []*common.PRInfo{ { PR: &models.PullRequest{ User: &models.User{UserName: "foo"}, Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}}, }, Reviews: &common.PRReviews{}, }, { PR: &models.PullRequest{ User: &models.User{UserName: "foo"}, Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "prg"}}}, }, Reviews: &common.PRReviews{}, }, }, Config: &common.AutogitConfig{ GitProjectName: "prg/repo#main", Organization: "org", Branch: "main", Reviewers: []string{"-user1", "user2"}, }, }, maintainers: &common.MaintainershipMap{Data: map[string][]string{}}, expected_missing_reviewers: [][]string{ []string{"user2"}, []string{"autogits_obs_staging_bot", "user1"}, }, }, { name: "No need to get reviews of submitter reviewer", prset: &common.PRSet{ PRs: []*common.PRInfo{ { PR: &models.PullRequest{ User: &models.User{UserName: "submitter"}, Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}}, }, Reviews: &common.PRReviews{ Reviews: []*models.PullReview{{State: common.ReviewStateRequestReview, User: &models.User{UserName: "m1"}}}, RequestedReviewers: []string{"m1"}, FullTimeline: []*models.TimelineComment{ {User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "m1"}, Type: common.TimelineCommentType_ReviewRequested}, }, }, }, { PR: &models.PullRequest{ User: &models.User{UserName: "foo"}, Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "prg"}}}, }, Reviews: &common.PRReviews{}, }, }, Config: &common.AutogitConfig{ GitProjectName: "prg/repo#main", Organization: "org", Branch: "main", Reviewers: []string{"-user1", "submitter"}, }, BotUser: "bot", }, maintainers: &common.MaintainershipMap{Data: map[string][]string{"pkg": []string{"m1", "submitter"}}}, expected_missing_reviewers: [][]string{ nil, {"autogits_obs_staging_bot", "user1"}, }, expected_extra_reviewers: [][]string{ {"m1"}, }, }, { name: "No need to get reviews of submitter maintainer", prset: &common.PRSet{ PRs: []*common.PRInfo{ { PR: &models.PullRequest{ User: &models.User{UserName: "submitter"}, Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}}, }, Reviews: &common.PRReviews{}, }, { PR: &models.PullRequest{ User: &models.User{UserName: "foo"}, Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "prg"}}}, }, Reviews: &common.PRReviews{}, }, }, Config: &common.AutogitConfig{ GitProjectName: "prg/repo#main", Organization: "org", Branch: "main", Reviewers: []string{"-user1", "submitter"}, }, }, maintainers: &common.MaintainershipMap{Data: map[string][]string{"pkg": []string{"submitter"}}}, expected_missing_reviewers: [][]string{ []string{}, []string{"autogits_obs_staging_bot", "user1"}, }, }, { name: "Add reviewer if also maintainer where review by maintainer is not needed", prset: &common.PRSet{ PRs: []*common.PRInfo{ { PR: &models.PullRequest{ User: &models.User{UserName: "submitter"}, Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}}, }, Reviews: &common.PRReviews{}, }, { PR: &models.PullRequest{ User: &models.User{UserName: "bot"}, Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "prg"}}}, }, Reviews: &common.PRReviews{}, }, }, Config: &common.AutogitConfig{ GitProjectName: "prg/repo#main", Organization: "org", Branch: "main", Reviewers: []string{"-user1", "submitter", "*reviewer"}, }, BotUser: "bot", }, maintainers: &common.MaintainershipMap{Data: map[string][]string{"pkg": []string{"submitter", "reviewer"}, "": []string{"reviewer"}}}, expected_missing_reviewers: [][]string{ []string{"reviewer"}, []string{"autogits_obs_staging_bot", "reviewer", "user1"}, }, }, { name: "Dont remove reviewer if also maintainer", prset: &common.PRSet{ PRs: []*common.PRInfo{ { PR: &models.PullRequest{ User: &models.User{UserName: "submitter"}, Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}}, }, Reviews: &common.PRReviews{ Reviews: []*models.PullReview{{State: common.ReviewStateRequestReview, User: &models.User{UserName: "reviewer"}}}, RequestedReviewers: []string{"reviewer"}, FullTimeline: []*models.TimelineComment{{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "reviewer"}}}, }, }, { PR: &models.PullRequest{ User: &models.User{UserName: "bot"}, Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "prg"}}}, }, Reviews: &common.PRReviews{ Reviews: []*models.PullReview{{State: common.ReviewStateRequestReview, User: &models.User{UserName: "reviewer"}}}, RequestedReviewers: []string{"reviewer"}, FullTimeline: []*models.TimelineComment{{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "reviewer"}}}, }, }, }, Config: &common.AutogitConfig{ GitProjectName: "prg/repo#main", Organization: "org", Branch: "main", Reviewers: []string{"-user1", "submitter", "*reviewer"}, }, BotUser: "bot", }, maintainers: &common.MaintainershipMap{Data: map[string][]string{"pkg": []string{"submitter", "reviewer"}, "": []string{"reviewer"}}}, expected_missing_reviewers: [][]string{ []string{}, []string{"autogits_obs_staging_bot", "user1"}, }, }, { name: "Extra project reviewer on the package", prset: &common.PRSet{ PRs: []*common.PRInfo{ { PR: &models.PullRequest{ User: &models.User{UserName: "submitter"}, Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}}, }, Reviews: &common.PRReviews{ Reviews: []*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"}}, }, RequestedReviewers: []string{"user2", "pkgmaintainer", "prjmaintainer"}, FullTimeline: []*models.TimelineComment{ {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "user2"}}, {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "pkgmaintainer"}}, {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prjmaintainer"}}, }, }, }, { PR: &models.PullRequest{ User: &models.User{UserName: "bot"}, Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "prg"}}}, }, Reviews: &common.PRReviews{ Reviews: []*models.PullReview{ {State: common.ReviewStateRequestChanges, User: &models.User{UserName: "user1"}}, {State: common.ReviewStateRequestReview, User: &models.User{UserName: "autogits_obs_staging_bot"}}, }, RequestedReviewers: []string{"user1", "autogits_obs_staging_bot"}, FullTimeline: []*models.TimelineComment{ {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "user1"}}, {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "autogits_obs_staging_bot"}}, }, }, }, }, Config: &common.AutogitConfig{ GitProjectName: "prg/repo#main", Organization: "org", Branch: "main", Reviewers: []string{"-user1", "submitter"}, }, BotUser: "bot", }, maintainers: &common.MaintainershipMap{Data: map[string][]string{"pkg": []string{"pkgmaintainer"}, "": {"prjmaintainer"}}}, expected_missing_reviewers: [][]string{}, expected_extra_reviewers: [][]string{{"prjmaintainer"}}, }, { name: "Extra project reviewers on the package and project", prset: &common.PRSet{ PRs: []*common.PRInfo{ { PR: &models.PullRequest{ User: &models.User{UserName: "submitter"}, Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}}, }, Reviews: &common.PRReviews{ Reviews: []*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"}}, {State: common.ReviewStatePending, User: &models.User{UserName: "pkgm1"}}, {State: common.ReviewStatePending, User: &models.User{UserName: "pkgm2"}}, {State: common.ReviewStatePending, User: &models.User{UserName: "prj1"}}, {State: common.ReviewStatePending, User: &models.User{UserName: "prj2"}}, {State: common.ReviewStatePending, User: &models.User{UserName: "someother"}}, }, RequestedReviewers: []string{"user2", "pkgmaintainer", "prjmaintainer", "pkgm1", "pkgm2", "someother", "prj1", "prj2"}, FullTimeline: []*models.TimelineComment{ {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "pkgmaintainer"}}, {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prjmaintainer"}}, {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prj1"}}, {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prj2"}}, {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "pkgm1"}}, {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "pkgm2"}}, }, }, }, { PR: &models.PullRequest{ User: &models.User{UserName: "bot"}, Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "prg"}}}, }, Reviews: &common.PRReviews{ Reviews: []*models.PullReview{ {State: common.ReviewStateRequestChanges, User: &models.User{UserName: "user1"}}, {State: common.ReviewStateRequestReview, User: &models.User{UserName: "autogits_obs_staging_bot"}}, {State: common.ReviewStatePending, User: &models.User{UserName: "prj1"}}, {State: common.ReviewStatePending, User: &models.User{UserName: "prj2"}}, }, RequestedReviewers: []string{"user1", "autogits_obs_staging_bot", "prj1", "prj2"}, FullTimeline: []*models.TimelineComment{ {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "autogits_obs_staging_bot"}}, {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prj1"}}, {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prj2"}}, }, }, }, }, Config: &common.AutogitConfig{ GitProjectName: "prg/repo#main", Organization: "org", Branch: "main", Reviewers: []string{"-user1", "submitter"}, }, BotUser: "bot", }, maintainers: &common.MaintainershipMap{Data: map[string][]string{"pkg": []string{"pkgmaintainer", "pkgm1", "pkgm2"}, "": {"prjmaintainer", "prj1", "prj2"}}}, expected_missing_reviewers: [][]string{}, expected_extra_reviewers: [][]string{{"pkgm1", "pkgm2", "prj1", "prj2", "prjmaintainer"}, {"prj1", "prj2"}}, }, { name: "No extra project reviewers on the package and project (all pending)", prset: &common.PRSet{ PRs: []*common.PRInfo{ { PR: &models.PullRequest{ User: &models.User{UserName: "submitter"}, Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}}, }, Reviews: &common.PRReviews{ Reviews: []*models.PullReview{ {State: common.ReviewStateApproved, User: &models.User{UserName: "user2"}}, {State: common.ReviewStateRequestReview, User: &models.User{UserName: "pkgmaintainer"}}, {State: common.ReviewStateRequestReview, User: &models.User{UserName: "prjmaintainer"}}, {State: common.ReviewStateRequestReview, User: &models.User{UserName: "pkgm1"}}, {State: common.ReviewStateRequestReview, User: &models.User{UserName: "prj1"}}, {State: common.ReviewStateRequestReview, User: &models.User{UserName: "someother"}}, }, RequestedReviewers: []string{"user2", "pkgmaintainer", "prjmaintainer", "pkgm1", "someother", "prj1"}, FullTimeline: []*models.TimelineComment{ {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "pkgm1"}}, {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "pkgmaintainer"}}, {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prjmaintainer"}}, {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prj1"}}, {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "!bot"}, Assignee: &models.User{UserName: "someother"}}, }, }, }, { PR: &models.PullRequest{ User: &models.User{UserName: "bot"}, Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "prj"}}}, }, Reviews: &common.PRReviews{ Reviews: []*models.PullReview{ {State: common.ReviewStateRequestChanges, User: &models.User{UserName: "user1"}}, {State: common.ReviewStateRequestReview, User: &models.User{UserName: "autogits_obs_staging_bot"}}, {State: common.ReviewStateRequestReview, User: &models.User{UserName: "prj1"}}, }, RequestedReviewers: []string{"user1", "autogits_obs_staging_bot", "prj1"}, FullTimeline: []*models.TimelineComment{ {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "autogits_obs_staging_bot"}}, {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prj1"}}, {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "!bot"}, Assignee: &models.User{UserName: "user1"}}, }, }, }, }, Config: &common.AutogitConfig{ GitProjectName: "prj/repo#main", Organization: "org", Branch: "main", Reviewers: []string{"-user1", "submitter"}, }, BotUser: "bot", }, maintainers: &common.MaintainershipMap{Data: map[string][]string{"pkg": []string{"pkgmaintainer", "pkgm1", "pkgm2"}, "": {"prjmaintainer", "prj1", "prj2"}}}, expected_missing_reviewers: [][]string{{"pkgm2", "prj2"}}, expected_extra_reviewers: [][]string{{}, {"prj1"}}, }, { name: "Package maintainer submitter, AlwaysRequireReview=false", prset: &common.PRSet{ PRs: []*common.PRInfo{ { PR: &models.PullRequest{ User: &models.User{UserName: "pkgmaintainer"}, Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}}, }, Reviews: &common.PRReviews{}, }, }, Config: &common.AutogitConfig{ GitProjectName: "prg/repo#main", Organization: "org", Branch: "main", Reviewers: []string{}, ReviewRequired: false, }, }, maintainers: &common.MaintainershipMap{ Data: map[string][]string{ "pkg": {"pkgmaintainer", "pkgm1"}, }, }, noAutoStaging: true, expected_missing_reviewers: [][]string{ {}, }, }, { name: "Package maintainer submitter, AlwaysRequireReview=true", prset: &common.PRSet{ PRs: []*common.PRInfo{ { PR: &models.PullRequest{ User: &models.User{UserName: "pkgmaintainer"}, Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}}, }, Reviews: &common.PRReviews{}, }, }, Config: &common.AutogitConfig{ GitProjectName: "prg/repo#main", Organization: "org", Branch: "main", Reviewers: []string{}, ReviewRequired: true, }, }, maintainers: &common.MaintainershipMap{ Data: map[string][]string{ "pkg": {"pkgmaintainer", "pkgm1"}, }, }, noAutoStaging: true, expected_missing_reviewers: [][]string{ {"pkgm1"}, }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { test.prset.HasAutoStaging = !test.noAutoStaging for idx, pr := range test.prset.PRs { missing, extra := test.prset.FindMissingAndExtraReviewers(test.maintainers, idx) // avoid nil dereference below, by adding empty array elements if idx >= len(test.expected_missing_reviewers) { test.expected_missing_reviewers = append(test.expected_missing_reviewers, nil) } if idx >= len(test.expected_extra_reviewers) { test.expected_extra_reviewers = append(test.expected_extra_reviewers, nil) } slices.Sort(test.expected_extra_reviewers[idx]) slices.Sort(test.expected_missing_reviewers[idx]) if slices.Compare(missing, test.expected_missing_reviewers[idx]) != 0 { t.Error("Expected missing reviewers for", common.PRtoString(pr.PR), ":", test.expected_missing_reviewers[idx], "but have:", missing) } if slices.Compare(extra, test.expected_extra_reviewers[idx]) != 0 { t.Error("Expected reviewers to remove for", common.PRtoString(pr.PR), ":", test.expected_extra_reviewers[idx], "but have:", extra) } } }) } } func TestPRMerge(t *testing.T) { t.Skip("FAIL: No PrjGit PR found, missing calls") 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: "org/prj#master", } 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: "ssh://git@src.opensuse.org/org/prj.git", }, }, 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: "ssh://git@src.opensuse.org/org/prj.git", }, }, 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.NewMockGiteaPRTimelineReviewFetcher(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) { t.Skip("FAIL: unexpected calls, missing calls") 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.NewMockGiteaPRTimelineReviewFetcher(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!") } }) } } func TestPRPrepareForMerge(t *testing.T) { tests := []struct { name string setup func(*mock_common.MockGit, *models.PullRequest, *models.PullRequest) config *common.AutogitConfig expected bool editable bool }{ { name: "Success Devel", config: &common.AutogitConfig{ Organization: "org", GitProjectName: "org/_ObsPrj#master", MergeMode: common.MergeModeDevel, }, setup: func(m *mock_common.MockGit, prjPR, pkgPR *models.PullRequest) {}, expected: true, }, { name: "Success FF", config: &common.AutogitConfig{ Organization: "org", GitProjectName: "org/_ObsPrj#master", MergeMode: common.MergeModeFF, }, setup: func(m *mock_common.MockGit, prjPR, pkgPR *models.PullRequest) { m.EXPECT().GitClone("pkg", "master", pkgPR.Base.Repo.SSHURL).Return("origin", nil) m.EXPECT().GitExecOrPanic("pkg", "fetch", "origin", pkgPR.Head.Sha) m.EXPECT().GitExec("pkg", "merge-base", "--is-ancestor", "HEAD", pkgPR.Head.Sha).Return(nil) m.EXPECT().GitClone("_ObsPrj", "master", prjPR.Base.Repo.SSHURL).Return("origin", nil) m.EXPECT().GitExecOrPanic("_ObsPrj", "fetch", "origin", prjPR.Head.Sha) m.EXPECT().GitExec("_ObsPrj", "checkout", "-B", "PR_1_mergetest", prjPR.Base.Sha).Return(nil) m.EXPECT().GitExec("_ObsPrj", "merge", "--no-ff", "--no-commit", prjPR.Head.Sha).Return(nil) }, expected: true, }, { name: "Success Replace MergeCommit", config: &common.AutogitConfig{ Organization: "org", GitProjectName: "org/_ObsPrj#master", MergeMode: common.MergeModeReplace, }, setup: func(m *mock_common.MockGit, prjPR, pkgPR *models.PullRequest) { m.EXPECT().GitClone("pkg", "master", pkgPR.Base.Repo.SSHURL).Return("origin", nil) m.EXPECT().GitExecOrPanic("pkg", "fetch", "origin", pkgPR.Head.Sha) // merge-base fails initially m.EXPECT().GitExec("pkg", "merge-base", "--is-ancestor", "HEAD", pkgPR.Head.Sha).Return(fmt.Errorf("not ancestor")) // HasMerge returns true m.EXPECT().GitExecWithOutput("pkg", "show", "-s", "--format=%P", pkgPR.Head.Sha).Return("parent1 target_head", nil) m.EXPECT().GitExecWithOutput("pkg", "rev-parse", "HEAD").Return("target_head", nil) m.EXPECT().GitClone("_ObsPrj", "master", prjPR.Base.Repo.SSHURL).Return("origin", nil) m.EXPECT().GitExecOrPanic("_ObsPrj", "fetch", "origin", prjPR.Head.Sha) m.EXPECT().GitExec("_ObsPrj", "checkout", "-B", "PR_1_mergetest", prjPR.Base.Sha).Return(nil) m.EXPECT().GitExec("_ObsPrj", "merge", "--no-ff", "--no-commit", prjPR.Head.Sha).Return(nil) }, expected: true, }, { name: "Merge Conflict in PrjGit", config: &common.AutogitConfig{ Organization: "org", GitProjectName: "org/_ObsPrj#master", MergeMode: common.MergeModeFF, }, setup: func(m *mock_common.MockGit, prjPR, pkgPR *models.PullRequest) { m.EXPECT().GitClone("pkg", "master", pkgPR.Base.Repo.SSHURL).Return("origin", nil) m.EXPECT().GitExecOrPanic("pkg", "fetch", "origin", pkgPR.Head.Sha) m.EXPECT().GitExec("pkg", "merge-base", "--is-ancestor", "HEAD", pkgPR.Head.Sha).Return(nil) m.EXPECT().GitClone("_ObsPrj", "master", prjPR.Base.Repo.SSHURL).Return("origin", nil) m.EXPECT().GitExecOrPanic("_ObsPrj", "fetch", "origin", prjPR.Head.Sha) m.EXPECT().GitExec("_ObsPrj", "checkout", "-B", "PR_1_mergetest", prjPR.Base.Sha).Return(nil) m.EXPECT().GitExec("_ObsPrj", "merge", "--no-ff", "--no-commit", prjPR.Head.Sha).Return(fmt.Errorf("conflict")) }, expected: false, }, { name: "Not FF in PkgGit", config: &common.AutogitConfig{ Organization: "org", GitProjectName: "org/_ObsPrj#master", MergeMode: common.MergeModeFF, }, setup: func(m *mock_common.MockGit, prjPR, pkgPR *models.PullRequest) { m.EXPECT().GitClone("pkg", "master", pkgPR.Base.Repo.SSHURL).Return("origin", nil) m.EXPECT().GitExecOrPanic("pkg", "fetch", "origin", pkgPR.Head.Sha) m.EXPECT().GitExec("pkg", "merge-base", "--is-ancestor", "HEAD", pkgPR.Head.Sha).Return(fmt.Errorf("not ancestor")) }, expected: false, }, { name: "Success Replace with AddMergeCommit", config: &common.AutogitConfig{ Organization: "org", GitProjectName: "org/_ObsPrj#master", MergeMode: common.MergeModeReplace, }, editable: true, setup: func(m *mock_common.MockGit, prjPR, pkgPR *models.PullRequest) { m.EXPECT().GitClone("pkg", "master", pkgPR.Base.Repo.SSHURL).Return("origin", nil) m.EXPECT().GitExecOrPanic("pkg", "fetch", "origin", pkgPR.Head.Sha) // First merge-base fails m.EXPECT().GitExec("pkg", "merge-base", "--is-ancestor", "HEAD", pkgPR.Head.Sha).Return(fmt.Errorf("not ancestor")) // HasMerge returns false m.EXPECT().GitExecWithOutput("pkg", "show", "-s", "--format=%P", pkgPR.Head.Sha).Return("parent1", nil) m.EXPECT().GitClone("pkg", pkgPR.Head.Name, pkgPR.Base.Repo.SSHURL).Return("origin_fork", nil) // AddMergeCommit is called m.EXPECT().GitExecOrPanic("pkg", "checkout", "origin/master") m.EXPECT().GitExec("pkg", "merge", "--no-ff", "--no-commit", "-X", "theirs", pkgPR.Head.Sha).Return(nil) m.EXPECT().GitExecOrPanic("pkg", "read-tree", "-m", pkgPR.Head.Sha) m.EXPECT().GitExecOrPanic("pkg", "commit", "-m", gomock.Any()) m.EXPECT().GitExecOrPanic("pkg", "clean", "-fxd") m.EXPECT().GitExecOrPanic("pkg", "push", "origin_fork", "HEAD:"+pkgPR.Head.Name) m.EXPECT().GitExecWithOutputOrPanic("pkg", "rev-list", "-1", "HEAD").Return("new_pkg_head_sha") // Second merge-base succeeds (after goto Verify) m.EXPECT().GitExec("pkg", "merge-base", "--is-ancestor", "HEAD", "new_pkg_head_sha").Return(nil) m.EXPECT().GitClone("_ObsPrj", "master", prjPR.Base.Repo.SSHURL).Return("origin", nil) m.EXPECT().GitExecOrPanic("_ObsPrj", "fetch", "origin", prjPR.Head.Sha) m.EXPECT().GitExec("_ObsPrj", "checkout", "-B", "PR_1_mergetest", prjPR.Base.Sha).Return(nil) m.EXPECT().GitExec("_ObsPrj", "merge", "--no-ff", "--no-commit", prjPR.Head.Sha).Return(nil) }, expected: true, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { prjPR := &models.PullRequest{ Index: 1, Base: &models.PRBranchInfo{ Name: "master", Sha: "base_sha", Repo: &models.Repository{ Owner: &models.User{UserName: "org"}, Name: "_ObsPrj", SSHURL: "ssh://git@src.opensuse.org/org/_ObsPrj.git", }, }, Head: &models.PRBranchInfo{ Sha: "head_sha", Repo: &models.Repository{ Owner: &models.User{UserName: "org"}, Name: "_ObsPrj", SSHURL: "ssh://git@src.opensuse.org/org/_ObsPrj.git", }, }, } pkgPR := &models.PullRequest{ Index: 2, Base: &models.PRBranchInfo{ Name: "master", Sha: "pkg_base_sha", Repo: &models.Repository{ Owner: &models.User{UserName: "org"}, Name: "pkg", SSHURL: "ssh://git@src.opensuse.org/org/pkg.git", }, }, Head: &models.PRBranchInfo{ Name: "branch_name", Sha: "pkg_head_sha", Repo: &models.Repository{ Owner: &models.User{UserName: "org"}, Name: "pkg", SSHURL: "ssh://git@src.opensuse.org/org/pkg.git", }, }, AllowMaintainerEdit: test.editable, } ctl := gomock.NewController(t) git := mock_common.NewMockGit(ctl) test.setup(git, prjPR, pkgPR) prset := &common.PRSet{ Config: test.config, PRs: []*common.PRInfo{ {PR: prjPR}, {PR: pkgPR}, }, } if res := prset.PrepareForMerge(git); res != test.expected { t.Errorf("Expected %v, got %v", test.expected, res) } }) } } func TestPRMergeMock(t *testing.T) { tests := []struct { name string setup func(*mock_common.MockGit, *models.PullRequest, *models.PullRequest) config *common.AutogitConfig }{ { name: "Success FF", config: &common.AutogitConfig{ Organization: "org", GitProjectName: "org/_ObsPrj#master", MergeMode: common.MergeModeFF, }, setup: func(m *mock_common.MockGit, prjPR, pkgPR *models.PullRequest) { m.EXPECT().GitClone("_ObsPrj", "master", prjPR.Base.Repo.SSHURL).Return("origin", nil) m.EXPECT().GitExecOrPanic("_ObsPrj", "fetch", "origin", prjPR.Head.Sha) m.EXPECT().GitExec("_ObsPrj", "merge", "--no-ff", "-m", gomock.Any(), prjPR.Head.Sha).Return(nil) m.EXPECT().GitClone("pkg", "master", pkgPR.Base.Repo.SSHURL).Return("origin_pkg", nil) m.EXPECT().GitExecOrPanic("pkg", "fetch", "origin_pkg", pkgPR.Head.Sha) m.EXPECT().GitExecOrPanic("pkg", "merge", "--ff", pkgPR.Head.Sha) m.EXPECT().GitExecOrPanic("pkg", "push", "origin_pkg") m.EXPECT().GitExecOrPanic("_ObsPrj", "push", "origin") }, }, { name: "Success Devel", config: &common.AutogitConfig{ Organization: "org", GitProjectName: "org/_ObsPrj#master", MergeMode: common.MergeModeDevel, }, setup: func(m *mock_common.MockGit, prjPR, pkgPR *models.PullRequest) { m.EXPECT().GitClone("_ObsPrj", "master", prjPR.Base.Repo.SSHURL).Return("origin", nil) m.EXPECT().GitExecOrPanic("_ObsPrj", "fetch", "origin", prjPR.Head.Sha) m.EXPECT().GitExec("_ObsPrj", "merge", "--no-ff", "-m", gomock.Any(), prjPR.Head.Sha).Return(nil) m.EXPECT().GitClone("pkg", "master", pkgPR.Base.Repo.SSHURL).Return("origin_pkg", nil) m.EXPECT().GitExecOrPanic("pkg", "checkout", "-B", "master", pkgPR.Head.Sha) m.EXPECT().GitExecOrPanic("pkg", "push", "-f", "origin_pkg") m.EXPECT().GitExecOrPanic("_ObsPrj", "push", "origin") }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { prjPR := &models.PullRequest{ Index: 1, Base: &models.PRBranchInfo{ Name: "master", Sha: "prj_base_sha", Repo: &models.Repository{ Owner: &models.User{UserName: "org"}, Name: "_ObsPrj", SSHURL: "ssh://git@src.opensuse.org/org/_ObsPrj.git", }, }, Head: &models.PRBranchInfo{ Sha: "prj_head_sha", }, } pkgPR := &models.PullRequest{ Index: 2, Base: &models.PRBranchInfo{ Name: "master", Sha: "pkg_base_sha", Repo: &models.Repository{ Owner: &models.User{UserName: "org"}, Name: "pkg", SSHURL: "ssh://git@src.opensuse.org/org/pkg.git", }, }, Head: &models.PRBranchInfo{ Sha: "pkg_head_sha", }, } ctl := gomock.NewController(t) git := mock_common.NewMockGit(ctl) reviewUnrequestMock := mock_common.NewMockGiteaReviewUnrequester(ctl) reviewUnrequestMock.EXPECT().UnrequestReview(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(nil) test.setup(git, prjPR, pkgPR) prset := &common.PRSet{ Config: test.config, PRs: []*common.PRInfo{ {PR: prjPR}, {PR: pkgPR}, }, } if err := prset.Merge(reviewUnrequestMock, git); err != nil { t.Errorf("Unexpected error: %v", err) } }) } } func TestPRAddMergeCommit(t *testing.T) { pkgPR := &models.PullRequest{ Index: 2, Base: &models.PRBranchInfo{ Name: "master", Sha: "pkg_base_sha", Repo: &models.Repository{ Owner: &models.User{UserName: "org"}, Name: "pkg", SSHURL: "ssh://git@src.opensuse.org/org/pkg.git", }, }, Head: &models.PRBranchInfo{ Name: "branch_name", Sha: "pkg_head_sha", }, AllowMaintainerEdit: true, } config := &common.AutogitConfig{ Organization: "org", GitProjectName: "org/_ObsPrj#master", MergeMode: common.MergeModeReplace, } ctl := gomock.NewController(t) git := mock_common.NewMockGit(ctl) git.EXPECT().GitExec("pkg", "merge", "--no-ff", "--no-commit", "-X", "theirs", pkgPR.Head.Sha).Return(nil) git.EXPECT().GitExecOrPanic("pkg", "read-tree", "-m", pkgPR.Head.Sha) git.EXPECT().GitExecOrPanic("pkg", "commit", "-m", gomock.Any()) git.EXPECT().GitExecOrPanic("pkg", "clean", "-fxd") git.EXPECT().GitExecOrPanic("pkg", "push", "origin", "HEAD:branch_name") git.EXPECT().GitExecWithOutputOrPanic("pkg", "rev-list", "-1", "HEAD").Return("new_head_sha") prset := &common.PRSet{ Config: config, PRs: []*common.PRInfo{ {PR: &models.PullRequest{}}, // prjgit at index 0 {PR: pkgPR}, // pkg at index 1 }, } if res := prset.AddMergeCommit(git, "origin", 1); !res { t.Errorf("Expected true, got %v", res) } }