Compare commits
2 Commits
test5
...
t-manual-m
| Author | SHA256 | Date | |
|---|---|---|---|
|
|
4691747038 | ||
| af2ff0bdd2 |
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
TimelineCommentType_ReviewDismissed = "dismiss_review"
|
||||
TimelineCommentType_ReviewRequested = "review_request"
|
||||
TimelineCommentType_Review = "review"
|
||||
TimelineCommentType_PushPull = "pull_push"
|
||||
|
||||
@@ -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".<br>2. Remove the WIP flag from the PackageGit PR. | 1. The corresponding ProjectGit PR is also marked as "Work In Progress".<br>2. The WIP flag on the ProjectGit PR is removed. | Medium |
|
||||
| **TC-SYNC-004** | - | **WIP Flag (multiple referenced package PRs)** | 1. Create a ProjectGit PR that references multiple PackageGit PRs.<br>2. Mark one of the PackageGit PRs as "Work In Progress".<br>3. Remove the "Work In Progress" flag from all PackageGit PRs. | 1. The ProjectGit PR is marked as "Work In Progress".<br>2. The "Work In Progress" flag is removed from the ProjectGit PR only after it has been removed from all associated PackageGit PRs. | Medium |
|
||||
| **TC-SYNC-005** | x | **NoProjectGitPR = true, edits disabled** | 1. Set `NoProjectGitPR = true` in `workflow.config`.<br>2. Create a PackageGit PR without "Allow edits from maintainers" enabled. <br>3. Push a new commit to the PackageGit PR. | 1. No ProjectGit PR is created.<br>2. The bot adds a warning comment to the PackageGit PR explaining that it cannot update the PR. | High |
|
||||
| **TC-SYNC-006** | x | **NoProjectGitPR = true, edits enabled** | 1. Set `NoProjectGitPR = true` in `workflow.config`.<br>2. Create a PackageGit PR with "Allow edits from maintainers" enabled.<br>3. Push a new commit to the PackageGit PR. | 1. No ProjectGit PR is created.<br>2. The submodule commit on the project PR is updated with the new commit from the PackageGit PR. | High |
|
||||
| **TC-SYNC-005** | S | **NoProjectGitPR = true, edits disabled** | 1. Set `NoProjectGitPR = true` in `workflow.config`.<br>2. Create a PackageGit PR without "Allow edits from maintainers" enabled. <br>3. Push a new commit to the PackageGit PR. | 1. No ProjectGit PR is created.<br>2. The bot adds a warning comment to the PackageGit PR explaining that it cannot update the PR. | High |
|
||||
| **TC-SYNC-006** | S | **NoProjectGitPR = true, edits enabled** | 1. Set `NoProjectGitPR = true` in `workflow.config`.<br>2. Create a PackageGit PR with "Allow edits from maintainers" enabled.<br>3. Push a new commit to the PackageGit PR. | 1. No ProjectGit PR is created.<br>2. The submodule commit on the project PR is updated with the new commit from the PackageGit PR. | High |
|
||||
| **TC-COMMENT-001** | - | **Detect duplicate comments** | 1. Create a PackageGit PR.<br>2. Wait for the `workflow-pr` bot to act on the PR.<br>3. Edit the body of the PR to trigger the bot a second time. | 1. The bot should not post a duplicate comment. | High |
|
||||
| **TC-REVIEW-001** | P | **Add mandatory reviewers** | 1. Create a new PackageGit PR. | 1. All mandatory reviewers are added to both the PackageGit and ProjectGit PRs. | High |
|
||||
| **TC-REVIEW-002** | - | **Add advisory reviewers** | 1. Create a new PackageGit PR with advisory reviewers defined in the configuration. | 1. Advisory reviewers are added to the PR, but their approval is not required for merging. | Medium |
|
||||
| **TC-REVIEW-003** | - | **Re-add reviewers** | 1. Push a new commit to a PackageGit PR after it has been approved. | 1. The original reviewers are re-added to the PR. | Medium |
|
||||
| **TC-REVIEW-004** | x | **Package PR created by a maintainer** | 1. Create a PackageGit PR from the account of a package maintainer. | 1. No review is requested from other package maintainers. | High |
|
||||
| **TC-REVIEW-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.<br>2. One of the package maintainers approves the PR. | 1. All package maintainers are added as reviewers.<br>2. Once one maintainer approves the PR, the other maintainers are removed as reviewers. | High |
|
||||
| **TC-REVIEW-006** | P | **Package PR created by an external user (reject)** | 1. Create a PackageGit PR from the account of a user who is not a package maintainer.<br>2. One of the package maintainers rejects the PR. | 1. All package maintainers are added as reviewers.<br>2. Once one maintainer rejects the PR, the other maintainers are removed as reviewers. | High |
|
||||
| **TC-REVIEW-007** | P | **Package PR created by a maintainer with ReviewRequired=true** | 1. Set `ReviewRequired = true` in `workflow.config`.<br>2. Create a PackageGit PR from the account of a package maintainer. | 1. A review is requested from other package maintainers if available. | High |
|
||||
| **TC-MERGE-001** | x | **Automatic Merge** | 1. Create a PackageGit PR.<br>2. Ensure all mandatory reviews are completed on both project and package PRs. | 1. The PR is automatically merged. | High |
|
||||
| **TC-MERGE-002** | - | **ManualMergeOnly with Package Maintainer** | 1. Create a PackageGit PR with `ManualMergeOnly` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the package PR from the account of a package maintainer for that package. | 1. The PR is merged. | High |
|
||||
| **TC-MERGE-001** | P | **Automatic Merge** | 1. Create a PackageGit PR.<br>2. Ensure all mandatory reviews are completed on both project and package PRs. | 1. The PR is automatically merged. | High |
|
||||
| **TC-MERGE-002** | P | **ManualMergeOnly with Package Maintainer** | 1. Create a PackageGit PR with `ManualMergeOnly` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the package PR from the account of a package maintainer (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`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the package PR from the account of a user who is not a maintainer for that package. | 1. The PR is not merged. | High |
|
||||
| **TC-MERGE-004** | - | **ManualMergeOnly with multiple packages** | 1. Create a ProjectGit PR that references multiple PackageGit PRs with `ManualMergeOnly` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on each package PR from the account of a package maintainer. | 1. The PR is merged only after "merge ok" is commented on all associated PackageGit PRs. | High |
|
||||
| **TC-MERGE-005** | - | **ManualMergeOnly with Project Maintainer** | 1. Create a PackageGit PR with `ManualMergeOnly` set to `true`.<br>2. Ensure all mandatory reviews are completed on both project and package PRs.<br>3. Comment "merge ok" on the package PR from the account of a project maintainer. | 1. The PR is merged. | High |
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<title>openSUSE Leap 16.0 based on SLFO</title>
|
||||
<description>Leap 16.0 based on SLES 16.0 (specifically SLFO:1.2)</description>
|
||||
<link project="openSUSE:Backports:SLE-16.0"/>
|
||||
<scmsync>http://gitea-test:3000/products/SLFO#main</scmsync>
|
||||
<scmsync>http://gitea-test:3000/products/SLFO#staging-main</scmsync>
|
||||
<person userid="dimstar_suse" role="maintainer"/>
|
||||
<person userid="lkocman-factory" role="maintainer"/>
|
||||
<person userid="maxlin_factory" role="maintainer"/>
|
||||
@@ -56,4 +56,4 @@
|
||||
<arch>ppc64le</arch>
|
||||
<arch>s390x</arch>
|
||||
</repository>
|
||||
</project>
|
||||
</project>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'.")
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user