Skip to content

Commit 00ce90a

Browse files
6543tbedavidsvantesson
authored and
Yohann Delafollye
committed
API: Add pull review endpoints (go-gitea#11224)
* API: Added pull review read only endpoints * Update Structs, move Conversion, Refactor * refactor * lint & co * fix lint + refactor * add new Review state, rm unessesary, refacotr loadAttributes, convert patch to diff * add DeletePullReview * add paggination * draft1: Create & submit review * fix lint * fix lint * impruve test * DONT use GhostUser for loadReviewer * expose comments_count of a PullReview * infent GetCodeCommentsCount() * fixes * fix+impruve * some nits * Handle Ghosts 👻 * add TEST for GET apis * complete TESTS * add HTMLURL to PullReview responce * code format as per @lafriks * update swagger definition * Update routers/api/v1/repo/pull_review.go Co-authored-by: David Svantesson <davidsvantesson@gmail.com> * add comments Co-authored-by: Thomas Berger <loki@lokis-chaos.de> Co-authored-by: David Svantesson <davidsvantesson@gmail.com>
1 parent d1deb1f commit 00ce90a

File tree

12 files changed

+1580
-26
lines changed

12 files changed

+1580
-26
lines changed

integrations/api_pull_review_test.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// Copyright 2020 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package integrations
6+
7+
import (
8+
"fmt"
9+
"net/http"
10+
"testing"
11+
12+
"code.gitea.io/gitea/models"
13+
api "code.gitea.io/gitea/modules/structs"
14+
"github.com/stretchr/testify/assert"
15+
)
16+
17+
func TestAPIPullReview(t *testing.T) {
18+
defer prepareTestEnv(t)()
19+
pullIssue := models.AssertExistsAndLoadBean(t, &models.Issue{ID: 3}).(*models.Issue)
20+
assert.NoError(t, pullIssue.LoadAttributes())
21+
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: pullIssue.RepoID}).(*models.Repository)
22+
23+
// test ListPullReviews
24+
session := loginUser(t, "user2")
25+
token := getTokenForLoggedInUser(t, session)
26+
req := NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/reviews?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token)
27+
resp := session.MakeRequest(t, req, http.StatusOK)
28+
29+
var reviews []*api.PullReview
30+
DecodeJSON(t, resp, &reviews)
31+
if !assert.Len(t, reviews, 6) {
32+
return
33+
}
34+
for _, r := range reviews {
35+
assert.EqualValues(t, pullIssue.HTMLURL(), r.HTMLPullURL)
36+
}
37+
assert.EqualValues(t, 8, reviews[3].ID)
38+
assert.EqualValues(t, "APPROVED", reviews[3].State)
39+
assert.EqualValues(t, 0, reviews[3].CodeCommentsCount)
40+
assert.EqualValues(t, true, reviews[3].Stale)
41+
assert.EqualValues(t, false, reviews[3].Official)
42+
43+
assert.EqualValues(t, 10, reviews[5].ID)
44+
assert.EqualValues(t, "REQUEST_CHANGES", reviews[5].State)
45+
assert.EqualValues(t, 1, reviews[5].CodeCommentsCount)
46+
assert.EqualValues(t, 0, reviews[5].Reviewer.ID) // ghost user
47+
assert.EqualValues(t, false, reviews[5].Stale)
48+
assert.EqualValues(t, true, reviews[5].Official)
49+
50+
// test GetPullReview
51+
req = NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/reviews/%d?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, reviews[3].ID, token)
52+
resp = session.MakeRequest(t, req, http.StatusOK)
53+
var review api.PullReview
54+
DecodeJSON(t, resp, &review)
55+
assert.EqualValues(t, *reviews[3], review)
56+
57+
req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/pulls/%d/reviews/%d?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, reviews[5].ID, token)
58+
resp = session.MakeRequest(t, req, http.StatusOK)
59+
DecodeJSON(t, resp, &review)
60+
assert.EqualValues(t, *reviews[5], review)
61+
62+
// test GetPullReviewComments
63+
comment := models.AssertExistsAndLoadBean(t, &models.Comment{ID: 7}).(*models.Comment)
64+
req = NewRequestf(t, http.MethodGet, "/api/v1/repos/%s/%s/pulls/%d/reviews/%d/comments?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, 10, token)
65+
resp = session.MakeRequest(t, req, http.StatusOK)
66+
var reviewComments []*api.PullReviewComment
67+
DecodeJSON(t, resp, &reviewComments)
68+
assert.Len(t, reviewComments, 1)
69+
assert.EqualValues(t, "Ghost", reviewComments[0].Reviewer.UserName)
70+
assert.EqualValues(t, "a review from a deleted user", reviewComments[0].Body)
71+
assert.EqualValues(t, comment.ID, reviewComments[0].ID)
72+
assert.EqualValues(t, comment.UpdatedUnix, reviewComments[0].Updated.Unix())
73+
assert.EqualValues(t, comment.HTMLURL(), reviewComments[0].HTMLURL)
74+
75+
// test CreatePullReview
76+
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token), &api.CreatePullReviewOptions{
77+
Body: "body1",
78+
// Event: "" # will result in PENDING
79+
Comments: []api.CreatePullReviewComment{{
80+
Path: "README.md",
81+
Body: "first new line",
82+
OldLineNum: 0,
83+
NewLineNum: 1,
84+
}, {
85+
Path: "README.md",
86+
Body: "first old line",
87+
OldLineNum: 1,
88+
NewLineNum: 0,
89+
},
90+
},
91+
})
92+
resp = session.MakeRequest(t, req, http.StatusOK)
93+
DecodeJSON(t, resp, &review)
94+
assert.EqualValues(t, 6, review.ID)
95+
assert.EqualValues(t, "PENDING", review.State)
96+
assert.EqualValues(t, 2, review.CodeCommentsCount)
97+
98+
// test SubmitPullReview
99+
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews/%d?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, review.ID, token), &api.SubmitPullReviewOptions{
100+
Event: "APPROVED",
101+
Body: "just two nits",
102+
})
103+
resp = session.MakeRequest(t, req, http.StatusOK)
104+
DecodeJSON(t, resp, &review)
105+
assert.EqualValues(t, 6, review.ID)
106+
assert.EqualValues(t, "APPROVED", review.State)
107+
assert.EqualValues(t, 2, review.CodeCommentsCount)
108+
109+
// test DeletePullReview
110+
req = NewRequestWithJSON(t, http.MethodPost, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, token), &api.CreatePullReviewOptions{
111+
Body: "just a comment",
112+
Event: "COMMENT",
113+
})
114+
resp = session.MakeRequest(t, req, http.StatusOK)
115+
DecodeJSON(t, resp, &review)
116+
assert.EqualValues(t, "COMMENT", review.State)
117+
assert.EqualValues(t, 0, review.CodeCommentsCount)
118+
req = NewRequestf(t, http.MethodDelete, "/api/v1/repos/%s/%s/pulls/%d/reviews/%d?token=%s", repo.OwnerName, repo.Name, pullIssue.Index, review.ID, token)
119+
resp = session.MakeRequest(t, req, http.StatusNoContent)
120+
}

