code.gitea.io/gitea@v1.22.3/tests/integration/api_pull_review_test.go (about)

     1  // Copyright 2020 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package integration
     5  
     6  import (
     7  	"fmt"
     8  	"net/http"
     9  	"testing"
    10  
    11  	auth_model "code.gitea.io/gitea/models/auth"
    12  	"code.gitea.io/gitea/models/db"
    13  	issues_model "code.gitea.io/gitea/models/issues"
    14  	repo_model "code.gitea.io/gitea/models/repo"
    15  	"code.gitea.io/gitea/models/unittest"
    16  	user_model "code.gitea.io/gitea/models/user"
    17  	"code.gitea.io/gitea/modules/json"
    18  	api "code.gitea.io/gitea/modules/structs"
    19  	issue_service "code.gitea.io/gitea/services/issue"
    20  	"code.gitea.io/gitea/tests"
    21  
    22  	"github.com/stretchr/testify/assert"
    23  	"xorm.io/builder"
    24  )
    25  
    26  func TestAPIPullReview(t *testing.T) {
    27  	defer tests.PrepareTestEnv(t)()
    28  	pullIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3})
    29  	assert.NoError(t, pullIssue.LoadAttributes(db.DefaultContext))
    30  	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue.RepoID})
    31  
    32  	// test ListPullReviews
    33  	session := loginUser(t, "user2")
    34  	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
    35  	req := NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index).
    36  		AddTokenAuth(token)
    37  	resp := MakeRequest(t, req, http.StatusOK)
    38  
    39  	var reviews []*api.PullReview
    40  	DecodeJSON(t, resp, &reviews)
    41  	if !assert.Len(t, reviews, 8) {
    42  		return
    43  	}
    44  	for _, r := range reviews {
    45  		assert.EqualValues(t, pullIssue.HTMLURL(), r.HTMLPullURL)
    46  	}
    47  	assert.EqualValues(t, 8, reviews[3].ID)
    48  	assert.EqualValues(t, "APPROVED", reviews[3].State)
    49  	assert.EqualValues(t, 0, reviews[3].CodeCommentsCount)
    50  	assert.True(t, reviews[3].Stale)
    51  	assert.False(t, reviews[3].Official)
    52  
    53  	assert.EqualValues(t, 10, reviews[5].ID)
    54  	assert.EqualValues(t, "REQUEST_CHANGES", reviews[5].State)
    55  	assert.EqualValues(t, 1, reviews[5].CodeCommentsCount)
    56  	assert.EqualValues(t, -1, reviews[5].Reviewer.ID) // ghost user
    57  	assert.False(t, reviews[5].Stale)
    58  	assert.True(t, reviews[5].Official)
    59  
    60  	// test GetPullReview
    61  	req = NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/reviews/%d", repo.OwnerName, repo.Name, pullIssue.Index, reviews[3].ID).
    62  		AddTokenAuth(token)
    63  	resp = MakeRequest(t, req, http.StatusOK)
    64  	var review api.PullReview
    65  	DecodeJSON(t, resp, &review)
    66  	assert.EqualValues(t, *reviews[3], review)
    67  
    68  	req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/pulls/%d/reviews/%d", repo.OwnerName, repo.Name, pullIssue.Index, reviews[5].ID).
    69  		AddTokenAuth(token)
    70  	resp = MakeRequest(t, req, http.StatusOK)
    71  	DecodeJSON(t, resp, &review)
    72  	assert.EqualValues(t, *reviews[5], review)
    73  
    74  	// test GetPullReviewComments
    75  	comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 7})
    76  	req = NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/reviews/%d/comments", repo.OwnerName, repo.Name, pullIssue.Index, 10).
    77  		AddTokenAuth(token)
    78  	resp = MakeRequest(t, req, http.StatusOK)
    79  	var reviewComments []*api.PullReviewComment
    80  	DecodeJSON(t, resp, &reviewComments)
    81  	assert.Len(t, reviewComments, 1)
    82  	assert.EqualValues(t, "Ghost", reviewComments[0].Poster.UserName)
    83  	assert.EqualValues(t, "a review from a deleted user", reviewComments[0].Body)
    84  	assert.EqualValues(t, comment.ID, reviewComments[0].ID)
    85  	assert.EqualValues(t, comment.UpdatedUnix, reviewComments[0].Updated.Unix())
    86  	assert.EqualValues(t, comment.HTMLURL(db.DefaultContext), reviewComments[0].HTMLURL)
    87  
    88  	// test CreatePullReview
    89  	req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{
    90  		Body: "body1",
    91  		// Event: "" # will result in PENDING
    92  		Comments: []api.CreatePullReviewComment{
    93  			{
    94  				Path:       "README.md",
    95  				Body:       "first new line",
    96  				OldLineNum: 0,
    97  				NewLineNum: 1,
    98  			}, {
    99  				Path:       "README.md",
   100  				Body:       "first old line",
   101  				OldLineNum: 1,
   102  				NewLineNum: 0,
   103  			}, {
   104  				Path:       "iso-8859-1.txt",
   105  				Body:       "this line contains a non-utf-8 character",
   106  				OldLineNum: 0,
   107  				NewLineNum: 1,
   108  			},
   109  		},
   110  	}).AddTokenAuth(token)
   111  	resp = MakeRequest(t, req, http.StatusOK)
   112  	DecodeJSON(t, resp, &review)
   113  	assert.EqualValues(t, 6, review.ID)
   114  	assert.EqualValues(t, "PENDING", review.State)
   115  	assert.EqualValues(t, 3, review.CodeCommentsCount)
   116  
   117  	// test SubmitPullReview
   118  	req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews/%d", repo.OwnerName, repo.Name, pullIssue.Index, review.ID), &api.SubmitPullReviewOptions{
   119  		Event: "APPROVED",
   120  		Body:  "just two nits",
   121  	}).AddTokenAuth(token)
   122  	resp = MakeRequest(t, req, http.StatusOK)
   123  	DecodeJSON(t, resp, &review)
   124  	assert.EqualValues(t, 6, review.ID)
   125  	assert.EqualValues(t, "APPROVED", review.State)
   126  	assert.EqualValues(t, 3, review.CodeCommentsCount)
   127  
   128  	// test dismiss review
   129  	req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews/%d/dismissals", repo.OwnerName, repo.Name, pullIssue.Index, review.ID), &api.DismissPullReviewOptions{
   130  		Message: "test",
   131  	}).AddTokenAuth(token)
   132  	resp = MakeRequest(t, req, http.StatusOK)
   133  	DecodeJSON(t, resp, &review)
   134  	assert.EqualValues(t, 6, review.ID)
   135  	assert.True(t, review.Dismissed)
   136  
   137  	// test dismiss review
   138  	req = NewRequest(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews/%d/undismissals", repo.OwnerName, repo.Name, pullIssue.Index, review.ID)).
   139  		AddTokenAuth(token)
   140  	resp = MakeRequest(t, req, http.StatusOK)
   141  	DecodeJSON(t, resp, &review)
   142  	assert.EqualValues(t, 6, review.ID)
   143  	assert.False(t, review.Dismissed)
   144  
   145  	// test DeletePullReview
   146  	req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{
   147  		Body:  "just a comment",
   148  		Event: "COMMENT",
   149  	}).AddTokenAuth(token)
   150  	resp = MakeRequest(t, req, http.StatusOK)
   151  	DecodeJSON(t, resp, &review)
   152  	assert.EqualValues(t, "COMMENT", review.State)
   153  	assert.EqualValues(t, 0, review.CodeCommentsCount)
   154  	req = NewRequestf(t, http.MethodDelete, "/api/v1/repos/%s/%s/pulls/%d/reviews/%d", repo.OwnerName, repo.Name, pullIssue.Index, review.ID).
   155  		AddTokenAuth(token)
   156  	MakeRequest(t, req, http.StatusNoContent)
   157  
   158  	// test CreatePullReview Comment without body but with comments
   159  	req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{
   160  		// Body:  "",
   161  		Event: "COMMENT",
   162  		Comments: []api.CreatePullReviewComment{
   163  			{
   164  				Path:       "README.md",
   165  				Body:       "first new line",
   166  				OldLineNum: 0,
   167  				NewLineNum: 1,
   168  			}, {
   169  				Path:       "README.md",
   170  				Body:       "first old line",
   171  				OldLineNum: 1,
   172  				NewLineNum: 0,
   173  			},
   174  		},
   175  	}).AddTokenAuth(token)
   176  	var commentReview api.PullReview
   177  
   178  	resp = MakeRequest(t, req, http.StatusOK)
   179  	DecodeJSON(t, resp, &commentReview)
   180  	assert.EqualValues(t, "COMMENT", commentReview.State)
   181  	assert.EqualValues(t, 2, commentReview.CodeCommentsCount)
   182  	assert.Empty(t, commentReview.Body)
   183  	assert.False(t, commentReview.Dismissed)
   184  
   185  	// test CreatePullReview Comment with body but without comments
   186  	commentBody := "This is a body of the comment."
   187  	req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{
   188  		Body:     commentBody,
   189  		Event:    "COMMENT",
   190  		Comments: []api.CreatePullReviewComment{},
   191  	}).AddTokenAuth(token)
   192  
   193  	resp = MakeRequest(t, req, http.StatusOK)
   194  	DecodeJSON(t, resp, &commentReview)
   195  	assert.EqualValues(t, "COMMENT", commentReview.State)
   196  	assert.EqualValues(t, 0, commentReview.CodeCommentsCount)
   197  	assert.EqualValues(t, commentBody, commentReview.Body)
   198  	assert.False(t, commentReview.Dismissed)
   199  
   200  	// test CreatePullReview Comment without body and no comments
   201  	req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{
   202  		Body:     "",
   203  		Event:    "COMMENT",
   204  		Comments: []api.CreatePullReviewComment{},
   205  	}).AddTokenAuth(token)
   206  	resp = MakeRequest(t, req, http.StatusUnprocessableEntity)
   207  	errMap := make(map[string]any)
   208  	json.Unmarshal(resp.Body.Bytes(), &errMap)
   209  	assert.EqualValues(t, "review event COMMENT requires a body or a comment", errMap["message"].(string))
   210  
   211  	// test get review requests
   212  	// to make it simple, use same api with get review
   213  	pullIssue12 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 12})
   214  	assert.NoError(t, pullIssue12.LoadAttributes(db.DefaultContext))
   215  	repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue12.RepoID})
   216  
   217  	req = NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/reviews", repo3.OwnerName, repo3.Name, pullIssue12.Index).
   218  		AddTokenAuth(token)
   219  	resp = MakeRequest(t, req, http.StatusOK)
   220  	DecodeJSON(t, resp, &reviews)
   221  	assert.EqualValues(t, 11, reviews[0].ID)
   222  	assert.EqualValues(t, "REQUEST_REVIEW", reviews[0].State)
   223  	assert.EqualValues(t, 0, reviews[0].CodeCommentsCount)
   224  	assert.False(t, reviews[0].Stale)
   225  	assert.True(t, reviews[0].Official)
   226  	assert.EqualValues(t, "test_team", reviews[0].ReviewerTeam.Name)
   227  
   228  	assert.EqualValues(t, 12, reviews[1].ID)
   229  	assert.EqualValues(t, "REQUEST_REVIEW", reviews[1].State)
   230  	assert.EqualValues(t, 0, reviews[0].CodeCommentsCount)
   231  	assert.False(t, reviews[1].Stale)
   232  	assert.True(t, reviews[1].Official)
   233  	assert.EqualValues(t, 1, reviews[1].Reviewer.ID)
   234  }
   235  
   236  func TestAPIPullReviewRequest(t *testing.T) {
   237  	defer tests.PrepareTestEnv(t)()
   238  	pullIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3})
   239  	assert.NoError(t, pullIssue.LoadAttributes(db.DefaultContext))
   240  	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue.RepoID})
   241  
   242  	// Test add Review Request
   243  	session := loginUser(t, "user2")
   244  	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
   245  	req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{
   246  		Reviewers: []string{"user4@example.com", "user8"},
   247  	}).AddTokenAuth(token)
   248  	MakeRequest(t, req, http.StatusCreated)
   249  
   250  	// poster of pr can't be reviewer
   251  	req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{
   252  		Reviewers: []string{"user1"},
   253  	}).AddTokenAuth(token)
   254  	MakeRequest(t, req, http.StatusUnprocessableEntity)
   255  
   256  	// test user not exist
   257  	req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{
   258  		Reviewers: []string{"testOther"},
   259  	}).AddTokenAuth(token)
   260  	MakeRequest(t, req, http.StatusNotFound)
   261  
   262  	// Test Remove Review Request
   263  	session2 := loginUser(t, "user4")
   264  	token2 := getTokenForLoggedInUser(t, session2, auth_model.AccessTokenScopeWriteRepository)
   265  
   266  	req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{
   267  		Reviewers: []string{"user4"},
   268  	}).AddTokenAuth(token2)
   269  	MakeRequest(t, req, http.StatusNoContent)
   270  
   271  	// doer is not admin
   272  	req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{
   273  		Reviewers: []string{"user8"},
   274  	}).AddTokenAuth(token2)
   275  	MakeRequest(t, req, http.StatusUnprocessableEntity)
   276  
   277  	req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{
   278  		Reviewers: []string{"user8"},
   279  	}).AddTokenAuth(token)
   280  	MakeRequest(t, req, http.StatusNoContent)
   281  
   282  	// a collaborator can add/remove a review request
   283  	pullIssue21 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 21})
   284  	assert.NoError(t, pullIssue21.LoadAttributes(db.DefaultContext))
   285  	pull21Repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue21.RepoID}) // repo60
   286  	user38Session := loginUser(t, "user38")
   287  	user38Token := getTokenForLoggedInUser(t, user38Session, auth_model.AccessTokenScopeWriteRepository)
   288  	req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", pull21Repo.OwnerName, pull21Repo.Name, pullIssue21.Index), &api.PullReviewRequestOptions{
   289  		Reviewers: []string{"user4@example.com"},
   290  	}).AddTokenAuth(user38Token)
   291  	MakeRequest(t, req, http.StatusCreated)
   292  
   293  	req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", pull21Repo.OwnerName, pull21Repo.Name, pullIssue21.Index), &api.PullReviewRequestOptions{
   294  		Reviewers: []string{"user4@example.com"},
   295  	}).AddTokenAuth(user38Token)
   296  	MakeRequest(t, req, http.StatusNoContent)
   297  
   298  	// the poster of the PR can add/remove a review request
   299  	user39Session := loginUser(t, "user39")
   300  	user39Token := getTokenForLoggedInUser(t, user39Session, auth_model.AccessTokenScopeWriteRepository)
   301  	req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", pull21Repo.OwnerName, pull21Repo.Name, pullIssue21.Index), &api.PullReviewRequestOptions{
   302  		Reviewers: []string{"user8"},
   303  	}).AddTokenAuth(user39Token)
   304  	MakeRequest(t, req, http.StatusCreated)
   305  
   306  	req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", pull21Repo.OwnerName, pull21Repo.Name, pullIssue21.Index), &api.PullReviewRequestOptions{
   307  		Reviewers: []string{"user8"},
   308  	}).AddTokenAuth(user39Token)
   309  	MakeRequest(t, req, http.StatusNoContent)
   310  
   311  	// user with read permission on pull requests unit can add/remove a review request
   312  	pullIssue22 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 22})
   313  	assert.NoError(t, pullIssue22.LoadAttributes(db.DefaultContext))
   314  	pull22Repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue22.RepoID}) // repo61
   315  	req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", pull22Repo.OwnerName, pull22Repo.Name, pullIssue22.Index), &api.PullReviewRequestOptions{
   316  		Reviewers: []string{"user38"},
   317  	}).AddTokenAuth(user39Token) // user39 is from a team with read permission on pull requests unit
   318  	MakeRequest(t, req, http.StatusCreated)
   319  
   320  	req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", pull22Repo.OwnerName, pull22Repo.Name, pullIssue22.Index), &api.PullReviewRequestOptions{
   321  		Reviewers: []string{"user38"},
   322  	}).AddTokenAuth(user39Token) // user39 is from a team with read permission on pull requests unit
   323  	MakeRequest(t, req, http.StatusNoContent)
   324  
   325  	// Test team review request
   326  	pullIssue12 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 12})
   327  	assert.NoError(t, pullIssue12.LoadAttributes(db.DefaultContext))
   328  	repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue12.RepoID})
   329  
   330  	// Test add Team Review Request
   331  	req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{
   332  		TeamReviewers: []string{"team1", "owners"},
   333  	}).AddTokenAuth(token)
   334  	MakeRequest(t, req, http.StatusCreated)
   335  
   336  	// Test add Team Review Request to not allowned
   337  	req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{
   338  		TeamReviewers: []string{"test_team"},
   339  	}).AddTokenAuth(token)
   340  	MakeRequest(t, req, http.StatusUnprocessableEntity)
   341  
   342  	// Test add Team Review Request to not exist
   343  	req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{
   344  		TeamReviewers: []string{"not_exist_team"},
   345  	}).AddTokenAuth(token)
   346  	MakeRequest(t, req, http.StatusNotFound)
   347  
   348  	// Test Remove team Review Request
   349  	req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{
   350  		TeamReviewers: []string{"team1"},
   351  	}).AddTokenAuth(token)
   352  	MakeRequest(t, req, http.StatusNoContent)
   353  
   354  	// empty request test
   355  	req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{}).
   356  		AddTokenAuth(token)
   357  	MakeRequest(t, req, http.StatusCreated)
   358  
   359  	req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{}).
   360  		AddTokenAuth(token)
   361  	MakeRequest(t, req, http.StatusNoContent)
   362  }
   363  
   364  func TestAPIPullReviewStayDismissed(t *testing.T) {
   365  	// This test against issue https://github.com/go-gitea/gitea/issues/28542
   366  	// where old reviews surface after a review request got dismissed.
   367  	defer tests.PrepareTestEnv(t)()
   368  	pullIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3})
   369  	assert.NoError(t, pullIssue.LoadAttributes(db.DefaultContext))
   370  	repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue.RepoID})
   371  	user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
   372  	session2 := loginUser(t, user2.LoginName)
   373  	token2 := getTokenForLoggedInUser(t, session2, auth_model.AccessTokenScopeWriteRepository)
   374  	user8 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 8})
   375  	session8 := loginUser(t, user8.LoginName)
   376  	token8 := getTokenForLoggedInUser(t, session8, auth_model.AccessTokenScopeWriteRepository)
   377  
   378  	// user2 request user8
   379  	req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{
   380  		Reviewers: []string{user8.LoginName},
   381  	}).AddTokenAuth(token2)
   382  	MakeRequest(t, req, http.StatusCreated)
   383  
   384  	reviewsCountCheck(t,
   385  		"check we have only one review request",
   386  		pullIssue.ID, user8.ID, 0, 1, 1, false)
   387  
   388  	// user2 request user8 again, it is expected to be ignored
   389  	req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{
   390  		Reviewers: []string{user8.LoginName},
   391  	}).AddTokenAuth(token2)
   392  	MakeRequest(t, req, http.StatusCreated)
   393  
   394  	reviewsCountCheck(t,
   395  		"check we have only one review request, even after re-request it again",
   396  		pullIssue.ID, user8.ID, 0, 1, 1, false)
   397  
   398  	// user8 reviews it as accept
   399  	req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{
   400  		Event: "APPROVED",
   401  		Body:  "lgtm",
   402  	}).AddTokenAuth(token8)
   403  	MakeRequest(t, req, http.StatusOK)
   404  
   405  	reviewsCountCheck(t,
   406  		"check we have one valid approval",
   407  		pullIssue.ID, user8.ID, 0, 0, 1, true)
   408  
   409  	// emulate of auto-dismiss lgtm on a protected branch that where a pull just got an update
   410  	_, err := db.GetEngine(db.DefaultContext).Where("issue_id = ? AND reviewer_id = ?", pullIssue.ID, user8.ID).
   411  		Cols("dismissed").Update(&issues_model.Review{Dismissed: true})
   412  	assert.NoError(t, err)
   413  
   414  	// user2 request user8 again
   415  	req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{
   416  		Reviewers: []string{user8.LoginName},
   417  	}).AddTokenAuth(token2)
   418  	MakeRequest(t, req, http.StatusCreated)
   419  
   420  	reviewsCountCheck(t,
   421  		"check we have no valid approval and one review request",
   422  		pullIssue.ID, user8.ID, 1, 1, 2, false)
   423  
   424  	// user8 dismiss review
   425  	_, err = issue_service.ReviewRequest(db.DefaultContext, pullIssue, user8, user8, false)
   426  	assert.NoError(t, err)
   427  
   428  	reviewsCountCheck(t,
   429  		"check new review request is now dismissed",
   430  		pullIssue.ID, user8.ID, 1, 0, 1, false)
   431  
   432  	// add a new valid approval
   433  	req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{
   434  		Event: "APPROVED",
   435  		Body:  "lgtm",
   436  	}).AddTokenAuth(token8)
   437  	MakeRequest(t, req, http.StatusOK)
   438  
   439  	reviewsCountCheck(t,
   440  		"check that old reviews requests are deleted",
   441  		pullIssue.ID, user8.ID, 1, 0, 2, true)
   442  
   443  	// now add a change request witch should dismiss the approval
   444  	req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{
   445  		Event: "REQUEST_CHANGES",
   446  		Body:  "please change XYZ",
   447  	}).AddTokenAuth(token8)
   448  	MakeRequest(t, req, http.StatusOK)
   449  
   450  	reviewsCountCheck(t,
   451  		"check that old reviews are dismissed",
   452  		pullIssue.ID, user8.ID, 2, 0, 3, false)
   453  }
   454  
   455  func reviewsCountCheck(t *testing.T, name string, issueID, reviewerID int64, expectedDismissed, expectedRequested, expectedTotal int, expectApproval bool) {
   456  	t.Run(name, func(t *testing.T) {
   457  		unittest.AssertCountByCond(t, "review", builder.Eq{
   458  			"issue_id":    issueID,
   459  			"reviewer_id": reviewerID,
   460  			"dismissed":   true,
   461  		}, expectedDismissed)
   462  
   463  		unittest.AssertCountByCond(t, "review", builder.Eq{
   464  			"issue_id":    issueID,
   465  			"reviewer_id": reviewerID,
   466  		}, expectedTotal)
   467  
   468  		unittest.AssertCountByCond(t, "review", builder.Eq{
   469  			"issue_id":    issueID,
   470  			"reviewer_id": reviewerID,
   471  			"type":        issues_model.ReviewTypeRequest,
   472  		}, expectedRequested)
   473  
   474  		approvalCount := 0
   475  		if expectApproval {
   476  			approvalCount = 1
   477  		}
   478  		unittest.AssertCountByCond(t, "review", builder.Eq{
   479  			"issue_id":    issueID,
   480  			"reviewer_id": reviewerID,
   481  			"type":        issues_model.ReviewTypeApprove,
   482  			"dismissed":   false,
   483  		}, approvalCount)
   484  	})
   485  }