package main import ( "errors" "fmt" "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 TestPrjGitDescription(t *testing.T) { config := &common.AutogitConfig{ Organization: "test-org", GitProjectName: "test-prj#main", } prset := &common.PRSet{ Config: config, PRs: []*common.PRInfo{ { PR: &models.PullRequest{ State: "open", Index: 1, Base: &models.PRBranchInfo{ Ref: "main", Repo: &models.Repository{ Name: "pkg-a", Owner: &models.User{UserName: "test-org"}, }, }, }, }, { PR: &models.PullRequest{ State: "open", Index: 2, Base: &models.PRBranchInfo{ Ref: "main", Repo: &models.Repository{ Name: "pkg-b", Owner: &models.User{UserName: "test-org"}, }, }, }, }, }, } GitAuthor = "Bot" title, desc := PrjGitDescription(prset) expectedTitle := "Forwarded PRs: pkg-a, pkg-b" if title != expectedTitle { t.Errorf("Expected title %q, got %q", expectedTitle, title) } if !strings.Contains(desc, "PR: test-org/pkg-a!1") || !strings.Contains(desc, "PR: test-org/pkg-b!2") { t.Errorf("Description missing PR references: %s", desc) } } func TestAllocatePRProcessor(t *testing.T) { ctl := gomock.NewController(t) defer ctl.Finish() mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl) GitHandler = mockGitGen mockGit := mock_common.NewMockGit(ctl) configs := common.AutogitConfigs{ { Organization: "test-org", Branch: "main", GitProjectName: "test-prj#main", }, } req := &models.PullRequest{ Index: 1, Base: &models.PRBranchInfo{ Ref: "main", Repo: &models.Repository{ Name: "test-repo", Owner: &models.User{UserName: "test-org"}, }, }, } mockGitGen.EXPECT().CreateGitHandler("test-org").Return(mockGit, nil) mockGit.EXPECT().GetPath().Return("/tmp/test") processor, err := AllocatePRProcessor(req, configs) if err != nil { t.Fatalf("AllocatePRProcessor failed: %v", err) } if processor.config.Organization != "test-org" { t.Errorf("Expected organization test-org, got %s", processor.config.Organization) } } func TestAllocatePRProcessor_Failures(t *testing.T) { ctl := gomock.NewController(t) defer ctl.Finish() mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl) GitHandler = mockGitGen configs := common.AutogitConfigs{} // Empty configs req := &models.PullRequest{ Index: 1, Base: &models.PRBranchInfo{ Ref: "main", Repo: &models.Repository{ Name: "test-repo", Owner: &models.User{UserName: "test-org"}, }, }, } t.Run("Missing config", func(t *testing.T) { processor, err := AllocatePRProcessor(req, configs) if err == nil || err.Error() != "Cannot find config for PR" { t.Errorf("Expected 'Cannot find config for PR' error, got %v", err) } if processor != nil { t.Error("Expected nil processor") } }) t.Run("GitHandler failure", func(t *testing.T) { validConfigs := common.AutogitConfigs{ { Organization: "test-org", Branch: "main", GitProjectName: "test-prj#main", }, } mockGitGen.EXPECT().CreateGitHandler("test-org").Return(nil, errors.New("git error")) processor, err := AllocatePRProcessor(req, validConfigs) if err == nil || !strings.Contains(err.Error(), "Error allocating GitHandler") { t.Errorf("Expected GitHandler error, got %v", err) } if processor != nil { t.Error("Expected nil processor") } }) } func TestSetSubmodulesToMatchPRSet_Failures(t *testing.T) { ctl := gomock.NewController(t) defer ctl.Finish() mockGit := mock_common.NewMockGit(ctl) config := &common.AutogitConfig{ Organization: "test-org", GitProjectName: "test-prj#main", } processor := &PRProcessor{ config: config, git: mockGit, } t.Run("GitSubmoduleList failure", func(t *testing.T) { mockGit.EXPECT().GitSubmoduleList(gomock.Any(), "HEAD").Return(nil, errors.New("list error")) err := processor.SetSubmodulesToMatchPRSet(&common.PRSet{}) if err == nil || err.Error() != "list error" { t.Errorf("Expected 'list error', got %v", err) } }) } func TestSetSubmodulesToMatchPRSet(t *testing.T) { ctl := gomock.NewController(t) defer ctl.Finish() mockGit := mock_common.NewMockGit(ctl) config := &common.AutogitConfig{ Organization: "test-org", GitProjectName: "test-prj#main", } processor := &PRProcessor{ config: config, git: mockGit, } prset := &common.PRSet{ Config: config, PRs: []*common.PRInfo{ { PR: &models.PullRequest{ State: "open", Index: 1, Base: &models.PRBranchInfo{ Ref: "main", Repo: &models.Repository{ Name: "pkg-a", Owner: &models.User{UserName: "test-org"}, }, }, Head: &models.PRBranchInfo{ Sha: "new-sha", }, }, }, }, } mockGit.EXPECT().GitSubmoduleList(gomock.Any(), "HEAD").Return(map[string]string{"pkg-a": "old-sha"}, nil) // Expect submodule update and commit mockGit.EXPECT().GitExec(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() mockGit.EXPECT().GitStatus(gomock.Any()).Return([]common.GitStatusData{}, nil).AnyTimes() err := processor.SetSubmodulesToMatchPRSet(prset) if err != nil { t.Errorf("SetSubmodulesToMatchPRSet failed: %v", err) } } func TestRebaseAndSkipSubmoduleCommits(t *testing.T) { ctl := gomock.NewController(t) defer ctl.Finish() mockGit := mock_common.NewMockGit(ctl) config := &common.AutogitConfig{ Organization: "test-org", GitProjectName: "test-prj#main", } processor := &PRProcessor{ config: config, git: mockGit, } prset := &common.PRSet{ Config: config, PRs: []*common.PRInfo{ { RemoteName: "origin", PR: &models.PullRequest{ Base: &models.PRBranchInfo{ Name: "main", Repo: &models.Repository{ Name: "test-prj", Owner: &models.User{UserName: "test-org"}, }, }, }, }, }, } t.Run("Clean rebase", func(t *testing.T) { mockGit.EXPECT().GitExec(common.DefaultGitPrj, "rebase", "origin/main").Return(nil) err := processor.RebaseAndSkipSubmoduleCommits(prset, "main") if err != nil { t.Errorf("Expected nil error, got %v", err) } }) t.Run("Rebase with submodule conflict - skip", func(t *testing.T) { // First rebase fails mockGit.EXPECT().GitExec(common.DefaultGitPrj, "rebase", "origin/main").Return(errors.New("conflict")) // Status shows submodule change mockGit.EXPECT().GitStatus(common.DefaultGitPrj).Return([]common.GitStatusData{ {SubmoduleChanges: "S..."}, }, nil) // Skip called mockGit.EXPECT().GitExec(common.DefaultGitPrj, "rebase", "--skip").Return(nil) err := processor.RebaseAndSkipSubmoduleCommits(prset, "main") if err != nil { t.Errorf("Expected nil error, got %v", err) } }) t.Run("Rebase with real conflict - abort", func(t *testing.T) { mockGit.EXPECT().GitExec(common.DefaultGitPrj, "rebase", "origin/main").Return(errors.New("conflict")) // Status shows real change mockGit.EXPECT().GitStatus(common.DefaultGitPrj).Return([]common.GitStatusData{ {SubmoduleChanges: "N..."}, }, nil) // Abort called mockGit.EXPECT().GitExecOrPanic(common.DefaultGitPrj, "rebase", "--abort").Return() err := processor.RebaseAndSkipSubmoduleCommits(prset, "main") if err == nil || !strings.Contains(err.Error(), "Unexpected conflict in rebase") { t.Errorf("Expected 'Unexpected conflict' error, got %v", err) } }) } func TestUpdatePrjGitPR(t *testing.T) { ctl := gomock.NewController(t) defer ctl.Finish() mockGit := mock_common.NewMockGit(ctl) gitea := mock_common.NewMockGitea(ctl) Gitea = gitea CurrentUser = &models.User{UserName: "bot"} config := &common.AutogitConfig{ Organization: "test-org", GitProjectName: "test-prj#main", } processor := &PRProcessor{ config: config, git: mockGit, } t.Run("Only project git in PR", func(t *testing.T) { prset := &common.PRSet{ Config: config, PRs: []*common.PRInfo{ { RemoteName: "origin", PR: &models.PullRequest{ Base: &models.PRBranchInfo{ Name: "main", Repo: &models.Repository{ Name: "test-prj", Owner: &models.User{UserName: "test-org"}, }, }, Head: &models.PRBranchInfo{ Sha: "sha1", }, }, }, }, } mockGit.EXPECT().GitExecOrPanic(common.DefaultGitPrj, "fetch", "origin", "sha1") err := processor.UpdatePrjGitPR(prset) if err != nil { t.Errorf("Unexpected error: %v", err) } }) t.Run("PR on another remote", func(t *testing.T) { prset := &common.PRSet{ Config: config, PRs: []*common.PRInfo{ { RemoteName: "origin", PR: &models.PullRequest{ Base: &models.PRBranchInfo{ Name: "main", RepoID: 1, Repo: &models.Repository{ Name: "test-prj", Owner: &models.User{UserName: "test-org"}, SSHURL: "url", }, }, Head: &models.PRBranchInfo{ Name: "feature", RepoID: 2, // Different RepoID Sha: "sha1", }, }, }, { PR: &models.PullRequest{ Base: &models.PRBranchInfo{ Name: "other", Repo: &models.Repository{ Name: "other-pkg", Owner: &models.User{UserName: "test-org"}, }, }, }, }, }, } mockGit.EXPECT().GitClone(gomock.Any(), gomock.Any(), gomock.Any()).Return("remote2", nil) mockGit.EXPECT().GitExecOrPanic(gomock.Any(), "fetch", "remote2", "sha1") mockGit.EXPECT().GitExecOrPanic(gomock.Any(), "checkout", "sha1") err := processor.UpdatePrjGitPR(prset) if err != nil { t.Errorf("Unexpected error: %v", err) } }) t.Run("Standard update with rebase and force push", func(t *testing.T) { prset := &common.PRSet{ Config: config, BotUser: "bot", PRs: []*common.PRInfo{ { RemoteName: "origin", PR: &models.PullRequest{ User: &models.User{UserName: "bot"}, Mergeable: false, // Triggers rebase Base: &models.PRBranchInfo{ Name: "main", RepoID: 1, Repo: &models.Repository{ Name: "test-prj", Owner: &models.User{UserName: "test-org"}, SSHURL: "url", }, }, Head: &models.PRBranchInfo{ Name: "PR_branch", RepoID: 1, Sha: "old-head", }, }, }, { PR: &models.PullRequest{ State: "open", Base: &models.PRBranchInfo{ Repo: &models.Repository{ Name: "pkg-a", Owner: &models.User{UserName: "test-org"}, }, }, Head: &models.PRBranchInfo{Sha: "pkg-sha"}, }, }, }, } mockGit.EXPECT().GitClone(gomock.Any(), gomock.Any(), gomock.Any()).Return("origin", nil) mockGit.EXPECT().GitExecOrPanic(gomock.Any(), "fetch", gomock.Any(), gomock.Any()) // Rebase expectations mockGit.EXPECT().GitExec(gomock.Any(), "rebase", gomock.Any()).Return(nil) // First call returns old-head, second returns new-head to trigger push mockGit.EXPECT().GitBranchHead(gomock.Any(), gomock.Any()).Return("old-head", nil).Times(1) mockGit.EXPECT().GitBranchHead(gomock.Any(), gomock.Any()).Return("new-head", nil).Times(1) // SetSubmodulesToMatchPRSet expectations mockGit.EXPECT().GitSubmoduleList(gomock.Any(), "HEAD").Return(map[string]string{"pkg-a": "old-pkg-sha"}, nil) // Catch all GitExec calls with any number of arguments up to 5 mockGit.EXPECT().GitExec(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() mockGit.EXPECT().GitExec(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() mockGit.EXPECT().GitExec(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() mockGit.EXPECT().GitExec(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() mockGit.EXPECT().GitStatus(gomock.Any()).Return(nil, nil).AnyTimes() // UpdatePullRequest expectation gitea.EXPECT().UpdatePullRequest(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() err := processor.UpdatePrjGitPR(prset) if err != nil { t.Errorf("Unexpected error: %v", err) } }) t.Run("isPrTitleSame logic", func(t *testing.T) { longTitle := strings.Repeat("a", 251) + "..." prset := &common.PRSet{ Config: config, BotUser: "bot", PRs: []*common.PRInfo{ { RemoteName: "origin", PR: &models.PullRequest{ User: &models.User{UserName: "bot"}, Title: longTitle, Base: &models.PRBranchInfo{ Name: "main", RepoID: 1, Repo: &models.Repository{ Name: "test-prj", Owner: &models.User{UserName: "test-org"}, }, }, Head: &models.PRBranchInfo{ Name: "PR_branch", RepoID: 1, Sha: "head", }, }, }, { PR: &models.PullRequest{ State: "open", Base: &models.PRBranchInfo{ Repo: &models.Repository{ Name: "pkg-a", Owner: &models.User{UserName: "test-org"}, }, }, Head: &models.PRBranchInfo{Sha: "pkg-sha"}, }, }, }, } mockGit.EXPECT().GitClone(gomock.Any(), gomock.Any(), gomock.Any()).Return("origin", nil) mockGit.EXPECT().GitExecOrPanic(gomock.Any(), "fetch", gomock.Any(), gomock.Any()) mockGit.EXPECT().GitBranchHead(gomock.Any(), gomock.Any()).Return("head", nil).AnyTimes() mockGit.EXPECT().GitSubmoduleList(gomock.Any(), "HEAD").Return(map[string]string{"pkg-a": "pkg-sha"}, nil) // mockGit.EXPECT().GitExec(...) not called because no push (headCommit == newHeadCommit) mockGit.EXPECT().GitExec(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() err := processor.UpdatePrjGitPR(prset) if err != nil { t.Errorf("Unexpected error: %v", err) } }) } func TestCreatePRjGitPR_Integration(t *testing.T) { ctl := gomock.NewController(t) defer ctl.Finish() mockGit := mock_common.NewMockGit(ctl) gitea := mock_common.NewMockGitea(ctl) Gitea = gitea config := &common.AutogitConfig{ Organization: "test-org", GitProjectName: "test-prj#main", } processor := &PRProcessor{ config: config, git: mockGit, } prset := &common.PRSet{ Config: config, PRs: []*common.PRInfo{ { PR: &models.PullRequest{ State: "open", Base: &models.PRBranchInfo{ Repo: &models.Repository{Name: "pkg-a", Owner: &models.User{UserName: "test-org"}}, }, Head: &models.PRBranchInfo{Sha: "pkg-sha"}, }, }, }, } t.Run("Create new project PR with label", func(t *testing.T) { mockGit.EXPECT().GitBranchHead(gomock.Any(), gomock.Any()).Return("head-sha", nil).AnyTimes() mockGit.EXPECT().GitSubmoduleList(gomock.Any(), gomock.Any()).Return(map[string]string{}, nil).AnyTimes() mockGit.EXPECT().GitExec(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() mockGit.EXPECT().GitExec(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() mockGit.EXPECT().GitExec(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() mockGit.EXPECT().GitExec(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() mockGit.EXPECT().GitClone(gomock.Any(), gomock.Any(), gomock.Any()).Return("origin", nil).AnyTimes() mockGit.EXPECT().GitStatus(gomock.Any()).Return(nil, nil).AnyTimes() prjPR := &models.PullRequest{ Index: 10, Base: &models.PRBranchInfo{ Name: "main", RepoID: 1, Repo: &models.Repository{Name: "test-prj", Owner: &models.User{UserName: "test-org"}}, }, Head: &models.PRBranchInfo{ Sha: "prj-head-sha", }, } gitea.EXPECT().GetRepository(gomock.Any(), gomock.Any()).Return(&models.Repository{Owner: &models.User{UserName: "test-org"}}, nil).AnyTimes() // CreatePullRequestIfNotExist returns isNew=true gitea.EXPECT().CreatePullRequestIfNotExist(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(prjPR, nil, true).AnyTimes() // Expect SetLabels to be called for new PR gitea.EXPECT().SetLabels("test-org", gomock.Any(), int64(10), gomock.Any()).Return(nil, nil).AnyTimes() gitea.EXPECT().UpdatePullRequest(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() mockGit.EXPECT().GitExecOrPanic(gomock.Any(), gomock.Any()).Return().AnyTimes() mockGit.EXPECT().GitExecOrPanic(gomock.Any(), gomock.Any(), gomock.Any()).Return().AnyTimes() mockGit.EXPECT().GitExecOrPanic(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return().AnyTimes() mockGit.EXPECT().GitExecOrPanic(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return().AnyTimes() mockGit.EXPECT().GitExecOrPanic(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return().AnyTimes() err := processor.CreatePRjGitPR("PR_branch", prset) if err != nil { t.Errorf("CreatePRjGitPR failed: %v", err) } }) } func TestMultiPackagePRSet(t *testing.T) { GitAuthor = "Bot" // Ensure non-empty author config := &common.AutogitConfig{ Organization: "test-org", GitProjectName: "test-prj#main", } prset := &common.PRSet{ Config: config, } for i := 1; i <= 5; i++ { name := fmt.Sprintf("pkg-%d", i) prset.PRs = append(prset.PRs, &common.PRInfo{ PR: &models.PullRequest{ Index: int64(i), State: "open", Base: &models.PRBranchInfo{ Ref: "main", Repo: &models.Repository{Name: name, Owner: &models.User{UserName: "test-org"}}, }, }, }) } GitAuthor = "Bot" title, desc := PrjGitDescription(prset) // PrjGitDescription generates title like "Forwarded PRs: pkg-1, pkg-2, pkg-3, pkg-4, pkg-5" for i := 1; i <= 5; i++ { name := fmt.Sprintf("pkg-%d", i) if !strings.Contains(title, name) { t.Errorf("Title missing package %s: %s", name, title) } } for i := 1; i <= 5; i++ { ref := fmt.Sprintf("PR: test-org/pkg-%d!%d", i, i) if !strings.Contains(desc, ref) { t.Errorf("Description missing reference %s", ref) } } } func TestPRProcessor_Process_EdgeCases(t *testing.T) { ctl := gomock.NewController(t) defer ctl.Finish() mockGit := mock_common.NewMockGit(ctl) gitea := mock_common.NewMockGitea(ctl) Gitea = gitea CurrentUser = &models.User{UserName: "bot"} config := &common.AutogitConfig{ Organization: "test-org", GitProjectName: "test-prj#main", } processor := &PRProcessor{ config: config, git: mockGit, } t.Run("Merged project PR - update downstream", func(t *testing.T) { prjPR := &models.PullRequest{ State: "closed", HasMerged: true, Index: 100, Base: &models.PRBranchInfo{ Name: "main", Repo: &models.Repository{Name: "test-prj", Owner: &models.User{UserName: "test-org"}, SSHURL: "url"}, }, Head: &models.PRBranchInfo{Name: "PR_branch"}, } pkgPR := &models.PullRequest{ State: "open", Index: 1, Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg-a", Owner: &models.User{UserName: "test-org"}}}, Head: &models.PRBranchInfo{Sha: "pkg-sha"}, } prset := &common.PRSet{ BotUser: "bot", Config: config, PRs: []*common.PRInfo{ {PR: prjPR}, {PR: pkgPR}, }, } _ = prset // Suppress unused for now if it's really unused, but it's likely used by common.FetchPRSet internally if we weren't mocking everything // Mock expectations for Process setup gitea.EXPECT().GetTimeline(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.TimelineComment{}, nil).AnyTimes() gitea.EXPECT().GetPullRequest(gomock.Any(), gomock.Any(), gomock.Any()).Return(prjPR, nil).AnyTimes() gitea.EXPECT().GetPullRequestReviews(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.PullReview{}, nil).AnyTimes() // Mock maintainership file calls gitea.EXPECT().FetchMaintainershipFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, "", nil).AnyTimes() gitea.EXPECT().FetchMaintainershipDirFile(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, "", nil).AnyTimes() // Mock expectations for the merged path mockGit.EXPECT().GitClone(gomock.Any(), gomock.Any(), gomock.Any()).Return("origin", nil) mockGit.EXPECT().GitSubmoduleList(gomock.Any(), gomock.Any()).Return(map[string]string{"pkg-a": "old-sha"}, nil).AnyTimes() gitea.EXPECT().GetRecentCommits(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.Commit{{SHA: "pkg-sha"}}, nil).AnyTimes() // Downstream update expectations gitea.EXPECT().AddComment(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() gitea.EXPECT().ManualMergePR(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() err := processor.Process(pkgPR) if err != nil { t.Errorf("Unexpected error: %v", err) } }) t.Run("Superfluous PR - close it", func(t *testing.T) { prjPR := &models.PullRequest{ State: "open", Index: 100, User: &models.User{UserName: "bot"}, Body: "Forwarded PRs: \n", // No PRs linked Base: &models.PRBranchInfo{ Name: "main", Repo: &models.Repository{Name: "test-prj", Owner: &models.User{UserName: "test-org"}}, }, Head: &models.PRBranchInfo{Name: "PR_branch", Sha: "head-sha"}, MergeBase: "base-sha", } prset := &common.PRSet{ BotUser: "bot", Config: config, PRs: []*common.PRInfo{ {PR: prjPR}, }, } _ = prset gitea.EXPECT().GetTimeline(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.TimelineComment{}, nil).AnyTimes() gitea.EXPECT().GetPullRequest(gomock.Any(), gomock.Any(), gomock.Any()).Return(prjPR, nil).AnyTimes() gitea.EXPECT().GetPullRequestReviews(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.PullReview{}, nil).AnyTimes() gitea.EXPECT().GetRepository(gomock.Any(), gomock.Any()).Return(&models.Repository{Owner: &models.User{UserName: "test-org"}}, nil).AnyTimes() // Mock maintainership file calls gitea.EXPECT().FetchMaintainershipFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, "", nil).AnyTimes() gitea.EXPECT().FetchMaintainershipDirFile(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, "", nil).AnyTimes() // Standard update calls within Process mockGit.EXPECT().GitClone(gomock.Any(), gomock.Any(), gomock.Any()).Return("origin", nil).AnyTimes() mockGit.EXPECT().GitExecOrPanic(gomock.Any(), "fetch", gomock.Any(), gomock.Any()).AnyTimes() mockGit.EXPECT().GitBranchHead(gomock.Any(), gomock.Any()).Return("head-sha", nil).AnyTimes() mockGit.EXPECT().GitSubmoduleList(gomock.Any(), gomock.Any()).Return(map[string]string{}, nil).AnyTimes() mockGit.EXPECT().GitExec(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() mockGit.EXPECT().GitStatus(gomock.Any()).Return(nil, nil).AnyTimes() gitea.EXPECT().SetRepoOptions(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() // Diff check for superfluous mockGit.EXPECT().GitDiff(gomock.Any(), gomock.Any(), gomock.Any()).Return("", nil).AnyTimes() // Expectations for closing gitea.EXPECT().AddComment(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() gitea.EXPECT().UpdatePullRequest(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() err := processor.Process(prjPR) if err != nil { t.Errorf("Unexpected error: %v", err) } }) } func TestVerifyRepositoryConfiguration(t *testing.T) { ctl := gomock.NewController(t) defer ctl.Finish() gitea := mock_common.NewMockGitea(ctl) Gitea = gitea repo := &models.Repository{ Name: "test-repo", Owner: &models.User{ UserName: "test-user", }, AutodetectManualMerge: true, AllowManualMerge: true, } t.Run("Config already correct", func(t *testing.T) { err := verifyRepositoryConfiguration(repo) if err != nil { t.Errorf("Expected nil error, got %v", err) } }) t.Run("Config incorrect - trigger update", func(t *testing.T) { repo.AllowManualMerge = false gitea.EXPECT().SetRepoOptions("test-user", "test-repo", true).Return(&models.Repository{}, nil) err := verifyRepositoryConfiguration(repo) if err != nil { t.Errorf("Expected nil error, got %v", err) } }) t.Run("Update failure", func(t *testing.T) { repo.AllowManualMerge = false expectedErr := errors.New("update failed") gitea.EXPECT().SetRepoOptions("test-user", "test-repo", true).Return(nil, expectedErr) err := verifyRepositoryConfiguration(repo) if err != expectedErr { t.Errorf("Expected %v, got %v", expectedErr, err) } }) } func TestProcessFunc(t *testing.T) { ctl := gomock.NewController(t) defer ctl.Finish() gitea := mock_common.NewMockGitea(ctl) Gitea = gitea mockGit := mock_common.NewMockGit(ctl) mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl) GitHandler = mockGitGen config := &common.AutogitConfig{ Organization: "test-org", GitProjectName: "test-prj#main", } reqProc := &RequestProcessor{ configuredRepos: map[string][]*common.AutogitConfig{ "test-org": {config}, }, } modelPR := &models.PullRequest{ Index: 1, Base: &models.PRBranchInfo{ Ref: "main", Repo: &models.Repository{ Name: "test-repo", DefaultBranch: "main", Owner: &models.User{UserName: "test-org"}, }, }, } t.Run("PullRequestWebhookEvent", func(t *testing.T) { event := &common.PullRequestWebhookEvent{ Pull_Request: &common.PullRequest{ Number: 1, Base: common.Head{ Ref: "main", Repo: &common.Repository{ Name: "test-repo", Owner: &common.Organization{ Username: "test-org", }, }, }, }, } gitea.EXPECT().GetPullRequest("test-org", "test-repo", int64(1)).Return(modelPR, nil).AnyTimes() // AllocatePRProcessor and ProcesPullRequest calls inside mockGitGen.EXPECT().CreateGitHandler(gomock.Any()).Return(mockGit, nil) mockGit.EXPECT().GetPath().Return("/tmp").AnyTimes() mockGit.EXPECT().Close().Return(nil) // Expect Process calls (mocked via mockGit mostly) gitea.EXPECT().GetTimeline(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.TimelineComment{}, nil).AnyTimes() gitea.EXPECT().GetPullRequestReviews(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.PullReview{}, nil).AnyTimes() gitea.EXPECT().FetchMaintainershipFile(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, "", nil).AnyTimes() gitea.EXPECT().FetchMaintainershipDirFile(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, "", nil).AnyTimes() gitea.EXPECT().SetRepoOptions(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).AnyTimes() err := reqProc.ProcessFunc(&common.Request{Data: event}) if err != nil { t.Errorf("Unexpected error: %v", err) } }) t.Run("IssueCommentWebhookEvent", func(t *testing.T) { event := &common.IssueCommentWebhookEvent{ Issue: &common.IssueDetail{Number: 1}, Repository: &common.Repository{ Name: "test-repo", Owner: &common.Organization{Username: "test-org"}, }, } gitea.EXPECT().GetPullRequest("test-org", "test-repo", int64(1)).Return(modelPR, nil).AnyTimes() mockGitGen.EXPECT().CreateGitHandler(gomock.Any()).Return(mockGit, nil) mockGit.EXPECT().Close().Return(nil) err := reqProc.ProcessFunc(&common.Request{Data: event}) if err != nil { t.Errorf("Unexpected error: %v", err) } }) t.Run("Recursion limit", func(t *testing.T) { reqProc.recursive = 3 err := reqProc.ProcessFunc(&common.Request{}) if err != nil { t.Errorf("Expected nil error on recursion limit, got %v", err) } if reqProc.recursive != 3 { t.Errorf("Expected recursive to be 3, got %d", reqProc.recursive) } reqProc.recursive = 0 // Reset }) t.Run("Invalid data format", func(t *testing.T) { err := reqProc.ProcessFunc(&common.Request{Data: nil}) if err == nil || !strings.Contains(err.Error(), "Invalid data format") { t.Errorf("Expected 'Invalid data format' error, got %v", err) } }) }