package main import ( "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" "testing" ) func TestProcessIssue_Add(t *testing.T) { ctl := NewController(t) defer ctl.Finish() gitea := mock_common.NewMockGitea(ctl) Gitea = gitea common.IsDryRun = false gitea.EXPECT().GetTimeline(gomock.Any(), gomock.Any(), gomock.Not(int64(999))).Return([]*models.TimelineComment{}, nil).AnyTimes() CurrentUser = &models.User{UserName: "bot-user"} config := &common.AutogitConfig{ Organization: "target-org", GitProjectName: "test-org/test-prj#main", } configs := []*common.AutogitConfig{config} issue := &models.Issue{ Title: "[ADD] pkg1", Body: "src-org/pkg1#master", Index: 123, Repository: &models.RepositoryMeta{ Owner: "test-org", Name: "test-prj", }, Ref: "refs/heads/main", State: "open", } expectedBody := "See issue test-org/test-prj#123" t.Run("Repository does not exist - labels issue", func(t *testing.T) { mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl) GitHandler = mockGitGen mockGit := mock_common.NewMockGit(ctl) mockGitGen.EXPECT().CreateGitHandler("target-org").Return(mockGit, nil) mockGit.EXPECT().Close().Return(nil) gitea.EXPECT().GetRepository("target-org", "pkg1").Return(nil, nil) gitea.EXPECT().SetLabels("test-org", "test-prj", int64(123), []string{"new/New Repository"}).Return(nil, nil) err := ProcessIssue(issue, configs) if err != nil { t.Errorf("Unexpected error: %v", err) } }) t.Run("Source is SHA - creates temp branch in target", func(t *testing.T) { mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl) GitHandler = mockGitGen mockGit := mock_common.NewMockGit(ctl) mockGitGen.EXPECT().CreateGitHandler("target-org").Return(mockGit, nil) mockGit.EXPECT().Close().Return(nil) targetRepo := &models.Repository{ Name: "pkg1", SSHURL: "target-ssh-url", Owner: &models.User{UserName: "target-org"}, } srcRepo := &models.Repository{ Name: "pkg1", SSHURL: "src-ssh-url", DefaultBranch: "master", Owner: &models.User{UserName: "src-org"}, Parent: &models.Repository{ Name: "pkg1", Owner: &models.User{UserName: "target-org"}, }, } sha := "abcdef0123456789abcdef0123456789abcdef01" issueSHA := &models.Issue{ Title: "[ADD] pkg1", Body: "src-org/pkg1#" + sha, Index: 123, Repository: &models.RepositoryMeta{Owner: "test-org", Name: "test-prj"}, Ref: "refs/heads/main", State: "open", } gitea.EXPECT().GetRepository("target-org", "pkg1").Return(targetRepo, nil) gitea.EXPECT().GetRepository("src-org", "pkg1").Return(srcRepo, nil) mockGit.EXPECT().GitClone("pkg1", sha, "src-ssh-url").Return("src-remote", nil) mockGit.EXPECT().GitClone("pkg1", sha, "target-ssh-url").Return("origin", nil) // Source commit list and reset logic mockGit.EXPECT().GitExecWithOutputOrPanic("pkg1", "rev-list", "--first-parent", "src-remote/"+sha).Return(sha + "\n" + "parent-sha") mockGit.EXPECT().GitExecOrPanic("pkg1", "checkout", "-B", sha, "parent-sha") mockGit.EXPECT().GitExecOrPanic("pkg1", "push", "-f", "origin", sha) mockGit.EXPECT().GitExecWithOutput("pkg1", "ls-remote", "--heads", "src-ssh-url", sha).Return("", nil) // SHA source logic (creates temp branch) tempBranch := "new_package_123_pkg1" mockGit.EXPECT().GitClone("pkg1", sha, "target-ssh-url").Return("origin", nil) mockGit.EXPECT().GitExecOrPanic("pkg1", "remote", "add", "source", "src-ssh-url") mockGit.EXPECT().GitExecOrPanic("pkg1", "fetch", "source", sha) mockGit.EXPECT().GitExecOrPanic("pkg1", "checkout", "-B", tempBranch, "FETCH_HEAD") mockGit.EXPECT().GitExecOrPanic("pkg1", "push", "-f", "origin", tempBranch) // PR creation using temp branch pr := &models.PullRequest{ Index: 456, Body: expectedBody, Base: &models.PRBranchInfo{ Repo: targetRepo, }, } gitea.EXPECT().CreatePullRequestIfNotExist(targetRepo, tempBranch, "main", gomock.Any(), gomock.Any()).Return(pr, nil, true) gitea.EXPECT().SetLabels("target-org", "pkg1", int64(456), []string{"new/New Repository"}).Return(nil, nil) err := ProcessIssue(issueSHA, configs) if err != nil { t.Errorf("Unexpected error: %v", err) } }) t.Run("Repository exists - continue processing and create PR", func(t *testing.T) { mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl) GitHandler = mockGitGen mockGit := mock_common.NewMockGit(ctl) mockGitGen.EXPECT().CreateGitHandler("target-org").Return(mockGit, nil) mockGit.EXPECT().Close().Return(nil) targetRepo := &models.Repository{ Name: "pkg1", SSHURL: "target-ssh-url", Owner: &models.User{UserName: "target-org"}, } srcRepo := &models.Repository{ Name: "pkg1", SSHURL: "src-ssh-url", DefaultBranch: "master", Owner: &models.User{UserName: "src-org"}, Parent: &models.Repository{ Name: "pkg1", Owner: &models.User{UserName: "target-org"}, }, } gitea.EXPECT().GetRepository("target-org", "pkg1").Return(targetRepo, nil) gitea.EXPECT().GetRepository("src-org", "pkg1").Return(srcRepo, nil) mockGit.EXPECT().GitClone("pkg1", "master", "src-ssh-url").Return("src-remote", nil) mockGit.EXPECT().GitClone("pkg1", "master", "target-ssh-url").Return("origin", nil) // Commit list logic mockGit.EXPECT().GitExecWithOutputOrPanic("pkg1", "rev-list", "--first-parent", "src-remote/master").Return("sha1\nsha2") mockGit.EXPECT().GitExecOrPanic("pkg1", "checkout", "-B", "master", "sha2") mockGit.EXPECT().GitExecOrPanic("pkg1", "push", "-f", "origin", "master") // Check if source is a branch via ls-remote mockGit.EXPECT().GitExecWithOutput("pkg1", "ls-remote", "--heads", "src-ssh-url", "master").Return("sha1 refs/heads/master", nil) // PR creation pr := &models.PullRequest{ Index: 456, Body: expectedBody, Base: &models.PRBranchInfo{ Repo: targetRepo, }, } gitea.EXPECT().CreatePullRequestIfNotExist(targetRepo, "src-org:master", "main", gomock.Any(), gomock.Any()).Return(pr, nil, true) gitea.EXPECT().SetLabels("target-org", "pkg1", int64(456), []string{"new/New Repository"}).Return(nil, nil) err := ProcessIssue(issue, configs) if err != nil { t.Errorf("Unexpected error: %v", err) } }) t.Run("Source repository is not fork of target repository - aborts", func(t *testing.T) { mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl) GitHandler = mockGitGen mockGit := mock_common.NewMockGit(ctl) mockGitGen.EXPECT().CreateGitHandler("target-org").Return(mockGit, nil) mockGit.EXPECT().Close().Return(nil) targetRepo := &models.Repository{ Name: "pkg1", SSHURL: "target-ssh-url", Owner: &models.User{UserName: "target-org"}, } srcRepo := &models.Repository{ Name: "pkg1", Owner: &models.User{UserName: "src-org"}, SSHURL: "src-ssh-url", Parent: &models.Repository{ Name: "other-repo", Owner: &models.User{UserName: "other-org"}, }, } gitea.EXPECT().GetRepository("target-org", "pkg1").Return(targetRepo, nil) gitea.EXPECT().GetRepository("src-org", "pkg1").Return(srcRepo, nil) mockGit.EXPECT().GitClone("pkg1", "master", "src-ssh-url").Return("src-remote", nil) mockGit.EXPECT().GitClone("pkg1", "master", "target-ssh-url").Return("origin", nil) err := ProcessIssue(issue, configs) if err != nil { t.Errorf("Unexpected error: %v", err) } }) t.Run("Source repository is fork of target repository - proceeds", func(t *testing.T) { mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl) GitHandler = mockGitGen mockGit := mock_common.NewMockGit(ctl) mockGitGen.EXPECT().CreateGitHandler("target-org").Return(mockGit, nil) mockGit.EXPECT().Close().Return(nil) targetRepo := &models.Repository{ Name: "pkg1", Owner: &models.User{UserName: "target-org"}, SSHURL: "target-ssh-url", } srcRepo := &models.Repository{ Name: "pkg1", Owner: &models.User{UserName: "src-org"}, SSHURL: "src-ssh-url", Parent: &models.Repository{ Name: "pkg1", Owner: &models.User{UserName: "target-org"}, }, DefaultBranch: "master", } gitea.EXPECT().GetRepository("target-org", "pkg1").Return(targetRepo, nil) gitea.EXPECT().GetRepository("src-org", "pkg1").Return(srcRepo, nil) mockGit.EXPECT().GitClone("pkg1", "master", "src-ssh-url").Return("src-remote", nil) mockGit.EXPECT().GitClone("pkg1", "master", "target-ssh-url").Return("origin", nil) mockGit.EXPECT().GitExecWithOutputOrPanic("pkg1", "rev-list", "--first-parent", "src-remote/master").Return("sha1\nsha2") mockGit.EXPECT().GitExecOrPanic("pkg1", "checkout", "-B", "master", "sha2") mockGit.EXPECT().GitExecOrPanic("pkg1", "push", "-f", "origin", "master") mockGit.EXPECT().GitExecWithOutput("pkg1", "ls-remote", "--heads", "src-ssh-url", "master").Return("sha1 refs/heads/master", nil) pr := &models.PullRequest{ Index: 456, Body: expectedBody, Base: &models.PRBranchInfo{ Repo: targetRepo, }, } gitea.EXPECT().CreatePullRequestIfNotExist(targetRepo, "src-org:master", "main", gomock.Any(), gomock.Any()).Return(pr, nil, false) gitea.EXPECT().UpdatePullRequest("target-org", "pkg1", int64(456), gomock.Any()) err := ProcessIssue(issue, configs) if err != nil { t.Errorf("Unexpected error: %v", err) } }) t.Run("Source repository has no parent (not a fork) - aborts", func(t *testing.T) { mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl) GitHandler = mockGitGen mockGit := mock_common.NewMockGit(ctl) mockGitGen.EXPECT().CreateGitHandler("target-org").Return(mockGit, nil) mockGit.EXPECT().Close().Return(nil) targetRepo := &models.Repository{ Name: "pkg1", Owner: &models.User{UserName: "target-org"}, SSHURL: "target-ssh-url", } srcRepo := &models.Repository{ Name: "pkg1", Owner: &models.User{UserName: "src-org"}, SSHURL: "src-ssh-url", Parent: nil, DefaultBranch: "master", } gitea.EXPECT().GetRepository("target-org", "pkg1").Return(targetRepo, nil) gitea.EXPECT().GetRepository("src-org", "pkg1").Return(srcRepo, nil) err := ProcessIssue(issue, configs) if err != nil { t.Errorf("Unexpected error: %v", err) } }) t.Run("Target branch missing - creates orphan branch", func(t *testing.T) { mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl) GitHandler = mockGitGen mockGit := mock_common.NewMockGit(ctl) mockGitGen.EXPECT().CreateGitHandler("target-org").Return(mockGit, nil) mockGit.EXPECT().Close().Return(nil) targetRepo := &models.Repository{ Name: "pkg1", SSHURL: "target-ssh-url", Owner: &models.User{UserName: "target-org"}, } srcRepo := &models.Repository{ Name: "pkg1", SSHURL: "src-ssh-url", DefaultBranch: "master", Owner: &models.User{UserName: "src-org"}, Parent: &models.Repository{ Name: "pkg1", Owner: &models.User{UserName: "target-org"}, }, } gitea.EXPECT().GetRepository("target-org", "pkg1").Return(targetRepo, nil) gitea.EXPECT().GetRepository("src-org", "pkg1").Return(srcRepo, nil) mockGit.EXPECT().GitClone("pkg1", "master", "src-ssh-url").Return("src-remote", nil) mockGit.EXPECT().GitClone("pkg1", "master", "target-ssh-url").Return("origin", nil) // Branch check - rev-list works but says only 1 commit mockGit.EXPECT().GitExecWithOutputOrPanic("pkg1", "rev-list", "--first-parent", "src-remote/master").Return("sha1") // Orphan branch creation via createEmptyBranch mockGit.EXPECT().GitDirectoryContentList("pkg1", "master").Return(map[string]string{"file": "sha"}, nil) mockGit.EXPECT().GitExecOrPanic("pkg1", "checkout", "--detach") mockGit.EXPECT().GitExec("pkg1", "branch", "-D", "master") mockGit.EXPECT().GitExecOrPanic("pkg1", "checkout", "-f", "--orphan", "master") mockGit.EXPECT().GitExecOrPanic("pkg1", "rm", "-rf", ".") mockGit.EXPECT().GitExecOrPanic("pkg1", "commit", "--allow-empty", "-m", "Initial empty branch") mockGit.EXPECT().GitExecOrPanic("pkg1", "push", "-f", "origin", "master") mockGit.EXPECT().GitExecWithOutput("pkg1", "ls-remote", "--heads", "src-ssh-url", "master").Return("sha1 refs/heads/master", nil) // PR creation pr := &models.PullRequest{ Index: 456, Body: expectedBody, Base: &models.PRBranchInfo{ Repo: targetRepo, }, } gitea.EXPECT().CreatePullRequestIfNotExist(targetRepo, "src-org:master", "main", gomock.Any(), gomock.Any()).Return(pr, nil, true) gitea.EXPECT().SetLabels("target-org", "pkg1", int64(456), []string{"new/New Repository"}).Return(nil, nil) err := ProcessIssue(issue, configs) if err != nil { t.Errorf("Unexpected error: %v", err) } }) t.Run("Config not found", func(t *testing.T) { issueNoConfig := &models.Issue{ Title: "[ADD] pkg1", Body: "src-org/pkg1#master", Index: 123, Repository: &models.RepositoryMeta{ Owner: "other-org", Name: "other-prj", }, Ref: "refs/heads/main", State: "open", } err := ProcessIssue(issueNoConfig, configs) if err == nil || err.Error() != "Cannot find config for other-org/other-prj#main" { t.Errorf("Expected config not found error, got %v", err) } }) t.Run("No repos in body", func(t *testing.T) { err := ProcessIssue(&models.Issue{ Title: "[ADD] pkg1", Body: "nothing here", Ref: "refs/heads/main", Repository: &models.RepositoryMeta{ Owner: "test-org", Name: "test-prj", }, State: "open", }, configs) if err != nil { t.Errorf("Unexpected error: %v", err) } }) t.Run("Source SHA update - updates existing temp branch", func(t *testing.T) { mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl) GitHandler = mockGitGen mockGit := mock_common.NewMockGit(ctl) mockGitGen.EXPECT().CreateGitHandler("target-org").Return(mockGit, nil).Times(2) mockGit.EXPECT().Close().Return(nil).Times(2) targetRepo := &models.Repository{ Name: "pkg1", SSHURL: "target-ssh-url", Owner: &models.User{UserName: "target-org"}, } srcRepo := &models.Repository{ Name: "pkg1", SSHURL: "src-ssh-url", DefaultBranch: "master", Owner: &models.User{UserName: "src-org"}, Parent: &models.Repository{ Name: "pkg1", Owner: &models.User{UserName: "target-org"}, }, } sha1 := "abcdef0123456789abcdef0123456789abcdef01" issue1 := &models.Issue{ Title: "[ADD] pkg1", Body: "src-org/pkg1#" + sha1, Index: 123, Repository: &models.RepositoryMeta{Owner: "test-org", Name: "test-prj"}, Ref: "refs/heads/main", State: "open", } // First call expectations gitea.EXPECT().GetRepository("target-org", "pkg1").Return(targetRepo, nil) gitea.EXPECT().GetRepository("src-org", "pkg1").Return(srcRepo, nil) mockGit.EXPECT().GitClone("pkg1", sha1, "src-ssh-url").Return("src-remote", nil) mockGit.EXPECT().GitClone("pkg1", sha1, "target-ssh-url").Return("origin", nil) mockGit.EXPECT().GitExecWithOutputOrPanic("pkg1", "rev-list", "--first-parent", "src-remote/"+sha1).Return(sha1 + "\n" + "parent") mockGit.EXPECT().GitExecOrPanic("pkg1", "checkout", "-B", sha1, "parent") mockGit.EXPECT().GitExecOrPanic("pkg1", "push", "-f", "origin", sha1) mockGit.EXPECT().GitExecWithOutput("pkg1", "ls-remote", "--heads", "src-ssh-url", sha1).Return("", nil) tempBranch := "new_package_123_pkg1" mockGit.EXPECT().GitClone("pkg1", sha1, "target-ssh-url").Return("origin", nil) mockGit.EXPECT().GitExecOrPanic("pkg1", "remote", "add", "source", "src-ssh-url") mockGit.EXPECT().GitExecOrPanic("pkg1", "fetch", "source", sha1) mockGit.EXPECT().GitExecOrPanic("pkg1", "checkout", "-B", tempBranch, "FETCH_HEAD") mockGit.EXPECT().GitExecOrPanic("pkg1", "push", "-f", "origin", tempBranch) pr := &models.PullRequest{ Index: 456, Body: expectedBody, Base: &models.PRBranchInfo{ Repo: targetRepo, }, } gitea.EXPECT().CreatePullRequestIfNotExist(targetRepo, tempBranch, "main", gomock.Any(), gomock.Any()).Return(pr, nil, true) gitea.EXPECT().SetLabels("target-org", "pkg1", int64(456), []string{"new/New Repository"}).Return(nil, nil) err := ProcessIssue(issue1, configs) if err != nil { t.Errorf("First call failed: %v", err) } // Second call with different SHA sha2 := "0123456789abcdef0123456789abcdef01234567" issue2 := &models.Issue{ Title: "[ADD] pkg1", Body: "src-org/pkg1#" + sha2, Index: 123, Repository: &models.RepositoryMeta{Owner: "test-org", Name: "test-prj"}, Ref: "refs/heads/main", State: "open", } gitea.EXPECT().GetRepository("target-org", "pkg1").Return(targetRepo, nil) gitea.EXPECT().GetRepository("src-org", "pkg1").Return(srcRepo, nil) mockGit.EXPECT().GitClone("pkg1", sha2, "src-ssh-url").Return("src-remote", nil) mockGit.EXPECT().GitClone("pkg1", sha2, "target-ssh-url").Return("origin", nil) mockGit.EXPECT().GitExecWithOutputOrPanic("pkg1", "rev-list", "--first-parent", "src-remote/"+sha2).Return(sha2 + "\n" + "parent") mockGit.EXPECT().GitExecOrPanic("pkg1", "checkout", "-B", sha2, "parent") mockGit.EXPECT().GitExecOrPanic("pkg1", "push", "-f", "origin", sha2) mockGit.EXPECT().GitExecWithOutput("pkg1", "ls-remote", "--heads", "src-ssh-url", sha2).Return("", nil) mockGit.EXPECT().GitClone("pkg1", sha2, "target-ssh-url").Return("origin", nil) mockGit.EXPECT().GitExecOrPanic("pkg1", "remote", "add", "source", "src-ssh-url") mockGit.EXPECT().GitExecOrPanic("pkg1", "fetch", "source", sha2) mockGit.EXPECT().GitExecOrPanic("pkg1", "checkout", "-B", tempBranch, "FETCH_HEAD") mockGit.EXPECT().GitExecOrPanic("pkg1", "push", "-f", "origin", tempBranch) // CreatePullRequestIfNotExist should be called with same tempBranch, return existing PR gitea.EXPECT().CreatePullRequestIfNotExist(targetRepo, tempBranch, "main", gomock.Any(), gomock.Any()).Return(pr, nil, false) gitea.EXPECT().UpdatePullRequest("target-org", "pkg1", int64(456), gomock.Any()) err = ProcessIssue(issue2, configs) if err != nil { t.Errorf("Second call failed: %v", err) } }) t.Run("PR already exists and issue is open - does nothing", func(t *testing.T) { issue999 := &models.Issue{ Title: "[ADD] pkg1", Body: "src-org/pkg1#master", Index: 999, Repository: &models.RepositoryMeta{ Owner: "test-org", Name: "test-prj", }, Ref: "refs/heads/main", State: "open", } timeline := []*models.TimelineComment{ { Type: common.TimelineCommentType_PullRequestRef, RefIssue: &models.Issue{ Index: 456, Repository: &models.RepositoryMeta{ Owner: "target-org", Name: "pkg1", }, }, User: &models.User{UserName: "bot-user"}, }, } // We need to override the default GetTimeline mock gitea.EXPECT().GetTimeline("test-org", "test-prj", int64(999)).Return(timeline, nil) mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl) GitHandler = mockGitGen mockGit := mock_common.NewMockGit(ctl) mockGitGen.EXPECT().CreateGitHandler("target-org").Return(mockGit, nil) mockGit.EXPECT().Close().Return(nil) targetRepo := &models.Repository{ Name: "pkg1", Owner: &models.User{UserName: "target-org"}, } gitea.EXPECT().GetRepository("target-org", "pkg1").Return(targetRepo, nil) pr := &models.PullRequest{ Index: 456, Base: &models.PRBranchInfo{ Repo: targetRepo, }, } gitea.EXPECT().GetPullRequest("target-org", "pkg1", int64(456)).Return(pr, nil) err := ProcessIssue(issue999, configs) if err != nil { t.Errorf("Unexpected error: %v", err) } }) t.Run("PR already exists and issue is closed - closes PR", func(t *testing.T) { closedIssue := &models.Issue{ Title: "[ADD] pkg1", Body: "src-org/pkg1#master", Index: 999, Repository: &models.RepositoryMeta{ Owner: "test-org", Name: "test-prj", }, Ref: "refs/heads/main", State: "closed", } timeline := []*models.TimelineComment{ { Type: common.TimelineCommentType_PullRequestRef, RefIssue: &models.Issue{ Index: 456, Repository: &models.RepositoryMeta{ Owner: "target-org", Name: "pkg1", }, }, User: &models.User{UserName: "bot-user"}, }, } gitea.EXPECT().GetTimeline("test-org", "test-prj", int64(999)).Return(timeline, nil) mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl) GitHandler = mockGitGen mockGit := mock_common.NewMockGit(ctl) mockGitGen.EXPECT().CreateGitHandler("target-org").Return(mockGit, nil) mockGit.EXPECT().Close().Return(nil) targetRepo := &models.Repository{ Name: "pkg1", Owner: &models.User{UserName: "target-org"}, } gitea.EXPECT().GetRepository("target-org", "pkg1").Return(targetRepo, nil) pr := &models.PullRequest{ Index: 456, Base: &models.PRBranchInfo{ Repo: targetRepo, }, } gitea.EXPECT().GetPullRequest("target-org", "pkg1", int64(456)).Return(pr, nil) gitea.EXPECT().UpdateIssue("target-org", "pkg1", int64(456), gomock.Any()).Return(nil, nil) err := ProcessIssue(closedIssue, configs) if err != nil { t.Errorf("Unexpected error: %v", err) } }) }