1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package rpc
4
5import (
6 "testing"
7 "time"
8
9 pb "bnema.dev/ermite-api/gen/ermite"
10 "bnema.dev/ermite/internal/domain"
11 "bnema.dev/ermite/internal/ports"
12 "bnema.dev/ermite/internal/ports/mocks"
13 "connectrpc.com/connect"
14 "github.com/stretchr/testify/assert"
15 "github.com/stretchr/testify/mock"
16 "github.com/stretchr/testify/require"
17)
18
19func TestNoteRPCFields(t *testing.T) {
20 now := time.Now().UTC()
21 note := domain.Comment{ID: 4, ReplyTo: 2, StartLine: 10, Line: 24, Verdict: domain.VerdictApprove, EditedAt: now, Deleted: true}
22 got := commentToPB(note)
23 assert.Equal(t, "2", got.GetReplyTo())
24 assert.EqualValues(t, 10, got.GetStartLine())
25 assert.Equal(t, pb.Verdict_VERDICT_APPROVE, got.GetVerdict())
26 assert.Equal(t, now, got.GetEditedAt().AsTime())
27 assert.True(t, got.GetDeleted())
28 assert.Empty(t, commentToPB(domain.Comment{}).GetReplyTo())
29 assert.Nil(t, commentToPB(domain.Comment{}).GetEditedAt())
30 for _, v := range []domain.Verdict{domain.VerdictNone, domain.VerdictApprove, domain.VerdictChangesRequested} {
31 back, err := verdictFromPB(verdictToPB(v))
32 require.NoError(t, err)
33 assert.Equal(t, v, back)
34 }
35 m := mocks.NewMergeRequestService(t)
36 m.EXPECT().AddComment(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1), ports.AddCommentParams{Text: "note", StartLine: 10, Line: 24, Verdict: domain.VerdictApprove}).Return(note, nil).Once()
37 res, err := (&mergeRequestService{m}).AddComment(ownerCtx(), connect.NewRequest(&pb.AddCommentRequest{Repo: "demo", Number: 1, Text: "note", StartLine: 10, Line: 24, Verdict: pb.Verdict_VERDICT_APPROVE}))
38 require.NoError(t, err)
39 assert.Equal(t, got, res.Msg.GetComment())
40 _, err = (&mergeRequestService{m}).AddComment(ownerCtx(), connect.NewRequest(&pb.AddCommentRequest{Repo: "demo", Verdict: pb.Verdict(99)}))
41 requireCode(t, connect.CodeInvalidArgument, err)
42}
43
44func TestNoteMutationRPCCodes(t *testing.T) {
45 for _, edit := range []bool{false, true} {
46 for _, failure := range []error{nil, domain.ErrForbidden, domain.ErrPreconditionFailed, domain.ErrNotFound} {
47 m := mocks.NewMergeRequestService(t)
48 note := domain.Comment{ID: 2, Deleted: !edit}
49 var err error
50 if edit {
51 m.EXPECT().EditComment(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1), int64(2), "new").Return(note, failure).Once()
52 _, err = (&mergeRequestService{m}).EditComment(ownerCtx(), connect.NewRequest(&pb.EditCommentRequest{Repo: "demo", Number: 1, CommentId: "2", Text: "new"}))
53 } else {
54 m.EXPECT().DeleteComment(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1), int64(2)).Return(note, failure).Once()
55 _, err = (&mergeRequestService{m}).DeleteComment(ownerCtx(), connect.NewRequest(&pb.DeleteCommentRequest{Repo: "demo", Number: 1, CommentId: "2"}))
56 }
57 switch failure {
58 case nil:
59 require.NoError(t, err)
60 case domain.ErrForbidden:
61 requireCode(t, connect.CodePermissionDenied, err)
62 case domain.ErrPreconditionFailed:
63 requireCode(t, connect.CodeFailedPrecondition, err)
64 case domain.ErrNotFound:
65 requireCode(t, connect.CodeNotFound, err)
66 }
67 }
68 }
69}
70
71func TestNoteEventsRPC(t *testing.T) {
72 repo := mustRepoName(t, "demo")
73 note := domain.Comment{ID: 3, Verdict: domain.VerdictApprove, Deleted: true}
74 at := time.Now().UTC()
75 events := []domain.Event{
76 domain.CommentEdited{Repo: repo, Number: 1, Comment: note, At: at},
77 domain.CommentDeleted{Repo: repo, Number: 1, Comment: note, At: at},
78 domain.CommentResolved{Repo: repo, Number: 1, Comment: note, At: at},
79 }
80 for i, ev := range events {
81 got := watchEventToPB(ports.WatchEvent{ID: "e", Event: ev})
82 require.NotNil(t, got)
83 assert.Equal(t, at, got.GetTime().AsTime())
84 var c *pb.Comment
85 switch i {
86 case 0:
87 c = got.GetCommentEdited().GetComment()
88 case 1:
89 c = got.GetCommentDeleted().GetComment()
90 case 2:
91 c = got.GetCommentResolved().GetComment()
92 }
93 assert.Equal(t, "3", c.GetId())
94 assert.True(t, c.GetDeleted())
95 assert.Equal(t, pb.Verdict_VERDICT_APPROVE, c.GetVerdict())
96 }
97}
98
99func TestNoteReplyIDValidation(t *testing.T) {
100 for _, id := range []string{"abc", "0", "-1", "01", "9223372036854775808"} {
101 _, err := (&mergeRequestService{mocks.NewMergeRequestService(t)}).AddComment(ownerCtx(), connect.NewRequest(&pb.AddCommentRequest{Repo: "demo", Number: 1, ReplyTo: id, Text: "reply"}))
102 requireCode(t, connect.CodeInvalidArgument, err)
103 assert.Contains(t, err.Error(), "reply_to")
104 assert.Contains(t, err.Error(), "is not a note id")
105 }
106 m := mocks.NewMergeRequestService(t)
107 m.EXPECT().AddComment(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1), ports.AddCommentParams{ReplyTo: 2, Text: "reply"}).Return(domain.Comment{ID: 3, ReplyTo: 2}, nil).Once()
108 res, err := (&mergeRequestService{m}).AddComment(ownerCtx(), connect.NewRequest(&pb.AddCommentRequest{Repo: "demo", Number: 1, ReplyTo: "2", Text: "reply"}))
109 require.NoError(t, err)
110 assert.Equal(t, "2", res.Msg.GetComment().GetReplyTo())
111}
112
113func TestNoteVerdictsDetailAndScope(t *testing.T) {
114 m := mocks.NewMergeRequestService(t)
115 m.EXPECT().Get(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1), ports.GetMergeRequestOptions{}).Return(ports.MergeRequestDetail{Verdicts: []domain.AuthorVerdict{{Author: "owner", Verdict: domain.VerdictApprove, CommentID: 4, Stale: true}}}, nil).Once()
116 res, err := (&mergeRequestService{m}).GetMergeRequest(ownerCtx(), connect.NewRequest(&pb.GetMergeRequestRequest{Repo: "demo", Number: 1}))
117 require.NoError(t, err)
118 require.Len(t, res.Msg.GetVerdicts(), 1)
119 assert.Equal(t, "4", res.Msg.GetVerdicts()[0].GetCommentId())
120 assert.True(t, res.Msg.GetVerdicts()[0].GetStale())
121 m.EXPECT().AddComment(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1), ports.AddCommentParams{Verdict: domain.VerdictApprove}).Return(domain.Comment{}, domain.ErrForbidden).Once()
122 _, err = (&mergeRequestService{m}).AddComment(ownerCtx(), connect.NewRequest(&pb.AddCommentRequest{Repo: "demo", Number: 1, Verdict: pb.Verdict_VERDICT_APPROVE}))
123 requireCode(t, connect.CodePermissionDenied, err)
124}