初始提交: Gitea 项目代码
This commit is contained in:
@@ -0,0 +1,611 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
auth_model "gitea.dev/models/auth"
|
||||
"gitea.dev/models/db"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
access_model "gitea.dev/models/perm/access"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/models/unittest"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/gitrepo"
|
||||
"gitea.dev/modules/json"
|
||||
api "gitea.dev/modules/structs"
|
||||
issue_service "gitea.dev/services/issue"
|
||||
pull_service "gitea.dev/services/pull"
|
||||
"gitea.dev/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
func TestAPIPullReview(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
t.Run("General", testAPIPullReviewGeneral)
|
||||
t.Run("CommentReply", testAPIPullReviewCommentReply)
|
||||
}
|
||||
|
||||
func testAPIPullReviewGeneral(t *testing.T) {
|
||||
pullIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3})
|
||||
assert.NoError(t, pullIssue.LoadAttributes(t.Context()))
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue.RepoID})
|
||||
|
||||
// test ListPullReviews
|
||||
session := loginUser(t, "user2")
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
req := NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index).
|
||||
AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
reviews := DecodeJSON(t, resp, []*api.PullReview{})
|
||||
require.Len(t, reviews, 8)
|
||||
|
||||
for _, r := range reviews {
|
||||
assert.Equal(t, pullIssue.HTMLURL(t.Context()), r.HTMLPullURL)
|
||||
}
|
||||
assert.EqualValues(t, 8, reviews[3].ID)
|
||||
assert.EqualValues(t, "APPROVED", reviews[3].State)
|
||||
assert.Equal(t, 0, reviews[3].CodeCommentsCount)
|
||||
assert.True(t, reviews[3].Stale)
|
||||
assert.False(t, reviews[3].Official)
|
||||
|
||||
assert.EqualValues(t, 10, reviews[5].ID)
|
||||
assert.EqualValues(t, "REQUEST_CHANGES", reviews[5].State)
|
||||
assert.Equal(t, 1, reviews[5].CodeCommentsCount)
|
||||
assert.EqualValues(t, -1, reviews[5].Reviewer.ID) // ghost user
|
||||
assert.False(t, reviews[5].Stale)
|
||||
assert.True(t, reviews[5].Official)
|
||||
|
||||
// test GetPullReview
|
||||
req = NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/reviews/%d", repo.OwnerName, repo.Name, pullIssue.Index, reviews[3].ID).
|
||||
AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
review := DecodeJSON(t, resp, &api.PullReview{})
|
||||
assert.Equal(t, reviews[3], review)
|
||||
|
||||
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/pulls/%d/reviews/%d", repo.OwnerName, repo.Name, pullIssue.Index, reviews[5].ID).
|
||||
AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
review = DecodeJSON(t, resp, &api.PullReview{})
|
||||
assert.Equal(t, reviews[5], review)
|
||||
|
||||
// test GetPullReviewComments
|
||||
comment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 7})
|
||||
req = NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/reviews/%d/comments", repo.OwnerName, repo.Name, pullIssue.Index, 10).
|
||||
AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
reviewComments := DecodeJSON(t, resp, []*api.PullReviewComment{})
|
||||
assert.Len(t, reviewComments, 1)
|
||||
assert.Equal(t, "Ghost", reviewComments[0].Poster.UserName)
|
||||
assert.Equal(t, "a review from a deleted user", reviewComments[0].Body)
|
||||
assert.Equal(t, comment.ID, reviewComments[0].ID)
|
||||
assert.EqualValues(t, comment.UpdatedUnix, reviewComments[0].Updated.Unix())
|
||||
assert.Equal(t, comment.HTMLURL(t.Context()), reviewComments[0].HTMLURL)
|
||||
|
||||
// test CreatePullReview
|
||||
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{
|
||||
Body: "body1",
|
||||
// Event: "" # will result in PENDING
|
||||
Comments: []api.CreatePullReviewComment{
|
||||
{
|
||||
Path: "README.md",
|
||||
Body: "first new line",
|
||||
OldLineNum: 0,
|
||||
NewLineNum: 1,
|
||||
}, {
|
||||
Path: "README.md",
|
||||
Body: "first old line",
|
||||
OldLineNum: 1,
|
||||
NewLineNum: 0,
|
||||
}, {
|
||||
Path: "iso-8859-1.txt",
|
||||
Body: "this line contains a non-utf-8 character",
|
||||
OldLineNum: 0,
|
||||
NewLineNum: 1,
|
||||
},
|
||||
},
|
||||
}).AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
review = DecodeJSON(t, resp, &api.PullReview{})
|
||||
assert.EqualValues(t, 6, review.ID)
|
||||
assert.EqualValues(t, "PENDING", review.State)
|
||||
assert.Equal(t, 3, review.CodeCommentsCount)
|
||||
|
||||
// test SubmitPullReview
|
||||
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{
|
||||
Event: "APPROVED",
|
||||
Body: "just two nits",
|
||||
}).AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
review = DecodeJSON(t, resp, &api.PullReview{})
|
||||
assert.EqualValues(t, 6, review.ID)
|
||||
assert.EqualValues(t, "APPROVED", review.State)
|
||||
assert.Equal(t, 3, review.CodeCommentsCount)
|
||||
|
||||
// test dismiss review
|
||||
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{
|
||||
Message: "test",
|
||||
}).AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
review = DecodeJSON(t, resp, &api.PullReview{})
|
||||
assert.EqualValues(t, 6, review.ID)
|
||||
assert.True(t, review.Dismissed)
|
||||
|
||||
// test dismiss review
|
||||
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)).
|
||||
AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
review = DecodeJSON(t, resp, &api.PullReview{})
|
||||
assert.EqualValues(t, 6, review.ID)
|
||||
assert.False(t, review.Dismissed)
|
||||
|
||||
// test DeletePullReview
|
||||
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{
|
||||
Body: "just a comment",
|
||||
Event: "COMMENT",
|
||||
}).AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
review = DecodeJSON(t, resp, &api.PullReview{})
|
||||
assert.EqualValues(t, "COMMENT", review.State)
|
||||
assert.Equal(t, 0, review.CodeCommentsCount)
|
||||
req = NewRequestf(t, http.MethodDelete, "/api/v1/repos/%s/%s/pulls/%d/reviews/%d", repo.OwnerName, repo.Name, pullIssue.Index, review.ID).
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
// test CreatePullReview Comment without body but with comments
|
||||
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{
|
||||
// Body: "",
|
||||
Event: "COMMENT",
|
||||
Comments: []api.CreatePullReviewComment{
|
||||
{
|
||||
Path: "README.md",
|
||||
Body: "first new line",
|
||||
OldLineNum: 0,
|
||||
NewLineNum: 1,
|
||||
}, {
|
||||
Path: "README.md",
|
||||
Body: "first old line",
|
||||
OldLineNum: 1,
|
||||
NewLineNum: 0,
|
||||
},
|
||||
},
|
||||
}).AddTokenAuth(token)
|
||||
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
commentReview := DecodeJSON(t, resp, &api.PullReview{})
|
||||
assert.EqualValues(t, "COMMENT", commentReview.State)
|
||||
assert.Equal(t, 2, commentReview.CodeCommentsCount)
|
||||
assert.Empty(t, commentReview.Body)
|
||||
assert.False(t, commentReview.Dismissed)
|
||||
|
||||
// test CreatePullReview Comment with body but without comments
|
||||
commentBody := "This is a body of the comment."
|
||||
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{
|
||||
Body: commentBody,
|
||||
Event: "COMMENT",
|
||||
Comments: []api.CreatePullReviewComment{},
|
||||
}).AddTokenAuth(token)
|
||||
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
commentReview = DecodeJSON(t, resp, &api.PullReview{})
|
||||
assert.EqualValues(t, "COMMENT", commentReview.State)
|
||||
assert.Equal(t, 0, commentReview.CodeCommentsCount)
|
||||
assert.Equal(t, commentBody, commentReview.Body)
|
||||
assert.False(t, commentReview.Dismissed)
|
||||
|
||||
// test CreatePullReview Comment without body and no comments
|
||||
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{
|
||||
Body: "",
|
||||
Event: "COMMENT",
|
||||
Comments: []api.CreatePullReviewComment{},
|
||||
}).AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusUnprocessableEntity)
|
||||
errMap := make(map[string]any)
|
||||
json.Unmarshal(resp.Body.Bytes(), &errMap)
|
||||
assert.Equal(t, "review event COMMENT requires a body or a comment", errMap["message"].(string))
|
||||
|
||||
// test get review requests
|
||||
// to make it simple, use same api with get review
|
||||
pullIssue12 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 12})
|
||||
assert.NoError(t, pullIssue12.LoadAttributes(t.Context()))
|
||||
repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue12.RepoID})
|
||||
|
||||
req = NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/reviews", repo3.OwnerName, repo3.Name, pullIssue12.Index).
|
||||
AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
reviews = DecodeJSON(t, resp, []*api.PullReview{})
|
||||
assert.EqualValues(t, 11, reviews[0].ID)
|
||||
assert.EqualValues(t, "REQUEST_REVIEW", reviews[0].State)
|
||||
assert.Equal(t, 0, reviews[0].CodeCommentsCount)
|
||||
assert.False(t, reviews[0].Stale)
|
||||
assert.True(t, reviews[0].Official)
|
||||
assert.Equal(t, "test_team", reviews[0].ReviewerTeam.Name)
|
||||
|
||||
assert.EqualValues(t, 12, reviews[1].ID)
|
||||
assert.EqualValues(t, "REQUEST_REVIEW", reviews[1].State)
|
||||
assert.Equal(t, 0, reviews[0].CodeCommentsCount)
|
||||
assert.False(t, reviews[1].Stale)
|
||||
assert.True(t, reviews[1].Official)
|
||||
assert.EqualValues(t, 1, reviews[1].Reviewer.ID)
|
||||
}
|
||||
|
||||
func TestAPIPullReviewRequest(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
pullIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3})
|
||||
assert.NoError(t, pullIssue.LoadAttributes(t.Context()))
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue.RepoID})
|
||||
|
||||
// Test add Review Request
|
||||
session := loginUser(t, "user2")
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{
|
||||
Reviewers: []string{"user4@example.com", "user8"},
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
// poster of pr can't be reviewer
|
||||
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{
|
||||
Reviewers: []string{"user1"},
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusUnprocessableEntity)
|
||||
|
||||
// test user not exist
|
||||
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{
|
||||
Reviewers: []string{"testOther"},
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
// Test Remove Review Request
|
||||
session2 := loginUser(t, "user4")
|
||||
token2 := getTokenForLoggedInUser(t, session2, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{
|
||||
Reviewers: []string{"user4"},
|
||||
}).AddTokenAuth(token2)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
// doer is not admin
|
||||
req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{
|
||||
Reviewers: []string{"user8"},
|
||||
}).AddTokenAuth(token2)
|
||||
MakeRequest(t, req, http.StatusUnprocessableEntity)
|
||||
|
||||
req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{
|
||||
Reviewers: []string{"user8"},
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
// a collaborator can add/remove a review request
|
||||
pullIssue21 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 21})
|
||||
assert.NoError(t, pullIssue21.LoadAttributes(t.Context()))
|
||||
pull21Repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue21.RepoID}) // repo60
|
||||
user38Session := loginUser(t, "user38")
|
||||
user38Token := getTokenForLoggedInUser(t, user38Session, auth_model.AccessTokenScopeWriteRepository)
|
||||
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", pull21Repo.OwnerName, pull21Repo.Name, pullIssue21.Index), &api.PullReviewRequestOptions{
|
||||
Reviewers: []string{"user4@example.com"},
|
||||
}).AddTokenAuth(user38Token)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", pull21Repo.OwnerName, pull21Repo.Name, pullIssue21.Index), &api.PullReviewRequestOptions{
|
||||
Reviewers: []string{"user4@example.com"},
|
||||
}).AddTokenAuth(user38Token)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
// the poster of the PR can add/remove a review request
|
||||
user39Session := loginUser(t, "user39")
|
||||
user39Token := getTokenForLoggedInUser(t, user39Session, auth_model.AccessTokenScopeWriteRepository)
|
||||
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", pull21Repo.OwnerName, pull21Repo.Name, pullIssue21.Index), &api.PullReviewRequestOptions{
|
||||
Reviewers: []string{"user8"},
|
||||
}).AddTokenAuth(user39Token)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", pull21Repo.OwnerName, pull21Repo.Name, pullIssue21.Index), &api.PullReviewRequestOptions{
|
||||
Reviewers: []string{"user8"},
|
||||
}).AddTokenAuth(user39Token)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
// user with read permission on pull requests unit can add/remove a review request
|
||||
pullIssue22 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 22})
|
||||
assert.NoError(t, pullIssue22.LoadAttributes(t.Context()))
|
||||
pull22Repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue22.RepoID}) // repo61
|
||||
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", pull22Repo.OwnerName, pull22Repo.Name, pullIssue22.Index), &api.PullReviewRequestOptions{
|
||||
Reviewers: []string{"user38"},
|
||||
}).AddTokenAuth(user39Token) // user39 is from a team with read permission on pull requests unit
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", pull22Repo.OwnerName, pull22Repo.Name, pullIssue22.Index), &api.PullReviewRequestOptions{
|
||||
Reviewers: []string{"user38"},
|
||||
}).AddTokenAuth(user39Token) // user39 is from a team with read permission on pull requests unit
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
// Test team review request
|
||||
pullIssue12 := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 12})
|
||||
assert.NoError(t, pullIssue12.LoadAttributes(t.Context()))
|
||||
repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue12.RepoID})
|
||||
|
||||
// Test add Team Review Request
|
||||
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{
|
||||
TeamReviewers: []string{"team1", "owners"},
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
// Test add Team Review Request to not allowned
|
||||
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{
|
||||
TeamReviewers: []string{"test_team"},
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusUnprocessableEntity)
|
||||
|
||||
// Test add Team Review Request to not exist
|
||||
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{
|
||||
TeamReviewers: []string{"not_exist_team"},
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
// Test Remove team Review Request
|
||||
req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{
|
||||
TeamReviewers: []string{"team1"},
|
||||
}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
// empty request test
|
||||
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{}).
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
req = NewRequestWithJSON(t, http.MethodDelete, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo3.OwnerName, repo3.Name, pullIssue12.Index), &api.PullReviewRequestOptions{}).
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
}
|
||||
|
||||
func TestAPIPullReviewCommentResolveEndpoints(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
ctx := t.Context()
|
||||
pullIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3})
|
||||
require.NoError(t, pullIssue.LoadAttributes(ctx))
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue.RepoID})
|
||||
|
||||
doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: pullIssue.PosterID})
|
||||
require.NoError(t, pullIssue.LoadPullRequest(ctx))
|
||||
gitRepo, err := gitrepo.OpenRepository(ctx, repo)
|
||||
require.NoError(t, err)
|
||||
defer gitRepo.Close()
|
||||
|
||||
latestCommitID, err := gitRepo.GetRefCommitID(pullIssue.PullRequest.GetGitHeadRefName())
|
||||
require.NoError(t, err)
|
||||
|
||||
codeComment, err := pull_service.CreateCodeComment(ctx, doer, gitRepo, pullIssue, 1, "resolve comment", "README.md", false, 0, latestCommitID, nil)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, codeComment)
|
||||
|
||||
session := loginUser(t, doer.Name)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
resolveURL := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/comments/%d/resolve", repo.OwnerName, repo.Name, codeComment.ID)
|
||||
unresolveURL := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/comments/%d/unresolve", repo.OwnerName, repo.Name, codeComment.ID)
|
||||
|
||||
req := NewRequest(t, http.MethodPost, resolveURL).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
// Verify comment is resolved
|
||||
updatedComment, err := issues_model.GetCommentByID(ctx, codeComment.ID)
|
||||
require.NoError(t, err)
|
||||
assert.NotZero(t, updatedComment.ResolveDoerID)
|
||||
assert.Equal(t, doer.ID, updatedComment.ResolveDoerID)
|
||||
|
||||
// Resolving again should be idempotent
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
req = NewRequest(t, http.MethodPost, unresolveURL).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
// Verify comment is unresolved
|
||||
updatedComment, err = issues_model.GetCommentByID(ctx, codeComment.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Zero(t, updatedComment.ResolveDoerID)
|
||||
|
||||
// Unresolving again should be idempotent
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
|
||||
// Non-existing comment ID
|
||||
req = NewRequest(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/comments/999999/resolve", repo.OwnerName, repo.Name)).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
// Non-code-comment
|
||||
plainComment, err := issue_service.CreateIssueComment(ctx, doer, repo, pullIssue, "not a review comment", nil)
|
||||
require.NoError(t, err)
|
||||
req = NewRequest(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/comments/%d/resolve", repo.OwnerName, repo.Name, plainComment.ID)).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusBadRequest)
|
||||
|
||||
// Test permission check: use a user without write access for target repo to test 403 response
|
||||
unauthorizedUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
|
||||
require.NotEqual(t, pullIssue.PosterID, unauthorizedUser.ID)
|
||||
|
||||
unauthorizedSession := loginUser(t, unauthorizedUser.Name)
|
||||
unauthorizedToken := getTokenForLoggedInUser(t, unauthorizedSession, auth_model.AccessTokenScopeWriteIssue, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
req = NewRequest(t, http.MethodGet, fmt.Sprintf("/api/v1/repos/%s/%s/issues/comments/%d", repo.OwnerName, repo.Name, plainComment.ID)).AddTokenAuth(unauthorizedToken)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
req = NewRequest(t, http.MethodPost, resolveURL).AddTokenAuth(unauthorizedToken)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
}
|
||||
|
||||
func TestAPIPullReviewStayDismissed(t *testing.T) {
|
||||
// This test against issue https://github.com/go-gitea/gitea/issues/28542
|
||||
// where old reviews surface after a review request got dismissed.
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
pullIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3})
|
||||
assert.NoError(t, pullIssue.LoadAttributes(t.Context()))
|
||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: pullIssue.RepoID})
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
session2 := loginUser(t, user2.LoginName)
|
||||
token2 := getTokenForLoggedInUser(t, session2, auth_model.AccessTokenScopeWriteRepository)
|
||||
user8 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 8})
|
||||
session8 := loginUser(t, user8.LoginName)
|
||||
token8 := getTokenForLoggedInUser(t, session8, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
// user2 request user8
|
||||
req := NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{
|
||||
Reviewers: []string{user8.LoginName},
|
||||
}).AddTokenAuth(token2)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
reviewsCountCheck(t,
|
||||
"check we have only one review request",
|
||||
pullIssue.ID, user8.ID, 0, 1, 1, false)
|
||||
|
||||
// user2 request user8 again, it is expected to be ignored
|
||||
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{
|
||||
Reviewers: []string{user8.LoginName},
|
||||
}).AddTokenAuth(token2)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
reviewsCountCheck(t,
|
||||
"check we have only one review request, even after re-request it again",
|
||||
pullIssue.ID, user8.ID, 0, 1, 1, false)
|
||||
|
||||
// user8 reviews it as accept
|
||||
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{
|
||||
Event: "APPROVED",
|
||||
Body: "lgtm",
|
||||
}).AddTokenAuth(token8)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
reviewsCountCheck(t,
|
||||
"check we have one valid approval",
|
||||
pullIssue.ID, user8.ID, 0, 0, 1, true)
|
||||
|
||||
// emulate of auto-dismiss lgtm on a protected branch that where a pull just got an update
|
||||
_, err := db.GetEngine(t.Context()).Where("issue_id = ? AND reviewer_id = ?", pullIssue.ID, user8.ID).
|
||||
Cols("dismissed").Update(&issues_model.Review{Dismissed: true})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// user2 request user8 again
|
||||
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/requested_reviewers", repo.OwnerName, repo.Name, pullIssue.Index), &api.PullReviewRequestOptions{
|
||||
Reviewers: []string{user8.LoginName},
|
||||
}).AddTokenAuth(token2)
|
||||
MakeRequest(t, req, http.StatusCreated)
|
||||
|
||||
reviewsCountCheck(t,
|
||||
"check we have no valid approval and one review request",
|
||||
pullIssue.ID, user8.ID, 1, 1, 2, false)
|
||||
|
||||
// user8 dismiss review
|
||||
permUser8, err := access_model.GetIndividualUserRepoPermission(t.Context(), pullIssue.Repo, user8)
|
||||
assert.NoError(t, err)
|
||||
_, err = issue_service.ReviewRequest(t.Context(), pullIssue, user8, &permUser8, user8, false)
|
||||
assert.NoError(t, err)
|
||||
|
||||
reviewsCountCheck(t,
|
||||
"check new review request is now dismissed",
|
||||
pullIssue.ID, user8.ID, 1, 0, 1, false)
|
||||
|
||||
// add a new valid approval
|
||||
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{
|
||||
Event: "APPROVED",
|
||||
Body: "lgtm",
|
||||
}).AddTokenAuth(token8)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
reviewsCountCheck(t,
|
||||
"check that old reviews requests are deleted",
|
||||
pullIssue.ID, user8.ID, 1, 0, 2, true)
|
||||
|
||||
// now add a change request witch should dismiss the approval
|
||||
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", repo.OwnerName, repo.Name, pullIssue.Index), &api.CreatePullReviewOptions{
|
||||
Event: "REQUEST_CHANGES",
|
||||
Body: "please change XYZ",
|
||||
}).AddTokenAuth(token8)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
reviewsCountCheck(t,
|
||||
"check that old reviews are dismissed",
|
||||
pullIssue.ID, user8.ID, 2, 0, 3, false)
|
||||
}
|
||||
|
||||
func testAPIPullReviewCommentReply(t *testing.T) {
|
||||
pullIssue := unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{ID: 3})
|
||||
require.NoError(t, pullIssue.LoadRepo(t.Context()))
|
||||
require.NoError(t, pullIssue.LoadPullRequest(t.Context()))
|
||||
doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
gitRepo, err := gitrepo.OpenRepository(t.Context(), pullIssue.Repo)
|
||||
require.NoError(t, err)
|
||||
defer gitRepo.Close()
|
||||
|
||||
commitID, err := gitRepo.GetRefCommitID(pullIssue.PullRequest.GetGitHeadRefName())
|
||||
require.NoError(t, err)
|
||||
|
||||
parent, err := pull_service.CreateCodeComment(t.Context(), doer, gitRepo, pullIssue, 1, "parent comment", "README.md", false, 0, commitID, nil)
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, parent.ReviewID)
|
||||
|
||||
repo := pullIssue.Repo
|
||||
session := loginUser(t, doer.Name)
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
|
||||
url := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/comments/%d/replies", repo.OwnerName, repo.Name, pullIssue.Index, parent.ID)
|
||||
|
||||
// happy path
|
||||
req := NewRequestWithJSON(t, http.MethodPost, url, &api.CreatePullReviewCommentReplyOptions{Body: "the reply"}).AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusCreated)
|
||||
reply := DecodeJSON(t, resp, &api.PullReviewComment{})
|
||||
assert.Equal(t, "the reply", reply.Body)
|
||||
assert.Equal(t, parent.ReviewID, reply.ReviewID)
|
||||
assert.Equal(t, "README.md", reply.Path)
|
||||
|
||||
// empty body — caught by binding
|
||||
req = NewRequestWithJSON(t, http.MethodPost, url, &api.CreatePullReviewCommentReplyOptions{}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusUnprocessableEntity)
|
||||
|
||||
// reply to a non-existent comment
|
||||
bad := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/comments/%d/replies", repo.OwnerName, repo.Name, pullIssue.Index, 999999)
|
||||
req = NewRequestWithJSON(t, http.MethodPost, bad, &api.CreatePullReviewCommentReplyOptions{Body: "x"}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
|
||||
// reply to a code comment that belongs to a different PR — 404
|
||||
otherCodeComment := unittest.AssertExistsAndLoadBean(t, &issues_model.Comment{ID: 4, Type: issues_model.CommentTypeCode})
|
||||
require.NotEqual(t, pullIssue.ID, otherCodeComment.IssueID)
|
||||
wrongPR := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/comments/%d/replies", repo.OwnerName, repo.Name, pullIssue.Index, otherCodeComment.ID)
|
||||
req = NewRequestWithJSON(t, http.MethodPost, wrongPR, &api.CreatePullReviewCommentReplyOptions{Body: "x"}).AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
}
|
||||
|
||||
func reviewsCountCheck(t *testing.T, name string, issueID, reviewerID int64, expectedDismissed, expectedRequested, expectedTotal int, expectApproval bool) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
unittest.AssertCountByCond(t, "review", builder.Eq{
|
||||
"issue_id": issueID,
|
||||
"reviewer_id": reviewerID,
|
||||
"dismissed": true,
|
||||
}, expectedDismissed)
|
||||
|
||||
unittest.AssertCountByCond(t, "review", builder.Eq{
|
||||
"issue_id": issueID,
|
||||
"reviewer_id": reviewerID,
|
||||
}, expectedTotal)
|
||||
|
||||
unittest.AssertCountByCond(t, "review", builder.Eq{
|
||||
"issue_id": issueID,
|
||||
"reviewer_id": reviewerID,
|
||||
"type": issues_model.ReviewTypeRequest,
|
||||
}, expectedRequested)
|
||||
|
||||
approvalCount := 0
|
||||
if expectApproval {
|
||||
approvalCount = 1
|
||||
}
|
||||
unittest.AssertCountByCond(t, "review", builder.Eq{
|
||||
"issue_id": issueID,
|
||||
"reviewer_id": reviewerID,
|
||||
"type": issues_model.ReviewTypeApprove,
|
||||
"dismissed": false,
|
||||
}, approvalCount)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user