mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-18 19:11:06 +00:00
1. use MockVariableValue as much as possible 2. use wg.Go as much as possible instead of Add/Done 3. simplify global lock's DefaultLocker logic to make it easier to test 4. introduce a general approach for getting external service config in CI 5. remove unclear & unnecessary "t.Skip" 6. use modern generic syntax for remaining "DecodeJSON" calls 7. clarify test result for "list gitignore templates" and "list licenses"
612 lines
28 KiB
Go
612 lines
28 KiB
Go
// Copyright 2020 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package integration
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"testing"
|
|
|
|
auth_model "code.gitea.io/gitea/models/auth"
|
|
"code.gitea.io/gitea/models/db"
|
|
issues_model "code.gitea.io/gitea/models/issues"
|
|
access_model "code.gitea.io/gitea/models/perm/access"
|
|
repo_model "code.gitea.io/gitea/models/repo"
|
|
"code.gitea.io/gitea/models/unittest"
|
|
user_model "code.gitea.io/gitea/models/user"
|
|
"code.gitea.io/gitea/modules/gitrepo"
|
|
"code.gitea.io/gitea/modules/json"
|
|
api "code.gitea.io/gitea/modules/structs"
|
|
issue_service "code.gitea.io/gitea/services/issue"
|
|
pull_service "code.gitea.io/gitea/services/pull"
|
|
"code.gitea.io/gitea/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)
|
|
})
|
|
}
|