detached 03a1ab8

Merge branch 'feat/inbox' into feat/issue-form

# Conflicts:
#	internal/adapters/rpc/api.go
commit
03a1ab8aa282aaf6651237efd7a7ec8750083a64
author
brice · · 2026-10-19 08:39 UTC
merge of
b5344bf + 0092b37 · diff against the first parent
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
5 files · +30 -17
M internal/adapters/client/scopes.go +5 -3
@@ -35,12 +35,14 @@ func ScopeName(s ermite.Scope) string {
3535 return strings.Replace(strings.ToLower(strings.TrimPrefix(name, "SCOPE_")), "_", ":", 1)
3636 }
3737
38-// AllScopes lists every scope the contract defines, in enum order. Deriving it
39-// keeps the `cli` preset ("every scope") true when the contract grows.
38+// AllScopes lists every scope the contract defines that this server accepts, in
39+// enum order. Deriving it keeps the `cli` preset ("every scope") true when the
40+// contract grows. SCOPE_MR_REVIEW is in the contract but not yet implemented
41+// here, and the server rejects it like any unknown scope, so it is left out.
4042 func AllScopes() []ermite.Scope {
4143 var scopes []ermite.Scope
4244 for n := range ermite.Scope_name {
43- if s := ermite.Scope(n); s != ermite.Scope_SCOPE_UNSPECIFIED {
45+ if s := ermite.Scope(n); s != ermite.Scope_SCOPE_UNSPECIFIED && s != ermite.Scope_SCOPE_MR_REVIEW {
4446 scopes = append(scopes, s)
4547 }
4648 }
@@ -35,12 +35,14 @@ func ScopeName(s ermite.Scope) string {
35 return strings.Replace(strings.ToLower(strings.TrimPrefix(name, "SCOPE_")), "_", ":", 1)35 return strings.Replace(strings.ToLower(strings.TrimPrefix(name, "SCOPE_")), "_", ":", 1)
36 }36 }
37 37
38-// AllScopes lists every scope the contract defines, in enum order. Deriving it38+// AllScopes lists every scope the contract defines that this server accepts, in
39-// keeps the `cli` preset ("every scope") true when the contract grows.39+// enum order. Deriving it keeps the `cli` preset ("every scope") true when the
40+// contract grows. SCOPE_MR_REVIEW is in the contract but not yet implemented
41+// here, and the server rejects it like any unknown scope, so it is left out.
40 func AllScopes() []ermite.Scope {42 func AllScopes() []ermite.Scope {
41 var scopes []ermite.Scope43 var scopes []ermite.Scope
42 for n := range ermite.Scope_name {44 for n := range ermite.Scope_name {
43- if s := ermite.Scope(n); s != ermite.Scope_SCOPE_UNSPECIFIED {45+ if s := ermite.Scope(n); s != ermite.Scope_SCOPE_UNSPECIFIED && s != ermite.Scope_SCOPE_MR_REVIEW {
44 scopes = append(scopes, s)46 scopes = append(scopes, s)
45 }47 }
46 }48 }
M internal/adapters/rpc/api.go +1 -1
@@ -145,7 +145,7 @@ func New(cfg Config) *API {
145145 features = append(features, FeatureCommitSignatures)
146146 }
147147 mux.Handle(ermiteconnect.NewServerServiceHandler(&serverService{version: cfg.Version, features: features, key: cfg.SigningKey, oidc: cfg.OIDCKeys}, opts...))
148- mux.Handle(ermiteconnect.NewMergeRequestServiceHandler(&mergeRequestService{cfg.MergeRequests}, opts...))
148+ mux.Handle(ermiteconnect.NewMergeRequestServiceHandler(&mergeRequestService{mrs: cfg.MergeRequests}, opts...))
149149 mux.Handle(ermiteconnect.NewIssueServiceHandler(&issueService{issues: cfg.Issues, moderation: cfg.IssueModeration}, opts...))
150150 if cfg.Sessions != nil {
151151 mux.Handle(ermiteconnect.NewSessionServiceHandler(&sessionService{sessions: cfg.Sessions, passkeys: cfg.Passkeys}, opts...))
@@ -145,7 +145,7 @@ func New(cfg Config) *API {
145 features = append(features, FeatureCommitSignatures)145 features = append(features, FeatureCommitSignatures)
146 }146 }
147 mux.Handle(ermiteconnect.NewServerServiceHandler(&serverService{version: cfg.Version, features: features, key: cfg.SigningKey, oidc: cfg.OIDCKeys}, opts...))147 mux.Handle(ermiteconnect.NewServerServiceHandler(&serverService{version: cfg.Version, features: features, key: cfg.SigningKey, oidc: cfg.OIDCKeys}, opts...))
148- mux.Handle(ermiteconnect.NewMergeRequestServiceHandler(&mergeRequestService{cfg.MergeRequests}, opts...))148+ mux.Handle(ermiteconnect.NewMergeRequestServiceHandler(&mergeRequestService{mrs: cfg.MergeRequests}, opts...))
149 mux.Handle(ermiteconnect.NewIssueServiceHandler(&issueService{issues: cfg.Issues, moderation: cfg.IssueModeration}, opts...))149 mux.Handle(ermiteconnect.NewIssueServiceHandler(&issueService{issues: cfg.Issues, moderation: cfg.IssueModeration}, opts...))
150 if cfg.Sessions != nil {150 if cfg.Sessions != nil {
151 mux.Handle(ermiteconnect.NewSessionServiceHandler(&sessionService{sessions: cfg.Sessions, passkeys: cfg.Passkeys}, opts...))151 mux.Handle(ermiteconnect.NewSessionServiceHandler(&sessionService{sessions: cfg.Sessions, passkeys: cfg.Passkeys}, opts...))
M internal/adapters/rpc/convert_test.go +6 -1
@@ -50,12 +50,17 @@ func TestEnumMappersCoverEveryDomainValue(t *testing.T) {
5050 }
5151 _, err := scopeFromPB(pb.Scope_SCOPE_UNSPECIFIED)
5252 assert.ErrorIs(t, err, domain.ErrInvalidScope)
53- // Every scope of the contract is a scope of the domain.
53+ // Every scope of the contract is a scope of the domain, except mr:review.
5454 for v := range pb.Scope_name {
5555 if pb.Scope(v) == pb.Scope_SCOPE_UNSPECIFIED {
5656 continue
5757 }
5858 _, err := scopeFromPB(pb.Scope(v))
59+ if pb.Scope(v) == pb.Scope_SCOPE_MR_REVIEW {
60+ // In the contract, not implemented here yet: rejected like an unknown scope.
61+ assert.ErrorIs(t, err, domain.ErrInvalidScope, pb.Scope(v))
62+ continue
63+ }
5964 assert.NoError(t, err, pb.Scope(v))
6065 }
6166 })
@@ -50,12 +50,17 @@ func TestEnumMappersCoverEveryDomainValue(t *testing.T) {
50 }50 }
51 _, err := scopeFromPB(pb.Scope_SCOPE_UNSPECIFIED)51 _, err := scopeFromPB(pb.Scope_SCOPE_UNSPECIFIED)
52 assert.ErrorIs(t, err, domain.ErrInvalidScope)52 assert.ErrorIs(t, err, domain.ErrInvalidScope)
53- // Every scope of the contract is a scope of the domain.53+ // Every scope of the contract is a scope of the domain, except mr:review.
54 for v := range pb.Scope_name {54 for v := range pb.Scope_name {
55 if pb.Scope(v) == pb.Scope_SCOPE_UNSPECIFIED {55 if pb.Scope(v) == pb.Scope_SCOPE_UNSPECIFIED {
56 continue56 continue
57 }57 }
58 _, err := scopeFromPB(pb.Scope(v))58 _, err := scopeFromPB(pb.Scope(v))
59+ if pb.Scope(v) == pb.Scope_SCOPE_MR_REVIEW {
60+ // In the contract, not implemented here yet: rejected like an unknown scope.
61+ assert.ErrorIs(t, err, domain.ErrInvalidScope, pb.Scope(v))
62+ continue
63+ }
59 assert.NoError(t, err, pb.Scope(v))64 assert.NoError(t, err, pb.Scope(v))
60 }65 }
61 })66 })
M internal/adapters/rpc/mergerequest.go +7 -1
@@ -10,12 +10,18 @@ import (
1010 "connectrpc.com/connect"
1111
1212 pb "bnema.dev/ermite-api/gen/ermite"
13+ "bnema.dev/ermite-api/gen/ermite/ermiteconnect"
1314 "bnema.dev/ermite/internal/domain"
1415 "bnema.dev/ermite/internal/ports"
1516 )
1617
1718 // mergeRequestService implements ermiteconnect.MergeRequestServiceHandler.
18-type mergeRequestService struct{ mrs ports.MergeRequestService }
19+type mergeRequestService struct {
20+ // The note methods (edit, delete, verdicts) are implemented by the merge
21+ // request notes work; until then they answer CodeUnimplemented.
22+ ermiteconnect.UnimplementedMergeRequestServiceHandler
23+ mrs ports.MergeRequestService
24+}
1925
2026 func mergeRequestStateToPB(s domain.MRState) pb.MergeRequestState {
2127 switch s {
@@ -10,12 +10,18 @@ import (
10 "connectrpc.com/connect"10 "connectrpc.com/connect"
11 11
12 pb "bnema.dev/ermite-api/gen/ermite"12 pb "bnema.dev/ermite-api/gen/ermite"
13+ "bnema.dev/ermite-api/gen/ermite/ermiteconnect"
13 "bnema.dev/ermite/internal/domain"14 "bnema.dev/ermite/internal/domain"
14 "bnema.dev/ermite/internal/ports"15 "bnema.dev/ermite/internal/ports"
15 )16 )
16 17
17 // mergeRequestService implements ermiteconnect.MergeRequestServiceHandler.18 // mergeRequestService implements ermiteconnect.MergeRequestServiceHandler.
18-type mergeRequestService struct{ mrs ports.MergeRequestService }19+type mergeRequestService struct {
20+ // The note methods (edit, delete, verdicts) are implemented by the merge
21+ // request notes work; until then they answer CodeUnimplemented.
22+ ermiteconnect.UnimplementedMergeRequestServiceHandler
23+ mrs ports.MergeRequestService
24+}
19 25
20 func mergeRequestStateToPB(s domain.MRState) pb.MergeRequestState {26 func mergeRequestStateToPB(s domain.MRState) pb.MergeRequestState {
21 switch s {27 switch s {
M internal/adapters/rpc/mergerequest_test.go +11 -11
@@ -41,7 +41,7 @@ func TestMergeRequestServiceCreate(t *testing.T) {
4141 Title: "t", Description: "d", Source: "feat", Target: "main",
4242 }).Return(domain.MergeRequest{Number: 7, Source: "feat", Target: "main", State: domain.MRStateOpen}, tt.usecase).Once()
4343 }
44- resp, err := (&mergeRequestService{mrs}).CreateMergeRequest(ownerCtx(), connect.NewRequest(&pb.CreateMergeRequestRequest{
44+ resp, err := (&mergeRequestService{mrs: mrs}).CreateMergeRequest(ownerCtx(), connect.NewRequest(&pb.CreateMergeRequestRequest{
4545 Repo: tt.repo, Title: "t", Description: "d", SourceBranch: "feat", TargetBranch: "main",
4646 }))
4747 if tt.wantCode != 0 {
@@ -57,7 +57,7 @@ func TestMergeRequestServiceCreate(t *testing.T) {
5757
5858 func TestMergeRequestServiceListRejectsUnknownState(t *testing.T) {
5959 t.Parallel()
60- _, err := (&mergeRequestService{mocks.NewMergeRequestService(t)}).ListMergeRequests(ownerCtx(),
60+ _, err := (&mergeRequestService{mrs: mocks.NewMergeRequestService(t)}).ListMergeRequests(ownerCtx(),
6161 connect.NewRequest(&pb.ListMergeRequestsRequest{Repo: "demo", State: pb.MergeRequestState(99)}))
6262 requireCode(t, connect.CodeInvalidArgument, err)
6363 }
@@ -68,7 +68,7 @@ func TestMergeRequestServiceListFiltersByState(t *testing.T) {
6868 mrs.EXPECT().List(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), ports.ListMergeRequestsParams{
6969 State: domain.MRStateMerged, PageSize: 3, PageToken: "tok",
7070 }).Return(ports.MergeRequestPage{MergeRequests: []domain.MergeRequest{{Number: 1}}, NextPageToken: "n"}, nil).Once()
71- resp, err := (&mergeRequestService{mrs}).ListMergeRequests(ownerCtx(), connect.NewRequest(&pb.ListMergeRequestsRequest{
71+ resp, err := (&mergeRequestService{mrs: mrs}).ListMergeRequests(ownerCtx(), connect.NewRequest(&pb.ListMergeRequestsRequest{
7272 Repo: "demo", State: pb.MergeRequestState_MERGE_REQUEST_STATE_MERGED, PageSize: 3, PageToken: "tok",
7373 }))
7474 require.NoError(t, err)
@@ -99,7 +99,7 @@ func TestMergeRequestServiceAddComment(t *testing.T) {
9999 mrs.EXPECT().AddComment(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(2), mock.Anything).
100100 Return(domain.Comment{ID: 5}, tt.usecase).Once()
101101 }
102- resp, err := (&mergeRequestService{mrs}).AddComment(ownerCtx(), connect.NewRequest(&pb.AddCommentRequest{
102+ resp, err := (&mergeRequestService{mrs: mrs}).AddComment(ownerCtx(), connect.NewRequest(&pb.AddCommentRequest{
103103 Repo: "demo", Number: 2, Path: "f.go", Line: 3, Side: tt.side, Text: "hi",
104104 }))
105105 if tt.wantCode != 0 {
@@ -120,7 +120,7 @@ func TestMergeRequestServiceResolveCommentMapsUnissuedIDsToZero(t *testing.T) {
120120 mrs := mocks.NewMergeRequestService(t)
121121 mrs.EXPECT().ResolveComment(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1), int64(0), true).
122122 Return(domain.Comment{}, domain.ErrNotFound).Once()
123- _, err := (&mergeRequestService{mrs}).ResolveComment(ownerCtx(), connect.NewRequest(&pb.ResolveCommentRequest{
123+ _, err := (&mergeRequestService{mrs: mrs}).ResolveComment(ownerCtx(), connect.NewRequest(&pb.ResolveCommentRequest{
124124 Repo: "demo", Number: 1, CommentId: id, Resolved: true,
125125 }))
126126 requireCode(t, connect.CodeNotFound, err)
@@ -154,7 +154,7 @@ func TestMergeRequestServiceMerge(t *testing.T) {
154154 return p.DeleteSource == tt.req.GetDeleteSourceBranch() && (tt.req.GetHeadId() == "") == p.HeadID.IsZero()
155155 })).Return(ports.MergeResult{MergeRequest: domain.MergeRequest{Number: 1}, SourceDeleted: true}, tt.usecase).Once()
156156 }
157- resp, err := (&mergeRequestService{mrs}).MergeMergeRequest(ownerCtx(), connect.NewRequest(tt.req))
157+ resp, err := (&mergeRequestService{mrs: mrs}).MergeMergeRequest(ownerCtx(), connect.NewRequest(tt.req))
158158 if tt.wantCode != 0 {
159159 requireCode(t, tt.wantCode, err)
160160 return
@@ -171,20 +171,20 @@ func TestMergeRequestServiceCloseAndMarkRead(t *testing.T) {
171171 t.Parallel()
172172 mrs := mocks.NewMergeRequestService(t)
173173 mrs.EXPECT().Close(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(9)).Return(domain.MergeRequest{}, domain.ErrNotFound).Once()
174- _, err := (&mergeRequestService{mrs}).CloseMergeRequest(ownerCtx(), connect.NewRequest(&pb.CloseMergeRequestRequest{Repo: "demo", Number: 9}))
174+ _, err := (&mergeRequestService{mrs: mrs}).CloseMergeRequest(ownerCtx(), connect.NewRequest(&pb.CloseMergeRequestRequest{Repo: "demo", Number: 9}))
175175 requireCode(t, connect.CodeNotFound, err)
176176 })
177177 t.Run("close ok", func(t *testing.T) {
178178 t.Parallel()
179179 mrs := mocks.NewMergeRequestService(t)
180180 mrs.EXPECT().Close(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(9)).Return(domain.MergeRequest{Number: 9, State: domain.MRStateClosed}, nil).Once()
181- resp, err := (&mergeRequestService{mrs}).CloseMergeRequest(ownerCtx(), connect.NewRequest(&pb.CloseMergeRequestRequest{Repo: "demo", Number: 9}))
181+ resp, err := (&mergeRequestService{mrs: mrs}).CloseMergeRequest(ownerCtx(), connect.NewRequest(&pb.CloseMergeRequestRequest{Repo: "demo", Number: 9}))
182182 require.NoError(t, err)
183183 assert.Equal(t, pb.MergeRequestState_MERGE_REQUEST_STATE_CLOSED, resp.Msg.GetMergeRequest().GetState())
184184 })
185185 t.Run("mark read rejects a short head", func(t *testing.T) {
186186 t.Parallel()
187- _, err := (&mergeRequestService{mocks.NewMergeRequestService(t)}).MarkRead(ownerCtx(),
187+ _, err := (&mergeRequestService{mrs: mocks.NewMergeRequestService(t)}).MarkRead(ownerCtx(),
188188 connect.NewRequest(&pb.MarkReadRequest{Repo: "demo", Number: 1, HeadId: "abc"}))
189189 requireCode(t, connect.CodeInvalidArgument, err)
190190 })
@@ -192,7 +192,7 @@ func TestMergeRequestServiceCloseAndMarkRead(t *testing.T) {
192192 t.Parallel()
193193 mrs := mocks.NewMergeRequestService(t)
194194 mrs.EXPECT().MarkRead(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1), domain.ObjectID{}).Return(domain.ObjectID{}, domain.ErrUnauthenticated).Once()
195- _, err := (&mergeRequestService{mrs}).MarkRead(ownerCtx(), connect.NewRequest(&pb.MarkReadRequest{Repo: "demo", Number: 1}))
195+ _, err := (&mergeRequestService{mrs: mrs}).MarkRead(ownerCtx(), connect.NewRequest(&pb.MarkReadRequest{Repo: "demo", Number: 1}))
196196 requireCode(t, connect.CodeUnauthenticated, err)
197197 })
198198 }
@@ -214,7 +214,7 @@ func TestMergeRequestServiceGet(t *testing.T) {
214214 mrs := mocks.NewMergeRequestService(t)
215215 mrs.EXPECT().Get(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(4), ports.GetMergeRequestOptions{SinceLastRead: true}).
216216 Return(ports.MergeRequestDetail{MergeRequest: domain.MergeRequest{Number: 4}, CommitsTruncated: true}, tt.usecase).Once()
217- resp, err := (&mergeRequestService{mrs}).GetMergeRequest(ownerCtx(), connect.NewRequest(&pb.GetMergeRequestRequest{Repo: "demo", Number: 4, SinceLastRead: true}))
217+ resp, err := (&mergeRequestService{mrs: mrs}).GetMergeRequest(ownerCtx(), connect.NewRequest(&pb.GetMergeRequestRequest{Repo: "demo", Number: 4, SinceLastRead: true}))
218218 if tt.wantCode != 0 {
219219 requireCode(t, tt.wantCode, err)
220220 return
@@ -41,7 +41,7 @@ func TestMergeRequestServiceCreate(t *testing.T) {
41 Title: "t", Description: "d", Source: "feat", Target: "main",41 Title: "t", Description: "d", Source: "feat", Target: "main",
42 }).Return(domain.MergeRequest{Number: 7, Source: "feat", Target: "main", State: domain.MRStateOpen}, tt.usecase).Once()42 }).Return(domain.MergeRequest{Number: 7, Source: "feat", Target: "main", State: domain.MRStateOpen}, tt.usecase).Once()
43 }43 }
44- resp, err := (&mergeRequestService{mrs}).CreateMergeRequest(ownerCtx(), connect.NewRequest(&pb.CreateMergeRequestRequest{44+ resp, err := (&mergeRequestService{mrs: mrs}).CreateMergeRequest(ownerCtx(), connect.NewRequest(&pb.CreateMergeRequestRequest{
45 Repo: tt.repo, Title: "t", Description: "d", SourceBranch: "feat", TargetBranch: "main",45 Repo: tt.repo, Title: "t", Description: "d", SourceBranch: "feat", TargetBranch: "main",
46 }))46 }))
47 if tt.wantCode != 0 {47 if tt.wantCode != 0 {
@@ -57,7 +57,7 @@ func TestMergeRequestServiceCreate(t *testing.T) {
57 57
58 func TestMergeRequestServiceListRejectsUnknownState(t *testing.T) {58 func TestMergeRequestServiceListRejectsUnknownState(t *testing.T) {
59 t.Parallel()59 t.Parallel()
60- _, err := (&mergeRequestService{mocks.NewMergeRequestService(t)}).ListMergeRequests(ownerCtx(),60+ _, err := (&mergeRequestService{mrs: mocks.NewMergeRequestService(t)}).ListMergeRequests(ownerCtx(),
61 connect.NewRequest(&pb.ListMergeRequestsRequest{Repo: "demo", State: pb.MergeRequestState(99)}))61 connect.NewRequest(&pb.ListMergeRequestsRequest{Repo: "demo", State: pb.MergeRequestState(99)}))
62 requireCode(t, connect.CodeInvalidArgument, err)62 requireCode(t, connect.CodeInvalidArgument, err)
63 }63 }
@@ -68,7 +68,7 @@ func TestMergeRequestServiceListFiltersByState(t *testing.T) {
68 mrs.EXPECT().List(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), ports.ListMergeRequestsParams{68 mrs.EXPECT().List(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), ports.ListMergeRequestsParams{
69 State: domain.MRStateMerged, PageSize: 3, PageToken: "tok",69 State: domain.MRStateMerged, PageSize: 3, PageToken: "tok",
70 }).Return(ports.MergeRequestPage{MergeRequests: []domain.MergeRequest{{Number: 1}}, NextPageToken: "n"}, nil).Once()70 }).Return(ports.MergeRequestPage{MergeRequests: []domain.MergeRequest{{Number: 1}}, NextPageToken: "n"}, nil).Once()
71- resp, err := (&mergeRequestService{mrs}).ListMergeRequests(ownerCtx(), connect.NewRequest(&pb.ListMergeRequestsRequest{71+ resp, err := (&mergeRequestService{mrs: mrs}).ListMergeRequests(ownerCtx(), connect.NewRequest(&pb.ListMergeRequestsRequest{
72 Repo: "demo", State: pb.MergeRequestState_MERGE_REQUEST_STATE_MERGED, PageSize: 3, PageToken: "tok",72 Repo: "demo", State: pb.MergeRequestState_MERGE_REQUEST_STATE_MERGED, PageSize: 3, PageToken: "tok",
73 }))73 }))
74 require.NoError(t, err)74 require.NoError(t, err)
@@ -99,7 +99,7 @@ func TestMergeRequestServiceAddComment(t *testing.T) {
99 mrs.EXPECT().AddComment(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(2), mock.Anything).99 mrs.EXPECT().AddComment(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(2), mock.Anything).
100 Return(domain.Comment{ID: 5}, tt.usecase).Once()100 Return(domain.Comment{ID: 5}, tt.usecase).Once()
101 }101 }
102- resp, err := (&mergeRequestService{mrs}).AddComment(ownerCtx(), connect.NewRequest(&pb.AddCommentRequest{102+ resp, err := (&mergeRequestService{mrs: mrs}).AddComment(ownerCtx(), connect.NewRequest(&pb.AddCommentRequest{
103 Repo: "demo", Number: 2, Path: "f.go", Line: 3, Side: tt.side, Text: "hi",103 Repo: "demo", Number: 2, Path: "f.go", Line: 3, Side: tt.side, Text: "hi",
104 }))104 }))
105 if tt.wantCode != 0 {105 if tt.wantCode != 0 {
@@ -120,7 +120,7 @@ func TestMergeRequestServiceResolveCommentMapsUnissuedIDsToZero(t *testing.T) {
120 mrs := mocks.NewMergeRequestService(t)120 mrs := mocks.NewMergeRequestService(t)
121 mrs.EXPECT().ResolveComment(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1), int64(0), true).121 mrs.EXPECT().ResolveComment(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1), int64(0), true).
122 Return(domain.Comment{}, domain.ErrNotFound).Once()122 Return(domain.Comment{}, domain.ErrNotFound).Once()
123- _, err := (&mergeRequestService{mrs}).ResolveComment(ownerCtx(), connect.NewRequest(&pb.ResolveCommentRequest{123+ _, err := (&mergeRequestService{mrs: mrs}).ResolveComment(ownerCtx(), connect.NewRequest(&pb.ResolveCommentRequest{
124 Repo: "demo", Number: 1, CommentId: id, Resolved: true,124 Repo: "demo", Number: 1, CommentId: id, Resolved: true,
125 }))125 }))
126 requireCode(t, connect.CodeNotFound, err)126 requireCode(t, connect.CodeNotFound, err)
@@ -154,7 +154,7 @@ func TestMergeRequestServiceMerge(t *testing.T) {
154 return p.DeleteSource == tt.req.GetDeleteSourceBranch() && (tt.req.GetHeadId() == "") == p.HeadID.IsZero()154 return p.DeleteSource == tt.req.GetDeleteSourceBranch() && (tt.req.GetHeadId() == "") == p.HeadID.IsZero()
155 })).Return(ports.MergeResult{MergeRequest: domain.MergeRequest{Number: 1}, SourceDeleted: true}, tt.usecase).Once()155 })).Return(ports.MergeResult{MergeRequest: domain.MergeRequest{Number: 1}, SourceDeleted: true}, tt.usecase).Once()
156 }156 }
157- resp, err := (&mergeRequestService{mrs}).MergeMergeRequest(ownerCtx(), connect.NewRequest(tt.req))157+ resp, err := (&mergeRequestService{mrs: mrs}).MergeMergeRequest(ownerCtx(), connect.NewRequest(tt.req))
158 if tt.wantCode != 0 {158 if tt.wantCode != 0 {
159 requireCode(t, tt.wantCode, err)159 requireCode(t, tt.wantCode, err)
160 return160 return
@@ -171,20 +171,20 @@ func TestMergeRequestServiceCloseAndMarkRead(t *testing.T) {
171 t.Parallel()171 t.Parallel()
172 mrs := mocks.NewMergeRequestService(t)172 mrs := mocks.NewMergeRequestService(t)
173 mrs.EXPECT().Close(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(9)).Return(domain.MergeRequest{}, domain.ErrNotFound).Once()173 mrs.EXPECT().Close(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(9)).Return(domain.MergeRequest{}, domain.ErrNotFound).Once()
174- _, err := (&mergeRequestService{mrs}).CloseMergeRequest(ownerCtx(), connect.NewRequest(&pb.CloseMergeRequestRequest{Repo: "demo", Number: 9}))174+ _, err := (&mergeRequestService{mrs: mrs}).CloseMergeRequest(ownerCtx(), connect.NewRequest(&pb.CloseMergeRequestRequest{Repo: "demo", Number: 9}))
175 requireCode(t, connect.CodeNotFound, err)175 requireCode(t, connect.CodeNotFound, err)
176 })176 })
177 t.Run("close ok", func(t *testing.T) {177 t.Run("close ok", func(t *testing.T) {
178 t.Parallel()178 t.Parallel()
179 mrs := mocks.NewMergeRequestService(t)179 mrs := mocks.NewMergeRequestService(t)
180 mrs.EXPECT().Close(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(9)).Return(domain.MergeRequest{Number: 9, State: domain.MRStateClosed}, nil).Once()180 mrs.EXPECT().Close(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(9)).Return(domain.MergeRequest{Number: 9, State: domain.MRStateClosed}, nil).Once()
181- resp, err := (&mergeRequestService{mrs}).CloseMergeRequest(ownerCtx(), connect.NewRequest(&pb.CloseMergeRequestRequest{Repo: "demo", Number: 9}))181+ resp, err := (&mergeRequestService{mrs: mrs}).CloseMergeRequest(ownerCtx(), connect.NewRequest(&pb.CloseMergeRequestRequest{Repo: "demo", Number: 9}))
182 require.NoError(t, err)182 require.NoError(t, err)
183 assert.Equal(t, pb.MergeRequestState_MERGE_REQUEST_STATE_CLOSED, resp.Msg.GetMergeRequest().GetState())183 assert.Equal(t, pb.MergeRequestState_MERGE_REQUEST_STATE_CLOSED, resp.Msg.GetMergeRequest().GetState())
184 })184 })
185 t.Run("mark read rejects a short head", func(t *testing.T) {185 t.Run("mark read rejects a short head", func(t *testing.T) {
186 t.Parallel()186 t.Parallel()
187- _, err := (&mergeRequestService{mocks.NewMergeRequestService(t)}).MarkRead(ownerCtx(),187+ _, err := (&mergeRequestService{mrs: mocks.NewMergeRequestService(t)}).MarkRead(ownerCtx(),
188 connect.NewRequest(&pb.MarkReadRequest{Repo: "demo", Number: 1, HeadId: "abc"}))188 connect.NewRequest(&pb.MarkReadRequest{Repo: "demo", Number: 1, HeadId: "abc"}))
189 requireCode(t, connect.CodeInvalidArgument, err)189 requireCode(t, connect.CodeInvalidArgument, err)
190 })190 })
@@ -192,7 +192,7 @@ func TestMergeRequestServiceCloseAndMarkRead(t *testing.T) {
192 t.Parallel()192 t.Parallel()
193 mrs := mocks.NewMergeRequestService(t)193 mrs := mocks.NewMergeRequestService(t)
194 mrs.EXPECT().MarkRead(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1), domain.ObjectID{}).Return(domain.ObjectID{}, domain.ErrUnauthenticated).Once()194 mrs.EXPECT().MarkRead(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1), domain.ObjectID{}).Return(domain.ObjectID{}, domain.ErrUnauthenticated).Once()
195- _, err := (&mergeRequestService{mrs}).MarkRead(ownerCtx(), connect.NewRequest(&pb.MarkReadRequest{Repo: "demo", Number: 1}))195+ _, err := (&mergeRequestService{mrs: mrs}).MarkRead(ownerCtx(), connect.NewRequest(&pb.MarkReadRequest{Repo: "demo", Number: 1}))
196 requireCode(t, connect.CodeUnauthenticated, err)196 requireCode(t, connect.CodeUnauthenticated, err)
197 })197 })
198 }198 }
@@ -214,7 +214,7 @@ func TestMergeRequestServiceGet(t *testing.T) {
214 mrs := mocks.NewMergeRequestService(t)214 mrs := mocks.NewMergeRequestService(t)
215 mrs.EXPECT().Get(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(4), ports.GetMergeRequestOptions{SinceLastRead: true}).215 mrs.EXPECT().Get(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(4), ports.GetMergeRequestOptions{SinceLastRead: true}).
216 Return(ports.MergeRequestDetail{MergeRequest: domain.MergeRequest{Number: 4}, CommitsTruncated: true}, tt.usecase).Once()216 Return(ports.MergeRequestDetail{MergeRequest: domain.MergeRequest{Number: 4}, CommitsTruncated: true}, tt.usecase).Once()
217- resp, err := (&mergeRequestService{mrs}).GetMergeRequest(ownerCtx(), connect.NewRequest(&pb.GetMergeRequestRequest{Repo: "demo", Number: 4, SinceLastRead: true}))217+ resp, err := (&mergeRequestService{mrs: mrs}).GetMergeRequest(ownerCtx(), connect.NewRequest(&pb.GetMergeRequestRequest{Repo: "demo", Number: 4, SinceLastRead: true}))
218 if tt.wantCode != 0 {218 if tt.wantCode != 0 {
219 requireCode(t, tt.wantCode, err)219 requireCode(t, tt.wantCode, err)
220 return220 return

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 43

↑↓ moveenter openesc close