From af2ff0bdd217713b94e7c9c6fde318e6212aaac42a06af317f6d73b2ec0dd565 Mon Sep 17 00:00:00 2001 From: Adam Majer Date: Wed, 25 Feb 2026 18:51:08 +0100 Subject: [PATCH 1/3] common: check for old pending request reviews Timeline events will contain Reviews and ReviewRequests and ReviewDismissed events. We need to handle this at event parsing time and not to punt this to the query functions later on. If the last event is an actual review, we use this. If no review, check if last event associated with the reviewer is Dismissed or Requested Review but not if a dismissed Review preceeds it. --- common/pr.go | 6 ++-- common/pr_test.go | 32 +++++++++------------ common/reviews.go | 64 +++++++++++++++++++++++++++++------------- common/reviews_test.go | 49 +++++++++++++++++++++++++++++++- common/timeline.go | 1 + 5 files changed, 110 insertions(+), 42 deletions(-) diff --git a/common/pr.go b/common/pr.go index 013f421..9ccf31c 100644 --- a/common/pr.go +++ b/common/pr.go @@ -468,7 +468,7 @@ func (rs *PRSet) IsApproved(gitea GiteaPRChecker, maintainers MaintainershipData LogError("Cannot fetch gita reaviews for PR:", err) return false } - r.RequestedReviewers = reviewers + r.SetRequiredReviewers(reviewers) prjgit.Reviews = r if prjgit.Reviews.IsManualMergeOK() { is_manually_reviewed_ok = true @@ -489,7 +489,7 @@ func (rs *PRSet) IsApproved(gitea GiteaPRChecker, maintainers MaintainershipData LogError("Cannot fetch gita reaviews for PR:", err) return false } - r.RequestedReviewers = reviewers + r.SetRequiredReviewers(reviewers) pr.Reviews = r if !pr.Reviews.IsManualMergeOK() { LogInfo("Not approved manual merge. PR:", pr.PR.URL) @@ -530,7 +530,7 @@ func (rs *PRSet) IsApproved(gitea GiteaPRChecker, maintainers MaintainershipData LogError("Cannot fetch gitea reaviews for PR:", err) return false } - r.RequestedReviewers = reviewers + r.SetRequiredReviewers(reviewers) is_manually_reviewed_ok = r.IsApproved() LogDebug("PR to", pr.PR.Base.Repo.Name, "reviewed?", is_manually_reviewed_ok) diff --git a/common/pr_test.go b/common/pr_test.go index 4b9fe63..90059a9 100644 --- a/common/pr_test.go +++ b/common/pr_test.go @@ -807,9 +807,8 @@ func TestFindMissingAndExtraReviewers(t *testing.T) { Base: &models.PRBranchInfo{Name: "main", Repo: &models.Repository{Name: "pkg", Owner: &models.User{UserName: "org"}}}, }, Reviews: &common.PRReviews{ - Reviews: []*models.PullReview{{State: common.ReviewStateRequestReview, User: &models.User{UserName: "m1"}}}, - RequestedReviewers: []string{"m1"}, - FullTimeline: []*models.TimelineComment{ + Reviews: []*models.PullReview{{State: common.ReviewStateRequestReview, User: &models.User{UserName: "m1"}}}, + RequestedReviewers: []*models.TimelineComment{ {User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "m1"}, Type: common.TimelineCommentType_ReviewRequested}, }, }, @@ -919,8 +918,7 @@ func TestFindMissingAndExtraReviewers(t *testing.T) { }, Reviews: &common.PRReviews{ Reviews: []*models.PullReview{{State: common.ReviewStateRequestReview, User: &models.User{UserName: "reviewer"}}}, - RequestedReviewers: []string{"reviewer"}, - FullTimeline: []*models.TimelineComment{{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "reviewer"}}}, + RequestedReviewers: []*models.TimelineComment{{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "reviewer"}}}, }, }, { @@ -930,8 +928,7 @@ func TestFindMissingAndExtraReviewers(t *testing.T) { }, Reviews: &common.PRReviews{ Reviews: []*models.PullReview{{State: common.ReviewStateRequestReview, User: &models.User{UserName: "reviewer"}}}, - RequestedReviewers: []string{"reviewer"}, - FullTimeline: []*models.TimelineComment{{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "reviewer"}}}, + RequestedReviewers: []*models.TimelineComment{{Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "reviewer"}}}, }, }, }, @@ -966,8 +963,7 @@ func TestFindMissingAndExtraReviewers(t *testing.T) { {State: common.ReviewStateApproved, User: &models.User{UserName: "pkgmaintainer"}}, {State: common.ReviewStatePending, User: &models.User{UserName: "prjmaintainer"}}, }, - RequestedReviewers: []string{"user2", "pkgmaintainer", "prjmaintainer"}, - FullTimeline: []*models.TimelineComment{ + RequestedReviewers: []*models.TimelineComment{ {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "user2"}}, {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "pkgmaintainer"}}, {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prjmaintainer"}}, @@ -985,8 +981,7 @@ func TestFindMissingAndExtraReviewers(t *testing.T) { {State: common.ReviewStateRequestChanges, User: &models.User{UserName: "user1"}}, {State: common.ReviewStateRequestReview, User: &models.User{UserName: "autogits_obs_staging_bot"}}, }, - RequestedReviewers: []string{"user1", "autogits_obs_staging_bot"}, - FullTimeline: []*models.TimelineComment{ + RequestedReviewers: []*models.TimelineComment{ {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "user1"}}, {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "autogits_obs_staging_bot"}}, }, @@ -1026,8 +1021,7 @@ func TestFindMissingAndExtraReviewers(t *testing.T) { {State: common.ReviewStatePending, User: &models.User{UserName: "prj2"}}, {State: common.ReviewStatePending, User: &models.User{UserName: "someother"}}, }, - RequestedReviewers: []string{"user2", "pkgmaintainer", "prjmaintainer", "pkgm1", "pkgm2", "someother", "prj1", "prj2"}, - FullTimeline: []*models.TimelineComment{ + RequestedReviewers: []*models.TimelineComment{ {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "pkgmaintainer"}}, {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prjmaintainer"}}, {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prj1"}}, @@ -1050,8 +1044,7 @@ func TestFindMissingAndExtraReviewers(t *testing.T) { {State: common.ReviewStatePending, User: &models.User{UserName: "prj1"}}, {State: common.ReviewStatePending, User: &models.User{UserName: "prj2"}}, }, - RequestedReviewers: []string{"user1", "autogits_obs_staging_bot", "prj1", "prj2"}, - FullTimeline: []*models.TimelineComment{ + RequestedReviewers: []*models.TimelineComment{ {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "autogits_obs_staging_bot"}}, {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prj1"}}, {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prj2"}}, @@ -1090,8 +1083,7 @@ func TestFindMissingAndExtraReviewers(t *testing.T) { {State: common.ReviewStateRequestReview, User: &models.User{UserName: "prj1"}}, {State: common.ReviewStateRequestReview, User: &models.User{UserName: "someother"}}, }, - RequestedReviewers: []string{"user2", "pkgmaintainer", "prjmaintainer", "pkgm1", "someother", "prj1"}, - FullTimeline: []*models.TimelineComment{ + RequestedReviewers: []*models.TimelineComment{ {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "pkgm1"}}, {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "pkgmaintainer"}}, {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prjmaintainer"}}, @@ -1112,8 +1104,7 @@ func TestFindMissingAndExtraReviewers(t *testing.T) { {State: common.ReviewStateRequestReview, User: &models.User{UserName: "autogits_obs_staging_bot"}}, {State: common.ReviewStateRequestReview, User: &models.User{UserName: "prj1"}}, }, - RequestedReviewers: []string{"user1", "autogits_obs_staging_bot", "prj1"}, - FullTimeline: []*models.TimelineComment{ + RequestedReviewers: []*models.TimelineComment{ {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "autogits_obs_staging_bot"}}, {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "bot"}, Assignee: &models.User{UserName: "prj1"}}, {Type: common.TimelineCommentType_ReviewRequested, User: &models.User{UserName: "!bot"}, Assignee: &models.User{UserName: "user1"}}, @@ -1199,6 +1190,9 @@ func TestFindMissingAndExtraReviewers(t *testing.T) { t.Run(test.name, func(t *testing.T) { test.prset.HasAutoStaging = !test.noAutoStaging for idx, pr := range test.prset.PRs { + if pr.Reviews != nil { + pr.Reviews.SetRequiredReviewers(test.prset.Config.Reviewers) + } missing, extra := test.prset.FindMissingAndExtraReviewers(test.maintainers, idx) // avoid nil dereference below, by adding empty array elements diff --git a/common/reviews.go b/common/reviews.go index 4bc9080..da03839 100644 --- a/common/reviews.go +++ b/common/reviews.go @@ -8,12 +8,28 @@ import ( "src.opensuse.org/autogits/common/gitea-generated/models" ) +type ReviewInterface interface { + IsManualMergeOK() bool + IsApproved() bool + MisingReviews() []string + FindReviewRequester(reviewer string) *models.TimelineComment + HasPendingReviewBy(reviewer string) bool + IsReviewedBy(reviewer string) bool + IsReviewedByOneOf(reviewers ...string) bool + + SetRequiredReviewers(reviewers []string) +} + type PRReviews struct { Reviews []*models.PullReview - RequestedReviewers []string + RequestedReviewers []*models.TimelineComment Comments []*models.TimelineComment - FullTimeline []*models.TimelineComment + RequiredReviewers []string +} + +func (r *PRReviews) SetRequiredReviewers(reviewers []string) { + r.RequiredReviewers = reviewers } func FetchGiteaReviews(rf GiteaReviewTimelineFetcher, org, repo string, no int64) (*PRReviews, error) { @@ -28,11 +44,11 @@ func FetchGiteaReviews(rf GiteaReviewTimelineFetcher, org, repo string, no int64 } reviews := make([]*models.PullReview, 0, 10) - needNewReviews := []string{} var comments []*models.TimelineComment + var foundUsers []string alreadyHaveUserReview := func(user string) bool { - if slices.Contains(needNewReviews, user) { + if slices.Contains(foundUsers, user) { return true } for _, r := range reviews { @@ -48,20 +64,24 @@ func FetchGiteaReviews(rf GiteaReviewTimelineFetcher, org, repo string, no int64 LogDebug("Number of items in timeline:", len(timeline)) cutOffIdx := len(timeline) + var PendingRequestedReviews []*models.TimelineComment for idx, item := range timeline { - if item.Type == TimelineCommentType_Review || item.Type == TimelineCommentType_ReviewRequested { + if item.Type == TimelineCommentType_Review { for _, r := range rawReviews { if r.ID == item.ReviewID && r.User != nil { if !alreadyHaveUserReview(r.User.UserName) { - if item.Type == TimelineCommentType_Review && idx > cutOffIdx { - needNewReviews = append(needNewReviews, r.User.UserName) - } else { + if idx < cutOffIdx { reviews = append(reviews, r) } + foundUsers = append(foundUsers, r.User.UserName) } break } } + } else if item.Type == TimelineCommentType_ReviewRequested && item.Assignee != nil && !alreadyHaveUserReview(item.Assignee.UserName) { + PendingRequestedReviews = append(PendingRequestedReviews, item) + } else if item.Type == TimelineCommentType_DismissReview && item.Assignee != nil && !alreadyHaveUserReview(item.Assignee.UserName) { + foundUsers = append(foundUsers, item.Assignee.UserName) } else if item.Type == TimelineCommentType_Comment && cutOffIdx > idx { comments = append(comments, item) } else if item.Type == TimelineCommentType_PushPull && cutOffIdx == len(timeline) { @@ -74,9 +94,9 @@ func FetchGiteaReviews(rf GiteaReviewTimelineFetcher, org, repo string, no int64 LogDebug("num comments:", len(comments), "timeline:", len(reviews)) return &PRReviews{ - Reviews: reviews, - Comments: comments, - FullTimeline: timeline, + Reviews: reviews, + Comments: comments, + RequestedReviewers: PendingRequestedReviews, }, nil } @@ -104,7 +124,7 @@ func (r *PRReviews) IsManualMergeOK() bool { continue } LogDebug("comment:", c.User.UserName, c.Body) - if slices.Contains(r.RequestedReviewers, c.User.UserName) { + if slices.Contains(r.RequiredReviewers, c.User.UserName) { if bodyCommandManualMergeOK(c.Body) { return true } @@ -115,7 +135,7 @@ func (r *PRReviews) IsManualMergeOK() bool { if c.Updated != c.Submitted { continue } - if slices.Contains(r.RequestedReviewers, c.User.UserName) { + if slices.Contains(r.RequiredReviewers, c.User.UserName) { if bodyCommandManualMergeOK(c.Body) { return true } @@ -131,7 +151,7 @@ func (r *PRReviews) IsApproved() bool { } goodReview := true - for _, reviewer := range r.RequestedReviewers { + for _, reviewer := range r.RequiredReviewers { goodReview = false for _, review := range r.Reviews { if review.User.UserName == reviewer && review.State == ReviewStateApproved && !review.Stale && !review.Dismissed { @@ -155,7 +175,7 @@ func (r *PRReviews) MissingReviews() []string { return missing } - for _, reviewer := range r.RequestedReviewers { + for _, reviewer := range r.RequiredReviewers { if !r.IsReviewedBy(reviewer) { missing = append(missing, reviewer) } @@ -168,12 +188,11 @@ func (r *PRReviews) FindReviewRequester(reviewer string) *models.TimelineComment return nil } - for _, r := range r.FullTimeline { - if r.Type == TimelineCommentType_ReviewRequested && r.Assignee.UserName == reviewer { - return r + for _, t := range r.RequestedReviewers { + if t.Assignee.UserName == reviewer { + return t } } - return nil } @@ -193,6 +212,13 @@ func (r *PRReviews) HasPendingReviewBy(reviewer string) bool { } } + // at this point, we do not have actual review by user. Check if we have a pending review + for _, t := range r.RequestedReviewers { + if t.Assignee != nil && t.Assignee.UserName == reviewer { + return true + } + } + return false } diff --git a/common/reviews_test.go b/common/reviews_test.go index b5fdcce..afea916 100644 --- a/common/reviews_test.go +++ b/common/reviews_test.go @@ -145,6 +145,53 @@ func TestReviews(t *testing.T) { reviewers: []string{"user1"}, isApproved: false, }, + { + name: "ReviewRequested predates PushPull should be seen as pending", + reviews: []*models.PullReview{}, + timeline: []*models.TimelineComment{ + {Type: common.TimelineCommentType_PushPull}, + {Type: common.TimelineCommentType_ReviewRequested, Assignee: &models.User{UserName: "user1"}}, + }, + reviewers: []string{"user1"}, + isPendingByTest1: true, + }, + { + name: "ReviewRequested postdates PushPull but blocked by older dismiss", + reviews: []*models.PullReview{}, + timeline: []*models.TimelineComment{ + {Type: common.TimelineCommentType_ReviewRequested, Assignee: &models.User{UserName: "user1"}}, + {Type: common.TimelineCommentType_PushPull}, + {Type: common.TimelineCommentType_ReviewDismissed, Assignee: &models.User{UserName: "user1"}}, + }, + reviewers: []string{"user1"}, + isPendingByTest1: true, + }, + { + name: "ReviewRequested predates PushPull should be seen as pending", + reviews: []*models.PullReview{ + {ID: 101, State: common.ReviewStateRequestReview, User: &models.User{UserName: "user1"}}, + }, + timeline: []*models.TimelineComment{ + {Type: common.TimelineCommentType_PushPull}, + {Type: common.TimelineCommentType_ReviewRequested, Assignee: &models.User{UserName: "user1"}}, + }, + reviewers: []string{"user1"}, + isPendingByTest1: true, + }, + { + name: "Review requested, review, then push needs re-requesting", + reviews: []*models.PullReview{ + {ID: 100, State: common.ReviewStateRequestChanges, User: &models.User{UserName: "user1"}}, + }, + timeline: []*models.TimelineComment{ + {Type: common.TimelineCommentType_PushPull}, + {Type: common.TimelineCommentType_Review, ReviewID: 100}, + {Type: common.TimelineCommentType_ReviewRequested, Assignee: &models.User{UserName: "user1"}}, + }, + reviewers: []string{"user1"}, + isReviewedByTest1: false, // Should be stale + isPendingByTest1: false, // Should be stale + }, } for _, test := range tests { @@ -166,7 +213,7 @@ func TestReviews(t *testing.T) { } return } - reviews.RequestedReviewers = test.reviewers + reviews.SetRequiredReviewers(test.reviewers) if r := reviews.IsApproved(); r != test.isApproved { t.Fatal("Unexpected IsReviewed():", r, "vs. expected", test.isApproved) diff --git a/common/timeline.go b/common/timeline.go index fd7a50f..02f6b66 100644 --- a/common/timeline.go +++ b/common/timeline.go @@ -8,6 +8,7 @@ import ( ) const ( + TimelineCommentType_ReviewDismissed = "dismiss_review" TimelineCommentType_ReviewRequested = "review_request" TimelineCommentType_Review = "review" TimelineCommentType_PushPull = "pull_push" -- 2.51.1 From 46917470389c47cab99f756450a01a2e9b437556d85eae6a268fde087064c20b Mon Sep 17 00:00:00 2001 From: Andrii Nikitin Date: Fri, 27 Feb 2026 02:30:42 +0100 Subject: [PATCH 2/3] t: add manual merge test and improve robustness - Add TC-MERGE-002: new end-to-end test for ManualMergeOnly functionality. - Implement global object tracking in conftest.py to prevent redundant setup. - Update test-plan.md to reflect current test implementation and skip statuses. - Improve review tests robustness by using unique filenames and better assertions. - Configure SLFO staging-main and manual-merge branches for monitored tests. - Move flaky NoProjectGitPR tests from xfail to skip. --- integration/test-plan.md | 11 +- integration/tests/conftest.py | 83 ++++++++--- .../data/source_openSUSE_Leap_16.0__meta | 4 +- integration/tests/lib/common_test_utils.py | 8 ++ integration/tests/test_pr_workflow.py | 10 +- integration/tests/workflow_pr_merge_test.py | 133 +++++++++++++++--- integration/tests/workflow_pr_review_test.py | 65 ++++----- integration/tests/workflow_pr_sync_test.py | 4 +- integration/workflow-pr/workflow-pr.json | 4 +- 9 files changed, 234 insertions(+), 88 deletions(-) diff --git a/integration/test-plan.md b/integration/test-plan.md index c9ae045..97b40bf 100644 --- a/integration/test-plan.md +++ b/integration/test-plan.md @@ -59,18 +59,18 @@ The testing will be conducted in a dedicated test environment that mimics the pr | **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".
2. Remove the WIP flag from the PackageGit PR. | 1. The corresponding ProjectGit PR is also marked as "Work In Progress".
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.
2. Mark one of the PackageGit PRs as "Work In Progress".
3. Remove the "Work In Progress" flag from all PackageGit PRs. | 1. The ProjectGit PR is marked as "Work In Progress".
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`.
2. Create a PackageGit PR without "Allow edits from maintainers" enabled.
3. Push a new commit to the PackageGit PR. | 1. No ProjectGit PR is created.
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`.
2. Create a PackageGit PR with "Allow edits from maintainers" enabled.
3. Push a new commit to the PackageGit PR. | 1. No ProjectGit PR is created.
2. The submodule commit on the project PR is updated with the new commit from the PackageGit PR. | High | +| **TC-SYNC-005** | S | **NoProjectGitPR = true, edits disabled** | 1. Set `NoProjectGitPR = true` in `workflow.config`.
2. Create a PackageGit PR without "Allow edits from maintainers" enabled.
3. Push a new commit to the PackageGit PR. | 1. No ProjectGit PR is created.
2. The bot adds a warning comment to the PackageGit PR explaining that it cannot update the PR. | High | +| **TC-SYNC-006** | S | **NoProjectGitPR = true, edits enabled** | 1. Set `NoProjectGitPR = true` in `workflow.config`.
2. Create a PackageGit PR with "Allow edits from maintainers" enabled.
3. Push a new commit to the PackageGit PR. | 1. No ProjectGit PR is created.
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.
2. Wait for the `workflow-pr` bot to act on the PR.
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-004** | P | **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.
2. One of the package maintainers approves the PR. | 1. All package maintainers are added as reviewers.
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.
2. One of the package maintainers rejects the PR. | 1. All package maintainers are added as reviewers.
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`.
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.
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`.
2. Ensure all mandatory reviews are completed on both project and package PRs.
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-001** | P | **Automatic Merge** | 1. Create a PackageGit PR.
2. Ensure all mandatory reviews are completed on both project and package PRs. | 1. The PR is automatically merged. | High | +| **TC-MERGE-002** | P | **ManualMergeOnly with Package Maintainer** | 1. Create a PackageGit PR with `ManualMergeOnly` set to `true`.
2. Ensure all mandatory reviews are completed on both project and package PRs.
3. Comment "merge ok" on the package PR from the account of a package maintainer (or requested reviewer). | 1. The PR is merged. | High | | **TC-MERGE-003** | - | **ManualMergeOnly with unauthorized user** | 1. Create a PackageGit PR with `ManualMergeOnly` set to `true`.
2. Ensure all mandatory reviews are completed on both project and package PRs.
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`.
2. Ensure all mandatory reviews are completed on both project and package PRs.
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`.
2. Ensure all mandatory reviews are completed on both project and package PRs.
3. Comment "merge ok" on the package PR from the account of a project maintainer. | 1. The PR is merged. | High | @@ -84,6 +84,7 @@ The testing will be conducted in a dedicated test environment that mimics the pr #### Legend: * P = implemented and passing; +* S = skipped because is not implemented yet to save some execution time; * 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 diff --git a/integration/tests/conftest.py b/integration/tests/conftest.py index c5e882b..e2b0fcb 100644 --- a/integration/tests/conftest.py +++ b/integration/tests/conftest.py @@ -25,7 +25,8 @@ BRANCH_CONFIG_COMMON = { } BRANCH_CONFIG_CUSTOM = { - "main": { + "main": {}, + "staging-main": { "workflow.config": { "ManualMergeProject": True }, @@ -54,6 +55,12 @@ BRANCH_CONFIG_CUSTOM = { "NoProjectGitPR": True } }, + "manual-merge": { + "workflow.config": { + "ManualMergeOnly": True, + "Reviewers": ["+usera", "+userb", "-autogits_obs_staging_bot"] + } + }, "label-test": { "workflow.config": { "ManualMergeProject": True, @@ -67,6 +74,13 @@ BRANCH_CONFIG_CUSTOM = { } } +# Global state to track created Gitea objects during a pytest run +_CREATED_ORGS = set() +_CREATED_REPOS = set() +_CREATED_USERS = set() +_CREATED_LABELS = set() +_ADDED_COLLABORATORS = set() # format: (org_repo, username) + def setup_users_from_config(client: GiteaAPIClient, wf: dict, mt: dict): """ Parses workflow.config and _maintainership.json, creates users, and adds them as collaborators. @@ -87,18 +101,27 @@ def setup_users_from_config(client: GiteaAPIClient, wf: dict, mt: dict): # Create all users for username in all_users: - client.create_user(username, "password123", f"{username}@example.com") - client.add_collaborator("products", "SLFO", username, "write") + if username not in _CREATED_USERS: + client.create_user(username, "password123", f"{username}@example.com") + _CREATED_USERS.add(username) + + if ("products/SLFO", username) not in _ADDED_COLLABORATORS: + client.add_collaborator("products", "SLFO", username, "write") + _ADDED_COLLABORATORS.add(("products/SLFO", username)) # Set specific repository permissions based on maintainership for pkg, users in mt.items(): repo_name = pkg if pkg else None for username in users: if not repo_name: - client.add_collaborator("pool", "pkgA", username, "write") - client.add_collaborator("pool", "pkgB", username, "write") + for r in ["pkgA", "pkgB"]: + if (f"pool/{r}", username) not in _ADDED_COLLABORATORS: + client.add_collaborator("pool", r, username, "write") + _ADDED_COLLABORATORS.add((f"pool/{r}", username)) else: - client.add_collaborator("pool", repo_name, username, "write") + if (f"pool/{repo_name}", username) not in _ADDED_COLLABORATORS: + client.add_collaborator("pool", repo_name, username, "write") + _ADDED_COLLABORATORS.add((f"pool/{repo_name}", username)) def ensure_config_file(client: GiteaAPIClient, owner: str, repo: str, branch: str, file_name: str, expected_content_dict: dict): """ @@ -149,26 +172,34 @@ def gitea_env(): 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") - client.update_repo_settings("products", "SLFO") - client.update_repo_settings("pool", "pkgA") - client.update_repo_settings("pool", "pkgB") + for org in ["products", "pool"]: + if org not in _CREATED_ORGS: + client.create_org(org) + _CREATED_ORGS.add(org) + + for org, repo in [("products", "SLFO"), ("pool", "pkgA"), ("pool", "pkgB")]: + if f"{org}/{repo}" not in _CREATED_REPOS: + client.create_repo(org, repo) + client.update_repo_settings(org, repo) + _CREATED_REPOS.add(f"{org}/{repo}") # Create labels - client.create_label("products", "SLFO", "staging/Backlog", color="#0000ff") - client.create_label("products", "SLFO", "review/Pending", color="#ffff00") + for name, color in [("staging/Backlog", "#0000ff"), ("review/Pending", "#ffff00")]: + if ("products/SLFO", name) not in _CREATED_LABELS: + client.create_label("products", "SLFO", name, color=color) + _CREATED_LABELS.add(("products/SLFO", name)) # Submodules in SLFO client.add_submodules("products", "SLFO") - 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") + for repo_full, bot in [("products/SLFO", "autogits_obs_staging_bot"), + ("products/SLFO", "workflow-pr"), + ("pool/pkgA", "workflow-pr"), + ("pool/pkgB", "workflow-pr")]: + if (repo_full, bot) not in _ADDED_COLLABORATORS: + org_part, repo_part = repo_full.split("/") + client.add_collaborator(org_part, repo_part, bot, "write") + _ADDED_COLLABORATORS.add((repo_full, bot)) restart_needed = False @@ -220,6 +251,14 @@ def gitea_env(): def automerge_env(gitea_env): return gitea_env, "products/SLFO", "merge" +@pytest.fixture(scope="session") +def staging_main_env(gitea_env): + return gitea_env, "products/SLFO", "staging-main" + +@pytest.fixture(scope="session") +def manual_merge_env(gitea_env): + return gitea_env, "products/SLFO", "manual-merge" + @pytest.fixture(scope="session") def maintainer_env(gitea_env): return gitea_env, "products/SLFO", "maintainer-merge" @@ -236,6 +275,10 @@ def no_project_git_pr_env(gitea_env): def label_env(gitea_env): return gitea_env, "products/SLFO", "label-test" +@pytest.fixture(scope="session") +def usera_client(gitea_env): + return GiteaAPIClient(base_url=gitea_env.base_url, token=gitea_env.headers["Authorization"].split(" ")[1], sudo="usera") + @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") diff --git a/integration/tests/data/source_openSUSE_Leap_16.0__meta b/integration/tests/data/source_openSUSE_Leap_16.0__meta index d69c257..a54da34 100644 --- a/integration/tests/data/source_openSUSE_Leap_16.0__meta +++ b/integration/tests/data/source_openSUSE_Leap_16.0__meta @@ -2,7 +2,7 @@ openSUSE Leap 16.0 based on SLFO Leap 16.0 based on SLES 16.0 (specifically SLFO:1.2) - http://gitea-test:3000/products/SLFO#main + http://gitea-test:3000/products/SLFO#staging-main @@ -56,4 +56,4 @@ ppc64le s390x - \ No newline at end of file + diff --git a/integration/tests/lib/common_test_utils.py b/integration/tests/lib/common_test_utils.py index c5960c6..7e973fa 100644 --- a/integration/tests/lib/common_test_utils.py +++ b/integration/tests/lib/common_test_utils.py @@ -389,6 +389,14 @@ index 0000000..{pkg_b_sha} response = self._request("PATCH", url, json=kwargs) return response.json() + def create_issue_comment(self, repo_full_name: str, issue_number: int, body: str): + owner, repo = repo_full_name.split("/") + url = f"repos/{owner}/{repo}/issues/{issue_number}/comments" + data = {"body": body} + print(f"--- Creating comment on {repo_full_name} issue #{issue_number} ---") + response = self._request("POST", url, json=data) + return response.json() + def get_timeline_events(self, repo_full_name: str, pr_number: int): owner, repo = repo_full_name.split("/") url = f"repos/{owner}/{repo}/issues/{pr_number}/timeline" diff --git a/integration/tests/test_pr_workflow.py b/integration/tests/test_pr_workflow.py index 33be6a8..10ac5f0 100755 --- a/integration/tests/test_pr_workflow.py +++ b/integration/tests/test_pr_workflow.py @@ -14,10 +14,11 @@ from tests.lib.common_test_utils import ( # ============================================================================= -def test_pr_workflow_succeeded(gitea_env, mock_build_result): +def test_pr_workflow_succeeded(staging_main_env, mock_build_result): """End-to-end test for a successful PR workflow.""" + gitea_env, test_full_repo_name, merge_branch_name = staging_main_env diff = "diff --git a/test.txt b/test.txt\nnew file mode 100644\nindex 0000000..e69de29\n" - pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test PR - should succeed", False) + pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test PR - should succeed", False, base_branch=merge_branch_name) initial_pr_number = pr["number"] compose_dir = Path(__file__).parent.parent @@ -84,10 +85,11 @@ def test_pr_workflow_succeeded(gitea_env, mock_build_result): assert status_comment_found, "Staging bot did not post a 'successful' comment." -def test_pr_workflow_failed(gitea_env, mock_build_result): +def test_pr_workflow_failed(staging_main_env, mock_build_result): """End-to-end test for a failed PR workflow.""" + gitea_env, test_full_repo_name, merge_branch_name = staging_main_env diff = "diff --git a/another_test.txt b/another_test.txt\nnew file mode 100644\nindex 0000000..e69de29\n" - pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test PR - should fail", False) + pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test PR - should fail", False, base_branch=merge_branch_name) initial_pr_number = pr["number"] compose_dir = Path(__file__).parent.parent diff --git a/integration/tests/workflow_pr_merge_test.py b/integration/tests/workflow_pr_merge_test.py index 2d09c7e..6a840fe 100644 --- a/integration/tests/workflow_pr_merge_test.py +++ b/integration/tests/workflow_pr_merge_test.py @@ -5,20 +5,17 @@ from pathlib import Path from tests.lib.common_test_utils import GiteaAPIClient @pytest.mark.t001 -@pytest.mark.xfail(reason="The bot sometimes re-request reviews despite having all the approvals") def test_001_automerge(automerge_env, test_user_client): """ - Test scenario: - 1. Setup custom workflow.config with mandatory reviewers (+usera, +userb). - 2. Create a package PR in 'merge' branch. - 3. Make sure the workflow-pr service created related project PR in 'merge' branch. - 4. React on 'requested' reviews by approving them. - 5. Make sure both PRs are merged automatically by the workflow-pr service. + Test scenario TC-MERGE-001: + 1. Create a PackageGit PR. + 2. Ensure all mandatory reviews are completed on both project and package PRs. + 3. Verify the PR is merged automatically. """ gitea_env, test_full_repo_name, merge_branch_name = automerge_env # 1. Create a package PR - diff = """diff --git a/merge_test_fixture.txt b/merge_test_fixture.txt + diff = """diff --git a/automerge_test.txt b/automerge_test.txt new file mode 100644 index 0000000..e69de29 """ @@ -48,20 +45,20 @@ index 0000000..e69de29 assert project_pr_number is not None, "Workflow bot did not create a project PR." print(f"Found project PR: products/SLFO#{project_pr_number}") - # 4. Make sure both PRs are merged automatically by the workflow-pr service - print("Polling for PR merge status and reacting on REQUEST_REVIEW...") + # 3. Approve reviews and verify merged + print("Approving reviews and verifying both PRs are merged...") package_merged = False project_merged = False - for i in range(15): # Poll for up to 15 seconds - # Package PR + for i in range(20): # Poll for up to 20 seconds + gitea_env.approve_requested_reviews("pool/pkgA", package_pr_number) + gitea_env.approve_requested_reviews("products/SLFO", project_pr_number) + if not package_merged: pkg_details = gitea_env.get_pr_details("pool/pkgA", package_pr_number) if pkg_details.get("merged"): package_merged = True print(f"Package PR pool/pkgA#{package_pr_number} merged.") - else: - gitea_env.approve_requested_reviews("pool/pkgA", package_pr_number) # Project PR if not project_merged: @@ -69,8 +66,6 @@ index 0000000..e69de29 if prj_details.get("merged"): project_merged = True print(f"Project PR products/SLFO#{project_pr_number} merged.") - else: - gitea_env.approve_requested_reviews("products/SLFO", project_pr_number) if package_merged and project_merged: break @@ -80,3 +75,109 @@ index 0000000..e69de29 assert package_merged, f"Package PR pool/pkgA#{package_pr_number} was not merged automatically." assert project_merged, f"Project PR products/SLFO#{project_pr_number} was not merged automatically." print("Both PRs merged successfully.") + +@pytest.mark.t002 +def test_002_manual_merge(manual_merge_env, test_user_client, usera_client, staging_bot_client): + """ + Test scenario TC-MERGE-002: + 1. Create a PackageGit PR with ManualMergeOnly set to true. + 2. Ensure all mandatory reviews are completed on both project and package PRs. + 3. Comment "merge ok" on the package PR from the account of a requested reviewer. + 4. Verify the PR is merged. + """ + gitea_env, test_full_repo_name, merge_branch_name = manual_merge_env + + # 1. Create a package PR + diff = """diff --git a/manual_merge_test.txt b/manual_merge_test.txt +new file mode 100644 +index 0000000..e69de29 +""" + print(f"--- Creating package PR in pool/pkgA on branch {merge_branch_name} ---") + package_pr = test_user_client.create_gitea_pr("pool/pkgA", diff, "Test Manual Merge Fixture", False, base_branch=merge_branch_name) + package_pr_number = package_pr["number"] + print(f"Created package PR pool/pkgA#{package_pr_number}") + + # 2. Make sure the workflow-pr service created related project PR + project_pr_number = None + print(f"Polling pool/pkgA PR #{package_pr_number} timeline for forwarded PR event...") + for _ in range(40): + time.sleep(1) + timeline_events = gitea_env.get_timeline_events("pool/pkgA", package_pr_number) + for event in timeline_events: + if event.get("type") == "pull_ref": + if not (ref_issue := event.get("ref_issue")): + continue + url_to_check = ref_issue.get("html_url", "") + match = re.search(r"products/SLFO/pulls/(\d+)", url_to_check) + if match: + project_pr_number = int(match.group(1)) + break + if project_pr_number: + break + + assert project_pr_number is not None, "Workflow bot did not create a project PR." + print(f"Found project PR: products/SLFO#{project_pr_number}") + + # 3. Approve reviews and verify NOT merged + print("Approving reviews and verifying PRs are NOT merged yet...") + # ManualMergeOnly still requires regular reviews to be satisfied. + # We poll for review requests and approve them. + for _ in range(15): + gitea_env.approve_requested_reviews("pool/pkgA", package_pr_number) + gitea_env.approve_requested_reviews("products/SLFO", project_pr_number) + + # Explicitly handle staging bot if it is requested or pending + prj_reviews = gitea_env.list_reviews("products/SLFO", project_pr_number) + if any(r["user"]["login"] == "autogits_obs_staging_bot" and r["state"] in ["REQUEST_REVIEW", "PENDING"] for r in prj_reviews): + print("Staging bot has a pending/requested review. Approving...") + staging_bot_client.create_review("products/SLFO", project_pr_number, event="APPROVED", body="Staging bot approves") + + pkg_details = gitea_env.get_pr_details("pool/pkgA", package_pr_number) + prj_details = gitea_env.get_pr_details("products/SLFO", project_pr_number) + + assert not pkg_details.get("merged"), "Package PR merged prematurely (ManualMergeOnly ignored?)" + assert not prj_details.get("merged"), "Project PR merged prematurely (ManualMergeOnly ignored?)" + + # Check if we have at least some reviews to consider it "ready" for merge ok + pkg_reviews = gitea_env.list_reviews("pool/pkgA", package_pr_number) + prj_reviews = gitea_env.list_reviews("products/SLFO", project_pr_number) + + pkg_approved = any(r["state"] == "APPROVED" for r in pkg_reviews) + prj_approved = any(r["state"] == "APPROVED" for r in prj_reviews) + + if pkg_approved and prj_approved: + print("Both PRs have approvals but are not merged (as expected with ManualMergeOnly).") + break + + time.sleep(2) + + # 4. Comment "merge ok" from a requested reviewer (usera) + print("Commenting 'merge ok' on package PR...") + usera_client.create_issue_comment("pool/pkgA", package_pr_number, "merge ok") + + # 5. Verify both PRs are merged + print("Polling for PR merge status...") + package_merged = False + project_merged = False + + for i in range(20): # Poll for up to 20 seconds + if not package_merged: + pkg_details = gitea_env.get_pr_details("pool/pkgA", package_pr_number) + if pkg_details.get("merged"): + package_merged = True + print(f"Package PR pool/pkgA#{package_pr_number} merged.") + + if not project_merged: + prj_details = gitea_env.get_pr_details("products/SLFO", project_pr_number) + if prj_details.get("merged"): + project_merged = True + print(f"Project PR products/SLFO#{project_pr_number} merged.") + + if package_merged and project_merged: + break + + time.sleep(1) + + assert package_merged, f"Package PR pool/pkgA#{package_pr_number} was not merged after 'merge ok'." + assert project_merged, f"Project PR products/SLFO#{project_pr_number} was not merged after 'merge ok'." + print("Both PRs merged successfully after 'merge ok'.") diff --git a/integration/tests/workflow_pr_review_test.py b/integration/tests/workflow_pr_review_test.py index a6f2d45..498ab39 100644 --- a/integration/tests/workflow_pr_review_test.py +++ b/integration/tests/workflow_pr_review_test.py @@ -16,7 +16,9 @@ def test_001_review_requests_matching_config(automerge_env, ownerA_client): 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 + ts = int(time.time() * 1000) + filename = f"pkgB_test_{ts}.txt" + diff = f"""diff --git a/{filename} b/{filename} new file mode 100644 index 0000000..e69de29 """ @@ -43,7 +45,6 @@ index 0000000..e69de29 @pytest.mark.t004 -@pytest.mark.xfail(reason="the bot sometimes re-requests review from autogits_obs_staging_bot despite having the approval") def test_004_maintainer(maintainer_env, ownerA_client): """ Test scenario: @@ -81,7 +82,9 @@ def test_004_maintainer(maintainer_env, ownerA_client): print(f"Verified user exists: {username}") # 1. Create a package PR as ownerA - diff = """diff --git a/maintainer_test_fixture.txt b/maintainer_test_fixture.txt + ts = int(time.time() * 1000) + filename = f"maintainer_test_{ts}.txt" + diff = f"""diff --git a/{filename} b/{filename} new file mode 100644 index 0000000..e69de29 """ @@ -164,7 +167,9 @@ def test_005_any_maintainer_approval_sufficient(maintainer_env, ownerA_client, o gitea_env, test_full_repo_name, branch_name = maintainer_env # 1. Create a package PR for pool/pkgB as ownerA - diff = """diff --git a/pkgB_test_fixture.txt b/pkgB_test_fixture.txt + ts = int(time.time() * 1000) + filename = f"pkgB_test_{ts}.txt" + diff = f"""diff --git a/{filename} b/{filename} new file mode 100644 index 0000000..e69de29 """ @@ -212,39 +217,19 @@ index 0000000..e69de29 print("ownerBB approving the PR...") ownerBB_client.create_review("pool/pkgB", package_pr_number, event="APPROVED", body="Approval from ownerBB") - # 5. Check that both PRs are merged automatically - print("Polling for PR merge status (only bot approval allowed for project PR)...") - package_merged = False - project_merged = False - - for i in range(15): # Poll for up to 15 seconds - # Package PR - if not package_merged: - pkg_details = gitea_env.get_pr_details("pool/pkgB", package_pr_number) - if pkg_details.get("merged"): - package_merged = True - print(f"Package PR pool/pkgB#{package_pr_number} merged.") - - # Project PR - if not project_merged: - prj_details = gitea_env.get_pr_details("products/SLFO", project_pr_number) - if prj_details.get("merged"): - project_merged = True - print(f"Project PR products/SLFO#{project_pr_number} merged.") - else: - # Approve ONLY bot if requested - reviews = gitea_env.list_reviews("products/SLFO", project_pr_number) - if any(r["state"] == "REQUEST_REVIEW" and r["user"]["login"] == "autogits_obs_staging_bot" for r in reviews): - gitea_env.approve_requested_reviews("products/SLFO", project_pr_number) - - if package_merged and project_merged: - break - + # 5. Check that review request for ownerB is removed + print("Polling for ownerB review request removal...") + ownerB_removed = False + for _ in range(30): time.sleep(1) - - assert package_merged, f"Package PR pool/pkgB#{package_pr_number} was not merged automatically." - assert project_merged, f"Project PR products/SLFO#{project_pr_number} was not merged automatically." - print("Both PRs merged successfully with only one maintainer approval.") + reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number) + reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"} + if "ownerB" not in reviewers_requested: + ownerB_removed = True + break + + assert ownerB_removed, f"ownerB review request was not removed after ownerBB approval. Current requested: {reviewers_requested}" + print("Confirmed: ownerB review request removed after single maintainer approval.") @pytest.mark.t006 @@ -260,7 +245,9 @@ def test_006_maintainer_rejection_removes_other_requests(maintainer_env, ownerA_ gitea_env, test_full_repo_name, branch_name = maintainer_env # 1. Create a package PR for pool/pkgB as ownerA - diff = """diff --git a/pkgB_rejection_test.txt b/pkgB_rejection_test.txt + ts = int(time.time() * 1000) + filename = f"pkgB_rejection_test_{ts}.txt" + diff = f"""diff --git a/{filename} b/{filename} new file mode 100644 index 0000000..e69de29 """ @@ -318,7 +305,9 @@ def test_007_review_required_needs_all_approvals(review_required_env, ownerA_cli print(f"ownerA_client smoke test passed") # 1. Create a package PR for pool/pkgB as ownerA - diff = """diff --git a/pkgB_review_required_test.txt b/pkgB_review_required_test.txt + ts = int(time.time() * 1000) + filename = f"pkgB_review_required_test_{ts}.txt" + diff = f"""diff --git a/{filename} b/{filename} new file mode 100644 index 0000000..e69de29 """ diff --git a/integration/tests/workflow_pr_sync_test.py b/integration/tests/workflow_pr_sync_test.py index 73e6cbe..720828e 100755 --- a/integration/tests/workflow_pr_sync_test.py +++ b/integration/tests/workflow_pr_sync_test.py @@ -121,7 +121,7 @@ def test_003_wip(gitea_env): @pytest.mark.t005 -@pytest.mark.xfail(reason="works only in ibs_state branch?") +@pytest.mark.skip(reason="works only in ibs_state branch?") @pytest.mark.dependency() def test_005_NoProjectGitPR_edits_disabled(no_project_git_pr_env, test_user_client): """ @@ -221,7 +221,7 @@ index 0000000..e69de29 @pytest.mark.t006 -@pytest.mark.xfail(reason="works only in ibs_state branch?") +@pytest.mark.skip(reason="works only in ibs_state branch?") @pytest.mark.dependency() def test_006_NoProjectGitPR_edits_enabled(no_project_git_pr_env, test_user_client): """ diff --git a/integration/workflow-pr/workflow-pr.json b/integration/workflow-pr/workflow-pr.json index b8340a8..85d06ee 100644 --- a/integration/workflow-pr/workflow-pr.json +++ b/integration/workflow-pr/workflow-pr.json @@ -1,8 +1,10 @@ [ "products/SLFO#main", + "products/SLFO#staging-main", "products/SLFO#dev", "products/SLFO#merge", "products/SLFO#maintainer-merge", "products/SLFO#review-required", - "products/SLFO#label-test" + "products/SLFO#label-test", + "products/SLFO#manual-merge" ] -- 2.51.1 From 7832ef90c032bf58e755fa671a759b952e5f1005aafc55ecc1f9681ba4934bde Mon Sep 17 00:00:00 2001 From: Andrii Nikitin Date: Fri, 27 Feb 2026 13:22:26 +0100 Subject: [PATCH 3/3] t: rename Gitea objects to avoid accidental collision with real repos Also address potential race condition between requested reviews and sending approvals in TC-MERGE-002 --- .../GET_source_openSUSE:Leap:16.0__meta | 2 +- integration/tests/conftest.py | 68 +++++----- .../tests/data/build_result.xml.template | 8 +- .../data/source_openSUSE_Leap_16.0__meta | 2 +- integration/tests/lib/common_test_utils.py | 8 +- integration/tests/test_pr_workflow.py | 36 +++--- integration/tests/workflow_pr_label_test.py | 34 ++--- integration/tests/workflow_pr_merge_test.py | 110 +++++++++-------- integration/tests/workflow_pr_review_test.py | 116 +++++++++--------- integration/tests/workflow_pr_sync_test.py | 76 ++++++------ integration/workflow-pr/entrypoint.sh | 4 +- integration/workflow-pr/workflow-pr.json | 16 +-- 12 files changed, 246 insertions(+), 234 deletions(-) diff --git a/integration/mock-obs/responses/GET_source_openSUSE:Leap:16.0__meta b/integration/mock-obs/responses/GET_source_openSUSE:Leap:16.0__meta index 5d3a001..dbbf926 100644 --- a/integration/mock-obs/responses/GET_source_openSUSE:Leap:16.0__meta +++ b/integration/mock-obs/responses/GET_source_openSUSE:Leap:16.0__meta @@ -2,7 +2,7 @@ openSUSE Leap 16.0 based on SLFO Leap 16.0 based on SLES 16.0 (specifically SLFO:1.2) - http://gitea-test:3000/products/SLFO#main + http://gitea-test:3000/myproducts/mySLFO#staging-main diff --git a/integration/tests/conftest.py b/integration/tests/conftest.py index e2b0fcb..0718bf2 100644 --- a/integration/tests/conftest.py +++ b/integration/tests/conftest.py @@ -13,9 +13,9 @@ from tests.lib.common_test_utils import GiteaAPIClient BRANCH_CONFIG_COMMON = { "workflow.config": { "Workflows": ["pr"], - "Organization": "pool", + "Organization": "mypool", "Reviewers": ["-autogits_obs_staging_bot"], - "GitProjectName": "products/SLFO#{branch}" + "GitProjectName": "myproducts/mySLFO#{branch}" }, "_maintainership.json": { "": ["ownerX", "ownerY"], @@ -105,9 +105,9 @@ def setup_users_from_config(client: GiteaAPIClient, wf: dict, mt: dict): client.create_user(username, "password123", f"{username}@example.com") _CREATED_USERS.add(username) - if ("products/SLFO", username) not in _ADDED_COLLABORATORS: - client.add_collaborator("products", "SLFO", username, "write") - _ADDED_COLLABORATORS.add(("products/SLFO", username)) + if ("myproducts/mySLFO", username) not in _ADDED_COLLABORATORS: + client.add_collaborator("myproducts", "mySLFO", username, "write") + _ADDED_COLLABORATORS.add(("myproducts/mySLFO", username)) # Set specific repository permissions based on maintainership for pkg, users in mt.items(): @@ -115,13 +115,13 @@ def setup_users_from_config(client: GiteaAPIClient, wf: dict, mt: dict): for username in users: if not repo_name: for r in ["pkgA", "pkgB"]: - if (f"pool/{r}", username) not in _ADDED_COLLABORATORS: - client.add_collaborator("pool", r, username, "write") - _ADDED_COLLABORATORS.add((f"pool/{r}", username)) + if (f"mypool/{r}", username) not in _ADDED_COLLABORATORS: + client.add_collaborator("mypool", r, username, "write") + _ADDED_COLLABORATORS.add((f"mypool/{r}", username)) else: - if (f"pool/{repo_name}", username) not in _ADDED_COLLABORATORS: - client.add_collaborator("pool", repo_name, username, "write") - _ADDED_COLLABORATORS.add((f"pool/{repo_name}", username)) + if (f"mypool/{repo_name}", username) not in _ADDED_COLLABORATORS: + client.add_collaborator("mypool", repo_name, username, "write") + _ADDED_COLLABORATORS.add((f"mypool/{repo_name}", username)) def ensure_config_file(client: GiteaAPIClient, owner: str, repo: str, branch: str, file_name: str, expected_content_dict: dict): """ @@ -172,12 +172,12 @@ def gitea_env(): raise Exception("Gitea not available.") print("--- Starting Gitea Global Setup ---") - for org in ["products", "pool"]: + for org in ["myproducts", "mypool"]: if org not in _CREATED_ORGS: client.create_org(org) _CREATED_ORGS.add(org) - for org, repo in [("products", "SLFO"), ("pool", "pkgA"), ("pool", "pkgB")]: + for org, repo in [("myproducts", "mySLFO"), ("mypool", "pkgA"), ("mypool", "pkgB")]: if f"{org}/{repo}" not in _CREATED_REPOS: client.create_repo(org, repo) client.update_repo_settings(org, repo) @@ -185,17 +185,17 @@ def gitea_env(): # Create labels for name, color in [("staging/Backlog", "#0000ff"), ("review/Pending", "#ffff00")]: - if ("products/SLFO", name) not in _CREATED_LABELS: - client.create_label("products", "SLFO", name, color=color) - _CREATED_LABELS.add(("products/SLFO", name)) + if ("myproducts/mySLFO", name) not in _CREATED_LABELS: + client.create_label("myproducts", "mySLFO", name, color=color) + _CREATED_LABELS.add(("myproducts/mySLFO", name)) - # Submodules in SLFO - client.add_submodules("products", "SLFO") + # Submodules in mySLFO + client.add_submodules("myproducts", "mySLFO") - for repo_full, bot in [("products/SLFO", "autogits_obs_staging_bot"), - ("products/SLFO", "workflow-pr"), - ("pool/pkgA", "workflow-pr"), - ("pool/pkgB", "workflow-pr")]: + for repo_full, bot in [("myproducts/mySLFO", "autogits_obs_staging_bot"), + ("myproducts/mySLFO", "workflow-pr"), + ("mypool/pkgA", "workflow-pr"), + ("mypool/pkgB", "workflow-pr")]: if (repo_full, bot) not in _ADDED_COLLABORATORS: org_part, repo_part = repo_full.split("/") client.add_collaborator(org_part, repo_part, bot, "write") @@ -206,7 +206,7 @@ def gitea_env(): # 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")]: + for owner, repo in [("myproducts", "mySLFO"), ("mypool", "pkgA"), ("mypool", "pkgB")]: if branch_name != "main": try: main_sha = client._request("GET", f"repos/{owner}/{repo}/branches/main").json()["commit"]["id"] @@ -232,9 +232,9 @@ def gitea_env(): else: merged_configs[file_name] = custom_content - # Ensure config files in products/SLFO + # Ensure config files in myproducts/mySLFO for file_name, content_dict in merged_configs.items(): - if ensure_config_file(client, "products", "SLFO", branch_name, file_name, content_dict): + if ensure_config_file(client, "myproducts", "mySLFO", branch_name, file_name, content_dict): restart_needed = True # Setup users (using configs from this branch) @@ -249,31 +249,31 @@ def gitea_env(): @pytest.fixture(scope="session") def automerge_env(gitea_env): - return gitea_env, "products/SLFO", "merge" + return gitea_env, "myproducts/mySLFO", "merge" @pytest.fixture(scope="session") def staging_main_env(gitea_env): - return gitea_env, "products/SLFO", "staging-main" + return gitea_env, "myproducts/mySLFO", "staging-main" @pytest.fixture(scope="session") def manual_merge_env(gitea_env): - return gitea_env, "products/SLFO", "manual-merge" + return gitea_env, "myproducts/mySLFO", "manual-merge" @pytest.fixture(scope="session") def maintainer_env(gitea_env): - return gitea_env, "products/SLFO", "maintainer-merge" + return gitea_env, "myproducts/mySLFO", "maintainer-merge" @pytest.fixture(scope="session") def review_required_env(gitea_env): - return gitea_env, "products/SLFO", "review-required" + return gitea_env, "myproducts/mySLFO", "review-required" @pytest.fixture(scope="session") def no_project_git_pr_env(gitea_env): - return gitea_env, "products/SLFO", "dev" + return gitea_env, "myproducts/mySLFO", "dev" @pytest.fixture(scope="session") def label_env(gitea_env): - return gitea_env, "products/SLFO", "label-test" + return gitea_env, "myproducts/mySLFO", "label-test" @pytest.fixture(scope="session") def usera_client(gitea_env): @@ -299,6 +299,6 @@ def staging_bot_client(gitea_env): def test_user_client(gitea_env): username = f"test-user-{int(time.time())}" gitea_env.create_user(username, "password123", f"{username}@example.com") - gitea_env.add_collaborator("pool", "pkgA", username, "write") - gitea_env.add_collaborator("products", "SLFO", username, "write") + gitea_env.add_collaborator("mypool", "pkgA", username, "write") + gitea_env.add_collaborator("myproducts", "mySLFO", username, "write") return GiteaAPIClient(base_url=gitea_env.base_url, token=gitea_env.headers["Authorization"].split(" ")[1], sudo=username) diff --git a/integration/tests/data/build_result.xml.template b/integration/tests/data/build_result.xml.template index 067665e..12f6fdb 100644 --- a/integration/tests/data/build_result.xml.template +++ b/integration/tests/data/build_result.xml.template @@ -1,21 +1,21 @@ - https://src.suse.de/products/SLFO.git?onlybuild=openjpeg2#d99ac14dedf9f44e1744c71aaf221d15f6bed479ca11f15738e98f3bf9ae05a1 + https://src.suse.de/myproducts/mySLFO.git?onlybuild=openjpeg2#d99ac14dedf9f44e1744c71aaf221d15f6bed479ca11f15738e98f3bf9ae05a1 d99ac14dedf9f44e1744c71aaf221d15f6bed479ca11f15738e98f3bf9ae05a1 - https://src.suse.de/products/SLFO.git?onlybuild=openjpeg2#d99ac14dedf9f44e1744c71aaf221d15f6bed479ca11f15738e98f3bf9ae05a1 + https://src.suse.de/myproducts/mySLFO.git?onlybuild=openjpeg2#d99ac14dedf9f44e1744c71aaf221d15f6bed479ca11f15738e98f3bf9ae05a1 d99ac14dedf9f44e1744c71aaf221d15f6bed479ca11f15738e98f3bf9ae05a1 - https://src.suse.de/products/SLFO.git?onlybuild=openjpeg2#d99ac14dedf9f44e1744c71aaf221d15f6bed479ca11f15738e98f3bf9ae05a1 + https://src.suse.de/myproducts/mySLFO.git?onlybuild=openjpeg2#d99ac14dedf9f44e1744c71aaf221d15f6bed479ca11f15738e98f3bf9ae05a1 d99ac14dedf9f44e1744c71aaf221d15f6bed479ca11f15738e98f3bf9ae05a1 - https://src.suse.de/products/SLFO.git?onlybuild=openjpeg2#d99ac14dedf9f44e1744c71aaf221d15f6bed479ca11f15738e98f3bf9ae05a1 + https://src.suse.de/myproducts/mySLFO.git?onlybuild=openjpeg2#d99ac14dedf9f44e1744c71aaf221d15f6bed479ca11f15738e98f3bf9ae05a1 d99ac14dedf9f44e1744c71aaf221d15f6bed479ca11f15738e98f3bf9ae05a1 diff --git a/integration/tests/data/source_openSUSE_Leap_16.0__meta b/integration/tests/data/source_openSUSE_Leap_16.0__meta index a54da34..dbbf926 100644 --- a/integration/tests/data/source_openSUSE_Leap_16.0__meta +++ b/integration/tests/data/source_openSUSE_Leap_16.0__meta @@ -2,7 +2,7 @@ openSUSE Leap 16.0 based on SLFO Leap 16.0 based on SLES 16.0 (specifically SLFO:1.2) - http://gitea-test:3000/products/SLFO#staging-main + http://gitea-test:3000/myproducts/mySLFO#staging-main diff --git a/integration/tests/lib/common_test_utils.py b/integration/tests/lib/common_test_utils.py index 7e973fa..54007d3 100644 --- a/integration/tests/lib/common_test_utils.py +++ b/integration/tests/lib/common_test_utils.py @@ -172,8 +172,8 @@ class GiteaAPIClient: raise # Get latest commit SHAs for the submodules - pkg_a_sha = self._request("GET", "repos/pool/pkgA/branches/main").json()["commit"]["id"] - pkg_b_sha = self._request("GET", "repos/pool/pkgB/branches/main").json()["commit"]["id"] + pkg_a_sha = self._request("GET", "repos/mypool/pkgA/branches/main").json()["commit"]["id"] + pkg_b_sha = self._request("GET", "repos/mypool/pkgB/branches/main").json()["commit"]["id"] if not pkg_a_sha or not pkg_b_sha: raise Exception("Error: Could not get submodule commit SHAs. Cannot apply patch.") @@ -186,10 +186,10 @@ index 0000000..f1838bd @@ -0,0 +1,6 @@ +[submodule "pkgA"] + path = pkgA -+ url = ../../pool/pkgA.git ++ url = ../../mypool/pkgA.git +[submodule "pkgB"] + path = pkgB -+ url = ../../pool/pkgB.git ++ url = ../../mypool/pkgB.git diff --git a/pkgA b/pkgA new file mode 160000 index 0000000..{pkg_a_sha} diff --git a/integration/tests/test_pr_workflow.py b/integration/tests/test_pr_workflow.py index 10ac5f0..416b159 100755 --- a/integration/tests/test_pr_workflow.py +++ b/integration/tests/test_pr_workflow.py @@ -18,24 +18,24 @@ def test_pr_workflow_succeeded(staging_main_env, mock_build_result): """End-to-end test for a successful PR workflow.""" gitea_env, test_full_repo_name, merge_branch_name = staging_main_env diff = "diff --git a/test.txt b/test.txt\nnew file mode 100644\nindex 0000000..e69de29\n" - pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test PR - should succeed", False, base_branch=merge_branch_name) + pr = gitea_env.create_gitea_pr("mypool/pkgA", diff, "Test PR - should succeed", False, base_branch=merge_branch_name) initial_pr_number = pr["number"] compose_dir = Path(__file__).parent.parent forwarded_pr_number = None print( - f"Polling pool/pkgA PR #{initial_pr_number} timeline for forwarded PR event..." + f"Polling mypool/pkgA PR #{initial_pr_number} timeline for forwarded PR event..." ) for _ in range(20): time.sleep(1) - timeline_events = gitea_env.get_timeline_events("pool/pkgA", initial_pr_number) + timeline_events = gitea_env.get_timeline_events("mypool/pkgA", initial_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) + match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check) if match: forwarded_pr_number = match.group(1) break @@ -44,13 +44,13 @@ def test_pr_workflow_succeeded(staging_main_env, mock_build_result): assert ( forwarded_pr_number is not None ), "Workflow bot did not create a pull_ref event on the timeline." - print(f"Found forwarded PR: products/SLFO #{forwarded_pr_number}") + print(f"Found forwarded PR: myproducts/mySLFO #{forwarded_pr_number}") - print(f"Polling products/SLFO PR #{forwarded_pr_number} for reviewer assignment...") + print(f"Polling myproducts/mySLFO PR #{forwarded_pr_number} for reviewer assignment...") reviewer_added = False for _ in range(15): time.sleep(1) - pr_details = gitea_env.get_pr_details("products/SLFO", forwarded_pr_number) + pr_details = gitea_env.get_pr_details("myproducts/mySLFO", forwarded_pr_number) if any( r.get("login") == "autogits_obs_staging_bot" for r in pr_details.get("requested_reviewers", []) @@ -70,11 +70,11 @@ def test_pr_workflow_succeeded(staging_main_env, mock_build_result): capture_output=True, ) - print(f"Polling products/SLFO PR #{forwarded_pr_number} for final status...") + print(f"Polling myproducts/mySLFO PR #{forwarded_pr_number} for final status...") status_comment_found = False for _ in range(20): time.sleep(1) - timeline_events = gitea_env.get_timeline_events("products/SLFO", forwarded_pr_number) + timeline_events = gitea_env.get_timeline_events("myproducts/mySLFO", forwarded_pr_number) for event in timeline_events: print(event.get("body", "not a body")) if event.get("body") and "successful" in event["body"]: @@ -89,24 +89,24 @@ def test_pr_workflow_failed(staging_main_env, mock_build_result): """End-to-end test for a failed PR workflow.""" gitea_env, test_full_repo_name, merge_branch_name = staging_main_env diff = "diff --git a/another_test.txt b/another_test.txt\nnew file mode 100644\nindex 0000000..e69de29\n" - pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test PR - should fail", False, base_branch=merge_branch_name) + pr = gitea_env.create_gitea_pr("mypool/pkgA", diff, "Test PR - should fail", False, base_branch=merge_branch_name) initial_pr_number = pr["number"] compose_dir = Path(__file__).parent.parent forwarded_pr_number = None print( - f"Polling pool/pkgA PR #{initial_pr_number} timeline for forwarded PR event..." + f"Polling mypool/pkgA PR #{initial_pr_number} timeline for forwarded PR event..." ) for _ in range(20): time.sleep(1) - timeline_events = gitea_env.get_timeline_events("pool/pkgA", initial_pr_number) + timeline_events = gitea_env.get_timeline_events("mypool/pkgA", initial_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) + match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check) if match: forwarded_pr_number = match.group(1) break @@ -115,13 +115,13 @@ def test_pr_workflow_failed(staging_main_env, mock_build_result): assert ( forwarded_pr_number is not None ), "Workflow bot did not create a pull_ref event on the timeline." - print(f"Found forwarded PR: products/SLFO #{forwarded_pr_number}") + print(f"Found forwarded PR: myproducts/mySLFO #{forwarded_pr_number}") - print(f"Polling products/SLFO PR #{forwarded_pr_number} for reviewer assignment...") + print(f"Polling myproducts/mySLFO PR #{forwarded_pr_number} for reviewer assignment...") reviewer_added = False for _ in range(15): time.sleep(1) - pr_details = gitea_env.get_pr_details("products/SLFO", forwarded_pr_number) + pr_details = gitea_env.get_pr_details("myproducts/mySLFO", forwarded_pr_number) if any( r.get("login") == "autogits_obs_staging_bot" for r in pr_details.get("requested_reviewers", []) @@ -141,11 +141,11 @@ def test_pr_workflow_failed(staging_main_env, mock_build_result): capture_output=True, ) - print(f"Polling products/SLFO PR #{forwarded_pr_number} for final status...") + print(f"Polling myproducts/mySLFO PR #{forwarded_pr_number} for final status...") status_comment_found = False for _ in range(20): time.sleep(1) - timeline_events = gitea_env.get_timeline_events("products/SLFO", forwarded_pr_number) + timeline_events = gitea_env.get_timeline_events("myproducts/mySLFO", forwarded_pr_number) for event in timeline_events: if event.get("body") and "failed" in event["body"]: status_comment_found = True diff --git a/integration/tests/workflow_pr_label_test.py b/integration/tests/workflow_pr_label_test.py index 9274291..6fa8469 100644 --- a/integration/tests/workflow_pr_label_test.py +++ b/integration/tests/workflow_pr_label_test.py @@ -29,23 +29,23 @@ def test_001_project_pr_labels(label_env, staging_bot_client): 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) + print(f"--- Creating package PR in mypool/pkgA on branch {branch_name} ---") + package_pr = gitea_env.create_gitea_pr("mypool/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}") + print(f"Created package PR mypool/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...") + print(f"Polling mypool/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) + timeline_events = gitea_env.get_timeline_events("mypool/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) + match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check) if match: project_pr_number = int(match.group(1)) break @@ -53,16 +53,16 @@ index 0000000..e69de29 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}") + print(f"Found project PR: myproducts/mySLFO#{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}...") + print(f"Checking for 'staging/Backlog' label on project PR myproducts/mySLFO#{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) + project_pr_details = gitea_env.get_pr_details("myproducts/mySLFO", project_pr_number) labels = project_pr_details.get("labels", []) label_names = [l["name"] for l in labels] if expected_backlog_label in label_names: @@ -70,21 +70,21 @@ index 0000000..e69de29 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}'.") + assert backlog_label_found, f"Project PR myproducts/mySLFO#{project_pr_number} does not have the expected label '{expected_backlog_label}'." + print(f"Project PR myproducts/mySLFO#{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") + print(f"--- Posting approval from autogits_obs_staging_bot on project PR myproducts/mySLFO#{project_pr_number} ---") + staging_bot_client.create_review("myproducts/mySLFO", 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}...") + print(f"Checking for 'review/Pending' label on project PR myproducts/mySLFO#{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) + project_pr_details = gitea_env.get_pr_details("myproducts/mySLFO", project_pr_number) labels = project_pr_details.get("labels", []) label_names = [l["name"] for l in labels] print(f"Current labels: {label_names}") @@ -93,5 +93,5 @@ index 0000000..e69de29 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}'.") + assert pending_label_found, f"Project PR myproducts/mySLFO#{project_pr_number} does not have the expected label '{expected_pending_label}'." + print(f"Project PR myproducts/mySLFO#{project_pr_number} has the expected label '{expected_pending_label}'.") diff --git a/integration/tests/workflow_pr_merge_test.py b/integration/tests/workflow_pr_merge_test.py index 6a840fe..f01a178 100644 --- a/integration/tests/workflow_pr_merge_test.py +++ b/integration/tests/workflow_pr_merge_test.py @@ -19,23 +19,23 @@ def test_001_automerge(automerge_env, test_user_client): new file mode 100644 index 0000000..e69de29 """ - print(f"--- Creating package PR in pool/pkgA on branch {merge_branch_name} ---") - package_pr = test_user_client.create_gitea_pr("pool/pkgA", diff, "Test Automerge Fixture", False, base_branch=merge_branch_name) + print(f"--- Creating package PR in mypool/pkgA on branch {merge_branch_name} ---") + package_pr = test_user_client.create_gitea_pr("mypool/pkgA", diff, "Test Automerge Fixture", False, base_branch=merge_branch_name) package_pr_number = package_pr["number"] - print(f"Created package PR pool/pkgA#{package_pr_number}") + print(f"Created package PR mypool/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...") + print(f"Polling mypool/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) + timeline_events = gitea_env.get_timeline_events("mypool/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) + match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check) if match: project_pr_number = int(match.group(1)) break @@ -43,7 +43,7 @@ index 0000000..e69de29 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}") + print(f"Found project PR: myproducts/mySLFO#{project_pr_number}") # 3. Approve reviews and verify merged print("Approving reviews and verifying both PRs are merged...") @@ -51,29 +51,29 @@ index 0000000..e69de29 project_merged = False for i in range(20): # Poll for up to 20 seconds - gitea_env.approve_requested_reviews("pool/pkgA", package_pr_number) - gitea_env.approve_requested_reviews("products/SLFO", project_pr_number) + gitea_env.approve_requested_reviews("mypool/pkgA", package_pr_number) + gitea_env.approve_requested_reviews("myproducts/mySLFO", project_pr_number) if not package_merged: - pkg_details = gitea_env.get_pr_details("pool/pkgA", package_pr_number) + pkg_details = gitea_env.get_pr_details("mypool/pkgA", package_pr_number) if pkg_details.get("merged"): package_merged = True - print(f"Package PR pool/pkgA#{package_pr_number} merged.") + print(f"Package PR mypool/pkgA#{package_pr_number} merged.") # Project PR if not project_merged: - prj_details = gitea_env.get_pr_details("products/SLFO", project_pr_number) + prj_details = gitea_env.get_pr_details("myproducts/mySLFO", project_pr_number) if prj_details.get("merged"): project_merged = True - print(f"Project PR products/SLFO#{project_pr_number} merged.") + print(f"Project PR myproducts/mySLFO#{project_pr_number} merged.") if package_merged and project_merged: break time.sleep(1) - assert package_merged, f"Package PR pool/pkgA#{package_pr_number} was not merged automatically." - assert project_merged, f"Project PR products/SLFO#{project_pr_number} was not merged automatically." + assert package_merged, f"Package PR mypool/pkgA#{package_pr_number} was not merged automatically." + assert project_merged, f"Project PR myproducts/mySLFO#{project_pr_number} was not merged automatically." print("Both PRs merged successfully.") @pytest.mark.t002 @@ -92,23 +92,23 @@ def test_002_manual_merge(manual_merge_env, test_user_client, usera_client, stag new file mode 100644 index 0000000..e69de29 """ - print(f"--- Creating package PR in pool/pkgA on branch {merge_branch_name} ---") - package_pr = test_user_client.create_gitea_pr("pool/pkgA", diff, "Test Manual Merge Fixture", False, base_branch=merge_branch_name) + print(f"--- Creating package PR in mypool/pkgA on branch {merge_branch_name} ---") + package_pr = test_user_client.create_gitea_pr("mypool/pkgA", diff, "Test Manual Merge Fixture", False, base_branch=merge_branch_name) package_pr_number = package_pr["number"] - print(f"Created package PR pool/pkgA#{package_pr_number}") + print(f"Created package PR mypool/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...") + print(f"Polling mypool/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) + timeline_events = gitea_env.get_timeline_events("mypool/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) + match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check) if match: project_pr_number = int(match.group(1)) break @@ -116,44 +116,56 @@ index 0000000..e69de29 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}") + print(f"Found project PR: myproducts/mySLFO#{project_pr_number}") # 3. Approve reviews and verify NOT merged - print("Approving reviews and verifying PRs are NOT merged yet...") + print("Waiting for all expected review requests and approving them...") + # Expected reviewers based on manual-merge branch config and pkgA maintainership + expected_reviewers = {"usera", "userb", "ownerA", "ownerX", "ownerY"} + # ManualMergeOnly still requires regular reviews to be satisfied. - # We poll for review requests and approve them. - for _ in range(15): - gitea_env.approve_requested_reviews("pool/pkgA", package_pr_number) - gitea_env.approve_requested_reviews("products/SLFO", project_pr_number) + # We poll until all expected reviewers are requested, then approve them. + all_requested = False + for _ in range(30): + # Trigger approvals for whatever is already requested + gitea_env.approve_requested_reviews("mypool/pkgA", package_pr_number) + gitea_env.approve_requested_reviews("myproducts/mySLFO", project_pr_number) # Explicitly handle staging bot if it is requested or pending - prj_reviews = gitea_env.list_reviews("products/SLFO", project_pr_number) + prj_reviews = gitea_env.list_reviews("myproducts/mySLFO", project_pr_number) if any(r["user"]["login"] == "autogits_obs_staging_bot" and r["state"] in ["REQUEST_REVIEW", "PENDING"] for r in prj_reviews): print("Staging bot has a pending/requested review. Approving...") - staging_bot_client.create_review("products/SLFO", project_pr_number, event="APPROVED", body="Staging bot approves") + staging_bot_client.create_review("myproducts/mySLFO", project_pr_number, event="APPROVED", body="Staging bot approves") - pkg_details = gitea_env.get_pr_details("pool/pkgA", package_pr_number) - prj_details = gitea_env.get_pr_details("products/SLFO", project_pr_number) + # Check if all expected reviewers have at least one review record (any state) + pkg_reviews = gitea_env.list_reviews("mypool/pkgA", package_pr_number) + current_reviewers = {r["user"]["login"] for r in pkg_reviews} + + if expected_reviewers.issubset(current_reviewers): + # Also ensure they are all approved (not just requested) + approved_reviewers = {r["user"]["login"] for r in pkg_reviews if r["state"] == "APPROVED"} + if expected_reviewers.issubset(approved_reviewers): + # And check project PR for bot approval + prj_approved = any(r["user"]["login"] == "autogits_obs_staging_bot" and r["state"] == "APPROVED" for r in prj_reviews) + if prj_approved: + all_requested = True + print(f"All expected reviewers {expected_reviewers} and staging bot have approved.") + break + + pkg_details = gitea_env.get_pr_details("mypool/pkgA", package_pr_number) + prj_details = gitea_env.get_pr_details("myproducts/mySLFO", project_pr_number) assert not pkg_details.get("merged"), "Package PR merged prematurely (ManualMergeOnly ignored?)" assert not prj_details.get("merged"), "Project PR merged prematurely (ManualMergeOnly ignored?)" - # Check if we have at least some reviews to consider it "ready" for merge ok - pkg_reviews = gitea_env.list_reviews("pool/pkgA", package_pr_number) - prj_reviews = gitea_env.list_reviews("products/SLFO", project_pr_number) - - pkg_approved = any(r["state"] == "APPROVED" for r in pkg_reviews) - prj_approved = any(r["state"] == "APPROVED" for r in prj_reviews) - - if pkg_approved and prj_approved: - print("Both PRs have approvals but are not merged (as expected with ManualMergeOnly).") - break - time.sleep(2) + assert all_requested, f"Timed out waiting for all expected reviewers {expected_reviewers} to approve. Current: {current_reviewers}" + print("Both PRs have all required approvals but are not merged (as expected with ManualMergeOnly).") + # 4. Comment "merge ok" from a requested reviewer (usera) print("Commenting 'merge ok' on package PR...") - usera_client.create_issue_comment("pool/pkgA", package_pr_number, "merge ok") + usera_client.create_issue_comment("mypool/pkgA", package_pr_number, "merge ok") # 5. Verify both PRs are merged print("Polling for PR merge status...") @@ -162,22 +174,22 @@ index 0000000..e69de29 for i in range(20): # Poll for up to 20 seconds if not package_merged: - pkg_details = gitea_env.get_pr_details("pool/pkgA", package_pr_number) + pkg_details = gitea_env.get_pr_details("mypool/pkgA", package_pr_number) if pkg_details.get("merged"): package_merged = True - print(f"Package PR pool/pkgA#{package_pr_number} merged.") + print(f"Package PR mypool/pkgA#{package_pr_number} merged.") if not project_merged: - prj_details = gitea_env.get_pr_details("products/SLFO", project_pr_number) + prj_details = gitea_env.get_pr_details("myproducts/mySLFO", project_pr_number) if prj_details.get("merged"): project_merged = True - print(f"Project PR products/SLFO#{project_pr_number} merged.") + print(f"Project PR myproducts/mySLFO#{project_pr_number} merged.") if package_merged and project_merged: break time.sleep(1) - assert package_merged, f"Package PR pool/pkgA#{package_pr_number} was not merged after 'merge ok'." - assert project_merged, f"Project PR products/SLFO#{project_pr_number} was not merged after 'merge ok'." + assert package_merged, f"Package PR mypool/pkgA#{package_pr_number} was not merged after 'merge ok'." + assert project_merged, f"Project PR myproducts/mySLFO#{project_pr_number} was not merged after 'merge ok'." print("Both PRs merged successfully after 'merge ok'.") diff --git a/integration/tests/workflow_pr_review_test.py b/integration/tests/workflow_pr_review_test.py index 498ab39..f3985c8 100644 --- a/integration/tests/workflow_pr_review_test.py +++ b/integration/tests/workflow_pr_review_test.py @@ -15,24 +15,24 @@ def test_001_review_requests_matching_config(automerge_env, ownerA_client): """ gitea_env, test_full_repo_name, branch_name = automerge_env - # 1. Create a package PR for pool/pkgB as ownerA + # 1. Create a package PR for mypool/pkgB as ownerA ts = int(time.time() * 1000) filename = f"pkgB_test_{ts}.txt" diff = f"""diff --git a/{filename} b/{filename} 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) + print(f"--- Creating package PR in mypool/pkgB on branch {branch_name} as ownerA ---") + package_pr = ownerA_client.create_gitea_pr("mypool/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}") + print(f"Created package PR mypool/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) + reviews = gitea_env.list_reviews("mypool/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 @@ -63,9 +63,9 @@ def test_004_maintainer(maintainer_env, ownerA_client): # 0.1 Verify all users from config exist print("--- Verifying all users from config exist ---") import json - wf_file = gitea_env.get_file_info("products", "SLFO", "workflow.config", branch=branch_name) + wf_file = gitea_env.get_file_info("myproducts", "mySLFO", "workflow.config", branch=branch_name) wf = json.loads(base64.b64decode(wf_file["content"]).decode("utf-8")) - mt_file = gitea_env.get_file_info("products", "SLFO", "_maintainership.json", branch=branch_name) + mt_file = gitea_env.get_file_info("myproducts", "mySLFO", "_maintainership.json", branch=branch_name) mt = json.loads(base64.b64decode(mt_file["content"]).decode("utf-8")) expected_users = set() @@ -88,23 +88,23 @@ def test_004_maintainer(maintainer_env, ownerA_client): new file mode 100644 index 0000000..e69de29 """ - print(f"--- Creating package PR in pool/pkgA on branch {branch_name} as ownerA ---") - package_pr = ownerA_client.create_gitea_pr("pool/pkgA", diff, "Test Maintainer Merge", True, base_branch=branch_name) + print(f"--- Creating package PR in mypool/pkgA on branch {branch_name} as ownerA ---") + package_pr = ownerA_client.create_gitea_pr("mypool/pkgA", diff, "Test Maintainer Merge", True, base_branch=branch_name) package_pr_number = package_pr["number"] - print(f"Created package PR pool/pkgA#{package_pr_number}") + print(f"Created package PR mypool/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...") + print(f"Polling mypool/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) + timeline_events = gitea_env.get_timeline_events("mypool/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) + match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check) if match: project_pr_number = int(match.group(1)) break @@ -112,7 +112,7 @@ index 0000000..e69de29 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}") + print(f"Found project PR: myproducts/mySLFO#{project_pr_number}") # 3. Make sure both PRs are merged automatically WITHOUT manual approvals print("Polling for PR merge status (only bot approval allowed)...") @@ -122,35 +122,35 @@ index 0000000..e69de29 for i in range(15): # Poll for up to 15 seconds # Package PR if not package_merged: - pkg_details = gitea_env.get_pr_details("pool/pkgA", package_pr_number) + pkg_details = gitea_env.get_pr_details("mypool/pkgA", package_pr_number) if pkg_details.get("merged"): package_merged = True - print(f"Package PR pool/pkgA#{package_pr_number} merged.") + print(f"Package PR mypool/pkgA#{package_pr_number} merged.") else: # Approve ONLY bot if requested - reviews = gitea_env.list_reviews("pool/pkgA", package_pr_number) + reviews = gitea_env.list_reviews("mypool/pkgA", package_pr_number) if any(r["state"] == "REQUEST_REVIEW" and r["user"]["login"] == "autogits_obs_staging_bot" for r in reviews): - gitea_env.approve_requested_reviews("pool/pkgA", package_pr_number) + gitea_env.approve_requested_reviews("mypool/pkgA", package_pr_number) # Project PR if not project_merged: - prj_details = gitea_env.get_pr_details("products/SLFO", project_pr_number) + prj_details = gitea_env.get_pr_details("myproducts/mySLFO", project_pr_number) if prj_details.get("merged"): project_merged = True - print(f"Project PR products/SLFO#{project_pr_number} merged.") + print(f"Project PR myproducts/mySLFO#{project_pr_number} merged.") else: # Approve ONLY bot if requested - reviews = gitea_env.list_reviews("products/SLFO", project_pr_number) + reviews = gitea_env.list_reviews("myproducts/mySLFO", project_pr_number) if any(r["state"] == "REQUEST_REVIEW" and r["user"]["login"] == "autogits_obs_staging_bot" for r in reviews): - gitea_env.approve_requested_reviews("products/SLFO", project_pr_number) + gitea_env.approve_requested_reviews("myproducts/mySLFO", project_pr_number) if package_merged and project_merged: break time.sleep(1) - assert package_merged, f"Package PR pool/pkgA#{package_pr_number} was not merged automatically." - assert project_merged, f"Project PR products/SLFO#{project_pr_number} was not merged automatically." + assert package_merged, f"Package PR mypool/pkgA#{package_pr_number} was not merged automatically." + assert project_merged, f"Project PR myproducts/mySLFO#{project_pr_number} was not merged automatically." print("Both PRs merged successfully by maintainer rule.") @@ -166,30 +166,30 @@ def test_005_any_maintainer_approval_sufficient(maintainer_env, ownerA_client, o """ gitea_env, test_full_repo_name, branch_name = maintainer_env - # 1. Create a package PR for pool/pkgB as ownerA + # 1. Create a package PR for mypool/pkgB as ownerA ts = int(time.time() * 1000) filename = f"pkgB_test_{ts}.txt" diff = f"""diff --git a/{filename} b/{filename} new file mode 100644 index 0000000..e69de29 """ - print(f"--- Creating package PR in pool/pkgB on branch {branch_name} as ownerA ---") - package_pr = ownerA_client.create_gitea_pr("pool/pkgB", diff, "Test Single Maintainer Merge", True, base_branch=branch_name) + print(f"--- Creating package PR in mypool/pkgB on branch {branch_name} as ownerA ---") + package_pr = ownerA_client.create_gitea_pr("mypool/pkgB", diff, "Test Single Maintainer Merge", True, base_branch=branch_name) package_pr_number = package_pr["number"] - print(f"Created package PR pool/pkgB#{package_pr_number}") + print(f"Created package PR mypool/pkgB#{package_pr_number}") # 2. Make sure the workflow-pr service created related project PR project_pr_number = None - print(f"Polling pool/pkgB PR #{package_pr_number} timeline for forwarded PR event...") + print(f"Polling mypool/pkgB PR #{package_pr_number} timeline for forwarded PR event...") for _ in range(40): time.sleep(1) - timeline_events = gitea_env.get_timeline_events("pool/pkgB", package_pr_number) + timeline_events = gitea_env.get_timeline_events("mypool/pkgB", package_pr_number) for event in timeline_events: if event.get("type") == "pull_ref": if not (ref_issue := event.get("ref_issue")): continue url_to_check = ref_issue.get("html_url", "") - match = re.search(r"products/SLFO/pulls/(\d+)", url_to_check) + match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check) if match: project_pr_number = int(match.group(1)) break @@ -197,13 +197,13 @@ index 0000000..e69de29 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}") + print(f"Found project PR: myproducts/mySLFO#{project_pr_number}") # 3. Check that review requests came to ownerB and ownerBB print("Checking for review requests from ownerB and ownerBB...") reviewers_requested = set() for _ in range(20): - reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number) + reviews = gitea_env.list_reviews("mypool/pkgB", package_pr_number) reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"} if "ownerB" in reviewers_requested and "ownerBB" in reviewers_requested: break @@ -215,14 +215,14 @@ index 0000000..e69de29 # 4. ownerBB leaves review, ownerB does not. print("ownerBB approving the PR...") - ownerBB_client.create_review("pool/pkgB", package_pr_number, event="APPROVED", body="Approval from ownerBB") + ownerBB_client.create_review("mypool/pkgB", package_pr_number, event="APPROVED", body="Approval from ownerBB") # 5. Check that review request for ownerB is removed print("Polling for ownerB review request removal...") ownerB_removed = False for _ in range(30): time.sleep(1) - reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number) + reviews = gitea_env.list_reviews("mypool/pkgB", package_pr_number) reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"} if "ownerB" not in reviewers_requested: ownerB_removed = True @@ -244,39 +244,39 @@ def test_006_maintainer_rejection_removes_other_requests(maintainer_env, ownerA_ """ gitea_env, test_full_repo_name, branch_name = maintainer_env - # 1. Create a package PR for pool/pkgB as ownerA + # 1. Create a package PR for mypool/pkgB as ownerA ts = int(time.time() * 1000) filename = f"pkgB_rejection_test_{ts}.txt" diff = f"""diff --git a/{filename} b/{filename} new file mode 100644 index 0000000..e69de29 """ - print(f"--- Creating package PR in pool/pkgB on branch {branch_name} as ownerA ---") - package_pr = ownerA_client.create_gitea_pr("pool/pkgB", diff, "Test Maintainer Rejection", True, base_branch=branch_name) + print(f"--- Creating package PR in mypool/pkgB on branch {branch_name} as ownerA ---") + package_pr = ownerA_client.create_gitea_pr("mypool/pkgB", diff, "Test Maintainer Rejection", True, base_branch=branch_name) package_pr_number = package_pr["number"] - print(f"Created package PR pool/pkgB#{package_pr_number}") + print(f"Created package PR mypool/pkgB#{package_pr_number}") # 2. Check that review requests came to ownerB and ownerBB print("Checking for review requests from ownerB and ownerBB...") for _ in range(20): - reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number) + reviews = gitea_env.list_reviews("mypool/pkgB", package_pr_number) reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"} if "ownerB" in reviewers_requested and "ownerBB" in reviewers_requested: break time.sleep(1) else: - reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number) + reviews = gitea_env.list_reviews("mypool/pkgB", package_pr_number) reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"} pytest.fail(f"ownerB and ownerBB were not both requested. Got: {reviewers_requested}") # 3. ownerBB rejects the PR print("ownerBB rejecting the PR...") - ownerBB_client.create_review("pool/pkgB", package_pr_number, event="REQUEST_CHANGES", body="Rejecting from ownerBB") + ownerBB_client.create_review("mypool/pkgB", package_pr_number, event="REQUEST_CHANGES", body="Rejecting from ownerBB") # 4. Check that review request for ownerB is removed print("Checking if ownerB's review request is removed...") for _ in range(20): - reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number) + reviews = gitea_env.list_reviews("mypool/pkgB", package_pr_number) reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"} if "ownerB" not in reviewers_requested: print("Confirmed: ownerB's review request was removed.") @@ -304,30 +304,30 @@ def test_007_review_required_needs_all_approvals(review_required_env, ownerA_cli ownerA_client._request("GET", "users/admin") print(f"ownerA_client smoke test passed") - # 1. Create a package PR for pool/pkgB as ownerA + # 1. Create a package PR for mypool/pkgB as ownerA ts = int(time.time() * 1000) filename = f"pkgB_review_required_test_{ts}.txt" diff = f"""diff --git a/{filename} b/{filename} new file mode 100644 index 0000000..e69de29 """ - print(f"--- Creating package PR in pool/pkgB on branch {branch_name} as ownerA ---") - package_pr = ownerA_client.create_gitea_pr("pool/pkgB", diff, "Test Review Required", True, base_branch=branch_name) + print(f"--- Creating package PR in mypool/pkgB on branch {branch_name} as ownerA ---") + package_pr = ownerA_client.create_gitea_pr("mypool/pkgB", diff, "Test Review Required", True, base_branch=branch_name) package_pr_number = package_pr["number"] - print(f"Created package PR pool/pkgB#{package_pr_number}") + print(f"Created package PR mypool/pkgB#{package_pr_number}") # 2. Make sure the workflow-pr service created related project PR project_pr_number = None - print(f"Polling pool/pkgB PR #{package_pr_number} timeline for forwarded PR event...") + print(f"Polling mypool/pkgB PR #{package_pr_number} timeline for forwarded PR event...") for _ in range(40): time.sleep(1) - timeline_events = gitea_env.get_timeline_events("pool/pkgB", package_pr_number) + timeline_events = gitea_env.get_timeline_events("mypool/pkgB", package_pr_number) for event in timeline_events: if event.get("type") == "pull_ref": if not (ref_issue := event.get("ref_issue")): continue url_to_check = ref_issue.get("html_url", "") - match = re.search(r"products/SLFO/pulls/(\d+)", url_to_check) + match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check) if match: project_pr_number = int(match.group(1)) break @@ -335,38 +335,38 @@ index 0000000..e69de29 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}") + print(f"Found project PR: myproducts/mySLFO#{project_pr_number}") # 3. Check that review requests came to ownerB and ownerBB print("Checking for review requests from ownerB and ownerBB...") for _ in range(20): - reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number) + reviews = gitea_env.list_reviews("mypool/pkgB", package_pr_number) reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"} if "ownerB" in reviewers_requested and "ownerBB" in reviewers_requested: break time.sleep(1) else: - reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number) + reviews = gitea_env.list_reviews("mypool/pkgB", package_pr_number) reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"} pytest.fail(f"ownerB and ownerBB were not both requested. Got: {reviewers_requested}") # 4. ownerBB leaves review, ownerB does not. print("ownerBB approving the PR...") - ownerBB_client.create_review("pool/pkgB", package_pr_number, event="APPROVED", body="Approval from ownerBB") + ownerBB_client.create_review("mypool/pkgB", package_pr_number, event="APPROVED", body="Approval from ownerBB") # 5. Check that the PR is NOT merged automatically and ownerB request remains print("Waiting to ensure PR is NOT merged and ownerB request remains...") for i in range(10): - pkg_details = gitea_env.get_pr_details("pool/pkgB", package_pr_number) - reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number) + pkg_details = gitea_env.get_pr_details("mypool/pkgB", package_pr_number) + reviews = gitea_env.list_reviews("mypool/pkgB", package_pr_number) review_states = [(r["user"]["login"], r["state"]) for r in reviews] print(f"Attempt {i+1}: Merged={pkg_details.get('merged')}, Reviews={review_states}") time.sleep(2) - pkg_details = gitea_env.get_pr_details("pool/pkgB", package_pr_number) + pkg_details = gitea_env.get_pr_details("mypool/pkgB", package_pr_number) assert not pkg_details.get("merged"), "Package PR was merged automatically but it should NOT have been (ReviewRequired=true)." - reviews = gitea_env.list_reviews("pool/pkgB", package_pr_number) + reviews = gitea_env.list_reviews("mypool/pkgB", package_pr_number) reviewers_requested = {r["user"]["login"] for r in reviews if r["state"] == "REQUEST_REVIEW"} assert "ownerB" in reviewers_requested, f"ownerB's review request was removed, but it should have remained. All reviews: {[(r['user']['login'], r['state']) for r in reviews]}" diff --git a/integration/tests/workflow_pr_sync_test.py b/integration/tests/workflow_pr_sync_test.py index 720828e..b9590c5 100755 --- a/integration/tests/workflow_pr_sync_test.py +++ b/integration/tests/workflow_pr_sync_test.py @@ -23,7 +23,7 @@ pytest.forwarded_pr_number = None def test_001_project_pr(gitea_env): """Forwarded PR correct title""" diff = "diff --git a/another_test.txt b/another_test.txt\nnew file mode 100644\nindex 0000000..e69de29\n" - pytest.pr = gitea_env.create_gitea_pr("pool/pkgA", diff, "Test PR", False) + pytest.pr = gitea_env.create_gitea_pr("mypool/pkgA", diff, "Test PR", False) pytest.initial_pr_number = pytest.pr["number"] time.sleep(5) # Give Gitea some time to process the PR and make the timeline available @@ -31,18 +31,18 @@ def test_001_project_pr(gitea_env): pytest.forwarded_pr_number = None print( - f"Polling pool/pkgA PR #{pytest.initial_pr_number} timeline for forwarded PR event..." + f"Polling mypool/pkgA PR #{pytest.initial_pr_number} timeline for forwarded PR event..." ) # Instead of polling timeline, check if forwarded PR exists directly for _ in range(20): time.sleep(1) - timeline_events = gitea_env.get_timeline_events("pool/pkgA", pytest.initial_pr_number) + timeline_events = gitea_env.get_timeline_events("mypool/pkgA", pytest.initial_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) + match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check) if match: pytest.forwarded_pr_number = match.group(1) break @@ -51,7 +51,7 @@ def test_001_project_pr(gitea_env): assert ( pytest.forwarded_pr_number is not None ), "Workflow bot did not create a forwarded PR." - pytest.pr_details = gitea_env.get_pr_details("products/SLFO", pytest.forwarded_pr_number) + pytest.pr_details = gitea_env.get_pr_details("myproducts/mySLFO", pytest.forwarded_pr_number) assert ( pytest.pr_details["title"] == "Forwarded PRs: pkgA" ), "Forwarded PR correct title" @@ -62,13 +62,13 @@ def test_001_project_pr(gitea_env): def test_002_updated_project_pr(gitea_env): """Forwarded PR head is updated""" diff = "diff --git a/another_test.txt b/another_test.txt\nnew file mode 100444\nindex 0000000..e69de21\n" - gitea_env.modify_gitea_pr("pool/pkgA", pytest.initial_pr_number, diff, "Tweaks") + gitea_env.modify_gitea_pr("mypool/pkgA", pytest.initial_pr_number, diff, "Tweaks") sha_old = pytest.pr_details["head"]["sha"] sha_changed = False for _ in range(20): time.sleep(1) - new_pr_details = gitea_env.get_pr_details("products/SLFO", pytest.forwarded_pr_number) + new_pr_details = gitea_env.get_pr_details("myproducts/mySLFO", pytest.forwarded_pr_number) sha_new = new_pr_details["head"]["sha"] if sha_new != sha_old: print(f"Sha changed from {sha_old} to {sha_new}") @@ -82,17 +82,17 @@ def test_002_updated_project_pr(gitea_env): @pytest.mark.dependency(depends=["test_001_project_pr"]) def test_003_wip(gitea_env): """WIP flag set for PR""" - # 1. set WIP flag in PR f"pool/pkgA#{pytest.initial_pr_number}" - initial_pr_details = gitea_env.get_pr_details("pool/pkgA", pytest.initial_pr_number) + # 1. set WIP flag in PR f"mypool/pkgA#{pytest.initial_pr_number}" + initial_pr_details = gitea_env.get_pr_details("mypool/pkgA", pytest.initial_pr_number) wip_title = "WIP: " + initial_pr_details["title"] - gitea_env.update_gitea_pr_properties("pool/pkgA", pytest.initial_pr_number, title=wip_title) - # 2. in loop check whether WIP flag is set for PR f"products/SLFO #{pytest.forwarded_pr_number}" + gitea_env.update_gitea_pr_properties("mypool/pkgA", pytest.initial_pr_number, title=wip_title) + # 2. in loop check whether WIP flag is set for PR f"myproducts/mySLFO #{pytest.forwarded_pr_number}" wip_flag_set = False for _ in range(20): time.sleep(1) forwarded_pr_details = gitea_env.get_pr_details( - "products/SLFO", pytest.forwarded_pr_number + "myproducts/mySLFO", pytest.forwarded_pr_number ) if "WIP: " in forwarded_pr_details["title"]: wip_flag_set = True @@ -100,19 +100,19 @@ def test_003_wip(gitea_env): assert wip_flag_set, "WIP flag was not set in the forwarded PR." - # Remove WIP flag from PR f"pool/pkgA#{pytest.initial_pr_number}" - initial_pr_details = gitea_env.get_pr_details("pool/pkgA", pytest.initial_pr_number) + # Remove WIP flag from PR f"mypool/pkgA#{pytest.initial_pr_number}" + initial_pr_details = gitea_env.get_pr_details("mypool/pkgA", pytest.initial_pr_number) non_wip_title = initial_pr_details["title"].replace("WIP: ", "") gitea_env.update_gitea_pr_properties( - "pool/pkgA", pytest.initial_pr_number, title=non_wip_title + "mypool/pkgA", pytest.initial_pr_number, title=non_wip_title ) - # In loop check whether WIP flag is removed for PR f"products/SLFO #{pytest.forwarded_pr_number}" + # In loop check whether WIP flag is removed for PR f"myproducts/mySLFO #{pytest.forwarded_pr_number}" wip_flag_removed = False for _ in range(20): time.sleep(1) forwarded_pr_details = gitea_env.get_pr_details( - "products/SLFO", pytest.forwarded_pr_number + "myproducts/mySLFO", pytest.forwarded_pr_number ) if "WIP: " not in forwarded_pr_details["title"]: wip_flag_removed = True @@ -139,7 +139,7 @@ index 0000000..e69de29 @@ -0,0 +1 @@ +Initial content """ - package_pr = test_user_client.create_gitea_pr("pool/pkgA", initial_diff, "Test PR for No Project PR, No Edits", False, base_branch=dev_branch_name) + package_pr = test_user_client.create_gitea_pr("mypool/pkgA", initial_diff, "Test PR for No Project PR, No Edits", False, base_branch=dev_branch_name) package_pr_number = package_pr["number"] print(f"Created Package PR #{package_pr_number}") @@ -147,29 +147,29 @@ index 0000000..e69de29 project_pr_created = False for i in range(10): # Poll for some time time.sleep(2) - timeline_events = gitea_env.get_timeline_events("pool/pkgA", package_pr_number) + timeline_events = gitea_env.get_timeline_events("mypool/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) + match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check) if match: project_pr_created = True break if project_pr_created: break - assert not project_pr_created, "Workflow bot unexpectedly created a Project PR in products/SLFO." + assert not project_pr_created, "Workflow bot unexpectedly created a Project PR in myproducts/mySLFO." print("Verification complete: No Project PR was created by the bot.") # 3. Manually create the Project PR - pkgA_main_sha = gitea_env._request("GET", f"repos/pool/pkgA/branches/{dev_branch_name}").json()["commit"]["id"] - package_pr_details = gitea_env.get_pr_details("pool/pkgA", package_pr_number) + pkgA_main_sha = gitea_env._request("GET", f"repos/mypool/pkgA/branches/{dev_branch_name}").json()["commit"]["id"] + package_pr_details = gitea_env.get_pr_details("mypool/pkgA", package_pr_number) pkgA_pr_head_sha = package_pr_details["head"]["sha"] project_pr_title = "Forwarded PRs: pkgA (Manual)" - project_pr_body = f"Manual Project PR for NoProjectGitPR. \nPR: pool/pkgA!{package_pr_number}" + project_pr_body = f"Manual Project PR for NoProjectGitPR. \nPR: mypool/pkgA!{package_pr_number}" project_pr_diff = f"""diff --git a/pkgA b/pkgA index {pkgA_main_sha[:7]}..{pkgA_pr_head_sha[:7]} 160000 --- a/pkgA @@ -199,14 +199,14 @@ index 0000000..e69de29 @@ -0,0 +1 @@ +Trigger content """ - test_user_client.modify_gitea_pr("pool/pkgA", package_pr_number, new_diff_content, "Trigger bot update") + test_user_client.modify_gitea_pr("mypool/pkgA", package_pr_number, new_diff_content, "Trigger bot update") # 5. Verify that the bot adds a warning comment because it cannot update the manual PR (edits disabled) warning_found = False print(f"Polling Package PR #{package_pr_number} for warning comment...") for _ in range(20): time.sleep(3) - comments = gitea_env.get_comments("pool/pkgA", package_pr_number) + comments = gitea_env.get_comments("mypool/pkgA", package_pr_number) for comment in comments: # According to test-plan.md, the warning explains that it cannot update the PR. if "cannot update" in comment.get("body", "").lower(): @@ -239,42 +239,42 @@ index 0000000..e69de29 @@ -0,0 +1 @@ +New feature content """ - package_pr = test_user_client.create_gitea_pr("pool/pkgA", diff, "Test PR for NoProjectGitPR", False, base_branch=dev_branch_name) + package_pr = test_user_client.create_gitea_pr("mypool/pkgA", diff, "Test PR for NoProjectGitPR", False, base_branch=dev_branch_name) package_pr_number = package_pr["number"] # Enable "Allow edits from maintainers" - test_user_client.update_gitea_pr_properties("pool/pkgA", package_pr_number, allow_maintainer_edit=True) + test_user_client.update_gitea_pr_properties("mypool/pkgA", package_pr_number, allow_maintainer_edit=True) print(f"Created Package PR #{package_pr_number} and enabled 'Allow edits from maintainers'.") # Get SHAs needed for the manual Project PR diff - pkgA_main_sha = gitea_env._request("GET", f"repos/pool/pkgA/branches/{dev_branch_name}").json()["commit"]["id"] - package_pr_details = gitea_env.get_pr_details("pool/pkgA", package_pr_number) + pkgA_main_sha = gitea_env._request("GET", f"repos/mypool/pkgA/branches/{dev_branch_name}").json()["commit"]["id"] + package_pr_details = gitea_env.get_pr_details("mypool/pkgA", package_pr_number) pkgA_pr_head_sha = package_pr_details["head"]["sha"] - # 3. Assert that the workflow-pr bot did not create a Project PR in the products/SLFO repository + # 3. Assert that the workflow-pr bot did not create a Project PR in the myproducts/mySLFO repository project_pr_created = False for i in range(20): # Poll for a reasonable time time.sleep(2) # Wait a bit longer to be sure - timeline_events = gitea_env.get_timeline_events("pool/pkgA", package_pr_number) + timeline_events = gitea_env.get_timeline_events("mypool/pkgA", package_pr_number) for event in timeline_events: if event.get("type") == "pull_ref": if not (ref_issue := event.get("ref_issue")): continue url_to_check = ref_issue.get("html_url", "") - # Regex now searches for products/SLFO/pulls/(\d+) - match = re.search(r"products/SLFO/pulls/(\d+)", url_to_check) + # Regex now searches for myproducts/mySLFO/pulls/(\d+) + match = re.search(r"myproducts/mySLFO/pulls/(\d+)", url_to_check) if match: project_pr_created = True break if project_pr_created: break - assert not project_pr_created, "Workflow bot unexpectedly created a Project PR in products/SLFO." - print("Verification complete: No Project PR was created in products/SLFO as expected.") + assert not project_pr_created, "Workflow bot unexpectedly created a Project PR in myproducts/mySLFO." + print("Verification complete: No Project PR was created in myproducts/mySLFO as expected.") # 1. Create that Project PR from the test code. project_pr_title = "Forwarded PRs: pkgA" - project_pr_body = f"Test Project PR for NoProjectGitPR. \nPR: pool/pkgA!{package_pr_number}" + project_pr_body = f"Test Project PR for NoProjectGitPR. \nPR: mypool/pkgA!{package_pr_number}" project_pr_diff = f"""diff --git a/pkgA b/pkgA index {pkgA_main_sha[:7]}..{pkgA_pr_head_sha[:7]} 160000 --- a/pkgA @@ -304,7 +304,7 @@ index 0000000..f587a12 @@ -0,0 +1 @@ +Another file content """ - test_user_client.modify_gitea_pr("pool/pkgA", package_pr_number, new_diff_content, "Add another file to Package PR") + test_user_client.modify_gitea_pr("mypool/pkgA", package_pr_number, new_diff_content, "Add another file to Package PR") print(f"Added new commit to Package PR #{package_pr_number}.") time.sleep(5) # Give the bot time to react diff --git a/integration/workflow-pr/entrypoint.sh b/integration/workflow-pr/entrypoint.sh index 04b3361..c7a7d1b 100644 --- a/integration/workflow-pr/entrypoint.sh +++ b/integration/workflow-pr/entrypoint.sh @@ -19,8 +19,8 @@ export GITEA_TOKEN echo "GITEA_TOKEN exported (length: ${#GITEA_TOKEN})" # Wait for the dummy data to be created by the gitea setup script -echo "Waiting for workflow.config in products/SLFO..." -API_URL="http://gitea-test:3000/api/v1/repos/products/SLFO/contents/workflow.config" +echo "Waiting for workflow.config in myproducts/mySLFO..." +API_URL="http://gitea-test:3000/api/v1/repos/myproducts/mySLFO/contents/workflow.config" HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: token $GITEA_TOKEN" "$API_URL") while [ "$HTTP_STATUS" != "200" ]; do diff --git a/integration/workflow-pr/workflow-pr.json b/integration/workflow-pr/workflow-pr.json index 85d06ee..15401a6 100644 --- a/integration/workflow-pr/workflow-pr.json +++ b/integration/workflow-pr/workflow-pr.json @@ -1,10 +1,10 @@ [ - "products/SLFO#main", - "products/SLFO#staging-main", - "products/SLFO#dev", - "products/SLFO#merge", - "products/SLFO#maintainer-merge", - "products/SLFO#review-required", - "products/SLFO#label-test", - "products/SLFO#manual-merge" + "myproducts/mySLFO#main", + "myproducts/mySLFO#staging-main", + "myproducts/mySLFO#dev", + "myproducts/mySLFO#merge", + "myproducts/mySLFO#maintainer-merge", + "myproducts/mySLFO#review-required", + "myproducts/mySLFO#label-test", + "myproducts/mySLFO#manual-merge" ] -- 2.51.1