Compare commits
6 Commits
staging-up
...
t-add-ci
| Author | SHA256 | Date | |
|---|---|---|---|
|
|
40e2f9e130 | ||
|
|
a814c4ce24 | ||
|
|
aadc7f9d41 | ||
|
|
0598448fdb | ||
|
|
2c174b687a | ||
|
|
4826d0869a |
45
.gitea/workflows/t.yaml
Normal file
45
.gitea/workflows/t.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
name: Integration tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['main']
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
HOME: /var/lib/gitea-runner
|
||||
REPO_PATH: /var/lib/gitea-runner/autogits
|
||||
REPO_URL: http://src.opensuse.org//git-workflow/autogits.git
|
||||
|
||||
jobs:
|
||||
t:
|
||||
runs-on: linux-x86_64
|
||||
steps:
|
||||
- name: whoami
|
||||
run: whoami
|
||||
- name: Check variables
|
||||
run: test /var/lib/gitea-runner/autogits == "${{ env.REPO_PATH }}"
|
||||
- name: Checkout product
|
||||
run: |
|
||||
test -n "${{ env.REPO_PATH }}" && rm -rf /var/lib/gitea-runner/autogits
|
||||
git config --global --add safe.directory ${{ env.REPO_PATH }}
|
||||
git clone ${{ env.REPO_URL }} ${{ env.REPO_PATH }}
|
||||
- name: Prepare binaries
|
||||
run: make build
|
||||
working-directory: ${{ env.REPO_PATH }}
|
||||
- name: Prepare images
|
||||
run: make build
|
||||
working-directory: ${{ env.REPO_PATH }}/integration
|
||||
- name: Make sure the pod is down
|
||||
run: make down
|
||||
working-directory: ${{ env.REPO_PATH }}/integration
|
||||
- name: Start images
|
||||
run: make up
|
||||
working-directory: ${{ env.REPO_PATH }}/integration
|
||||
- name: Run tests
|
||||
run: py.test-3.11 -v tests
|
||||
working-directory: ${{ env.REPO_PATH }}/integration
|
||||
- name: Make sure the pod is down
|
||||
run: make down
|
||||
working-directory: ${{ env.REPO_PATH }}/integration
|
||||
|
||||
@@ -83,260 +83,3 @@ func (c *MockObsStatusFetcherWithStateBuildStatusWithStateCall) DoAndReturn(f fu
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// MockObsClientInterface is a mock of ObsClientInterface interface.
|
||||
type MockObsClientInterface struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockObsClientInterfaceMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockObsClientInterfaceMockRecorder is the mock recorder for MockObsClientInterface.
|
||||
type MockObsClientInterfaceMockRecorder struct {
|
||||
mock *MockObsClientInterface
|
||||
}
|
||||
|
||||
// NewMockObsClientInterface creates a new mock instance.
|
||||
func NewMockObsClientInterface(ctrl *gomock.Controller) *MockObsClientInterface {
|
||||
mock := &MockObsClientInterface{ctrl: ctrl}
|
||||
mock.recorder = &MockObsClientInterfaceMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockObsClientInterface) EXPECT() *MockObsClientInterfaceMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// BuildStatus mocks base method.
|
||||
func (m *MockObsClientInterface) BuildStatus(project string, packages ...string) (*common.BuildResultList, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{project}
|
||||
for _, a := range packages {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "BuildStatus", varargs...)
|
||||
ret0, _ := ret[0].(*common.BuildResultList)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// BuildStatus indicates an expected call of BuildStatus.
|
||||
func (mr *MockObsClientInterfaceMockRecorder) BuildStatus(project any, packages ...any) *MockObsClientInterfaceBuildStatusCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]any{project}, packages...)
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BuildStatus", reflect.TypeOf((*MockObsClientInterface)(nil).BuildStatus), varargs...)
|
||||
return &MockObsClientInterfaceBuildStatusCall{Call: call}
|
||||
}
|
||||
|
||||
// MockObsClientInterfaceBuildStatusCall wrap *gomock.Call
|
||||
type MockObsClientInterfaceBuildStatusCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockObsClientInterfaceBuildStatusCall) Return(arg0 *common.BuildResultList, arg1 error) *MockObsClientInterfaceBuildStatusCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockObsClientInterfaceBuildStatusCall) Do(f func(string, ...string) (*common.BuildResultList, error)) *MockObsClientInterfaceBuildStatusCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockObsClientInterfaceBuildStatusCall) DoAndReturn(f func(string, ...string) (*common.BuildResultList, error)) *MockObsClientInterfaceBuildStatusCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DeleteProject mocks base method.
|
||||
func (m *MockObsClientInterface) DeleteProject(project string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteProject", project)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// DeleteProject indicates an expected call of DeleteProject.
|
||||
func (mr *MockObsClientInterfaceMockRecorder) DeleteProject(project any) *MockObsClientInterfaceDeleteProjectCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteProject", reflect.TypeOf((*MockObsClientInterface)(nil).DeleteProject), project)
|
||||
return &MockObsClientInterfaceDeleteProjectCall{Call: call}
|
||||
}
|
||||
|
||||
// MockObsClientInterfaceDeleteProjectCall wrap *gomock.Call
|
||||
type MockObsClientInterfaceDeleteProjectCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockObsClientInterfaceDeleteProjectCall) Return(arg0 error) *MockObsClientInterfaceDeleteProjectCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockObsClientInterfaceDeleteProjectCall) Do(f func(string) error) *MockObsClientInterfaceDeleteProjectCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockObsClientInterfaceDeleteProjectCall) DoAndReturn(f func(string) error) *MockObsClientInterfaceDeleteProjectCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// GetHomeProject mocks base method.
|
||||
func (m *MockObsClientInterface) GetHomeProject() string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetHomeProject")
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetHomeProject indicates an expected call of GetHomeProject.
|
||||
func (mr *MockObsClientInterfaceMockRecorder) GetHomeProject() *MockObsClientInterfaceGetHomeProjectCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHomeProject", reflect.TypeOf((*MockObsClientInterface)(nil).GetHomeProject))
|
||||
return &MockObsClientInterfaceGetHomeProjectCall{Call: call}
|
||||
}
|
||||
|
||||
// MockObsClientInterfaceGetHomeProjectCall wrap *gomock.Call
|
||||
type MockObsClientInterfaceGetHomeProjectCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockObsClientInterfaceGetHomeProjectCall) Return(arg0 string) *MockObsClientInterfaceGetHomeProjectCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockObsClientInterfaceGetHomeProjectCall) Do(f func() string) *MockObsClientInterfaceGetHomeProjectCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockObsClientInterfaceGetHomeProjectCall) DoAndReturn(f func() string) *MockObsClientInterfaceGetHomeProjectCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// GetProjectMeta mocks base method.
|
||||
func (m *MockObsClientInterface) GetProjectMeta(project string) (*common.ProjectMeta, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetProjectMeta", project)
|
||||
ret0, _ := ret[0].(*common.ProjectMeta)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetProjectMeta indicates an expected call of GetProjectMeta.
|
||||
func (mr *MockObsClientInterfaceMockRecorder) GetProjectMeta(project any) *MockObsClientInterfaceGetProjectMetaCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProjectMeta", reflect.TypeOf((*MockObsClientInterface)(nil).GetProjectMeta), project)
|
||||
return &MockObsClientInterfaceGetProjectMetaCall{Call: call}
|
||||
}
|
||||
|
||||
// MockObsClientInterfaceGetProjectMetaCall wrap *gomock.Call
|
||||
type MockObsClientInterfaceGetProjectMetaCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockObsClientInterfaceGetProjectMetaCall) Return(arg0 *common.ProjectMeta, arg1 error) *MockObsClientInterfaceGetProjectMetaCall {
|
||||
c.Call = c.Call.Return(arg0, arg1)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockObsClientInterfaceGetProjectMetaCall) Do(f func(string) (*common.ProjectMeta, error)) *MockObsClientInterfaceGetProjectMetaCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockObsClientInterfaceGetProjectMetaCall) DoAndReturn(f func(string) (*common.ProjectMeta, error)) *MockObsClientInterfaceGetProjectMetaCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// SetHomeProject mocks base method.
|
||||
func (m *MockObsClientInterface) SetHomeProject(project string) {
|
||||
m.ctrl.T.Helper()
|
||||
m.ctrl.Call(m, "SetHomeProject", project)
|
||||
}
|
||||
|
||||
// SetHomeProject indicates an expected call of SetHomeProject.
|
||||
func (mr *MockObsClientInterfaceMockRecorder) SetHomeProject(project any) *MockObsClientInterfaceSetHomeProjectCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetHomeProject", reflect.TypeOf((*MockObsClientInterface)(nil).SetHomeProject), project)
|
||||
return &MockObsClientInterfaceSetHomeProjectCall{Call: call}
|
||||
}
|
||||
|
||||
// MockObsClientInterfaceSetHomeProjectCall wrap *gomock.Call
|
||||
type MockObsClientInterfaceSetHomeProjectCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockObsClientInterfaceSetHomeProjectCall) Return() *MockObsClientInterfaceSetHomeProjectCall {
|
||||
c.Call = c.Call.Return()
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockObsClientInterfaceSetHomeProjectCall) Do(f func(string)) *MockObsClientInterfaceSetHomeProjectCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockObsClientInterfaceSetHomeProjectCall) DoAndReturn(f func(string)) *MockObsClientInterfaceSetHomeProjectCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// SetProjectMeta mocks base method.
|
||||
func (m *MockObsClientInterface) SetProjectMeta(meta *common.ProjectMeta) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "SetProjectMeta", meta)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// SetProjectMeta indicates an expected call of SetProjectMeta.
|
||||
func (mr *MockObsClientInterfaceMockRecorder) SetProjectMeta(meta any) *MockObsClientInterfaceSetProjectMetaCall {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetProjectMeta", reflect.TypeOf((*MockObsClientInterface)(nil).SetProjectMeta), meta)
|
||||
return &MockObsClientInterfaceSetProjectMetaCall{Call: call}
|
||||
}
|
||||
|
||||
// MockObsClientInterfaceSetProjectMetaCall wrap *gomock.Call
|
||||
type MockObsClientInterfaceSetProjectMetaCall struct {
|
||||
*gomock.Call
|
||||
}
|
||||
|
||||
// Return rewrite *gomock.Call.Return
|
||||
func (c *MockObsClientInterfaceSetProjectMetaCall) Return(arg0 error) *MockObsClientInterfaceSetProjectMetaCall {
|
||||
c.Call = c.Call.Return(arg0)
|
||||
return c
|
||||
}
|
||||
|
||||
// Do rewrite *gomock.Call.Do
|
||||
func (c *MockObsClientInterfaceSetProjectMetaCall) Do(f func(*common.ProjectMeta) error) *MockObsClientInterfaceSetProjectMetaCall {
|
||||
c.Call = c.Call.Do(f)
|
||||
return c
|
||||
}
|
||||
|
||||
// DoAndReturn rewrite *gomock.Call.DoAndReturn
|
||||
func (c *MockObsClientInterfaceSetProjectMetaCall) DoAndReturn(f func(*common.ProjectMeta) error) *MockObsClientInterfaceSetProjectMetaCall {
|
||||
c.Call = c.Call.DoAndReturn(f)
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -46,15 +46,6 @@ type ObsStatusFetcherWithState interface {
|
||||
BuildStatusWithState(project string, opts *BuildResultOptions, packages ...string) (*BuildResultList, error)
|
||||
}
|
||||
|
||||
type ObsClientInterface interface {
|
||||
GetProjectMeta(project string) (*ProjectMeta, error)
|
||||
SetProjectMeta(meta *ProjectMeta) error
|
||||
DeleteProject(project string) error
|
||||
BuildStatus(project string, packages ...string) (*BuildResultList, error)
|
||||
GetHomeProject() string
|
||||
SetHomeProject(project string)
|
||||
}
|
||||
|
||||
type ObsClient struct {
|
||||
baseUrl *url.URL
|
||||
client *http.Client
|
||||
@@ -66,14 +57,6 @@ type ObsClient struct {
|
||||
HomeProject string
|
||||
}
|
||||
|
||||
func (c *ObsClient) GetHomeProject() string {
|
||||
return c.HomeProject
|
||||
}
|
||||
|
||||
func (c *ObsClient) SetHomeProject(project string) {
|
||||
c.HomeProject = project
|
||||
}
|
||||
|
||||
func NewObsClient(host string) (*ObsClient, error) {
|
||||
baseUrl, err := url.Parse(host)
|
||||
if err != nil {
|
||||
|
||||
@@ -44,7 +44,7 @@ build_container:
|
||||
|
||||
# Run tests in topology 1
|
||||
test_container:
|
||||
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/*"
|
||||
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/*"
|
||||
|
||||
|
||||
build_local: AUTO_DETECT_MODE=.local
|
||||
|
||||
@@ -11,7 +11,7 @@ RUN zypper -n install \
|
||||
openssh \
|
||||
jq \
|
||||
devel_Factory_git-workflow:gitea \
|
||||
&& rm -rf /var/cache/zypp/*
|
||||
&& rm -rf /var/cache/zypp/* || ( tail -n 1000 /var/log/zypper.log ; exit 1 )
|
||||
|
||||
# Copy the minimal set of required files from the local 'container-files' directory
|
||||
COPY container-files/ /
|
||||
|
||||
@@ -53,31 +53,37 @@ The testing will be conducted in a dedicated test environment that mimics the pr
|
||||
|
||||
## 5. Test Cases
|
||||
|
||||
| Test Case ID | Description | Steps to Reproduce | Expected Results | Priority |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| **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** | **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** | **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-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** | **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-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-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** | **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** | **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** | **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** | **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** | **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-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-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-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-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** | **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 |
|
||||
| Test Case ID | Status | 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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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
|
||||
|
||||
@@ -6,232 +6,77 @@ import pytest
|
||||
import requests
|
||||
import time
|
||||
import os
|
||||
|
||||
# Assuming GiteaAPIClient is in tests/lib/common_test_utils.py
|
||||
import json
|
||||
import base64
|
||||
from tests.lib.common_test_utils import GiteaAPIClient
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def gitea_env():
|
||||
"""
|
||||
Sets up the Gitea environment with dummy data and provides a GiteaAPIClient instance.
|
||||
"""
|
||||
gitea_url = "http://127.0.0.1:3000"
|
||||
|
||||
# Read admin token
|
||||
admin_token_path = "./gitea-data/admin.token" # Corrected path
|
||||
admin_token = None
|
||||
try:
|
||||
with open(admin_token_path, "r") as f:
|
||||
admin_token = f.read().strip()
|
||||
except FileNotFoundError:
|
||||
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 = 5
|
||||
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 1 seconds... ({i+1}/{max_retries})")
|
||||
time.sleep(1)
|
||||
else:
|
||||
raise Exception("Gitea did not become available within the expected time.")
|
||||
|
||||
client = GiteaAPIClient(base_url=gitea_url, token=admin_token)
|
||||
|
||||
# Setup dummy data
|
||||
print("--- Starting Gitea Dummy Data Setup from Pytest Fixture ---")
|
||||
client.create_org("products")
|
||||
client.create_org("pool")
|
||||
|
||||
client.create_repo("products", "SLFO")
|
||||
client.create_repo("pool", "pkgA")
|
||||
client.create_repo("pool", "pkgB")
|
||||
|
||||
# The add_submodules method also creates workflow.config and staging.config
|
||||
client.add_submodules("products", "SLFO")
|
||||
time.sleep(1)
|
||||
|
||||
workflow_config_content = """{
|
||||
BRANCH_CONFIG_COMMON = {
|
||||
"workflow.config": {
|
||||
"Workflows": ["pr"],
|
||||
"GitProjectName": "products/SLFO#main",
|
||||
"Organization": "pool",
|
||||
"Branch": "main",
|
||||
"ManualMergeProject": true,
|
||||
"Reviewers": [ "-autogits_obs_staging_bot" ]
|
||||
}"""
|
||||
client.create_file("products", "SLFO", "workflow.config", workflow_config_content)
|
||||
"Reviewers": ["-autogits_obs_staging_bot"],
|
||||
"GitProjectName": "products/SLFO#{branch}"
|
||||
},
|
||||
"_maintainership.json": {
|
||||
"": ["ownerX", "ownerY"],
|
||||
"pkgA": ["ownerA"],
|
||||
"pkgB": ["ownerB", "ownerBB"]
|
||||
}
|
||||
}
|
||||
|
||||
staging_config_content = """{
|
||||
"ObsProject": "openSUSE:Leap:16.0",
|
||||
"StagingProject": "openSUSE:Leap:16.0:PullRequest"
|
||||
}"""
|
||||
client.create_file("products", "SLFO", "staging.config", staging_config_content)
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client.add_collaborator("products", "SLFO", "autogits_obs_staging_bot", "write")
|
||||
client.add_collaborator("products", "SLFO", "workflow-pr", "write")
|
||||
client.add_collaborator("pool", "pkgA", "workflow-pr", "write")
|
||||
client.add_collaborator("pool", "pkgB", "workflow-pr", "write")
|
||||
|
||||
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(1) # Give workflow-pr bot time to become fully active
|
||||
|
||||
yield client
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def configured_dev_branch_env(gitea_env: GiteaAPIClient, request):
|
||||
"""
|
||||
Fixture to set up a 'dev' branch in products/SLFO and pool/pkgA,
|
||||
and configure workflow.config in products/SLFO#dev with specific content.
|
||||
Yields (gitea_env, test_full_repo_name, dev_branch_name).
|
||||
"""
|
||||
test_org_name = "products"
|
||||
test_repo_name = "SLFO"
|
||||
test_full_repo_name = f"{test_org_name}/{test_repo_name}"
|
||||
dev_branch_name = "dev"
|
||||
|
||||
workflow_config_content = request.param # Get config content from parametrization
|
||||
|
||||
print(f"--- Setting up 'dev' branch and workflow.config in {test_full_repo_name}#{dev_branch_name} ---")
|
||||
|
||||
# Get the latest commit SHA of the main branch
|
||||
main_branch_sha = gitea_env._request("GET", f"repos/{test_org_name}/{test_repo_name}/branches/main").json()["commit"]["id"]
|
||||
|
||||
# Create 'dev' branch from 'main' in products/SLFO
|
||||
gitea_env.create_branch(test_org_name, test_repo_name, dev_branch_name, main_branch_sha)
|
||||
|
||||
# Create 'dev' branch in pool/pkgA as well
|
||||
pool_pkga_main_sha = gitea_env._request("GET", "repos/pool/pkgA/branches/main").json()["commit"]["id"]
|
||||
gitea_env.create_branch("pool", "pkgA", dev_branch_name, pool_pkga_main_sha)
|
||||
|
||||
# Create 'dev' branch in pool/pkgB as well
|
||||
pool_pkgb_main_sha = gitea_env._request("GET", "repos/pool/pkgB/branches/main").json()["commit"]["id"]
|
||||
gitea_env.create_branch("pool", "pkgB", dev_branch_name, pool_pkgb_main_sha)
|
||||
|
||||
# Create/update workflow.config with the provided content
|
||||
gitea_env.create_file(test_org_name, test_repo_name, "workflow.config", workflow_config_content, branch=dev_branch_name)
|
||||
print(f"Created workflow.config with specific content in {test_full_repo_name}#{dev_branch_name}")
|
||||
|
||||
# Restart workflow-pr service to pick up new project config
|
||||
gitea_env.restart_service("workflow-pr")
|
||||
time.sleep(1) # Give the service time to restart and re-initialize
|
||||
|
||||
yield gitea_env, test_full_repo_name, dev_branch_name
|
||||
|
||||
|
||||
# Teardown (optional, depending on test strategy)
|
||||
# For now, we'll leave resources for inspection. If a clean slate is needed for each test,
|
||||
# this fixture's scope would be 'function' and teardown logic would be added here.
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def no_project_git_pr_env(gitea_env: GiteaAPIClient):
|
||||
"""
|
||||
Sets up 'dev' branch in products/SLFO and pool/pkgA,
|
||||
and configures workflow.config in products/SLFO#dev with NoProjectGitPR: true.
|
||||
"""
|
||||
test_org_name = "products"
|
||||
test_repo_name = "SLFO"
|
||||
test_full_repo_name = f"{test_org_name}/{test_repo_name}"
|
||||
dev_branch_name = "dev"
|
||||
|
||||
print(f"--- Setting up workflow.config in {test_full_repo_name}#{dev_branch_name} for No Project PR ---")
|
||||
|
||||
# Get the latest commit SHA of the main branch
|
||||
main_branch_sha = gitea_env._request("GET", f"repos/{test_org_name}/{test_repo_name}/branches/main").json()["commit"]["id"]
|
||||
|
||||
# Create 'dev' branch from 'main' in products/SLFO
|
||||
try:
|
||||
gitea_env.create_branch(test_org_name, test_repo_name, dev_branch_name, main_branch_sha)
|
||||
except Exception as e:
|
||||
if "already exists" not in str(e).lower():
|
||||
raise
|
||||
|
||||
# Create 'dev' branch in pool/pkgA as well
|
||||
pool_pkga_main_sha = gitea_env._request("GET", "repos/pool/pkgA/branches/main").json()["commit"]["id"]
|
||||
try:
|
||||
gitea_env.create_branch("pool", "pkgA", dev_branch_name, pool_pkga_main_sha)
|
||||
except Exception as e:
|
||||
if "already exists" not in str(e).lower():
|
||||
raise
|
||||
|
||||
# Create 'dev' branch in pool/pkgB as well
|
||||
pool_pkgb_main_sha = gitea_env._request("GET", "repos/pool/pkgB/branches/main").json()["commit"]["id"]
|
||||
try:
|
||||
gitea_env.create_branch("pool", "pkgB", dev_branch_name, pool_pkgb_main_sha)
|
||||
except Exception as e:
|
||||
if "already exists" not in str(e).lower():
|
||||
raise
|
||||
|
||||
# Setup workflow.config to have "NoProjectGitPR": true
|
||||
workflow_config_content_no_project_pr = f"""{{
|
||||
"Workflows": ["pr"],
|
||||
"GitProjectName": "{test_full_repo_name}#{dev_branch_name}",
|
||||
"Organization": "pool",
|
||||
"Branch": "dev",
|
||||
"ManualMergeProject": true,
|
||||
"Reviewers": [ "-autogits_obs_staging_bot" ],
|
||||
"NoProjectGitPR": true
|
||||
}}"""
|
||||
gitea_env.create_file(test_org_name, test_repo_name, "workflow.config", workflow_config_content_no_project_pr, branch=dev_branch_name)
|
||||
print(f"Created workflow.config with NoProjectGitPR: true in {test_full_repo_name}#{dev_branch_name}")
|
||||
|
||||
# Restart workflow-pr service
|
||||
gitea_env.restart_service("workflow-pr")
|
||||
time.sleep(1) # Give the service time to restart and re-initialize
|
||||
|
||||
return gitea_env, test_full_repo_name, dev_branch_name
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def test_user_client(gitea_env: GiteaAPIClient):
|
||||
"""
|
||||
Creates a new unique user and returns a GiteaAPIClient instance for them using sudo.
|
||||
This user should not have write permissions to the test repositories by default.
|
||||
"""
|
||||
username = f"user-{int(time.time())}"
|
||||
password = "password123"
|
||||
email = f"{username}@example.com"
|
||||
|
||||
gitea_env.create_user(username, password, email)
|
||||
|
||||
# Grant write access to pool/pkgA
|
||||
gitea_env.add_collaborator("pool", "pkgA", username, "write")
|
||||
|
||||
# Use admin token with Sudo header
|
||||
admin_token = gitea_env.headers["Authorization"].split(" ")[1]
|
||||
return GiteaAPIClient(base_url=gitea_env.base_url, token=admin_token, sudo=username)
|
||||
|
||||
|
||||
def setup_users_from_config(client: GiteaAPIClient, workflow_config: str, maintainership_config: str):
|
||||
def setup_users_from_config(client: GiteaAPIClient, wf: dict, mt: dict):
|
||||
"""
|
||||
Parses workflow.config and _maintainership.json, creates users, and adds them as collaborators.
|
||||
"""
|
||||
import json
|
||||
|
||||
wf = json.loads(workflow_config)
|
||||
mt = json.loads(maintainership_config)
|
||||
|
||||
all_users = set()
|
||||
|
||||
# Extract from workflow.config Reviewers
|
||||
reviewers = wf.get("Reviewers", [])
|
||||
for r in reviewers:
|
||||
# Strip +, - prefixes
|
||||
username = r.lstrip("+-")
|
||||
username = r.lstrip("+-*")
|
||||
if username and username not in ["autogits_obs_staging_bot", "workflow-pr"]:
|
||||
all_users.add(username)
|
||||
|
||||
@@ -243,8 +88,6 @@ def setup_users_from_config(client: GiteaAPIClient, workflow_config: str, mainta
|
||||
# Create all users
|
||||
for username in all_users:
|
||||
client.create_user(username, "password123", f"{username}@example.com")
|
||||
# Global maintainers (empty key) get write access to everything
|
||||
# Actually, let's just make them collaborators on SLFO, pkgA, pkgB for simplicity in tests
|
||||
client.add_collaborator("products", "SLFO", username, "write")
|
||||
|
||||
# Set specific repository permissions based on maintainership
|
||||
@@ -252,469 +95,167 @@ def setup_users_from_config(client: GiteaAPIClient, workflow_config: str, mainta
|
||||
repo_name = pkg if pkg else None
|
||||
for username in users:
|
||||
if not repo_name:
|
||||
# Global maintainer - already added to SLFO, add to pkgA/pkgB
|
||||
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")
|
||||
def gitea_env():
|
||||
"""
|
||||
Sets up the Gitea environment with dummy data and provides a GiteaAPIClient instance.
|
||||
Global fixture to set up the Gitea environment for all tests.
|
||||
"""
|
||||
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
|
||||
try:
|
||||
with open(admin_token_path, "r") as f:
|
||||
admin_token = f.read().strip()
|
||||
except FileNotFoundError:
|
||||
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 = 5
|
||||
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 1 seconds... ({i+1}/{max_retries})")
|
||||
time.sleep(1)
|
||||
else:
|
||||
raise Exception("Gitea did not become available within the expected time.")
|
||||
raise Exception(f"Admin token file not found at {admin_token_path}.")
|
||||
|
||||
client = GiteaAPIClient(base_url=gitea_url, token=admin_token)
|
||||
|
||||
# Setup dummy data
|
||||
print("--- Starting Gitea Dummy Data Setup from Pytest Fixture ---")
|
||||
# Wait for Gitea
|
||||
for i in range(10):
|
||||
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("pool")
|
||||
|
||||
client.create_repo("products", "SLFO")
|
||||
client.create_repo("pool", "pkgA")
|
||||
client.create_repo("pool", "pkgB")
|
||||
|
||||
# The add_submodules method also creates workflow.config and staging.config
|
||||
client.update_repo_settings("products", "SLFO")
|
||||
client.update_repo_settings("pool", "pkgA")
|
||||
client.update_repo_settings("pool", "pkgB")
|
||||
|
||||
# Create labels
|
||||
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")
|
||||
time.sleep(1)
|
||||
|
||||
workflow_config_content = """{
|
||||
"Workflows": ["pr"],
|
||||
"GitProjectName": "products/SLFO#main",
|
||||
"Organization": "pool",
|
||||
"Branch": "main",
|
||||
"ManualMergeProject": true,
|
||||
"Reviewers": [ "-autogits_obs_staging_bot" ]
|
||||
}"""
|
||||
client.create_file("products", "SLFO", "workflow.config", workflow_config_content)
|
||||
|
||||
staging_config_content = """{
|
||||
"ObsProject": "openSUSE:Leap:16.0",
|
||||
"StagingProject": "openSUSE:Leap:16.0:PullRequest"
|
||||
}"""
|
||||
client.create_file("products", "SLFO", "staging.config", staging_config_content)
|
||||
|
||||
maintainership_content = """{
|
||||
"": ["ownerX","ownerY"],
|
||||
"pkgA": ["ownerA"],
|
||||
"pkgB": ["ownerB","ownerBB"]
|
||||
}"""
|
||||
# Create users from default main config
|
||||
setup_users_from_config(client, workflow_config_content, maintainership_content)
|
||||
|
||||
client.add_collaborator("products", "SLFO", "autogits_obs_staging_bot", "write")
|
||||
client.add_collaborator("products", "SLFO", "workflow-pr", "write")
|
||||
client.add_collaborator("pool", "pkgA", "workflow-pr", "write")
|
||||
client.add_collaborator("pool", "pkgB", "workflow-pr", "write")
|
||||
|
||||
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(1) # Give workflow-pr bot time to become fully active
|
||||
restart_needed = False
|
||||
|
||||
# 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
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def automerge_env(gitea_env):
|
||||
return gitea_env, "products/SLFO", "merge"
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def configured_dev_branch_env(gitea_env: GiteaAPIClient, request):
|
||||
"""
|
||||
Fixture to set up a 'dev' branch in products/SLFO and pool/pkgA,
|
||||
and configure workflow.config in products/SLFO#dev with specific content.
|
||||
Yields (gitea_env, test_full_repo_name, dev_branch_name).
|
||||
"""
|
||||
test_org_name = "products"
|
||||
test_repo_name = "SLFO"
|
||||
test_full_repo_name = f"{test_org_name}/{test_repo_name}"
|
||||
dev_branch_name = "dev"
|
||||
|
||||
workflow_config_content = request.param # Get config content from parametrization
|
||||
|
||||
print(f"--- Setting up 'dev' branch and workflow.config in {test_full_repo_name}#{dev_branch_name} ---")
|
||||
|
||||
# Get the latest commit SHA of the main branch
|
||||
gitea_env.ensure_branch_exists(test_org_name, test_repo_name, "main")
|
||||
main_branch_sha = gitea_env._request("GET", f"repos/{test_org_name}/{test_repo_name}/branches/main").json()["commit"]["id"]
|
||||
|
||||
# Create 'dev' branch from 'main' in products/SLFO
|
||||
gitea_env.create_branch(test_org_name, test_repo_name, dev_branch_name, main_branch_sha)
|
||||
|
||||
# Create 'dev' branch in pool/pkgA as well
|
||||
gitea_env.ensure_branch_exists("pool", "pkgA", "main")
|
||||
pool_pkga_main_sha = gitea_env._request("GET", "repos/pool/pkgA/branches/main").json()["commit"]["id"]
|
||||
gitea_env.create_branch("pool", "pkgA", dev_branch_name, pool_pkga_main_sha)
|
||||
|
||||
# Create 'dev' branch in pool/pkgB as well
|
||||
gitea_env.ensure_branch_exists("pool", "pkgB", "main")
|
||||
pool_pkgb_main_sha = gitea_env._request("GET", "repos/pool/pkgB/branches/main").json()["commit"]["id"]
|
||||
gitea_env.create_branch("pool", "pkgB", dev_branch_name, pool_pkgb_main_sha)
|
||||
|
||||
# Create/update workflow.config with the provided content
|
||||
gitea_env.create_file(test_org_name, test_repo_name, "workflow.config", workflow_config_content, branch=dev_branch_name)
|
||||
|
||||
# For this fixture, we use default maintainership as we don't receive it in request.param
|
||||
maintainership_content = """{
|
||||
"": ["ownerX","ownerY"],
|
||||
"pkgA": ["ownerA"],
|
||||
"pkgB": ["ownerB","ownerBB"]
|
||||
}"""
|
||||
setup_users_from_config(gitea_env, workflow_config_content, maintainership_content)
|
||||
|
||||
print(f"Created workflow.config with specific content in {test_full_repo_name}#{dev_branch_name}")
|
||||
|
||||
# Restart workflow-pr service to pick up new project config
|
||||
gitea_env.restart_service("workflow-pr")
|
||||
time.sleep(1) # Give the service time to restart and re-initialize
|
||||
|
||||
yield gitea_env, test_full_repo_name, dev_branch_name
|
||||
|
||||
def maintainer_env(gitea_env):
|
||||
return gitea_env, "products/SLFO", "maintainer-merge"
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def no_project_git_pr_env(gitea_env: GiteaAPIClient):
|
||||
"""
|
||||
Sets up 'dev' branch in products/SLFO and pool/pkgA,
|
||||
and configures workflow.config in products/SLFO#dev with NoProjectGitPR: true.
|
||||
"""
|
||||
test_org_name = "products"
|
||||
test_repo_name = "SLFO"
|
||||
test_full_repo_name = f"{test_org_name}/{test_repo_name}"
|
||||
dev_branch_name = "dev"
|
||||
|
||||
print(f"--- Setting up workflow.config in {test_full_repo_name}#{dev_branch_name} for No Project PR ---")
|
||||
|
||||
# Get the latest commit SHA of the main branch
|
||||
gitea_env.ensure_branch_exists(test_org_name, test_repo_name, "main")
|
||||
main_branch_sha = gitea_env._request("GET", f"repos/{test_org_name}/{test_repo_name}/branches/main").json()["commit"]["id"]
|
||||
|
||||
# Create 'dev' branch from 'main' in products/SLFO
|
||||
try:
|
||||
gitea_env.create_branch(test_org_name, test_repo_name, dev_branch_name, main_branch_sha)
|
||||
except Exception as e:
|
||||
if "already exists" not in str(e).lower():
|
||||
raise
|
||||
|
||||
# Create 'dev' branch in pool/pkgA as well
|
||||
gitea_env.ensure_branch_exists("pool", "pkgA", "main")
|
||||
pool_pkga_main_sha = gitea_env._request("GET", "repos/pool/pkgA/branches/main").json()["commit"]["id"]
|
||||
try:
|
||||
gitea_env.create_branch("pool", "pkgA", dev_branch_name, pool_pkga_main_sha)
|
||||
except Exception as e:
|
||||
if "already exists" not in str(e).lower():
|
||||
raise
|
||||
|
||||
# Create 'dev' branch in pool/pkgB as well
|
||||
gitea_env.ensure_branch_exists("pool", "pkgB", "main")
|
||||
pool_pkgb_main_sha = gitea_env._request("GET", "repos/pool/pkgB/branches/main").json()["commit"]["id"]
|
||||
try:
|
||||
gitea_env.create_branch("pool", "pkgB", dev_branch_name, pool_pkgb_main_sha)
|
||||
except Exception as e:
|
||||
if "already exists" not in str(e).lower():
|
||||
raise
|
||||
|
||||
# Setup workflow.config to have "NoProjectGitPR": true
|
||||
workflow_config_content = f"""{{
|
||||
"Workflows": ["pr"],
|
||||
"GitProjectName": "{test_full_repo_name}#{dev_branch_name}",
|
||||
"Organization": "pool",
|
||||
"Branch": "dev",
|
||||
"ManualMergeProject": true,
|
||||
"Reviewers": [ "-autogits_obs_staging_bot" ],
|
||||
"NoProjectGitPR": true
|
||||
}}"""
|
||||
gitea_env.create_file(test_org_name, test_repo_name, "workflow.config", workflow_config_content, branch=dev_branch_name)
|
||||
|
||||
maintainership_content = """{
|
||||
"": ["ownerX","ownerY"],
|
||||
"pkgA": ["ownerA"],
|
||||
"pkgB": ["ownerB","ownerBB"]
|
||||
}"""
|
||||
setup_users_from_config(gitea_env, workflow_config_content, maintainership_content)
|
||||
|
||||
print(f"Created workflow.config with NoProjectGitPR: true in {test_full_repo_name}#{dev_branch_name}")
|
||||
|
||||
# Restart workflow-pr service
|
||||
gitea_env.restart_service("workflow-pr")
|
||||
time.sleep(1) # Give the service time to restart and re-initialize
|
||||
|
||||
return gitea_env, test_full_repo_name, dev_branch_name
|
||||
|
||||
def review_required_env(gitea_env):
|
||||
return gitea_env, "products/SLFO", "review-required"
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def test_user_client(gitea_env: GiteaAPIClient):
|
||||
"""
|
||||
Creates a new unique user and returns a GiteaAPIClient instance for them using sudo.
|
||||
This user should not have write permissions to the test repositories by default.
|
||||
"""
|
||||
username = f"user-{int(time.time())}"
|
||||
password = "password123"
|
||||
email = f"{username}@example.com"
|
||||
|
||||
gitea_env.create_user(username, password, email)
|
||||
|
||||
# Grant write access to pool/pkgA
|
||||
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")
|
||||
|
||||
# Use admin token with Sudo header
|
||||
admin_token = gitea_env.headers["Authorization"].split(" ")[1]
|
||||
return GiteaAPIClient(base_url=gitea_env.base_url, token=admin_token, sudo=username)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def automerge_env(gitea_env: GiteaAPIClient):
|
||||
"""
|
||||
Sets up 'merge' branch and custom workflow.config for automerge tests.
|
||||
"""
|
||||
test_org_name = "products"
|
||||
test_repo_name = "SLFO"
|
||||
test_full_repo_name = f"{test_org_name}/{test_repo_name}"
|
||||
merge_branch_name = "merge"
|
||||
|
||||
print(f"--- Setting up '{merge_branch_name}' branch and workflow.config in {test_full_repo_name} ---")
|
||||
|
||||
# Get the latest commit SHA of the main branch
|
||||
gitea_env.ensure_branch_exists(test_org_name, test_repo_name, "main")
|
||||
main_branch_sha = gitea_env._request("GET", f"repos/{test_org_name}/{test_repo_name}/branches/main").json()["commit"]["id"]
|
||||
|
||||
# Create 'merge' branch from 'main' in products/SLFO
|
||||
try:
|
||||
gitea_env.create_branch(test_org_name, test_repo_name, merge_branch_name, main_branch_sha)
|
||||
except Exception as e:
|
||||
if "already exists" not in str(e).lower():
|
||||
raise
|
||||
|
||||
# Create 'merge' branch in pool/pkgA as well
|
||||
gitea_env.ensure_branch_exists("pool", "pkgA", "main")
|
||||
pool_pkga_main_sha = gitea_env._request("GET", "repos/pool/pkgA/branches/main").json()["commit"]["id"]
|
||||
try:
|
||||
gitea_env.create_branch("pool", "pkgA", merge_branch_name, pool_pkga_main_sha)
|
||||
except Exception as e:
|
||||
if "already exists" not in str(e).lower():
|
||||
raise
|
||||
|
||||
# Create 'merge' branch in pool/pkgB as well
|
||||
gitea_env.ensure_branch_exists("pool", "pkgB", "main")
|
||||
pool_pkgb_main_sha = gitea_env._request("GET", "repos/pool/pkgB/branches/main").json()["commit"]["id"]
|
||||
try:
|
||||
gitea_env.create_branch("pool", "pkgB", merge_branch_name, pool_pkgb_main_sha)
|
||||
except Exception as e:
|
||||
if "already exists" not in str(e).lower():
|
||||
raise
|
||||
|
||||
custom_workflow_config = f"""{{
|
||||
"Workflows": ["pr"],
|
||||
"GitProjectName": "{test_full_repo_name}#{merge_branch_name}",
|
||||
"Organization": "pool",
|
||||
"Branch": "{merge_branch_name}",
|
||||
"Reviewers": [ "+usera", "+userb", "-autogits_obs_staging_bot" ]
|
||||
}}"""
|
||||
gitea_env.create_file(test_org_name, test_repo_name, "workflow.config", custom_workflow_config, branch=merge_branch_name)
|
||||
|
||||
maintainership_content = """{
|
||||
"": ["ownerX","ownerY"],
|
||||
"pkgA": ["ownerA"],
|
||||
"pkgB": ["ownerB","ownerBB"]
|
||||
}"""
|
||||
gitea_env.create_file(test_org_name, test_repo_name, "_maintainership.json", maintainership_content, branch=merge_branch_name)
|
||||
|
||||
setup_users_from_config(gitea_env, custom_workflow_config, maintainership_content)
|
||||
|
||||
# Restart workflow-pr service
|
||||
gitea_env.restart_service("workflow-pr")
|
||||
time.sleep(1)
|
||||
|
||||
return gitea_env, test_full_repo_name, merge_branch_name
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def maintainer_env(gitea_env: GiteaAPIClient):
|
||||
"""
|
||||
Sets up 'maintainer-merge' branch and workflow.config without mandatory reviewers.
|
||||
"""
|
||||
test_org_name = "products"
|
||||
test_repo_name = "SLFO"
|
||||
test_full_repo_name = f"{test_org_name}/{test_repo_name}"
|
||||
branch_name = "maintainer-merge"
|
||||
|
||||
print(f"--- Setting up '{branch_name}' branch and workflow.config in {test_full_repo_name} ---")
|
||||
|
||||
# Get the latest commit SHA of the main branch
|
||||
gitea_env.ensure_branch_exists(test_org_name, test_repo_name, "main")
|
||||
main_branch_sha = gitea_env._request("GET", f"repos/{test_org_name}/{test_repo_name}/branches/main").json()["commit"]["id"]
|
||||
|
||||
# Create branch in products/SLFO
|
||||
try:
|
||||
gitea_env.create_branch(test_org_name, test_repo_name, branch_name, main_branch_sha)
|
||||
except Exception as e:
|
||||
if "already exists" not in str(e).lower():
|
||||
raise
|
||||
|
||||
# Create branch in pool/pkgA
|
||||
gitea_env.ensure_branch_exists("pool", "pkgA", "main")
|
||||
pool_pkga_main_sha = gitea_env._request("GET", "repos/pool/pkgA/branches/main").json()["commit"]["id"]
|
||||
try:
|
||||
gitea_env.create_branch("pool", "pkgA", branch_name, pool_pkga_main_sha)
|
||||
except Exception as e:
|
||||
if "already exists" not in str(e).lower():
|
||||
raise
|
||||
|
||||
# Create branch in pool/pkgB
|
||||
gitea_env.ensure_branch_exists("pool", "pkgB", "main")
|
||||
pool_pkgb_main_sha = gitea_env._request("GET", "repos/pool/pkgB/branches/main").json()["commit"]["id"]
|
||||
try:
|
||||
gitea_env.create_branch("pool", "pkgB", branch_name, pool_pkgb_main_sha)
|
||||
except Exception as e:
|
||||
if "already exists" not in str(e).lower():
|
||||
raise
|
||||
|
||||
custom_workflow_config = f"""{{
|
||||
"Workflows": ["pr"],
|
||||
"GitProjectName": "{test_full_repo_name}#{branch_name}",
|
||||
"Organization": "pool",
|
||||
"Branch": "{branch_name}",
|
||||
"Reviewers": [ "-autogits_obs_staging_bot" ]
|
||||
}}"""
|
||||
gitea_env.create_file(test_org_name, test_repo_name, "workflow.config", custom_workflow_config, branch=branch_name)
|
||||
|
||||
maintainership_content = """{
|
||||
"": ["ownerX","ownerY"],
|
||||
"pkgA": ["ownerA"],
|
||||
"pkgB": ["ownerB","ownerBB"]
|
||||
}"""
|
||||
gitea_env.create_file(test_org_name, test_repo_name, "_maintainership.json", maintainership_content, branch=branch_name)
|
||||
|
||||
setup_users_from_config(gitea_env, custom_workflow_config, maintainership_content)
|
||||
|
||||
gitea_env.add_collaborator(test_org_name, test_repo_name, "autogits_obs_staging_bot", "write")
|
||||
|
||||
# Restart workflow-pr service
|
||||
gitea_env.restart_service("workflow-pr")
|
||||
time.sleep(1)
|
||||
|
||||
return gitea_env, test_full_repo_name, branch_name
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def review_required_env(gitea_env: GiteaAPIClient):
|
||||
"""
|
||||
Sets up 'review-required' branch and workflow.config with ReviewRequired: true.
|
||||
"""
|
||||
test_org_name = "products"
|
||||
test_repo_name = "SLFO"
|
||||
test_full_repo_name = f"{test_org_name}/{test_repo_name}"
|
||||
branch_name = "review-required"
|
||||
|
||||
print(f"--- Setting up '{branch_name}' branch and workflow.config in {test_full_repo_name} ---")
|
||||
|
||||
# Get the latest commit SHA of the main branch
|
||||
gitea_env.ensure_branch_exists(test_org_name, test_repo_name, "main")
|
||||
main_branch_sha = gitea_env._request("GET", f"repos/{test_org_name}/{test_repo_name}/branches/main").json()["commit"]["id"]
|
||||
|
||||
# Create branch in products/SLFO
|
||||
try:
|
||||
gitea_env.create_branch(test_org_name, test_repo_name, branch_name, main_branch_sha)
|
||||
except Exception as e:
|
||||
if "already exists" not in str(e).lower():
|
||||
raise
|
||||
|
||||
# Create branch in pool/pkgA
|
||||
gitea_env.ensure_branch_exists("pool", "pkgA", "main")
|
||||
pool_pkga_main_sha = gitea_env._request("GET", "repos/pool/pkgA/branches/main").json()["commit"]["id"]
|
||||
try:
|
||||
gitea_env.create_branch("pool", "pkgA", branch_name, pool_pkga_main_sha)
|
||||
except Exception as e:
|
||||
if "already exists" not in str(e).lower():
|
||||
raise
|
||||
|
||||
# Create branch in pool/pkgB
|
||||
gitea_env.ensure_branch_exists("pool", "pkgB", "main")
|
||||
pool_pkgb_main_sha = gitea_env._request("GET", "repos/pool/pkgB/branches/main").json()["commit"]["id"]
|
||||
try:
|
||||
gitea_env.create_branch("pool", "pkgB", branch_name, pool_pkgb_main_sha)
|
||||
except Exception as e:
|
||||
if "already exists" not in str(e).lower():
|
||||
raise
|
||||
|
||||
custom_workflow_config = f"""{{
|
||||
"Workflows": ["pr"],
|
||||
"GitProjectName": "{test_full_repo_name}#{branch_name}",
|
||||
"Organization": "pool",
|
||||
"Branch": "{branch_name}",
|
||||
"Reviewers": [ "-autogits_obs_staging_bot" ],
|
||||
"ReviewRequired": true
|
||||
}}"""
|
||||
gitea_env.create_file(test_org_name, test_repo_name, "workflow.config", custom_workflow_config, branch=branch_name)
|
||||
|
||||
maintainership_content = """{
|
||||
"": ["ownerX","ownerY"],
|
||||
"pkgA": ["ownerA"],
|
||||
"pkgB": ["ownerB","ownerBB"]
|
||||
}"""
|
||||
gitea_env.create_file(test_org_name, test_repo_name, "_maintainership.json", maintainership_content, branch=branch_name)
|
||||
|
||||
setup_users_from_config(gitea_env, custom_workflow_config, maintainership_content)
|
||||
|
||||
gitea_env.add_collaborator(test_org_name, test_repo_name, "autogits_obs_staging_bot", "write")
|
||||
|
||||
# Restart workflow-pr service
|
||||
gitea_env.restart_service("workflow-pr")
|
||||
time.sleep(1)
|
||||
|
||||
return gitea_env, test_full_repo_name, branch_name
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def ownerA_client(gitea_env: GiteaAPIClient):
|
||||
"""
|
||||
Returns a GiteaAPIClient instance for ownerA.
|
||||
"""
|
||||
admin_token = gitea_env.headers["Authorization"].split(" ")[1]
|
||||
return GiteaAPIClient(base_url=gitea_env.base_url, token=admin_token, sudo="ownerA")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def ownerB_client(gitea_env: GiteaAPIClient):
|
||||
"""
|
||||
Returns a GiteaAPIClient instance for ownerB.
|
||||
"""
|
||||
admin_token = gitea_env.headers["Authorization"].split(" ")[1]
|
||||
return GiteaAPIClient(base_url=gitea_env.base_url, token=admin_token, sudo="ownerB")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def ownerBB_client(gitea_env: GiteaAPIClient):
|
||||
"""
|
||||
Returns a GiteaAPIClient instance for ownerBB.
|
||||
"""
|
||||
admin_token = gitea_env.headers["Authorization"].split(" ")[1]
|
||||
return GiteaAPIClient(base_url=gitea_env.base_url, token=admin_token, sudo="ownerBB")
|
||||
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)
|
||||
|
||||
@@ -226,6 +226,21 @@ index 0000000..{pkg_b_sha}
|
||||
self._request("PATCH", f"repos/{org_name}/{repo_name}", json=repo_data)
|
||||
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"):
|
||||
file_info = self.get_file_info(owner, repo, file_path, branch=branch)
|
||||
|
||||
97
integration/tests/workflow_pr_label_test.py
Normal file
97
integration/tests/workflow_pr_label_test.py
Normal file
@@ -0,0 +1,97 @@
|
||||
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}'.")
|
||||
@@ -5,6 +5,43 @@ 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):
|
||||
|
||||
@@ -6,7 +6,7 @@ COPY integration/rabbitmq-config/certs/cert.pem /usr/share/pki/trust/anchors/git
|
||||
RUN update-ca-certificates
|
||||
|
||||
# Install git and ssh
|
||||
RUN zypper -n in git-core openssh-clients binutils git-lfs
|
||||
RUN zypper -n in git-core openssh-clients binutils git-lfs || (tail -n 1000 /var/log/zypper.log; exit 1)
|
||||
|
||||
# Copy the pre-built binary into the container
|
||||
COPY workflow-pr/workflow-pr /usr/local/bin/workflow-pr
|
||||
|
||||
@@ -9,7 +9,7 @@ RUN zypper ar -f http://download.opensuse.org/repositories/devel:/Factory:/git-w
|
||||
RUN zypper --gpg-auto-import-keys ref
|
||||
|
||||
# Install git and ssh
|
||||
RUN zypper -n in git-core openssh-clients autogits-workflow-pr binutils git-lfs
|
||||
RUN zypper -n in git-core openssh-clients autogits-workflow-pr binutils git-lfs || ( tail -n 1000 /var/log/zypper.log; exit 1 )
|
||||
|
||||
COPY integration/workflow-pr/entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
RUN chmod +4755 /usr/local/bin/entrypoint.sh
|
||||
|
||||
@@ -3,5 +3,6 @@
|
||||
"products/SLFO#dev",
|
||||
"products/SLFO#merge",
|
||||
"products/SLFO#maintainer-merge",
|
||||
"products/SLFO#review-required"
|
||||
"products/SLFO#review-required",
|
||||
"products/SLFO#label-test"
|
||||
]
|
||||
|
||||
@@ -50,10 +50,6 @@ const (
|
||||
|
||||
var runId uint
|
||||
|
||||
var GitWorkTreeAllocate func(string, string, string) (common.GitHandlerGenerator, error) = func(basePath, gitAuthor, email string) (common.GitHandlerGenerator, error) {
|
||||
return common.AllocateGitWorkTree(basePath, gitAuthor, email)
|
||||
}
|
||||
|
||||
func FetchPrGit(git common.Git, pr *models.PullRequest) error {
|
||||
// clone PR head via base (target) repo
|
||||
cloneURL := pr.Base.Repo.CloneURL
|
||||
@@ -148,9 +144,9 @@ func ProcessBuildStatus(project *common.BuildResultList) BuildStatusSummary {
|
||||
|
||||
func ProcessRepoBuildStatus(results []*common.PackageBuildStatus) (status BuildStatusSummary) {
|
||||
|
||||
PackageBuildStatusSorter := func(a, b *common.PackageBuildStatus) int {
|
||||
return strings.Compare(a.Package, b.Package)
|
||||
}
|
||||
PackageBuildStatusSorter := func(a, b *common.PackageBuildStatus) int {
|
||||
return strings.Compare(a.Package, b.Package)
|
||||
}
|
||||
|
||||
common.LogDebug("******* RESULTS: ")
|
||||
data, _ := xml.MarshalIndent(results, "", " ")
|
||||
@@ -195,23 +191,24 @@ func GetPackageBuildStatus(project *common.BuildResultList, packageName string)
|
||||
return true, BuildStatusSummaryUnknown // true for 'missing'
|
||||
}
|
||||
|
||||
// Check for any unfinished builds
|
||||
// Check for any failures
|
||||
for _, pkgStatus := range packageStatuses {
|
||||
res, ok := common.ObsBuildStatusDetails[pkgStatus.Code]
|
||||
if !ok {
|
||||
common.LogInfo("unknown package result code:", pkgStatus.Code, "for package:", pkgStatus.Package)
|
||||
return false, BuildStatusSummaryUnknown
|
||||
}
|
||||
if !res.Finished {
|
||||
return false, BuildStatusSummaryBuilding
|
||||
if !res.Success {
|
||||
return false, BuildStatusSummaryFailed
|
||||
}
|
||||
}
|
||||
|
||||
// Check for any failures
|
||||
// Check for any unfinished builds
|
||||
for _, pkgStatus := range packageStatuses {
|
||||
res, _ := common.ObsBuildStatusDetails[pkgStatus.Code]
|
||||
if !res.Success {
|
||||
return false, BuildStatusSummaryFailed
|
||||
// 'ok' is already checked in the loop above
|
||||
if !res.Finished {
|
||||
return false, BuildStatusSummaryBuilding
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,7 +216,7 @@ func GetPackageBuildStatus(project *common.BuildResultList, packageName string)
|
||||
return false, BuildStatusSummarySuccess
|
||||
}
|
||||
|
||||
func GenerateObsPrjMeta(obs common.ObsClientInterface, git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingPrj, buildPrj string, stagingMasterPrj string) (*common.ProjectMeta, error) {
|
||||
func GenerateObsPrjMeta(git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingPrj, buildPrj string, stagingMasterPrj string) (*common.ProjectMeta, error) {
|
||||
common.LogDebug("repo content fetching ...")
|
||||
err := FetchPrGit(git, pr)
|
||||
if err != nil {
|
||||
@@ -263,13 +260,13 @@ func GenerateObsPrjMeta(obs common.ObsClientInterface, git common.Git, gitea com
|
||||
}
|
||||
|
||||
common.LogDebug("Trying first staging master project: ", stagingMasterPrj)
|
||||
meta, err := obs.GetProjectMeta(stagingMasterPrj)
|
||||
meta, err := ObsClient.GetProjectMeta(stagingMasterPrj)
|
||||
if err == nil {
|
||||
// success, so we use that staging master project as our build project
|
||||
buildPrj = stagingMasterPrj
|
||||
} else {
|
||||
common.LogInfo("error fetching project meta for ", stagingMasterPrj, ". Fall Back to ", buildPrj)
|
||||
meta, err = obs.GetProjectMeta(buildPrj)
|
||||
meta, err = ObsClient.GetProjectMeta(buildPrj)
|
||||
}
|
||||
if err != nil {
|
||||
common.LogError("error fetching project meta for", buildPrj, ". Err:", err)
|
||||
@@ -333,10 +330,10 @@ func GenerateObsPrjMeta(obs common.ObsClientInterface, git common.Git, gitea com
|
||||
// stagingProject:$buildProject
|
||||
// ^- stagingProject:$buildProject:$subProjectName (based on templateProject)
|
||||
|
||||
func CreateQASubProject(obs common.ObsClientInterface, stagingConfig *common.StagingConfig, git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingProject, templateProject, subProjectName string, buildDisableRepos []string) error {
|
||||
func CreateQASubProject(stagingConfig *common.StagingConfig, git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingProject, templateProject, subProjectName string, buildDisableRepos []string) error {
|
||||
common.LogDebug("Setup QA sub projects")
|
||||
common.LogDebug("reading templateProject ", templateProject)
|
||||
templateMeta, err := obs.GetProjectMeta(templateProject)
|
||||
templateMeta, err := ObsClient.GetProjectMeta(templateProject)
|
||||
if err != nil {
|
||||
common.LogError("error fetching template project meta for", templateProject, ":", err)
|
||||
return err
|
||||
@@ -346,10 +343,10 @@ func CreateQASubProject(obs common.ObsClientInterface, stagingConfig *common.Sta
|
||||
templateMeta.Name = stagingProject + ":" + subProjectName
|
||||
// freeze tag for now
|
||||
if len(templateMeta.ScmSync) > 0 {
|
||||
repository, err := url.Parse(templateMeta.ScmSync)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
repository, err := url.Parse(templateMeta.ScmSync)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
common.LogDebug("getting data for ", repository.EscapedPath())
|
||||
split := strings.Split(repository.EscapedPath(), "/")
|
||||
@@ -357,12 +354,12 @@ func CreateQASubProject(obs common.ObsClientInterface, stagingConfig *common.Sta
|
||||
|
||||
common.LogDebug("getting commit for ", org, " repo ", repo, " fragment ", repository.Fragment)
|
||||
branch, err := gitea.GetCommit(org, repo, repository.Fragment)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// set expanded commit url
|
||||
repository.Fragment = branch.SHA
|
||||
repository.Fragment = branch.SHA
|
||||
templateMeta.ScmSync = repository.String()
|
||||
common.LogDebug("Setting scmsync url to ", templateMeta.ScmSync)
|
||||
}
|
||||
@@ -409,11 +406,11 @@ func CreateQASubProject(obs common.ObsClientInterface, stagingConfig *common.Sta
|
||||
templateMeta.Repositories[idx].Paths[pidx].Project = templateMeta.Name
|
||||
} else
|
||||
// Check for path prefixes against a template project inside of template project area
|
||||
if strings.HasPrefix(path.Project, stagingConfig.StagingProject+":") {
|
||||
if strings.HasPrefix(path.Project, stagingConfig.StagingProject + ":") {
|
||||
newProjectName := stagingProject
|
||||
// find project name
|
||||
for _, setup := range stagingConfig.QA {
|
||||
if setup.Origin == path.Project {
|
||||
if setup.Origin == path.Project {
|
||||
common.LogDebug(" Match:", setup.Origin)
|
||||
newProjectName = newProjectName + ":" + setup.Name
|
||||
common.LogDebug(" New:", newProjectName)
|
||||
@@ -421,14 +418,14 @@ func CreateQASubProject(obs common.ObsClientInterface, stagingConfig *common.Sta
|
||||
}
|
||||
}
|
||||
templateMeta.Repositories[idx].Paths[pidx].Project = newProjectName
|
||||
common.LogDebug(" Matched prefix")
|
||||
common.LogDebug(" Matched prefix")
|
||||
}
|
||||
common.LogDebug(" Path using project ", templateMeta.Repositories[idx].Paths[pidx].Project)
|
||||
}
|
||||
}
|
||||
|
||||
if !IsDryRun {
|
||||
err = obs.SetProjectMeta(templateMeta)
|
||||
err = ObsClient.SetProjectMeta(templateMeta)
|
||||
if err != nil {
|
||||
common.LogError("cannot create project:", templateMeta.Name, err)
|
||||
x, _ := xml.MarshalIndent(templateMeta, "", " ")
|
||||
@@ -442,10 +439,10 @@ func CreateQASubProject(obs common.ObsClientInterface, stagingConfig *common.Sta
|
||||
return nil
|
||||
}
|
||||
|
||||
func StartOrUpdateBuild(obs common.ObsClientInterface, config *common.StagingConfig, git common.Git, gitea common.Gitea, pr *models.PullRequest) (RequestModification, error) {
|
||||
func StartOrUpdateBuild(config *common.StagingConfig, git common.Git, gitea common.Gitea, pr *models.PullRequest) (RequestModification, error) {
|
||||
common.LogDebug("fetching OBS project Meta")
|
||||
obsPrProject := GetObsProjectAssociatedWithPr(config, obs.GetHomeProject(), pr)
|
||||
meta, err := obs.GetProjectMeta(obsPrProject)
|
||||
obsPrProject := GetObsProjectAssociatedWithPr(config, ObsClient.HomeProject, pr)
|
||||
meta, err := ObsClient.GetProjectMeta(obsPrProject)
|
||||
if err != nil {
|
||||
common.LogError("error fetching project meta for", obsPrProject, ":", err)
|
||||
return RequestModificationNoChange, err
|
||||
@@ -470,7 +467,7 @@ func StartOrUpdateBuild(obs common.ObsClientInterface, config *common.StagingCon
|
||||
if meta == nil {
|
||||
// new build
|
||||
common.LogDebug(" Staging master:", config.StagingProject)
|
||||
meta, err = GenerateObsPrjMeta(obs, git, gitea, pr, obsPrProject, config.ObsProject, config.StagingProject)
|
||||
meta, err = GenerateObsPrjMeta(git, gitea, pr, obsPrProject, config.ObsProject, config.StagingProject)
|
||||
if err != nil {
|
||||
return RequestModificationNoChange, err
|
||||
}
|
||||
@@ -482,7 +479,7 @@ func StartOrUpdateBuild(obs common.ObsClientInterface, config *common.StagingCon
|
||||
common.LogDebug("Creating build project:")
|
||||
common.LogDebug(" meta:", string(x))
|
||||
} else {
|
||||
err = obs.SetProjectMeta(meta)
|
||||
err = ObsClient.SetProjectMeta(meta)
|
||||
if err != nil {
|
||||
x, _ := xml.MarshalIndent(meta, "", " ")
|
||||
common.LogDebug(" meta:", string(x))
|
||||
@@ -553,7 +550,7 @@ func ParseNotificationToPR(thread *models.NotificationThread) (org string, repo
|
||||
return
|
||||
}
|
||||
|
||||
func ProcessPullNotification(obs common.ObsClientInterface, gitea common.Gitea, thread *models.NotificationThread) {
|
||||
func ProcessPullNotification(gitea common.Gitea, thread *models.NotificationThread) {
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err != nil {
|
||||
@@ -569,7 +566,7 @@ func ProcessPullNotification(obs common.ObsClientInterface, gitea common.Gitea,
|
||||
}
|
||||
common.LogInfo("processing PR:", org, "/", repo, "#", num)
|
||||
|
||||
done, err := ProcessPullRequest(obs, gitea, org, repo, num)
|
||||
done, err := ProcessPullRequest(gitea, org, repo, num)
|
||||
if !IsDryRun && err == nil && done {
|
||||
gitea.SetNotificationRead(thread.ID)
|
||||
} else if err != nil {
|
||||
@@ -579,7 +576,7 @@ func ProcessPullNotification(obs common.ObsClientInterface, gitea common.Gitea,
|
||||
|
||||
var CleanedUpIssues []int64 = []int64{}
|
||||
|
||||
func CleanupPullNotification(obs common.ObsClientInterface, gitea common.Gitea, thread *models.NotificationThread) (CleanupComplete bool) {
|
||||
func CleanupPullNotification(gitea common.Gitea, thread *models.NotificationThread) (CleanupComplete bool) {
|
||||
defer func() {
|
||||
err := recover()
|
||||
if err != nil {
|
||||
@@ -646,8 +643,8 @@ func CleanupPullNotification(obs common.ObsClientInterface, gitea common.Gitea,
|
||||
return false
|
||||
}
|
||||
|
||||
stagingProject := GetObsProjectAssociatedWithPr(config, obs.GetHomeProject(), pr)
|
||||
if prj, err := obs.GetProjectMeta(stagingProject); err != nil {
|
||||
stagingProject := GetObsProjectAssociatedWithPr(config, ObsClient.HomeProject, pr)
|
||||
if prj, err := ObsClient.GetProjectMeta(stagingProject); err != nil {
|
||||
common.LogError("Failed fetching meta for project:", stagingProject, ". Not cleaning up")
|
||||
return false
|
||||
} else if prj == nil && err == nil {
|
||||
@@ -661,13 +658,13 @@ func CleanupPullNotification(obs common.ObsClientInterface, gitea common.Gitea,
|
||||
project := stagingProject + ":" + qa.Name
|
||||
common.LogDebug("Cleaning up QA staging", project)
|
||||
if !IsDryRun {
|
||||
if err := obs.DeleteProject(project); err != nil {
|
||||
if err := ObsClient.DeleteProject(project); err != nil {
|
||||
common.LogError("Failed to cleanup QA staging", project, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !IsDryRun {
|
||||
if err := obs.DeleteProject(stagingProject); err != nil {
|
||||
if err := ObsClient.DeleteProject(stagingProject); err != nil {
|
||||
common.LogError("Failed to cleanup staging", stagingProject, err)
|
||||
}
|
||||
}
|
||||
@@ -688,7 +685,7 @@ func SetStatus(gitea common.Gitea, org, repo, hash string, status *models.Commit
|
||||
return err
|
||||
}
|
||||
|
||||
func CommentPROnce(gitea common.Gitea, org string, repo string, prNum int64, msg string) {
|
||||
func commentOnPackagePR(gitea common.Gitea, org string, repo string, prNum int64, msg string) {
|
||||
if IsDryRun {
|
||||
common.LogInfo("Would comment on package PR %s/%s#%d: %s", org, repo, prNum, msg)
|
||||
return
|
||||
@@ -700,18 +697,6 @@ func CommentPROnce(gitea common.Gitea, org string, repo string, prNum int64, msg
|
||||
return
|
||||
}
|
||||
|
||||
timeline, err := gitea.GetTimeline(org, repo, prNum)
|
||||
if err != nil {
|
||||
common.LogError("Failed to get timeline for PR %s/%s#%d: %v", org, repo, prNum, err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, t := range timeline {
|
||||
if t.User != nil && t.User.UserName == BotUser && t.Type == common.TimelineCommentType_Comment && t.Body == msg {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = gitea.AddComment(pr, msg)
|
||||
if err != nil {
|
||||
common.LogError("Failed to comment on package PR %s/%s#%d: %v", org, repo, prNum, err)
|
||||
@@ -719,7 +704,7 @@ func CommentPROnce(gitea common.Gitea, org string, repo string, prNum int64, msg
|
||||
}
|
||||
|
||||
// Create and remove QA projects
|
||||
func ProcessQaProjects(obs common.ObsClientInterface, stagingConfig *common.StagingConfig, git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingProject string) ([]string, string) {
|
||||
func ProcessQaProjects(stagingConfig *common.StagingConfig, git common.Git, gitea common.Gitea, pr *models.PullRequest, stagingProject string) []string {
|
||||
usedQAprojects := make([]string, 0)
|
||||
prLabelNames := make(map[string]int)
|
||||
for _, label := range pr.Labels {
|
||||
@@ -732,7 +717,7 @@ func ProcessQaProjects(obs common.ObsClientInterface, stagingConfig *common.Stag
|
||||
if _, ok := prLabelNames[setup.Label]; !ok {
|
||||
if !IsDryRun {
|
||||
// blindly remove, will fail when not existing
|
||||
obs.DeleteProject(QAproject)
|
||||
ObsClient.DeleteProject(QAproject)
|
||||
}
|
||||
common.LogInfo("QA project ", setup.Name, "has no matching Label")
|
||||
continue
|
||||
@@ -741,22 +726,24 @@ func ProcessQaProjects(obs common.ObsClientInterface, stagingConfig *common.Stag
|
||||
|
||||
usedQAprojects = append(usedQAprojects, QAproject)
|
||||
// check for existens first, no error, but no meta is a 404
|
||||
if meta, err := obs.GetProjectMeta(QAproject); meta == nil && err == nil {
|
||||
if meta, err := ObsClient.GetProjectMeta(QAproject); meta == nil && err == nil {
|
||||
common.LogInfo("Create QA project ", QAproject)
|
||||
CreateQASubProject(obs, stagingConfig, git, gitea, pr,
|
||||
CreateQASubProject(stagingConfig, git, gitea, pr,
|
||||
stagingProject,
|
||||
setup.Origin,
|
||||
setup.Name,
|
||||
setup.BuildDisableRepos)
|
||||
msg = msg + "QA Project added: " + ObsWebHost + "/project/show/" +
|
||||
QAproject + "\n"
|
||||
msg = msg + "QA Project added: " + ObsWebHost + "/project/show/" +
|
||||
QAproject + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
return usedQAprojects, msg
|
||||
if len(msg) > 1 {
|
||||
gitea.AddComment(pr, msg)
|
||||
}
|
||||
return usedQAprojects
|
||||
}
|
||||
|
||||
func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org, repo string, id int64) (bool, error) {
|
||||
func ProcessPullRequest(gitea common.Gitea, org, repo string, id int64) (bool, error) {
|
||||
dir, err := os.MkdirTemp(os.TempDir(), BotName)
|
||||
common.PanicOnError(err)
|
||||
if IsDryRun {
|
||||
@@ -765,7 +752,7 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
|
||||
defer os.RemoveAll(dir)
|
||||
}
|
||||
|
||||
gh, err := GitWorkTreeAllocate(dir, GitAuthor, "noaddress@suse.de")
|
||||
gh, err := common.AllocateGitWorkTree(dir, GitAuthor, "noaddress@suse.de")
|
||||
common.PanicOnError(err)
|
||||
|
||||
git, err := gh.CreateGitHandler(org)
|
||||
@@ -810,7 +797,7 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
|
||||
if err != nil {
|
||||
common.LogError("Staging config", common.StagingConfigFile, "not found in PR to the project. Aborting.")
|
||||
if !IsDryRun {
|
||||
_, _ = gitea.AddReviewComment(pr, common.ReviewStateRequestChanges, "Cannot find project config in PR: "+common.ProjectConfigFile)
|
||||
_, err = gitea.AddReviewComment(pr, common.ReviewStateRequestChanges, "Cannot find project config in PR: "+common.ProjectConfigFile)
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
@@ -830,7 +817,7 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
|
||||
return true, nil
|
||||
}
|
||||
|
||||
meta, err := obs.GetProjectMeta(stagingConfig.ObsProject)
|
||||
meta, err := ObsClient.GetProjectMeta(stagingConfig.ObsProject)
|
||||
if err != nil || meta == nil {
|
||||
common.LogError("Cannot find reference project meta:", stagingConfig.ObsProject, err)
|
||||
if !IsDryRun && err == nil {
|
||||
@@ -959,8 +946,8 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
|
||||
}
|
||||
|
||||
common.LogDebug("ObsProject:", stagingConfig.ObsProject)
|
||||
stagingProject := GetObsProjectAssociatedWithPr(stagingConfig, obs.GetHomeProject(), pr)
|
||||
change, err := StartOrUpdateBuild(obs, stagingConfig, git, gitea, pr)
|
||||
stagingProject := GetObsProjectAssociatedWithPr(stagingConfig, ObsClient.HomeProject, pr)
|
||||
change, err := StartOrUpdateBuild(stagingConfig, git, gitea, pr)
|
||||
status := &models.CommitStatus{
|
||||
Context: BotName,
|
||||
Description: "OBS Staging build",
|
||||
@@ -991,8 +978,11 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
|
||||
|
||||
SetStatus(gitea, org, repo, pr.Head.Sha, status)
|
||||
}
|
||||
if change != RequestModificationNoChange && !IsDryRun {
|
||||
gitea.AddComment(pr, msg)
|
||||
}
|
||||
|
||||
stagingResult, err := obs.BuildStatus(stagingProject)
|
||||
stagingResult, err := ObsClient.BuildStatus(stagingProject)
|
||||
if err != nil {
|
||||
common.LogError("failed fetching stage project status for", stagingProject, ":", err)
|
||||
}
|
||||
@@ -1000,12 +990,7 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
|
||||
_, packagePRs := common.ExtractDescriptionAndPRs(bufio.NewScanner(strings.NewReader(pr.Body)))
|
||||
|
||||
// always update QA projects because Labels can change
|
||||
qaProjects, qaProjectMsg := ProcessQaProjects(obs, stagingConfig, git, gitea, pr, stagingProject)
|
||||
|
||||
if change != RequestModificationNoChange && !IsDryRun {
|
||||
msg += qaProjectMsg
|
||||
CommentPROnce(gitea, org, repo, id, msg)
|
||||
}
|
||||
qaProjects := ProcessQaProjects(stagingConfig, git, gitea, pr, stagingProject)
|
||||
|
||||
done := false
|
||||
overallBuildStatus := ProcessBuildStatus(stagingResult)
|
||||
@@ -1013,7 +998,7 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
|
||||
if len(qaProjects) > 0 && overallBuildStatus == BuildStatusSummarySuccess {
|
||||
seperator := " in "
|
||||
for _, qaProject := range qaProjects {
|
||||
qaResult, err := obs.BuildStatus(qaProject)
|
||||
qaResult, err := ObsClient.BuildStatus(qaProject)
|
||||
if err != nil {
|
||||
common.LogError("failed fetching stage project status for", qaProject, ":", err)
|
||||
}
|
||||
@@ -1073,7 +1058,7 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
|
||||
default:
|
||||
continue
|
||||
}
|
||||
CommentPROnce(gitea, packagePR.Org, packagePR.Repo, packagePR.Num, msg)
|
||||
commentOnPackagePR(gitea, packagePR.Org, packagePR.Repo, packagePR.Num, msg)
|
||||
}
|
||||
|
||||
if len(missingPkgs) > 0 {
|
||||
@@ -1083,7 +1068,10 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
|
||||
msg = msg + " - " + pkg + "\n"
|
||||
}
|
||||
common.LogInfo(msg)
|
||||
CommentPROnce(gitea, org, repo, id, msg)
|
||||
err := gitea.AddComment(pr, msg)
|
||||
if err != nil {
|
||||
common.LogError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1102,7 +1090,8 @@ func ProcessPullRequest(obs common.ObsClientInterface, gitea common.Gitea, org,
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func PollWorkNotifications(obs common.ObsClientInterface, gitea common.Gitea) {
|
||||
func PollWorkNotifications(giteaUrl string) {
|
||||
gitea := common.AllocateGiteaTransport(giteaUrl)
|
||||
data, err := gitea.GetNotifications(common.GiteaNotificationType_Pull, nil)
|
||||
|
||||
if err != nil {
|
||||
@@ -1118,7 +1107,7 @@ func PollWorkNotifications(obs common.ObsClientInterface, gitea common.Gitea) {
|
||||
if !ListPullNotificationsOnly {
|
||||
switch notification.Subject.Type {
|
||||
case "Pull":
|
||||
ProcessPullNotification(obs, gitea, notification)
|
||||
ProcessPullNotification(gitea, notification)
|
||||
default:
|
||||
if !IsDryRun {
|
||||
gitea.SetNotificationRead(notification.ID)
|
||||
@@ -1141,7 +1130,7 @@ func PollWorkNotifications(obs common.ObsClientInterface, gitea common.Gitea) {
|
||||
continue
|
||||
}
|
||||
|
||||
cleanupFinished = CleanupPullNotification(obs, gitea, n) && cleanupFinished
|
||||
cleanupFinished = CleanupPullNotification(gitea, n) && cleanupFinished
|
||||
}
|
||||
} else if err != nil {
|
||||
common.LogError(err)
|
||||
@@ -1155,8 +1144,7 @@ var ObsApiHost string
|
||||
var ObsWebHost string
|
||||
var IsDryRun bool
|
||||
var ProcessPROnly string
|
||||
var ObsClient common.ObsClientInterface
|
||||
var BotUser string
|
||||
var ObsClient *common.ObsClient
|
||||
|
||||
func ObsWebHostFromApiHost(apihost string) string {
|
||||
u, err := url.Parse(apihost)
|
||||
@@ -1221,18 +1209,9 @@ func main() {
|
||||
}
|
||||
|
||||
if len(*buildRoot) > 0 {
|
||||
ObsClient.SetHomeProject(*buildRoot)
|
||||
ObsClient.HomeProject = *buildRoot
|
||||
}
|
||||
|
||||
gitea := common.AllocateGiteaTransport(GiteaUrl)
|
||||
|
||||
user, err := gitea.GetCurrentUser()
|
||||
if err != nil {
|
||||
common.LogError("Cannot fetch current user:", err)
|
||||
return
|
||||
}
|
||||
BotUser = user.UserName
|
||||
|
||||
if len(*ProcessPROnly) > 0 {
|
||||
rx := regexp.MustCompile("^([^/#]+)/([^/#]+)#([0-9]+)$")
|
||||
m := rx.FindStringSubmatch(*ProcessPROnly)
|
||||
@@ -1241,14 +1220,15 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
gitea := common.AllocateGiteaTransport(GiteaUrl)
|
||||
id, _ := strconv.ParseInt(m[3], 10, 64)
|
||||
|
||||
ProcessPullRequest(ObsClient, gitea, m[1], m[2], id)
|
||||
ProcessPullRequest(gitea, m[1], m[2], id)
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
PollWorkNotifications(ObsClient, gitea)
|
||||
PollWorkNotifications(GiteaUrl)
|
||||
common.LogInfo("Poll cycle finished")
|
||||
time.Sleep(5 * time.Minute)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user