Skip to content

Commit 79f0729

Browse files
committed
Workaround to clean up old reviews on creating a new one (go-gitea#28554)
close go-gitea#28542 blocks go-gitea#28544 --- *Sponsored by Kithara Software GmbH*
1 parent f79530c commit 79f0729

File tree

3 files changed

+165
-9
lines changed

3 files changed

+165
-9
lines changed

models/issues/review.go

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -293,8 +293,14 @@ func IsOfficialReviewerTeam(ctx context.Context, issue *Issue, team *organizatio
293293

294294
// CreateReview creates a new review based on opts
295295
func CreateReview(ctx context.Context, opts CreateReviewOptions) (*Review, error) {
296+
ctx, committer, err := db.TxContext(ctx)
297+
if err != nil {
298+
return nil, err
299+
}
300+
defer committer.Close()
301+
sess := db.GetEngine(ctx)
302+
296303
review := &Review{
297-
Type: opts.Type,
298304
Issue: opts.Issue,
299305
IssueID: opts.Issue.ID,
300306
Reviewer: opts.Reviewer,
@@ -304,15 +310,39 @@ func CreateReview(ctx context.Context, opts CreateReviewOptions) (*Review, error
304310
CommitID: opts.CommitID,
305311
Stale: opts.Stale,
306312
}
313+
307314
if opts.Reviewer != nil {
315+
review.Type = opts.Type
308316
review.ReviewerID = opts.Reviewer.ID
309-
} else {
310-
if review.Type != ReviewTypeRequest {
311-
review.Type = ReviewTypeRequest
317+
318+
reviewCond := builder.Eq{"reviewer_id": opts.Reviewer.ID, "issue_id": opts.Issue.ID}
319+
// make sure user review requests are cleared
320+
if opts.Type != ReviewTypePending {
321+
if _, err := sess.Where(reviewCond.And(builder.Eq{"type": ReviewTypeRequest})).Delete(new(Review)); err != nil {
322+
return nil, err
323+
}
312324
}
325+
// make sure if the created review gets dismissed no old review surface
326+
// other types can be ignored, as they don't affect branch protection
327+
if opts.Type == ReviewTypeApprove || opts.Type == ReviewTypeReject {
328+
if _, err := sess.Where(reviewCond.And(builder.In("type", ReviewTypeApprove, ReviewTypeReject))).
329+
Cols("dismissed").Update(&Review{Dismissed: true}); err != nil {
330+
return nil, err
331+
}
332+
}
333+
334+
} else if opts.ReviewerTeam != nil {
335+
review.Type = ReviewTypeRequest
313336
review.ReviewerTeamID = opts.ReviewerTeam.ID
337+
338+
} else {
339+
return nil, fmt.Errorf("provide either reviewer or reviewer team")
340+
}
341+
342+
if _, err := sess.Insert(review); err != nil {
343+
return nil, err
314344
}
315-
return review, db.Insert(ctx, review)
345+
return review, committer.Commit()
316346
}
317347

318348
// GetCurrentReview returns the current pending review of reviewer for given issue

models/unittest/unit_tests.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,8 @@ func AssertSuccessfulInsert(t assert.TestingT, beans ...any) {
131131
}
132132

133133
// AssertCount assert the count of a bean
134-
func AssertCount(t assert.TestingT, bean, expected any) {
135-
assert.EqualValues(t, expected, GetCount(t, bean))
134+
func AssertCount(t assert.TestingT, bean, expected any) bool {
135+
return assert.EqualValues(t, expected, GetCount(t, bean))
136136
}
137137

138138
// AssertInt64InRange assert value is in range [low, high]
@@ -150,7 +150,7 @@ func GetCountByCond(t assert.TestingT, tableName string, cond builder.Cond) int6
150150
}
151151

152152
// AssertCountByCond test the count of database entries matching bean
153-
func AssertCountByCond(t assert.TestingT, tableName string, cond builder.Cond, expected int) {
154-
assert.EqualValues(t, expected, GetCountByCond(t, tableName, cond),
153+
func AssertCountByCond(t assert.TestingT, tableName string, cond builder.Cond, expected int) bool {
154+
return assert.EqualValues(t, expected, GetCountByCond(t, tableName, cond),
155155
"Failed consistency test, the counted bean (of table %s) was %+v", tableName, cond)
156156
}

tests/integration/api_pull_review_test.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,14 @@ import (
1313
issues_model "code.gitea.io/gitea/models/issues"
1414
repo_model "code.gitea.io/gitea/models/repo"
1515
"code.gitea.io/gitea/models/unittest"
16+
user_model "code.gitea.io/gitea/models/user"
1617
"code.gitea.io/gitea/modules/json"
1718
api "code.gitea.io/gitea/modules/structs"
19+
issue_service "code.gitea.io/gitea/services/issue"
1820
"code.gitea.io/gitea/tests"
1921

2022
"github.com/stretchr/testify/assert"
23+
"xorm.io/builder"
2124
)
2225

2326
func TestAPIPullReview(t *testing.T) {
@@ -305,3 +308,126 @@ func TestAPIPullReviewRequest(t *testing.T) {
305308
req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers?token=%s", repo3.OwnerName, repo3.Name, pullIssue12.Index, token), &api.PullReviewRequestOptions{})
306309
MakeRequest(t, req, http.StatusNoContent)
307310
}
311+
312+
func TestAPIPullReviewStayDismissed(t *testing.T) {
313+
// This test against issue https://github.com/go-gitea/gitea/issues/28542
314+
// where old reviews surface after a review request got dismissed.
315+
defer tests.PrepareTestEnv(t)()
316+
pullIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3})
317+
assert.NoError(t, pullIssue.LoadAttributes(db.DefaultContext))
318+
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue.RepoID})
319+
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
320+
session2 := loginUser(t, user2.LoginName)
321+
token2 := getTokenForLoggedInUser(t, session2, auth_model.AccessTokenScopeWriteRepository)
322+
user8 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 8})
323+
session8 := loginUser(t, user8.LoginName)
324+
token8 := getTokenForLoggedInUser(t, session8, auth_model.AccessTokenScopeWriteRepository)
325+
326+
// user2 request user8
327+
req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{
328+
Reviewers: []string{user8.LoginName},
329+
}).AddTokenAuth(token2)
330+
MakeRequest(t, req, http.StatusCreated)
331+
332+
reviewsCountCheck(t,
333+
"check we have only one review request",
334+
pullIssue.ID, user8.ID, 0, 1, 1, false)
335+
336+
// user2 request user8 again, it is expected to be ignored
337+
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{
338+
Reviewers: []string{user8.LoginName},
339+
}).AddTokenAuth(token2)
340+
MakeRequest(t, req, http.StatusCreated)
341+
342+
reviewsCountCheck(t,
343+
"check we have only one review request, even after re-request it again",
344+
pullIssue.ID, user8.ID, 0, 1, 1, false)
345+
346+
// user8 reviews it as accept
347+
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{
348+
Event: "APPROVED",
349+
Body: "lgtm",
350+
}).AddTokenAuth(token8)
351+
MakeRequest(t, req, http.StatusOK)
352+
353+
reviewsCountCheck(t,
354+
"check we have one valid approval",
355+
pullIssue.ID, user8.ID, 0, 0, 1, true)
356+
357+
// emulate of auto-dismiss lgtm on a protected branch that where a pull just got an update
358+
_, err := db.GetEngine(db.DefaultContext).Where("issue_id = ? AND reviewer_id = ?", pullIssue.ID, user8.ID).
359+
Cols("dismissed").Update(&issues_model.Review{Dismissed: true})
360+
assert.NoError(t, err)
361+
362+
// user2 request user8 again
363+
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{
364+
Reviewers: []string{user8.LoginName},
365+
}).AddTokenAuth(token2)
366+
MakeRequest(t, req, http.StatusCreated)
367+
368+
reviewsCountCheck(t,
369+
"check we have no valid approval and one review request",
370+
pullIssue.ID, user8.ID, 1, 1, 2, false)
371+
372+
// user8 dismiss review
373+
_, err = issue_service.ReviewRequest(db.DefaultContext, pullIssue, user8, user8, false)
374+
assert.NoError(t, err)
375+
376+
reviewsCountCheck(t,
377+
"check new review request is now dismissed",
378+
pullIssue.ID, user8.ID, 1, 0, 1, false)
379+
380+
// add a new valid approval
381+
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{
382+
Event: "APPROVED",
383+
Body: "lgtm",
384+
}).AddTokenAuth(token8)
385+
MakeRequest(t, req, http.StatusOK)
386+
387+
reviewsCountCheck(t,
388+
"check that old reviews requests are deleted",
389+
pullIssue.ID, user8.ID, 1, 0, 2, true)
390+
391+
// now add a change request witch should dismiss the approval
392+
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{
393+
Event: "REQUEST_CHANGES",
394+
Body: "please change XYZ",
395+
}).AddTokenAuth(token8)
396+
MakeRequest(t, req, http.StatusOK)
397+
398+
reviewsCountCheck(t,
399+
"check that old reviews are dismissed",
400+
pullIssue.ID, user8.ID, 2, 0, 3, false)
401+
}
402+
403+
func reviewsCountCheck(t *testing.T, name string, issueID, reviewerID int64, expectedDismissed, expectedRequested, expectedTotal int, expectApproval bool) {
404+
t.Run(name, func(t *testing.T) {
405+
unittest.AssertCountByCond(t, "review", builder.Eq{
406+
"issue_id": issueID,
407+
"reviewer_id": reviewerID,
408+
"dismissed": true,
409+
}, expectedDismissed)
410+
411+
unittest.AssertCountByCond(t, "review", builder.Eq{
412+
"issue_id": issueID,
413+
"reviewer_id": reviewerID,
414+
}, expectedTotal)
415+
416+
unittest.AssertCountByCond(t, "review", builder.Eq{
417+
"issue_id": issueID,
418+
"reviewer_id": reviewerID,
419+
"type": issues_model.ReviewTypeRequest,
420+
}, expectedRequested)
421+
422+
approvalCount := 0
423+
if expectApproval {
424+
approvalCount = 1
425+
}
426+
unittest.AssertCountByCond(t, "review", builder.Eq{
427+
"issue_id": issueID,
428+
"reviewer_id": reviewerID,
429+
"type": issues_model.ReviewTypeApprove,
430+
"dismissed": false,
431+
}, approvalCount)
432+
})
433+
}

0 commit comments

Comments
 (0)