models/fixtures/pull_request.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
base_repo_id: 1
99
head_branch: branch1
1010
base_branch: master
11-
merge_base: 1234567890abcdef
11+
merge_base: 4a357436d925b5c974181ff12a994538ddc5a269
1212
has_merged: true
1313
merger_id: 2
1414

@@ -22,7 +22,7 @@
2222
base_repo_id: 1
2323
head_branch: branch2
2424
base_branch: master
25-
merge_base: fedcba9876543210
25+
merge_base: 4a357436d925b5c974181ff12a994538ddc5a269
2626
has_merged: false
2727

2828
-

models/fixtures/review.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@
6060
reviewer_id: 4
6161
issue_id: 3
6262
content: "New review 5"
63+
commit_id: 8091a55037cd59e47293aca02981b5a67076b364
64+
stale: true
6365
updated_unix: 946684813
6466
created_unix: 946684813
6567
-
@@ -77,5 +79,6 @@
7779
reviewer_id: 100
7880
issue_id: 3
7981
content: "a deleted user's review"
82+
official: true
8083
updated_unix: 946684815
81-
created_unix: 946684815
84+
created_unix: 946684815

models/review.go

Lines changed: 93 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,13 @@ type Review struct {
7474
}
7575

7676
func (r *Review) loadCodeComments(e Engine) (err error) {
77-
if r.CodeComments == nil {
78-
r.CodeComments, err = fetchCodeCommentsByReview(e, r.Issue, nil, r)
77+
if r.CodeComments != nil {
78+
return
79+
}
80+
if err = r.loadIssue(e); err != nil {
81+
return
7982
}
83+
r.CodeComments, err = fetchCodeCommentsByReview(e, r.Issue, nil, r)
8084
return
8185
}
8286

@@ -86,12 +90,15 @@ func (r *Review) LoadCodeComments() error {
8690
}
8791

8892
func (r *Review) loadIssue(e Engine) (err error) {
93+
if r.Issue != nil {
94+
return
95+
}
8996
r.Issue, err = getIssueByID(e, r.IssueID)
9097
return
9198
}
9299

93100
func (r *Review) loadReviewer(e Engine) (err error) {
94-
if r.ReviewerID == 0 {
101+
if r.Reviewer != nil || r.ReviewerID == 0 {
95102
return nil
96103
}
97104
r.Reviewer, err = getUserByID(e, r.ReviewerID)
@@ -104,10 +111,13 @@ func (r *Review) LoadReviewer() error {
104111
}
105112

106113
func (r *Review) loadAttributes(e Engine) (err error) {
107-
if err = r.loadReviewer(e); err != nil {
114+
if err = r.loadIssue(e); err != nil {
108115
return
109116
}
110-
if err = r.loadIssue(e); err != nil {
117+
if err = r.loadCodeComments(e); err != nil {
118+
return
119+
}
120+
if err = r.loadReviewer(e); err != nil {
111121
return
112122
}
113123
return
@@ -136,6 +146,7 @@ func GetReviewByID(id int64) (*Review, error) {
136146

137147
// FindReviewOptions represent possible filters to find reviews
138148
type FindReviewOptions struct {
149+
ListOptions
139150
Type ReviewType
140151
IssueID int64
141152
ReviewerID int64
@@ -162,6 +173,9 @@ func (opts *FindReviewOptions) toCond() builder.Cond {
162173
func findReviews(e Engine, opts FindReviewOptions) ([]*Review, error) {
163174
reviews := make([]*Review, 0, 10)
164175
sess := e.Where(opts.toCond())
176+
if opts.Page > 0 {
177+
sess = opts.ListOptions.setSessionPagination(sess)
178+
}
165179
return reviews, sess.
166180
Asc("created_unix").
167181
Asc("id").
@@ -656,3 +670,77 @@ func CanMarkConversation(issue *Issue, doer *User) (permResult bool, err error)
656670

657671
return true, nil
658672
}
673+
674+
// DeleteReview delete a review and it's code comments
675+
func DeleteReview(r *Review) error {
676+
sess := x.NewSession()
677+
defer sess.Close()
678+
679+
if err := sess.Begin(); err != nil {
680+
return err
681+
}
682+
683+
if r.ID == 0 {
684+
return fmt.Errorf("review is not allowed to be 0")
685+
}
686+
687+
opts := FindCommentsOptions{
688+
Type: CommentTypeCode,
689+
IssueID: r.IssueID,
690+
ReviewID: r.ID,
691+
}
692+
693+
if _, err := sess.Where(opts.toConds()).Delete(new(Comment)); err != nil {
694+
return err
695+
}
696+
697+
opts = FindCommentsOptions{
698+
Type: CommentTypeReview,
699+
IssueID: r.IssueID,
700+
ReviewID: r.ID,
701+
}
702+
703+
if _, err := sess.Where(opts.toConds()).Delete(new(Comment)); err != nil {
704+
return err
705+
}
706+
707+
if _, err := sess.ID(r.ID).Delete(new(Review)); err != nil {
708+
return err
709+
}
710+
711+
return sess.Commit()
712+
}
713+
714+
// GetCodeCommentsCount return count of CodeComments a Review has
715+
func (r *Review) GetCodeCommentsCount() int {
716+
opts := FindCommentsOptions{
717+
Type: CommentTypeCode,
718+
IssueID: r.IssueID,
719+
ReviewID: r.ID,
720+
}
721+
conds := opts.toConds()
722+
if r.ID == 0 {
723+
conds = conds.And(builder.Eq{"invalidated": false})
724+
}
725+
726+
count, err := x.Where(conds).Count(new(Comment))
727+
if err != nil {
728+
return 0
729+
}
730+
return int(count)
731+
}
732+
733+
// HTMLURL formats a URL-string to the related review issue-comment
734+
func (r *Review) HTMLURL() string {
735+
opts := FindCommentsOptions{
736+
Type: CommentTypeReview,
737+
IssueID: r.IssueID,
738+
ReviewID: r.ID,
739+
}
740+
comment := new(Comment)
741+
has, err := x.Where(opts.toConds()).Get(comment)
742+
if err != nil || !has {
743+
return ""
744+
}
745+
return comment.HTMLURL()
746+
}

models/review_test.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,11 @@ func TestGetReviewersByIssueID(t *testing.T) {
131131

132132
allReviews, err := GetReviewersByIssueID(issue.ID)
133133
assert.NoError(t, err)
134-
for i, review := range allReviews {
135-
assert.Equal(t, expectedReviews[i].Reviewer, review.Reviewer)
136-
assert.Equal(t, expectedReviews[i].Type, review.Type)
137-
assert.Equal(t, expectedReviews[i].UpdatedUnix, review.UpdatedUnix)
134+
if assert.Len(t, allReviews, 3) {
135+
for i, review := range allReviews {
136+
assert.Equal(t, expectedReviews[i].Reviewer, review.Reviewer)
137+
assert.Equal(t, expectedReviews[i].Type, review.Type)
138+
assert.Equal(t, expectedReviews[i].UpdatedUnix, review.UpdatedUnix)
139+
}
138140
}
139141
}

0 commit comments

Comments
 (0)