mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 11:01:25 +01:00
271 lines
12 KiB
Go
271 lines
12 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package services
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
gitea "gitea.dev/sdk"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestReviewCommentsContinueWhenServerCapsPageSize(t *testing.T) {
|
|
reviewPages, collections := 0, 0
|
|
client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/api/v1/repos/owner/repo/pulls/3/reviews" {
|
|
reviewPages++
|
|
switch r.URL.Query().Get("page") {
|
|
case "1":
|
|
_, _ = w.Write([]byte(`[{"id":1,"comments_count":1}]`))
|
|
case "2":
|
|
_, _ = w.Write([]byte(`[{"id":2,"comments_count":1}]`))
|
|
case "3":
|
|
_, _ = w.Write([]byte(`[]`))
|
|
default:
|
|
t.Errorf("unexpected review page %q", r.URL.Query().Get("page"))
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
}
|
|
return
|
|
}
|
|
collections++
|
|
assert.Empty(t, r.URL.RawQuery, "per-review comments are unpaginated")
|
|
if r.URL.Path == "/api/v1/repos/owner/repo/pulls/3/reviews/1/comments" {
|
|
_, _ = w.Write([]byte(`[{"id":5,"position":4,"pull_request_review_id":1,"user":{"login":"alice"},"resolver":{"login":"bob"}}]`))
|
|
} else {
|
|
assert.Equal(t, "/api/v1/repos/owner/repo/pulls/3/reviews/2/comments", r.URL.Path)
|
|
_, _ = w.Write([]byte(`[{"id":2}]`))
|
|
}
|
|
}))
|
|
items, err := (Reviews{Client: client, Repository: "owner/repo"}).Comments(context.Background(), 3, 0, 1, 2)
|
|
require.NoError(t, err)
|
|
require.Len(t, items, 1)
|
|
require.EqualValues(t, 5, items[0].ID)
|
|
require.EqualValues(t, 4, items[0].Line)
|
|
require.Equal(t, "alice", items[0].Author)
|
|
require.Equal(t, "bob", items[0].Resolver)
|
|
require.Equal(t, 3, reviewPages)
|
|
require.Equal(t, 2, collections)
|
|
}
|
|
|
|
func TestInlineCommentsSkipPendingReviews(t *testing.T) {
|
|
var fetched []string
|
|
client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
fetched = append(fetched, r.URL.Path)
|
|
_, _ = w.Write([]byte(`[{"id":5,"pull_request_review_id":1}]`))
|
|
}))
|
|
reviews := []Review{{ID: 1, State: "COMMENT", CommentsCount: 1}, {ID: 2, State: "PENDING", CommentsCount: 1}}
|
|
items, err := (Reviews{Client: client, Repository: "owner/repo"}).Inline(context.Background(), 3, reviews)
|
|
require.NoError(t, err)
|
|
require.Len(t, items, 1)
|
|
require.Equal(t, []string{"/api/v1/repos/owner/repo/pulls/3/reviews/1/comments"}, fetched)
|
|
}
|
|
|
|
func TestReviewListPageReachesPastServerPageCap(t *testing.T) {
|
|
client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
size, _ := strconv.Atoi(r.URL.Query().Get("limit"))
|
|
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
|
|
size = min(size, 2) // the server caps its pages at two items
|
|
var reviews []string
|
|
for id := (page-1)*size + 1; id <= min(page*size, 5); id++ {
|
|
reviews = append(reviews, fmt.Sprintf(`{"id":%d}`, id))
|
|
}
|
|
w.Header().Set("X-Total-Count", "5")
|
|
_, _ = w.Write([]byte("[" + strings.Join(reviews, ",") + "]"))
|
|
}))
|
|
window, err := (Reviews{Client: client, Repository: "owner/repo"}).ListPage(context.Background(), 3, 4, 1)
|
|
require.NoError(t, err)
|
|
require.Len(t, window.Items, 4)
|
|
require.EqualValues(t, 4, window.Items[3].ID)
|
|
require.True(t, window.More)
|
|
window, err = (Reviews{Client: client, Repository: "owner/repo"}).ListPage(context.Background(), 3, 4, 2)
|
|
require.NoError(t, err)
|
|
require.Len(t, window.Items, 1)
|
|
require.EqualValues(t, 5, window.Items[0].ID)
|
|
require.False(t, window.More)
|
|
}
|
|
|
|
func TestReviewDismissalReturnsTheUpdatedReview(t *testing.T) {
|
|
for _, dismiss := range []bool{false, true} {
|
|
t.Run(fmt.Sprintf("dismiss=%t", dismiss), func(t *testing.T) {
|
|
calls := 0
|
|
client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
calls++
|
|
if r.Method == http.MethodGet {
|
|
assert.Equal(t, 2, calls, "the review is read after the mutation")
|
|
assert.Equal(t, "/api/v1/repos/owner/repo/pulls/1/reviews/2", r.URL.Path)
|
|
_ = json.NewEncoder(w).Encode(map[string]any{"id": 2, "dismissed": dismiss})
|
|
return
|
|
}
|
|
assert.Equal(t, http.MethodPost, r.Method)
|
|
if dismiss {
|
|
assert.Equal(t, "/api/v1/repos/owner/repo/pulls/1/reviews/2/dismissals", r.URL.Path)
|
|
var data map[string]string
|
|
assert.NoError(t, json.NewDecoder(r.Body).Decode(&data))
|
|
assert.Equal(t, map[string]string{"message": "Reason"}, data)
|
|
} else {
|
|
assert.Equal(t, "/api/v1/repos/owner/repo/pulls/1/reviews/2/undismissals", r.URL.Path)
|
|
assert.Zero(t, r.ContentLength)
|
|
}
|
|
_ = json.NewEncoder(w).Encode(map[string]any{"id": 2, "dismissed": dismiss})
|
|
}))
|
|
item, err := (Reviews{Client: client, Repository: "owner/repo"}).Dismiss(context.Background(), 1, 2, dismiss, "Reason")
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, 2, item.ID)
|
|
require.Equal(t, dismiss, item.Dismissed)
|
|
require.Equal(t, 2, calls)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestReviewSubmitPreservesBodyUnlessReplaced(t *testing.T) {
|
|
empty := ""
|
|
for name, test := range map[string]struct {
|
|
body *string
|
|
wantBody string
|
|
wantGets int
|
|
}{
|
|
"omitted keeps current": {wantBody: "Pending notes", wantGets: 1},
|
|
"explicit empty clears": {body: &empty, wantBody: ""},
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
gets := 0
|
|
var submitted map[string]string
|
|
client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
assert.Equal(t, "/api/v1/repos/owner/repo/pulls/1/reviews/2", r.URL.Path)
|
|
if r.Method == http.MethodGet {
|
|
gets++
|
|
_, _ = w.Write([]byte(`{"id":2,"body":"Pending notes","state":"PENDING"}`))
|
|
return
|
|
}
|
|
assert.Equal(t, http.MethodPost, r.Method)
|
|
assert.NoError(t, json.NewDecoder(r.Body).Decode(&submitted))
|
|
_, _ = w.Write([]byte(`{"id":2,"state":"APPROVED"}`))
|
|
}))
|
|
item, err := (Reviews{Client: client, Repository: "owner/repo"}).Submit(context.Background(), 1, 2, ReviewSubmitOptions{Event: "APPROVED", Body: test.body})
|
|
require.NoError(t, err)
|
|
require.EqualValues(t, 2, item.ID)
|
|
require.Equal(t, test.wantGets, gets)
|
|
require.Equal(t, map[string]string{"event": "APPROVED", "body": test.wantBody}, submitted)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestReviewSubmitAllowsEmptyCommentBody(t *testing.T) {
|
|
var submitted map[string]string
|
|
client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
assert.Equal(t, "POST /api/v1/repos/owner/repo/pulls/1/reviews/2", r.Method+" "+r.URL.Path)
|
|
assert.NoError(t, json.NewDecoder(r.Body).Decode(&submitted))
|
|
_, _ = w.Write([]byte(`{"id":2,"state":"COMMENT","comments_count":1}`))
|
|
}))
|
|
empty := ""
|
|
item, err := (Reviews{Client: client, Repository: "owner/repo"}).Submit(context.Background(), 1, 2, ReviewSubmitOptions{Event: "COMMENT", Body: &empty})
|
|
require.NoError(t, err)
|
|
require.Equal(t, Review{ID: 2, State: "COMMENT", CommentsCount: 1}, item)
|
|
require.Equal(t, map[string]string{"event": "COMMENT", "body": ""}, submitted)
|
|
}
|
|
|
|
func TestReviewCreateSendsInlineComments(t *testing.T) {
|
|
var created struct {
|
|
Event string `json:"event"`
|
|
Body string `json:"body"`
|
|
Commit string `json:"commit_id"`
|
|
Comments []InlineComment
|
|
}
|
|
client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
assert.Equal(t, http.MethodPost, r.Method)
|
|
assert.Equal(t, "/api/v1/repos/owner/repo/pulls/1/reviews", r.URL.Path)
|
|
assert.NoError(t, json.NewDecoder(r.Body).Decode(&created))
|
|
_, _ = w.Write([]byte(`{"id":7,"state":"REQUEST_CHANGES","user":{"login":"alice"},"commit_id":"abc"}`))
|
|
}))
|
|
item, err := (Reviews{Client: client, Repository: "owner/repo"}).Create(context.Background(), 1, ReviewOptions{
|
|
Event: "REQUEST_CHANGES", Body: "Fix", Commit: "abc",
|
|
Comments: []InlineComment{{Path: "a.go", Body: "here", NewLine: 3}, {Path: "b.go", Body: "there", OldLine: 2}},
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, Review{ID: 7, State: "REQUEST_CHANGES", Author: "alice", Commit: "abc"}, item)
|
|
require.Equal(t, "REQUEST_CHANGES", created.Event)
|
|
require.Equal(t, "Fix", created.Body)
|
|
require.Equal(t, "abc", created.Commit)
|
|
require.Equal(t, []InlineComment{{Path: "a.go", Body: "here", NewLine: 3}, {Path: "b.go", Body: "there", OldLine: 2}}, created.Comments)
|
|
}
|
|
|
|
func TestReviewResolveAndReplyUseCommentEndpoints(t *testing.T) {
|
|
paths := []string{}
|
|
client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
assert.Equal(t, http.MethodPost, r.Method)
|
|
paths = append(paths, r.URL.Path)
|
|
if r.URL.Path == "/api/v1/repos/owner/repo/pulls/1/comments/5/replies" {
|
|
var reply map[string]string
|
|
assert.NoError(t, json.NewDecoder(r.Body).Decode(&reply))
|
|
assert.Equal(t, map[string]string{"body": "Done"}, reply)
|
|
_, _ = w.Write([]byte(`{"id":6,"body":"Done","user":{"login":"bob"},"pull_request_review_id":3}`))
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
service := Reviews{Client: client, Repository: "owner/repo"}
|
|
require.NoError(t, service.Resolve(context.Background(), 5, true))
|
|
require.NoError(t, service.Resolve(context.Background(), 5, false))
|
|
reply, err := service.Reply(context.Background(), 1, 5, "Done")
|
|
require.NoError(t, err)
|
|
require.Equal(t, ReviewComment{ID: 6, ReviewID: 3, Body: "Done", Author: "bob"}, reply)
|
|
require.Equal(t, []string{
|
|
"/api/v1/repos/owner/repo/pulls/comments/5/resolve",
|
|
"/api/v1/repos/owner/repo/pulls/comments/5/unresolve",
|
|
"/api/v1/repos/owner/repo/pulls/1/comments/5/replies",
|
|
}, paths)
|
|
}
|
|
|
|
func TestReviewDecisionIgnoresCommentReviews(t *testing.T) {
|
|
reviews := []Review{
|
|
{ID: 1, Author: "bob", State: "REQUEST_CHANGES"},
|
|
{ID: 2, Author: "bob", State: "COMMENT"},
|
|
{ID: 3, Author: "carol", State: "APPROVED"},
|
|
}
|
|
assert.Equal(t, "changes_requested", ReviewDecision(reviews, false))
|
|
assert.Equal(t, "approved", ReviewDecision([]Review{{ID: 1, Author: "carol", State: "APPROVED"}, {ID: 2, Author: "carol", State: "COMMENT"}}, false))
|
|
assert.Equal(t, "", ReviewDecision([]Review{{ID: 1, Author: "carol", State: "COMMENT"}}, false))
|
|
}
|
|
|
|
func TestReviewFromMapsServerFields(t *testing.T) {
|
|
review := decode[gitea.PullReview](t, `{"id":2,"body":"LGTM","state":"APPROVED","user":{"login":"alice"},"html_url":"https://gitea.test/r/2",
|
|
"commit_id":"abc","submitted_at":"2026-10-01T12:00:00Z","comments_count":3,"dismissed":true}`)
|
|
require.Equal(t, Review{
|
|
ID: 2, Body: "LGTM", State: "APPROVED", Author: "alice", URL: "https://gitea.test/r/2", Commit: "abc",
|
|
SubmittedAt: "2026-10-01T12:00:00Z", CommentsCount: 3, Dismissed: true,
|
|
}, reviewFrom(review))
|
|
}
|
|
|
|
func TestReviewFromNamesRequestedTeam(t *testing.T) {
|
|
review := reviewFrom(decode[gitea.PullReview](t, `{"id":251,"state":"REQUEST_REVIEW","user":null,"team":{"name":"devs"}}`))
|
|
require.Equal(t, "devs", review.Team)
|
|
require.Equal(t, "team devs", review.Reviewer())
|
|
require.Equal(t, "alice", Review{Author: "alice", Team: "devs"}.Reviewer())
|
|
}
|
|
|
|
func TestReviewCommentFromMapsServerFields(t *testing.T) {
|
|
comment := decode[gitea.PullReviewComment](t, `{"id":5,"pull_request_review_id":1,"body":"Nit","user":{"login":"alice"},
|
|
"resolver":{"login":"bob"},"html_url":"https://gitea.test/c/5","path":"main.go","position":12,
|
|
"created_at":"2026-10-01T12:00:00Z","updated_at":"2026-10-02T12:00:00Z"}`)
|
|
require.Equal(t, ReviewComment{
|
|
ID: 5, ReviewID: 1, Body: "Nit", Author: "alice", Resolver: "bob", URL: "https://gitea.test/c/5", Path: "main.go", Line: 12,
|
|
CreatedAt: "2026-10-01T12:00:00Z", UpdatedAt: "2026-10-02T12:00:00Z",
|
|
}, reviewCommentFrom(comment))
|
|
}
|
|
|
|
func TestReviewCommentLocationNamesRemovedLines(t *testing.T) {
|
|
removed := reviewCommentFrom(decode[gitea.PullReviewComment](t, `{"id":6,"path":"main.go","position":0,"original_position":4}`))
|
|
require.EqualValues(t, 4, removed.OldLine)
|
|
require.Equal(t, "main.go:4 (old)", removed.Location())
|
|
require.Equal(t, "main.go:12", ReviewComment{Path: "main.go", Line: 12}.Location())
|
|
require.Equal(t, "main.go", ReviewComment{Path: "main.go"}.Location())
|
|
}
|