3 Commits

Author SHA256 Message Date
e1059e9447 pr: No need to try to merge changes
All checks were successful
go-generate-check / go-generate-check (pull_request) Successful in 28s
We can reset current worktree and clobber it with the merged changes.
We want to emulate `git merge -s theirs` strategy while
`git merge -Xtheirs` only picks `theirs` in case of conflicts.
So, resetting the changes and reading exact is sufficient

`git read-tree -u` updates the current work tree so we do not have
unstaged changes.
2026-02-24 17:48:58 +01:00
db70452cbc pr: add function that checks and prepares PRs
All checks were successful
go-generate-check / go-generate-check (pull_request) Successful in 26s
2026-02-20 02:07:26 +01:00
53eebb75f7 pr: add merge modes documentation and config parsing 2026-02-19 15:36:09 +01:00
67 changed files with 968 additions and 3751 deletions

View File

@@ -1,52 +0,0 @@
name: Integration tests
on:
push:
branches: ['main']
pull_request:
workflow_dispatch:
env:
HOME: /var/lib/gitea-runner
REPO_URL: http://src.opensuse.org//git-workflow/autogits.git
jobs:
t:
runs-on: linux-x86_64
steps:
- name: whoami
run: whoami
- name: pwd
run: pwd
- name: vars
run: |
set | grep GITEA_
- name: Clone
run: |
git clone -q ${{ env.REPO_URL }}
- name: Checkout
run: |
echo ${{ gitea.ref }}
git fetch origin ${{ gitea.ref }}
git checkout FETCH_HEAD
working-directory: ./autogits
- name: Prepare binaries
run: make build
working-directory: ./autogits
- name: Prepare images
run: make build
working-directory: ./autogits/integration
- name: Make sure the pod is down
run: make down
working-directory: ./autogits/integration
- name: Start images
run: make up
working-directory: ./autogits/integration
- name: Run tests
run: py.test-3.11 -v tests
working-directory: ./autogits/integration
- name: Make sure the pod is down
if: always()
run: make down
working-directory: ./autogits/integration

1
.gitignore vendored
View File

@@ -1,6 +1,5 @@
*.osc *.osc
*.conf *.conf
!/integration/**/*.conf
/integration/gitea-data /integration/gitea-data
/integration/gitea-logs /integration/gitea-logs
/integration/rabbitmq-data /integration/rabbitmq-data

View File

@@ -181,10 +181,8 @@ install -D -m0755 obs-status-service/obs-status-service
install -D -m0644 systemd/obs-status-service.service %{buildroot}%{_unitdir}/obs-status-service.service install -D -m0644 systemd/obs-status-service.service %{buildroot}%{_unitdir}/obs-status-service.service
install -D -m0755 workflow-direct/workflow-direct %{buildroot}%{_bindir}/workflow-direct install -D -m0755 workflow-direct/workflow-direct %{buildroot}%{_bindir}/workflow-direct
install -D -m0644 systemd/workflow-direct@.service %{buildroot}%{_unitdir}/workflow-direct@.service install -D -m0644 systemd/workflow-direct@.service %{buildroot}%{_unitdir}/workflow-direct@.service
install -D -m0644 systemd/workflow-direct.target %{buildroot}%{_unitdir}/workflow-direct.target
install -D -m0755 workflow-pr/workflow-pr %{buildroot}%{_bindir}/workflow-pr install -D -m0755 workflow-pr/workflow-pr %{buildroot}%{_bindir}/workflow-pr
install -D -m0644 systemd/workflow-pr@.service %{buildroot}%{_unitdir}/workflow-pr@.service install -D -m0644 systemd/workflow-pr@.service %{buildroot}%{_unitdir}/workflow-pr@.service
install -D -m0644 systemd/workflow-pr.target %{buildroot}%{_unitdir}/workflow-pr.target
install -D -m0755 utils/hujson/hujson %{buildroot}%{_bindir}/hujson install -D -m0755 utils/hujson/hujson %{buildroot}%{_bindir}/hujson
install -D -m0755 utils/maintainer-update/maintainer-update %{buildroot}%{_bindir}/maintainer-update install -D -m0755 utils/maintainer-update/maintainer-update %{buildroot}%{_bindir}/maintainer-update
@@ -237,28 +235,28 @@ install -D -m0755 utils/maintainer-update/maintainer-update
%service_del_postun obs-status-service.service %service_del_postun obs-status-service.service
%pre workflow-direct %pre workflow-direct
%service_add_pre workflow-direct.target %service_add_pre workflow-direct.service
%post workflow-direct %post workflow-direct
%service_add_post workflow-direct.target %service_add_post workflow-direct.service
%preun workflow-direct %preun workflow-direct
%service_del_preun workflow-direct.target %service_del_preun workflow-direct.service
%postun workflow-direct %postun workflow-direct
%service_del_postun workflow-direct.target %service_del_postun workflow-direct.service
%pre workflow-pr %pre workflow-pr
%service_add_pre workflow-pr.target %service_add_pre workflow-pr.service
%post workflow-pr %post workflow-pr
%service_add_post workflow-pr.target %service_add_post workflow-pr.service
%preun workflow-pr %preun workflow-pr
%service_del_preun workflow-pr.target %service_del_preun workflow-pr.service
%postun workflow-pr %postun workflow-pr
%service_del_postun workflow-pr.target %service_del_postun workflow-pr.service
%files devel-importer %files devel-importer
%license COPYING %license COPYING
@@ -312,12 +310,10 @@ install -D -m0755 utils/maintainer-update/maintainer-update
%doc workflow-direct/README.md %doc workflow-direct/README.md
%{_bindir}/workflow-direct %{_bindir}/workflow-direct
%{_unitdir}/workflow-direct@.service %{_unitdir}/workflow-direct@.service
%{_unitdir}/workflow-direct.target
%files workflow-pr %files workflow-pr
%license COPYING %license COPYING
%doc workflow-pr/README.md %doc workflow-pr/README.md
%{_bindir}/workflow-pr %{_bindir}/workflow-pr
%{_unitdir}/workflow-pr@.service %{_unitdir}/workflow-pr@.service
%{_unitdir}/workflow-pr.target

View File

@@ -14,7 +14,6 @@ func newStringScanner(s string) *bufio.Scanner {
} }
func TestAssociatedPRScanner(t *testing.T) { func TestAssociatedPRScanner(t *testing.T) {
common.SetTestLogger(t)
testTable := []struct { testTable := []struct {
name string name string
input string input string
@@ -96,7 +95,6 @@ func TestAssociatedPRScanner(t *testing.T) {
} }
func TestAppendingPRsToDescription(t *testing.T) { func TestAppendingPRsToDescription(t *testing.T) {
common.SetTestLogger(t)
testTable := []struct { testTable := []struct {
name string name string
desc string desc string

View File

@@ -39,6 +39,10 @@ const (
Permission_ForceMerge = "force-merge" Permission_ForceMerge = "force-merge"
Permission_Group = "release-engineering" Permission_Group = "release-engineering"
MergeModeFF = "ff-only"
MergeModeReplace = "replace"
MergeModeDevel = "devel"
) )
type ConfigFile struct { type ConfigFile struct {
@@ -52,9 +56,9 @@ type ReviewGroup struct {
} }
type QAConfig struct { type QAConfig struct {
Name string Name string
Origin string Origin string
Label string // requires this gitea lable to be set or skipped Label string // requires this gitea lable to be set or skipped
BuildDisableRepos []string // which repos to build disable in the new project BuildDisableRepos []string // which repos to build disable in the new project
} }
@@ -67,7 +71,6 @@ const (
Label_StagingAuto = "staging/Auto" Label_StagingAuto = "staging/Auto"
Label_ReviewPending = "review/Pending" Label_ReviewPending = "review/Pending"
Label_ReviewDone = "review/Done" Label_ReviewDone = "review/Done"
Label_NewRepository = "new/New Repository"
) )
func LabelKey(tag_value string) string { func LabelKey(tag_value string) string {
@@ -89,7 +92,8 @@ type AutogitConfig struct {
Committers []string // group in addition to Reviewers and Maintainers that can order the bot around, mostly as helper for factory-maintainers Committers []string // group in addition to Reviewers and Maintainers that can order the bot around, mostly as helper for factory-maintainers
Subdirs []string // list of directories to sort submodules into. Needed b/c _manifest cannot list non-existent directories Subdirs []string // list of directories to sort submodules into. Needed b/c _manifest cannot list non-existent directories
Labels map[string]string // list of tags, if not default, to apply Labels map[string]string // list of tags, if not default, to apply
MergeMode string // project merge mode
NoProjectGitPR bool // do not automatically create project git PRs, just assign reviewers and assume somethign else creates the ProjectGit PR NoProjectGitPR bool // do not automatically create project git PRs, just assign reviewers and assume somethign else creates the ProjectGit PR
ManualMergeOnly bool // only merge with "Merge OK" comment by Project Maintainers and/or Package Maintainers and/or reviewers ManualMergeOnly bool // only merge with "Merge OK" comment by Project Maintainers and/or Package Maintainers and/or reviewers
@@ -184,6 +188,17 @@ func ReadWorkflowConfig(gitea GiteaFileContentAndRepoFetcher, git_project string
} }
} }
config.GitProjectName = config.GitProjectName + "#" + branch config.GitProjectName = config.GitProjectName + "#" + branch
// verify merge modes
switch config.MergeMode {
case MergeModeFF, MergeModeDevel, MergeModeReplace:
break // good results
case "":
config.MergeMode = MergeModeFF
default:
return nil, fmt.Errorf("Unsupported merge mode in %s: %s", git_project, config.MergeMode)
}
return config, nil return config, nil
} }

View File

@@ -4,6 +4,7 @@ import (
"slices" "slices"
"testing" "testing"
"go.uber.org/mock/gomock"
"src.opensuse.org/autogits/common" "src.opensuse.org/autogits/common"
"src.opensuse.org/autogits/common/gitea-generated/models" "src.opensuse.org/autogits/common/gitea-generated/models"
mock_common "src.opensuse.org/autogits/common/mock" mock_common "src.opensuse.org/autogits/common/mock"
@@ -53,7 +54,7 @@ func TestConfigLabelParser(t *testing.T) {
DefaultBranch: "master", DefaultBranch: "master",
} }
ctl := NewController(t) ctl := gomock.NewController(t)
gitea := mock_common.NewMockGiteaFileContentAndRepoFetcher(ctl) gitea := mock_common.NewMockGiteaFileContentAndRepoFetcher(ctl)
gitea.EXPECT().GetRepositoryFileContent("foo", "bar", "", "workflow.config").Return([]byte(test.json), "abc", nil) gitea.EXPECT().GetRepositoryFileContent("foo", "bar", "", "workflow.config").Return([]byte(test.json), "abc", nil)
gitea.EXPECT().GetRepository("foo", "bar").Return(&repo, nil) gitea.EXPECT().GetRepository("foo", "bar").Return(&repo, nil)
@@ -175,7 +176,7 @@ func TestConfigWorkflowParser(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
gitea := mock_common.NewMockGiteaFileContentAndRepoFetcher(ctl) gitea := mock_common.NewMockGiteaFileContentAndRepoFetcher(ctl)
gitea.EXPECT().GetRepositoryFileContent("foo", "bar", "", "workflow.config").Return([]byte(test.config_json), "abc", nil) gitea.EXPECT().GetRepositoryFileContent("foo", "bar", "", "workflow.config").Return([]byte(test.config_json), "abc", nil)
gitea.EXPECT().GetRepository("foo", "bar").Return(&test.repo, nil) gitea.EXPECT().GetRepository("foo", "bar").Return(&test.repo, nil)
@@ -341,3 +342,67 @@ func TestConfigPermissions(t *testing.T) {
}) })
} }
} }
func TestConfigMergeModeParser(t *testing.T) {
tests := []struct {
name string
json string
mergeMode string
wantErr bool
}{
{
name: "empty",
json: "{}",
mergeMode: common.MergeModeFF,
},
{
name: "ff-only",
json: `{"MergeMode": "ff-only"}`,
mergeMode: common.MergeModeFF,
},
{
name: "replace",
json: `{"MergeMode": "replace"}`,
mergeMode: common.MergeModeReplace,
},
{
name: "devel",
json: `{"MergeMode": "devel"}`,
mergeMode: common.MergeModeDevel,
},
{
name: "unsupported",
json: `{"MergeMode": "invalid"}`,
wantErr: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
repo := models.Repository{
DefaultBranch: "master",
}
ctl := gomock.NewController(t)
gitea := mock_common.NewMockGiteaFileContentAndRepoFetcher(ctl)
gitea.EXPECT().GetRepositoryFileContent("foo", "bar", "", "workflow.config").Return([]byte(test.json), "abc", nil)
gitea.EXPECT().GetRepository("foo", "bar").Return(&repo, nil)
config, err := common.ReadWorkflowConfig(gitea, "foo/bar")
if test.wantErr {
if err == nil {
t.Fatal("Expected error, got nil")
}
return
}
if err != nil {
t.Fatal(err)
}
if config.MergeMode != test.mergeMode {
t.Errorf("Expected MergeMode %s, got %s", test.mergeMode, config.MergeMode)
}
})
}
}

View File

@@ -42,7 +42,6 @@ type GitSubmoduleLister interface {
type GitDirectoryLister interface { type GitDirectoryLister interface {
GitDirectoryList(gitPath, commitId string) (dirlist map[string]string, err error) GitDirectoryList(gitPath, commitId string) (dirlist map[string]string, err error)
GitDirectoryContentList(gitPath, commitId string) (dirlist map[string]string, err error)
} }
type GitStatusLister interface { type GitStatusLister interface {
@@ -273,11 +272,7 @@ func (e *GitHandlerImpl) GitClone(repo, branch, remoteUrl string) (string, error
LogDebug("branch", branch) LogDebug("branch", branch)
} }
*/ */
args := []string{"fetch", "--prune", remoteName} args := []string{"fetch", "--prune", remoteName, branch}
if len(branch) > 0 {
args = append(args, branch)
}
if strings.TrimSpace(e.GitExecWithOutputOrPanic(repo, "rev-parse", "--is-shallow-repository")) == "true" { if strings.TrimSpace(e.GitExecWithOutputOrPanic(repo, "rev-parse", "--is-shallow-repository")) == "true" {
args = slices.Insert(args, 1, "--unshallow") args = slices.Insert(args, 1, "--unshallow")
} }
@@ -792,7 +787,7 @@ func (e *GitHandlerImpl) GitCatFile(cwd, commitId, filename string) (data []byte
return return
} }
// return (directory) -> (hash) map for all submodules // return (filename) -> (hash) map for all submodules
func (e *GitHandlerImpl) GitDirectoryList(gitPath, commitId string) (directoryList map[string]string, err error) { func (e *GitHandlerImpl) GitDirectoryList(gitPath, commitId string) (directoryList map[string]string, err error) {
var done sync.Mutex var done sync.Mutex
directoryList = make(map[string]string) directoryList = make(map[string]string)
@@ -866,82 +861,6 @@ func (e *GitHandlerImpl) GitDirectoryList(gitPath, commitId string) (directoryLi
return directoryList, err return directoryList, err
} }
// return (directory) -> (hash) map for all submodules
func (e *GitHandlerImpl) GitDirectoryContentList(gitPath, commitId string) (directoryList map[string]string, err error) {
var done sync.Mutex
directoryList = make(map[string]string)
done.Lock()
data_in, data_out := ChanIO{make(chan byte)}, ChanIO{make(chan byte)}
LogDebug("Getting directory content for:", commitId)
go func() {
defer done.Unlock()
defer close(data_out.ch)
data_out.Write([]byte(commitId))
data_out.ch <- '\x00'
var c GitCommit
c, err = parseGitCommit(data_in.ch)
if err != nil {
err = fmt.Errorf("Error parsing git commit. Err: %w", err)
return
}
trees := make(map[string]string)
trees[""] = c.Tree
for len(trees) > 0 {
for p, tree := range trees {
delete(trees, p)
data_out.Write([]byte(tree))
data_out.ch <- '\x00'
var tree GitTree
tree, err = parseGitTree(data_in.ch)
if err != nil {
err = fmt.Errorf("Error parsing git tree: %w", err)
return
}
for _, te := range tree.items {
if te.isBlob() || te.isSubmodule() {
directoryList[p+te.name] = te.hash
} else if te.isTree() {
trees[p+te.name] = te.hash
}
}
}
}
}()
cmd := exec.Command("/usr/bin/git", "cat-file", "--batch", "-Z")
cmd.Env = []string{
"GIT_CEILING_DIRECTORIES=" + e.GitPath,
"GIT_LFS_SKIP_SMUDGE=1",
"GIT_CONFIG_GLOBAL=/dev/null",
}
cmd.Dir = filepath.Join(e.GitPath, gitPath)
cmd.Stdout = &data_in
cmd.Stdin = &data_out
cmd.Stderr = writeFunc(func(data []byte) (int, error) {
LogError(string(data))
return len(data), nil
})
LogDebug("command run:", cmd.Args)
if e := cmd.Run(); e != nil {
LogError(e)
close(data_in.ch)
close(data_out.ch)
return directoryList, e
}
done.Lock()
return directoryList, err
}
// return (filename) -> (hash) map for all submodules // return (filename) -> (hash) map for all submodules
func (e *GitHandlerImpl) GitSubmoduleList(gitPath, commitId string) (submoduleList map[string]string, err error) { func (e *GitHandlerImpl) GitSubmoduleList(gitPath, commitId string) (submoduleList map[string]string, err error) {
var done sync.Mutex var done sync.Mutex

View File

@@ -24,14 +24,12 @@ import (
"os" "os"
"os/exec" "os/exec"
"path" "path"
"path/filepath"
"slices" "slices"
"strings" "strings"
"testing" "testing"
) )
func TestGitClone(t *testing.T) { func TestGitClone(t *testing.T) {
SetTestLogger(t)
tests := []struct { tests := []struct {
name string name string
@@ -95,59 +93,7 @@ func TestGitClone(t *testing.T) {
} }
} }
func TestGitCloneCommitID(t *testing.T) {
SetTestLogger(t)
execPath, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
d := t.TempDir()
if err := os.Chdir(d); err != nil {
t.Fatal(err)
}
defer os.Chdir(execPath)
cmd := exec.Command(path.Join(execPath, "test_repo_setup.sh"))
if out, err := cmd.CombinedOutput(); err != nil {
t.Log(string(out))
t.Fatal(err)
}
gh, err := AllocateGitWorkTree(d, "Test", "test@example.com")
if err != nil {
t.Fatal(err)
}
g, err := gh.CreateGitHandler("org")
if err != nil {
t.Fatal(err)
}
// Get a commit ID from pkgA
remoteUrl := "file://" + d + "/pkgA"
out, err := exec.Command("git", "-C", path.Join(d, "pkgA"), "rev-parse", "main").Output()
if err != nil {
t.Fatal(err)
}
commitID := strings.TrimSpace(string(out))
repo := "pkgAcloneCommitID"
if _, err := g.GitClone(repo, commitID, remoteUrl); err != nil {
t.Skip("TODO: Add GitClone CommitID support")
t.Fatalf("GitClone failed with commit ID: %v", err)
}
// Verify we are at the right commit
head, err := g.GitBranchHead(repo, commitID)
if err != nil {
t.Fatalf("GitBranchHead failed: %v", err)
}
if head != commitID {
t.Errorf("Expected head %s, got %s", commitID, head)
}
}
func TestGitMsgParsing(t *testing.T) { func TestGitMsgParsing(t *testing.T) {
SetTestLogger(t)
t.Run("tree message with size 56", func(t *testing.T) { t.Run("tree message with size 56", func(t *testing.T) {
const hdr = "f40888ea4515fe2e8eea617a16f5f50a45f652d894de3ad181d58de3aafb8f98 tree 56\x00" const hdr = "f40888ea4515fe2e8eea617a16f5f50a45f652d894de3ad181d58de3aafb8f98 tree 56\x00"
@@ -226,7 +172,6 @@ func TestGitMsgParsing(t *testing.T) {
} }
func TestGitCommitParsing(t *testing.T) { func TestGitCommitParsing(t *testing.T) {
SetTestLogger(t)
t.Run("parse valid commit message", func(t *testing.T) { t.Run("parse valid commit message", func(t *testing.T) {
const commitData = "f40888ea4515fe2e8eea617a16f5f50a45f652d894de3ad181d58de3aafb8f99 commit 253\000" + const commitData = "f40888ea4515fe2e8eea617a16f5f50a45f652d894de3ad181d58de3aafb8f99 commit 253\000" +
`tree e20033df9f18780756ba4a96dbc7eb1a626253961039cb674156f266ba7a4e53 `tree e20033df9f18780756ba4a96dbc7eb1a626253961039cb674156f266ba7a4e53
@@ -437,7 +382,6 @@ dummy change, don't merge
} }
func TestCommitTreeParsing(t *testing.T) { func TestCommitTreeParsing(t *testing.T) {
SetTestLogger(t)
gitDir := t.TempDir() gitDir := t.TempDir()
testDir, _ := os.Getwd() testDir, _ := os.Getwd()
var commitId string var commitId string
@@ -546,7 +490,6 @@ func TestCommitTreeParsing(t *testing.T) {
} }
func TestGitStatusParse(t *testing.T) { func TestGitStatusParse(t *testing.T) {
SetTestLogger(t)
testData := []struct { testData := []struct {
name string name string
data []byte data []byte
@@ -653,67 +596,3 @@ func TestGitStatusParse(t *testing.T) {
}) })
} }
} }
func TestGitDirectoryListRepro(t *testing.T) {
SetTestLogger(t)
d := t.TempDir()
// Setup a mock environment for GitHandlerImpl
gh, err := AllocateGitWorkTree(d, "Test", "test@example.com")
if err != nil {
t.Fatal(err)
}
org := "repo-org"
repoName := "test-repo"
repoPath := filepath.Join(d, org, repoName)
err = os.MkdirAll(repoPath, 0755)
if err != nil {
t.Fatal(err)
}
runGit := func(args ...string) {
cmd := exec.Command("git", args...)
cmd.Dir = repoPath
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("git %v failed: %v\n%s", args, err, out)
}
}
runGit("init", "-b", "main", "--object-format=sha256")
runGit("config", "user.email", "test@example.com")
runGit("config", "user.name", "test")
// Create a directory and a file
err = os.Mkdir(filepath.Join(repoPath, "subdir"), 0755)
if err != nil {
t.Fatal(err)
}
err = os.WriteFile(filepath.Join(repoPath, "subdir", "file.txt"), []byte("hello"), 0644)
if err != nil {
t.Fatal(err)
}
runGit("add", "subdir/file.txt")
runGit("commit", "-m", "add subdir")
// Now create the handler
g, err := gh.CreateGitHandler(org)
if err != nil {
t.Fatal(err)
}
// Call GitDirectoryList
dirs, err := g.GitDirectoryList(repoName, "HEAD")
if err != nil {
t.Fatal(err)
}
t.Logf("Directories found: %v", dirs)
if len(dirs) == 0 {
t.Error("No directories found, but 'subdir' should be there")
}
if _, ok := dirs["subdir"]; !ok {
t.Errorf("Expected 'subdir' in directory list, got %v", dirs)
}
}

View File

@@ -75,10 +75,6 @@ type GiteaLabelSettter interface {
SetLabels(org, repo string, idx int64, labels []string) ([]*models.Label, error) SetLabels(org, repo string, idx int64, labels []string) ([]*models.Label, error)
} }
type GiteaIssueFetcher interface {
GetIssue(org, repo string, idx int64) (*models.Issue, error)
}
type GiteaTimelineFetcher interface { type GiteaTimelineFetcher interface {
ResetTimelineCache(org, repo string, idx int64) ResetTimelineCache(org, repo string, idx int64)
GetTimeline(org, repo string, idx int64) ([]*models.TimelineComment, error) GetTimeline(org, repo string, idx int64) ([]*models.TimelineComment, error)
@@ -152,7 +148,6 @@ type GiteaReviewRequester interface {
type GiteaReviewUnrequester interface { type GiteaReviewUnrequester interface {
UnrequestReview(org, repo string, id int64, reviwers ...string) error UnrequestReview(org, repo string, id int64, reviwers ...string) error
UpdateIssue(org, repo string, idx int64, options *models.EditIssueOption) (*models.Issue, error)
} }
type GiteaReviewer interface { type GiteaReviewer interface {
@@ -206,7 +201,6 @@ type Gitea interface {
GiteaSetRepoOptions GiteaSetRepoOptions
GiteaLabelGetter GiteaLabelGetter
GiteaLabelSettter GiteaLabelSettter
GiteaIssueFetcher
GetNotifications(Type string, since *time.Time) ([]*models.NotificationThread, error) GetNotifications(Type string, since *time.Time) ([]*models.NotificationThread, error)
GetDoneNotifications(Type string, page int64) ([]*models.NotificationThread, error) GetDoneNotifications(Type string, page int64) ([]*models.NotificationThread, error)
@@ -515,26 +509,6 @@ func (gitea *GiteaTransport) SetLabels(owner, repo string, idx int64, labels []s
return ret.Payload, nil return ret.Payload, nil
} }
func (gitea *GiteaTransport) GetIssue(owner, repo string, idx int64) (*models.Issue, error) {
ret, err := gitea.client.Issue.IssueGetIssue(
issue.NewIssueGetIssueParams().WithOwner(owner).WithRepo(repo).WithIndex(idx),
gitea.transport.DefaultAuthentication)
if err != nil {
return nil, err
}
return ret.Payload, nil
}
func (gitea *GiteaTransport) UpdateIssue(owner, repo string, idx int64, options *models.EditIssueOption) (*models.Issue, error) {
ret, err := gitea.client.Issue.IssueEditIssue(
issue.NewIssueEditIssueParams().WithOwner(owner).WithRepo(repo).WithIndex(idx).WithBody(options),
gitea.transport.DefaultAuthentication)
if err != nil {
return nil, err
}
return ret.Payload, nil
}
const ( const (
GiteaNotificationType_Pull = "Pull" GiteaNotificationType_Pull = "Pull"
) )
@@ -742,7 +716,6 @@ func (gitea *GiteaTransport) CreatePullRequestIfNotExist(repo *models.Repository
) )
if err != nil { if err != nil {
LogError("owner:", repo.Owner.UserName, " repo:", repo.Name, " body:", prOptions)
return nil, fmt.Errorf("Cannot create pull request. %w", err), true return nil, fmt.Errorf("Cannot create pull request. %w", err), true
} }

View File

@@ -67,16 +67,6 @@ func GetLoggingLevel() LogLevel {
return logLevel return logLevel
} }
type Logger interface {
Log(args ...any)
}
var testLogger Logger
func SetTestLogger(l Logger) {
testLogger = l
}
func SetLoggingLevelFromString(ll string) error { func SetLoggingLevelFromString(ll string) error {
switch ll { switch ll {
case "info": case "info":
@@ -96,26 +86,18 @@ func SetLoggingLevelFromString(ll string) error {
func LogError(params ...any) { func LogError(params ...any) {
if logLevel >= LogLevelError { if logLevel >= LogLevelError {
logit("[E]", params...) log.Println(append([]any{"[E]"}, params...)...)
} }
} }
func LogDebug(params ...any) { func LogDebug(params ...any) {
if logLevel >= LogLevelDebug { if logLevel >= LogLevelDebug {
logit("[D]", params...) log.Println(append([]any{"[D]"}, params...)...)
} }
} }
func LogInfo(params ...any) { func LogInfo(params ...any) {
if logLevel >= LogLevelInfo { if logLevel >= LogLevelInfo {
logit("[I]", params...) log.Println(append([]any{"[I]"}, params...)...)
}
}
func logit(prefix string, params ...any) {
if testLogger != nil {
testLogger.Log(append([]any{prefix}, params...)...)
} else {
log.Println(append([]any{prefix}, params...)...)
} }
} }

View File

@@ -172,7 +172,7 @@ func TestMaintainership(t *testing.T) {
} }
t.Run(test.name+"_File", func(t *testing.T) { t.Run(test.name+"_File", func(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
mi := mock_common.NewMockGiteaMaintainershipReader(ctl) mi := mock_common.NewMockGiteaMaintainershipReader(ctl)
// tests with maintainership file // tests with maintainership file
@@ -185,7 +185,7 @@ func TestMaintainership(t *testing.T) {
}) })
t.Run(test.name+"_Dir", func(t *testing.T) { t.Run(test.name+"_Dir", func(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
mi := mock_common.NewMockGiteaMaintainershipReader(ctl) mi := mock_common.NewMockGiteaMaintainershipReader(ctl)
// run same tests with directory maintainership data // run same tests with directory maintainership data

View File

@@ -7,7 +7,6 @@ import (
) )
func TestManifestSubdirAssignments(t *testing.T) { func TestManifestSubdirAssignments(t *testing.T) {
common.SetTestLogger(t)
tests := []struct { tests := []struct {
Name string Name string
ManifestContent string ManifestContent string

View File

@@ -142,45 +142,6 @@ func (m *MockGitDirectoryLister) EXPECT() *MockGitDirectoryListerMockRecorder {
return m.recorder return m.recorder
} }
// GitDirectoryContentList mocks base method.
func (m *MockGitDirectoryLister) GitDirectoryContentList(gitPath, commitId string) (map[string]string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GitDirectoryContentList", gitPath, commitId)
ret0, _ := ret[0].(map[string]string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GitDirectoryContentList indicates an expected call of GitDirectoryContentList.
func (mr *MockGitDirectoryListerMockRecorder) GitDirectoryContentList(gitPath, commitId any) *MockGitDirectoryListerGitDirectoryContentListCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitDirectoryContentList", reflect.TypeOf((*MockGitDirectoryLister)(nil).GitDirectoryContentList), gitPath, commitId)
return &MockGitDirectoryListerGitDirectoryContentListCall{Call: call}
}
// MockGitDirectoryListerGitDirectoryContentListCall wrap *gomock.Call
type MockGitDirectoryListerGitDirectoryContentListCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGitDirectoryListerGitDirectoryContentListCall) Return(dirlist map[string]string, err error) *MockGitDirectoryListerGitDirectoryContentListCall {
c.Call = c.Call.Return(dirlist, err)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGitDirectoryListerGitDirectoryContentListCall) Do(f func(string, string) (map[string]string, error)) *MockGitDirectoryListerGitDirectoryContentListCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGitDirectoryListerGitDirectoryContentListCall) DoAndReturn(f func(string, string) (map[string]string, error)) *MockGitDirectoryListerGitDirectoryContentListCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// GitDirectoryList mocks base method. // GitDirectoryList mocks base method.
func (m *MockGitDirectoryLister) GitDirectoryList(gitPath, commitId string) (map[string]string, error) { func (m *MockGitDirectoryLister) GitDirectoryList(gitPath, commitId string) (map[string]string, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
@@ -602,45 +563,6 @@ func (c *MockGitGitDiffCall) DoAndReturn(f func(string, string, string) (string,
return c return c
} }
// GitDirectoryContentList mocks base method.
func (m *MockGit) GitDirectoryContentList(gitPath, commitId string) (map[string]string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GitDirectoryContentList", gitPath, commitId)
ret0, _ := ret[0].(map[string]string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GitDirectoryContentList indicates an expected call of GitDirectoryContentList.
func (mr *MockGitMockRecorder) GitDirectoryContentList(gitPath, commitId any) *MockGitGitDirectoryContentListCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GitDirectoryContentList", reflect.TypeOf((*MockGit)(nil).GitDirectoryContentList), gitPath, commitId)
return &MockGitGitDirectoryContentListCall{Call: call}
}
// MockGitGitDirectoryContentListCall wrap *gomock.Call
type MockGitGitDirectoryContentListCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGitGitDirectoryContentListCall) Return(dirlist map[string]string, err error) *MockGitGitDirectoryContentListCall {
c.Call = c.Call.Return(dirlist, err)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGitGitDirectoryContentListCall) Do(f func(string, string) (map[string]string, error)) *MockGitGitDirectoryContentListCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGitGitDirectoryContentListCall) DoAndReturn(f func(string, string) (map[string]string, error)) *MockGitGitDirectoryContentListCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// GitDirectoryList mocks base method. // GitDirectoryList mocks base method.
func (m *MockGit) GitDirectoryList(gitPath, commitId string) (map[string]string, error) { func (m *MockGit) GitDirectoryList(gitPath, commitId string) (map[string]string, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()

View File

@@ -144,69 +144,6 @@ func (c *MockGiteaLabelSettterSetLabelsCall) DoAndReturn(f func(string, string,
return c return c
} }
// MockGiteaIssueFetcher is a mock of GiteaIssueFetcher interface.
type MockGiteaIssueFetcher struct {
ctrl *gomock.Controller
recorder *MockGiteaIssueFetcherMockRecorder
isgomock struct{}
}
// MockGiteaIssueFetcherMockRecorder is the mock recorder for MockGiteaIssueFetcher.
type MockGiteaIssueFetcherMockRecorder struct {
mock *MockGiteaIssueFetcher
}
// NewMockGiteaIssueFetcher creates a new mock instance.
func NewMockGiteaIssueFetcher(ctrl *gomock.Controller) *MockGiteaIssueFetcher {
mock := &MockGiteaIssueFetcher{ctrl: ctrl}
mock.recorder = &MockGiteaIssueFetcherMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockGiteaIssueFetcher) EXPECT() *MockGiteaIssueFetcherMockRecorder {
return m.recorder
}
// GetIssue mocks base method.
func (m *MockGiteaIssueFetcher) GetIssue(org, repo string, idx int64) (*models.Issue, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetIssue", org, repo, idx)
ret0, _ := ret[0].(*models.Issue)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetIssue indicates an expected call of GetIssue.
func (mr *MockGiteaIssueFetcherMockRecorder) GetIssue(org, repo, idx any) *MockGiteaIssueFetcherGetIssueCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIssue", reflect.TypeOf((*MockGiteaIssueFetcher)(nil).GetIssue), org, repo, idx)
return &MockGiteaIssueFetcherGetIssueCall{Call: call}
}
// MockGiteaIssueFetcherGetIssueCall wrap *gomock.Call
type MockGiteaIssueFetcherGetIssueCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaIssueFetcherGetIssueCall) Return(arg0 *models.Issue, arg1 error) *MockGiteaIssueFetcherGetIssueCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaIssueFetcherGetIssueCall) Do(f func(string, string, int64) (*models.Issue, error)) *MockGiteaIssueFetcherGetIssueCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaIssueFetcherGetIssueCall) DoAndReturn(f func(string, string, int64) (*models.Issue, error)) *MockGiteaIssueFetcherGetIssueCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// MockGiteaTimelineFetcher is a mock of GiteaTimelineFetcher interface. // MockGiteaTimelineFetcher is a mock of GiteaTimelineFetcher interface.
type MockGiteaTimelineFetcher struct { type MockGiteaTimelineFetcher struct {
ctrl *gomock.Controller ctrl *gomock.Controller
@@ -1686,45 +1623,6 @@ func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterUnrequestReviewCall) Do
return c return c
} }
// UpdateIssue mocks base method.
func (m *MockGiteaReviewFetcherAndRequesterAndUnrequester) UpdateIssue(org, repo string, idx int64, options *models.EditIssueOption) (*models.Issue, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateIssue", org, repo, idx, options)
ret0, _ := ret[0].(*models.Issue)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateIssue indicates an expected call of UpdateIssue.
func (mr *MockGiteaReviewFetcherAndRequesterAndUnrequesterMockRecorder) UpdateIssue(org, repo, idx, options any) *MockGiteaReviewFetcherAndRequesterAndUnrequesterUpdateIssueCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateIssue", reflect.TypeOf((*MockGiteaReviewFetcherAndRequesterAndUnrequester)(nil).UpdateIssue), org, repo, idx, options)
return &MockGiteaReviewFetcherAndRequesterAndUnrequesterUpdateIssueCall{Call: call}
}
// MockGiteaReviewFetcherAndRequesterAndUnrequesterUpdateIssueCall wrap *gomock.Call
type MockGiteaReviewFetcherAndRequesterAndUnrequesterUpdateIssueCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterUpdateIssueCall) Return(arg0 *models.Issue, arg1 error) *MockGiteaReviewFetcherAndRequesterAndUnrequesterUpdateIssueCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterUpdateIssueCall) Do(f func(string, string, int64, *models.EditIssueOption) (*models.Issue, error)) *MockGiteaReviewFetcherAndRequesterAndUnrequesterUpdateIssueCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaReviewFetcherAndRequesterAndUnrequesterUpdateIssueCall) DoAndReturn(f func(string, string, int64, *models.EditIssueOption) (*models.Issue, error)) *MockGiteaReviewFetcherAndRequesterAndUnrequesterUpdateIssueCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// MockGiteaUnreviewTimelineFetcher is a mock of GiteaUnreviewTimelineFetcher interface. // MockGiteaUnreviewTimelineFetcher is a mock of GiteaUnreviewTimelineFetcher interface.
type MockGiteaUnreviewTimelineFetcher struct { type MockGiteaUnreviewTimelineFetcher struct {
ctrl *gomock.Controller ctrl *gomock.Controller
@@ -1867,45 +1765,6 @@ func (c *MockGiteaUnreviewTimelineFetcherUnrequestReviewCall) DoAndReturn(f func
return c return c
} }
// UpdateIssue mocks base method.
func (m *MockGiteaUnreviewTimelineFetcher) UpdateIssue(org, repo string, idx int64, options *models.EditIssueOption) (*models.Issue, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateIssue", org, repo, idx, options)
ret0, _ := ret[0].(*models.Issue)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateIssue indicates an expected call of UpdateIssue.
func (mr *MockGiteaUnreviewTimelineFetcherMockRecorder) UpdateIssue(org, repo, idx, options any) *MockGiteaUnreviewTimelineFetcherUpdateIssueCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateIssue", reflect.TypeOf((*MockGiteaUnreviewTimelineFetcher)(nil).UpdateIssue), org, repo, idx, options)
return &MockGiteaUnreviewTimelineFetcherUpdateIssueCall{Call: call}
}
// MockGiteaUnreviewTimelineFetcherUpdateIssueCall wrap *gomock.Call
type MockGiteaUnreviewTimelineFetcherUpdateIssueCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaUnreviewTimelineFetcherUpdateIssueCall) Return(arg0 *models.Issue, arg1 error) *MockGiteaUnreviewTimelineFetcherUpdateIssueCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaUnreviewTimelineFetcherUpdateIssueCall) Do(f func(string, string, int64, *models.EditIssueOption) (*models.Issue, error)) *MockGiteaUnreviewTimelineFetcherUpdateIssueCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaUnreviewTimelineFetcherUpdateIssueCall) DoAndReturn(f func(string, string, int64, *models.EditIssueOption) (*models.Issue, error)) *MockGiteaUnreviewTimelineFetcherUpdateIssueCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// MockGiteaReviewRequester is a mock of GiteaReviewRequester interface. // MockGiteaReviewRequester is a mock of GiteaReviewRequester interface.
type MockGiteaReviewRequester struct { type MockGiteaReviewRequester struct {
ctrl *gomock.Controller ctrl *gomock.Controller
@@ -2041,45 +1900,6 @@ func (c *MockGiteaReviewUnrequesterUnrequestReviewCall) DoAndReturn(f func(strin
return c return c
} }
// UpdateIssue mocks base method.
func (m *MockGiteaReviewUnrequester) UpdateIssue(org, repo string, idx int64, options *models.EditIssueOption) (*models.Issue, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateIssue", org, repo, idx, options)
ret0, _ := ret[0].(*models.Issue)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateIssue indicates an expected call of UpdateIssue.
func (mr *MockGiteaReviewUnrequesterMockRecorder) UpdateIssue(org, repo, idx, options any) *MockGiteaReviewUnrequesterUpdateIssueCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateIssue", reflect.TypeOf((*MockGiteaReviewUnrequester)(nil).UpdateIssue), org, repo, idx, options)
return &MockGiteaReviewUnrequesterUpdateIssueCall{Call: call}
}
// MockGiteaReviewUnrequesterUpdateIssueCall wrap *gomock.Call
type MockGiteaReviewUnrequesterUpdateIssueCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaReviewUnrequesterUpdateIssueCall) Return(arg0 *models.Issue, arg1 error) *MockGiteaReviewUnrequesterUpdateIssueCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaReviewUnrequesterUpdateIssueCall) Do(f func(string, string, int64, *models.EditIssueOption) (*models.Issue, error)) *MockGiteaReviewUnrequesterUpdateIssueCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaReviewUnrequesterUpdateIssueCall) DoAndReturn(f func(string, string, int64, *models.EditIssueOption) (*models.Issue, error)) *MockGiteaReviewUnrequesterUpdateIssueCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// MockGiteaReviewer is a mock of GiteaReviewer interface. // MockGiteaReviewer is a mock of GiteaReviewer interface.
type MockGiteaReviewer struct { type MockGiteaReviewer struct {
ctrl *gomock.Controller ctrl *gomock.Controller
@@ -2874,45 +2694,6 @@ func (c *MockGiteaGetDoneNotificationsCall) DoAndReturn(f func(string, int64) ([
return c return c
} }
// GetIssue mocks base method.
func (m *MockGitea) GetIssue(org, repo string, idx int64) (*models.Issue, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetIssue", org, repo, idx)
ret0, _ := ret[0].(*models.Issue)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetIssue indicates an expected call of GetIssue.
func (mr *MockGiteaMockRecorder) GetIssue(org, repo, idx any) *MockGiteaGetIssueCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIssue", reflect.TypeOf((*MockGitea)(nil).GetIssue), org, repo, idx)
return &MockGiteaGetIssueCall{Call: call}
}
// MockGiteaGetIssueCall wrap *gomock.Call
type MockGiteaGetIssueCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaGetIssueCall) Return(arg0 *models.Issue, arg1 error) *MockGiteaGetIssueCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaGetIssueCall) Do(f func(string, string, int64) (*models.Issue, error)) *MockGiteaGetIssueCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaGetIssueCall) DoAndReturn(f func(string, string, int64) (*models.Issue, error)) *MockGiteaGetIssueCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// GetIssueComments mocks base method. // GetIssueComments mocks base method.
func (m *MockGitea) GetIssueComments(org, project string, issueNo int64) ([]*models.Comment, error) { func (m *MockGitea) GetIssueComments(org, project string, issueNo int64) ([]*models.Comment, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
@@ -3777,45 +3558,6 @@ func (c *MockGiteaUnrequestReviewCall) DoAndReturn(f func(string, string, int64,
return c return c
} }
// UpdateIssue mocks base method.
func (m *MockGitea) UpdateIssue(org, repo string, idx int64, options *models.EditIssueOption) (*models.Issue, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateIssue", org, repo, idx, options)
ret0, _ := ret[0].(*models.Issue)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateIssue indicates an expected call of UpdateIssue.
func (mr *MockGiteaMockRecorder) UpdateIssue(org, repo, idx, options any) *MockGiteaUpdateIssueCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateIssue", reflect.TypeOf((*MockGitea)(nil).UpdateIssue), org, repo, idx, options)
return &MockGiteaUpdateIssueCall{Call: call}
}
// MockGiteaUpdateIssueCall wrap *gomock.Call
type MockGiteaUpdateIssueCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockGiteaUpdateIssueCall) Return(arg0 *models.Issue, arg1 error) *MockGiteaUpdateIssueCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockGiteaUpdateIssueCall) Do(f func(string, string, int64, *models.EditIssueOption) (*models.Issue, error)) *MockGiteaUpdateIssueCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockGiteaUpdateIssueCall) DoAndReturn(f func(string, string, int64, *models.EditIssueOption) (*models.Issue, error)) *MockGiteaUpdateIssueCall {
c.Call = c.Call.DoAndReturn(f)
return c
}
// UpdatePullRequest mocks base method. // UpdatePullRequest mocks base method.
func (m *MockGitea) UpdatePullRequest(org, repo string, num int64, options *models.EditPullRequestOption) (*models.PullRequest, error) { func (m *MockGitea) UpdatePullRequest(org, repo string, num int64, options *models.EditPullRequestOption) (*models.PullRequest, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()

View File

@@ -6,9 +6,7 @@ import (
"fmt" "fmt"
"os" "os"
"path" "path"
"regexp"
"slices" "slices"
"strconv"
"strings" "strings"
"src.opensuse.org/autogits/common/gitea-generated/client/repository" "src.opensuse.org/autogits/common/gitea-generated/client/repository"
@@ -554,6 +552,144 @@ func (rs *PRSet) IsApproved(gitea GiteaPRChecker, maintainers MaintainershipData
return is_manually_reviewed_ok return is_manually_reviewed_ok
} }
func (rs *PRSet) AddMergeCommit(git Git, remote string, pr int) bool {
prinfo := rs.PRs[pr]
LogDebug("Adding merge commit for %s", PRtoString(prinfo.PR))
if !prinfo.PR.AllowMaintainerEdit {
LogError(" PR is not editable by maintainer")
return false
}
repo := prinfo.PR.Base.Repo
head := prinfo.PR.Head
br := rs.Config.Branch
if len(br) == 0 {
br = prinfo.PR.Base.Name
}
msg := fmt.Sprintf("Merge branch '%s' into %s", br, head.Name)
if err := git.GitExec(repo.Name, "merge", "--no-ff", "--no-commit", "-X", "theirs", head.Sha); err != nil {
if err := git.GitExec(repo.Name, "merge", "--no-ff", "--no-commit", "--allow-unrelated-histories", "-X", "theirs", head.Sha); err != nil {
return false
}
LogError("WARNING: Merging unrelated histories")
}
// ensure only files that are in head.Sha are kept
git.GitExecOrPanic(repo.Name, "read-tree", "--reset", "-u", head.Sha)
git.GitExecOrPanic(repo.Name, "commit", "-m", msg)
if !IsDryRun {
git.GitExecOrPanic(repo.Name, "push", remote, "HEAD:"+head.Name)
prinfo.PR.Head.Sha = strings.TrimSpace(git.GitExecWithOutputOrPanic(repo.Name, "rev-list", "-1", "HEAD")) // need to update as it's pushed but pr not refetched
}
return true
}
func (rs *PRSet) HasMerge(git Git, pr int) bool {
prinfo := rs.PRs[pr]
repo := prinfo.PR.Base.Repo
head := prinfo.PR.Head
br := rs.Config.Branch
if len(br) == 0 {
br = prinfo.PR.Base.Name
}
parents, err := git.GitExecWithOutput(repo.Name, "show", "-s", "--format=%P", head.Sha)
if err == nil {
p := strings.Fields(strings.TrimSpace(parents))
if len(p) == 2 {
targetHead, _ := git.GitExecWithOutput(repo.Name, "rev-parse", "HEAD")
targetHead = strings.TrimSpace(targetHead)
if p[0] == targetHead || p[1] == targetHead {
return true
}
}
}
return false
}
func (rs *PRSet) PrepareForMerge(git Git) bool {
// verify that package can merge here. Checkout current target branch of each PRSet, make a temporary branch
// PR_#_mergetest and perform the merge based
if rs.Config.MergeMode == MergeModeDevel {
return true // always can merge as we set branch here, not merge anything
} else {
// make sure that all the package PRs are in mergeable state
for idx, prinfo := range rs.PRs {
if rs.IsPrjGitPR(prinfo.PR) {
continue
}
repo := prinfo.PR.Base.Repo
head := prinfo.PR.Head
br := rs.Config.Branch
if len(br) == 0 {
br = prinfo.PR.Base.Name
}
remote, err := git.GitClone(repo.Name, br, repo.SSHURL)
if err != nil {
return false
}
git.GitExecOrPanic(repo.Name, "fetch", remote, head.Sha)
switch rs.Config.MergeMode {
case MergeModeFF:
if err := git.GitExec(repo.Name, "merge-base", "--is-ancestor", "HEAD", head.Sha); err != nil {
return false
}
case MergeModeReplace:
Verify:
if err := git.GitExec(repo.Name, "merge-base", "--is-ancestor", "HEAD", head.Sha); err != nil {
if !rs.HasMerge(git, idx) {
forkRemote, err := git.GitClone(repo.Name, head.Name, head.Repo.SSHURL)
if err != nil {
LogError("Failed to clone head repo:", head.Name, head.Repo.SSHURL)
return false
}
LogDebug("Merge commit is missing and this is not FF merge possibility")
git.GitExecOrPanic(repo.Name, "checkout", remote+"/"+br)
if !rs.AddMergeCommit(git, forkRemote, idx) {
return false
}
if !IsDryRun {
goto Verify
}
}
}
}
}
}
// now we check project git if mergeable
prjgit_info, err := rs.GetPrjGitPR()
if err != nil {
return false
}
prjgit := prjgit_info.PR
_, _, prjgitBranch := rs.Config.GetPrjGit()
remote, err := git.GitClone(DefaultGitPrj, prjgitBranch, prjgit.Base.Repo.SSHURL)
if err != nil {
return false
}
testBranch := fmt.Sprintf("PR_%d_mergetest", prjgit.Index)
git.GitExecOrPanic(DefaultGitPrj, "fetch", remote, prjgit.Head.Sha)
if err := git.GitExec(DefaultGitPrj, "checkout", "-B", testBranch, prjgit.Base.Sha); err != nil {
return false
}
if err := git.GitExec(DefaultGitPrj, "merge", "--no-ff", "--no-commit", prjgit.Head.Sha); err != nil {
return false
}
return true
}
func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error { func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error {
prjgit_info, err := rs.GetPrjGitPR() prjgit_info, err := rs.GetPrjGitPR()
if err != nil { if err != nil {
@@ -661,8 +797,6 @@ func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error {
} }
// FF all non-prj git and unrequest reviews. // FF all non-prj git and unrequest reviews.
newRepoIssues := make(map[int64]string) // issue index -> org/repo
for _, prinfo := range rs.PRs { for _, prinfo := range rs.PRs {
// remove pending review requests // remove pending review requests
repo := prinfo.PR.Base.Repo repo := prinfo.PR.Base.Repo
@@ -684,15 +818,6 @@ func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error {
if rs.IsPrjGitPR(prinfo.PR) { if rs.IsPrjGitPR(prinfo.PR) {
continue continue
} }
isNewRepo := false
for _, l := range prinfo.PR.Labels {
if l.Name == Label_NewRepository {
isNewRepo = true
break
}
}
br := rs.Config.Branch br := rs.Config.Branch
if len(br) == 0 { if len(br) == 0 {
// if branch is unspecified, take it from the PR as it // if branch is unspecified, take it from the PR as it
@@ -701,30 +826,15 @@ func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error {
} else if br != prinfo.PR.Base.Name { } else if br != prinfo.PR.Base.Name {
panic(prinfo.PR.Base.Name + " is expected to match " + br) panic(prinfo.PR.Base.Name + " is expected to match " + br)
} }
if isNewRepo {
// Extract issue reference from body: "See issue #XYZ"
rx := regexp.MustCompile(`See issue #(\d+)`)
if matches := rx.FindStringSubmatch(prinfo.PR.Body); len(matches) > 1 {
if issueIdx, err := strconv.ParseInt(matches[1], 10, 64); err == nil {
// We need to know which project git this issue belongs to.
// Since the PR set is linked to a ProjectGit, we can use its org/repo.
prjGitOrg, prjGitRepo, _ := rs.Config.GetPrjGit()
newRepoIssues[issueIdx] = prjGitOrg + "/" + prjGitRepo
}
}
}
prinfo.RemoteName, err = git.GitClone(repo.Name, br, repo.SSHURL) prinfo.RemoteName, err = git.GitClone(repo.Name, br, repo.SSHURL)
PanicOnError(err) PanicOnError(err)
git.GitExecOrPanic(repo.Name, "fetch", prinfo.RemoteName, head.Sha) if rs.Config.MergeMode == MergeModeDevel {
git.GitExecOrPanic(repo.Name, "checkout", "-B", br, head.Sha)
if isNewRepo {
LogInfo("Force-pushing new repository branch", br, "to", head.Sha)
// we don't merge, we just set the branch to this commit
} else { } else {
git.GitExecOrPanic(repo.Name, "fetch", prinfo.RemoteName, head.Sha)
git.GitExecOrPanic(repo.Name, "merge", "--ff", head.Sha) git.GitExecOrPanic(repo.Name, "merge", "--ff", head.Sha)
} }
} }
// push changes // push changes
@@ -739,37 +849,17 @@ func (rs *PRSet) Merge(gitea GiteaReviewUnrequester, git Git) error {
} }
repo := prinfo.PR.Base.Repo repo := prinfo.PR.Base.Repo
isNewRepo := false
for _, l := range prinfo.PR.Labels {
if l.Name == Label_NewRepository {
isNewRepo = true
break
}
}
if !IsDryRun { if !IsDryRun {
if isNewRepo { params := []string{"push"}
git.GitExecOrPanic(repo.Name, "push", "-f", prinfo.RemoteName, prinfo.PR.Head.Sha+":"+prinfo.PR.Base.Name) if rs.Config.MergeMode == MergeModeDevel {
} else { params = append(params, "-f")
git.GitExecOrPanic(repo.Name, "push", prinfo.RemoteName)
} }
params = append(params, prinfo.RemoteName)
git.GitExecOrPanic(repo.Name, params...)
} else { } else {
LogInfo("*** WOULD push", repo.Name, "to", prinfo.RemoteName) LogInfo("*** WOULD push", repo.Name, "to", prinfo.RemoteName)
} }
} }
// Close referencing issues
if !IsDryRun {
for issueIdx, prjPath := range newRepoIssues {
parts := strings.Split(prjPath, "/")
if len(parts) == 2 {
LogInfo("Closing issue", prjPath+"#"+strconv.FormatInt(issueIdx, 10))
gitea.UpdateIssue(parts[0], parts[1], issueIdx, &models.EditIssueOption{
State: "closed",
})
}
}
}
return nil return nil
} }

View File

@@ -1,123 +0,0 @@
package common_test
import (
"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 TestFetchPRSet_Linkage(t *testing.T) {
config := &common.AutogitConfig{
Organization: "target-org",
GitProjectName: "test-org/prjgit#main",
}
// 1. Mock a package PR
pkgPR := &models.PullRequest{
Index: 101,
State: "open",
Base: &models.PRBranchInfo{
Ref: "main",
Repo: &models.Repository{
Name: "pkg1",
Owner: &models.User{UserName: "target-org"},
},
},
Head: &models.PRBranchInfo{Sha: "pkg-sha"},
}
// 2. Mock a ProjectGit PR that references the package PR
prjGitPR := &models.PullRequest{
Index: 500,
State: "open",
Base: &models.PRBranchInfo{
Ref: "main",
Name: "main",
Repo: &models.Repository{
Name: "prjgit",
Owner: &models.User{UserName: "test-org"},
},
},
Body: "Forwarded PRs: pkg1\n\nPR: target-org/pkg1!101",
}
t.Run("Fetch from ProjectGit PR", func(t *testing.T) {
ctl := NewController(t)
defer ctl.Finish()
mockGitea := mock_common.NewMockGiteaPRTimelineReviewFetcher(ctl)
mockGitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
// Expect fetch of prjGitPR
mockGitea.EXPECT().GetPullRequest("test-org", "prjgit", int64(500)).Return(prjGitPR, nil)
// Expect fetch of pkgPR because it's linked in body
mockGitea.EXPECT().GetPullRequest("target-org", "pkg1", int64(101)).Return(pkgPR, nil)
// Expect review fetching (part of FetchPRSet)
mockGitea.EXPECT().GetPullRequestReviews(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.PullReview{}, nil).AnyTimes()
mockGitea.EXPECT().GetTimeline(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.TimelineComment{}, nil).AnyTimes()
prset, err := common.FetchPRSet("bot", mockGitea, "test-org", "prjgit", 500, config)
if err != nil {
t.Fatalf("FetchPRSet failed: %v", err)
}
if len(prset.PRs) != 2 {
t.Errorf("Expected 2 PRs in set, got %d", len(prset.PRs))
}
if !prset.IsConsistent() {
t.Error("PR set should be consistent")
}
})
t.Run("Fetch from Package PR via Timeline", func(t *testing.T) {
ctl := NewController(t)
defer ctl.Finish()
mockGitea := mock_common.NewMockGiteaPRTimelineReviewFetcher(ctl)
mockGitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
// 1. FetchPRSet for pkgPR will call LastPrjGitRefOnTimeline
mockGitea.EXPECT().GetTimeline("target-org", "pkg1", int64(101)).Return([]*models.TimelineComment{
{
Type: common.TimelineCommentType_PullRequestRef,
RefIssue: &models.Issue{
Index: 500,
Body: "PR: target-org/pkg1!101",
Repository: &models.RepositoryMeta{
Owner: "test-org",
Name: "prjgit",
},
User: &models.User{UserName: "bot"},
},
},
}, nil)
// 2. It will then fetch the prjGitPR found in timeline (twice in LastPrjGitRefOnTimeline)
mockGitea.EXPECT().GetPullRequest("test-org", "prjgit", int64(500)).Return(prjGitPR, nil).Times(2)
// 3. Then it will recursively fetch linked PRs from prjGitPR body in readPRData
mockGitea.EXPECT().GetPullRequest("target-org", "pkg1", int64(101)).Return(pkgPR, nil)
// Review fetching for all PRs in the set
mockGitea.EXPECT().GetPullRequestReviews(gomock.Any(), gomock.Any(), gomock.Any()).Return([]*models.PullReview{}, nil).AnyTimes()
mockGitea.EXPECT().GetTimeline("test-org", "prjgit", int64(500)).Return([]*models.TimelineComment{}, nil).AnyTimes()
mockGitea.EXPECT().GetTimeline("target-org", "pkg1", int64(101)).Return([]*models.TimelineComment{}, nil).AnyTimes()
prset, err := common.FetchPRSet("bot", mockGitea, "target-org", "pkg1", 101, config)
if err != nil {
t.Fatalf("FetchPRSet failed: %v", err)
}
if len(prset.PRs) != 2 {
t.Errorf("Expected 2 PRs in set, got %d", len(prset.PRs))
}
prjPRInfo, err := prset.GetPrjGitPR()
if err != nil || prjPRInfo.PR.Index != 500 {
t.Errorf("Expected ProjectGit PR 500 to be found, got %v", prjPRInfo)
}
})
}

View File

@@ -1,94 +0,0 @@
package common_test
import (
"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 TestPRSet_Merge_Special(t *testing.T) {
ctl := NewController(t)
defer ctl.Finish()
mockGitea := mock_common.NewMockGiteaReviewUnrequester(ctl)
mockGit := mock_common.NewMockGit(ctl)
config := &common.AutogitConfig{
Organization: "target-org",
GitProjectName: "test-org/prjgit#main",
Branch: "main",
}
// 1. Regular ProjectGit PR
prjGitPR := &models.PullRequest{
Index: 500,
Base: &models.PRBranchInfo{
Ref: "main",
Name: "main",
Repo: &models.Repository{Name: "prjgit", Owner: &models.User{UserName: "test-org"}, SSHURL: "prj-ssh-url"},
Sha: "base-sha",
},
Head: &models.PRBranchInfo{Sha: "prj-head-sha"},
}
// 2. "new/New Repository" Package PR
newPkgPR := &models.PullRequest{
Index: 101,
Base: &models.PRBranchInfo{
Ref: "main",
Name: "main",
Repo: &models.Repository{Name: "new-pkg", Owner: &models.User{UserName: "target-org"}, SSHURL: "pkg-ssh-url"},
},
Head: &models.PRBranchInfo{Sha: "pkg-head-sha"},
Labels: []*models.Label{
{Name: "new/New Repository"},
},
Body: "See issue #123",
}
prset := &common.PRSet{
Config: config,
PRs: []*common.PRInfo{
{PR: prjGitPR},
{PR: newPkgPR},
},
}
common.IsDryRun = false
// Mock expectations for Merge
// Clone and fetch for PrjGit
mockGit.EXPECT().GitClone("_ObsPrj", "main", "prj-ssh-url").Return("origin", nil)
mockGit.EXPECT().GitExecOrPanic("_ObsPrj", "fetch", "origin", "prj-head-sha")
// mockGit.EXPECT().GitExecWithOutputOrPanic("_ObsPrj", "merge-base", "HEAD", "base-sha", "prj-head-sha").Return("base-sha")
mockGit.EXPECT().GitExec("_ObsPrj", "merge", "--no-ff", "-m", gomock.Any(), "prj-head-sha").Return(nil)
// Unrequest reviews
mockGitea.EXPECT().UnrequestReview("test-org", "prjgit", int64(500), gomock.Any()).Return(nil)
mockGitea.EXPECT().UnrequestReview("target-org", "new-pkg", int64(101), gomock.Any()).Return(nil)
// Clone and fetch for new-pkg
mockGit.EXPECT().GitClone("new-pkg", "main", "pkg-ssh-url").Return("origin", nil)
mockGit.EXPECT().GitExecOrPanic("new-pkg", "fetch", "origin", "pkg-head-sha")
// Pushing changes
mockGit.EXPECT().GitExecOrPanic("_ObsPrj", "push", "origin")
// Special push for new repo: git push -f origin pkg-head-sha:main
mockGit.EXPECT().GitExecOrPanic("new-pkg", "push", "-f", "origin", "pkg-head-sha:main")
// Closing issue
mockGitea.EXPECT().UpdateIssue("test-org", "prjgit", int64(123), gomock.Any()).DoAndReturn(func(org, repo string, idx int64, opt *models.EditIssueOption) (*models.Issue, error) {
if opt.State != "closed" {
t.Errorf("Expected issue state to be closed, got %s", opt.State)
}
return nil, nil
})
err := prset.Merge(mockGitea, mockGit)
if err != nil {
t.Fatalf("Merge failed: %v", err)
}
}

View File

@@ -2,6 +2,7 @@ package common_test
import ( import (
"errors" "errors"
"fmt"
"os" "os"
"os/exec" "os/exec"
"path" "path"
@@ -48,6 +49,8 @@ func reviewsToTimeline(reviews []*models.PullReview) []*models.TimelineComment {
} }
func TestPR(t *testing.T) { func TestPR(t *testing.T) {
return
baseConfig := common.AutogitConfig{ baseConfig := common.AutogitConfig{
Reviewers: []string{"+super1", "*super2", "m1", "-m2"}, Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
Branch: "branch", Branch: "branch",
@@ -78,21 +81,21 @@ func TestPR(t *testing.T) {
{ {
name: "Error fetching PullRequest", name: "Error fetching PullRequest",
data: []prdata{ data: []prdata{
{pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "open"}, pr_err: errors.New("Missing PR")}, {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, prjGitPRIndex: -1,
}, },
{ {
name: "Error fetching PullRequest in PrjGit", name: "Error fetching PullRequest in PrjGit",
data: []prdata{ 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: "open"}, pr_err: errors.New("missing PR")}, {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"}}, Name: "master"}, State: "open"}}, {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", name: "Error fetching prjgit",
data: []prdata{ data: []prdata{
{pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "open"}}, {pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "opened"}},
}, },
resLen: 1, resLen: 1,
prjGitPRIndex: -1, prjGitPRIndex: -1,
@@ -100,20 +103,8 @@ func TestPR(t *testing.T) {
{ {
name: "Review set is consistent", name: "Review set is consistent",
data: []prdata{ 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: foo/barPrj#22", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "open"}, {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"}},
timeline: []*models.TimelineComment{
{
Type: common.TimelineCommentType_PullRequestRef,
RefIssue: &models.Issue{
Index: 22,
Repository: &models.RepositoryMeta{Name: "barPrj", Owner: "foo"},
User: &models.User{UserName: "test"},
Body: "PR: test/repo#42",
},
},
}},
{pr: &models.PullRequest{Body: "PR: test/repo#42", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}, Name: "master"}, State: "open"}},
}, },
resLen: 2, resLen: 2,
prjGitPRIndex: 1, prjGitPRIndex: 1,
@@ -123,21 +114,8 @@ func TestPR(t *testing.T) {
{ {
name: "Review set is consistent: 1pkg", name: "Review set is consistent: 1pkg",
data: []prdata{ 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: foo/barPrj#22", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "open"}, {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"}},
timeline: []*models.TimelineComment{
{
Type: common.TimelineCommentType_PullRequestRef,
RefIssue: &models.Issue{
Index: 22,
Repository: &models.RepositoryMeta{Name: "barPrj", Owner: "foo"},
User: &models.User{UserName: "test"},
Body: "PR: test/repo#42",
},
},
},
},
{pr: &models.PullRequest{Body: "PR: test/repo#42", Index: 22, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}, Name: "master"}, State: "open"}},
}, },
resLen: 2, resLen: 2,
prjGitPRIndex: 1, prjGitPRIndex: 1,
@@ -146,22 +124,9 @@ func TestPR(t *testing.T) {
{ {
name: "Review set is consistent: 2pkg", name: "Review set is consistent: 2pkg",
data: []prdata{ 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: "some desc\nPR: foo/barPrj#22", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "open"}, {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"}},
timeline: []*models.TimelineComment{ {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"}},
{
Type: common.TimelineCommentType_PullRequestRef,
RefIssue: &models.Issue{
Index: 22,
Repository: &models.RepositoryMeta{Name: "barPrj", Owner: "foo"},
User: &models.User{UserName: "test"},
Body: "PR: test/repo#42\nPR: test/repo2#41",
},
},
},
},
{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"}}, Name: "master"}, State: "open"}},
{pr: &models.PullRequest{Body: "some other desc\nPR: foo/barPrj#22", Index: 41, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo2", Owner: &models.User{UserName: "test"}}}, State: "open"}},
}, },
resLen: 3, resLen: 3,
prjGitPRIndex: 1, prjGitPRIndex: 1,
@@ -171,7 +136,7 @@ func TestPR(t *testing.T) {
name: "Review set of prjgit PR is consistent", name: "Review set of prjgit PR is consistent",
data: []prdata{ data: []prdata{
{ {
pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"}, 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{ reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved}, {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: "super2"}, State: common.ReviewStateApproved},
@@ -190,23 +155,10 @@ func TestPR(t *testing.T) {
{ {
name: "Review set is consistent: 2pkg", name: "Review set is consistent: 2pkg",
data: []prdata{ 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: foo/barPrj#222", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "test"}}}, State: "open"}, {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"}},
timeline: []*models.TimelineComment{ {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"}},
Type: common.TimelineCommentType_PullRequestRef,
RefIssue: &models.Issue{
Index: 22,
Repository: &models.RepositoryMeta{Name: "barPrj", Owner: "foo"},
User: &models.User{UserName: "test"},
Body: "PR: test/repo#42\nPR: test/repo2#41",
},
},
},
},
{pr: &models.PullRequest{Body: "PR: test/repo2#41", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}, Name: "master"}, State: "open"}},
{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"}}, Name: "master"}, State: "open"}},
{pr: &models.PullRequest{Body: "PR: foo/barPrj#20", Index: 41, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo2", Owner: &models.User{UserName: "test"}}}, State: "open"}},
}, },
resLen: 3, resLen: 3,
prjGitPRIndex: 2, prjGitPRIndex: 2,
@@ -216,7 +168,7 @@ func TestPR(t *testing.T) {
name: "WIP PR is not approved", name: "WIP PR is not approved",
data: []prdata{ 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"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"}, 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{ reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved}, {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: "super2"}, State: common.ReviewStateApproved},
@@ -233,9 +185,10 @@ func TestPR(t *testing.T) {
}, },
}, },
{ {
name: "Manual review is missing", data: []prdata{ 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"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"}, 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{ reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved}, {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: "super2"}, State: common.ReviewStateApproved},
@@ -243,7 +196,7 @@ func TestPR(t *testing.T) {
}, },
}, },
{ {
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: "open"}, 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{ reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved}, {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: "super1"}, State: common.ReviewStateApproved},
@@ -260,15 +213,16 @@ func TestPR(t *testing.T) {
Reviewers: []string{"+super1", "*super2", "m1", "-m2"}, Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
Branch: "branch", Branch: "branch",
Organization: "foo", Organization: "foo",
GitProjectName: "barPrj#master", GitProjectName: "barPrj",
ManualMergeOnly: true, ManualMergeOnly: true,
}) })
}}, },
},
{ {
name: "Manual review is done, via PrjGit", name: "Manual review is done, via PrjGit",
data: []prdata{ 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"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"}, 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{ reviews: []*models.PullReview{
{Body: "merge ok", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved}, {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: "super2"}, State: common.ReviewStateApproved},
@@ -276,7 +230,7 @@ func TestPR(t *testing.T) {
}, },
}, },
{ {
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: "open"}, 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{ reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved}, {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: "super1"}, State: common.ReviewStateApproved},
@@ -293,7 +247,7 @@ func TestPR(t *testing.T) {
Reviewers: []string{"+super1", "*super2", "m1", "-m2"}, Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
Branch: "branch", Branch: "branch",
Organization: "foo", Organization: "foo",
GitProjectName: "barPrj#master", GitProjectName: "barPrj",
ManualMergeOnly: true, ManualMergeOnly: true,
}) })
}, },
@@ -302,7 +256,7 @@ func TestPR(t *testing.T) {
name: "Manual review is done, via PrjGit", name: "Manual review is done, via PrjGit",
data: []prdata{ 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"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"}, 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{ reviews: []*models.PullReview{
{Body: "merge ok", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved}, {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: "super2"}, State: common.ReviewStateApproved},
@@ -310,7 +264,7 @@ func TestPR(t *testing.T) {
}, },
}, },
{ {
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: "open"}, 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{ reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved}, {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: "super1"}, State: common.ReviewStateApproved},
@@ -327,7 +281,7 @@ func TestPR(t *testing.T) {
Reviewers: []string{"+super1", "*super2", "m1", "-m2"}, Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
Branch: "branch", Branch: "branch",
Organization: "foo", Organization: "foo",
GitProjectName: "barPrj#master", GitProjectName: "barPrj",
ManualMergeOnly: true, ManualMergeOnly: true,
ManualMergeProject: true, ManualMergeProject: true,
}) })
@@ -337,7 +291,7 @@ func TestPR(t *testing.T) {
name: "Manual review is not done, via PrjGit", name: "Manual review is not done, via PrjGit",
data: []prdata{ 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"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"}, 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{ reviews: []*models.PullReview{
{Body: "merge ok", User: &models.User{UserName: "notm2"}, State: common.ReviewStateApproved}, {Body: "merge ok", User: &models.User{UserName: "notm2"}, State: common.ReviewStateApproved},
{Body: "merge not ok", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved}, {Body: "merge not ok", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved},
@@ -346,7 +300,7 @@ func TestPR(t *testing.T) {
}, },
}, },
{ {
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: "open"}, 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{ reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved}, {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: "super1"}, State: common.ReviewStateApproved},
@@ -363,7 +317,7 @@ func TestPR(t *testing.T) {
Reviewers: []string{"+super1", "*super2", "m1", "-m2"}, Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
Branch: "branch", Branch: "branch",
Organization: "foo", Organization: "foo",
GitProjectName: "barPrj#master", GitProjectName: "barPrj",
ManualMergeOnly: true, ManualMergeOnly: true,
ManualMergeProject: true, ManualMergeProject: true,
}) })
@@ -373,7 +327,7 @@ func TestPR(t *testing.T) {
name: "Manual review is done via PackageGit", name: "Manual review is done via PackageGit",
data: []prdata{ 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"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"}, 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{ reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved}, {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: "super2"}, State: common.ReviewStateApproved},
@@ -381,7 +335,7 @@ func TestPR(t *testing.T) {
}, },
}, },
{ {
pr: &models.PullRequest{Body: "PR: foo/repo#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "open"}, 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{ reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved}, {Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
{Body: "Merge ok", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved}, {Body: "Merge ok", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
@@ -398,7 +352,7 @@ func TestPR(t *testing.T) {
Reviewers: []string{"+super1", "*super2", "m1", "-m2"}, Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
Branch: "branch", Branch: "branch",
Organization: "foo", Organization: "foo",
GitProjectName: "barPrj#master", GitProjectName: "barPrj",
ManualMergeOnly: true, ManualMergeOnly: true,
}) })
}, },
@@ -407,7 +361,7 @@ func TestPR(t *testing.T) {
name: "Manual review done via PkgGits", name: "Manual review done via PkgGits",
data: []prdata{ 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"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"}, 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{ reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved}, {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: "super2"}, State: common.ReviewStateApproved},
@@ -415,7 +369,7 @@ func TestPR(t *testing.T) {
}, },
}, },
{ {
pr: &models.PullRequest{Body: "PR: foo/repo#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "open"}, 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{ reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved}, {Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
{Body: "Merge OK!", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved}, {Body: "Merge OK!", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
@@ -423,7 +377,7 @@ func TestPR(t *testing.T) {
}, },
}, },
{ {
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: "open"}, 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{ reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved}, {Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
{Body: "merge ok", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved}, {Body: "merge ok", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
@@ -440,7 +394,7 @@ func TestPR(t *testing.T) {
Reviewers: []string{"+super1", "*super2", "m1", "-m2"}, Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
Branch: "branch", Branch: "branch",
Organization: "foo", Organization: "foo",
GitProjectName: "barPrj#master", GitProjectName: "barPrj",
ManualMergeOnly: true, ManualMergeOnly: true,
}) })
}, },
@@ -449,7 +403,7 @@ func TestPR(t *testing.T) {
name: "Manual review done via PkgGits not allowed", name: "Manual review done via PkgGits not allowed",
data: []prdata{ 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"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"}, 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{ reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved}, {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: "super2"}, State: common.ReviewStateApproved},
@@ -457,7 +411,7 @@ func TestPR(t *testing.T) {
}, },
}, },
{ {
pr: &models.PullRequest{Body: "PR: foo/repo#42", Index: 20, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "repo", Owner: &models.User{UserName: "foo"}}}, User: &models.User{UserName: "submitter"}, State: "open"}, 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{ reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved}, {Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
{Body: "Merge OK!", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved}, {Body: "Merge OK!", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
@@ -465,7 +419,7 @@ func TestPR(t *testing.T) {
}, },
}, },
{ {
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: "open"}, 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{ reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved}, {Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved},
{Body: "merge ok", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved}, {Body: "merge ok", User: &models.User{UserName: "super1"}, State: common.ReviewStateApproved},
@@ -482,7 +436,7 @@ func TestPR(t *testing.T) {
Reviewers: []string{"+super1", "*super2", "m1", "-m2"}, Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
Branch: "branch", Branch: "branch",
Organization: "foo", Organization: "foo",
GitProjectName: "barPrj#master", GitProjectName: "barPrj",
ManualMergeOnly: true, ManualMergeOnly: true,
ManualMergeProject: true, ManualMergeProject: true,
}) })
@@ -492,7 +446,7 @@ func TestPR(t *testing.T) {
name: "Manual review is is missing on one PR", name: "Manual review is is missing on one PR",
data: []prdata{ 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"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"}, 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{ reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved}, {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: "super2"}, State: common.ReviewStateApproved},
@@ -500,7 +454,7 @@ func TestPR(t *testing.T) {
}, },
}, },
{ {
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: "open"}, 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{ reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved}, {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: "super1"}, State: common.ReviewStateApproved},
@@ -508,7 +462,7 @@ func TestPR(t *testing.T) {
}, },
}, },
{ {
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: "open"}, 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{ reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m1"}, State: common.ReviewStateApproved}, {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: "super1"}, State: common.ReviewStateApproved},
@@ -525,7 +479,7 @@ func TestPR(t *testing.T) {
Reviewers: []string{"+super1", "*super2", "m1", "-m2"}, Reviewers: []string{"+super1", "*super2", "m1", "-m2"},
Branch: "branch", Branch: "branch",
Organization: "foo", Organization: "foo",
GitProjectName: "barPrj#master", GitProjectName: "barPrj",
ManualMergeOnly: true, ManualMergeOnly: true,
}) })
}, },
@@ -534,7 +488,7 @@ func TestPR(t *testing.T) {
name: "PR is approved with negative optional review", name: "PR is approved with negative optional review",
data: []prdata{ data: []prdata{
{ {
pr: &models.PullRequest{Body: "", Index: 42, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "barPrj", Owner: &models.User{UserName: "foo"}}, Name: "master"}, User: &models.User{UserName: "submitter"}, State: "open"}, 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{ reviews: []*models.PullReview{
{Body: "LGTM", User: &models.User{UserName: "m2"}, State: common.ReviewStateApproved}, {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: "super2"}, State: common.ReviewStateApproved},
@@ -552,7 +506,7 @@ func TestPR(t *testing.T) {
Reviewers: []string{"+super1", "*super2", "m1", "-m2", "~*bot"}, Reviewers: []string{"+super1", "*super2", "m1", "-m2", "~*bot"},
Branch: "branch", Branch: "branch",
Organization: "foo", Organization: "foo",
GitProjectName: "barPrj#master", GitProjectName: "barPrj",
} }
return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &config) return common.FetchPRSet("test", mock, "foo", "barPrj", 42, &config)
}, },
@@ -561,37 +515,32 @@ func TestPR(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
pr_mock := mock_common.NewMockGiteaPRTimelineReviewFetcher(ctl) pr_mock := mock_common.NewMockGiteaPRTimelineReviewFetcher(ctl)
pr_mock.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
review_mock := mock_common.NewMockGiteaPRChecker(ctl) review_mock := mock_common.NewMockGiteaPRChecker(ctl)
// reviewer_mock := mock_common.NewMockGiteaReviewRequester(ctl) // reviewer_mock := mock_common.NewMockGiteaReviewRequester(ctl)
prjGitOrg, prjGitRepo, _ := baseConfig.GetPrjGit()
if test.reviewSetFetcher == nil { // if we are fetching the prjgit directly, the these mocks are not called if test.reviewSetFetcher == nil { // if we are fetching the prjgit directly, the these mocks are not called
if test.prjGitPRIndex >= 0 { if test.prjGitPRIndex >= 0 {
pr_mock.EXPECT().GetPullRequest(prjGitOrg, prjGitRepo, int64(test.data[test.prjGitPRIndex].pr.Index)). pr_mock.EXPECT().GetPullRequest(baseConfig.Organization, baseConfig.GitProjectName, test.prjGitPRIndex).
Return(test.data[test.prjGitPRIndex].pr, test.data[test.prjGitPRIndex].pr_err).AnyTimes() Return(test.data[test.prjGitPRIndex].pr, test.data[test.prjGitPRIndex].pr_err)
} else if test.prjGitPRIndex < 0 { } else if test.prjGitPRIndex < 0 {
// no prjgit PR // no prjgit PR
pr_mock.EXPECT().GetPullRequest(prjGitOrg, prjGitRepo, gomock.Any()). pr_mock.EXPECT().GetPullRequest(baseConfig.Organization, baseConfig.GitProjectName, gomock.Any()).
Return(nil, nil).AnyTimes() Return(nil, nil)
} }
} }
var test_err error
for _, data := range test.data { 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() 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 { if data.pr_err != nil {
// test_err is not used and was causing a build error. test_err = data.pr_err
// data.pr_err is directly used in the previous EXPECT call.
} }
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 { if data.timeline == nil {
data.timeline = reviewsToTimeline(data.reviews) data.timeline = reviewsToTimeline(data.reviews)
} }
pr_mock.EXPECT().GetTimeline(data.pr.Base.Repo.Owner.UserName, data.pr.Base.Repo.Name, data.pr.Index).Return(data.timeline, nil).AnyTimes()
pr_mock.EXPECT().GetPullRequestReviews(data.pr.Base.Repo.Owner.UserName, data.pr.Base.Repo.Name, data.pr.Index).Return(data.reviews, data.review_error).AnyTimes()
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()
review_mock.EXPECT().GetTimeline(data.pr.Base.Repo.Owner.UserName, data.pr.Base.Repo.Name, data.pr.Index).Return(data.timeline, nil).AnyTimes() review_mock.EXPECT().GetTimeline(data.pr.Base.Repo.Owner.UserName, data.pr.Base.Repo.Name, data.pr.Index).Return(data.timeline, nil).AnyTimes()
} }
@@ -604,12 +553,27 @@ func TestPR(t *testing.T) {
res, err = common.FetchPRSet("test", pr_mock, "test", "repo", 42, &baseConfig) res, err = common.FetchPRSet("test", pr_mock, "test", "repo", 42, &baseConfig)
} }
if res == nil { 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 return
} }
if len(res.PRs) != test.resLen { if test.resLen != len(res.PRs) {
t.Errorf("Test Case '%s': expected result len %d but got %d", test.name, test.resLen, len(res.PRs)) t.Error("expected result len", test.resLen, "but got", len(res.PRs))
} }
PrjGitPR, err := res.GetPrjGitPR() PrjGitPR, err := res.GetPrjGitPR()
@@ -620,9 +584,6 @@ func TestPR(t *testing.T) {
} }
pr_found := false pr_found := false
if test.prjGitPRIndex >= 0 { if test.prjGitPRIndex >= 0 {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
for i := range test.data { for i := range test.data {
if PrjGitPR.PR == test.data[i].pr && i == test.prjGitPRIndex { if PrjGitPR.PR == test.data[i].pr && i == test.prjGitPRIndex {
t.Log("found at index", i) t.Log("found at index", i)
@@ -1224,6 +1185,7 @@ func TestFindMissingAndExtraReviewers(t *testing.T) {
} }
func TestPRMerge(t *testing.T) { func TestPRMerge(t *testing.T) {
t.Skip("FAIL: No PrjGit PR found, missing calls")
repoDir := t.TempDir() repoDir := t.TempDir()
cwd, _ := os.Getwd() cwd, _ := os.Getwd()
@@ -1233,11 +1195,7 @@ func TestPRMerge(t *testing.T) {
t.Fatal(string(out)) t.Fatal(string(out))
} }
t.Skip("No tests of PRMerge yet")
return
common.ExtraGitParams = []string{ common.ExtraGitParams = []string{
"TZ=UTC",
"GIT_CONFIG_COUNT=1", "GIT_CONFIG_COUNT=1",
"GIT_CONFIG_KEY_0=protocol.file.allow", "GIT_CONFIG_KEY_0=protocol.file.allow",
"GIT_CONFIG_VALUE_0=always", "GIT_CONFIG_VALUE_0=always",
@@ -1252,7 +1210,7 @@ func TestPRMerge(t *testing.T) {
config := &common.AutogitConfig{ config := &common.AutogitConfig{
Organization: "org", Organization: "org",
GitProjectName: "org/prj#main", GitProjectName: "org/prj#master",
} }
tests := []struct { tests := []struct {
@@ -1264,20 +1222,18 @@ func TestPRMerge(t *testing.T) {
name: "Merge base not merged in main", name: "Merge base not merged in main",
pr: &models.PullRequest{ pr: &models.PullRequest{
Index: 1,
Base: &models.PRBranchInfo{ Base: &models.PRBranchInfo{
Name: "main", Sha: "e8b0de43d757c96a9d2c7101f4bff404e322f53a1fa4041fb85d646110c38ad4", // "base_add_b1"
Sha: "96515c092626c716a4613ba4f68a8d1cc4894317658342c450e656390f524ec3", // "base_add_b1"
Repo: &models.Repository{ Repo: &models.Repository{
Name: "prj", Name: "prj",
Owner: &models.User{ Owner: &models.User{
UserName: "org", UserName: "org",
}, },
SSHURL: "file://" + path.Join(repoDir, "prjgit"), SSHURL: "ssh://git@src.opensuse.org/org/prj.git",
}, },
}, },
Head: &models.PRBranchInfo{ Head: &models.PRBranchInfo{
Sha: "4119fc725dc11cdf11f982d5bb0a8ba2a138f1180c4323862a18b8e08def5603", // "base_add_b2" Sha: "88584433de1c917c1d773f62b82381848d882491940b5e9b427a540aa9057d9a", // "base_add_b2"
}, },
}, },
mergeError: "Aborting merge", mergeError: "Aborting merge",
@@ -1286,20 +1242,18 @@ func TestPRMerge(t *testing.T) {
name: "Merge conflict in modules, auto-resolved", name: "Merge conflict in modules, auto-resolved",
pr: &models.PullRequest{ pr: &models.PullRequest{
Index: 1,
Base: &models.PRBranchInfo{ Base: &models.PRBranchInfo{
Name: "main", Sha: "4fbd1026b2d7462ebe9229a49100c11f1ad6555520a21ba515122d8bc41328a8",
Sha: "85f59f7aa732b742e58b48356cd46cb1ab1b5c4349eb5c0eda324e2dbea8f9e7",
Repo: &models.Repository{ Repo: &models.Repository{
Name: "prj", Name: "prj",
Owner: &models.User{ Owner: &models.User{
UserName: "org", UserName: "org",
}, },
SSHURL: "file://" + path.Join(cmd.Dir, "prjgit"), SSHURL: "ssh://git@src.opensuse.org/org/prj.git",
}, },
}, },
Head: &models.PRBranchInfo{ Head: &models.PRBranchInfo{
Sha: "4119fc725dc11cdf11f982d5bb0a8ba2a138f1180c4323862a18b8e08def5603", // "base_add_b2" Sha: "88584433de1c917c1d773f62b82381848d882491940b5e9b427a540aa9057d9a", // "base_add_b2"
}, },
}, },
}, },
@@ -1307,18 +1261,15 @@ func TestPRMerge(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
mock := mock_common.NewMockGiteaPRTimelineReviewFetcher(ctl) mock := mock_common.NewMockGiteaPRTimelineReviewFetcher(ctl)
mock.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
reviewUnrequestMock := mock_common.NewMockGiteaReviewUnrequester(ctl) reviewUnrequestMock := mock_common.NewMockGiteaReviewUnrequester(ctl)
reviewUnrequestMock.EXPECT().UnrequestReview(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes() reviewUnrequestMock.EXPECT().UnrequestReview(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
testDir := t.TempDir() testDir := t.TempDir()
t.Log("dir:", testDir) t.Log("dir:", testDir)
mock.EXPECT().GetPullRequest("org", "prj", int64(1)).Return(test.pr, nil) mock.EXPECT().GetPullRequest("org", "prj", int64(1)).Return(test.pr, nil)
mock.EXPECT().GetTimeline("org", "prj", int64(1)).Return(nil, nil).AnyTimes()
mock.EXPECT().GetPullRequestReviews("org", "prj", int64(1)).Return(nil, nil).AnyTimes()
set, err := common.FetchPRSet("test", mock, "org", "prj", 1, config) set, err := common.FetchPRSet("test", mock, "org", "prj", 1, config)
if err != nil { if err != nil {
@@ -1339,11 +1290,11 @@ func TestPRMerge(t *testing.T) {
} }
func TestPRChanges(t *testing.T) { func TestPRChanges(t *testing.T) {
t.Skip("FAIL: unexpected calls, missing calls")
tests := []struct { tests := []struct {
name string name string
PRs []*models.PullRequest PRs []*models.PullRequest
PrjPRs *models.PullRequest PrjPRs *models.PullRequest
Timeline []*models.TimelineComment
}{ }{
{ {
name: "Pkg PR is closed", name: "Pkg PR is closed",
@@ -1355,22 +1306,10 @@ func TestPRChanges(t *testing.T) {
}, },
}, },
PrjPRs: &models.PullRequest{ PrjPRs: &models.PullRequest{
Index: 42,
Title: "some PR", Title: "some PR",
Base: &models.PRBranchInfo{Name: "branch", Repo: &models.Repository{Name: "prjgit", Owner: &models.User{UserName: "org"}}}, Base: &models.PRBranchInfo{Repo: &models.Repository{Name: "prjgit", Owner: &models.User{UserName: "org"}}},
Body: "PR: org/repo#42", Body: "PR: org/repo#42",
State: "open", State: "opened",
},
Timeline: []*models.TimelineComment{
{
Type: common.TimelineCommentType_PullRequestRef,
RefIssue: &models.Issue{
Index: 42,
Repository: &models.RepositoryMeta{Name: "prjgit", Owner: "org"},
User: &models.User{UserName: "user"},
Body: "PR: org/repo#42",
},
},
}, },
}, },
} }
@@ -1381,16 +1320,11 @@ func TestPRChanges(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
mock_fetcher := mock_common.NewMockGiteaPRTimelineReviewFetcher(ctl) mock_fetcher := mock_common.NewMockGiteaPRTimelineReviewFetcher(ctl)
mock_fetcher.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() mock_fetcher.EXPECT().GetPullRequest("org", "prjgit", int64(42)).Return(test.PrjPRs, nil)
mock_fetcher.EXPECT().GetPullRequest("org", "prjgit", int64(42)).Return(test.PrjPRs, nil).AnyTimes()
mock_fetcher.EXPECT().GetTimeline("org", "prjgit", int64(42)).Return(nil, nil).AnyTimes()
mock_fetcher.EXPECT().GetPullRequestReviews("org", "prjgit", int64(42)).Return(nil, nil).AnyTimes()
for _, pr := range test.PRs { for _, pr := range test.PRs {
mock_fetcher.EXPECT().GetPullRequest(pr.Base.Repo.Owner.UserName, pr.Base.Repo.Name, pr.Index).Return(pr, nil) mock_fetcher.EXPECT().GetPullRequest(pr.Base.Repo.Owner.UserName, pr.Base.Repo.Name, pr.Index).Return(pr, nil)
mock_fetcher.EXPECT().GetTimeline(pr.Base.Repo.Owner.UserName, pr.Base.Repo.Name, pr.Index).Return(test.Timeline, nil).AnyTimes()
mock_fetcher.EXPECT().GetPullRequestReviews(pr.Base.Repo.Owner.UserName, pr.Base.Repo.Name, pr.Index).Return(nil, nil).AnyTimes()
} }
PRs, err := common.FetchPRSet("user", mock_fetcher, "org", "repo", 42, &config) PRs, err := common.FetchPRSet("user", mock_fetcher, "org", "repo", 42, &config)
@@ -1405,3 +1339,344 @@ func TestPRChanges(t *testing.T) {
}) })
} }
} }
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", "--reset", "-u", pkgPR.Head.Sha)
m.EXPECT().GitExecOrPanic("pkg", "commit", "-m", gomock.Any())
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", "--reset", "-u", pkgPR.Head.Sha)
git.EXPECT().GitExecOrPanic("pkg", "commit", "-m", gomock.Any())
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)
}
}

View File

@@ -21,6 +21,8 @@ package common
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"log"
"os"
) )
type RequestType interface { type RequestType interface {
@@ -85,14 +87,24 @@ func ParseRequestJSON(reqType string, data []byte) (req *Request, err error) {
} }
type RequestHandler struct { type RequestHandler struct {
StdLogger, ErrLogger *log.Logger
Request *Request Request *Request
} }
func (r *RequestHandler) WriteError() { func (r *RequestHandler) WriteError() {
LogError("internal error sent") r.ErrLogger.Println("internal error sent")
} }
func CreateRequestHandler() (*RequestHandler, error) { func CreateRequestHandler() (*RequestHandler, error) {
var h *RequestHandler = new(RequestHandler) var h *RequestHandler = new(RequestHandler)
h.StdLogger, h.ErrLogger = CreateStdoutLogger(os.Stdout, os.Stderr)
/* var err error
h.Git, err = CreateGitHandler(git_author, name)
if err != nil {
return nil, err
}
*/
return h, nil return h, nil
} }

View File

@@ -19,6 +19,7 @@ package common
*/ */
import ( import (
"os"
"strings" "strings"
"testing" "testing"
) )
@@ -26,6 +27,8 @@ import (
func TestPrParsing(t *testing.T) { func TestPrParsing(t *testing.T) {
t.Run("Test parsing", func(t *testing.T) { t.Run("Test parsing", func(t *testing.T) {
var h RequestHandler var h RequestHandler
h.StdLogger, h.ErrLogger = CreateStdoutLogger(os.Stdout, os.Stdout)
pr, err := h.parsePullRequest(strings.NewReader(samplePR_JSON)) pr, err := h.parsePullRequest(strings.NewReader(samplePR_JSON))
if err != nil { if err != nil {
t.Fatalf("error parsing PR: %v\n", err) t.Fatalf("error parsing PR: %v\n", err)

View File

@@ -62,7 +62,7 @@ func (h *RequestHandler) ParsePushRequest(data io.Reader) (*PushWebhookEvent, er
return nil, fmt.Errorf("Unexpected URL for SSH repository: '%s'", action.Repository.Name) return nil, fmt.Errorf("Unexpected URL for SSH repository: '%s'", action.Repository.Name)
} }
LogInfo("Request push for repo:", action.Repository.Full_Name) h.StdLogger.Printf("Request push for repo: %s\n", action.Repository.Full_Name)
h.Request = &Request{ h.Request = &Request{
Type: RequestType_Push, Type: RequestType_Push,
Data: action, Data: action,

View File

@@ -19,6 +19,7 @@ package common_test
*/ */
import ( import (
"os"
"strings" "strings"
"testing" "testing"
@@ -26,10 +27,10 @@ import (
) )
func TestPushRequestParsing(t *testing.T) { func TestPushRequestParsing(t *testing.T) {
common.SetTestLogger(t)
t.Run("parsing repo creation message", func(t *testing.T) { t.Run("parsing repo creation message", func(t *testing.T) {
var h common.RequestHandler var h common.RequestHandler
h.StdLogger, h.ErrLogger = common.CreateStdoutLogger(os.Stdout, os.Stderr)
json, err := h.ParsePushRequest(strings.NewReader(examplePushJSON)) json, err := h.ParsePushRequest(strings.NewReader(examplePushJSON))
if err != nil { if err != nil {
t.Fatalf("failed to parser push request: %v", err) t.Fatalf("failed to parser push request: %v", err)

View File

@@ -117,7 +117,7 @@ func (h *RequestHandler) ParseRepositoryRequest(dataReader io.Reader) (data *Rep
data.PrjGit = data.Repository.Ssh_Url[:repoIdx+1] + DefaultGitPrj + ".git" data.PrjGit = data.Repository.Ssh_Url[:repoIdx+1] + DefaultGitPrj + ".git"
LogInfo(data.Action, "request for repo:", data.Repository.Full_Name) h.StdLogger.Printf("Request '%s' for repo: %s\n", data.Action, data.Repository.Full_Name)
if len(data.Action) < 1 { if len(data.Action) < 1 {
return nil, fmt.Errorf("Request has no data.... skipping") return nil, fmt.Errorf("Request has no data.... skipping")
} }

View File

@@ -19,6 +19,7 @@ package common_test
*/ */
import ( import (
"os"
"strings" "strings"
"testing" "testing"
@@ -35,10 +36,10 @@ func (s *testLogger) WriteString(str2 string) (int, error) {
} }
func TestRepositoryRequestParsing(t *testing.T) { func TestRepositoryRequestParsing(t *testing.T) {
common.SetTestLogger(t)
t.Run("parsing repo creation message", func(t *testing.T) { t.Run("parsing repo creation message", func(t *testing.T) {
var h common.RequestHandler var h common.RequestHandler
h.StdLogger, h.ErrLogger = common.CreateStdoutLogger(os.Stdout, os.Stdout)
json, err := h.ParseRepositoryRequest(strings.NewReader(repoCreateJSON)) json, err := h.ParseRepositoryRequest(strings.NewReader(repoCreateJSON))
if err != nil { if err != nil {
t.Fatalf("Can't parse struct: %s", err) t.Fatalf("Can't parse struct: %s", err)

View File

@@ -52,7 +52,7 @@ func (h *RequestHandler) ParseStatusRequest(data io.Reader) (*StatusWebhookEvent
return nil, fmt.Errorf("Got error while parsing: %w", err) return nil, fmt.Errorf("Got error while parsing: %w", err)
} }
LogInfo("Request status for repo:", action.Repository.Full_Name+"#"+action.Sha) h.StdLogger.Printf("Request status for repo: %s#%s\n", action.Repository.Full_Name, action.Sha)
h.Request = &Request{ h.Request = &Request{
Type: RequestType_Status, Type: RequestType_Status,
Data: action, Data: action,

View File

@@ -1,6 +1,7 @@
package common_test package common_test
import ( import (
"os"
"strings" "strings"
"testing" "testing"
@@ -8,10 +9,10 @@ import (
) )
func TestStatusRequestParsing(t *testing.T) { func TestStatusRequestParsing(t *testing.T) {
common.SetTestLogger(t)
t.Run("parsing repo creation message", func(t *testing.T) { t.Run("parsing repo creation message", func(t *testing.T) {
var h common.RequestHandler var h common.RequestHandler
h.StdLogger, h.ErrLogger = common.CreateStdoutLogger(os.Stdout, os.Stdout)
json, err := h.ParseStatusRequest(strings.NewReader(requestStatusJSON)) json, err := h.ParseStatusRequest(strings.NewReader(requestStatusJSON))
if err != nil { if err != nil {
t.Fatalf("Can't parse struct: %s", err) t.Fatalf("Can't parse struct: %s", err)

View File

@@ -8,7 +8,6 @@ import (
) )
func TestMaintainerGroupReplacer(t *testing.T) { func TestMaintainerGroupReplacer(t *testing.T) {
common.SetTestLogger(t)
GroupName := "my_group" GroupName := "my_group"
tests := []struct { tests := []struct {

View File

@@ -8,7 +8,6 @@ import (
) )
func TestReviewers(t *testing.T) { func TestReviewers(t *testing.T) {
common.SetTestLogger(t)
tests := []struct { tests := []struct {
name string name string
input []string input []string

View File

@@ -4,6 +4,7 @@ import (
"errors" "errors"
"testing" "testing"
"go.uber.org/mock/gomock"
"src.opensuse.org/autogits/common" "src.opensuse.org/autogits/common"
"src.opensuse.org/autogits/common/gitea-generated/models" "src.opensuse.org/autogits/common/gitea-generated/models"
mock_common "src.opensuse.org/autogits/common/mock" mock_common "src.opensuse.org/autogits/common/mock"
@@ -141,7 +142,7 @@ func TestReviews(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
rf := mock_common.NewMockGiteaReviewTimelineFetcher(ctl) rf := mock_common.NewMockGiteaReviewTimelineFetcher(ctl)
if test.timeline == nil { if test.timeline == nil {

View File

@@ -10,7 +10,6 @@ import (
) )
func TestSubmodulesParsing(t *testing.T) { func TestSubmodulesParsing(t *testing.T) {
common.SetTestLogger(t)
tests := []struct { tests := []struct {
name string name string
file string file string
@@ -124,7 +123,6 @@ func TestSubmodulesParsing(t *testing.T) {
} }
func TestSubmodulesWriting(t *testing.T) { func TestSubmodulesWriting(t *testing.T) {
common.SetTestLogger(t)
tests := []struct { tests := []struct {
name string name string
subs []common.Submodule subs []common.Submodule

View File

@@ -2,7 +2,6 @@
set -x set -x
export TZ=UTC
export GIT_CONFIG_COUNT=2 export GIT_CONFIG_COUNT=2
export GIT_CONFIG_KEY_0=protocol.file.allow export GIT_CONFIG_KEY_0=protocol.file.allow

View File

@@ -26,7 +26,6 @@ import (
"net/url" "net/url"
"regexp" "regexp"
"slices" "slices"
"strconv"
"strings" "strings"
"unicode" "unicode"
@@ -287,30 +286,3 @@ func TrimRemovedBranchSuffix(branchName string) string {
return branchName return branchName
} }
func GetEnvOverrideString(envValue, def string) string {
if len(envValue) != 0 {
return envValue
}
return def
}
func GetEnvOverrideBool(envValue string, def bool) bool {
if len(envValue) == 0 {
return def
}
if value, err := strconv.Atoi(envValue); err == nil {
if value > 0 {
return true
}
return false
}
envValue = strings.TrimSpace(strings.ToLower(envValue))
switch envValue {
case "t", "true", "yes", "y", "on":
return true
}
return false
}

View File

@@ -3,7 +3,6 @@ package common_test
import ( import (
"reflect" "reflect"
"testing" "testing"
"go.uber.org/mock/gomock"
"src.opensuse.org/autogits/common" "src.opensuse.org/autogits/common"
) )
@@ -223,60 +222,6 @@ func TestRemovedBranchName(t *testing.T) {
} }
} }
func TestSplitStringNoEmpty(t *testing.T) {
tests := []struct {
name string
input string
sep string
expected []string
}{
{"Empty string", "", ",", []string{}},
{"Only separators", ",,,", ",", []string{}},
{"Spaces and separators", " , , ", ",", []string{}},
{"Normal split", "a,b,c", ",", []string{"a", "b", "c"}},
{"Leading/trailing spaces", " a , b ", ",", []string{"a", "b"}},
{"Multiple separators", "a,,b", ",", []string{"a", "b"}},
{"Newlines", "line1\n\nline2", "\n", []string{"line1", "line2"}},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
res := common.SplitStringNoEmpty(test.input, test.sep)
if !reflect.DeepEqual(res, test.expected) {
t.Errorf("SplitStringNoEmpty(%q, %q) = %v; want %v", test.input, test.sep, res, test.expected)
}
})
}
}
func TestTranslateHttpsToSshUrl(t *testing.T) {
tests := []struct {
name string
input string
expected string
err bool
}{
{"Opensuse HTTPS", "https://src.opensuse.org/org/repo", "ssh://gitea@src.opensuse.org/org/repo", false},
{"Suse HTTPS", "https://src.suse.de/org/repo", "ssh://gitea@src.suse.de/org/repo", false},
{"Already SSH", "ssh://gitea@src.opensuse.org/org/repo", "ssh://gitea@src.opensuse.org/org/repo", false},
{"Native SSH", "gitea@src.opensuse.org:org/repo", "gitea@src.opensuse.org:org/repo", false},
{"Unknown URL", "https://github.com/org/repo", "", true},
{"Empty URL", "", "", true},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
res, err := common.TranslateHttpsToSshUrl(test.input)
if (err != nil) != test.err {
t.Errorf("TranslateHttpsToSshUrl(%q) error = %v; want error %v", test.input, err, test.err)
}
if res != test.expected {
t.Errorf("TranslateHttpsToSshUrl(%q) = %q; want %q", test.input, res, test.expected)
}
})
}
}
func TestNewPackageIssueParsing(t *testing.T) { func TestNewPackageIssueParsing(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
@@ -296,7 +241,7 @@ func TestNewPackageIssueParsing(t *testing.T) {
}, },
}, },
{ {
name: "Default branch and junk lines and approval for maintainership", name: "Default branch and junk lines and approval for maintainership",
input: "\n\nsome comments\n\norg1/repo2\n\nmaintainership: yes", input: "\n\nsome comments\n\norg1/repo2\n\nmaintainership: yes",
issues: &common.NewRepos{ issues: &common.NewRepos{
Repos: []struct{ Organization, Repository, Branch, PackageName string }{ Repos: []struct{ Organization, Repository, Branch, PackageName string }{
@@ -306,7 +251,7 @@ func TestNewPackageIssueParsing(t *testing.T) {
}, },
}, },
{ {
name: "Default branch and junk lines and no maintainership", name: "Default branch and junk lines and no maintainership",
input: "\n\nsome comments\n\norg1/repo2\n\nmaintainership: NEVER", input: "\n\nsome comments\n\norg1/repo2\n\nmaintainership: NEVER",
issues: &common.NewRepos{ issues: &common.NewRepos{
Repos: []struct{ Organization, Repository, Branch, PackageName string }{ Repos: []struct{ Organization, Repository, Branch, PackageName string }{
@@ -315,7 +260,7 @@ func TestNewPackageIssueParsing(t *testing.T) {
}, },
}, },
{ {
name: "3 repos with comments and maintainership", name: "3 repos with comments and maintainership",
input: "\n\nsome comments for org1/repo2 are here and more\n\norg1/repo2#master\n org2/repo3#master\n some/repo3#m\nMaintainer ok", input: "\n\nsome comments for org1/repo2 are here and more\n\norg1/repo2#master\n org2/repo3#master\n some/repo3#m\nMaintainer ok",
issues: &common.NewRepos{ issues: &common.NewRepos{
Repos: []struct{ Organization, Repository, Branch, PackageName string }{ Repos: []struct{ Organization, Repository, Branch, PackageName string }{
@@ -327,11 +272,11 @@ func TestNewPackageIssueParsing(t *testing.T) {
}, },
}, },
{ {
name: "Invalid repos with spaces", name: "Invalid repos with spaces",
input: "or g/repo#branch\norg/r epo#branch\norg/repo#br anch\norg/repo#branch As foo ++", input: "or g/repo#branch\norg/r epo#branch\norg/repo#br anch\norg/repo#branch As foo ++",
}, },
{ {
name: "Valid repos with spaces", name: "Valid repos with spaces",
input: " org / repo # branch", input: " org / repo # branch",
issues: &common.NewRepos{ issues: &common.NewRepos{
Repos: []struct{ Organization, Repository, Branch, PackageName string }{ Repos: []struct{ Organization, Repository, Branch, PackageName string }{
@@ -340,7 +285,7 @@ func TestNewPackageIssueParsing(t *testing.T) {
}, },
}, },
{ {
name: "Package name is not repo name", name: "Package name is not repo name",
input: " org / repo # branch as repo++ \nmaintainer true", input: " org / repo # branch as repo++ \nmaintainer true",
issues: &common.NewRepos{ issues: &common.NewRepos{
Repos: []struct{ Organization, Repository, Branch, PackageName string }{ Repos: []struct{ Organization, Repository, Branch, PackageName string }{
@@ -360,63 +305,3 @@ func TestNewPackageIssueParsing(t *testing.T) {
}) })
} }
} }
func TestGetEnvOverride(t *testing.T) {
t.Run("GetEnvOverrideString", func(t *testing.T) {
tests := []struct {
envValue string
def string
expected string
}{
{"", "default", "default"},
{"override", "default", "override"},
}
for _, test := range tests {
if res := common.GetEnvOverrideString(test.envValue, test.def); res != test.expected {
t.Errorf("GetEnvOverrideString(%q, %q) = %q; want %q", test.envValue, test.def, res, test.expected)
}
}
})
t.Run("GetEnvOverrideBool", func(t *testing.T) {
tests := []struct {
name string
envValue string
def bool
expected bool
}{
{"Empty env value, default false", "", false, false},
{"Empty env value, default true", "", true, true},
{"Env '1', default false", "1", false, true},
{"Env '2', default false", "2", false, true},
{"Env '0', default false", "0", false, false},
{"Env 'invalid', default true", "abc", true, false},
{"Env 'true', default false", "true", false, true},
{"Env 'YES', default false", "YES", false, true},
{"Env '0', default true", "0", true, false},
{"Env 'false', default true", "false", true, false},
{"Env 'FALSE', default true", "FALSE", true, false},
{"Env ' true ', default false", " true ", false, true},
{"Env 'no', default true", "no", true, false},
{"Env 'NO', default true", "NO", true, false},
{"Env 'off', default true", "off", true, false},
{"Env 'on', default false", "on", false, true},
{"Env 'invalid', default false", "tbc", false, false},
{"Env 'garbage', default false", "!@#$", false, false},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if res := common.GetEnvOverrideBool(test.envValue, test.def); res != test.expected {
t.Errorf("GetEnvOverrideBool(%q, %v) = %v; want %v", test.envValue, test.def, res, test.expected)
}
})
}
})
}
func NewController(t *testing.T) *gomock.Controller {
common.SetTestLogger(t)
return gomock.NewController(t)
}

View File

@@ -16,10 +16,10 @@
# 3. 'pytest -v tests/*' - run tests # 3. 'pytest -v tests/*' - run tests
# 4. 'make down' - once the containers are not needed # 4. 'make down' - once the containers are not needed
# B2: (make sure the go binaries in the parent folder are built) # B2: (make sure the go binaries in the parent folder are built)
# 1. 'make build_local' - prepared images (recommended, otherwise there might be surprises if image fails to build during `make up`) # 4. 'make build_local' - prepared images (recommended, otherwise there might be surprises if image fails to build during `make up`)
# 2. 'make up' - spawns podman-compose # 5. 'make up' - spawns podman-compose
# 3. 'pytest -v tests/*' - run tests # 6. 'pytest -v tests/*' - run tests
# 4. 'make down' - once the containers are not needed # 7. 'make down' - once the containers are not needed
AUTO_DETECT_MODE := $(shell if test -e ../workflow-pr/workflow-pr; then echo .local; else echo .package; fi) AUTO_DETECT_MODE := $(shell if test -e ../workflow-pr/workflow-pr; then echo .local; else echo .package; fi)
@@ -44,7 +44,7 @@ build_container:
# Run tests in topology 1 # Run tests in topology 1
test_container: test_container:
podman run --rm --privileged -t -e GIWTF_IMAGE_SUFFIX=$(GIWTF_IMAGE_SUFFIX) autogits_integration /usr/bin/bash -c "make build && make up && sleep 25 && pytest -v tests/*" podman run --rm --privileged -t --network integration_gitea-network -e GIWTF_IMAGE_SUFFIX=$(GIWTF_IMAGE_SUFFIX) autogits_integration /usr/bin/bash -c "make build && make up && sleep 25 && pytest -v tests/*"
build_local: AUTO_DETECT_MODE=.local build_local: AUTO_DETECT_MODE=.local

View File

@@ -1,57 +0,0 @@
+-------------------------------------------------------------------------------------------------+
| Makefile Targets |
+-------------------------------------------------------------------------------------------------+
| |
| [Default Test Workflow] |
| test (Auto-detects mode: .local or .package) |
| > build_container |
| > test_container |
| |
| [Specific Test Workflows - Topology 1: Privileged Container] |
| test_package (Mode A1: Bots from official packages) |
| > build_container |
| > test_container |
| |
| test_local (Mode B1: Bots from local binaries) |
| > build_container |
| > test_container |
| |
| build_container |
| - Action: Builds the `autogits_integration` privileged container image. |
| - Purpose: Prepares an environment for running tests within a single container. |
| |
| test_container |
| - Action: Runs `autogits_integration` container, executes `make build`, `make up`, and |
| `pytest -v tests/*` inside it. |
| - Purpose: Executes the full test suite in Topology 1 (privileged container). |
| |
| [Build & Orchestration Workflows - Topology 2: podman-compose] |
| |
| build_package (Mode A: Builds service images from official packages) |
| > build |
| |
| build_local (Mode B: Builds service images from local binaries) |
| > build |
| |
| build |
| - Action: Pulls `rabbitmq` image and iterates through `podman-compose.yml` services |
| to build each one. |
| - Purpose: Prepares all necessary service images for Topology 2 deployment. |
| |
| up |
| - Action: Starts all services defined in `podman-compose.yml` in detached mode. |
| - Purpose: Deploys the application topology (containers) for testing or development. |
| |
| down |
| - Action: Stops and removes all services started by `up`. |
| - Purpose: Cleans up the deployed application topology. |
| |
| up-bots-package (Mode A: Spawns Topology 2 with official package bots) |
| - Action: Calls `podman-compose up -d` with `GIWTF_IMAGE_SUFFIX=.package`. |
| - Purpose: Specifically brings up the environment using official package bots. |
| |
| up-bots-local (Mode B: Spawns Topology 2 with local binaries) |
| - Action: Calls `podman-compose up -d` with `GIWTF_IMAGE_SUFFIX=.local`. |
| - Purpose: Specifically brings up the environment using local binaries. |
| |
+-------------------------------------------------------------------------------------------------+

View File

@@ -10,7 +10,4 @@ echo "!!!!!!!!!!!!!!!! using binary $exe; installed package: $package"
which strings > /dev/null 2>&1 && strings "$exe" | grep -A 2 vcs.revision= | head -4 || : which strings > /dev/null 2>&1 && strings "$exe" | grep -A 2 vcs.revision= | head -4 || :
echo "RABBITMQ_HOST: $RABBITMQ_HOST" echo "RABBITMQ_HOST: $RABBITMQ_HOST"
echo "sleep 12 sec to let rabbitmq set up, because the bot currently retries only once"
sleep 12
exec $exe "$@" exec $exe "$@"

View File

@@ -11,7 +11,7 @@ RUN zypper -n install \
openssh \ openssh \
jq \ jq \
devel_Factory_git-workflow:gitea \ devel_Factory_git-workflow:gitea \
&& rm -rf /var/cache/zypp/* || ( tail -n 1000 /var/log/zypper.log ; exit 1 ) && rm -rf /var/cache/zypp/*
# Copy the minimal set of required files from the local 'container-files' directory # Copy the minimal set of required files from the local 'container-files' directory
COPY container-files/ / COPY container-files/ /

View File

@@ -1,77 +0,0 @@
+-------------------------------------------------------------------------------------------------+
| Podman-Compose Services Diagram |
+-------------------------------------------------------------------------------------------------+
| |
| [Network] |
| gitea-network (Bridge network for inter-service communication) |
| |
|-------------------------------------------------------------------------------------------------|
| |
| [Service: gitea] |
| Description: Self-hosted Git service, central hub for repositories and code management. |
| Container Name: gitea-test |
| Image: Built from ./gitea Dockerfile |
| Ports: 3000 (HTTP), 3022 (SSH) |
| Volumes: ./gitea-data (for persistent data), ./gitea-logs (for logs) |
| Network: gitea-network |
| |
|-------------------------------------------------------------------------------------------------|
| |
| [Service: rabbitmq] |
| Description: Message broker for asynchronous communication between services. |
| Container Name: rabbitmq-test |
| Image: rabbitmq:3.13.7-management |
| Ports: 5671 (AMQP), 15672 (Management UI) |
| Volumes: ./rabbitmq-data (for persistent data), ./rabbitmq-config/certs (TLS certs), |
| ./rabbitmq-config/rabbitmq.conf (config), ./rabbitmq-config/definitions.json (exchanges)|
| Healthcheck: Ensures RabbitMQ is running and healthy. |
| Network: gitea-network |
| |
|-------------------------------------------------------------------------------------------------|
| |
| [Service: gitea-publisher] |
| Description: Publishes events from Gitea to the RabbitMQ message queue. |
| Container Name: gitea-publisher |
| Image: Built from ../gitea-events-rabbitmq-publisher/Dockerfile (local/package) |
| Dependencies: gitea (started), rabbitmq (healthy) |
| Environment: RABBITMQ_HOST, RABBITMQ_USERNAME, RABBITMQ_PASSWORD, SSL_CERT_FILE |
| Command: Listens for Gitea events, publishes to 'suse' topic, debug enabled. |
| Network: gitea-network |
| |
|-------------------------------------------------------------------------------------------------|
| |
| [Service: workflow-pr] |
| Description: Manages pull request workflows, likely consuming events from RabbitMQ and |
| interacting with Gitea. |
| Container Name: workflow-pr |
| Image: Built from ../workflow-pr/Dockerfile (local/package) |
| Dependencies: gitea (started), rabbitmq (healthy) |
| Environment: AMQP_USERNAME, AMQP_PASSWORD, SSL_CERT_FILE |
| Volumes: ./gitea-data (read-only), ./workflow-pr/workflow-pr.json (config), |
| ./workflow-pr-repos (for repositories) |
| Command: Configures Gitea/RabbitMQ URLs, enables debug, manages repositories. |
| Network: gitea-network |
| |
|-------------------------------------------------------------------------------------------------|
| |
| [Service: mock-obs] |
| Description: A mock (simulated) service for the Open Build Service (OBS) for testing. |
| Container Name: mock-obs |
| Image: Built from ./mock-obs Dockerfile |
| Ports: 8080 |
| Volumes: ./mock-obs/responses (for mock API responses) |
| Network: gitea-network |
| |
|-------------------------------------------------------------------------------------------------|
| |
| [Service: obs-staging-bot] |
| Description: A bot that interacts with Gitea and the mock OBS, likely for staging processes. |
| Container Name: obs-staging-bot |
| Image: Built from ../obs-staging-bot/Dockerfile (local/package) |
| Dependencies: gitea (started), mock-obs (started) |
| Environment: OBS_USER, OBS_PASSWORD |
| Volumes: ./gitea-data (read-only) |
| Command: Configures Gitea/OBS URLs, enables debug. |
| Network: gitea-network |
| |
+-------------------------------------------------------------------------------------------------+

View File

@@ -1,10 +0,0 @@
[pytest]
markers =
t001: Test case 001
t002: Test case 002
t003: Test case 003
t004: Test case 004
t005: Test case 005
t006: Test case 006
t007: Test case 007
dependency: pytest-dependency marker

View File

@@ -1,7 +0,0 @@
listeners.ssl.default = 5671
ssl_options.certfile = /etc/rabbitmq/certs/cert.pem
ssl_options.keyfile = /etc/rabbitmq/certs/key.pem
ssl_options.verify = verify_none
ssl_options.fail_if_no_peer_cert = false
management.load_definitions = /etc/rabbitmq/definitions.json

View File

@@ -53,37 +53,31 @@ The testing will be conducted in a dedicated test environment that mimics the pr
## 5. Test Cases ## 5. Test Cases
| Test Case ID | Status | Description | Steps to Reproduce | Expected Results | Priority | | Test Case ID | Description | Steps to Reproduce | Expected Results | Priority |
| :--- | :--- | :--- | :--- | :--- | :--- | | :--- | :--- | :--- | :--- | :--- |
| **TC-SYNC-001** | P | **Create ProjectGit PR from PackageGit PR** | 1. Create a new PR in a PackageGit repository. | 1. A new PR is created in the corresponding ProjectGit repository with the title "Forwarded PRs: <package_name>".<br>2. The ProjectGit PR description contains a link to the PackageGit PR (e.g., `PR: org/package_repo!pr_number`).<br>3. The package submodule in the ProjectGit PR points to the PackageGit PR's commit. | High | | **TC-SYNC-001** | **Create ProjectGit PR from PackageGit PR** | 1. Create a new PR in a PackageGit repository. | 1. A new PR is created in the corresponding ProjectGit repository with the title "Forwarded PRs: <package_name>".<br>2. The ProjectGit PR description contains a link to the PackageGit PR (e.g., `PR: org/package_repo!pr_number`).<br>3. The package submodule in the ProjectGit PR points to the PackageGit PR's commit. | High |
| **TC-SYNC-002** | P | **Update ProjectGit PR from PackageGit PR** | 1. Push a new commit to an existing PackageGit PR. | 1. The corresponding ProjectGit PR's head branch is updated with the new commit. | High | | **TC-SYNC-002** | **Update ProjectGit PR from PackageGit PR** | 1. Push a new commit to an existing PackageGit PR. | 1. The corresponding ProjectGit PR's head branch is updated with the new commit. | High |
| **TC-SYNC-003** | P | **WIP Flag Synchronization** | 1. Mark a PackageGit PR as "Work In Progress".<br>2. Remove the WIP flag from the PackageGit PR. | 1. The corresponding ProjectGit PR is also marked as "Work In Progress".<br>2. The WIP flag on the ProjectGit PR is removed. | Medium | | **TC-SYNC-003** | **WIP Flag Synchronization** | 1. Mark a PackageGit PR as "Work In Progress".<br>2. Remove the WIP flag from the PackageGit PR. | 1. The corresponding ProjectGit PR is also marked as "Work In Progress".<br>2. The WIP flag on the ProjectGit PR is removed. | Medium |
| **TC-SYNC-004** | - | **WIP Flag (multiple referenced package PRs)** | 1. Create a ProjectGit PR that references multiple PackageGit PRs.<br>2. Mark one of the PackageGit PRs as "Work In Progress".<br>3. Remove the "Work In Progress" flag from all PackageGit PRs. | 1. The ProjectGit PR is marked as "Work In Progress".<br>2. The "Work In Progress" flag is removed from the ProjectGit PR only after it has been removed from all associated PackageGit PRs. | Medium | | **TC-SYNC-004** | **WIP Flag (multiple referenced package PRs)** | 1. Create a ProjectGit PR that references multiple PackageGit PRs.<br>2. Mark one of the PackageGit PRs as "Work In Progress".<br>3. Remove the "Work In Progress" flag from all PackageGit PRs. | 1. The ProjectGit PR is marked as "Work In Progress".<br>2. The "Work In Progress" flag is removed from the ProjectGit PR only after it has been removed from all associated PackageGit PRs. | Medium |
| **TC-SYNC-005** | x | **NoProjectGitPR = true, edits disabled** | 1. Set `NoProjectGitPR = true` in `workflow.config`.<br>2. Create a PackageGit PR without "Allow edits from maintainers" enabled. <br>3. Push a new commit to the PackageGit PR. | 1. No ProjectGit PR is created.<br>2. The bot adds a warning comment to the PackageGit PR explaining that it cannot update the PR. | High | | **TC-SYNC-005** | **NoProjectGitPR = true, edits disabled** | 1. Set `NoProjectGitPR = true` in `workflow.config`.<br>2. Create a PackageGit PR without "Allow edits from maintainers" enabled. <br>3. Push a new commit to the PackageGit PR. | 1. No ProjectGit PR is created.<br>2. The bot adds a warning comment to the PackageGit PR explaining that it cannot update the PR. | High |
| **TC-SYNC-006** | x | **NoProjectGitPR = true, edits enabled** | 1. Set `NoProjectGitPR = true` in `workflow.config`.<br>2. Create a PackageGit PR with "Allow edits from maintainers" enabled.<br>3. Push a new commit to the PackageGit PR. | 1. No ProjectGit PR is created.<br>2. The submodule commit on the project PR is updated with the new commit from the PackageGit PR. | High | | **TC-SYNC-006** | **NoProjectGitPR = true, edits enabled** | 1. Set `NoProjectGitPR = true` in `workflow.config`.<br>2. Create a PackageGit PR with "Allow edits from maintainers" enabled.<br>3. Push a new commit to the PackageGit PR. | 1. No ProjectGit PR is created.<br>2. The submodule commit on the project PR is updated with the new commit from the PackageGit PR. | High |
| **TC-COMMENT-001** | - | **Detect duplicate comments** | 1. Create a PackageGit PR.<br>2. Wait for the `workflow-pr` bot to act on the PR.<br>3. Edit the body of the PR to trigger the bot a second time. | 1. The bot should not post a duplicate comment. | High | | **TC-COMMENT-001** | **Detect duplicate comments** | 1. Create a PackageGit PR.<br>2. Wait for the `workflow-pr` bot to act on the PR.<br>3. Edit the body of the PR to trigger the bot a second time. | 1. The bot should not post a duplicate comment. | High |
| **TC-REVIEW-001** | P | **Add mandatory reviewers** | 1. Create a new PackageGit PR. | 1. All mandatory reviewers are added to both the PackageGit and ProjectGit PRs. | High | | **TC-REVIEW-001** | **Add mandatory reviewers** | 1. Create a new PackageGit PR. | 1. All mandatory reviewers are added to both the PackageGit and ProjectGit PRs. | High |
| **TC-REVIEW-002** | - | **Add advisory reviewers** | 1. Create a new PackageGit PR with advisory reviewers defined in the configuration. | 1. Advisory reviewers are added to the PR, but their approval is not required for merging. | Medium | | **TC-REVIEW-002** | **Add advisory reviewers** | 1. Create a new PackageGit PR with advisory reviewers defined in the configuration. | 1. Advisory reviewers are added to the PR, but their approval is not required for merging. | Medium |
| **TC-REVIEW-003** | - | **Re-add reviewers** | 1. Push a new commit to a PackageGit PR after it has been approved. | 1. The original reviewers are re-added to the PR. | Medium | | **TC-REVIEW-003** | **Re-add reviewers** | 1. Push a new commit to a PackageGit PR after it has been approved. | 1. The original reviewers are re-added to the PR. | Medium |
| **TC-REVIEW-004** | x | **Package PR created by a maintainer** | 1. Create a PackageGit PR from the account of a package maintainer. | 1. No review is requested from other package maintainers. | High | | **TC-REVIEW-004** | **Package PR created by a maintainer** | 1. Create a PackageGit PR from the account of a package maintainer. | 1. No review is requested from other package maintainers. | High |
| **TC-REVIEW-005** | P | **Package PR created by an external user (approve)** | 1. Create a PackageGit PR from the account of a user who is not a package maintainer.<br>2. One of the package maintainers approves the PR. | 1. All package maintainers are added as reviewers.<br>2. Once one maintainer approves the PR, the other maintainers are removed as reviewers. | High | | **TC-REVIEW-005** | **Package PR created by an external user (approve)** | 1. Create a PackageGit PR from the account of a user who is not a package maintainer.<br>2. One of the package maintainers approves the PR. | 1. All package maintainers are added as reviewers.<br>2. Once one maintainer approves the PR, the other maintainers are removed as reviewers. | High |
| **TC-REVIEW-006** | P | **Package PR created by an external user (reject)** | 1. Create a PackageGit PR from the account of a user who is not a package maintainer.<br>2. One of the package maintainers rejects the PR. | 1. All package maintainers are added as reviewers.<br>2. Once one maintainer rejects the PR, the other maintainers are removed as reviewers. | High | | **TC-REVIEW-006** | **Package PR created by an external user (reject)** | 1. Create a PackageGit PR from the account of a user who is not a package maintainer.<br>2. One of the package maintainers rejects the PR. | 1. All package maintainers are added as reviewers.<br>2. Once one maintainer rejects the PR, the other maintainers are removed as reviewers. | High |
| **TC-REVIEW-007** | P | **Package PR created by a maintainer with ReviewRequired=true** | 1. Set `ReviewRequired = true` in `workflow.config`.<br>2. Create a PackageGit PR from the account of a package maintainer. | 1. A review is requested from other package maintainers if available. | High | | **TC-REVIEW-007** | **Package PR created by a maintainer with ReviewRequired=true** | 1. Set `ReviewRequired = true` in `workflow.config`.<br>2. Create a PackageGit PR from the account of a package maintainer. | 1. A review is requested from other package maintainers if available. | High |
| **TC-MERGE-001** | x | **Automatic Merge** | 1. Create a PackageGit PR.<br>2. Ensure all mandatory reviews are completed on both project and package PRs. | 1. The PR is automatically merged. | High | | **TC-MERGE-001** | **Automatic Merge** | 1. Create a PackageGit PR.<br>2. Ensure all mandatory reviews are completed on both project and package PRs. | 1. The PR is automatically merged. | High |
| **TC-MERGE-002** | - | **ManualMergeOnly with Package Maintainer** | 1. Create a PackageGit PR with `ManualMergeOnly` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the package PR from the account of a package maintainer for that package. | 1. The PR is merged. | High | | **TC-MERGE-002** | **ManualMergeOnly with Package Maintainer** | 1. Create a PackageGit PR with `ManualMergeOnly` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the package PR from the account of a package maintainer for that package. | 1. The PR is merged. | High |
| **TC-MERGE-003** | - | **ManualMergeOnly with unauthorized user** | 1. Create a PackageGit PR with `ManualMergeOnly` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the package PR from the account of a user who is not a maintainer for that package. | 1. The PR is not merged. | High | | **TC-MERGE-003** | **ManualMergeOnly with unauthorized user** | 1. Create a PackageGit PR with `ManualMergeOnly` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the package PR from the account of a user who is not a maintainer for that package. | 1. The PR is not merged. | High |
| **TC-MERGE-004** | - | **ManualMergeOnly with multiple packages** | 1. Create a ProjectGit PR that references multiple PackageGit PRs with `ManualMergeOnly` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on each package PR from the account of a package maintainer. | 1. The PR is merged only after "merge ok" is commented on all associated PackageGit PRs. | High | | **TC-MERGE-004** | **ManualMergeOnly with multiple packages** | 1. Create a ProjectGit PR that references multiple PackageGit PRs with `ManualMergeOnly` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on each package PR from the account of a package maintainer. | 1. The PR is merged only after "merge ok" is commented on all associated PackageGit PRs. | High |
| **TC-MERGE-005** | - | **ManualMergeOnly with Project Maintainer** | 1. Create a PackageGit PR with `ManualMergeOnly` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the package PR from the account of a project maintainer. | 1. The PR is merged. | High | | **TC-MERGE-005** | **ManualMergeOnly with Project Maintainer** | 1. Create a PackageGit PR with `ManualMergeOnly` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the package PR from the account of a project maintainer. | 1. The PR is merged. | High |
| **TC-MERGE-006** | - | **ManualMergeProject with Project Maintainer** | 1. Create a PackageGit PR with `ManualMergeProject` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the project PR from the account of a project maintainer. | 1. The PR is merged. | High | | **TC-MERGE-006** | **ManualMergeProject with Project Maintainer** | 1. Create a PackageGit PR with `ManualMergeProject` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the project PR from the account of a project maintainer. | 1. The PR is merged. | High |
| **TC-MERGE-007** | - | **ManualMergeProject with unauthorized user** | 1. Create a PackageGit PR with `ManualMergeProject` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the project PR from the account of a package maintainer. | 1. The PR is not merged. | High | | **TC-MERGE-007** | **ManualMergeProject with unauthorized user** | 1. Create a PackageGit PR with `ManualMergeProject` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the project PR from the account of a package maintainer. | 1. The PR is not merged. | High |
| **TC-CONFIG-001** | - | **Invalid Configuration** | 1. Provide an invalid `workflow.config` file. | 1. The bot reports an error and does not process any PRs. | High | | **TC-CONFIG-001** | **Invalid Configuration** | 1. Provide an invalid `workflow.config` file. | 1. The bot reports an error and does not process any PRs. | High |
| **TC-LABEL-001** | P | **Apply `staging/Auto` label** | 1. Create a new PackageGit PR. | 1. The `staging/Auto` label is applied to the ProjectGit PR. | High | | **TC-LABEL-001** | **Apply `staging/Auto` label** | 1. Create a new PackageGit PR. | 1. The `staging/Auto` label is applied to the ProjectGit PR. | High |
| **TC-LABEL-002** | x | **Apply `review/Pending` label** | 1. Create a new PackageGit PR. | 1. The `review/Pending` label is applied to the ProjectGit PR when there are pending reviews. | Medium | | **TC-LABEL-002** | **Apply `review/Pending` label** | 1. Create a new PackageGit PR. | 1. The `review/Pending` label is applied to the ProjectGit PR when there are pending reviews. | Medium |
| **TC-LABEL-003** | - | **Apply `review/Done` label** | 1. Ensure all mandatory reviews for a PR are completed. | 1. The `review/Done` label is applied to the ProjectGit PR when all mandatory reviews are completed. | Medium | | **TC-LABEL-003** | **Apply `review/Done` label** | 1. Ensure all mandatory reviews for a PR are completed. | 1. The `review/Done` label is applied to the ProjectGit PR when all mandatory reviews are completed. | Medium |
#### Legend:
* P = implemented and passing;
* x = likely implemented, but investigation is needed;
* X = implemented and likely to pass, but someteimes may fail, but troubleshooting is needed;
* - = test is not implemented

View File

@@ -6,163 +6,58 @@ import pytest
import requests import requests
import time import time
import os import os
import json
import base64 # Assuming GiteaAPIClient is in tests/lib/common_test_utils.py
from tests.lib.common_test_utils import GiteaAPIClient from tests.lib.common_test_utils import GiteaAPIClient
BRANCH_CONFIG_COMMON = {
"workflow.config": {
"Workflows": ["pr"],
"Organization": "pool",
"Reviewers": ["-autogits_obs_staging_bot"],
"GitProjectName": "products/SLFO#{branch}"
},
"_maintainership.json": {
"": ["ownerX", "ownerY"],
"pkgA": ["ownerA"],
"pkgB": ["ownerB", "ownerBB"]
}
}
BRANCH_CONFIG_CUSTOM = {
"main": {
"workflow.config": {
"ManualMergeProject": True
},
"staging.config": {
"ObsProject": "openSUSE:Leap:16.0",
"StagingProject": "openSUSE:Leap:16.0:PullRequest"
}
},
"merge": {
"workflow.config": {
"Reviewers": ["+usera", "+userb", "-autogits_obs_staging_bot"]
}
},
"maintainer-merge": {
"workflow.config": {
}
},
"review-required": {
"workflow.config": {
"ReviewRequired": True
}
},
"dev": {
"workflow.config": {
"ManualMergeProject": True,
"NoProjectGitPR": True
}
},
"label-test": {
"workflow.config": {
"ManualMergeProject": True,
"Reviewers": ["*usera"],
"ReviewRequired": True,
"Labels": {
"StagingAuto": "staging/Backlog",
"ReviewPending": "review/Pending"
}
}
}
}
def setup_users_from_config(client: GiteaAPIClient, wf: dict, mt: dict):
"""
Parses workflow.config and _maintainership.json, creates users, and adds them as collaborators.
"""
all_users = set()
# Extract from workflow.config Reviewers
reviewers = wf.get("Reviewers", [])
for r in reviewers:
username = r.lstrip("+-*")
if username and username not in ["autogits_obs_staging_bot", "workflow-pr"]:
all_users.add(username)
# Extract from maintainership
for pkg, users in mt.items():
for username in users:
all_users.add(username)
# Create all users
for username in all_users:
client.create_user(username, "password123", f"{username}@example.com")
client.add_collaborator("products", "SLFO", username, "write")
# Set specific repository permissions based on maintainership
for pkg, users in mt.items():
repo_name = pkg if pkg else None
for username in users:
if not repo_name:
client.add_collaborator("pool", "pkgA", username, "write")
client.add_collaborator("pool", "pkgB", username, "write")
else:
client.add_collaborator("pool", repo_name, username, "write")
def ensure_config_file(client: GiteaAPIClient, owner: str, repo: str, branch: str, file_name: str, expected_content_dict: dict):
"""
Checks if a config file exists and has the correct content.
Returns True if a change was made, False otherwise.
"""
file_info = client.get_file_info(owner, repo, file_name, branch=branch)
expected_content = json.dumps(expected_content_dict, indent=4)
if file_info:
current_content_raw = base64.b64decode(file_info["content"]).decode("utf-8")
try:
current_content_dict = json.loads(current_content_raw)
if current_content_dict == expected_content_dict:
return False
except json.JSONDecodeError:
pass # Overwrite invalid JSON
client.create_file(owner, repo, file_name, expected_content, branch=branch)
return True
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def gitea_env(): def gitea_env():
""" """
Global fixture to set up the Gitea environment for all tests. Sets up the Gitea environment with dummy data and provides a GiteaAPIClient instance.
""" """
gitea_url = "http://127.0.0.1:3000" gitea_url = "http://127.0.0.1:3000"
admin_token_path = "./gitea-data/admin.token"
# Read admin token
admin_token_path = "./gitea-data/admin.token" # Corrected path
admin_token = None admin_token = None
try: try:
with open(admin_token_path, "r") as f: with open(admin_token_path, "r") as f:
admin_token = f.read().strip() admin_token = f.read().strip()
except FileNotFoundError: except FileNotFoundError:
raise Exception(f"Admin token file not found at {admin_token_path}.") raise Exception(f"Admin token file not found at {admin_token_path}. Ensure it's generated and accessible.")
# Headers for authenticated requests
auth_headers = {"Authorization": f"token {admin_token}", "Content-Type": "application/json"}
# Wait for Gitea to be available
print(f"Waiting for Gitea at {gitea_url}...")
max_retries = 30
for i in range(max_retries):
try:
# Check a specific API endpoint that indicates readiness
response = requests.get(f"{gitea_url}/api/v1/version", headers=auth_headers, timeout=5)
if response.status_code == 200:
print("Gitea API is available.")
break
except requests.exceptions.ConnectionError:
pass
print(f"Gitea not ready ({response.status_code if 'response' in locals() else 'ConnectionError'}), retrying in 5 seconds... ({i+1}/{max_retries})")
time.sleep(5)
else:
raise Exception("Gitea did not become available within the expected time.")
client = GiteaAPIClient(base_url=gitea_url, token=admin_token) client = GiteaAPIClient(base_url=gitea_url, token=admin_token)
# Wait for Gitea # Setup dummy data
for i in range(10): print("--- Starting Gitea Dummy Data Setup from Pytest Fixture ---")
try:
if client._request("GET", "version").status_code == 200:
break
except:
pass
time.sleep(1)
else:
raise Exception("Gitea not available.")
print("--- Starting Gitea Global Setup ---")
client.create_org("products") client.create_org("products")
client.create_org("pool") client.create_org("pool")
client.create_repo("products", "SLFO") client.create_repo("products", "SLFO")
client.create_repo("pool", "pkgA") client.create_repo("pool", "pkgA")
client.create_repo("pool", "pkgB") client.create_repo("pool", "pkgB")
client.update_repo_settings("products", "SLFO")
client.update_repo_settings("pool", "pkgA")
client.update_repo_settings("pool", "pkgB")
# Create labels # The add_submodules method also creates workflow.config and staging.config
client.create_label("products", "SLFO", "staging/Backlog", color="#0000ff")
client.create_label("products", "SLFO", "review/Pending", color="#ffff00")
# Submodules in SLFO
client.add_submodules("products", "SLFO") client.add_submodules("products", "SLFO")
client.add_collaborator("products", "SLFO", "autogits_obs_staging_bot", "write") client.add_collaborator("products", "SLFO", "autogits_obs_staging_bot", "write")
@@ -170,92 +65,14 @@ def gitea_env():
client.add_collaborator("pool", "pkgA", "workflow-pr", "write") client.add_collaborator("pool", "pkgA", "workflow-pr", "write")
client.add_collaborator("pool", "pkgB", "workflow-pr", "write") client.add_collaborator("pool", "pkgB", "workflow-pr", "write")
restart_needed = False client.update_repo_settings("products", "SLFO")
client.update_repo_settings("pool", "pkgA")
client.update_repo_settings("pool", "pkgB")
print("--- Gitea Dummy Data Setup Complete ---")
time.sleep(5) # Add a small delay for Gitea to fully process changes
# Setup all branches and configs
for branch_name, custom_configs in BRANCH_CONFIG_CUSTOM.items():
# Ensure branch exists in all 3 repos
for owner, repo in [("products", "SLFO"), ("pool", "pkgA"), ("pool", "pkgB")]:
if branch_name != "main":
try:
main_sha = client._request("GET", f"repos/{owner}/{repo}/branches/main").json()["commit"]["id"]
client.create_branch(owner, repo, branch_name, main_sha)
except Exception as e:
if "already exists" not in str(e).lower():
raise
# Merge configs
merged_configs = {}
for file_name, common_content in BRANCH_CONFIG_COMMON.items():
merged_configs[file_name] = common_content.copy()
# Dynamically format values containing {branch}
if file_name == "workflow.config":
if "GitProjectName" in merged_configs[file_name]:
merged_configs[file_name]["GitProjectName"] = merged_configs[file_name]["GitProjectName"].format(branch=branch_name)
# Inject branch name dynamically
merged_configs[file_name]["Branch"] = branch_name
for file_name, custom_content in custom_configs.items():
if file_name in merged_configs:
merged_configs[file_name].update(custom_content)
else:
merged_configs[file_name] = custom_content
# Ensure config files in products/SLFO
for file_name, content_dict in merged_configs.items():
if ensure_config_file(client, "products", "SLFO", branch_name, file_name, content_dict):
restart_needed = True
# Setup users (using configs from this branch)
setup_users_from_config(client, merged_configs.get("workflow.config", {}), merged_configs.get("_maintainership.json", {}))
if restart_needed:
client.restart_service("workflow-pr")
time.sleep(2) # Give it time to pick up changes
print("--- Gitea Global Setup Complete ---")
yield client yield client
@pytest.fixture(scope="session") # Teardown (optional, depending on test strategy)
def automerge_env(gitea_env): # For now, we'll leave resources for inspection. If a clean slate is needed for each test,
return gitea_env, "products/SLFO", "merge" # this fixture's scope would be 'function' and teardown logic would be added here.
@pytest.fixture(scope="session")
def maintainer_env(gitea_env):
return gitea_env, "products/SLFO", "maintainer-merge"
@pytest.fixture(scope="session")
def review_required_env(gitea_env):
return gitea_env, "products/SLFO", "review-required"
@pytest.fixture(scope="session")
def no_project_git_pr_env(gitea_env):
return gitea_env, "products/SLFO", "dev"
@pytest.fixture(scope="session")
def label_env(gitea_env):
return gitea_env, "products/SLFO", "label-test"
@pytest.fixture(scope="session")
def ownerA_client(gitea_env):
return GiteaAPIClient(base_url=gitea_env.base_url, token=gitea_env.headers["Authorization"].split(" ")[1], sudo="ownerA")
@pytest.fixture(scope="session")
def ownerB_client(gitea_env):
return GiteaAPIClient(base_url=gitea_env.base_url, token=gitea_env.headers["Authorization"].split(" ")[1], sudo="ownerB")
@pytest.fixture(scope="session")
def ownerBB_client(gitea_env):
return GiteaAPIClient(base_url=gitea_env.base_url, token=gitea_env.headers["Authorization"].split(" ")[1], sudo="ownerBB")
@pytest.fixture(scope="session")
def staging_bot_client(gitea_env):
return GiteaAPIClient(base_url=gitea_env.base_url, token=gitea_env.headers["Authorization"].split(" ")[1], sudo="autogits_obs_staging_bot")
@pytest.fixture(scope="session")
def test_user_client(gitea_env):
username = f"test-user-{int(time.time())}"
gitea_env.create_user(username, "password123", f"{username}@example.com")
gitea_env.add_collaborator("pool", "pkgA", username, "write")
gitea_env.add_collaborator("products", "SLFO", username, "write")
return GiteaAPIClient(base_url=gitea_env.base_url, token=gitea_env.headers["Authorization"].split(" ")[1], sudo=username)

View File

@@ -6,7 +6,6 @@ import json
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from pathlib import Path from pathlib import Path
import base64 import base64
import subprocess
TEST_DATA_DIR = Path(__file__).parent.parent / "data" TEST_DATA_DIR = Path(__file__).parent.parent / "data"
BUILD_RESULT_TEMPLATE = TEST_DATA_DIR / "build_result.xml.template" BUILD_RESULT_TEMPLATE = TEST_DATA_DIR / "build_result.xml.template"
@@ -44,11 +43,9 @@ def mock_build_result():
class GiteaAPIClient: class GiteaAPIClient:
def __init__(self, base_url, token, sudo=None): def __init__(self, base_url, token):
self.base_url = base_url self.base_url = base_url
self.headers = {"Authorization": f"token {token}", "Content-Type": "application/json"} self.headers = {"Authorization": f"token {token}", "Content-Type": "application/json"}
if sudo:
self.headers["Sudo"] = sudo
def _request(self, method, path, **kwargs): def _request(self, method, path, **kwargs):
url = f"{self.base_url}/api/v1/{path}" url = f"{self.base_url}/api/v1/{path}"
@@ -61,48 +58,6 @@ class GiteaAPIClient:
raise raise
return response return response
def get_file_info(self, owner: str, repo: str, file_path: str, branch: str = "main"):
url = f"repos/{owner}/{repo}/contents/{file_path}"
if branch and branch != "main":
url += f"?ref={branch}"
try:
response = self._request("GET", url)
return response.json()
except requests.exceptions.HTTPError as e:
if e.response.status_code == 404:
return None
raise
def create_user(self, username, password, email):
print(f"--- Creating user: {username} ---")
data = {
"username": username,
"password": password,
"email": email,
"must_change_password": False,
"send_notify": False
}
try:
self._request("POST", "admin/users", json=data)
print(f"User '{username}' created.")
except requests.exceptions.HTTPError as e:
if e.response.status_code == 422: # Already exists
print(f"User '{username}' already exists. Updating password...")
# Update password to be sure it matches our expectation
self._request("PATCH", f"admin/users/{username}", json={"password": password, "login_name": username})
else:
raise
def get_user_token(self, username, password, token_name="test-token"):
print(f"--- Getting token for user: {username} ---")
url = f"{self.base_url}/api/v1/users/{username}/tokens"
# Create new token using Basic Auth
response = requests.post(url, auth=(username, password), json={"name": token_name})
if response.status_code == 201:
return response.json()["sha1"]
response.raise_for_status()
def create_org(self, org_name): def create_org(self, org_name):
print(f"--- Checking organization: {org_name} ---") print(f"--- Checking organization: {org_name} ---")
try: try:
@@ -116,18 +71,6 @@ class GiteaAPIClient:
print(f"Organization '{org_name}' created.") print(f"Organization '{org_name}' created.")
else: else:
raise raise
print(f"--- Checking organization: {org_name} ---")
try:
self._request("GET", f"orgs/{org_name}")
print(f"Organization '{org_name}' already exists.")
except requests.exceptions.HTTPError as e:
if e.response.status_code == 404:
print(f"Creating organization '{org_name}'...")
data = {"username": org_name, "full_name": org_name}
self._request("POST", "orgs", json=data)
print(f"Organization '{org_name}' created.")
else:
raise
def create_repo(self, org_name, repo_name): def create_repo(self, org_name, repo_name):
print(f"--- Checking repository: {org_name}/{repo_name} ---") print(f"--- Checking repository: {org_name}/{repo_name} ---")
@@ -148,7 +91,7 @@ class GiteaAPIClient:
} }
self._request("POST", f"orgs/{org_name}/repos", json=data) self._request("POST", f"orgs/{org_name}/repos", json=data)
print(f"Repository '{org_name}/{repo_name}' created with a README.") print(f"Repository '{org_name}/{repo_name}' created with a README.")
time.sleep(0.1) # Added delay to allow Git operations to become available time.sleep(1) # Added delay to allow Git operations to become available
else: else:
raise raise
@@ -204,8 +147,30 @@ index 0000000..{pkg_b_sha}
+++ b/pkgB +++ b/pkgB
@@ -0,0 +1 @@ @@ -0,0 +1 @@
+Subproject commit {pkg_b_sha} +Subproject commit {pkg_b_sha}
diff --git a/workflow.config b/workflow.config
new file mode 100644
--- /dev/null
+++ b/workflow.config
@@ -0,0 +7 @@
+{{
+ "Workflows": ["pr"],
+ "GitProjectName": "products/SLFO#main",
+ "Organization": "pool",
+ "Branch": "main",
+ "ManualMergeProject": true,
+ "Reviewers": [ "-autogits_obs_staging_bot" ]
+}}
diff --git a/staging.config b/staging.config
new file mode 100644
--- /dev/null
+++ b/staging.config
@@ -0,0 +3 @@
+{{
+ "ObsProject": "openSUSE:Leap:16.0",
+ "StagingProject": "openSUSE:Leap:16.0:PullRequest"
+}}
""" """
message = "Add pkgA and pkgB as submodules" message = "Add pkgA and pkgB as submodules and config files"
data = { data = {
"branch": "main", "branch": "main",
"content": diff_content, "content": diff_content,
@@ -226,161 +191,57 @@ index 0000000..{pkg_b_sha}
self._request("PATCH", f"repos/{org_name}/{repo_name}", json=repo_data) self._request("PATCH", f"repos/{org_name}/{repo_name}", json=repo_data)
print(f"Repository settings for '{org_name}/{repo_name}' updated.") print(f"Repository settings for '{org_name}/{repo_name}' updated.")
def create_label(self, owner: str, repo: str, name: str, color: str = "#abcdef"):
print(f"--- Creating label '{name}' in {owner}/{repo} ---")
url = f"repos/{owner}/{repo}/labels"
data = {
"name": name,
"color": color
}
try:
self._request("POST", url, json=data)
print(f"Label '{name}' created.")
except requests.exceptions.HTTPError as e:
if e.response.status_code == 422: # Already exists
print(f"Label '{name}' already exists.")
else:
raise
def create_file(self, owner: str, repo: str, file_path: str, content: str, branch: str = "main", message: str = "Add file"): def create_gitea_pr(self, repo_full_name: str, diff_content: str, title: str):
file_info = self.get_file_info(owner, repo, file_path, branch=branch)
data = {
"content": base64.b64encode(content.encode('utf-8')).decode('ascii'),
"branch": branch,
"message": message
}
if file_info:
print(f"--- Updating file {file_path} in {owner}/{repo} ---")
# Re-fetch file_info to get the latest SHA right before update
latest_file_info = self.get_file_info(owner, repo, file_path, branch=branch)
if not latest_file_info:
raise Exception(f"File {file_path} disappeared during update attempt.")
data["sha"] = latest_file_info["sha"]
data["message"] = f"Update {file_path}"
method = "PUT"
else:
print(f"--- Creating file {file_path} in {owner}/{repo} ---")
method = "POST"
url = f"repos/{owner}/{repo}/contents/{file_path}"
self._request(method, url, json=data)
print(f"File {file_path} {'updated' if file_info else 'created'} in {owner}/{repo}.")
def create_gitea_pr(self, repo_full_name: str, diff_content: str, title: str, use_fork: bool, base_branch: str = "main", body: str = ""):
owner, repo = repo_full_name.split("/") owner, repo = repo_full_name.split("/")
url = f"repos/{owner}/{repo}/pulls"
base_branch = "main"
head_owner, head_repo = owner, repo # Create a new branch for the PR
new_branch_name = f"pr-branch-{int(time.time())}"
if use_fork: # Get the latest commit SHA of the base branch
sudo_user = self.headers.get("Sudo") base_commit_sha = self._request("GET", f"repos/{owner}/{repo}/branches/{base_branch}").json()["commit"]["id"]
head_owner = sudo_user
head_repo = repo
new_branch_name = f"pr-branch-{int(time.time()*1000)}"
print(f"--- Forking {repo_full_name} ---") # Create the new branch
try: self._request("POST", f"repos/{owner}/{repo}/branches", json={
self._request("POST", f"repos/{owner}/{repo}/forks", json={}) "new_branch_name": new_branch_name,
print(f"--- Forked to {head_owner}/{head_repo} ---") "old_ref": base_commit_sha # Use the commit SHA directly
time.sleep(0.5) # Give more time for fork to be ready
except requests.exceptions.HTTPError as e:
if e.response.status_code == 409: # Already forked
print(f"--- Already forked to {head_owner}/{head_repo} ---")
else:
raise
# Create a unique branch in the FORK
base_commit_sha = self._request("GET", f"repos/{owner}/{repo}/branches/{base_branch}").json()["commit"]["id"]
print(f"--- Creating branch {new_branch_name} in {head_owner}/{head_repo} from {base_branch} ({base_commit_sha}) ---")
self._request("POST", f"repos/{head_owner}/{head_repo}/branches", json={
"new_branch_name": new_branch_name,
"old_ref": base_commit_sha
})
else:
new_branch_name = f"pr-branch-{int(time.time()*1000)}"
# Get the latest commit SHA of the base branch from the ORIGINAL repo
base_commit_sha = self._request("GET", f"repos/{owner}/{repo}/branches/{base_branch}").json()["commit"]["id"]
# Try to create the branch in the ORIGINAL repo
print(f"--- Creating branch {new_branch_name} in {repo_full_name} ---")
self._request("POST", f"repos/{owner}/{repo}/branches", json={
"new_branch_name": new_branch_name,
"old_ref": base_commit_sha
})
# Apply the diff using diffpatch in the branch (wherever it is)
print(f"--- Applying diff to {head_owner}/{head_repo} branch {new_branch_name} ---")
self._request("POST", f"repos/{head_owner}/{head_repo}/diffpatch", json={
"branch": new_branch_name,
"content": diff_content,
"message": title
}) })
# Now create the PR in the ORIGINAL repo # Create a new file or modify an existing one in the new branch
file_path = f"test-file-{int(time.time())}.txt"
file_content = "This is a test file for the PR."
self._request("POST", f"repos/{owner}/{repo}/contents/{file_path}", json={
"content": base64.b64encode(file_content.encode('utf-8')).decode('ascii'),
"message": "Add test file",
"branch": new_branch_name
})
# Now create the PR
data = { data = {
"head": f"{head_owner}:{new_branch_name}" if head_owner != owner else new_branch_name, "head": new_branch_name, # Use the newly created branch as head
"base": base_branch, "base": base_branch,
"title": title, "title": title,
"body": body, "body": "Test Pull Request"
"allow_maintainer_edit": True
} }
print(f"--- Creating PR in {repo_full_name} from {data['head']} ---") response = self._request("POST", url, json=data)
response = self._request("POST", f"repos/{owner}/{repo}/pulls", json=data)
return response.json() return response.json()
def create_branch(self, owner: str, repo: str, new_branch_name: str, old_ref: str):
print(f"--- Checking branch '{new_branch_name}' in {owner}/{repo} ---")
try:
self._request("GET", f"repos/{owner}/{repo}/branches/{new_branch_name}")
print(f"Branch '{new_branch_name}' already exists.")
return
except requests.exceptions.HTTPError as e:
if e.response.status_code != 404:
raise # Re-raise other HTTP errors
print(f"--- Creating branch '{new_branch_name}' in {owner}/{repo} from {old_ref} ---")
url = f"repos/{owner}/{repo}/branches"
data = {
"new_branch_name": new_branch_name,
"old_ref": old_ref
}
self._request("POST", url, json=data)
print(f"Branch '{new_branch_name}' created in {owner}/{repo}.")
def ensure_branch_exists(self, owner: str, repo: str, branch: str = "main", timeout: int = 10):
print(f"--- Ensuring branch '{branch}' exists in {owner}/{repo} ---")
start_time = time.time()
while time.time() - start_time < timeout:
try:
self._request("GET", f"repos/{owner}/{repo}/branches/{branch}")
print(f"Branch '{branch}' confirmed in {owner}/{repo}.")
return
except requests.exceptions.HTTPError as e:
if e.response.status_code == 404:
print(f"Branch '{branch}' not found yet in {owner}/{repo}. Retrying...")
time.sleep(1)
continue
raise
raise Exception(f"Timeout waiting for branch {branch} in {owner}/{repo}")
def modify_gitea_pr(self, repo_full_name: str, pr_number: int, diff_content: str, message: str): def modify_gitea_pr(self, repo_full_name: str, pr_number: int, diff_content: str, message: str):
owner, repo = repo_full_name.split("/") owner, repo = repo_full_name.split("/")
# Get PR details to find the head branch AND head repo # Get PR details to find the head branch
pr_details = self._request("GET", f"repos/{owner}/{repo}/pulls/{pr_number}").json() pr_details = self._request("GET", f"repos/{owner}/{repo}/pulls/{pr_number}").json()
head_branch = pr_details["head"]["ref"] head_branch = pr_details["head"]["ref"]
head_repo_owner = pr_details["head"]["repo"]["owner"]["login"]
head_repo_name = pr_details["head"]["repo"]["name"]
# Apply the diff using diffpatch file_path = f"modified-file-{int(time.time())}.txt"
print(f"--- Modifying PR #{pr_number} in {head_repo_owner}/{head_repo_name} branch {head_branch} ---") file_content = "This is a modified test file for the PR."
self._request("POST", f"repos/{head_repo_owner}/{head_repo_name}/diffpatch", json={
"branch": head_branch, self._request("POST", f"repos/{owner}/{repo}/contents/{file_path}", json={
"content": diff_content, "content": base64.b64encode(file_content.encode('utf-8')).decode('ascii'),
"message": message "message": message,
"branch": head_branch
}) })
def update_gitea_pr_properties(self, repo_full_name: str, pr_number: int, **kwargs): def update_gitea_pr_properties(self, repo_full_name: str, pr_number: int, **kwargs):
@@ -400,12 +261,12 @@ index 0000000..{pkg_b_sha}
timeline_events = response.json() timeline_events = response.json()
if timeline_events: # Check if timeline_events list is not empty if timeline_events: # Check if timeline_events list is not empty
return timeline_events return timeline_events
print(f"Attempt {i+1}: Timeline for PR {pr_number} is empty. Retrying in 1 seconds...") print(f"Attempt {i+1}: Timeline for PR {pr_number} is empty. Retrying in 3 seconds...")
time.sleep(1) time.sleep(3)
except requests.exceptions.HTTPError as e: except requests.exceptions.HTTPError as e:
if e.response.status_code == 404: if e.response.status_code == 404:
print(f"Attempt {i+1}: Timeline for PR {pr_number} not found yet. Retrying in 1 seconds...") print(f"Attempt {i+1}: Timeline for PR {pr_number} not found yet. Retrying in 3 seconds...")
time.sleep(1) time.sleep(3)
else: else:
raise # Re-raise other HTTP errors raise # Re-raise other HTTP errors
raise Exception(f"Failed to retrieve timeline for PR {pr_number} after multiple retries.") raise Exception(f"Failed to retrieve timeline for PR {pr_number} after multiple retries.")
@@ -422,12 +283,12 @@ index 0000000..{pkg_b_sha}
print(f"Attempt {i+1}: Comments for PR {pr_number} received: {comments}") # Added debug print print(f"Attempt {i+1}: Comments for PR {pr_number} received: {comments}") # Added debug print
if comments: # Check if comments list is not empty if comments: # Check if comments list is not empty
return comments return comments
print(f"Attempt {i+1}: Comments for PR {pr_number} are empty. Retrying in 1 seconds...") print(f"Attempt {i+1}: Comments for PR {pr_number} are empty. Retrying in 3 seconds...")
time.sleep(1) time.sleep(3)
except requests.exceptions.HTTPError as e: except requests.exceptions.HTTPError as e:
if e.response.status_code == 404: if e.response.status_code == 404:
print(f"Attempt {i+1}: Comments for PR {pr_number} not found yet. Retrying in 1 seconds...") print(f"Attempt {i+1}: Comments for PR {pr_number} not found yet. Retrying in 3 seconds...")
time.sleep(1) time.sleep(3)
else: else:
raise # Re-raise other HTTP errors raise # Re-raise other HTTP errors
raise Exception(f"Failed to retrieve comments for PR {pr_number} after multiple retries.") raise Exception(f"Failed to retrieve comments for PR {pr_number} after multiple retries.")
@@ -438,87 +299,3 @@ index 0000000..{pkg_b_sha}
response = self._request("GET", url) response = self._request("GET", url)
return response.json() return response.json()
def create_review(self, repo_full_name: str, pr_number: int, event: str = "APPROVED", body: str = "LGTM"):
owner, repo = repo_full_name.split("/")
# Check if this user already has an APPROVED review to avoid 422
current_user = self.headers.get("Sudo") or "admin" # simplified
existing_reviews = self.list_reviews(repo_full_name, pr_number)
for r in existing_reviews:
if r["user"]["login"] == current_user and r["state"] == "APPROVED" and event == "APPROVED":
print(f"User {current_user} already has an APPROVED review for {repo_full_name} PR #{pr_number}")
return r
url = f"repos/{owner}/{repo}/pulls/{pr_number}/reviews"
data = {
"event": event,
"body": body
}
print(f"--- Creating and submitting review ({event}) for {repo_full_name} PR #{pr_number} as {current_user} ---")
try:
response = self._request("POST", url, json=data)
review = response.json()
except requests.exceptions.HTTPError as e:
# If it fails with 422, it might be because a review is already pending or something else
print(f"Failed to create review: {e.response.text}")
# Try to find a pending review to submit
existing_reviews = self.list_reviews(repo_full_name, pr_number)
pending_review = next((r for r in existing_reviews if r["user"]["login"] == current_user and r["state"] == "PENDING"), None)
if pending_review:
review = pending_review
else:
raise
# If the state is PENDING, we submit it.
if review.get("state") == "PENDING":
review_id = review["id"]
submit_url = f"repos/{owner}/{repo}/pulls/{pr_number}/reviews/{review_id}"
submit_data = {
"event": event,
"body": body
}
try:
self._request("POST", submit_url, json=submit_data)
print(f"--- Review {review_id} submitted ---")
except requests.exceptions.HTTPError as e:
if "already" in e.response.text.lower() or "stay pending" in e.response.text.lower():
print(f"Review {review_id} could not be submitted further: {e.response.text}")
else:
raise
return review
def list_reviews(self, repo_full_name: str, pr_number: int):
owner, repo = repo_full_name.split("/")
url = f"repos/{owner}/{repo}/pulls/{pr_number}/reviews"
response = self._request("GET", url)
return response.json()
def approve_requested_reviews(self, repo_full_name: str, pr_number: int):
print(f"--- Checking for REQUEST_REVIEW state in {repo_full_name} PR #{pr_number} ---")
reviews = self.list_reviews(repo_full_name, pr_number)
requested_reviews = [r for r in reviews if r["state"] == "REQUEST_REVIEW"]
if not requested_reviews:
print(f"No reviews in REQUEST_REVIEW state found for {repo_full_name} PR #{pr_number}")
return
admin_token = self.headers["Authorization"].split(" ")[1]
for r in requested_reviews:
reviewer_username = r["user"]["login"]
print(f"Reacting on REQUEST_REVIEW for user {reviewer_username} by approving...")
reviewer_client = GiteaAPIClient(base_url=self.base_url, token=admin_token, sudo=reviewer_username)
time.sleep(1) # give a chance to avoid possible concurrency issues with reviews request/approval
reviewer_client.create_review(repo_full_name, pr_number, event="APPROVED", body="Approving requested review")
def restart_service(self, service_name: str):
print(f"--- Restarting service: {service_name} ---")
try:
# Assumes podman-compose.yml is in the parent directory of tests/lib
subprocess.run(["podman-compose", "restart", service_name], check=True, cwd=os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)))
print(f"Service {service_name} restarted successfully.")
except subprocess.CalledProcessError as e:
print(f"Error restarting service {service_name}: {e}")
raise

View File

@@ -17,7 +17,7 @@ from tests.lib.common_test_utils import (
def test_pr_workflow_succeeded(gitea_env, mock_build_result): def test_pr_workflow_succeeded(gitea_env, mock_build_result):
"""End-to-end test for a successful PR workflow.""" """End-to-end test for a successful PR workflow."""
diff = "diff --git a/test.txt b/test.txt\nnew file mode 100644\nindex 0000000..e69de29\n" diff = "diff --git a/test.txt b/test.txt\nnew file mode 100644\nindex 0000000..e69de29\n"
pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test PR - should succeed", False) pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test PR - should succeed")
initial_pr_number = pr["number"] initial_pr_number = pr["number"]
compose_dir = Path(__file__).parent.parent compose_dir = Path(__file__).parent.parent
@@ -87,7 +87,7 @@ def test_pr_workflow_succeeded(gitea_env, mock_build_result):
def test_pr_workflow_failed(gitea_env, mock_build_result): def test_pr_workflow_failed(gitea_env, mock_build_result):
"""End-to-end test for a failed PR workflow.""" """End-to-end test for a failed PR workflow."""
diff = "diff --git a/another_test.txt b/another_test.txt\nnew file mode 100644\nindex 0000000..e69de29\n" diff = "diff --git a/another_test.txt b/another_test.txt\nnew file mode 100644\nindex 0000000..e69de29\n"
pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test PR - should fail", False) pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test PR - should fail")
initial_pr_number = pr["number"] initial_pr_number = pr["number"]
compose_dir = Path(__file__).parent.parent compose_dir = Path(__file__).parent.parent

View File

@@ -1,97 +0,0 @@
import pytest
import re
import time
from pathlib import Path
from tests.lib.common_test_utils import (
GiteaAPIClient,
)
# =============================================================================
# TEST CASES
# =============================================================================
@pytest.mark.t001
@pytest.mark.xfail(reason="review pending label is not applied")
def test_001_project_pr_labels(label_env, staging_bot_client):
"""
Test scenario:
1. Setup custom workflow.config with Labels: { "StagingAuto": "staging/Backlog", "ReviewPending": "review/Pending" }.
2. Create a package PR in 'label-test' branch.
3. Make sure the workflow-pr service created related project PR in 'label-test' branch.
4. Wait for the project PR to have the label "staging/Backlog".
5. Post approval from autogits_obs_staging_bot.
6. Check that the project PR gets the label "review/Pending".
"""
gitea_env, test_full_repo_name, branch_name = label_env
# 1. Create a package PR
diff = """diff --git a/label_test_fixture.txt b/label_test_fixture.txt
new file mode 100644
index 0000000..e69de29
"""
print(f"--- Creating package PR in pool/pkgA on branch {branch_name} ---")
package_pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test Labels Fixture", False, base_branch=branch_name)
package_pr_number = package_pr["number"]
print(f"Created package PR pool/pkgA#{package_pr_number}")
# 2. Make sure the workflow-pr service created related project PR
project_pr_number = None
print(f"Polling pool/pkgA PR #{package_pr_number} timeline for forwarded PR event...")
for _ in range(40):
time.sleep(1)
timeline_events = gitea_env.get_timeline_events("pool/pkgA", package_pr_number)
for event in timeline_events:
if event.get("type") == "pull_ref":
if not (ref_issue := event.get("ref_issue")):
continue
url_to_check = ref_issue.get("html_url", "")
match = re.search(r"products/SLFO/pulls/(\d+)", url_to_check)
if match:
project_pr_number = int(match.group(1))
break
if project_pr_number:
break
assert project_pr_number is not None, "Workflow bot did not create a project PR."
print(f"Found project PR: products/SLFO#{project_pr_number}")
# 3. Wait for the project PR to have the label "staging/Backlog"
print(f"Checking for 'staging/Backlog' label on project PR products/SLFO#{project_pr_number}...")
backlog_label_found = False
expected_backlog_label = "staging/Backlog"
for _ in range(20):
project_pr_details = gitea_env.get_pr_details("products/SLFO", project_pr_number)
labels = project_pr_details.get("labels", [])
label_names = [l["name"] for l in labels]
if expected_backlog_label in label_names:
backlog_label_found = True
break
time.sleep(1)
assert backlog_label_found, f"Project PR products/SLFO#{project_pr_number} does not have the expected label '{expected_backlog_label}'."
print(f"Project PR products/SLFO#{project_pr_number} has the expected label '{expected_backlog_label}'.")
# 4. Post approval from autogits_obs_staging_bot
print(f"--- Posting approval from autogits_obs_staging_bot on project PR products/SLFO#{project_pr_number} ---")
staging_bot_client.create_review("products/SLFO", project_pr_number, event="APPROVED", body="Staging OK")
# 5. Check that the project PR has the label "review/Pending"
print(f"Checking for 'review/Pending' label on project PR products/SLFO#{project_pr_number}...")
pending_label_found = False
expected_pending_label = "review/Pending"
for _ in range(20):
project_pr_details = gitea_env.get_pr_details("products/SLFO", project_pr_number)
labels = project_pr_details.get("labels", [])
label_names = [l["name"] for l in labels]
print(f"Current labels: {label_names}")
if expected_pending_label in label_names:
pending_label_found = True
break
time.sleep(1)
assert pending_label_found, f"Project PR products/SLFO#{project_pr_number} does not have the expected label '{expected_pending_label}'."
print(f"Project PR products/SLFO#{project_pr_number} has the expected label '{expected_pending_label}'.")

View File

@@ -1,82 +0,0 @@
import pytest
import re
import time
from pathlib import Path
from tests.lib.common_test_utils import GiteaAPIClient
@pytest.mark.t001
@pytest.mark.xfail(reason="The bot sometimes re-request reviews despite having all the approvals")
def test_001_automerge(automerge_env, test_user_client):
"""
Test scenario:
1. Setup custom workflow.config with mandatory reviewers (+usera, +userb).
2. Create a package PR in 'merge' branch.
3. Make sure the workflow-pr service created related project PR in 'merge' branch.
4. React on 'requested' reviews by approving them.
5. Make sure both PRs are merged automatically by the workflow-pr service.
"""
gitea_env, test_full_repo_name, merge_branch_name = automerge_env
# 1. Create a package PR
diff = """diff --git a/merge_test_fixture.txt b/merge_test_fixture.txt
new file mode 100644
index 0000000..e69de29
"""
print(f"--- Creating package PR in pool/pkgA on branch {merge_branch_name} ---")
package_pr = test_user_client.create_gitea_pr("pool/pkgA", diff, "Test Automerge Fixture", False, base_branch=merge_branch_name)
package_pr_number = package_pr["number"]
print(f"Created package PR pool/pkgA#{package_pr_number}")
# 2. Make sure the workflow-pr service created related project PR
project_pr_number = None
print(f"Polling pool/pkgA PR #{package_pr_number} timeline for forwarded PR event...")
for _ in range(40):
time.sleep(1)
timeline_events = gitea_env.get_timeline_events("pool/pkgA", package_pr_number)
for event in timeline_events:
if event.get("type") == "pull_ref":
if not (ref_issue := event.get("ref_issue")):
continue
url_to_check = ref_issue.get("html_url", "")
match = re.search(r"products/SLFO/pulls/(\d+)", url_to_check)
if match:
project_pr_number = int(match.group(1))
break
if project_pr_number:
break
assert project_pr_number is not None, "Workflow bot did not create a project PR."
print(f"Found project PR: products/SLFO#{project_pr_number}")
# 4. Make sure both PRs are merged automatically by the workflow-pr service
print("Polling for PR merge status and reacting on REQUEST_REVIEW...")
package_merged = False
project_merged = False
for i in range(15): # Poll for up to 15 seconds
# Package PR
if not package_merged:
pkg_details = gitea_env.get_pr_details("pool/pkgA", package_pr_number)
if pkg_details.get("merged"):
package_merged = True
print(f"Package PR pool/pkgA#{package_pr_number} merged.")
else:
gitea_env.approve_requested_reviews("pool/pkgA", package_pr_number)
# Project PR
if not project_merged:
prj_details = gitea_env.get_pr_details("products/SLFO", project_pr_number)
if prj_details.get("merged"):
project_merged = True
print(f"Project PR products/SLFO#{project_pr_number} merged.")
else:
gitea_env.approve_requested_reviews("products/SLFO", project_pr_number)
if package_merged and project_merged:
break
time.sleep(1)
assert package_merged, f"Package PR pool/pkgA#{package_pr_number} was not merged automatically."
assert project_merged, f"Project PR products/SLFO#{project_pr_number} was not merged automatically."
print("Both PRs merged successfully.")

View File

@@ -1,384 +0,0 @@
import pytest
import re
import time
import base64
from pathlib import Path
from tests.lib.common_test_utils import GiteaAPIClient
@pytest.mark.t001
def test_001_review_requests_matching_config(automerge_env, ownerA_client):
"""
Test scenario:
1. The package PR for pkgB is opened by ownerA (who is not a maintainer of pkgB).
2. Check that review request comes to ownerB and ownerBB (package maintainers)
AND usera and userb (from workflow.config).
"""
gitea_env, test_full_repo_name, branch_name = automerge_env
# 1. Create a package PR for pool/pkgB as ownerA
diff = """diff --git a/pkgB_test_001.txt b/pkgB_test_001.txt
new file mode 100644
index 0000000..e69de29
"""
print(f"--- Creating package PR in pool/pkgB on branch {branch_name} as ownerA ---")
package_pr = ownerA_client.create_gitea_pr("pool/pkgB", diff, "Test Review Requests Config", True, base_branch=branch_name)
package_pr_number = package_pr["number"]
print(f"Created package PR pool/pkgB#{package_pr_number}")
# 2. Check that review requests came to ownerB, ownerBB, usera, and userb
print("Checking for review requests from maintainers and workflow.config...")
reviewers_requested = set()
expected_reviewers = {"ownerB", "ownerBB", "usera", "userb"}
for _ in range(30):
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
if expected_reviewers.issubset(reviewers_requested):
break
time.sleep(1)
for reviewer in expected_reviewers:
assert reviewer in reviewers_requested, f"{reviewer} was not requested for review. Requested: {reviewers_requested}"
print(f"Confirmed: {expected_reviewers} were requested for review.")
@pytest.mark.t004
@pytest.mark.xfail(reason="the bot sometimes re-requests review from autogits_obs_staging_bot despite having the approval")
def test_004_maintainer(maintainer_env, ownerA_client):
"""
Test scenario:
1. workflow.config will not have users with '+' sign.
2. The package PR is opened by the package maintainer (ownerA for pkgA).
3. Do not submit any review approval.
4. Check that both PRs are automatically merged anyway.
"""
gitea_env, test_full_repo_name, branch_name = maintainer_env
# 0. Smoke test ownerA_client
print(f"--- Smoke testing ownerA_client ---")
ownerA_client._request("GET", "users/admin")
print(f"ownerA_client smoke test passed")
# 0.1 Verify all users from config exist
print("--- Verifying all users from config exist ---")
import json
wf_file = gitea_env.get_file_info("products", "SLFO", "workflow.config", branch=branch_name)
wf = json.loads(base64.b64decode(wf_file["content"]).decode("utf-8"))
mt_file = gitea_env.get_file_info("products", "SLFO", "_maintainership.json", branch=branch_name)
mt = json.loads(base64.b64decode(mt_file["content"]).decode("utf-8"))
expected_users = set()
for r in wf.get("Reviewers", []):
username = r.lstrip("+-")
if username and username not in ["autogits_obs_staging_bot", "workflow-pr"]:
expected_users.add(username)
for pkg_users in mt.values():
for username in pkg_users:
expected_users.add(username)
for username in expected_users:
gitea_env._request("GET", f"users/{username}")
print(f"Verified user exists: {username}")
# 1. Create a package PR as ownerA
diff = """diff --git a/maintainer_test_fixture.txt b/maintainer_test_fixture.txt
new file mode 100644
index 0000000..e69de29
"""
print(f"--- Creating package PR in pool/pkgA on branch {branch_name} as ownerA ---")
package_pr = ownerA_client.create_gitea_pr("pool/pkgA", diff, "Test Maintainer Merge", True, base_branch=branch_name)
package_pr_number = package_pr["number"]
print(f"Created package PR pool/pkgA#{package_pr_number}")
# 2. Make sure the workflow-pr service created related project PR
project_pr_number = None
print(f"Polling pool/pkgA PR #{package_pr_number} timeline for forwarded PR event...")
for _ in range(40):
time.sleep(1)
timeline_events = gitea_env.get_timeline_events("pool/pkgA", package_pr_number)
for event in timeline_events:
if event.get("type") == "pull_ref":
if not (ref_issue := event.get("ref_issue")):
continue
url_to_check = ref_issue.get("html_url", "")
match = re.search(r"products/SLFO/pulls/(\d+)", url_to_check)
if match:
project_pr_number = int(match.group(1))
break
if project_pr_number:
break
assert project_pr_number is not None, "Workflow bot did not create a project PR."
print(f"Found project PR: products/SLFO#{project_pr_number}")
# 3. Make sure both PRs are merged automatically WITHOUT manual approvals
print("Polling for PR merge status (only bot approval allowed)...")
package_merged = False
project_merged = False
for i in range(15): # Poll for up to 15 seconds
# Package PR
if not package_merged:
pkg_details = gitea_env.get_pr_details("pool/pkgA", package_pr_number)
if pkg_details.get("merged"):
package_merged = True
print(f"Package PR pool/pkgA#{package_pr_number} merged.")
else:
# Approve ONLY bot if requested
reviews = gitea_env.list_reviews("pool/pkgA", package_pr_number)
if any(r["state"] == "REQUEST_REVIEW" and r["user"]["login"] == "autogits_obs_staging_bot" for r in reviews):
gitea_env.approve_requested_reviews("pool/pkgA", package_pr_number)
# Project PR
if not project_merged:
prj_details = gitea_env.get_pr_details("products/SLFO", project_pr_number)
if prj_details.get("merged"):
project_merged = True
print(f"Project PR products/SLFO#{project_pr_number} merged.")
else:
# Approve ONLY bot if requested
reviews = gitea_env.list_reviews("products/SLFO", project_pr_number)
if any(r["state"] == "REQUEST_REVIEW" and r["user"]["login"] == "autogits_obs_staging_bot" for r in reviews):
gitea_env.approve_requested_reviews("products/SLFO", project_pr_number)
if package_merged and project_merged:
break
time.sleep(1)
assert package_merged, f"Package PR pool/pkgA#{package_pr_number} was not merged automatically."
assert project_merged, f"Project PR products/SLFO#{project_pr_number} was not merged automatically."
print("Both PRs merged successfully by maintainer rule.")
@pytest.mark.t005
# @pytest.mark.xfail(reason="TBD troubleshoot")
def test_005_any_maintainer_approval_sufficient(maintainer_env, ownerA_client, ownerBB_client):
"""
Test scenario:
1. The package PR for pkgB is opened by ownerA (who is not a maintainer of pkgB).
2. Check that review request comes to both ownerB and ownerBB.
3. ownerB doesn't leave review.
4. check that review from ownerBB was enough to get both PRs merged.
"""
gitea_env, test_full_repo_name, branch_name = maintainer_env
# 1. Create a package PR for pool/pkgB as ownerA
diff = """diff --git a/pkgB_test_fixture.txt b/pkgB_test_fixture.txt
new file mode 100644
index 0000000..e69de29
"""
print(f"--- Creating package PR in pool/pkgB on branch {branch_name} as ownerA ---")
package_pr = ownerA_client.create_gitea_pr("pool/pkgB", diff, "Test Single Maintainer Merge", True, base_branch=branch_name)
package_pr_number = package_pr["number"]
print(f"Created package PR pool/pkgB#{package_pr_number}")
# 2. Make sure the workflow-pr service created related project PR
project_pr_number = None
print(f"Polling pool/pkgB PR #{package_pr_number} timeline for forwarded PR event...")
for _ in range(40):
time.sleep(1)
timeline_events = gitea_env.get_timeline_events("pool/pkgB", package_pr_number)
for event in timeline_events:
if event.get("type") == "pull_ref":
if not (ref_issue := event.get("ref_issue")):
continue
url_to_check = ref_issue.get("html_url", "")
match = re.search(r"products/SLFO/pulls/(\d+)", url_to_check)
if match:
project_pr_number = int(match.group(1))
break
if project_pr_number:
break
assert project_pr_number is not None, "Workflow bot did not create a project PR."
print(f"Found project PR: products/SLFO#{project_pr_number}")
# 3. Check that review requests came to ownerB and ownerBB
print("Checking for review requests from ownerB and ownerBB...")
reviewers_requested = set()
for _ in range(20):
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
if "ownerB" in reviewers_requested and "ownerBB" in reviewers_requested:
break
time.sleep(1)
assert "ownerB" in reviewers_requested, f"ownerB was not requested for review. Requested: {reviewers_requested}"
assert "ownerBB" in reviewers_requested, f"ownerBB was not requested for review. Requested: {reviewers_requested}"
print(f"Confirmed: ownerB and ownerBB were requested for review.")
# 4. ownerBB leaves review, ownerB does not.
print("ownerBB approving the PR...")
ownerBB_client.create_review("pool/pkgB", package_pr_number, event="APPROVED", body="Approval from ownerBB")
# 5. Check that both PRs are merged automatically
print("Polling for PR merge status (only bot approval allowed for project PR)...")
package_merged = False
project_merged = False
for i in range(15): # Poll for up to 15 seconds
# Package PR
if not package_merged:
pkg_details = gitea_env.get_pr_details("pool/pkgB", package_pr_number)
if pkg_details.get("merged"):
package_merged = True
print(f"Package PR pool/pkgB#{package_pr_number} merged.")
# Project PR
if not project_merged:
prj_details = gitea_env.get_pr_details("products/SLFO", project_pr_number)
if prj_details.get("merged"):
project_merged = True
print(f"Project PR products/SLFO#{project_pr_number} merged.")
else:
# Approve ONLY bot if requested
reviews = gitea_env.list_reviews("products/SLFO", project_pr_number)
if any(r["state"] == "REQUEST_REVIEW" and r["user"]["login"] == "autogits_obs_staging_bot" for r in reviews):
gitea_env.approve_requested_reviews("products/SLFO", project_pr_number)
if package_merged and project_merged:
break
time.sleep(1)
assert package_merged, f"Package PR pool/pkgB#{package_pr_number} was not merged automatically."
assert project_merged, f"Project PR products/SLFO#{project_pr_number} was not merged automatically."
print("Both PRs merged successfully with only one maintainer approval.")
@pytest.mark.t006
@pytest.mark.xfail(reason="tbd flacky in ci")
def test_006_maintainer_rejection_removes_other_requests(maintainer_env, ownerA_client, ownerBB_client):
"""
Test scenario:
1. The package PR for pkgB is opened by ownerA (who is not a maintainer of pkgB).
2. Check that review request comes to both ownerB and ownerBB.
3. ownerBB rejects the PR (REQUEST_CHANGES).
4. Check that review request for ownerB is removed.
"""
gitea_env, test_full_repo_name, branch_name = maintainer_env
# 1. Create a package PR for pool/pkgB as ownerA
diff = """diff --git a/pkgB_rejection_test.txt b/pkgB_rejection_test.txt
new file mode 100644
index 0000000..e69de29
"""
print(f"--- Creating package PR in pool/pkgB on branch {branch_name} as ownerA ---")
package_pr = ownerA_client.create_gitea_pr("pool/pkgB", diff, "Test Maintainer Rejection", True, base_branch=branch_name)
package_pr_number = package_pr["number"]
print(f"Created package PR pool/pkgB#{package_pr_number}")
# 2. Check that review requests came to ownerB and ownerBB
print("Checking for review requests from ownerB and ownerBB...")
for _ in range(20):
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
if "ownerB" in reviewers_requested and "ownerBB" in reviewers_requested:
break
time.sleep(1)
else:
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
pytest.fail(f"ownerB and ownerBB were not both requested. Got: {reviewers_requested}")
# 3. ownerBB rejects the PR
print("ownerBB rejecting the PR...")
ownerBB_client.create_review("pool/pkgB", package_pr_number, event="REQUEST_CHANGES", body="Rejecting from ownerBB")
# 4. Check that review request for ownerB is removed
print("Checking if ownerB's review request is removed...")
for _ in range(20):
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
if "ownerB" not in reviewers_requested:
print("Confirmed: ownerB's review request was removed.")
break
time.sleep(1)
else:
pytest.fail("ownerB's review request was not removed after ownerBB rejection.")
@pytest.mark.t007
@pytest.mark.xfail(reason="TBD troubleshoot")
def test_007_review_required_needs_all_approvals(review_required_env, ownerA_client, ownerBB_client):
"""
Test scenario:
1. it uses new fixture with "ReviewRequired = true" in the workflow.config.
2. Package PR for pkgB opened by ownerA.
3. Check review request comes to both ownerB and ownerBB.
4. ownerBB approves.
5. make sure that review is not merged automatically and the request for ownerB is not removed.
"""
gitea_env, test_full_repo_name, branch_name = review_required_env
# 0. Smoke test ownerA_client
print(f"--- Smoke testing ownerA_client ---")
ownerA_client._request("GET", "users/admin")
print(f"ownerA_client smoke test passed")
# 1. Create a package PR for pool/pkgB as ownerA
diff = """diff --git a/pkgB_review_required_test.txt b/pkgB_review_required_test.txt
new file mode 100644
index 0000000..e69de29
"""
print(f"--- Creating package PR in pool/pkgB on branch {branch_name} as ownerA ---")
package_pr = ownerA_client.create_gitea_pr("pool/pkgB", diff, "Test Review Required", True, base_branch=branch_name)
package_pr_number = package_pr["number"]
print(f"Created package PR pool/pkgB#{package_pr_number}")
# 2. Make sure the workflow-pr service created related project PR
project_pr_number = None
print(f"Polling pool/pkgB PR #{package_pr_number} timeline for forwarded PR event...")
for _ in range(40):
time.sleep(1)
timeline_events = gitea_env.get_timeline_events("pool/pkgB", package_pr_number)
for event in timeline_events:
if event.get("type") == "pull_ref":
if not (ref_issue := event.get("ref_issue")):
continue
url_to_check = ref_issue.get("html_url", "")
match = re.search(r"products/SLFO/pulls/(\d+)", url_to_check)
if match:
project_pr_number = int(match.group(1))
break
if project_pr_number:
break
assert project_pr_number is not None, "Workflow bot did not create a project PR."
print(f"Found project PR: products/SLFO#{project_pr_number}")
# 3. Check that review requests came to ownerB and ownerBB
print("Checking for review requests from ownerB and ownerBB...")
for _ in range(20):
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
if "ownerB" in reviewers_requested and "ownerBB" in reviewers_requested:
break
time.sleep(1)
else:
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
pytest.fail(f"ownerB and ownerBB were not both requested. Got: {reviewers_requested}")
# 4. ownerBB leaves review, ownerB does not.
print("ownerBB approving the PR...")
ownerBB_client.create_review("pool/pkgB", package_pr_number, event="APPROVED", body="Approval from ownerBB")
# 5. Check that the PR is NOT merged automatically and ownerB request remains
print("Waiting to ensure PR is NOT merged and ownerB request remains...")
for i in range(10):
pkg_details = gitea_env.get_pr_details("pool/pkgB", package_pr_number)
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
review_states = [(r["user"]["login"], r["state"]) for r in reviews]
print(f"Attempt {i+1}: Merged={pkg_details.get('merged')}, Reviews={review_states}")
time.sleep(2)
pkg_details = gitea_env.get_pr_details("pool/pkgB", package_pr_number)
assert not pkg_details.get("merged"), "Package PR was merged automatically but it should NOT have been (ReviewRequired=true)."
reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number)
reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"}
assert "ownerB" in reviewers_requested, f"ownerB's review request was removed, but it should have remained. All reviews: {[(r['user']['login'], r['state']) for r in reviews]}"
print("Confirmed: PR not merged and ownerB review request remains as expected.")

View File

@@ -18,12 +18,11 @@ pytest.initial_pr_number = None
pytest.forwarded_pr_number = None pytest.forwarded_pr_number = None
@pytest.mark.t001
@pytest.mark.dependency() @pytest.mark.dependency()
def test_001_project_pr(gitea_env): def test_001_project_pr(gitea_env):
"""Forwarded PR correct title""" """Forwarded PR correct title"""
diff = "diff --git a/another_test.txt b/another_test.txt\nnew file mode 100644\nindex 0000000..e69de29\n" diff = "diff --git a/another_test.txt b/another_test.txt\nnew file mode 100644\nindex 0000000..e69de29\n"
pytest.pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test PR", False) pytest.pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test PR")
pytest.initial_pr_number = pytest.pr["number"] pytest.initial_pr_number = pytest.pr["number"]
time.sleep(5) # Give Gitea some time to process the PR and make the timeline available time.sleep(5) # Give Gitea some time to process the PR and make the timeline available
@@ -57,7 +56,6 @@ def test_001_project_pr(gitea_env):
), "Forwarded PR correct title" ), "Forwarded PR correct title"
@pytest.mark.t002
@pytest.mark.dependency(depends=["test_001_project_pr"]) @pytest.mark.dependency(depends=["test_001_project_pr"])
def test_002_updated_project_pr(gitea_env): def test_002_updated_project_pr(gitea_env):
"""Forwarded PR head is updated""" """Forwarded PR head is updated"""
@@ -78,7 +76,6 @@ def test_002_updated_project_pr(gitea_env):
assert sha_changed, "Forwarded PR has sha updated" assert sha_changed, "Forwarded PR has sha updated"
@pytest.mark.t003
@pytest.mark.dependency(depends=["test_001_project_pr"]) @pytest.mark.dependency(depends=["test_001_project_pr"])
def test_003_wip(gitea_env): def test_003_wip(gitea_env):
"""WIP flag set for PR""" """WIP flag set for PR"""
@@ -118,209 +115,3 @@ def test_003_wip(gitea_env):
wip_flag_removed = True wip_flag_removed = True
break break
assert wip_flag_removed, "WIP flag was not removed from the forwarded PR." assert wip_flag_removed, "WIP flag was not removed from the forwarded PR."
@pytest.mark.t005
@pytest.mark.xfail(reason="works only in ibs_state branch?")
@pytest.mark.dependency()
def test_005_NoProjectGitPR_edits_disabled(no_project_git_pr_env, test_user_client):
"""
Reworked test: Sets workflow.config with NoProjectGitPR: true and creates a Package PR.
Verifies that no Project PR is created, then manually creates one and checks for bot warning.
"""
gitea_env, test_full_repo_name, dev_branch_name = no_project_git_pr_env
# 1. Create a Package PR (without "Allow edits from maintainers" enabled)
initial_diff = """diff --git a/first_file.txt b/first_file.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/first_file.txt
@@ -0,0 +1 @@
+Initial content
"""
package_pr = test_user_client.create_gitea_pr("pool/pkgA", initial_diff, "Test PR for No Project PR, No Edits", False, base_branch=dev_branch_name)
package_pr_number = package_pr["number"]
print(f"Created Package PR #{package_pr_number}")
# 2. Verify that the workflow-pr bot did not create a Project PR
project_pr_created = False
for i in range(10): # Poll for some time
time.sleep(2)
timeline_events = gitea_env.get_timeline_events("pool/pkgA", package_pr_number)
for event in timeline_events:
if event.get("type") == "pull_ref":
if not (ref_issue := event.get("ref_issue")):
continue
url_to_check = ref_issue.get("html_url", "")
match = re.search(r"products/SLFO/pulls/(\d+)", url_to_check)
if match:
project_pr_created = True
break
if project_pr_created:
break
assert not project_pr_created, "Workflow bot unexpectedly created a Project PR in products/SLFO."
print("Verification complete: No Project PR was created by the bot.")
# 3. Manually create the Project PR
pkgA_main_sha = gitea_env._request("GET", f"repos/pool/pkgA/branches/{dev_branch_name}").json()["commit"]["id"]
package_pr_details = gitea_env.get_pr_details("pool/pkgA", package_pr_number)
pkgA_pr_head_sha = package_pr_details["head"]["sha"]
project_pr_title = "Forwarded PRs: pkgA (Manual)"
project_pr_body = f"Manual Project PR for NoProjectGitPR. \nPR: pool/pkgA!{package_pr_number}"
project_pr_diff = f"""diff --git a/pkgA b/pkgA
index {pkgA_main_sha[:7]}..{pkgA_pr_head_sha[:7]} 160000
--- a/pkgA
+++ b/pkgA
@@ -1 +1 @@
-Subproject commit {pkgA_main_sha}
+Subproject commit {pkgA_pr_head_sha}
"""
manual_project_pr = test_user_client.create_gitea_pr(test_full_repo_name, project_pr_diff, project_pr_title, True, base_branch=dev_branch_name, body=project_pr_body)
manual_project_pr_number = manual_project_pr["number"]
# Verify and set allow_maintainer_edit to False
test_user_client.update_gitea_pr_properties(test_full_repo_name, manual_project_pr_number, allow_maintainer_edit=False)
# Verify that allow_maintainer_edit is now disabled
updated_pr = gitea_env.get_pr_details(test_full_repo_name, manual_project_pr_number)
assert updated_pr.get("allow_maintainer_edit") is False, "Expected allow_maintainer_edit to be False after update"
print(f"Manually created Project PR #{manual_project_pr_number} in {test_full_repo_name}")
# 4. Trigger an update on the Package PR to prompt the bot to react to the manual Project PR
new_diff_content = """diff --git a/trigger_bot.txt b/trigger_bot.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/trigger_bot.txt
@@ -0,0 +1 @@
+Trigger content
"""
test_user_client.modify_gitea_pr("pool/pkgA", package_pr_number, new_diff_content, "Trigger bot update")
# 5. Verify that the bot adds a warning comment because it cannot update the manual PR (edits disabled)
warning_found = False
print(f"Polling Package PR #{package_pr_number} for warning comment...")
for _ in range(20):
time.sleep(3)
comments = gitea_env.get_comments("pool/pkgA", package_pr_number)
for comment in comments:
# According to test-plan.md, the warning explains that it cannot update the PR.
if "cannot update" in comment.get("body", "").lower():
warning_found = True
print(f"Warning comment found: {comment.get('body')}")
break
if warning_found:
break
# assert warning_found, "Bot did not post the expected warning comment on the Package PR."
# print("Verification complete: Bot posted a warning comment as expected.")
@pytest.mark.t006
@pytest.mark.xfail(reason="works only in ibs_state branch?")
@pytest.mark.dependency()
def test_006_NoProjectGitPR_edits_enabled(no_project_git_pr_env, test_user_client):
"""
Verify that no project PR is created when "NoProjectGitPR" is true
and "Allow edits from maintainers" is enabled, using a dev branch.
"""
gitea_env, test_full_repo_name, dev_branch_name = no_project_git_pr_env
# 2. Create a Package PR with "Allow edits from maintainers" enabled
diff = """diff --git a/new_feature.txt b/new_feature.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/new_feature.txt
@@ -0,0 +1 @@
+New feature content
"""
package_pr = test_user_client.create_gitea_pr("pool/pkgA", diff, "Test PR for NoProjectGitPR", False, base_branch=dev_branch_name)
package_pr_number = package_pr["number"]
# Enable "Allow edits from maintainers"
test_user_client.update_gitea_pr_properties("pool/pkgA", package_pr_number, allow_maintainer_edit=True)
print(f"Created Package PR #{package_pr_number} and enabled 'Allow edits from maintainers'.")
# Get SHAs needed for the manual Project PR diff
pkgA_main_sha = gitea_env._request("GET", f"repos/pool/pkgA/branches/{dev_branch_name}").json()["commit"]["id"]
package_pr_details = gitea_env.get_pr_details("pool/pkgA", package_pr_number)
pkgA_pr_head_sha = package_pr_details["head"]["sha"]
# 3. Assert that the workflow-pr bot did not create a Project PR in the products/SLFO repository
project_pr_created = False
for i in range(20): # Poll for a reasonable time
time.sleep(2) # Wait a bit longer to be sure
timeline_events = gitea_env.get_timeline_events("pool/pkgA", package_pr_number)
for event in timeline_events:
if event.get("type") == "pull_ref":
if not (ref_issue := event.get("ref_issue")):
continue
url_to_check = ref_issue.get("html_url", "")
# Regex now searches for products/SLFO/pulls/(\d+)
match = re.search(r"products/SLFO/pulls/(\d+)", url_to_check)
if match:
project_pr_created = True
break
if project_pr_created:
break
assert not project_pr_created, "Workflow bot unexpectedly created a Project PR in products/SLFO."
print("Verification complete: No Project PR was created in products/SLFO as expected.")
# 1. Create that Project PR from the test code.
project_pr_title = "Forwarded PRs: pkgA"
project_pr_body = f"Test Project PR for NoProjectGitPR. \nPR: pool/pkgA!{package_pr_number}"
project_pr_diff = f"""diff --git a/pkgA b/pkgA
index {pkgA_main_sha[:7]}..{pkgA_pr_head_sha[:7]} 160000
--- a/pkgA
+++ b/pkgA
@@ -1 +1 @@
-Subproject commit {pkgA_main_sha}
+Subproject commit {pkgA_pr_head_sha}
"""
manual_project_pr = test_user_client.create_gitea_pr(test_full_repo_name, project_pr_diff, project_pr_title, True, base_branch=dev_branch_name, body=project_pr_body)
manual_project_pr_number = manual_project_pr["number"]
# Explicitly ensure allow_maintainer_edit is True (it should be by default now, but just in case)
test_user_client.update_gitea_pr_properties(test_full_repo_name, manual_project_pr_number, allow_maintainer_edit=True)
print(f"Manually created Project PR #{manual_project_pr_number} in {test_full_repo_name}")
time.sleep(5) # Give the bot time to potentially react or for the PR to settle
# Get initial SHA of the manually created Project PR
initial_project_pr_details = gitea_env.get_pr_details(test_full_repo_name, manual_project_pr_number)
initial_head_sha = initial_project_pr_details["head"]["sha"]
print(f"Manually created Project PR initial head SHA: {initial_head_sha}")
# 2. Add new commit to the package PR.
new_diff_content = """diff --git a/another_file.txt b/another_file.txt
new file mode 100644
index 0000000..f587a12
--- /dev/null
+++ b/another_file.txt
@@ -0,0 +1 @@
+Another file content
"""
test_user_client.modify_gitea_pr("pool/pkgA", package_pr_number, new_diff_content, "Add another file to Package PR")
print(f"Added new commit to Package PR #{package_pr_number}.")
time.sleep(5) # Give the bot time to react
# 3. Make sure the project PR is properly updated by the bot
project_pr_updated = False
print(f"Polling manually created Project PR #{manual_project_pr_number} for update...")
for _ in range(20): # Poll for a reasonable time
time.sleep(2) # Wait a bit longer to be sure
current_project_pr_details = gitea_env.get_pr_details(test_full_repo_name, manual_project_pr_number)
current_head_sha = current_project_pr_details["head"]["sha"]
if current_head_sha != initial_head_sha:
project_pr_updated = True
print(f"Manually created Project PR updated. New head SHA: {current_head_sha}")
break
assert project_pr_updated, "Manually created Project PR was not updated by the bot."
print("Verification complete: Manually created Project PR was updated by the bot as expected.")

View File

@@ -6,7 +6,7 @@ COPY integration/rabbitmq-config/certs/cert.pem /usr/share/pki/trust/anchors/git
RUN update-ca-certificates RUN update-ca-certificates
# Install git and ssh # Install git and ssh
RUN zypper -n in git-core openssh-clients binutils git-lfs || (tail -n 1000 /var/log/zypper.log; exit 1) RUN zypper -n in git-core openssh-clients binutils
# Copy the pre-built binary into the container # Copy the pre-built binary into the container
COPY workflow-pr/workflow-pr /usr/local/bin/workflow-pr COPY workflow-pr/workflow-pr /usr/local/bin/workflow-pr

View File

@@ -2,14 +2,14 @@
FROM registry.suse.com/bci/bci-base:15.7 FROM registry.suse.com/bci/bci-base:15.7
# Add the custom CA to the trust store # Add the custom CA to the trust store
COPY integration/rabbitmq-config/certs/cert.pem /usr/share/pki/trust/anchors/gitea-rabbitmq-ca.crt COPY rabbitmq-config/certs/cert.pem /usr/share/pki/trust/anchors/gitea-rabbitmq-ca.crt
RUN update-ca-certificates RUN update-ca-certificates
RUN zypper ar -f http://download.opensuse.org/repositories/devel:/Factory:/git-workflow/15.7/devel:Factory:git-workflow.repo RUN zypper ar -f http://download.opensuse.org/repositories/devel:/Factory:/git-workflow/15.7/devel:Factory:git-workflow.repo
RUN zypper --gpg-auto-import-keys ref RUN zypper --gpg-auto-import-keys ref
# Install git and ssh # Install git and ssh
RUN zypper -n in git-core openssh-clients autogits-workflow-pr binutils git-lfs || ( tail -n 1000 /var/log/zypper.log; exit 1 ) RUN zypper -n in git-core openssh-clients autogits-workflow-pr binutils
COPY integration/workflow-pr/entrypoint.sh /usr/local/bin/entrypoint.sh COPY integration/workflow-pr/entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +4755 /usr/local/bin/entrypoint.sh RUN chmod +4755 /usr/local/bin/entrypoint.sh

View File

@@ -1,8 +1,3 @@
[ [
"products/SLFO#main", "products/SLFO#main"
"products/SLFO#dev",
"products/SLFO#merge",
"products/SLFO#maintainer-merge",
"products/SLFO#review-required",
"products/SLFO#label-test"
] ]

View File

@@ -1,4 +0,0 @@
[Unit]
Description=Autogits Workflow Direct instances
Documentation=https://src.opensuse.org/git-workflow/autogits

View File

@@ -5,7 +5,7 @@ After=network-online.target
[Service] [Service]
Type=exec Type=exec
ExecStart=/usr/bin/workflow-direct ExecStart=/usr/bin/workflow-direct
EnvironmentFile=/etc/default/%i/workflow-direct.env EnvironmentFile=-/etc/default/%i/workflow-direct.env
#DynamicUser=yes #DynamicUser=yes
NoNewPrivileges=yes NoNewPrivileges=yes
ProtectSystem=strict ProtectSystem=strict
@@ -20,5 +20,4 @@ PrivateTmp=yes
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
WantedBy=workflow-direct.target

View File

@@ -1,5 +0,0 @@
[Unit]
Description=Autogits Workflow PR instances
Documentation=https://src.opensuse.org/git-workflow/autogits

View File

@@ -5,7 +5,7 @@ After=network-online.target
[Service] [Service]
Type=exec Type=exec
ExecStart=/usr/bin/workflow-pr ExecStart=/usr/bin/workflow-pr
EnvironmentFile=/etc/default/%i/workflow-pr.env EnvironmentFile=-/etc/default/%i/workflow-pr.env
#DynamicUser=yes #DynamicUser=yes
NoNewPrivileges=yes NoNewPrivileges=yes
ProtectSystem=strict ProtectSystem=strict
@@ -20,5 +20,4 @@ PrivateTmp=yes
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
WantedBy=workflow-pr.target

View File

@@ -37,10 +37,8 @@ Main Tasks
| ManualMergeOnly | true | Both PackageGit PR and ProjectGit PR are merged upon an allowed package maintainer or project maintainer commenting “merge ok” in the PackageGit PR. | | ManualMergeOnly | true | Both PackageGit PR and ProjectGit PR are merged upon an allowed package maintainer or project maintainer commenting “merge ok” in the PackageGit PR. |
| ManualMergeOnly and ManualMergeProject | false | Both ProjectGit and PackageGit PRs are merged as soon as all reviews are completed in both PrjGit and PkgGit PRs. | | ManualMergeOnly and ManualMergeProject | false | Both ProjectGit and PackageGit PRs are merged as soon as all reviews are completed in both PrjGit and PkgGit PRs. |
Project specific config file Config file
---------------------------- -----------
This is the ProjectGit config file. For runtime config file, see bottom.
* Filename: `workflow.config` * Filename: `workflow.config`
* Location: ProjectGit * Location: ProjectGit
@@ -96,6 +94,19 @@ Package Deletion Requests
If you wish to re-add a package, create a new PrjGit PR which adds again the submodule on the branch that has the "-removed" suffix. The bot will automatically remove this suffix from the project branch in the pool. If you wish to re-add a package, create a new PrjGit PR which adds again the submodule on the branch that has the "-removed" suffix. The bot will automatically remove this suffix from the project branch in the pool.
Merge Modes
-----------
| Merge Mode | Description
|------------|--------------------------------------------------------------------------------
| ff-only | Only allow --ff-only merges in the package branch. This is best suited for
| | devel projects and openSUSE Tumbleweed development, where history should be linear
| replace | Merge is done via `-X theirs` strategy and old files are removed in the merge.
| | This works well for downstream codestreams, like Leap, that would update their branch
| | using latest version.
| devel | No merge, just set the project branch to PR HEAD. This is suitable for downstream
| | projects like Leap during development cycle, where keeping maintenance history is not important
Labels Labels
------ ------
@@ -158,44 +169,8 @@ NOTE: Project Maintainers have these permissions automatically.
Server configuration Server configuration
-------------------------- --------------------------
The configuration file is a JSON file that consists of a list of project git locations **Configuration file:**
that are then consulted for their `workflow.config` config files.
```
[]ProjectGit = {
"org" | "org/repo" | "org/repo#branch"
}
default repo = _ObsPrj
default branch = as specified in Gitea
```
For example,
```
[ "org", "openSUSE/Leap", "openSUSE/Leap#16.0" ]
```
Are all valid entries. These are then resolved to,
* For `org`, it's assumed that default repository of `_ObsPrj` in `org` organization and using Gitea's default branch
* For `openSUSE/Leap`, the repository "Leap" using Gitea's default branch in `openSUSE` organization.
* For `openSUSE/Leap#16.0`, the repository "Leap" with branch "16.0" in `openSUSE` organization.
For each of these project gits, `workflow.config` is read.
**Runtime Options**
| Option | Default | Environmental Default | Notes |
|---------------|----------------------------|-----------------------|------------------------------------|
| git-author | AutoGits PR Review Bot | AUTOGITS_GIT_AUTHOR | Name of author for bot created commits |
| git-email | noone@suse.de | AUTOGITS_GIT_EMAIL | Email for the bot created commits |
| config | | AUTOGITS_CONFIG | Path to above config file |
| gitea-url | https://src.opensuse.org | AUTOGITS_GITEA_URL | Gitea's URL instance |
| rabbit-url | amqps://rabbit.opensuse.org| AUTOGITS_RABBIT_URL | RabbitMQ's URL instance |
| debug | false | AUTOGITS_DEBUG | Extra logging |
| check-on-start| false | AUTOGITS_CHECK_ON_START| Whether to check all projects for consistency on start. Can take a while |
| check-interval| 5 | | Consistency check interval |
| repo-path | Uses temp directory | AUTOGITS_REPO_PATH | Path where to store repositories. |
| Field | Type | Notes |
| ----- | ----- | ----- |
| root | Array of string | Format **org/repo\#branch** |

View File

@@ -1,314 +0,0 @@
package main
import (
"fmt"
"slices"
"strings"
"src.opensuse.org/autogits/common"
"src.opensuse.org/autogits/common/gitea-generated/models"
)
func FindSourceRepository(org, repo string) (*models.Repository, error) {
srcRepo, err := Gitea.GetRepository(org, repo)
if err != nil {
return nil, err
}
if srcRepo == nil {
return nil, fmt.Errorf("Source repository not found: %s/%s", org, repo)
}
if srcRepo.Parent == nil {
return nil, fmt.Errorf("Source has no parents: %s/%s", org, repo)
}
return srcRepo, nil
}
func createEmptyBranch(git common.Git, PackageName, Branch string) {
git.GitExecOrPanic(PackageName, "checkout", "--detach")
git.GitExec(PackageName, "branch", "-D", Branch)
git.GitExecOrPanic(PackageName, "checkout", "-f", "--orphan", Branch)
git.GitExecOrPanic(PackageName, "rm", "-rf", ".")
git.GitExecOrPanic(PackageName, "commit", "--allow-empty", "-m", "Initial empty branch")
}
type TimelineInterface interface {
FindPullRequestReferences(org, repo string, idx int64, creator []string) []*models.TimelineComment
}
type Timeline []*models.TimelineComment
func (timeline *Timeline) FindIssuePullRequestRererences(org, repo string, idx int64, creator []string) []*models.TimelineComment {
ret := make([]*models.TimelineComment, 0, 1)
for _, t := range *timeline {
if t.Type == common.TimelineCommentType_PullRequestRef &&
t.RefIssue != nil &&
t.RefIssue.Repository.Owner == org &&
t.RefIssue.Repository.Name == repo &&
(idx == 0 || t.RefIssue.Index == idx) &&
(len(creator) == 0 || slices.Contains(creator, t.User.UserName)) {
ret = append(ret, t)
}
}
return ret
}
type IssueProcessorInterface interface {
IsAddIssue() bool
IsRmIssue() bool
GetTargetBranch() string
}
type IssueProcessor struct {
issue *models.Issue
IssueTimeline Timeline
TargetBranch string
}
func (i *IssueProcessor) GetTargetBranch() string {
const BranchPrefix = "refs/heads/"
branch := i.issue.Ref
if branch, found := strings.CutPrefix(branch, BranchPrefix); found {
return branch
} else {
common.LogDebug("Invalid branch specified:", branch, ". Using default.")
branch = ""
}
return branch
}
func ProcessIssue(issue *models.Issue, configs common.AutogitConfigs) error {
i := &IssueProcessor{issue: issue}
return i.ProcessIssue(configs)
}
func (i *IssueProcessor) IsAddIssue() bool {
if i == nil || i.issue == nil {
return false
}
title := i.issue.Title
return len(title) > 5 && strings.EqualFold(title[0:5], "[ADD]")
}
func (i *IssueProcessor) IsRmIssue() bool {
if i == nil || i.issue == nil {
return false
}
title := i.issue.Title
return len(title) > 4 && strings.EqualFold(title[0:4], "[RM]")
}
func (i *IssueProcessor) ProcessAddIssue(config *common.AutogitConfig) error {
issue := i.issue
org := issue.Repository.Owner
repo := issue.Repository.Name
// idx := issue.Index
// we need "New Package" label and "Approval Required" label, unless already approved
// either via Label "Approved" or via review comment.
NewIssues := common.FindNewReposInIssueBody(issue.Body)
if NewIssues == nil {
common.LogDebug("No new repos found in issue body")
return nil
}
git, err := GitHandler.CreateGitHandler(config.Organization)
if err != nil {
return err
}
defer git.Close()
for _, nr := range NewIssues.Repos {
common.LogDebug(" - Processing new repository src:", nr.Organization+"/"+nr.PackageName+"#"+nr.Branch)
targetRepo, err := Gitea.GetRepository(config.Organization, nr.PackageName)
if err != nil {
return err
}
if targetRepo == nil {
common.LogInfo(" - Repository", config.Organization+"/"+nr.PackageName, "does not exist. Labeling issue.")
if !common.IsDryRun && issue.State == "open" {
Gitea.SetLabels(org, repo, issue.Index, []string{config.Label(common.Label_NewRepository)})
}
common.LogDebug(" # Done for now with this repo")
continue
}
// check if we already have created a PR here
// TODO, we need to filter by project config permissions of target project, not just assume bot here.
users := []string{CurrentUser.UserName}
prs := i.IssueTimeline.FindIssuePullRequestRererences(config.Organization, nr.PackageName, 0, users)
for _, t := range prs {
pr, err := Gitea.GetPullRequest(config.Organization, nr.PackageName, t.RefIssue.Index)
if err != nil {
common.LogError("Failed to fetch PR", common.PRtoString(pr), ":", err)
}
if issue.State == "open" {
// PR already created, we just need to update it now
common.LogInfo("Update PR ", common.PRtoString(pr), "only... Nothing to do now")
return nil
}
// so, issue is closed .... close associated package PR
_, err = Gitea.UpdateIssue(config.Organization, nr.PackageName, t.RefIssue.Index, &models.EditIssueOption{State: "closed"})
if err != nil {
common.LogError("Failed to close associated PR", common.PRtoString(pr), ":", err)
}
// remove branch if it's a new repository.
return err
}
srcRepo, err := FindSourceRepository(nr.Organization, nr.Repository)
if err != nil {
continue
}
if len(nr.Branch) == 0 {
nr.Branch = srcRepo.DefaultBranch
}
srcRemoteName, err := git.GitClone(nr.PackageName, nr.Branch, srcRepo.SSHURL)
if err != nil {
return err
}
remoteName, err := git.GitClone(nr.PackageName, nr.Branch, targetRepo.SSHURL)
if err != nil {
return err
}
// Check that fork/parent repository relationship exists
if srcRepo.Parent.Name != targetRepo.Name || srcRepo.Parent.Owner.UserName != targetRepo.Owner.UserName {
common.LogError("Source repository is not fork of the Target repository. Fork of:", srcRepo.Parent.Owner.UserName+"/"+srcRepo.Parent.Name)
continue
}
srcBranch := nr.Branch
if srcBranch == "" {
srcBranch = srcRepo.DefaultBranch
}
// We are ready to setup a pending PR.
// 1. empty target branch with empty commit, this will be discarded no merge
// 2. create PR from source to target
// a) if source is not branch, create a source branch in target repo that contains the relevant commit
SourceCommitList := common.SplitLines(git.GitExecWithOutputOrPanic(nr.PackageName, "rev-list", "--first-parent", srcRemoteName+"/"+nr.Branch))
CommitLength := len(SourceCommitList)
SourceCommitId := SourceCommitList[CommitLength-1]
if CommitLength > 20 {
SourceCommitId = SourceCommitList[20]
}
if CommitLength < 2 {
// only 1 commit, then we need empty branch on target
if dl, err := git.GitDirectoryContentList(nr.PackageName, nr.Branch); err == nil && len(dl) > 0 {
createEmptyBranch(git, nr.PackageName, nr.Branch)
}
} else {
git.GitExecOrPanic(nr.PackageName, "checkout", "-B", nr.Branch, SourceCommitId)
}
if !common.IsDryRun {
git.GitExecOrPanic(nr.PackageName, "push", "-f", remoteName, nr.Branch)
}
head := nr.Organization + ":" + srcBranch
isBranch := false
// Hash can be branch name! Check if it's a branch or tag on the remote
out, err := git.GitExecWithOutput(nr.PackageName, "ls-remote", "--heads", srcRepo.SSHURL, srcBranch)
if err == nil && strings.Contains(out, "refs/heads/"+srcBranch) {
isBranch = true
}
if !isBranch {
tempBranch := fmt.Sprintf("new_package_%d_%s", issue.Index, nr.PackageName)
// Re-clone or use existing if branch check was done above
remoteName, err := git.GitClone(nr.PackageName, srcBranch, targetRepo.SSHURL)
if err != nil {
return err
}
git.GitExecOrPanic(nr.PackageName, "remote", "add", "source", srcRepo.SSHURL)
git.GitExecOrPanic(nr.PackageName, "fetch", "source", srcBranch)
git.GitExecOrPanic(nr.PackageName, "checkout", "-B", tempBranch, "FETCH_HEAD")
if !common.IsDryRun {
git.GitExecOrPanic(nr.PackageName, "push", "-f", remoteName, tempBranch)
}
head = tempBranch
}
title := fmt.Sprintf("Add package %s", nr.PackageName)
prjGitOrg, prjGitRepo, _ := config.GetPrjGit()
body := fmt.Sprintf("See issue %s/%s#%d", prjGitOrg, prjGitRepo, issue.Index)
br := i.TargetBranch
if len(br) == 0 {
br = targetRepo.DefaultBranch
}
pr, err, isNew := Gitea.CreatePullRequestIfNotExist(targetRepo, head, br, title, body)
if err != nil {
common.LogError(targetRepo.Name, head, i.TargetBranch, title, body)
return err
}
if !isNew && (pr.Body != body || !pr.AllowMaintainerEdit) {
Gitea.UpdatePullRequest(pr.Base.Repo.Owner.UserName, pr.Base.Repo.Name, pr.Index, &models.EditPullRequestOption{
AllowMaintainerEdit: true,
Body: body,
})
}
if isNew {
if _, err := Gitea.SetLabels(config.Organization, nr.PackageName, pr.Index, []string{config.Label(common.Label_NewRepository)}); err != nil {
common.LogError("Failed to set label:", common.Label_NewRepository, err)
}
}
}
return nil
}
func (i *IssueProcessor) ProcessIssue(configs common.AutogitConfigs) error {
issue := i.issue
org := issue.Repository.Owner
repo := issue.Repository.Name
idx := issue.Index
// out, _ := json.MarshalIndent(issue, "", " ")
// common.LogDebug(string(out))
var err error
i.IssueTimeline, err = Gitea.GetTimeline(org, repo, idx)
if err != nil {
common.LogError(" timeline fetch failed:", err)
return err
}
i.TargetBranch = i.GetTargetBranch()
config := configs.GetPrjGitConfig(org, repo, i.TargetBranch)
if config == nil {
return fmt.Errorf("Cannot find config for %s/%s#%s", org, repo, i.TargetBranch)
}
common.LogDebug("issue processing:", common.IssueToString(issue), "@", i.TargetBranch)
if i.IsAddIssue() {
i.ProcessAddIssue(config)
} else if i.IsRmIssue() {
// to remove a package, no approval is required. This should happen via
// project git PR reviews
} else {
common.LogError("Non-standard issue created. Ignoring", common.IssueToString(issue))
return nil
}
return nil
}

View File

@@ -1,523 +0,0 @@
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)
}
})
}

View File

@@ -44,30 +44,39 @@ var CurrentUser *models.User
var GitHandler common.GitHandlerGenerator var GitHandler common.GitHandlerGenerator
var Gitea common.Gitea var Gitea common.Gitea
func getEnvOverrideString(env, def string) string {
if envValue := os.Getenv(env); len(envValue) != 0 {
return envValue
}
return def
}
func getEnvOverrideBool(env string, def bool) bool {
if envValue := os.Getenv(env); len(envValue) != 0 {
if value, err := strconv.Atoi(envValue); err == nil && value > 0 {
return true
}
}
return def
}
func main() { func main() {
flag.StringVar(&GitAuthor, "git-author", common.GetEnvOverrideString(os.Getenv("AUTOGITS_GIT_AUTHOR"), "AutoGits PR Review Bot"), "Git commit author") flag.StringVar(&GitAuthor, "git-author", "AutoGits PR Review Bot", "Git commit author")
flag.StringVar(&GitEmail, "git-email", common.GetEnvOverrideString(os.Getenv("AUTOGITS_GIT_EMAIL"), "noone@suse.de"), "Git commit email") flag.StringVar(&GitEmail, "git-email", "amajer+devel-git@suse.de", "Git commit email")
workflowConfig := flag.String("config", common.GetEnvOverrideString(os.Getenv("AUTOGITS_CONFIG"), ""), "Repository and workflow definition file") workflowConfig := flag.String("config", getEnvOverrideString("AUTOGITS_CONFIG", ""), "Repository and workflow definition file")
giteaUrl := flag.String("gitea-url", common.GetEnvOverrideString(os.Getenv("AUTOGITS_GITEA_URL"), "https://src.opensuse.org"), "Gitea instance") giteaUrl := flag.String("gitea-url", "https://src.opensuse.org", "Gitea instance")
rabbitUrl := flag.String("url", "amqps://rabbit.opensuse.org", "URL for RabbitMQ instance")
legacyRabbitUrl := flag.String("url", "", "Legacy. Use rabbit-url") /* TO BE REMOVED */ debugMode := flag.Bool("debug", getEnvOverrideBool("AUTOGITS_DEBUG", false), "Extra debugging information")
rabbitUrl := flag.String("rabbit-url", common.GetEnvOverrideString(os.Getenv("AUTOGITS_RABBIT_URL"), "amqps://rabbit.opensuse.org"), "URL for RabbitMQ instance") checkOnStart := flag.Bool("check-on-start", getEnvOverrideBool("AUTOGITS_CHECK_ON_START", false), "Check all repositories for consistency on start, without delays")
debugMode := flag.Bool("debug", common.GetEnvOverrideBool(os.Getenv("AUTOGITS_DEBUG"), false), "Extra debugging information")
checkOnStart := flag.Bool("check-on-start", common.GetEnvOverrideBool(os.Getenv("AUTOGITS_CHECK_ON_START"), false), "Check all repositories for consistency on start, without delays")
checkIntervalHours := flag.Float64("check-interval", 5, "Check interval (+-random delay) for repositories for consitency, in hours") checkIntervalHours := flag.Float64("check-interval", 5, "Check interval (+-random delay) for repositories for consitency, in hours")
flag.BoolVar(&ListPROnly, "list-prs-only", false, "Only lists PRs without acting on them") flag.BoolVar(&ListPROnly, "list-prs-only", false, "Only lists PRs without acting on them")
flag.Int64Var(&PRID, "id", -1, "Process only the specific ID and ignore the rest. Use for debugging") flag.Int64Var(&PRID, "id", -1, "Process only the specific ID and ignore the rest. Use for debugging")
basePath := flag.String("repo-path", common.GetEnvOverrideString(os.Getenv("AUTOGITS_REPO_PATH"), ""), "Repository path. Default is temporary directory") basePath := flag.String("repo-path", getEnvOverrideString("AUTOGITS_REPO_PATH", ""), "Repository path. Default is temporary directory")
pr := flag.String("only-pr", "", "Only specific PR to process. For debugging") pr := flag.String("only-pr", "", "Only specific PR to process. For debugging")
flag.BoolVar(&common.IsDryRun, "dry", false, "Dry mode. Do not push changes to remote repo.") flag.BoolVar(&common.IsDryRun, "dry", false, "Dry mode. Do not push changes to remote repo.")
flag.Parse() flag.Parse()
if len(*legacyRabbitUrl) > 0 {
*rabbitUrl = *legacyRabbitUrl
}
common.SetLoggingLevel(common.LogLevelInfo) common.SetLoggingLevel(common.LogLevelInfo)
if *debugMode { if *debugMode {
common.SetLoggingLevel(common.LogLevelDebug) common.SetLoggingLevel(common.LogLevelDebug)
@@ -153,22 +162,15 @@ func main() {
num, err := strconv.ParseInt(data[3], 10, 64) num, err := strconv.ParseInt(data[3], 10, 64)
common.LogInfo("Processing:", org, "/", repo, "#", num) common.LogInfo("Processing:", org, "/", repo, "#", num)
common.PanicOnError(err) common.PanicOnError(err)
if pr, err := Gitea.GetPullRequest(org, repo, num); err == nil && pr != nil { pr, err := Gitea.GetPullRequest(org, repo, num)
if err = ProcesPullRequest(pr, configs); err != nil { if err != nil {
common.LogError("PR processor returned error", err) common.LogError("Cannot fetch PR", err)
}
} else if issue, err := Gitea.GetIssue(org, repo, num); err == nil && issue != nil {
processor := &IssueProcessor{
issue: issue,
}
if err = processor.ProcessIssue(configs); err != nil {
common.LogError("issue processor returned error:", err)
}
} else {
common.LogError("Cannot fetch PR or Issue", err)
return return
} }
if err = ProcesPullRequest(pr, configs); err != nil {
common.LogError("processor returned error", err)
}
} }
return return

View File

@@ -14,7 +14,6 @@ import (
) )
func TestProjectBranchName(t *testing.T) { func TestProjectBranchName(t *testing.T) {
common.SetTestLogger(t)
branchName := prGitBranchNameForPR("testingRepo", 10) branchName := prGitBranchNameForPR("testingRepo", 10)
if branchName != "PR_testingRepo#10" { if branchName != "PR_testingRepo#10" {
t.Error("Unexpected branch name:", branchName) t.Error("Unexpected branch name:", branchName)
@@ -22,7 +21,6 @@ func TestProjectBranchName(t *testing.T) {
} }
func TestUpdatePrBranch(t *testing.T) { func TestUpdatePrBranch(t *testing.T) {
common.SetTestLogger(t)
var buf bytes.Buffer var buf bytes.Buffer
origLogger := log.Writer() origLogger := log.Writer()
log.SetOutput(&buf) log.SetOutput(&buf)
@@ -60,7 +58,6 @@ func TestUpdatePrBranch(t *testing.T) {
} }
func TestCreatePrBranch(t *testing.T) { func TestCreatePrBranch(t *testing.T) {
common.SetTestLogger(t)
var buf bytes.Buffer var buf bytes.Buffer
origLogger := log.Writer() origLogger := log.Writer()
log.SetOutput(&buf) log.SetOutput(&buf)

View File

@@ -194,14 +194,7 @@ func (pr *PRProcessor) SetSubmodulesToMatchPRSet(prset *common.PRSet) error {
} }
if !submodule_found { if !submodule_found {
common.LogInfo("Adding new submodule", repo, "to PrjGit") common.LogError("Failed to find expected repo:", repo)
ref := fmt.Sprintf(common.PrPattern, org, repo, idx)
commitMsg := fmt.Sprintln("Add package", repo, "\n\nThis commit was autocreated by", GitAuthor, "\n\nreferencing PRs:\n", ref)
git.GitExecOrPanic(common.DefaultGitPrj, "submodule", "add", "-b", pr.PR.Base.Name, pr.PR.Base.Repo.SSHURL, repo)
updateSubmoduleInPR(repo, prHead, git)
common.PanicOnError(git.GitExec(common.DefaultGitPrj, "commit", "-a", "-m", commitMsg))
} }
} }
return nil return nil
@@ -413,6 +406,12 @@ func (pr *PRProcessor) Process(req *models.PullRequest) error {
} }
common.LogInfo("fetched PRSet of size:", len(prset.PRs)) common.LogInfo("fetched PRSet of size:", len(prset.PRs))
if !prset.PrepareForMerge(git) {
common.LogError("PRs are NOT mergeable.")
} else {
common.LogInfo("PRs are in mergeable state.")
}
prjGitPRbranch := prGitBranchNameForPR(prRepo, prNo) prjGitPRbranch := prGitBranchNameForPR(prRepo, prNo)
prjGitPR, err := prset.GetPrjGitPR() prjGitPR, err := prset.GetPrjGitPR()
if err == common.PRSet_PrjGitMissing { if err == common.PRSet_PrjGitMissing {
@@ -471,11 +470,11 @@ func (pr *PRProcessor) Process(req *models.PullRequest) error {
if _, ok := err.(*repository.RepoMergePullRequestConflict); !ok { if _, ok := err.(*repository.RepoMergePullRequestConflict); !ok {
common.PanicOnError(err) common.PanicOnError(err)
} }
// } else { } else {
// Gitea.AddComment(pr.PR, "Closing here because the associated Project PR has been closed.") Gitea.AddComment(pr.PR, "Closing here because the associated Project PR has been closed.")
// Gitea.UpdatePullRequest(org, repo, idx, &models.EditPullRequestOption{ Gitea.UpdatePullRequest(org, repo, idx, &models.EditPullRequestOption{
// State: "closed", State: "closed",
// }) })
} }
} }
} }
@@ -622,14 +621,6 @@ type RequestProcessor struct {
recursive int recursive int
} }
func (w *RequestProcessor) Process(pr *models.PullRequest) error {
configs, ok := w.configuredRepos[pr.Base.Repo.Owner.UserName]
if !ok {
return fmt.Errorf("no config found for org %s", pr.Base.Repo.Owner.UserName)
}
return ProcesPullRequest(pr, configs)
}
func ProcesPullRequest(pr *models.PullRequest, configs []*common.AutogitConfig) error { func ProcesPullRequest(pr *models.PullRequest, configs []*common.AutogitConfig) error {
if len(configs) < 1 { if len(configs) < 1 {
// ignoring pull request against unconfigured project (could be just regular sources?) // ignoring pull request against unconfigured project (could be just regular sources?)
@@ -646,6 +637,15 @@ func ProcesPullRequest(pr *models.PullRequest, configs []*common.AutogitConfig)
return PRProcessor.Process(pr) return PRProcessor.Process(pr)
} }
func (w *RequestProcessor) Process(pr *models.PullRequest) error {
configs, ok := w.configuredRepos[pr.Base.Repo.Owner.UserName]
if !ok {
common.LogError("*** Cannot find config for org:", pr.Base.Repo.Owner.UserName)
return fmt.Errorf("*** Cannot find config for org: %s", pr.Base.Repo.Owner.UserName)
}
return ProcesPullRequest(pr, configs)
}
func (w *RequestProcessor) ProcessFunc(request *common.Request) (err error) { func (w *RequestProcessor) ProcessFunc(request *common.Request) (err error) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@@ -674,21 +674,6 @@ func (w *RequestProcessor) ProcessFunc(request *common.Request) (err error) {
common.LogError("Cannot find PR for issue:", req.Repository.Owner.Username, req.Repository.Name, int64(req.Issue.Number)) common.LogError("Cannot find PR for issue:", req.Repository.Owner.Username, req.Repository.Name, int64(req.Issue.Number))
return err return err
} }
} else if req, ok := request.Data.(*common.IssueWebhookEvent); ok {
issue, err := Gitea.GetIssue(req.Repository.Owner.Username, req.Repository.Name, int64(req.Issue.Number))
if err != nil {
common.LogError("Cannot find issue for issue event:", req.Repository.Owner.Username, req.Repository.Name, int64(req.Issue.Number))
return err
}
configs, ok := w.configuredRepos[req.Repository.Owner.Username]
if !ok {
common.LogError("*** Cannot find config for org:", req.Repository.Owner.Username)
return nil
}
processor := &IssueProcessor{
issue: issue,
}
return processor.ProcessIssue(configs)
} else { } else {
common.LogError("*** Invalid data format for PR processing.") common.LogError("*** Invalid data format for PR processing.")
return fmt.Errorf("*** Invalid data format for PR processing.") return fmt.Errorf("*** Invalid data format for PR processing.")

View File

@@ -109,7 +109,7 @@ func TestOpenPR(t *testing.T) {
} }
t.Run("PR git opened request against PrjGit == no action", func(t *testing.T) { t.Run("PR git opened request against PrjGit == no action", func(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
gitea := mock_common.NewMockGitea(ctl) gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
Gitea = gitea Gitea = gitea
@@ -156,7 +156,7 @@ func TestOpenPR(t *testing.T) {
}) })
t.Run("Open PrjGit PR", func(t *testing.T) { t.Run("Open PrjGit PR", func(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
gitea := mock_common.NewMockGitea(ctl) gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
@@ -210,7 +210,7 @@ func TestOpenPR(t *testing.T) {
}) })
t.Run("Cannot create prjgit repository", func(t *testing.T) { t.Run("Cannot create prjgit repository", func(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
gitea := mock_common.NewMockGitea(ctl) gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
@@ -259,7 +259,7 @@ func TestOpenPR(t *testing.T) {
} }
}) })
t.Run("Cannot create PR", func(t *testing.T) { t.Run("Cannot create PR", func(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
gitea := mock_common.NewMockGitea(ctl) gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
@@ -311,7 +311,7 @@ func TestOpenPR(t *testing.T) {
} }
}) })
t.Run("Open PrjGit PR", func(t *testing.T) { t.Run("Open PrjGit PR", func(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
gitea := mock_common.NewMockGitea(ctl) gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()

View File

@@ -11,7 +11,6 @@ import (
) )
func TestSyncPR(t *testing.T) { func TestSyncPR(t *testing.T) {
CurrentUser = &models.User{UserName: "testuser"}
config := &common.AutogitConfig{ config := &common.AutogitConfig{
Reviewers: []string{"reviewer1", "reviewer2"}, Reviewers: []string{"reviewer1", "reviewer2"},
Branch: "testing", Branch: "testing",
@@ -74,7 +73,7 @@ func TestSyncPR(t *testing.T) {
} }
t.Run("PR_sync_request_against_PrjGit_==_no_action", func(t *testing.T) { t.Run("PR_sync_request_against_PrjGit_==_no_action", func(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
defer ctl.Finish() defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl) gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
@@ -109,7 +108,7 @@ func TestSyncPR(t *testing.T) {
}) })
t.Run("Missing PrjGit PR for the sync", func(t *testing.T) { t.Run("Missing PrjGit PR for the sync", func(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
defer ctl.Finish() defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl) gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
@@ -131,7 +130,7 @@ func TestSyncPR(t *testing.T) {
}) })
t.Run("PR sync", func(t *testing.T) { t.Run("PR sync", func(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
defer ctl.Finish() defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl) gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
@@ -155,3 +154,4 @@ func TestSyncPR(t *testing.T) {
} }
}) })
} }

View File

@@ -64,7 +64,7 @@ func TestPrjGitDescription(t *testing.T) {
} }
func TestAllocatePRProcessor(t *testing.T) { func TestAllocatePRProcessor(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
defer ctl.Finish() defer ctl.Finish()
mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl) mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl)
@@ -104,7 +104,7 @@ func TestAllocatePRProcessor(t *testing.T) {
} }
func TestAllocatePRProcessor_Failures(t *testing.T) { func TestAllocatePRProcessor_Failures(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
defer ctl.Finish() defer ctl.Finish()
mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl) mockGitGen := mock_common.NewMockGitHandlerGenerator(ctl)
@@ -154,7 +154,7 @@ func TestAllocatePRProcessor_Failures(t *testing.T) {
} }
func TestSetSubmodulesToMatchPRSet_Failures(t *testing.T) { func TestSetSubmodulesToMatchPRSet_Failures(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
defer ctl.Finish() defer ctl.Finish()
mockGit := mock_common.NewMockGit(ctl) mockGit := mock_common.NewMockGit(ctl)
@@ -178,7 +178,7 @@ func TestSetSubmodulesToMatchPRSet_Failures(t *testing.T) {
} }
func TestSetSubmodulesToMatchPRSet(t *testing.T) { func TestSetSubmodulesToMatchPRSet(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
defer ctl.Finish() defer ctl.Finish()
mockGit := mock_common.NewMockGit(ctl) mockGit := mock_common.NewMockGit(ctl)
@@ -226,7 +226,7 @@ func TestSetSubmodulesToMatchPRSet(t *testing.T) {
} }
func TestRebaseAndSkipSubmoduleCommits(t *testing.T) { func TestRebaseAndSkipSubmoduleCommits(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
defer ctl.Finish() defer ctl.Finish()
mockGit := mock_common.NewMockGit(ctl) mockGit := mock_common.NewMockGit(ctl)
@@ -299,7 +299,7 @@ func TestRebaseAndSkipSubmoduleCommits(t *testing.T) {
} }
func TestUpdatePrjGitPR(t *testing.T) { func TestUpdatePrjGitPR(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
defer ctl.Finish() defer ctl.Finish()
mockGit := mock_common.NewMockGit(ctl) mockGit := mock_common.NewMockGit(ctl)
@@ -547,7 +547,7 @@ func TestUpdatePrjGitPR(t *testing.T) {
} }
func TestCreatePRjGitPR_Integration(t *testing.T) { func TestCreatePRjGitPR_Integration(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
defer ctl.Finish() defer ctl.Finish()
mockGit := mock_common.NewMockGit(ctl) mockGit := mock_common.NewMockGit(ctl)
@@ -667,7 +667,7 @@ func TestMultiPackagePRSet(t *testing.T) {
} }
func TestPRProcessor_Process_EdgeCases(t *testing.T) { func TestPRProcessor_Process_EdgeCases(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
defer ctl.Finish() defer ctl.Finish()
mockGit := mock_common.NewMockGit(ctl) mockGit := mock_common.NewMockGit(ctl)
@@ -791,7 +791,7 @@ func TestPRProcessor_Process_EdgeCases(t *testing.T) {
} }
func TestVerifyRepositoryConfiguration(t *testing.T) { func TestVerifyRepositoryConfiguration(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
defer ctl.Finish() defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl) gitea := mock_common.NewMockGitea(ctl)
@@ -837,7 +837,7 @@ func TestVerifyRepositoryConfiguration(t *testing.T) {
} }
func TestProcessFunc(t *testing.T) { func TestProcessFunc(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
defer ctl.Finish() defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl) gitea := mock_common.NewMockGitea(ctl)

View File

@@ -13,7 +13,7 @@ import (
) )
func TestPrjGitSubmoduleCheck(t *testing.T) { func TestPrjGitSubmoduleCheck(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
defer ctl.Finish() defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl) gitea := mock_common.NewMockGitea(ctl)
@@ -94,7 +94,7 @@ func TestPrjGitSubmoduleCheck(t *testing.T) {
} }
func TestPrjGitSubmoduleCheck_Failures(t *testing.T) { func TestPrjGitSubmoduleCheck_Failures(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
defer ctl.Finish() defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl) gitea := mock_common.NewMockGitea(ctl)
@@ -152,7 +152,7 @@ func TestPullRequestToEventState(t *testing.T) {
} }
func TestDefaultStateChecker_ProcessPR(t *testing.T) { func TestDefaultStateChecker_ProcessPR(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
defer ctl.Finish() defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl) gitea := mock_common.NewMockGitea(ctl)
@@ -200,7 +200,7 @@ func TestDefaultStateChecker_ProcessPR(t *testing.T) {
} }
func TestDefaultStateChecker_VerifyProjectState(t *testing.T) { func TestDefaultStateChecker_VerifyProjectState(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
defer ctl.Finish() defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl) gitea := mock_common.NewMockGitea(ctl)
@@ -264,7 +264,7 @@ func TestDefaultStateChecker_VerifyProjectState(t *testing.T) {
} }
func TestDefaultStateChecker_CheckRepos(t *testing.T) { func TestDefaultStateChecker_CheckRepos(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
defer ctl.Finish() defer ctl.Finish()
gitea := mock_common.NewMockGitea(ctl) gitea := mock_common.NewMockGitea(ctl)

View File

@@ -20,7 +20,7 @@ func TestRepoCheck(t *testing.T) {
t.Run("Consistency Check On Start", func(t *testing.T) { t.Run("Consistency Check On Start", func(t *testing.T) {
c := CreateDefaultStateChecker(true, nil, nil, 100) c := CreateDefaultStateChecker(true, nil, nil, 100)
ctl := NewController(t) ctl := gomock.NewController(t)
state := NewMockStateChecker(ctl) state := NewMockStateChecker(ctl)
c.i = state c.i = state
state.EXPECT().CheckRepos().Do(func() { state.EXPECT().CheckRepos().Do(func() {
@@ -40,7 +40,7 @@ func TestRepoCheck(t *testing.T) {
t.Run("No consistency Check On Start", func(t *testing.T) { t.Run("No consistency Check On Start", func(t *testing.T) {
c := CreateDefaultStateChecker(true, nil, nil, 100) c := CreateDefaultStateChecker(true, nil, nil, 100)
ctl := NewController(t) ctl := gomock.NewController(t)
state := NewMockStateChecker(ctl) state := NewMockStateChecker(ctl)
c.i = state c.i = state
@@ -62,7 +62,7 @@ func TestRepoCheck(t *testing.T) {
}) })
t.Run("CheckRepos() calls CheckProjectState() for each project", func(t *testing.T) { t.Run("CheckRepos() calls CheckProjectState() for each project", func(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
state := NewMockStateChecker(ctl) state := NewMockStateChecker(ctl)
gitea := mock_common.NewMockGitea(ctl) gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
@@ -99,7 +99,7 @@ func TestRepoCheck(t *testing.T) {
}) })
t.Run("CheckRepos errors", func(t *testing.T) { t.Run("CheckRepos errors", func(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
state := NewMockStateChecker(ctl) state := NewMockStateChecker(ctl)
gitea := mock_common.NewMockGitea(ctl) gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
@@ -145,7 +145,7 @@ func TestVerifyProjectState(t *testing.T) {
defer log.SetOutput(oldOut) defer log.SetOutput(oldOut)
t.Run("Project state with no PRs", func(t *testing.T) { t.Run("Project state with no PRs", func(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
gitea := mock_common.NewMockGitea(ctl) gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
@@ -191,7 +191,7 @@ func TestVerifyProjectState(t *testing.T) {
}) })
t.Run("Project state with 1 PRs that doesn't trigger updates", func(t *testing.T) { t.Run("Project state with 1 PRs that doesn't trigger updates", func(t *testing.T) {
ctl := NewController(t) ctl := gomock.NewController(t)
gitea := mock_common.NewMockGitea(ctl) gitea := mock_common.NewMockGitea(ctl)
gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes() gitea.EXPECT().ResetTimelineCache(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()

View File

@@ -7,14 +7,8 @@ import (
"testing" "testing"
"src.opensuse.org/autogits/common" "src.opensuse.org/autogits/common"
"go.uber.org/mock/gomock"
) )
func NewController(t *testing.T) *gomock.Controller {
common.SetTestLogger(t)
return gomock.NewController(t)
}
const LocalCMD = "---" const LocalCMD = "---"
func gitExecs(t *testing.T, git *common.GitHandlerImpl, cmds [][]string) { func gitExecs(t *testing.T, git *common.GitHandlerImpl, cmds [][]string) {
@@ -62,7 +56,6 @@ func commandsForPackages(dir, prefix string, startN, endN int) [][]string {
func setupGitForTests(t *testing.T, git *common.GitHandlerImpl) { func setupGitForTests(t *testing.T, git *common.GitHandlerImpl) {
common.ExtraGitParams = []string{ common.ExtraGitParams = []string{
"TZ=UTC",
"GIT_CONFIG_COUNT=1", "GIT_CONFIG_COUNT=1",
"GIT_CONFIG_KEY_0=protocol.file.allow", "GIT_CONFIG_KEY_0=protocol.file.allow",
"GIT_CONFIG_VALUE_0=always", "GIT_CONFIG_VALUE_0=always",