detached f2f0e1b

refactor(ports): declare every cross-layer contract and its types in ports

- use-case interfaces (RepoService, TokenService, BrowseService, EventService,
  MergeRequestService, SessionService, WebSessionService, AuthService,
  PushService, PushHookService, SigningKeyService, RepoLookup, HookTokens,
  FailureLimiter, MaintenanceScheduler, Revalidator) move from rpc, githttp,
  web and app into ports; duplicates merge into one
- their params, pages and results move from app to ports; CreateParams and
  UpdateParams become CreateRepoParams and UpdateRepoParams, app.CommitPage
  becomes HistoryPage
- adapters no longer import app; app asserts it implements the ports
- all mocks are generated in internal/ports/mocks
commit
f2f0e1bf6ef9344c8f07974cf655c13a1f5e37f4
author
brice · · 2026-10-12 04:42 UTC
parent
ffb5f8d
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
91 files · +2065 -2439
M .hexcheck.yaml +4 -2
@@ -40,6 +40,9 @@ components:
40 role: ignore40 role: ignore
41 paths:41 paths:
42 - internal/readertest/**42 - internal/readertest/**
43+# Cross-layer contracts live in ports: adapters and app import ports and domain,
44+# never each other. hexcheck has no rule for an adapter importing app (only the
45+# reverse, no-infra-imports-in-usecase), so that direction is kept by review.
43 rules:46 rules:
44 no-adapter-imports-in-core: error47 no-adapter-imports-in-core: error
45 no-infra-imports-in-usecase: error48 no-infra-imports-in-usecase: error
@@ -53,8 +56,7 @@ rules:
53 mocking:56 mocking:
54 generatedMockPaths:57 generatedMockPaths:
55 - internal/ports/mocks/**58 - internal/ports/mocks/**
56- - internal/app/mocks/**59+ - internal/adapters/web/mocks/**
57- - internal/adapters/*/mocks/**
58 generatedMockNamePatterns:60 generatedMockNamePatterns:
59 - '{{Interface}}'61 - '{{Interface}}'
60 allow:62 allow:
M .mockery.yml +3 -10
@@ -1,5 +1,6 @@
1-# Mocks of the ports and of the consumer-side interfaces of the adapters and1+# Mocks of the ports, generated in internal/ports/mocks, and of the generated
2-# the use cases. Regenerate with: go generate ./internal/ports2+# Connect clients the web adapter calls. Regenerate with:
3+# go generate ./internal/ports
3 template: testify4 template: testify
4 _anchors:5 _anchors:
5 mocks: &mocks6 mocks: &mocks
@@ -11,14 +12,6 @@ _anchors:
11 packages:12 packages:
12 bnema.dev/ermite/internal/ports:13 bnema.dev/ermite/internal/ports:
13 config: *mocks14 config: *mocks
14- bnema.dev/ermite/internal/adapters/githttp:
15- config: *mocks
16- bnema.dev/ermite/internal/adapters/rpc:
17- config: *mocks
18- bnema.dev/ermite/internal/app:
19- config: *mocks
20- bnema.dev/ermite/internal/adapters/web:
21- config: *mocks
22 bnema.dev/ermite-api/gen/ermite/ermiteconnect:15 bnema.dev/ermite-api/gen/ermite/ermiteconnect:
23 config:16 config:
24 include-auto-generated: true17 include-auto-generated: true
M cmd/ermite/api_helpers_test.go +2 -2
@@ -19,8 +19,8 @@ import (
19 19
20 pb "bnema.dev/ermite-api/gen/ermite"20 pb "bnema.dev/ermite-api/gen/ermite"
21 "bnema.dev/ermite-api/gen/ermite/ermiteconnect"21 "bnema.dev/ermite-api/gen/ermite/ermiteconnect"
22- "bnema.dev/ermite/internal/app"
23 "bnema.dev/ermite/internal/domain"22 "bnema.dev/ermite/internal/domain"
23+ "bnema.dev/ermite/internal/ports"
24 )24 )
25 25
26 // clients are the generated Connect clients of one caller.26 // clients are the generated Connect clients of one caller.
@@ -105,7 +105,7 @@ func codeName(err error) string {
105 // commit per file to its main branch with git.105 // commit per file to its main branch with git.
106 func (h *harness) seed(name string, vis domain.Visibility, files ...string) {106 func (h *harness) seed(name string, vis domain.Visibility, files ...string) {
107 h.t.Helper()107 h.t.Helper()
108- _, err := h.forge.Repos.Create(h.ctx, domain.Root(), app.CreateParams{Name: h.name(name), Visibility: vis})108+ _, err := h.forge.Repos.Create(h.ctx, domain.Root(), ports.CreateRepoParams{Name: h.name(name), Visibility: vis})
109 require.NoError(h.t, err)109 require.NoError(h.t, err)
110 if len(files) == 0 {110 if len(files) == 0 {
111 return111 return
M cmd/ermite/api_test.go +3 -3
@@ -24,9 +24,9 @@ import (
24 24
25 pb "bnema.dev/ermite-api/gen/ermite"25 pb "bnema.dev/ermite-api/gen/ermite"
26 "bnema.dev/ermite/internal/adapters/rpc"26 "bnema.dev/ermite/internal/adapters/rpc"
27- "bnema.dev/ermite/internal/app"
28 "bnema.dev/ermite/internal/clientlimit"27 "bnema.dev/ermite/internal/clientlimit"
29 "bnema.dev/ermite/internal/domain"28 "bnema.dev/ermite/internal/domain"
29+ "bnema.dev/ermite/internal/ports"
30 )30 )
31 31
32 // principals of the access tables.32 // principals of the access tables.
@@ -652,8 +652,8 @@ func TestGetBlobStreamsInChunks(t *testing.T) {
652 }652 }
653 }653 }
654 654
655-func createParams(h *harness, name string, vis domain.Visibility) app.CreateParams {655+func createParams(h *harness, name string, vis domain.Visibility) ports.CreateRepoParams {
656- return app.CreateParams{Name: h.name(name), Visibility: vis}656+ return ports.CreateRepoParams{Name: h.name(name), Visibility: vis}
657 }657 }
658 658
659 // TestUpdateRepoDefaultBranch: HEAD moves with git symbolic-ref once the659 // TestUpdateRepoDefaultBranch: HEAD moves with git symbolic-ref once the
M cmd/ermite/e2e_helpers_test.go +2 -2
@@ -18,9 +18,9 @@ import (
18 "github.com/bnema/zerowrap"18 "github.com/bnema/zerowrap"
19 "github.com/stretchr/testify/require"19 "github.com/stretchr/testify/require"
20 20
21- "bnema.dev/ermite/internal/app"
22 "bnema.dev/ermite/internal/config"21 "bnema.dev/ermite/internal/config"
23 "bnema.dev/ermite/internal/domain"22 "bnema.dev/ermite/internal/domain"
23+ "bnema.dev/ermite/internal/ports"
24 )24 )
25 25
26 // harness is a forge wired like `ermite serve`, behind an httptest server,26 // harness is a forge wired like `ermite serve`, behind an httptest server,
@@ -186,7 +186,7 @@ func (h *harness) status(method, path string, hdr http.Header) int {
186 // token creates a stored token through the use case, as root.186 // token creates a stored token through the use case, as root.
187 func (h *harness) token(scopes []domain.Scope, repos ...string) string {187 func (h *harness) token(scopes []domain.Scope, repos ...string) string {
188 h.t.Helper()188 h.t.Helper()
189- p := app.CreateTokenParams{Label: "e2e", Scopes: scopes}189+ p := ports.CreateTokenParams{Label: "e2e", Scopes: scopes}
190 for _, r := range repos {190 for _, r := range repos {
191 p.Repos = append(p.Repos, h.name(r))191 p.Repos = append(p.Repos, h.name(r))
192 }192 }
M cmd/ermite/e2e_test.go +8 -8
@@ -20,8 +20,8 @@ import (
20 "github.com/stretchr/testify/assert"20 "github.com/stretchr/testify/assert"
21 "github.com/stretchr/testify/require"21 "github.com/stretchr/testify/require"
22 22
23- "bnema.dev/ermite/internal/app"
24 "bnema.dev/ermite/internal/domain"23 "bnema.dev/ermite/internal/domain"
24+ "bnema.dev/ermite/internal/ports"
25 )25 )
26 26
27 func scopes(s ...domain.Scope) []domain.Scope { return s }27 func scopes(s ...domain.Scope) []domain.Scope { return s }
@@ -29,7 +29,7 @@ func scopes(s ...domain.Scope) []domain.Scope { return s }
29 // publicRepo creates a public repository with one commit pushed by root.29 // publicRepo creates a public repository with one commit pushed by root.
30 func (h *harness) publicRepo(name string, files ...string) {30 func (h *harness) publicRepo(name string, files ...string) {
31 h.t.Helper()31 h.t.Helper()
32- _, err := h.forge.Repos.Create(h.ctx, domain.Root(), app.CreateParams{Name: h.name(name), Visibility: domain.VisibilityPublic})32+ _, err := h.forge.Repos.Create(h.ctx, domain.Root(), ports.CreateRepoParams{Name: h.name(name), Visibility: domain.VisibilityPublic})
33 require.NoError(h.t, err)33 require.NoError(h.t, err)
34 wt := h.workTree("main", files...)34 wt := h.workTree("main", files...)
35 h.mustGit(wt, "push", "-q", h.urlAuth(adminToken, "/"+name+".git"), "main")35 h.mustGit(wt, "push", "-q", h.urlAuth(adminToken, "/"+name+".git"), "main")
@@ -189,7 +189,7 @@ func TestAC2PushAuthorization(t *testing.T) {
189 assert.Contains(t, out, "Authentication failed")189 assert.Contains(t, out, "Authentication failed")
190 190
191 // Revoked token.191 // Revoked token.
192- created, err := h.forge.Tokens.Create(h.ctx, domain.Root(), app.CreateTokenParams{Label: "short", Scopes: scopes(domain.ScopeRepoWrite)})192+ created, err := h.forge.Tokens.Create(h.ctx, domain.Root(), ports.CreateTokenParams{Label: "short", Scopes: scopes(domain.ScopeRepoWrite)})
193 require.NoError(t, err)193 require.NoError(t, err)
194 next("before-revoke")194 next("before-revoke")
195 out, err = h.git(wt, "push", "-q", h.urlAuth(created.Secret, "/proj.git"), "main")195 out, err = h.git(wt, "push", "-q", h.urlAuth(created.Secret, "/proj.git"), "main")
@@ -570,20 +570,20 @@ func TestAC8TokenCreationThroughTheForge(t *testing.T) {
570 h := newHarness(t, harnessOpts{})570 h := newHarness(t, harnessOpts{})
571 ctx := context.Background()571 ctx := context.Background()
572 572
573- admin, err := h.forge.Tokens.Create(ctx, domain.Root(), app.CreateTokenParams{573+ admin, err := h.forge.Tokens.Create(ctx, domain.Root(), ports.CreateTokenParams{
574 Label: "limited admin", Scopes: scopes(domain.ScopeTokenAdmin, domain.ScopeRepoWrite), Repos: []domain.RepoName{h.name("a")},574 Label: "limited admin", Scopes: scopes(domain.ScopeTokenAdmin, domain.ScopeRepoWrite), Repos: []domain.RepoName{h.name("a")},
575 })575 })
576 require.NoError(t, err)576 require.NoError(t, err)
577 p, err := h.forge.Auth.Authenticate(ctx, admin.Secret)577 p, err := h.forge.Auth.Authenticate(ctx, admin.Secret)
578 require.NoError(t, err)578 require.NoError(t, err)
579 579
580- _, err = h.forge.Tokens.Create(ctx, p, app.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoAdmin), Repos: []domain.RepoName{h.name("a")}})580+ _, err = h.forge.Tokens.Create(ctx, p, ports.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoAdmin), Repos: []domain.RepoName{h.name("a")}})
581 assert.ErrorIs(t, err, domain.ErrForbidden, "scope escalation")581 assert.ErrorIs(t, err, domain.ErrForbidden, "scope escalation")
582- _, err = h.forge.Tokens.Create(ctx, p, app.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoWrite)})582+ _, err = h.forge.Tokens.Create(ctx, p, ports.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoWrite)})
583 assert.ErrorIs(t, err, domain.ErrForbidden, "repository list escalation")583 assert.ErrorIs(t, err, domain.ErrForbidden, "repository list escalation")
584- _, err = h.forge.Tokens.Create(ctx, p, app.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoWrite), Repos: []domain.RepoName{h.name("b")}})584+ _, err = h.forge.Tokens.Create(ctx, p, ports.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoWrite), Repos: []domain.RepoName{h.name("b")}})
585 assert.ErrorIs(t, err, domain.ErrForbidden, "another repository")585 assert.ErrorIs(t, err, domain.ErrForbidden, "another repository")
586- child, err := h.forge.Tokens.Create(ctx, p, app.CreateTokenParams{Label: "child", Scopes: scopes(domain.ScopeRepoWrite), Repos: []domain.RepoName{h.name("a")}})586+ child, err := h.forge.Tokens.Create(ctx, p, ports.CreateTokenParams{Label: "child", Scopes: scopes(domain.ScopeRepoWrite), Repos: []domain.RepoName{h.name("a")}})
587 require.NoError(t, err)587 require.NoError(t, err)
588 588
589 // The child works on its repository, and revoking the index entry kills it at once.589 // The child works on its repository, and revoking the index entry kills it at once.
M cmd/ermite/events_test.go +4 -3
@@ -21,6 +21,7 @@ import (
21 "bnema.dev/ermite/internal/adapters/rpc"21 "bnema.dev/ermite/internal/adapters/rpc"
22 "bnema.dev/ermite/internal/app"22 "bnema.dev/ermite/internal/app"
23 "bnema.dev/ermite/internal/domain"23 "bnema.dev/ermite/internal/domain"
24+ "bnema.dev/ermite/internal/ports"
24 )25 )
25 26
26 // watchWait is how long a test waits for an event that must arrive. Only27 // watchWait is how long a test waits for an event that must arrive. Only
@@ -222,7 +223,7 @@ func TestAC5NoPrivateEventsToAnonymous(t *testing.T) {
222 assert.Equal(t, "pub", ev.GetPushed().GetRepo(), name)223 assert.Equal(t, "pub", ev.GetPushed().GetRepo(), name)
223 }224 }
224 // Nothing about priv came before the sentinel, for either of them.225 // Nothing about priv came before the sentinel, for either of them.
225- _, err = h.forge.Repos.Create(h.ctx, domain.Root(), app.CreateParams{Name: h.name("sentinel"), Visibility: domain.VisibilityPublic})226+ _, err = h.forge.Repos.Create(h.ctx, domain.Root(), ports.CreateRepoParams{Name: h.name("sentinel"), Visibility: domain.VisibilityPublic})
226 require.NoError(t, err)227 require.NoError(t, err)
227 for name, w := range map[string]*stream{"anonymous": anon, "scoped": scoped} {228 for name, w := range map[string]*stream{"anonymous": anon, "scoped": scoped} {
228 assert.Equal(t, "sentinel", w.next(t, watchWait).GetRepoCreated().GetRepo().GetName(), name)229 assert.Equal(t, "sentinel", w.next(t, watchWait).GetRepoCreated().GetRepo().GetName(), name)
@@ -340,7 +341,7 @@ func TestServeShutdownEndsWatchStreams(t *testing.T) {
340 func TestWatchEndsWhenTheTokenIsRevoked(t *testing.T) {341 func TestWatchEndsWhenTheTokenIsRevoked(t *testing.T) {
341 h := newHarness(t, harnessOpts{revalidateEvery: 20 * time.Millisecond})342 h := newHarness(t, harnessOpts{revalidateEvery: 20 * time.Millisecond})
342 h.seed("priv", domain.VisibilityPrivate)343 h.seed("priv", domain.VisibilityPrivate)
343- created, err := h.forge.Tokens.Create(h.ctx, domain.Root(), app.CreateTokenParams{344+ created, err := h.forge.Tokens.Create(h.ctx, domain.Root(), ports.CreateTokenParams{
344 Label: "watcher", Scopes: scopes(domain.ScopeRepoRead),345 Label: "watcher", Scopes: scopes(domain.ScopeRepoRead),
345 })346 })
346 require.NoError(t, err)347 require.NoError(t, err)
@@ -396,7 +397,7 @@ func TestWatchLimits(t *testing.T) {
396 // order they were published.397 // order they were published.
397 func (h *harness) sentinel(t *testing.T, s *stream, name string) {398 func (h *harness) sentinel(t *testing.T, s *stream, name string) {
398 t.Helper()399 t.Helper()
399- _, err := h.forge.Repos.Create(h.ctx, domain.Root(), app.CreateParams{Name: h.name(name), Visibility: domain.VisibilityPublic})400+ _, err := h.forge.Repos.Create(h.ctx, domain.Root(), ports.CreateRepoParams{Name: h.name(name), Visibility: domain.VisibilityPublic})
400 require.NoError(t, err)401 require.NoError(t, err)
401 ev := s.next(t, watchWait)402 ev := s.next(t, watchWait)
402 assert.Equal(t, name, ev.GetRepoCreated().GetRepo().GetName(), "an event came before the sentinel: %v", ev)403 assert.Equal(t, name, ev.GetRepoCreated().GetRepo().GetName(), "an event came before the sentinel: %v", ev)
M cmd/ermite/gitreader_test.go +2 -2
@@ -10,9 +10,9 @@ import (
10 "github.com/stretchr/testify/assert"10 "github.com/stretchr/testify/assert"
11 "github.com/stretchr/testify/require"11 "github.com/stretchr/testify/require"
12 12
13- "bnema.dev/ermite/internal/app"
14 "bnema.dev/ermite/internal/config"13 "bnema.dev/ermite/internal/config"
15 "bnema.dev/ermite/internal/domain"14 "bnema.dev/ermite/internal/domain"
15+ "bnema.dev/ermite/internal/ports"
16 )16 )
17 17
18 // The suite runs with the reader of ERMITE_GIT_READER (ermite-git by default).18 // The suite runs with the reader of ERMITE_GIT_READER (ermite-git by default).
@@ -28,7 +28,7 @@ func TestGitReaderOfTheForgeAndReadAfterPush(t *testing.T) {
28 assert.Equal(t, want, h.forge.cfg.GitReader)28 assert.Equal(t, want, h.forge.cfg.GitReader)
29 assert.Equal(t, want == config.GitReaderErmiteGit, h.forge.reader != nil, "the ermite-git adapter exists only for ermite-git")29 assert.Equal(t, want == config.GitReaderErmiteGit, h.forge.reader != nil, "the ermite-git adapter exists only for ermite-git")
30 30
31- _, err := h.forge.Repos.Create(h.ctx, domain.Root(), app.CreateParams{31+ _, err := h.forge.Repos.Create(h.ctx, domain.Root(), ports.CreateRepoParams{
32 Name: h.name("fresh"), Visibility: domain.VisibilityPublic,32 Name: h.name("fresh"), Visibility: domain.VisibilityPublic,
33 })33 })
34 require.NoError(t, err)34 require.NoError(t, err)
M cmd/ermite/web.go +8 -5
@@ -21,6 +21,7 @@ import (
21 "bnema.dev/ermite/internal/clientlimit"21 "bnema.dev/ermite/internal/clientlimit"
22 "bnema.dev/ermite/internal/config"22 "bnema.dev/ermite/internal/config"
23 "bnema.dev/ermite/internal/domain"23 "bnema.dev/ermite/internal/domain"
24+ "bnema.dev/ermite/internal/ports"
24 )25 )
25 26
26 // newWeb builds the web pages on the API of this process: the generated27 // newWeb builds the web pages on the API of this process: the generated
@@ -96,15 +97,17 @@ func warnSharedClientKey(ctx context.Context, trusted []netip.Prefix, maxPerClie
96 "requests and one budget of failed authentications; set it to the proxy networks")97 "requests and one budget of failed authentications; set it to the proxy networks")
97 }98 }
98 99
99-// webSessions adapts the session use cases to web.SessionAuth: a valid token100+// webSessions adapts the session use cases to ports.WebSessionService: a valid
100-// gives the context of the owner, whose principal the API calls of the page101+// token gives the context of the owner, whose principal the API calls of the
101-// carry (rpc.API.InProcessClient).102+// page carry (rpc.API.InProcessClient).
102 type webSessions struct{ *app.Sessions }103 type webSessions struct{ *app.Sessions }
103 104
105+var _ ports.WebSessionService = webSessions{}
106+
104 func (s webSessions) Login(ctx context.Context, nonce string) (string, time.Duration, error) {107 func (s webSessions) Login(ctx context.Context, nonce string) (string, time.Duration, error) {
105 token, expiresAt, err := s.Sessions.Login(ctx, nonce)108 token, expiresAt, err := s.Sessions.Login(ctx, nonce)
106 if errors.Is(err, domain.ErrUnauthenticated) {109 if errors.Is(err, domain.ErrUnauthenticated) {
107- return "", 0, web.ErrLinkRejected110+ return "", 0, ports.ErrLinkRejected
108 }111 }
109 if err != nil {112 if err != nil {
110 return "", 0, err113 return "", 0, err
@@ -118,7 +121,7 @@ func (s webSessions) Login(ctx context.Context, nonce string) (string, time.Dura
118 lifetime = d.Round(time.Second)121 lifetime = d.Round(time.Second)
119 }122 }
120 if lifetime < time.Second {123 if lifetime < time.Second {
121- return "", 0, web.ErrLinkRejected124+ return "", 0, ports.ErrLinkRejected
122 }125 }
123 return token, lifetime, nil126 return token, lifetime, nil
124 }127 }
M cmd/ermite/web_commits_test.go +2 -2
@@ -14,8 +14,8 @@ import (
14 "github.com/stretchr/testify/assert"14 "github.com/stretchr/testify/assert"
15 "github.com/stretchr/testify/require"15 "github.com/stretchr/testify/require"
16 16
17- "bnema.dev/ermite/internal/app"
18 "bnema.dev/ermite/internal/domain"17 "bnema.dev/ermite/internal/domain"
18+ "bnema.dev/ermite/internal/ports"
19 )19 )
20 20
21 // diffFixture is the repository of the commit pages: one commit for each21 // diffFixture is the repository of the commit pages: one commit for each
@@ -27,7 +27,7 @@ type diffFixture struct {
27 // createRepo creates a public repository for the pages.27 // createRepo creates a public repository for the pages.
28 func (h *harness) createRepo(name string, vis domain.Visibility) {28 func (h *harness) createRepo(name string, vis domain.Visibility) {
29 h.t.Helper()29 h.t.Helper()
30- _, err := h.forge.Repos.Create(h.ctx, domain.Root(), app.CreateParams{Name: h.name(name), Description: "Diffs", Visibility: vis})30+ _, err := h.forge.Repos.Create(h.ctx, domain.Root(), ports.CreateRepoParams{Name: h.name(name), Description: "Diffs", Visibility: vis})
31 require.NoError(h.t, err)31 require.NoError(h.t, err)
32 }32 }
33 33
M cmd/ermite/web_events_test.go +5 -4
@@ -17,6 +17,7 @@ import (
17 17
18 "bnema.dev/ermite/internal/app"18 "bnema.dev/ermite/internal/app"
19 "bnema.dev/ermite/internal/domain"19 "bnema.dev/ermite/internal/domain"
20+ "bnema.dev/ermite/internal/ports"
20 )21 )
21 22
22 // sseEvent is one event read from /-/events.23 // sseEvent is one event read from /-/events.
@@ -126,7 +127,7 @@ func TestWebEventsHome(t *testing.T) {
126 assert.Contains(t, ev.data, `href="/pub"`)127 assert.Contains(t, ev.data, `href="/pub"`)
127 128
128 // A new repository is a row; so is a repository created by a push.129 // A new repository is a row; so is a repository created by a push.
129- _, err := h.forge.Repos.Create(h.ctx, domain.Root(), app.CreateParams{Name: h.name("fresh"), Description: "a <b>fresh</b> one", Visibility: domain.VisibilityPublic})130+ _, err := h.forge.Repos.Create(h.ctx, domain.Root(), ports.CreateRepoParams{Name: h.name("fresh"), Description: "a <b>fresh</b> one", Visibility: domain.VisibilityPublic})
130 require.NoError(t, err)131 require.NoError(t, err)
131 ev = s.next(t, watchWait)132 ev = s.next(t, watchWait)
132 assert.Equal(t, "home-list", ev.name)133 assert.Equal(t, "home-list", ev.name)
@@ -136,7 +137,7 @@ func TestWebEventsHome(t *testing.T) {
136 137
137 // A repository that turns private is removed from the anonymous list.138 // A repository that turns private is removed from the anonymous list.
138 priv := domain.VisibilityPrivate139 priv := domain.VisibilityPrivate
139- _, err = h.forge.Repos.Update(h.ctx, domain.Root(), h.name("fresh"), app.UpdateParams{Visibility: &priv})140+ _, err = h.forge.Repos.Update(h.ctx, domain.Root(), h.name("fresh"), ports.UpdateRepoParams{Visibility: &priv})
140 require.NoError(t, err)141 require.NoError(t, err)
141 ev = s.next(t, watchWait)142 ev = s.next(t, watchWait)
142 assert.Equal(t, "home-list", ev.name)143 assert.Equal(t, "home-list", ev.name)
@@ -286,7 +287,7 @@ func TestWebEventsPrivate(t *testing.T) {
286 wt := h.workTree("main", "a.txt")287 wt := h.workTree("main", "a.txt")
287 h.mustGit(wt, "push", "-q", h.urlAuth(adminToken, "/priv.git"), "main")288 h.mustGit(wt, "push", "-q", h.urlAuth(adminToken, "/priv.git"), "main")
288 desc := "secret change"289 desc := "secret change"
289- _, err := h.forge.Repos.Update(h.ctx, domain.Root(), h.name("priv"), app.UpdateParams{Description: &desc})290+ _, err := h.forge.Repos.Update(h.ctx, domain.Root(), h.name("priv"), ports.UpdateRepoParams{Description: &desc})
290 require.NoError(t, err)291 require.NoError(t, err)
291 // Made public later, it does not reveal what happened while it was292 // Made public later, it does not reveal what happened while it was
292 // private; the update that makes it public is shown.293 // private; the update that makes it public is shown.
@@ -303,7 +304,7 @@ func TestWebEventsPrivate(t *testing.T) {
303 // public repository and requires its row to be the next event.304 // public repository and requires its row to be the next event.
304 func (h *harness) sentinelSSE(t *testing.T, s *sseStream, name string) {305 func (h *harness) sentinelSSE(t *testing.T, s *sseStream, name string) {
305 t.Helper()306 t.Helper()
306- _, err := h.forge.Repos.Create(h.ctx, domain.Root(), app.CreateParams{Name: h.name(name), Visibility: domain.VisibilityPublic})307+ _, err := h.forge.Repos.Create(h.ctx, domain.Root(), ports.CreateRepoParams{Name: h.name(name), Visibility: domain.VisibilityPublic})
307 require.NoError(t, err)308 require.NoError(t, err)
308 ev := s.next(t, watchWait)309 ev := s.next(t, watchWait)
309 assert.Contains(t, ev.data, `id="repo-`+name+`"`, "an event came before the sentinel: %s", ev.data)310 assert.Contains(t, ev.data, `id="repo-`+name+`"`, "an event came before the sentinel: %s", ev.data)
M cmd/ermite/web_files_test.go +2 -2
@@ -16,8 +16,8 @@ import (
16 "github.com/stretchr/testify/assert"16 "github.com/stretchr/testify/assert"
17 "github.com/stretchr/testify/require"17 "github.com/stretchr/testify/require"
18 18
19- "bnema.dev/ermite/internal/app"
20 "bnema.dev/ermite/internal/domain"19 "bnema.dev/ermite/internal/domain"
20+ "bnema.dev/ermite/internal/ports"
21 )21 )
22 22
23 // Trojan Source: a right-to-left override and an isolate around code.23 // Trojan Source: a right-to-left override and an isolate around code.
@@ -37,7 +37,7 @@ func tinyPNG(t *testing.T) []byte {
37 // tag with a slash.37 // tag with a slash.
38 func (h *harness) webFilesRepo(name string) {38 func (h *harness) webFilesRepo(name string) {
39 h.t.Helper()39 h.t.Helper()
40- _, err := h.forge.Repos.Create(h.ctx, domain.Root(), app.CreateParams{40+ _, err := h.forge.Repos.Create(h.ctx, domain.Root(), ports.CreateRepoParams{
41 Name: h.name(name), Description: "Files of every kind", Visibility: domain.VisibilityPublic,41 Name: h.name(name), Description: "Files of every kind", Visibility: domain.VisibilityPublic,
42 })42 })
43 require.NoError(h.t, err)43 require.NoError(h.t, err)
M cmd/ermite/web_mr_test.go +2 -2
@@ -13,8 +13,8 @@ import (
13 "github.com/stretchr/testify/assert"13 "github.com/stretchr/testify/assert"
14 "github.com/stretchr/testify/require"14 "github.com/stretchr/testify/require"
15 15
16- "bnema.dev/ermite/internal/app"
17 "bnema.dev/ermite/internal/domain"16 "bnema.dev/ermite/internal/domain"
17+ "bnema.dev/ermite/internal/ports"
18 )18 )
19 19
20 // mrRepo is a public repository "proj" with README.md on main and a branch20 // mrRepo is a public repository "proj" with README.md on main and a branch
@@ -466,7 +466,7 @@ func TestWebMergeRequestLimitIsFriendly(t *testing.T) {
466 token := r.signIn(t)466 token := r.signIn(t)
467 r.cli(t, adminToken, "mr", "create", "proj", "--source", "feat/search", "--title", "t").ok(t)467 r.cli(t, adminToken, "mr", "create", "proj", "--source", "feat/search", "--title", "t").ok(t)
468 for i := 0; i < 1000; i++ {468 for i := 0; i < 1000; i++ {
469- _, err := r.forge.MergeRequests.AddComment(r.ctx, domain.Root(), r.name("proj"), 1, app.AddCommentParams{Text: "n"})469+ _, err := r.forge.MergeRequests.AddComment(r.ctx, domain.Root(), r.name("proj"), 1, ports.AddCommentParams{Text: "n"})
470 require.NoError(t, err)470 require.NoError(t, err)
471 }471 }
472 full := b.form("/proj/merge-requests/1/comments", token, url.Values{"text": {"one more"}})472 full := b.form("/proj/merge-requests/1/comments", token, url.Values{"text": {"one more"}})
M cmd/ermite/web_session_test.go +2 -2
@@ -19,8 +19,8 @@ import (
19 "github.com/stretchr/testify/require"19 "github.com/stretchr/testify/require"
20 20
21 pb "bnema.dev/ermite-api/gen/ermite"21 pb "bnema.dev/ermite-api/gen/ermite"
22- "bnema.dev/ermite/internal/app"
23 "bnema.dev/ermite/internal/domain"22 "bnema.dev/ermite/internal/domain"
23+ "bnema.dev/ermite/internal/ports"
24 )24 )
25 25
26 const (26 const (
@@ -311,7 +311,7 @@ func TestWebRevokingAnOwnerTokenSignsEveryoneOut(t *testing.T) {
311 311
312 // The cap: a link made by a token that expires in an hour gives a cookie,312 // The cap: a link made by a token that expires in an hour gives a cookie,
313 // and a session, of an hour.313 // and a session, of an hour.
314- soon, err := c.forge.Tokens.Create(c.ctx, domain.Root(), app.CreateTokenParams{314+ soon, err := c.forge.Tokens.Create(c.ctx, domain.Root(), ports.CreateTokenParams{
315 Label: "soon", Scopes: domain.AllScopes(), ExpiresAt: time.Now().Add(time.Hour),315 Label: "soon", Scopes: domain.AllScopes(), ExpiresAt: time.Now().Add(time.Hour),
316 })316 })
317 require.NoError(t, err)317 require.NoError(t, err)
M cmd/ermite/web_test.go +3 -3
@@ -16,8 +16,8 @@ import (
16 "github.com/stretchr/testify/assert"16 "github.com/stretchr/testify/assert"
17 "github.com/stretchr/testify/require"17 "github.com/stretchr/testify/require"
18 18
19- "bnema.dev/ermite/internal/app"
20 "bnema.dev/ermite/internal/domain"19 "bnema.dev/ermite/internal/domain"
20+ "bnema.dev/ermite/internal/ports"
21 )21 )
22 22
23 var updateGolden = flag.Bool("update", false, "rewrite the golden files of the web tests")23 var updateGolden = flag.Bool("update", false, "rewrite the golden files of the web tests")
@@ -30,7 +30,7 @@ const webFixtureReadme = "# Demo\n\nA small *demo* with a [guide](docs/guide.md)
30 // branches (one with a slash), two tags and a description.30 // branches (one with a slash), two tags and a description.
31 func (h *harness) webRepo(name string) {31 func (h *harness) webRepo(name string) {
32 h.t.Helper()32 h.t.Helper()
33- _, err := h.forge.Repos.Create(h.ctx, domain.Root(), app.CreateParams{33+ _, err := h.forge.Repos.Create(h.ctx, domain.Root(), ports.CreateRepoParams{
34 Name: h.name(name), Description: "A demo repository", Visibility: domain.VisibilityPublic,34 Name: h.name(name), Description: "A demo repository", Visibility: domain.VisibilityPublic,
35 })35 })
36 require.NoError(h.t, err)36 require.NoError(h.t, err)
@@ -162,7 +162,7 @@ func TestWebPrivateRepoIsInvisible(t *testing.T) {
162 // AC4, end to end: a README that attacks the page is neutralised.162 // AC4, end to end: a README that attacks the page is neutralised.
163 func TestWebReadmeIsNeutralised(t *testing.T) {163 func TestWebReadmeIsNeutralised(t *testing.T) {
164 h := newHarness(t, harnessOpts{})164 h := newHarness(t, harnessOpts{})
165- _, err := h.forge.Repos.Create(h.ctx, domain.Root(), app.CreateParams{Name: h.name("evil"), Visibility: domain.VisibilityPublic})165+ _, err := h.forge.Repos.Create(h.ctx, domain.Root(), ports.CreateRepoParams{Name: h.name("evil"), Visibility: domain.VisibilityPublic})
166 require.NoError(t, err)166 require.NoError(t, err)
167 wt := h.t.TempDir()167 wt := h.t.TempDir()
168 h.mustGit(wt, "init", "-q", "--initial-branch=main")168 h.mustGit(wt, "init", "-q", "--initial-branch=main")
M internal/adapters/githttp/handler.go +8 -44
@@ -61,56 +61,18 @@ const (
61 serviceReceive = "git-receive-pack"61 serviceReceive = "git-receive-pack"
62 )62 )
63 63
64-// Authenticator turns the secret of a request into a principal. It returns
65-// domain.ErrUnauthenticated for a secret that is not valid. app.Auth fits.
66-type Authenticator interface {
67- Authenticate(ctx context.Context, secret string) (domain.Principal, error)
68-}
69-
70-// Repos is the read side of the repository use cases. A repository the
71-// principal may not read is domain.ErrNotFound. app.Repos fits.
72-type Repos interface {
73- Get(ctx context.Context, p domain.Principal, name domain.RepoName) (domain.Repo, error)
74-}
75-
76-// Pushes opens a repository for a push: it is domain.ErrUnauthenticated for an
77-// anonymous caller, domain.ErrNotFound for a repository the caller may not
78-// write and cannot see (or that is missing and may not be created),
79-// domain.ErrForbidden for one it can see but not write. With create, a missing
80-// repository the principal may write is created. app.Pushes fits.
81-type Pushes interface {
82- Open(ctx context.Context, p domain.Principal, name domain.RepoName, create bool) error
83-}
84-
85-// HookTokens hands out the per-request tokens the hooks of a push present
86-// back to ermite, and the socket they present them on.
87-type HookTokens interface {
88- // Register binds a new token to the repository and principal of a push.
89- // release revokes it and must be called when the request ends.
90- Register(ctx context.Context, repo domain.RepoName, p domain.Principal) (token string, release func())
91- // Socket is the path of the hook socket.
92- Socket() string
93-}
94-
95-// FailureLimiter counts the failed authentications per client, with the other
96-// adapters that authenticate. *clientlimit.Limiter fits.
97-type FailureLimiter interface {
98- Blocked(ctx context.Context, addr string) (bool, time.Duration)
99- Fail(ctx context.Context, addr string)
100-}
101-
102 // Config configures a Handler.64 // Config configures a Handler.
103 type Config struct {65 type Config struct {
104- Auth Authenticator66+ Auth ports.AuthService
105- Repos Repos67+ Repos ports.RepoLookup
106- Pushes Pushes68+ Pushes ports.PushService
107 Backend ports.GitBackend69 Backend ports.GitBackend
108 // Hooks provides hook tokens for pushes. It is required.70 // Hooks provides hook tokens for pushes. It is required.
109- Hooks HookTokens71+ Hooks ports.HookTokens
110 // TrustedProxies are the networks allowed to set X-Forwarded-For.72 // TrustedProxies are the networks allowed to set X-Forwarded-For.
111 TrustedProxies []netip.Prefix73 TrustedProxies []netip.Prefix
112 // Limiter counts failed authentications; nil builds a default one.74 // Limiter counts failed authentications; nil builds a default one.
113- Limiter FailureLimiter75+ Limiter ports.FailureLimiter
114 // MaxBodyBytes bounds the body of a push; 0 means DefaultMaxBodyBytes.76 // MaxBodyBytes bounds the body of a push; 0 means DefaultMaxBodyBytes.
115 // The body of a fetch is bounded by 64 MiB at most.77 // The body of a fetch is bounded by 64 MiB at most.
116 MaxBodyBytes int6478 MaxBodyBytes int64
@@ -148,7 +110,7 @@ const (
148 // Handler serves the smart HTTP routes of Patterns.110 // Handler serves the smart HTTP routes of Patterns.
149 type Handler struct {111 type Handler struct {
150 cfg Config112 cfg Config
151- limiter FailureLimiter113+ limiter ports.FailureLimiter
152 maxBody int64114 maxBody int64
153 115
154 upload, receive chan struct{} // slots of the backends, per service116 upload, receive chan struct{} // slots of the backends, per service
@@ -220,6 +182,7 @@ func parseRoute(r *http.Request) (route, int) {
220 }182 }
221 183
222 // ServeHTTP implements http.Handler.184 // ServeHTTP implements http.Handler.
185+//hexcheck:ignore suspicious-business-logic-in-adapter transport orchestration: the authorization decisions are the use cases'
223 func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {186 func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
224 var rt route187 var rt route
225 var name domain.RepoName188 var name domain.RepoName
@@ -411,6 +374,7 @@ func (h *Handler) acquire(ctx context.Context, w http.ResponseWriter, rt route,
411 }374 }
412 375
413 // serveBackend runs git http-backend for an authorized request.376 // serveBackend runs git http-backend for an authorized request.
377+//hexcheck:ignore suspicious-business-logic-in-adapter process and CGI plumbing: no business rule is decided here
414 func (h *Handler) serveBackend(w http.ResponseWriter, r *http.Request, rt route, name domain.RepoName, p domain.Principal, client string) {378 func (h *Handler) serveBackend(w http.ResponseWriter, r *http.Request, rt route, name domain.RepoName, p domain.Principal, client string) {
415 ctx := r.Context()379 ctx := r.Context()
416 logger := zerowrap.FromCtx(ctx)380 logger := zerowrap.FromCtx(ctx)
M internal/adapters/githttp/handler_test.go +10 -11
@@ -21,7 +21,6 @@ import (
21 "github.com/stretchr/testify/require"21 "github.com/stretchr/testify/require"
22 22
23 "bnema.dev/ermite/internal/adapters/githttp"23 "bnema.dev/ermite/internal/adapters/githttp"
24- ghmocks "bnema.dev/ermite/internal/adapters/githttp/mocks"
25 "bnema.dev/ermite/internal/clientlimit"24 "bnema.dev/ermite/internal/clientlimit"
26 "bnema.dev/ermite/internal/domain"25 "bnema.dev/ermite/internal/domain"
27 "bnema.dev/ermite/internal/ports"26 "bnema.dev/ermite/internal/ports"
@@ -43,10 +42,10 @@ func cgiScript(t *testing.T) string {
43 type rig struct {42 type rig struct {
44 h *githttp.Handler43 h *githttp.Handler
45 srv *httptest.Server44 srv *httptest.Server
46- pushes *ghmocks.Pushes45+ pushes *mocks.PushService
47 backend *mocks.GitBackend46 backend *mocks.GitBackend
48- hooks *ghmocks.HookTokens47+ hooks *mocks.HookTokens
49- auth *ghmocks.Authenticator48+ auth *mocks.AuthService
50 49
51 pushFlags []bool50 pushFlags []bool
52 backendErr error51 backendErr error
@@ -93,7 +92,7 @@ func (r *rig) hookedRepo() domain.RepoName {
93 92
94 // stubUseCases answers like the use cases behind the handler: a root and a93 // stubUseCases answers like the use cases behind the handler: a root and a
95 // reader token, a public repository "pub" and a private one "priv".94 // reader token, a public repository "pub" and a private one "priv".
96-func (r *rig) stubUseCases(repos *ghmocks.Repos) {95+func (r *rig) stubUseCases(repos *mocks.RepoLookup) {
97 r.auth.EXPECT().Authenticate(mock.Anything, mock.Anything).RunAndReturn(96 r.auth.EXPECT().Authenticate(mock.Anything, mock.Anything).RunAndReturn(
98 func(_ context.Context, secret string) (domain.Principal, error) {97 func(_ context.Context, secret string) (domain.Principal, error) {
99 r.mu.Lock()98 r.mu.Lock()
@@ -145,10 +144,10 @@ func (r *rig) stubUseCases(repos *ghmocks.Repos) {
145 func newRig(t *testing.T, mut func(*githttp.Config)) *rig {144 func newRig(t *testing.T, mut func(*githttp.Config)) *rig {
146 t.Helper()145 t.Helper()
147 r := &rig{146 r := &rig{
148- pushes: ghmocks.NewPushes(t), backend: mocks.NewGitBackend(t),147+ pushes: mocks.NewPushService(t), backend: mocks.NewGitBackend(t),
149- hooks: ghmocks.NewHookTokens(t), auth: ghmocks.NewAuthenticator(t),148+ hooks: mocks.NewHookTokens(t), auth: mocks.NewAuthService(t),
150 }149 }
151- repos := ghmocks.NewRepos(t)150+ repos := mocks.NewRepoLookup(t)
152 r.stubUseCases(repos)151 r.stubUseCases(repos)
153 cfg := githttp.Config{152 cfg := githttp.Config{
154 Auth: r.auth, Repos: repos, Pushes: r.pushes, Backend: r.backend, Hooks: r.hooks,153 Auth: r.auth, Repos: repos, Pushes: r.pushes, Backend: r.backend, Hooks: r.hooks,
@@ -511,7 +510,7 @@ func TestHandlerUsesTheFailureLimiter(t *testing.T) {
511 path := "/pub.git/info/refs?service=git-upload-pack"510 path := "/pub.git/info/refs?service=git-upload-pack"
512 t.Run("a blocked client is refused before authentication", func(t *testing.T) {511 t.Run("a blocked client is refused before authentication", func(t *testing.T) {
513 t.Parallel()512 t.Parallel()
514- limiter := ghmocks.NewFailureLimiter(t)513+ limiter := mocks.NewFailureLimiter(t)
515 limiter.EXPECT().Blocked(mock.Anything, "127.0.0.1").Return(true, 3*time.Second).Once()514 limiter.EXPECT().Blocked(mock.Anything, "127.0.0.1").Return(true, 3*time.Second).Once()
516 r := newRig(t, func(c *githttp.Config) { c.Limiter = limiter })515 r := newRig(t, func(c *githttp.Config) { c.Limiter = limiter })
517 code, _, hdr := r.do(t, "GET", path, "u", "root-secret", nil, nil)516 code, _, hdr := r.do(t, "GET", path, "u", "root-secret", nil, nil)
@@ -521,7 +520,7 @@ func TestHandlerUsesTheFailureLimiter(t *testing.T) {
521 })520 })
522 t.Run("an invalid secret is recorded as a failure", func(t *testing.T) {521 t.Run("an invalid secret is recorded as a failure", func(t *testing.T) {
523 t.Parallel()522 t.Parallel()
524- limiter := ghmocks.NewFailureLimiter(t)523+ limiter := mocks.NewFailureLimiter(t)
525 limiter.EXPECT().Blocked(mock.Anything, "127.0.0.1").Return(false, time.Duration(0)).Once()524 limiter.EXPECT().Blocked(mock.Anything, "127.0.0.1").Return(false, time.Duration(0)).Once()
526 limiter.EXPECT().Fail(mock.Anything, "127.0.0.1").Once()525 limiter.EXPECT().Fail(mock.Anything, "127.0.0.1").Once()
527 r := newRig(t, func(c *githttp.Config) { c.Limiter = limiter })526 r := newRig(t, func(c *githttp.Config) { c.Limiter = limiter })
@@ -530,7 +529,7 @@ func TestHandlerUsesTheFailureLimiter(t *testing.T) {
530 })529 })
531 t.Run("a valid secret is not a failure", func(t *testing.T) {530 t.Run("a valid secret is not a failure", func(t *testing.T) {
532 t.Parallel()531 t.Parallel()
533- limiter := ghmocks.NewFailureLimiter(t)532+ limiter := mocks.NewFailureLimiter(t)
534 limiter.EXPECT().Blocked(mock.Anything, "127.0.0.1").Return(false, time.Duration(0)).Once()533 limiter.EXPECT().Blocked(mock.Anything, "127.0.0.1").Return(false, time.Duration(0)).Once()
535 r := newRig(t, func(c *githttp.Config) { c.Limiter = limiter })534 r := newRig(t, func(c *githttp.Config) { c.Limiter = limiter })
536 code, _, _ := r.do(t, "GET", path, "u", "root-secret", nil, nil)535 code, _, _ := r.do(t, "GET", path, "u", "root-secret", nil, nil)
M internal/adapters/githttp/hooks.go +5 -12
@@ -19,17 +19,9 @@ import (
19 "github.com/bnema/zerowrap"19 "github.com/bnema/zerowrap"
20 20
21 "bnema.dev/ermite/internal/domain"21 "bnema.dev/ermite/internal/domain"
22+ "bnema.dev/ermite/internal/ports"
22 )23 )
23 24
24-// PushHooks is what the hook socket asks of the use cases. app.Pushes fits.
25-type PushHooks interface {
26- // Check decides a push before Git applies it; an error matching
27- // domain.ErrRejected refuses it, any other error refuses it as a failure.
28- Check(ctx context.Context, p domain.Principal, name domain.RepoName, updates []domain.RefUpdate) error
29- // Record handles an accepted push.
30- Record(ctx context.Context, p domain.Principal, name domain.RepoName, updates []domain.RefUpdate) error
31-}
32-
33 // readTimeout bounds how long a connection may take to send its request line.25 // readTimeout bounds how long a connection may take to send its request line.
34 const readTimeout = 30 * time.Second26 const readTimeout = 30 * time.Second
35 27
@@ -46,7 +38,7 @@ type hookEntry struct {
46 // socket the hooks call back on. It implements HookTokens.38 // socket the hooks call back on. It implements HookTokens.
47 type HookServer struct {39 type HookServer struct {
48 path string40 path string
49- hooks PushHooks41+ hooks ports.PushHookService
50 42
51 mu sync.RWMutex43 mu sync.RWMutex
52 tokens map[string]hookEntry44 tokens map[string]hookEntry
@@ -57,10 +49,10 @@ type HookServer struct {
57 wg sync.WaitGroup49 wg sync.WaitGroup
58 }50 }
59 51
60-var _ HookTokens = (*HookServer)(nil)52+var _ ports.HookTokens = (*HookServer)(nil)
61 53
62 // NewHookServer builds a server for the socket at path. Call Serve to listen.54 // NewHookServer builds a server for the socket at path. Call Serve to listen.
63-func NewHookServer(path string, hooks PushHooks) *HookServer {55+func NewHookServer(path string, hooks ports.PushHookService) *HookServer {
64 return &HookServer{path: path, hooks: hooks, tokens: make(map[string]hookEntry), conns: make(map[net.Conn]struct{})}56 return &HookServer{path: path, hooks: hooks, tokens: make(map[string]hookEntry), conns: make(map[net.Conn]struct{})}
65 }57 }
66 58
@@ -183,6 +175,7 @@ func (s *HookServer) Close(ctx context.Context) {
183 }175 }
184 176
185 // handle serves one connection: one request line, one reply line.177 // handle serves one connection: one request line, one reply line.
178+//hexcheck:ignore suspicious-business-logic-in-adapter hook protocol dispatch: the decisions are the use cases'
186 func (s *HookServer) handle(ctx context.Context, conn net.Conn) {179 func (s *HookServer) handle(ctx context.Context, conn net.Conn) {
187 log := zerowrap.FromCtx(ctx)180 log := zerowrap.FromCtx(ctx)
188 _ = conn.SetReadDeadline(time.Now().Add(readTimeout))181 _ = conn.SetReadDeadline(time.Now().Add(readTimeout))
M internal/adapters/githttp/hooks_test.go +10 -9
@@ -22,8 +22,9 @@ import (
22 "github.com/stretchr/testify/require"22 "github.com/stretchr/testify/require"
23 23
24 "bnema.dev/ermite/internal/adapters/githttp"24 "bnema.dev/ermite/internal/adapters/githttp"
25- ghmocks "bnema.dev/ermite/internal/adapters/githttp/mocks"
26 "bnema.dev/ermite/internal/domain"25 "bnema.dev/ermite/internal/domain"
26+ "bnema.dev/ermite/internal/ports"
27+ "bnema.dev/ermite/internal/ports/mocks"
27 )28 )
28 29
29 const (30 const (
@@ -39,7 +40,7 @@ func repoName(t *testing.T, s string) domain.RepoName {
39 }40 }
40 41
41 // startHooks serves a hook socket in a short temporary directory.42 // startHooks serves a hook socket in a short temporary directory.
42-func startHooks(t *testing.T, h githttp.PushHooks) (*githttp.HookServer, string) {43+func startHooks(t *testing.T, h ports.PushHookService) (*githttp.HookServer, string) {
43 t.Helper()44 t.Helper()
44 dir, err := os.MkdirTemp("", "hk")45 dir, err := os.MkdirTemp("", "hk")
45 require.NoError(t, err)46 require.NoError(t, err)
@@ -80,7 +81,7 @@ func raw(t *testing.T, sock, line string) githttp.HookReply {
80 81
81 func TestHookSocketMode(t *testing.T) {82 func TestHookSocketMode(t *testing.T) {
82 t.Parallel()83 t.Parallel()
83- _, sock := startHooks(t, ghmocks.NewPushHooks(t))84+ _, sock := startHooks(t, mocks.NewPushHookService(t))
84 fi, err := os.Stat(sock)85 fi, err := os.Stat(sock)
85 require.NoError(t, err)86 require.NoError(t, err)
86 assert.Equal(t, os.FileMode(0o600), fi.Mode().Perm())87 assert.Equal(t, os.FileMode(0o600), fi.Mode().Perm())
@@ -94,21 +95,21 @@ func TestHookServerReplacesStaleSocketFile(t *testing.T) {
94 defer func() { _ = os.RemoveAll(dir) }()95 defer func() { _ = os.RemoveAll(dir) }()
95 sock := filepath.Join(dir, "hook.sock")96 sock := filepath.Join(dir, "hook.sock")
96 require.NoError(t, os.WriteFile(sock, []byte("stale"), 0o600))97 require.NoError(t, os.WriteFile(sock, []byte("stale"), 0o600))
97- s := githttp.NewHookServer(sock, ghmocks.NewPushHooks(t))98+ s := githttp.NewHookServer(sock, mocks.NewPushHookService(t))
98 require.NoError(t, s.Listen(context.Background()))99 require.NoError(t, s.Listen(context.Background()))
99 s.Close(context.Background())100 s.Close(context.Background())
100 }101 }
101 102
102 func TestHookServerRejectsTooLongSocketPath(t *testing.T) {103 func TestHookServerRejectsTooLongSocketPath(t *testing.T) {
103 t.Parallel()104 t.Parallel()
104- s := githttp.NewHookServer("/"+strings.Repeat("a", 120)+"/hook.sock", ghmocks.NewPushHooks(t))105+ s := githttp.NewHookServer("/"+strings.Repeat("a", 120)+"/hook.sock", mocks.NewPushHookService(t))
105 err := s.Listen(context.Background())106 err := s.Listen(context.Background())
106 require.Error(t, err)107 require.Error(t, err)
107 assert.Contains(t, err.Error(), "too long")108 assert.Contains(t, err.Error(), "too long")
108 }109 }
109 110
110 func TestHookProtocol(t *testing.T) {111 func TestHookProtocol(t *testing.T) {
111- hooks := ghmocks.NewPushHooks(t)112+ hooks := mocks.NewPushHookService(t)
112 srv, sock := startHooks(t, hooks)113 srv, sock := startHooks(t, hooks)
113 p := domain.TokenPrincipal(domain.Token{ID: "tok1", Scopes: []domain.Scope{domain.ScopeRepoWrite}})114 p := domain.TokenPrincipal(domain.Token{ID: "tok1", Scopes: []domain.Scope{domain.ScopeRepoWrite}})
114 tokenA, releaseA := srv.Register(context.Background(), repoName(t, "alpha"), p)115 tokenA, releaseA := srv.Register(context.Background(), repoName(t, "alpha"), p)
@@ -202,7 +203,7 @@ func TestHookProtocol(t *testing.T) {
202 // push is already in the repository.203 // push is already in the repository.
203 func TestHookRecordSurvivesShutdown(t *testing.T) {204 func TestHookRecordSurvivesShutdown(t *testing.T) {
204 t.Parallel()205 t.Parallel()
205- hooks := ghmocks.NewPushHooks(t)206+ hooks := mocks.NewPushHookService(t)
206 started, proceed := make(chan struct{}), make(chan struct{})207 started, proceed := make(chan struct{}), make(chan struct{})
207 var cancelled, hasDeadline bool208 var cancelled, hasDeadline bool
208 var ctxMu sync.Mutex209 var ctxMu sync.Mutex
@@ -257,7 +258,7 @@ func TestHookRecordSurvivesShutdown(t *testing.T) {
257 258
258 func TestHookConcurrentTokens(t *testing.T) {259 func TestHookConcurrentTokens(t *testing.T) {
259 t.Parallel()260 t.Parallel()
260- hooks := ghmocks.NewPushHooks(t)261+ hooks := mocks.NewPushHookService(t)
261 srv, sock := startHooks(t, hooks)262 srv, sock := startHooks(t, hooks)
262 var wg sync.WaitGroup263 var wg sync.WaitGroup
263 // Each of the 16 tokens reaches the use case for its own repository, once.264 // Each of the 16 tokens reaches the use case for its own repository, once.
@@ -280,7 +281,7 @@ func TestHookConcurrentTokens(t *testing.T) {
280 281
281 func TestRunHook(t *testing.T) {282 func TestRunHook(t *testing.T) {
282 ctx := context.Background()283 ctx := context.Background()
283- hooks := ghmocks.NewPushHooks(t)284+ hooks := mocks.NewPushHookService(t)
284 srv, sock := startHooks(t, hooks)285 srv, sock := startHooks(t, hooks)
285 tok, rel := srv.Register(context.Background(), repoName(t, "alpha"), domain.Root())286 tok, rel := srv.Register(context.Background(), repoName(t, "alpha"), domain.Root())
286 defer rel()287 defer rel()
M internal/adapters/rpc/api.go +10 -15
@@ -18,6 +18,7 @@ import (
18 "bnema.dev/ermite-api/gen/ermite/ermiteconnect"18 "bnema.dev/ermite-api/gen/ermite/ermiteconnect"
19 "bnema.dev/ermite/internal/clientlimit"19 "bnema.dev/ermite/internal/clientlimit"
20 "bnema.dev/ermite/internal/domain"20 "bnema.dev/ermite/internal/domain"
21+ "bnema.dev/ermite/internal/ports"
21 )22 )
22 23
23 // MaxRequestBytes bounds the size of a request message. Responses are not24 // MaxRequestBytes bounds the size of a request message. Responses are not
@@ -28,22 +29,16 @@ const MaxRequestBytes = 1 << 20
28 // implements, as GetVersion lists them.29 // implements, as GetVersion lists them.
29 var Features = []string{"repo.update", "events.resume", "merge_requests", "merge_requests.delete_branch", "signing_key", "session"}30 var Features = []string{"repo.update", "events.resume", "merge_requests", "merge_requests.delete_branch", "signing_key", "session"}
30 31
31-// Authenticator turns a token secret into a principal, or
32-// domain.ErrUnauthenticated. app.Auth fits.
33-type Authenticator interface {
34- Authenticate(ctx context.Context, secret string) (domain.Principal, error)
35-}
36-
37 // Config configures the API.32 // Config configures the API.
38 type Config struct {33 type Config struct {
39- Auth Authenticator34+ Auth ports.AuthService
40- Repos Repos35+ Repos ports.RepoService
41- Tokens Tokens36+ Tokens ports.TokenService
42- Browse Browse37+ Browse ports.BrowseService
43- Events Events38+ Events ports.EventService
44 // MergeRequests serves MergeRequestService, SigningKey GetSigningKey.39 // MergeRequests serves MergeRequestService, SigningKey GetSigningKey.
45- MergeRequests MergeRequests40+ MergeRequests ports.MergeRequestService
46- SigningKey SigningKey41+ SigningKey ports.SigningKeyService
47 Version string42 Version string
48 // TrustedProxies are the networks allowed to set X-Forwarded-For.43 // TrustedProxies are the networks allowed to set X-Forwarded-For.
49 TrustedProxies []netip.Prefix44 TrustedProxies []netip.Prefix
@@ -51,11 +46,11 @@ type Config struct {
51 // it with the other adapters that authenticate, so that a client has one46 // it with the other adapters that authenticate, so that a client has one
52 // budget of failures. Leave it unset rather than assigning a typed nil47 // budget of failures. Leave it unset rather than assigning a typed nil
53 // pointer (a nil *T in the interface is not nil, and would be called).48 // pointer (a nil *T in the interface is not nil, and would be called).
54- Limiter FailureLimiter49+ Limiter ports.FailureLimiter
55 // Sessions are the owner's web sessions; nil serves no SessionService.50 // Sessions are the owner's web sessions; nil serves no SessionService.
56 // Leave it unset rather than assigning a typed nil pointer (a nil *T in51 // Leave it unset rather than assigning a typed nil pointer (a nil *T in
57 // the interface is not nil, and the service would be served and called).52 // the interface is not nil, and the service would be served and called).
58- Sessions Sessions53+ Sessions ports.SessionService
59 }54 }
60 55
61 // API is the set of Connect handlers of the contract.56 // API is the set of Connect handlers of the contract.
M internal/adapters/rpc/convert.go +4 -4
@@ -9,8 +9,8 @@ import (
9 "google.golang.org/protobuf/types/known/timestamppb"9 "google.golang.org/protobuf/types/known/timestamppb"
10 10
11 pb "bnema.dev/ermite-api/gen/ermite"11 pb "bnema.dev/ermite-api/gen/ermite"
12- "bnema.dev/ermite/internal/app"
13 "bnema.dev/ermite/internal/domain"12 "bnema.dev/ermite/internal/domain"
13+ "bnema.dev/ermite/internal/ports"
14 )14 )
15 15
16 // This file maps domain values to the messages of the contract and back.16 // This file maps domain values to the messages of the contract and back.
@@ -55,7 +55,7 @@ func visibilityFromPB(v pb.Visibility) (domain.Visibility, error) {
55 return "", fmt.Errorf("%w: %v", domain.ErrInvalidVisibility, v)55 return "", fmt.Errorf("%w: %v", domain.ErrInvalidVisibility, v)
56 }56 }
57 57
58-func repoToPB(v app.RepoView) *pb.Repo {58+func repoToPB(v ports.RepoView) *pb.Repo {
59 settings := v.Settings59 settings := v.Settings
60 if settings.IsZero() {60 if settings.IsZero() {
61 settings = domain.DefaultRepoSettings()61 settings = domain.DefaultRepoSettings()
@@ -361,7 +361,7 @@ func refUpdatesToPB(updates []domain.RefUpdate) []*pb.RefUpdate {
361 361
362 // watchEventToPB maps one item of a watch; nil when the event has no362 // watchEventToPB maps one item of a watch; nil when the event has no
363 // representation in the contract.363 // representation in the contract.
364-func watchEventToPB(we app.WatchEvent) *pb.Event {364+func watchEventToPB(we ports.WatchEvent) *pb.Event {
365 if we.Gap {365 if we.Gap {
366 return &pb.Event{Kind: &pb.Event_Gap{Gap: &pb.Gap{}}}366 return &pb.Event{Kind: &pb.Event_Gap{Gap: &pb.Gap{}}}
367 }367 }
@@ -397,7 +397,7 @@ func watchEventToPB(we app.WatchEvent) *pb.Event {
397 return out397 return out
398 }398 }
399 399
400-func repoOrName(we app.WatchEvent, name domain.RepoName, vis domain.Visibility) *pb.Repo {400+func repoOrName(we ports.WatchEvent, name domain.RepoName, vis domain.Visibility) *pb.Repo {
401 if we.Repo != nil {401 if we.Repo != nil {
402 return repoToPB(*we.Repo)402 return repoToPB(*we.Repo)
403 }403 }
M internal/adapters/rpc/convert_test.go +5 -5
@@ -15,8 +15,8 @@ import (
15 "google.golang.org/protobuf/types/known/fieldmaskpb"15 "google.golang.org/protobuf/types/known/fieldmaskpb"
16 16
17 pb "bnema.dev/ermite-api/gen/ermite"17 pb "bnema.dev/ermite-api/gen/ermite"
18- "bnema.dev/ermite/internal/app"
19 "bnema.dev/ermite/internal/domain"18 "bnema.dev/ermite/internal/domain"
19+ "bnema.dev/ermite/internal/ports"
20 )20 )
21 21
22 // Every value of the domain maps to a real value of the contract, never to22 // Every value of the domain maps to a real value of the contract, never to
@@ -212,12 +212,12 @@ func TestCommentID(t *testing.T) {
212 func TestRepoToPBAlwaysHasSettings(t *testing.T) {212 func TestRepoToPBAlwaysHasSettings(t *testing.T) {
213 t.Parallel()213 t.Parallel()
214 // A repository value without settings (never stored with them) reads as the defaults.214 // A repository value without settings (never stored with them) reads as the defaults.
215- r := repoToPB(app.RepoView{Repo: domain.Repo{Name: mustName(t, "x")}})215+ r := repoToPB(ports.RepoView{Repo: domain.Repo{Name: mustName(t, "x")}})
216 assert.Equal(t, []pb.MergeMode{pb.MergeMode_MERGE_MODE_FAST_FORWARD, pb.MergeMode_MERGE_MODE_SQUASH}, r.GetSettings().GetAllowedMergeModes())216 assert.Equal(t, []pb.MergeMode{pb.MergeMode_MERGE_MODE_FAST_FORWARD, pb.MergeMode_MERGE_MODE_SQUASH}, r.GetSettings().GetAllowedMergeModes())
217 assert.True(t, r.GetSettings().GetFailingChecksBlock())217 assert.True(t, r.GetSettings().GetFailingChecksBlock())
218 218
219 custom := domain.RepoSettings{AllowedMergeModes: []domain.MergeMode{domain.MergeModeSquash}}219 custom := domain.RepoSettings{AllowedMergeModes: []domain.MergeMode{domain.MergeModeSquash}}
220- r = repoToPB(app.RepoView{Repo: domain.Repo{Name: mustName(t, "x"), Settings: custom}})220+ r = repoToPB(ports.RepoView{Repo: domain.Repo{Name: mustName(t, "x"), Settings: custom}})
221 assert.Equal(t, []pb.MergeMode{pb.MergeMode_MERGE_MODE_SQUASH}, r.GetSettings().GetAllowedMergeModes())221 assert.Equal(t, []pb.MergeMode{pb.MergeMode_MERGE_MODE_SQUASH}, r.GetSettings().GetAllowedMergeModes())
222 assert.False(t, r.GetSettings().GetFailingChecksBlock())222 assert.False(t, r.GetSettings().GetFailingChecksBlock())
223 }223 }
@@ -313,7 +313,7 @@ func TestWatchEventToPBMergeRequestEvents(t *testing.T) {
313 } {313 } {
314 t.Run(name, func(t *testing.T) {314 t.Run(name, func(t *testing.T) {
315 t.Parallel()315 t.Parallel()
316- got := watchEventToPB(app.WatchEvent{ID: "e1", Event: tc.ev})316+ got := watchEventToPB(ports.WatchEvent{ID: "e1", Event: tc.ev})
317 require.NotNil(t, got)317 require.NotNil(t, got)
318 assert.Equal(t, "e1", got.GetId())318 assert.Equal(t, "e1", got.GetId())
319 assert.Equal(t, at, got.GetTime().AsTime())319 assert.Equal(t, at, got.GetTime().AsTime())
@@ -327,7 +327,7 @@ func TestWatchEventToPBMergeRequestEvents(t *testing.T) {
327 }327 }
328 328
329 c := domain.Comment{ID: 9, Author: "root", Path: "a.txt", Line: 3, Side: domain.SideOld, CreatedAt: at}329 c := domain.Comment{ID: 9, Author: "root", Path: "a.txt", Line: 3, Side: domain.SideOld, CreatedAt: at}
330- got := watchEventToPB(app.WatchEvent{ID: "e2", Event: domain.CommentAdded{Repo: repo, Number: 4, Comment: c, At: at}})330+ got := watchEventToPB(ports.WatchEvent{ID: "e2", Event: domain.CommentAdded{Repo: repo, Number: 4, Comment: c, At: at}})
331 added := got.GetCommentAdded()331 added := got.GetCommentAdded()
332 require.NotNil(t, added)332 require.NotNil(t, added)
333 assert.Equal(t, "proj", added.GetRepo())333 assert.Equal(t, "proj", added.GetRepo())
M internal/adapters/rpc/event.go +4 -4
@@ -8,11 +8,11 @@ import (
8 "connectrpc.com/connect"8 "connectrpc.com/connect"
9 9
10 pb "bnema.dev/ermite-api/gen/ermite"10 pb "bnema.dev/ermite-api/gen/ermite"
11- "bnema.dev/ermite/internal/app"11+ "bnema.dev/ermite/internal/ports"
12 )12 )
13 13
14 // eventService implements ermiteconnect.EventServiceHandler.14 // eventService implements ermiteconnect.EventServiceHandler.
15-type eventService struct{ events Events }15+type eventService struct{ events ports.EventService }
16 16
17 // Watch streams events until the client goes away or the server stops. The17 // Watch streams events until the client goes away or the server stops. The
18 // stream ends with success in both cases: the contract has no error for18 // stream ends with success in both cases: the contract has no error for
@@ -20,7 +20,7 @@ type eventService struct{ events Events }
20 // the caller is revoked or expires, and a caller that has too many streams20 // the caller is revoked or expires, and a caller that has too many streams
21 // open gets resource_exhausted.21 // open gets resource_exhausted.
22 func (s *eventService) Watch(ctx context.Context, req *connect.Request[pb.WatchRequest], stream *connect.ServerStream[pb.WatchResponse]) error {22 func (s *eventService) Watch(ctx context.Context, req *connect.Request[pb.WatchRequest], stream *connect.ServerStream[pb.WatchResponse]) error {
23- params := app.WatchParams{23+ params := ports.WatchParams{
24 Topics: req.Msg.GetTopics(), ResumeAfter: req.Msg.GetResumeAfter(),24 Topics: req.Msg.GetTopics(), ResumeAfter: req.Msg.GetResumeAfter(),
25 Client: clientFromContext(ctx),25 Client: clientFromContext(ctx),
26 // A client's call returns when the response begins: send the headers26 // A client's call returns when the response begins: send the headers
@@ -28,7 +28,7 @@ func (s *eventService) Watch(ctx context.Context, req *connect.Request[pb.WatchR
28 // them for a nil message. A failure shows on the first real send.28 // them for a nil message. A failure shows on the first real send.
29 Ready: func() { _ = stream.Send(nil) },29 Ready: func() { _ = stream.Send(nil) },
30 }30 }
31- err := s.events.Watch(ctx, caller(ctx), params, func(we app.WatchEvent) error {31+ err := s.events.Watch(ctx, caller(ctx), params, func(we ports.WatchEvent) error {
32 ev := watchEventToPB(we)32 ev := watchEventToPB(we)
33 if ev == nil {33 if ev == nil {
34 return nil34 return nil
M internal/adapters/rpc/git.go +4 -4
@@ -11,15 +11,15 @@ import (
11 "connectrpc.com/connect"11 "connectrpc.com/connect"
12 12
13 pb "bnema.dev/ermite-api/gen/ermite"13 pb "bnema.dev/ermite-api/gen/ermite"
14- "bnema.dev/ermite/internal/app"
15 "bnema.dev/ermite/internal/domain"14 "bnema.dev/ermite/internal/domain"
15+ "bnema.dev/ermite/internal/ports"
16 )16 )
17 17
18 // BlobChunkSize is the largest chunk of a GetBlob stream.18 // BlobChunkSize is the largest chunk of a GetBlob stream.
19 const BlobChunkSize = 64 << 1019 const BlobChunkSize = 64 << 10
20 20
21 // gitService implements ermiteconnect.GitServiceHandler.21 // gitService implements ermiteconnect.GitServiceHandler.
22-type gitService struct{ browse Browse }22+type gitService struct{ browse ports.BrowseService }
23 23
24 // target is the repository and revision most requests start with.24 // target is the repository and revision most requests start with.
25 func target(repo, revision string) (domain.RepoName, domain.Revision, error) {25 func target(repo, revision string) (domain.RepoName, domain.Revision, error) {
@@ -39,7 +39,7 @@ func (s *gitService) ListRefs(ctx context.Context, req *connect.Request[pb.ListR
39 if err != nil {39 if err != nil {
40 return nil, connectError(ctx, err)40 return nil, connectError(ctx, err)
41 }41 }
42- params := app.ListRefsParams{PageSize: int(req.Msg.GetPageSize()), PageToken: req.Msg.GetPageToken()}42+ params := ports.ListRefsParams{PageSize: int(req.Msg.GetPageSize()), PageToken: req.Msg.GetPageToken()}
43 for _, k := range req.Msg.GetKinds() {43 for _, k := range req.Msg.GetKinds() {
44 kind, err := refKindFromPB(k)44 kind, err := refKindFromPB(k)
45 if err != nil {45 if err != nil {
@@ -144,7 +144,7 @@ func (s *gitService) ListCommits(ctx context.Context, req *connect.Request[pb.Li
144 if err != nil {144 if err != nil {
145 return nil, connectError(ctx, err)145 return nil, connectError(ctx, err)
146 }146 }
147- page, err := s.browse.ListCommits(ctx, caller(ctx), name, app.ListCommitsParams{147+ page, err := s.browse.ListCommits(ctx, caller(ctx), name, ports.ListCommitsParams{
148 Revision: rev, Path: path, PageSize: int(req.Msg.GetPageSize()), PageToken: req.Msg.GetPageToken(),148 Revision: rev, Path: path, PageSize: int(req.Msg.GetPageSize()), PageToken: req.Msg.GetPageToken(),
149 })149 })
150 if err != nil {150 if err != nil {
M internal/adapters/rpc/git_test.go +12 -12
@@ -11,9 +11,9 @@ import (
11 "github.com/stretchr/testify/require"11 "github.com/stretchr/testify/require"
12 12
13 pb "bnema.dev/ermite-api/gen/ermite"13 pb "bnema.dev/ermite-api/gen/ermite"
14- "bnema.dev/ermite/internal/adapters/rpc/mocks"
15- "bnema.dev/ermite/internal/app"
16 "bnema.dev/ermite/internal/domain"14 "bnema.dev/ermite/internal/domain"
15+ "bnema.dev/ermite/internal/ports"
16+ "bnema.dev/ermite/internal/ports/mocks"
17 )17 )
18 18
19 func TestGitServiceGetTree(t *testing.T) {19 func TestGitServiceGetTree(t *testing.T) {
@@ -34,7 +34,7 @@ func TestGitServiceGetTree(t *testing.T) {
34 for _, tt := range tests {34 for _, tt := range tests {
35 t.Run(tt.name, func(t *testing.T) {35 t.Run(tt.name, func(t *testing.T) {
36 t.Parallel()36 t.Parallel()
37- browse := mocks.NewBrowse(t)37+ browse := mocks.NewBrowseService(t)
38 if tt.wantCall {38 if tt.wantCall {
39 browse.EXPECT().GetTree(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), mock.Anything, mock.Anything, false).39 browse.EXPECT().GetTree(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), mock.Anything, mock.Anything, false).
40 Return(domain.Tree{}, tt.usecase).Once()40 Return(domain.Tree{}, tt.usecase).Once()
@@ -68,10 +68,10 @@ func TestGitServiceListRefs(t *testing.T) {
68 for _, tt := range tests {68 for _, tt := range tests {
69 t.Run(tt.name, func(t *testing.T) {69 t.Run(tt.name, func(t *testing.T) {
70 t.Parallel()70 t.Parallel()
71- browse := mocks.NewBrowse(t)71+ browse := mocks.NewBrowseService(t)
72 if tt.wantCall {72 if tt.wantCall {
73 browse.EXPECT().ListRefs(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), mock.Anything).73 browse.EXPECT().ListRefs(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), mock.Anything).
74- Return(app.RefPage{NextPageToken: "n"}, tt.usecase).Once()74+ Return(ports.RefPage{NextPageToken: "n"}, tt.usecase).Once()
75 }75 }
76 resp, err := (&gitService{browse}).ListRefs(ownerCtx(), connect.NewRequest(tt.req))76 resp, err := (&gitService{browse}).ListRefs(ownerCtx(), connect.NewRequest(tt.req))
77 if tt.wantCode != 0 {77 if tt.wantCode != 0 {
@@ -93,26 +93,26 @@ func TestGitServiceGetCommitAndResolveRevision(t *testing.T) {
93 for _, tt := range invalid {93 for _, tt := range invalid {
94 t.Run("GetCommit "+tt.name, func(t *testing.T) {94 t.Run("GetCommit "+tt.name, func(t *testing.T) {
95 t.Parallel()95 t.Parallel()
96- _, err := (&gitService{mocks.NewBrowse(t)}).GetCommit(ownerCtx(), connect.NewRequest(&pb.GetCommitRequest{Repo: tt.repo, Revision: tt.rev}))96+ _, err := (&gitService{mocks.NewBrowseService(t)}).GetCommit(ownerCtx(), connect.NewRequest(&pb.GetCommitRequest{Repo: tt.repo, Revision: tt.rev}))
97 requireCode(t, connect.CodeInvalidArgument, err)97 requireCode(t, connect.CodeInvalidArgument, err)
98 })98 })
99 t.Run("ResolveRevision "+tt.name, func(t *testing.T) {99 t.Run("ResolveRevision "+tt.name, func(t *testing.T) {
100 t.Parallel()100 t.Parallel()
101- _, err := (&gitService{mocks.NewBrowse(t)}).ResolveRevision(ownerCtx(), connect.NewRequest(&pb.ResolveRevisionRequest{Repo: tt.repo, Revision: tt.rev}))101+ _, err := (&gitService{mocks.NewBrowseService(t)}).ResolveRevision(ownerCtx(), connect.NewRequest(&pb.ResolveRevisionRequest{Repo: tt.repo, Revision: tt.rev}))
102 requireCode(t, connect.CodeInvalidArgument, err)102 requireCode(t, connect.CodeInvalidArgument, err)
103 })103 })
104 }104 }
105 105
106 t.Run("GetCommit not found", func(t *testing.T) {106 t.Run("GetCommit not found", func(t *testing.T) {
107 t.Parallel()107 t.Parallel()
108- browse := mocks.NewBrowse(t)108+ browse := mocks.NewBrowseService(t)
109 browse.EXPECT().GetCommit(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), mock.Anything).Return(domain.CommitDetail{}, domain.ErrNotFound).Once()109 browse.EXPECT().GetCommit(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), mock.Anything).Return(domain.CommitDetail{}, domain.ErrNotFound).Once()
110 _, err := (&gitService{browse}).GetCommit(ownerCtx(), connect.NewRequest(&pb.GetCommitRequest{Repo: "demo", Revision: "main"}))110 _, err := (&gitService{browse}).GetCommit(ownerCtx(), connect.NewRequest(&pb.GetCommitRequest{Repo: "demo", Revision: "main"}))
111 requireCode(t, connect.CodeNotFound, err)111 requireCode(t, connect.CodeNotFound, err)
112 })112 })
113 t.Run("ResolveRevision ok", func(t *testing.T) {113 t.Run("ResolveRevision ok", func(t *testing.T) {
114 t.Parallel()114 t.Parallel()
115- browse := mocks.NewBrowse(t)115+ browse := mocks.NewBrowseService(t)
116 browse.EXPECT().ResolveRevision(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), mock.Anything).116 browse.EXPECT().ResolveRevision(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), mock.Anything).
117 Return(domain.ResolvedRevision{Ref: "refs/heads/main"}, nil).Once()117 Return(domain.ResolvedRevision{Ref: "refs/heads/main"}, nil).Once()
118 resp, err := (&gitService{browse}).ResolveRevision(ownerCtx(), connect.NewRequest(&pb.ResolveRevisionRequest{Repo: "demo", Revision: "main"}))118 resp, err := (&gitService{browse}).ResolveRevision(ownerCtx(), connect.NewRequest(&pb.ResolveRevisionRequest{Repo: "demo", Revision: "main"}))
@@ -138,10 +138,10 @@ func TestGitServiceListCommits(t *testing.T) {
138 for _, tt := range tests {138 for _, tt := range tests {
139 t.Run(tt.name, func(t *testing.T) {139 t.Run(tt.name, func(t *testing.T) {
140 t.Parallel()140 t.Parallel()
141- browse := mocks.NewBrowse(t)141+ browse := mocks.NewBrowseService(t)
142 if tt.wantCall {142 if tt.wantCall {
143 browse.EXPECT().ListCommits(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), mock.Anything).143 browse.EXPECT().ListCommits(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), mock.Anything).
144- Return(app.CommitPage{}, tt.usecase).Once()144+ Return(ports.HistoryPage{}, tt.usecase).Once()
145 }145 }
146 _, err := (&gitService{browse}).ListCommits(ownerCtx(), connect.NewRequest(tt.req))146 _, err := (&gitService{browse}).ListCommits(ownerCtx(), connect.NewRequest(tt.req))
147 if tt.wantCode != 0 {147 if tt.wantCode != 0 {
@@ -167,7 +167,7 @@ func TestGitServiceGetBlobRejectsBadInput(t *testing.T) {
167 for _, tt := range tests {167 for _, tt := range tests {
168 t.Run(tt.name, func(t *testing.T) {168 t.Run(tt.name, func(t *testing.T) {
169 t.Parallel()169 t.Parallel()
170- err := (&gitService{mocks.NewBrowse(t)}).GetBlob(ownerCtx(), connect.NewRequest(tt.req), nil)170+ err := (&gitService{mocks.NewBrowseService(t)}).GetBlob(ownerCtx(), connect.NewRequest(tt.req), nil)
171 requireCode(t, connect.CodeInvalidArgument, err)171 requireCode(t, connect.CodeInvalidArgument, err)
172 })172 })
173 }173 }
M internal/adapters/rpc/interceptor.go +3 -2
@@ -18,6 +18,7 @@ import (
18 "bnema.dev/ermite-api/gen/ermite/ermiteconnect"18 "bnema.dev/ermite-api/gen/ermite/ermiteconnect"
19 "bnema.dev/ermite/internal/clientlimit"19 "bnema.dev/ermite/internal/clientlimit"
20 "bnema.dev/ermite/internal/domain"20 "bnema.dev/ermite/internal/domain"
21+ "bnema.dev/ermite/internal/ports"
21 )22 )
22 23
23 var errInternal = errors.New("internal error")24 var errInternal = errors.New("internal error")
@@ -37,8 +38,8 @@ var errInternal = errors.New("internal error")
37 // put there by a server's base context, is ignored and the request is38 // put there by a server's base context, is ignored and the request is
38 // authenticated from its headers.39 // authenticated from its headers.
39 type interceptor struct {40 type interceptor struct {
40- auth Authenticator41+ auth ports.AuthService
41- limiter FailureLimiter42+ limiter ports.FailureLimiter
42 trusted []netip.Prefix43 trusted []netip.Prefix
43 }44 }
44 45
M internal/adapters/rpc/interceptor_test.go +3 -3
@@ -15,8 +15,8 @@ import (
15 "github.com/stretchr/testify/require"15 "github.com/stretchr/testify/require"
16 16
17 "bnema.dev/ermite-api/gen/ermite/ermiteconnect"17 "bnema.dev/ermite-api/gen/ermite/ermiteconnect"
18- "bnema.dev/ermite/internal/adapters/rpc/mocks"
19 "bnema.dev/ermite/internal/domain"18 "bnema.dev/ermite/internal/domain"
19+ "bnema.dev/ermite/internal/ports/mocks"
20 )20 )
21 21
22 const peer = "203.0.113.7:4242"22 const peer = "203.0.113.7:4242"
@@ -69,7 +69,7 @@ func TestInterceptorAuthenticate(t *testing.T) {
69 for _, tt := range tests {69 for _, tt := range tests {
70 t.Run(tt.name, func(t *testing.T) {70 t.Run(tt.name, func(t *testing.T) {
71 t.Parallel()71 t.Parallel()
72- auth := mocks.NewAuthenticator(t)72+ auth := mocks.NewAuthService(t)
73 limiter := mocks.NewFailureLimiter(t)73 limiter := mocks.NewFailureLimiter(t)
74 if tt.wantLimit {74 if tt.wantLimit {
75 wait := time.Duration(0)75 wait := time.Duration(0)
@@ -117,7 +117,7 @@ func TestInterceptorIgnoresForwardedForFromUntrustedPeer(t *testing.T) {
117 limiter.EXPECT().Blocked(mock.Anything, "203.0.113.7").Return(false, time.Duration(0)).Once()117 limiter.EXPECT().Blocked(mock.Anything, "203.0.113.7").Return(false, time.Duration(0)).Once()
118 h := bearerHeader("")118 h := bearerHeader("")
119 h.Set("X-Forwarded-For", "198.51.100.1")119 h.Set("X-Forwarded-For", "198.51.100.1")
120- i := newInterceptor(Config{Auth: mocks.NewAuthenticator(t), Limiter: limiter})120+ i := newInterceptor(Config{Auth: mocks.NewAuthService(t), Limiter: limiter})
121 _, err := i.authenticate(context.Background(), ermiteconnect.RepoServiceListReposProcedure, peer, h)121 _, err := i.authenticate(context.Background(), ermiteconnect.RepoServiceListReposProcedure, peer, h)
122 require.NoError(t, err)122 require.NoError(t, err)
123 }123 }
M internal/adapters/rpc/mergerequest.go +6 -6
@@ -10,12 +10,12 @@ 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/internal/app"
14 "bnema.dev/ermite/internal/domain"13 "bnema.dev/ermite/internal/domain"
14+ "bnema.dev/ermite/internal/ports"
15 )15 )
16 16
17 // mergeRequestService implements ermiteconnect.MergeRequestServiceHandler.17 // mergeRequestService implements ermiteconnect.MergeRequestServiceHandler.
18-type mergeRequestService struct{ mrs MergeRequests }18+type mergeRequestService struct{ mrs ports.MergeRequestService }
19 19
20 func mergeRequestStateToPB(s domain.MRState) pb.MergeRequestState {20 func mergeRequestStateToPB(s domain.MRState) pb.MergeRequestState {
21 switch s {21 switch s {
@@ -129,7 +129,7 @@ func (s *mergeRequestService) CreateMergeRequest(ctx context.Context, req *conne
129 if err != nil {129 if err != nil {
130 return nil, connectError(ctx, err)130 return nil, connectError(ctx, err)
131 }131 }
132- mr, err := s.mrs.Create(ctx, caller(ctx), name, app.CreateMergeRequestParams{132+ mr, err := s.mrs.Create(ctx, caller(ctx), name, ports.CreateMergeRequestParams{
133 Title: req.Msg.GetTitle(), Description: req.Msg.GetDescription(),133 Title: req.Msg.GetTitle(), Description: req.Msg.GetDescription(),
134 Source: req.Msg.GetSourceBranch(), Target: req.Msg.GetTargetBranch(),134 Source: req.Msg.GetSourceBranch(), Target: req.Msg.GetTargetBranch(),
135 })135 })
@@ -148,7 +148,7 @@ func (s *mergeRequestService) ListMergeRequests(ctx context.Context, req *connec
148 if err != nil {148 if err != nil {
149 return nil, connectError(ctx, err)149 return nil, connectError(ctx, err)
150 }150 }
151- page, err := s.mrs.List(ctx, caller(ctx), name, app.ListMergeRequestsParams{151+ page, err := s.mrs.List(ctx, caller(ctx), name, ports.ListMergeRequestsParams{
152 State: state, PageSize: int(req.Msg.GetPageSize()), PageToken: req.Msg.GetPageToken(),152 State: state, PageSize: int(req.Msg.GetPageSize()), PageToken: req.Msg.GetPageToken(),
153 })153 })
154 if err != nil {154 if err != nil {
@@ -197,7 +197,7 @@ func (s *mergeRequestService) AddComment(ctx context.Context, req *connect.Reque
197 if err != nil {197 if err != nil {
198 return nil, connectError(ctx, err)198 return nil, connectError(ctx, err)
199 }199 }
200- c, err := s.mrs.AddComment(ctx, caller(ctx), name, req.Msg.GetNumber(), app.AddCommentParams{200+ c, err := s.mrs.AddComment(ctx, caller(ctx), name, req.Msg.GetNumber(), ports.AddCommentParams{
201 Path: req.Msg.GetPath(), Line: int(req.Msg.GetLine()), Side: side, Text: req.Msg.GetText(),201 Path: req.Msg.GetPath(), Line: int(req.Msg.GetLine()), Side: side, Text: req.Msg.GetText(),
202 })202 })
203 if err != nil {203 if err != nil {
@@ -245,7 +245,7 @@ func (s *mergeRequestService) MergeMergeRequest(ctx context.Context, req *connec
245 if err != nil {245 if err != nil {
246 return nil, connectError(ctx, err)246 return nil, connectError(ctx, err)
247 }247 }
248- res, err := s.mrs.Merge(ctx, caller(ctx), name, req.Msg.GetNumber(), app.MergeParams{248+ res, err := s.mrs.Merge(ctx, caller(ctx), name, req.Msg.GetNumber(), ports.MergeParams{
249 Mode: mode, HeadID: head, DeleteSource: req.Msg.GetDeleteSourceBranch(),249 Mode: mode, HeadID: head, DeleteSource: req.Msg.GetDeleteSourceBranch(),
250 })250 })
251 if err != nil {251 if err != nil {
M internal/adapters/rpc/mergerequest_test.go +19 -19
@@ -11,9 +11,9 @@ import (
11 "github.com/stretchr/testify/require"11 "github.com/stretchr/testify/require"
12 12
13 pb "bnema.dev/ermite-api/gen/ermite"13 pb "bnema.dev/ermite-api/gen/ermite"
14- "bnema.dev/ermite/internal/adapters/rpc/mocks"
15- "bnema.dev/ermite/internal/app"
16 "bnema.dev/ermite/internal/domain"14 "bnema.dev/ermite/internal/domain"
15+ "bnema.dev/ermite/internal/ports"
16+ "bnema.dev/ermite/internal/ports/mocks"
17 )17 )
18 18
19 func TestMergeRequestServiceCreate(t *testing.T) {19 func TestMergeRequestServiceCreate(t *testing.T) {
@@ -35,9 +35,9 @@ func TestMergeRequestServiceCreate(t *testing.T) {
35 for _, tt := range tests {35 for _, tt := range tests {
36 t.Run(tt.name, func(t *testing.T) {36 t.Run(tt.name, func(t *testing.T) {
37 t.Parallel()37 t.Parallel()
38- mrs := mocks.NewMergeRequests(t)38+ mrs := mocks.NewMergeRequestService(t)
39 if tt.wantCall {39 if tt.wantCall {
40- mrs.EXPECT().Create(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), app.CreateMergeRequestParams{40+ mrs.EXPECT().Create(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), ports.CreateMergeRequestParams{
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 }
@@ -57,17 +57,17 @@ 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.NewMergeRequests(t)}).ListMergeRequests(ownerCtx(),60+ _, err := (&mergeRequestService{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 }
64 64
65 func TestMergeRequestServiceListFiltersByState(t *testing.T) {65 func TestMergeRequestServiceListFiltersByState(t *testing.T) {
66 t.Parallel()66 t.Parallel()
67- mrs := mocks.NewMergeRequests(t)67+ mrs := mocks.NewMergeRequestService(t)
68- mrs.EXPECT().List(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), app.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(app.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}).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 }))
@@ -94,7 +94,7 @@ func TestMergeRequestServiceAddComment(t *testing.T) {
94 for _, tt := range tests {94 for _, tt := range tests {
95 t.Run(tt.name, func(t *testing.T) {95 t.Run(tt.name, func(t *testing.T) {
96 t.Parallel()96 t.Parallel()
97- mrs := mocks.NewMergeRequests(t)97+ mrs := mocks.NewMergeRequestService(t)
98 if tt.wantCall {98 if tt.wantCall {
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()
@@ -117,7 +117,7 @@ func TestMergeRequestServiceResolveCommentMapsUnissuedIDsToZero(t *testing.T) {
117 for _, id := range []string{"", "abc", "-1", "0", "007", "99999999999999999999"} {117 for _, id := range []string{"", "abc", "-1", "0", "007", "99999999999999999999"} {
118 t.Run(id, func(t *testing.T) {118 t.Run(id, func(t *testing.T) {
119 t.Parallel()119 t.Parallel()
120- mrs := mocks.NewMergeRequests(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}).ResolveComment(ownerCtx(), connect.NewRequest(&pb.ResolveCommentRequest{
@@ -148,11 +148,11 @@ func TestMergeRequestServiceMerge(t *testing.T) {
148 for _, tt := range tests {148 for _, tt := range tests {
149 t.Run(tt.name, func(t *testing.T) {149 t.Run(tt.name, func(t *testing.T) {
150 t.Parallel()150 t.Parallel()
151- mrs := mocks.NewMergeRequests(t)151+ mrs := mocks.NewMergeRequestService(t)
152 if tt.wantCall {152 if tt.wantCall {
153- mrs.EXPECT().Merge(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1), mock.MatchedBy(func(p app.MergeParams) bool {153+ mrs.EXPECT().Merge(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1), mock.MatchedBy(func(p ports.MergeParams) bool {
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(app.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}).MergeMergeRequest(ownerCtx(), connect.NewRequest(tt.req))
158 if tt.wantCode != 0 {158 if tt.wantCode != 0 {
@@ -169,14 +169,14 @@ func TestMergeRequestServiceCloseAndMarkRead(t *testing.T) {
169 t.Parallel()169 t.Parallel()
170 t.Run("close not found", func(t *testing.T) {170 t.Run("close not found", func(t *testing.T) {
171 t.Parallel()171 t.Parallel()
172- mrs := mocks.NewMergeRequests(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}).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.NewMergeRequests(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}).CloseMergeRequest(ownerCtx(), connect.NewRequest(&pb.CloseMergeRequestRequest{Repo: "demo", Number: 9}))
182 require.NoError(t, err)182 require.NoError(t, err)
@@ -184,13 +184,13 @@ func TestMergeRequestServiceCloseAndMarkRead(t *testing.T) {
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.NewMergeRequests(t)}).MarkRead(ownerCtx(),187+ _, err := (&mergeRequestService{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 })
191 t.Run("mark read unauthenticated", func(t *testing.T) {191 t.Run("mark read unauthenticated", func(t *testing.T) {
192 t.Parallel()192 t.Parallel()
193- mrs := mocks.NewMergeRequests(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}).MarkRead(ownerCtx(), connect.NewRequest(&pb.MarkReadRequest{Repo: "demo", Number: 1}))
196 requireCode(t, connect.CodeUnauthenticated, err)196 requireCode(t, connect.CodeUnauthenticated, err)
@@ -211,9 +211,9 @@ func TestMergeRequestServiceGet(t *testing.T) {
211 for _, tt := range tests {211 for _, tt := range tests {
212 t.Run(tt.name, func(t *testing.T) {212 t.Run(tt.name, func(t *testing.T) {
213 t.Parallel()213 t.Parallel()
214- mrs := mocks.NewMergeRequests(t)214+ mrs := mocks.NewMergeRequestService(t)
215 mrs.EXPECT().Get(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(4), true).215 mrs.EXPECT().Get(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(4), true).
216- Return(app.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}).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)
D internal/adapters/rpc/mocks/Authenticator.go +0 -114
@@ -1,114 +0,0 @@
1-// Code generated by mockery; DO NOT EDIT.
2-// github.com/vektra/mockery
3-// template: testify
4-
5-package mocks
6-
7-import (
8- "context"
9-
10- "bnema.dev/ermite/internal/domain"
11- mock "github.com/stretchr/testify/mock"
12-)
13-
14-// NewAuthenticator creates a new instance of Authenticator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
15-// The first argument is typically a *testing.T value.
16-func NewAuthenticator(t interface {
17- mock.TestingT
18- Cleanup(func())
19-}) *Authenticator {
20- if helper, ok := t.(interface{ Helper() }); ok {
21- helper.Helper()
22- }
23-
24- mock := &Authenticator{}
25- mock.Mock.Test(t)
26-
27- t.Cleanup(func() {
28- if helper, ok := t.(interface{ Helper() }); ok {
29- helper.Helper()
30- }
31- mock.AssertExpectations(t)
32- })
33-
34- return mock
35-}
36-
37-// Authenticator is an autogenerated mock type for the Authenticator type
38-type Authenticator struct {
39- mock.Mock
40-}
41-
42-type Authenticator_Expecter struct {
43- mock *mock.Mock
44-}
45-
46-func (_m *Authenticator) EXPECT() *Authenticator_Expecter {
47- return &Authenticator_Expecter{mock: &_m.Mock}
48-}
49-
50-// Authenticate provides a mock function for the type Authenticator
51-func (_mock *Authenticator) Authenticate(ctx context.Context, secret string) (domain.Principal, error) {
52- ret := _mock.Called(ctx, secret)
53-
54- if len(ret) == 0 {
55- panic("no return value specified for Authenticate")
56- }
57-
58- var r0 domain.Principal
59- var r1 error
60- if returnFunc, ok := ret.Get(0).(func(context.Context, string) (domain.Principal, error)); ok {
61- return returnFunc(ctx, secret)
62- }
63- if returnFunc, ok := ret.Get(0).(func(context.Context, string) domain.Principal); ok {
64- r0 = returnFunc(ctx, secret)
65- } else {
66- r0 = ret.Get(0).(domain.Principal)
67- }
68- if returnFunc, ok := ret.Get(1).(func(context.Context, string) error); ok {
69- r1 = returnFunc(ctx, secret)
70- } else {
71- r1 = ret.Error(1)
72- }
73- return r0, r1
74-}
75-
76-// Authenticator_Authenticate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Authenticate'
77-type Authenticator_Authenticate_Call struct {
78- *mock.Call
79-}
80-
81-// Authenticate is a helper method to define mock.On call
82-// - ctx context.Context
83-// - secret string
84-func (_e *Authenticator_Expecter) Authenticate(ctx any, secret any) *Authenticator_Authenticate_Call {
85- return &Authenticator_Authenticate_Call{Call: _e.mock.On("Authenticate", ctx, secret)}
86-}
87-
88-func (_c *Authenticator_Authenticate_Call) Run(run func(ctx context.Context, secret string)) *Authenticator_Authenticate_Call {
89- _c.Call.Run(func(args mock.Arguments) {
90- var arg0 context.Context
91- if args[0] != nil {
92- arg0 = args[0].(context.Context)
93- }
94- var arg1 string
95- if args[1] != nil {
96- arg1 = args[1].(string)
97- }
98- run(
99- arg0,
100- arg1,
101- )
102- })
103- return _c
104-}
105-
106-func (_c *Authenticator_Authenticate_Call) Return(principal domain.Principal, err error) *Authenticator_Authenticate_Call {
107- _c.Call.Return(principal, err)
108- return _c
109-}
110-
111-func (_c *Authenticator_Authenticate_Call) RunAndReturn(run func(ctx context.Context, secret string) (domain.Principal, error)) *Authenticator_Authenticate_Call {
112- _c.Call.Return(run)
113- return _c
114-}
D internal/adapters/rpc/mocks/Events.go +0 -118
@@ -1,118 +0,0 @@
1-// Code generated by mockery; DO NOT EDIT.
2-// github.com/vektra/mockery
3-// template: testify
4-
5-package mocks
6-
7-import (
8- "context"
9-
10- "bnema.dev/ermite/internal/app"
11- "bnema.dev/ermite/internal/domain"
12- mock "github.com/stretchr/testify/mock"
13-)
14-
15-// NewEvents creates a new instance of Events. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
16-// The first argument is typically a *testing.T value.
17-func NewEvents(t interface {
18- mock.TestingT
19- Cleanup(func())
20-}) *Events {
21- if helper, ok := t.(interface{ Helper() }); ok {
22- helper.Helper()
23- }
24-
25- mock := &Events{}
26- mock.Mock.Test(t)
27-
28- t.Cleanup(func() {
29- if helper, ok := t.(interface{ Helper() }); ok {
30- helper.Helper()
31- }
32- mock.AssertExpectations(t)
33- })
34-
35- return mock
36-}
37-
38-// Events is an autogenerated mock type for the Events type
39-type Events struct {
40- mock.Mock
41-}
42-
43-type Events_Expecter struct {
44- mock *mock.Mock
45-}
46-
47-func (_m *Events) EXPECT() *Events_Expecter {
48- return &Events_Expecter{mock: &_m.Mock}
49-}
50-
51-// Watch provides a mock function for the type Events
52-func (_mock *Events) Watch(ctx context.Context, p domain.Principal, params app.WatchParams, send func(app.WatchEvent) error) error {
53- ret := _mock.Called(ctx, p, params, send)
54-
55- if len(ret) == 0 {
56- panic("no return value specified for Watch")
57- }
58-
59- var r0 error
60- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, app.WatchParams, func(app.WatchEvent) error) error); ok {
61- r0 = returnFunc(ctx, p, params, send)
62- } else {
63- r0 = ret.Error(0)
64- }
65- return r0
66-}
67-
68-// Events_Watch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Watch'
69-type Events_Watch_Call struct {
70- *mock.Call
71-}
72-
73-// Watch is a helper method to define mock.On call
74-// - ctx context.Context
75-// - p domain.Principal
76-// - params app.WatchParams
77-// - send func(app.WatchEvent) error
78-func (_e *Events_Expecter) Watch(ctx any, p any, params any, send any) *Events_Watch_Call {
79- return &Events_Watch_Call{Call: _e.mock.On("Watch", ctx, p, params, send)}
80-}
81-
82-func (_c *Events_Watch_Call) Run(run func(ctx context.Context, p domain.Principal, params app.WatchParams, send func(app.WatchEvent) error)) *Events_Watch_Call {
83- _c.Call.Run(func(args mock.Arguments) {
84- var arg0 context.Context
85- if args[0] != nil {
86- arg0 = args[0].(context.Context)
87- }
88- var arg1 domain.Principal
89- if args[1] != nil {
90- arg1 = args[1].(domain.Principal)
91- }
92- var arg2 app.WatchParams
93- if args[2] != nil {
94- arg2 = args[2].(app.WatchParams)
95- }
96- var arg3 func(app.WatchEvent) error
97- if args[3] != nil {
98- arg3 = args[3].(func(app.WatchEvent) error)
99- }
100- run(
101- arg0,
102- arg1,
103- arg2,
104- arg3,
105- )
106- })
107- return _c
108-}
109-
110-func (_c *Events_Watch_Call) Return(err error) *Events_Watch_Call {
111- _c.Call.Return(err)
112- return _c
113-}
114-
115-func (_c *Events_Watch_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, params app.WatchParams, send func(app.WatchEvent) error) error) *Events_Watch_Call {
116- _c.Call.Return(run)
117- return _c
118-}
D internal/adapters/rpc/mocks/FailureLimiter.go +0 -160
@@ -1,160 +0,0 @@
1-// Code generated by mockery; DO NOT EDIT.
2-// github.com/vektra/mockery
3-// template: testify
4-
5-package mocks
6-
7-import (
8- "context"
9- "time"
10-
11- mock "github.com/stretchr/testify/mock"
12-)
13-
14-// NewFailureLimiter creates a new instance of FailureLimiter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
15-// The first argument is typically a *testing.T value.
16-func NewFailureLimiter(t interface {
17- mock.TestingT
18- Cleanup(func())
19-}) *FailureLimiter {
20- if helper, ok := t.(interface{ Helper() }); ok {
21- helper.Helper()
22- }
23-
24- mock := &FailureLimiter{}
25- mock.Mock.Test(t)
26-
27- t.Cleanup(func() {
28- if helper, ok := t.(interface{ Helper() }); ok {
29- helper.Helper()
30- }
31- mock.AssertExpectations(t)
32- })
33-
34- return mock
35-}
36-
37-// FailureLimiter is an autogenerated mock type for the FailureLimiter type
38-type FailureLimiter struct {
39- mock.Mock
40-}
41-
42-type FailureLimiter_Expecter struct {
43- mock *mock.Mock
44-}
45-
46-func (_m *FailureLimiter) EXPECT() *FailureLimiter_Expecter {
47- return &FailureLimiter_Expecter{mock: &_m.Mock}
48-}
49-
50-// Blocked provides a mock function for the type FailureLimiter
51-func (_mock *FailureLimiter) Blocked(ctx context.Context, addr string) (bool, time.Duration) {
52- ret := _mock.Called(ctx, addr)
53-
54- if len(ret) == 0 {
55- panic("no return value specified for Blocked")
56- }
57-
58- var r0 bool
59- var r1 time.Duration
60- if returnFunc, ok := ret.Get(0).(func(context.Context, string) (bool, time.Duration)); ok {
61- return returnFunc(ctx, addr)
62- }
63- if returnFunc, ok := ret.Get(0).(func(context.Context, string) bool); ok {
64- r0 = returnFunc(ctx, addr)
65- } else {
66- r0 = ret.Get(0).(bool)
67- }
68- if returnFunc, ok := ret.Get(1).(func(context.Context, string) time.Duration); ok {
69- r1 = returnFunc(ctx, addr)
70- } else {
71- r1 = ret.Get(1).(time.Duration)
72- }
73- return r0, r1
74-}
75-
76-// FailureLimiter_Blocked_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Blocked'
77-type FailureLimiter_Blocked_Call struct {
78- *mock.Call
79-}
80-
81-// Blocked is a helper method to define mock.On call
82-// - ctx context.Context
83-// - addr string
84-func (_e *FailureLimiter_Expecter) Blocked(ctx any, addr any) *FailureLimiter_Blocked_Call {
85- return &FailureLimiter_Blocked_Call{Call: _e.mock.On("Blocked", ctx, addr)}
86-}
87-
88-func (_c *FailureLimiter_Blocked_Call) Run(run func(ctx context.Context, addr string)) *FailureLimiter_Blocked_Call {
89- _c.Call.Run(func(args mock.Arguments) {
90- var arg0 context.Context
91- if args[0] != nil {
92- arg0 = args[0].(context.Context)
93- }
94- var arg1 string
95- if args[1] != nil {
96- arg1 = args[1].(string)
97- }
98- run(
99- arg0,
100- arg1,
101- )
102- })
103- return _c
104-}
105-
106-func (_c *FailureLimiter_Blocked_Call) Return(b bool, duration time.Duration) *FailureLimiter_Blocked_Call {
107- _c.Call.Return(b, duration)
108- return _c
109-}
110-
111-func (_c *FailureLimiter_Blocked_Call) RunAndReturn(run func(ctx context.Context, addr string) (bool, time.Duration)) *FailureLimiter_Blocked_Call {
112- _c.Call.Return(run)
113- return _c
114-}
115-
116-// Fail provides a mock function for the type FailureLimiter
117-func (_mock *FailureLimiter) Fail(ctx context.Context, addr string) {
118- _mock.Called(ctx, addr)
119- return
120-}
121-
122-// FailureLimiter_Fail_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Fail'
123-type FailureLimiter_Fail_Call struct {
124- *mock.Call
125-}
126-
127-// Fail is a helper method to define mock.On call
128-// - ctx context.Context
129-// - addr string
130-func (_e *FailureLimiter_Expecter) Fail(ctx any, addr any) *FailureLimiter_Fail_Call {
131- return &FailureLimiter_Fail_Call{Call: _e.mock.On("Fail", ctx, addr)}
132-}
133-
134-func (_c *FailureLimiter_Fail_Call) Run(run func(ctx context.Context, addr string)) *FailureLimiter_Fail_Call {
135- _c.Call.Run(func(args mock.Arguments) {
136- var arg0 context.Context
137- if args[0] != nil {
138- arg0 = args[0].(context.Context)
139- }
140- var arg1 string
141- if args[1] != nil {
142- arg1 = args[1].(string)
143- }
144- run(
145- arg0,
146- arg1,
147- )
148- })
149- return _c
150-}
151-
152-func (_c *FailureLimiter_Fail_Call) Return() *FailureLimiter_Fail_Call {
153- _c.Call.Return()
154- return _c
155-}
156-
157-func (_c *FailureLimiter_Fail_Call) RunAndReturn(run func(ctx context.Context, addr string)) *FailureLimiter_Fail_Call {
158- _c.Run(run)
159- return _c
160-}
D internal/adapters/rpc/mocks/Sessions.go +0 -172
@@ -1,172 +0,0 @@
1-// Code generated by mockery; DO NOT EDIT.
2-// github.com/vektra/mockery
3-// template: testify
4-
5-package mocks
6-
7-import (
8- "context"
9-
10- "bnema.dev/ermite/internal/app"
11- "bnema.dev/ermite/internal/domain"
12- mock "github.com/stretchr/testify/mock"
13-)
14-
15-// NewSessions creates a new instance of Sessions. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
16-// The first argument is typically a *testing.T value.
17-func NewSessions(t interface {
18- mock.TestingT
19- Cleanup(func())
20-}) *Sessions {
21- if helper, ok := t.(interface{ Helper() }); ok {
22- helper.Helper()
23- }
24-
25- mock := &Sessions{}
26- mock.Mock.Test(t)
27-
28- t.Cleanup(func() {
29- if helper, ok := t.(interface{ Helper() }); ok {
30- helper.Helper()
31- }
32- mock.AssertExpectations(t)
33- })
34-
35- return mock
36-}
37-
38-// Sessions is an autogenerated mock type for the Sessions type
39-type Sessions struct {
40- mock.Mock
41-}
42-
43-type Sessions_Expecter struct {
44- mock *mock.Mock
45-}
46-
47-func (_m *Sessions) EXPECT() *Sessions_Expecter {
48- return &Sessions_Expecter{mock: &_m.Mock}
49-}
50-
51-// CreateLoginLink provides a mock function for the type Sessions
52-func (_mock *Sessions) CreateLoginLink(ctx context.Context, p domain.Principal) (app.LoginLink, error) {
53- ret := _mock.Called(ctx, p)
54-
55- if len(ret) == 0 {
56- panic("no return value specified for CreateLoginLink")
57- }
58-
59- var r0 app.LoginLink
60- var r1 error
61- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal) (app.LoginLink, error)); ok {
62- return returnFunc(ctx, p)
63- }
64- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal) app.LoginLink); ok {
65- r0 = returnFunc(ctx, p)
66- } else {
67- r0 = ret.Get(0).(app.LoginLink)
68- }
69- if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal) error); ok {
70- r1 = returnFunc(ctx, p)
71- } else {
72- r1 = ret.Error(1)
73- }
74- return r0, r1
75-}
76-
77-// Sessions_CreateLoginLink_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateLoginLink'
78-type Sessions_CreateLoginLink_Call struct {
79- *mock.Call
80-}
81-
82-// CreateLoginLink is a helper method to define mock.On call
83-// - ctx context.Context
84-// - p domain.Principal
85-func (_e *Sessions_Expecter) CreateLoginLink(ctx any, p any) *Sessions_CreateLoginLink_Call {
86- return &Sessions_CreateLoginLink_Call{Call: _e.mock.On("CreateLoginLink", ctx, p)}
87-}
88-
89-func (_c *Sessions_CreateLoginLink_Call) Run(run func(ctx context.Context, p domain.Principal)) *Sessions_CreateLoginLink_Call {
90- _c.Call.Run(func(args mock.Arguments) {
91- var arg0 context.Context
92- if args[0] != nil {
93- arg0 = args[0].(context.Context)
94- }
95- var arg1 domain.Principal
96- if args[1] != nil {
97- arg1 = args[1].(domain.Principal)
98- }
99- run(
100- arg0,
101- arg1,
102- )
103- })
104- return _c
105-}
106-
107-func (_c *Sessions_CreateLoginLink_Call) Return(loginLink app.LoginLink, err error) *Sessions_CreateLoginLink_Call {
108- _c.Call.Return(loginLink, err)
109- return _c
110-}
111-
112-func (_c *Sessions_CreateLoginLink_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal) (app.LoginLink, error)) *Sessions_CreateLoginLink_Call {
113- _c.Call.Return(run)
114- return _c
115-}
116-
117-// SignOutEverywhere provides a mock function for the type Sessions
118-func (_mock *Sessions) SignOutEverywhere(ctx context.Context, p domain.Principal) error {
119- ret := _mock.Called(ctx, p)
120-
121- if len(ret) == 0 {
122- panic("no return value specified for SignOutEverywhere")
123- }
124-
125- var r0 error
126- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal) error); ok {
127- r0 = returnFunc(ctx, p)
128- } else {
129- r0 = ret.Error(0)
130- }
131- return r0
132-}
133-
134-// Sessions_SignOutEverywhere_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SignOutEverywhere'
135-type Sessions_SignOutEverywhere_Call struct {
136- *mock.Call
137-}
138-
139-// SignOutEverywhere is a helper method to define mock.On call
140-// - ctx context.Context
141-// - p domain.Principal
142-func (_e *Sessions_Expecter) SignOutEverywhere(ctx any, p any) *Sessions_SignOutEverywhere_Call {
143- return &Sessions_SignOutEverywhere_Call{Call: _e.mock.On("SignOutEverywhere", ctx, p)}
144-}
145-
146-func (_c *Sessions_SignOutEverywhere_Call) Run(run func(ctx context.Context, p domain.Principal)) *Sessions_SignOutEverywhere_Call {
147- _c.Call.Run(func(args mock.Arguments) {
148- var arg0 context.Context
149- if args[0] != nil {
150- arg0 = args[0].(context.Context)
151- }
152- var arg1 domain.Principal
153- if args[1] != nil {
154- arg1 = args[1].(domain.Principal)
155- }
156- run(
157- arg0,
158- arg1,
159- )
160- })
161- return _c
162-}
163-
164-func (_c *Sessions_SignOutEverywhere_Call) Return(err error) *Sessions_SignOutEverywhere_Call {
165- _c.Call.Return(err)
166- return _c
167-}
168-
169-func (_c *Sessions_SignOutEverywhere_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal) error) *Sessions_SignOutEverywhere_Call {
170- _c.Call.Return(run)
171- return _c
172-}
D internal/adapters/rpc/mocks/SigningKey.go +0 -90
@@ -1,90 +0,0 @@
1-// Code generated by mockery; DO NOT EDIT.
2-// github.com/vektra/mockery
3-// template: testify
4-
5-package mocks
6-
7-import (
8- "bnema.dev/ermite/internal/domain"
9- mock "github.com/stretchr/testify/mock"
10-)
11-
12-// NewSigningKey creates a new instance of SigningKey. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
13-// The first argument is typically a *testing.T value.
14-func NewSigningKey(t interface {
15- mock.TestingT
16- Cleanup(func())
17-}) *SigningKey {
18- if helper, ok := t.(interface{ Helper() }); ok {
19- helper.Helper()
20- }
21-
22- mock := &SigningKey{}
23- mock.Mock.Test(t)
24-
25- t.Cleanup(func() {
26- if helper, ok := t.(interface{ Helper() }); ok {
27- helper.Helper()
28- }
29- mock.AssertExpectations(t)
30- })
31-
32- return mock
33-}
34-
35-// SigningKey is an autogenerated mock type for the SigningKey type
36-type SigningKey struct {
37- mock.Mock
38-}
39-
40-type SigningKey_Expecter struct {
41- mock *mock.Mock
42-}
43-
44-func (_m *SigningKey) EXPECT() *SigningKey_Expecter {
45- return &SigningKey_Expecter{mock: &_m.Mock}
46-}
47-
48-// Info provides a mock function for the type SigningKey
49-func (_mock *SigningKey) Info() domain.SigningKeyInfo {
50- ret := _mock.Called()
51-
52- if len(ret) == 0 {
53- panic("no return value specified for Info")
54- }
55-
56- var r0 domain.SigningKeyInfo
57- if returnFunc, ok := ret.Get(0).(func() domain.SigningKeyInfo); ok {
58- r0 = returnFunc()
59- } else {
60- r0 = ret.Get(0).(domain.SigningKeyInfo)
61- }
62- return r0
63-}
64-
65-// SigningKey_Info_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Info'
66-type SigningKey_Info_Call struct {
67- *mock.Call
68-}
69-
70-// Info is a helper method to define mock.On call
71-func (_e *SigningKey_Expecter) Info() *SigningKey_Info_Call {
72- return &SigningKey_Info_Call{Call: _e.mock.On("Info")}
73-}
74-
75-func (_c *SigningKey_Info_Call) Run(run func()) *SigningKey_Info_Call {
76- _c.Call.Run(func(args mock.Arguments) {
77- run()
78- })
79- return _c
80-}
81-
82-func (_c *SigningKey_Info_Call) Return(signingKeyInfo domain.SigningKeyInfo) *SigningKey_Info_Call {
83- _c.Call.Return(signingKeyInfo)
84- return _c
85-}
86-
87-func (_c *SigningKey_Info_Call) RunAndReturn(run func() domain.SigningKeyInfo) *SigningKey_Info_Call {
88- _c.Call.Return(run)
89- return _c
90-}
D internal/adapters/rpc/mocks/Tokens.go +0 -262
@@ -1,262 +0,0 @@
1-// Code generated by mockery; DO NOT EDIT.
2-// github.com/vektra/mockery
3-// template: testify
4-
5-package mocks
6-
7-import (
8- "context"
9-
10- "bnema.dev/ermite/internal/app"
11- "bnema.dev/ermite/internal/domain"
12- mock "github.com/stretchr/testify/mock"
13-)
14-
15-// NewTokens creates a new instance of Tokens. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
16-// The first argument is typically a *testing.T value.
17-func NewTokens(t interface {
18- mock.TestingT
19- Cleanup(func())
20-}) *Tokens {
21- if helper, ok := t.(interface{ Helper() }); ok {
22- helper.Helper()
23- }
24-
25- mock := &Tokens{}
26- mock.Mock.Test(t)
27-
28- t.Cleanup(func() {
29- if helper, ok := t.(interface{ Helper() }); ok {
30- helper.Helper()
31- }
32- mock.AssertExpectations(t)
33- })
34-
35- return mock
36-}
37-
38-// Tokens is an autogenerated mock type for the Tokens type
39-type Tokens struct {
40- mock.Mock
41-}
42-
43-type Tokens_Expecter struct {
44- mock *mock.Mock
45-}
46-
47-func (_m *Tokens) EXPECT() *Tokens_Expecter {
48- return &Tokens_Expecter{mock: &_m.Mock}
49-}
50-
51-// Create provides a mock function for the type Tokens
52-func (_mock *Tokens) Create(ctx context.Context, p domain.Principal, params app.CreateTokenParams) (app.CreatedToken, error) {
53- ret := _mock.Called(ctx, p, params)
54-
55- if len(ret) == 0 {
56- panic("no return value specified for Create")
57- }
58-
59- var r0 app.CreatedToken
60- var r1 error
61- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, app.CreateTokenParams) (app.CreatedToken, error)); ok {
62- return returnFunc(ctx, p, params)
63- }
64- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, app.CreateTokenParams) app.CreatedToken); ok {
65- r0 = returnFunc(ctx, p, params)
66- } else {
67- r0 = ret.Get(0).(app.CreatedToken)
68- }
69- if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, app.CreateTokenParams) error); ok {
70- r1 = returnFunc(ctx, p, params)
71- } else {
72- r1 = ret.Error(1)
73- }
74- return r0, r1
75-}
76-
77-// Tokens_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create'
78-type Tokens_Create_Call struct {
79- *mock.Call
80-}
81-
82-// Create is a helper method to define mock.On call
83-// - ctx context.Context
84-// - p domain.Principal
85-// - params app.CreateTokenParams
86-func (_e *Tokens_Expecter) Create(ctx any, p any, params any) *Tokens_Create_Call {
87- return &Tokens_Create_Call{Call: _e.mock.On("Create", ctx, p, params)}
88-}
89-
90-func (_c *Tokens_Create_Call) Run(run func(ctx context.Context, p domain.Principal, params app.CreateTokenParams)) *Tokens_Create_Call {
91- _c.Call.Run(func(args mock.Arguments) {
92- var arg0 context.Context
93- if args[0] != nil {
94- arg0 = args[0].(context.Context)
95- }
96- var arg1 domain.Principal
97- if args[1] != nil {
98- arg1 = args[1].(domain.Principal)
99- }
100- var arg2 app.CreateTokenParams
101- if args[2] != nil {
102- arg2 = args[2].(app.CreateTokenParams)
103- }
104- run(
105- arg0,
106- arg1,
107- arg2,
108- )
109- })
110- return _c
111-}
112-
113-func (_c *Tokens_Create_Call) Return(createdToken app.CreatedToken, err error) *Tokens_Create_Call {
114- _c.Call.Return(createdToken, err)
115- return _c
116-}
117-
118-func (_c *Tokens_Create_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, params app.CreateTokenParams) (app.CreatedToken, error)) *Tokens_Create_Call {
119- _c.Call.Return(run)
120- return _c
121-}
122-
123-// ListPage provides a mock function for the type Tokens
124-func (_mock *Tokens) ListPage(ctx context.Context, p domain.Principal, size int, token string) (app.TokenPage, error) {
125- ret := _mock.Called(ctx, p, size, token)
126-
127- if len(ret) == 0 {
128- panic("no return value specified for ListPage")
129- }
130-
131- var r0 app.TokenPage
132- var r1 error
133- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, int, string) (app.TokenPage, error)); ok {
134- return returnFunc(ctx, p, size, token)
135- }
136- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, int, string) app.TokenPage); ok {
137- r0 = returnFunc(ctx, p, size, token)
138- } else {
139- r0 = ret.Get(0).(app.TokenPage)
140- }
141- if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, int, string) error); ok {
142- r1 = returnFunc(ctx, p, size, token)
143- } else {
144- r1 = ret.Error(1)
145- }
146- return r0, r1
147-}
148-
149-// Tokens_ListPage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListPage'
150-type Tokens_ListPage_Call struct {
151- *mock.Call
152-}
153-
154-// ListPage is a helper method to define mock.On call
155-// - ctx context.Context
156-// - p domain.Principal
157-// - size int
158-// - token string
159-func (_e *Tokens_Expecter) ListPage(ctx any, p any, size any, token any) *Tokens_ListPage_Call {
160- return &Tokens_ListPage_Call{Call: _e.mock.On("ListPage", ctx, p, size, token)}
161-}
162-
163-func (_c *Tokens_ListPage_Call) Run(run func(ctx context.Context, p domain.Principal, size int, token string)) *Tokens_ListPage_Call {
164- _c.Call.Run(func(args mock.Arguments) {
165- var arg0 context.Context
166- if args[0] != nil {
167- arg0 = args[0].(context.Context)
168- }
169- var arg1 domain.Principal
170- if args[1] != nil {
171- arg1 = args[1].(domain.Principal)
172- }
173- var arg2 int
174- if args[2] != nil {
175- arg2 = args[2].(int)
176- }
177- var arg3 string
178- if args[3] != nil {
179- arg3 = args[3].(string)
180- }
181- run(
182- arg0,
183- arg1,
184- arg2,
185- arg3,
186- )
187- })
188- return _c
189-}
190-
191-func (_c *Tokens_ListPage_Call) Return(tokenPage app.TokenPage, err error) *Tokens_ListPage_Call {
192- _c.Call.Return(tokenPage, err)
193- return _c
194-}
195-
196-func (_c *Tokens_ListPage_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, size int, token string) (app.TokenPage, error)) *Tokens_ListPage_Call {
197- _c.Call.Return(run)
198- return _c
199-}
200-
201-// Revoke provides a mock function for the type Tokens
202-func (_mock *Tokens) Revoke(ctx context.Context, p domain.Principal, id string) error {
203- ret := _mock.Called(ctx, p, id)
204-
205- if len(ret) == 0 {
206- panic("no return value specified for Revoke")
207- }
208-
209- var r0 error
210- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, string) error); ok {
211- r0 = returnFunc(ctx, p, id)
212- } else {
213- r0 = ret.Error(0)
214- }
215- return r0
216-}
217-
218-// Tokens_Revoke_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Revoke'
219-type Tokens_Revoke_Call struct {
220- *mock.Call
221-}
222-
223-// Revoke is a helper method to define mock.On call
224-// - ctx context.Context
225-// - p domain.Principal
226-// - id string
227-func (_e *Tokens_Expecter) Revoke(ctx any, p any, id any) *Tokens_Revoke_Call {
228- return &Tokens_Revoke_Call{Call: _e.mock.On("Revoke", ctx, p, id)}
229-}
230-
231-func (_c *Tokens_Revoke_Call) Run(run func(ctx context.Context, p domain.Principal, id string)) *Tokens_Revoke_Call {
232- _c.Call.Run(func(args mock.Arguments) {
233- var arg0 context.Context
234- if args[0] != nil {
235- arg0 = args[0].(context.Context)
236- }
237- var arg1 domain.Principal
238- if args[1] != nil {
239- arg1 = args[1].(domain.Principal)
240- }
241- var arg2 string
242- if args[2] != nil {
243- arg2 = args[2].(string)
244- }
245- run(
246- arg0,
247- arg1,
248- arg2,
249- )
250- })
251- return _c
252-}
253-
254-func (_c *Tokens_Revoke_Call) Return(err error) *Tokens_Revoke_Call {
255- _c.Call.Return(err)
256- return _c
257-}
258-
259-func (_c *Tokens_Revoke_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, id string) error) *Tokens_Revoke_Call {
260- _c.Call.Return(run)
261- return _c
262-}
M internal/adapters/rpc/repo.go +6 -6
@@ -9,12 +9,12 @@ import (
9 "connectrpc.com/connect"9 "connectrpc.com/connect"
10 10
11 pb "bnema.dev/ermite-api/gen/ermite"11 pb "bnema.dev/ermite-api/gen/ermite"
12- "bnema.dev/ermite/internal/app"
13 "bnema.dev/ermite/internal/domain"12 "bnema.dev/ermite/internal/domain"
13+ "bnema.dev/ermite/internal/ports"
14 )14 )
15 15
16 // repoService implements ermiteconnect.RepoServiceHandler.16 // repoService implements ermiteconnect.RepoServiceHandler.
17-type repoService struct{ repos Repos }17+type repoService struct{ repos ports.RepoService }
18 18
19 func (s *repoService) ListRepos(ctx context.Context, req *connect.Request[pb.ListReposRequest]) (*connect.Response[pb.ListReposResponse], error) {19 func (s *repoService) ListRepos(ctx context.Context, req *connect.Request[pb.ListReposRequest]) (*connect.Response[pb.ListReposResponse], error) {
20 page, err := s.repos.ListPage(ctx, caller(ctx), int(req.Msg.GetPageSize()), req.Msg.GetPageToken())20 page, err := s.repos.ListPage(ctx, caller(ctx), int(req.Msg.GetPageSize()), req.Msg.GetPageToken())
@@ -53,7 +53,7 @@ func (s *repoService) CreateRepo(ctx context.Context, req *connect.Request[pb.Cr
53 if err != nil {53 if err != nil {
54 return nil, connectError(ctx, err)54 return nil, connectError(ctx, err)
55 }55 }
56- repo, err := s.repos.Create(ctx, caller(ctx), app.CreateParams{56+ repo, err := s.repos.Create(ctx, caller(ctx), ports.CreateRepoParams{
57 Name: name, Description: req.Msg.GetDescription(), Visibility: vis,57 Name: name, Description: req.Msg.GetDescription(), Visibility: vis,
58 })58 })
59 if err != nil {59 if err != nil {
@@ -84,8 +84,8 @@ func (s *repoService) UpdateRepo(ctx context.Context, req *connect.Request[pb.Up
84 // nothing else.84 // nothing else.
85 //85 //
86 //hexcheck:ignore suspicious-business-logic-in-adapter maps the field mask of the contract to the optional fields of the use case; no rule is decided here86 //hexcheck:ignore suspicious-business-logic-in-adapter maps the field mask of the contract to the optional fields of the use case; no rule is decided here
87-func updateParams(msg *pb.UpdateRepoRequest) (app.UpdateParams, error) {87+func updateParams(msg *pb.UpdateRepoRequest) (ports.UpdateRepoParams, error) {
88- var params app.UpdateParams88+ var params ports.UpdateRepoParams
89 paths := msg.GetUpdateMask().GetPaths()89 paths := msg.GetUpdateMask().GetPaths()
90 if len(paths) == 0 {90 if len(paths) == 0 {
91 return params, fmt.Errorf("%w: update_mask is empty", domain.ErrInvalid)91 return params, fmt.Errorf("%w: update_mask is empty", domain.ErrInvalid)
@@ -135,7 +135,7 @@ func (s *repoService) DeleteRepo(ctx context.Context, req *connect.Request[pb.De
135 }135 }
136 136
137 // respondRepo adds the default branch to repo and wraps it in a response.137 // respondRepo adds the default branch to repo and wraps it in a response.
138-func respondRepo[T any](ctx context.Context, repos Repos, repo domain.Repo, wrap func(*pb.Repo) *T) (*connect.Response[T], error) {138+func respondRepo[T any](ctx context.Context, repos ports.RepoService, repo domain.Repo, wrap func(*pb.Repo) *T) (*connect.Response[T], error) {
139 view, err := repos.View(ctx, repo)139 view, err := repos.View(ctx, repo)
140 if err != nil {140 if err != nil {
141 return nil, connectError(ctx, err)141 return nil, connectError(ctx, err)
M internal/adapters/rpc/repo_test.go +16 -16
@@ -14,9 +14,9 @@ import (
14 "google.golang.org/protobuf/types/known/fieldmaskpb"14 "google.golang.org/protobuf/types/known/fieldmaskpb"
15 15
16 pb "bnema.dev/ermite-api/gen/ermite"16 pb "bnema.dev/ermite-api/gen/ermite"
17- "bnema.dev/ermite/internal/adapters/rpc/mocks"
18- "bnema.dev/ermite/internal/app"
19 "bnema.dev/ermite/internal/domain"17 "bnema.dev/ermite/internal/domain"
18+ "bnema.dev/ermite/internal/ports"
19+ "bnema.dev/ermite/internal/ports/mocks"
20 )20 )
21 21
22 // ownerCtx is the context of a call made by the owner, as the interceptor22 // ownerCtx is the context of a call made by the owner, as the interceptor
@@ -60,12 +60,12 @@ func TestRepoServiceGetRepo(t *testing.T) {
60 for _, tt := range tests {60 for _, tt := range tests {
61 t.Run(tt.name, func(t *testing.T) {61 t.Run(tt.name, func(t *testing.T) {
62 t.Parallel()62 t.Parallel()
63- repos := mocks.NewRepos(t)63+ repos := mocks.NewRepoService(t)
64 if tt.wantCall {64 if tt.wantCall {
65 repo := domain.Repo{Name: mustRepoName(t, "demo"), Description: "d", Visibility: domain.VisibilityPublic}65 repo := domain.Repo{Name: mustRepoName(t, "demo"), Description: "d", Visibility: domain.VisibilityPublic}
66 repos.EXPECT().Get(mock.Anything, domain.Owner(), repo.Name).Return(repo, tt.usecase).Once()66 repos.EXPECT().Get(mock.Anything, domain.Owner(), repo.Name).Return(repo, tt.usecase).Once()
67 if tt.usecase == nil {67 if tt.usecase == nil {
68- repos.EXPECT().View(mock.Anything, repo).Return(app.RepoView{Repo: repo, DefaultBranch: "main"}, tt.viewErr).Once()68+ repos.EXPECT().View(mock.Anything, repo).Return(ports.RepoView{Repo: repo, DefaultBranch: "main"}, tt.viewErr).Once()
69 }69 }
70 }70 }
71 svc := &repoService{repos}71 svc := &repoService{repos}
@@ -102,14 +102,14 @@ func TestRepoServiceCreateRepo(t *testing.T) {
102 for _, tt := range tests {102 for _, tt := range tests {
103 t.Run(tt.name, func(t *testing.T) {103 t.Run(tt.name, func(t *testing.T) {
104 t.Parallel()104 t.Parallel()
105- repos := mocks.NewRepos(t)105+ repos := mocks.NewRepoService(t)
106 if tt.wantCall {106 if tt.wantCall {
107 repo := domain.Repo{Name: mustRepoName(t, "demo"), Visibility: domain.VisibilityPrivate}107 repo := domain.Repo{Name: mustRepoName(t, "demo"), Visibility: domain.VisibilityPrivate}
108- repos.EXPECT().Create(mock.Anything, domain.Owner(), mock.MatchedBy(func(p app.CreateParams) bool {108+ repos.EXPECT().Create(mock.Anything, domain.Owner(), mock.MatchedBy(func(p ports.CreateRepoParams) bool {
109 return p.Name.String() == "demo"109 return p.Name.String() == "demo"
110 })).Return(repo, tt.usecase).Once()110 })).Return(repo, tt.usecase).Once()
111 if tt.usecase == nil {111 if tt.usecase == nil {
112- repos.EXPECT().View(mock.Anything, repo).Return(app.RepoView{Repo: repo}, nil).Once()112+ repos.EXPECT().View(mock.Anything, repo).Return(ports.RepoView{Repo: repo}, nil).Once()
113 }113 }
114 }114 }
115 resp, err := (&repoService{repos}).CreateRepo(ownerCtx(), connect.NewRequest(tt.req))115 resp, err := (&repoService{repos}).CreateRepo(ownerCtx(), connect.NewRequest(tt.req))
@@ -170,12 +170,12 @@ func TestRepoServiceUpdateRepoValidatesMask(t *testing.T) {
170 for _, tt := range tests {170 for _, tt := range tests {
171 t.Run(tt.name, func(t *testing.T) {171 t.Run(tt.name, func(t *testing.T) {
172 t.Parallel()172 t.Parallel()
173- repos := mocks.NewRepos(t)173+ repos := mocks.NewRepoService(t)
174 if tt.wantCall {174 if tt.wantCall {
175 repo := domain.Repo{Name: mustRepoName(t, "demo"), Description: "new"}175 repo := domain.Repo{Name: mustRepoName(t, "demo"), Description: "new"}
176 repos.EXPECT().Update(mock.Anything, domain.Owner(), repo.Name, mock.Anything).Return(repo, tt.usecase).Once()176 repos.EXPECT().Update(mock.Anything, domain.Owner(), repo.Name, mock.Anything).Return(repo, tt.usecase).Once()
177 if tt.usecase == nil {177 if tt.usecase == nil {
178- repos.EXPECT().View(mock.Anything, repo).Return(app.RepoView{Repo: repo}, nil).Once()178+ repos.EXPECT().View(mock.Anything, repo).Return(ports.RepoView{Repo: repo}, nil).Once()
179 }179 }
180 }180 }
181 resp, err := (&repoService{repos}).UpdateRepo(ownerCtx(), connect.NewRequest(tt.req))181 resp, err := (&repoService{repos}).UpdateRepo(ownerCtx(), connect.NewRequest(tt.req))
@@ -206,7 +206,7 @@ func TestRepoServiceDeleteRepo(t *testing.T) {
206 for _, tt := range tests {206 for _, tt := range tests {
207 t.Run(tt.name, func(t *testing.T) {207 t.Run(tt.name, func(t *testing.T) {
208 t.Parallel()208 t.Parallel()
209- repos := mocks.NewRepos(t)209+ repos := mocks.NewRepoService(t)
210 if tt.wantCall {210 if tt.wantCall {
211 repos.EXPECT().Delete(mock.Anything, domain.Owner(), mustRepoName(t, tt.repo)).Return(tt.usecase).Once()211 repos.EXPECT().Delete(mock.Anything, domain.Owner(), mustRepoName(t, tt.repo)).Return(tt.usecase).Once()
212 }212 }
@@ -224,12 +224,12 @@ func TestRepoServiceListRepos(t *testing.T) {
224 t.Parallel()224 t.Parallel()
225 t.Run("pages and views every repo", func(t *testing.T) {225 t.Run("pages and views every repo", func(t *testing.T) {
226 t.Parallel()226 t.Parallel()
227- repos := mocks.NewRepos(t)227+ repos := mocks.NewRepoService(t)
228 a := domain.Repo{Name: mustRepoName(t, "a")}228 a := domain.Repo{Name: mustRepoName(t, "a")}
229 b := domain.Repo{Name: mustRepoName(t, "b")}229 b := domain.Repo{Name: mustRepoName(t, "b")}
230- repos.EXPECT().ListPage(mock.Anything, domain.Owner(), 2, "tok").Return(app.RepoPage{Repos: []domain.Repo{a, b}, NextPageToken: "next"}, nil).Once()230+ repos.EXPECT().ListPage(mock.Anything, domain.Owner(), 2, "tok").Return(ports.RepoPage{Repos: []domain.Repo{a, b}, NextPageToken: "next"}, nil).Once()
231- repos.EXPECT().View(mock.Anything, a).Return(app.RepoView{Repo: a}, nil).Once()231+ repos.EXPECT().View(mock.Anything, a).Return(ports.RepoView{Repo: a}, nil).Once()
232- repos.EXPECT().View(mock.Anything, b).Return(app.RepoView{Repo: b}, nil).Once()232+ repos.EXPECT().View(mock.Anything, b).Return(ports.RepoView{Repo: b}, nil).Once()
233 resp, err := (&repoService{repos}).ListRepos(ownerCtx(), connect.NewRequest(&pb.ListReposRequest{PageSize: 2, PageToken: "tok"}))233 resp, err := (&repoService{repos}).ListRepos(ownerCtx(), connect.NewRequest(&pb.ListReposRequest{PageSize: 2, PageToken: "tok"}))
234 require.NoError(t, err)234 require.NoError(t, err)
235 require.Len(t, resp.Msg.GetRepos(), 2)235 require.Len(t, resp.Msg.GetRepos(), 2)
@@ -245,8 +245,8 @@ func TestRepoServiceListRepos(t *testing.T) {
245 } {245 } {
246 t.Run(name, func(t *testing.T) {246 t.Run(name, func(t *testing.T) {
247 t.Parallel()247 t.Parallel()
248- repos := mocks.NewRepos(t)248+ repos := mocks.NewRepoService(t)
249- repos.EXPECT().ListPage(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(app.RepoPage{}, tc.err).Once()249+ repos.EXPECT().ListPage(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(ports.RepoPage{}, tc.err).Once()
250 _, err := (&repoService{repos}).ListRepos(ownerCtx(), connect.NewRequest(&pb.ListReposRequest{}))250 _, err := (&repoService{repos}).ListRepos(ownerCtx(), connect.NewRequest(&pb.ListReposRequest{}))
251 requireCode(t, tc.code, err)251 requireCode(t, tc.code, err)
252 })252 })
M internal/adapters/rpc/server.go +2 -1
@@ -9,12 +9,13 @@ import (
9 "connectrpc.com/connect"9 "connectrpc.com/connect"
10 10
11 pb "bnema.dev/ermite-api/gen/ermite"11 pb "bnema.dev/ermite-api/gen/ermite"
12+ "bnema.dev/ermite/internal/ports"
12 )13 )
13 14
14 // serverService implements ermiteconnect.ServerServiceHandler.15 // serverService implements ermiteconnect.ServerServiceHandler.
15 type serverService struct {16 type serverService struct {
16 version string17 version string
17- key SigningKey18+ key ports.SigningKeyService
18 }19 }
19 20
20 func (s *serverService) GetVersion(context.Context, *connect.Request[pb.GetVersionRequest]) (*connect.Response[pb.GetVersionResponse], error) {21 func (s *serverService) GetVersion(context.Context, *connect.Request[pb.GetVersionRequest]) (*connect.Response[pb.GetVersionResponse], error) {
M internal/adapters/rpc/server_test.go +3 -3
@@ -11,13 +11,13 @@ import (
11 "github.com/stretchr/testify/require"11 "github.com/stretchr/testify/require"
12 12
13 pb "bnema.dev/ermite-api/gen/ermite"13 pb "bnema.dev/ermite-api/gen/ermite"
14- "bnema.dev/ermite/internal/adapters/rpc/mocks"
15 "bnema.dev/ermite/internal/domain"14 "bnema.dev/ermite/internal/domain"
15+ "bnema.dev/ermite/internal/ports/mocks"
16 )16 )
17 17
18 func TestServerServiceGetVersion(t *testing.T) {18 func TestServerServiceGetVersion(t *testing.T) {
19 t.Parallel()19 t.Parallel()
20- svc := &serverService{version: "v1.2.3", key: mocks.NewSigningKey(t)}20+ svc := &serverService{version: "v1.2.3", key: mocks.NewSigningKeyService(t)}
21 resp, err := svc.GetVersion(context.Background(), connect.NewRequest(&pb.GetVersionRequest{}))21 resp, err := svc.GetVersion(context.Background(), connect.NewRequest(&pb.GetVersionRequest{}))
22 require.NoError(t, err)22 require.NoError(t, err)
23 assert.Equal(t, "v1.2.3", resp.Msg.GetVersion())23 assert.Equal(t, "v1.2.3", resp.Msg.GetVersion())
@@ -30,7 +30,7 @@ func TestServerServiceGetVersion(t *testing.T) {
30 30
31 func TestServerServiceGetSigningKey(t *testing.T) {31 func TestServerServiceGetSigningKey(t *testing.T) {
32 t.Parallel()32 t.Parallel()
33- key := mocks.NewSigningKey(t)33+ key := mocks.NewSigningKeyService(t)
34 key.EXPECT().Info().Return(domain.SigningKeyInfo{34 key.EXPECT().Info().Return(domain.SigningKeyInfo{
35 Key: domain.SigningPublicKey{AuthorizedKey: "ssh-ed25519 AAAA", Fingerprint: "SHA256:xyz"},35 Key: domain.SigningPublicKey{AuthorizedKey: "ssh-ed25519 AAAA", Fingerprint: "SHA256:xyz"},
36 Committer: domain.Identity{Name: "ermite", Email: "ermite@example.com"},36 Committer: domain.Identity{Name: "ermite", Email: "ermite@example.com"},
D internal/adapters/rpc/services.go +0 -81
@@ -1,81 +0,0 @@
1-// SPDX-License-Identifier: AGPL-3.0-or-later
2-
3-package rpc
4-
5-import (
6- "context"
7- "io"
8- "time"
9-
10- "bnema.dev/ermite/internal/app"
11- "bnema.dev/ermite/internal/domain"
12-)
13-
14-// The interfaces below are what the handlers need of the use cases, declared
15-// where they are used. The types of app fit them; the mocks in rpc/mocks
16-// stand in for them in tests.
17-
18-// Repos is the repository use cases the API serves. *app.Repos fits.
19-type Repos interface {
20- ListPage(ctx context.Context, p domain.Principal, size int, token string) (app.RepoPage, error)
21- View(ctx context.Context, repo domain.Repo) (app.RepoView, error)
22- Get(ctx context.Context, p domain.Principal, name domain.RepoName) (domain.Repo, error)
23- Create(ctx context.Context, p domain.Principal, params app.CreateParams) (domain.Repo, error)
24- Update(ctx context.Context, p domain.Principal, name domain.RepoName, params app.UpdateParams) (domain.Repo, error)
25- Delete(ctx context.Context, p domain.Principal, name domain.RepoName) error
26-}
27-
28-// Tokens is the access token use cases the API serves. *app.Tokens fits.
29-type Tokens interface {
30- Create(ctx context.Context, p domain.Principal, params app.CreateTokenParams) (app.CreatedToken, error)
31- ListPage(ctx context.Context, p domain.Principal, size int, token string) (app.TokenPage, error)
32- Revoke(ctx context.Context, p domain.Principal, id string) error
33-}
34-
35-// Browse is the read side of a repository's content. *app.Browse fits.
36-type Browse interface {
37- ListRefs(ctx context.Context, p domain.Principal, repo domain.RepoName, params app.ListRefsParams) (app.RefPage, error)
38- ResolveRevision(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision) (domain.ResolvedRevision, error)
39- GetTree(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, path domain.TreePath, includeLastCommit bool) (domain.Tree, error)
40- OpenBlob(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, path domain.TreePath) (domain.BlobInfo, io.ReadCloser, error)
41- ListCommits(ctx context.Context, p domain.Principal, repo domain.RepoName, params app.ListCommitsParams) (app.CommitPage, error)
42- GetCommit(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision) (domain.CommitDetail, error)
43-}
44-
45-// Events streams the events of the forge. *app.Events fits.
46-type Events interface {
47- Watch(ctx context.Context, p domain.Principal, params app.WatchParams, send func(app.WatchEvent) error) error
48-}
49-
50-// MergeRequests is the merge request use cases. *app.MergeRequests fits.
51-type MergeRequests interface {
52- Create(ctx context.Context, p domain.Principal, name domain.RepoName, params app.CreateMergeRequestParams) (domain.MergeRequest, error)
53- List(ctx context.Context, p domain.Principal, name domain.RepoName, params app.ListMergeRequestsParams) (app.MergeRequestPage, error)
54- Get(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, sinceLastRead bool) (app.MergeRequestDetail, error)
55- AddComment(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, params app.AddCommentParams) (domain.Comment, error)
56- ResolveComment(ctx context.Context, p domain.Principal, name domain.RepoName, number, commentID int64, resolved bool) (domain.Comment, error)
57- Close(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (domain.MergeRequest, error)
58- MarkRead(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, head domain.ObjectID) (domain.ObjectID, error)
59- Merge(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, params app.MergeParams) (app.MergeResult, error)
60-}
61-
62-// SigningKey tells which key signs the commits of the forge. *app.SigningKey
63-// fits.
64-type SigningKey interface {
65- Info() domain.SigningKeyInfo
66-}
67-
68-// Sessions is the owner's web session use cases the API serves.
69-// *app.Sessions fits.
70-type Sessions interface {
71- CreateLoginLink(ctx context.Context, p domain.Principal) (app.LoginLink, error)
72- SignOutEverywhere(ctx context.Context, p domain.Principal) error
73-}
74-
75-// FailureLimiter counts the failed authentications per client. It is shared
76-// with the other adapters that authenticate, so that a client has one budget
77-// of failures. *clientlimit.Limiter fits.
78-type FailureLimiter interface {
79- Blocked(ctx context.Context, addr string) (bool, time.Duration)
80- Fail(ctx context.Context, addr string)
81-}
M internal/adapters/rpc/session.go +2 -1
@@ -8,11 +8,12 @@ import (
8 "connectrpc.com/connect"8 "connectrpc.com/connect"
9 9
10 pb "bnema.dev/ermite-api/gen/ermite"10 pb "bnema.dev/ermite-api/gen/ermite"
11+ "bnema.dev/ermite/internal/ports"
11 )12 )
12 13
13 // sessionService implements ermiteconnect.SessionServiceHandler. The browser14 // sessionService implements ermiteconnect.SessionServiceHandler. The browser
14 // never calls it: sessions come from the link, and the API ignores cookies.15 // never calls it: sessions come from the link, and the API ignores cookies.
15-type sessionService struct{ sessions Sessions }16+type sessionService struct{ sessions ports.SessionService }
16 17
17 func (s *sessionService) CreateLoginLink(ctx context.Context, _ *connect.Request[pb.CreateLoginLinkRequest]) (*connect.Response[pb.CreateLoginLinkResponse], error) {18 func (s *sessionService) CreateLoginLink(ctx context.Context, _ *connect.Request[pb.CreateLoginLinkRequest]) (*connect.Response[pb.CreateLoginLinkResponse], error) {
18 link, err := s.sessions.CreateLoginLink(ctx, caller(ctx))19 link, err := s.sessions.CreateLoginLink(ctx, caller(ctx))
M internal/adapters/rpc/session_test.go +5 -5
@@ -12,9 +12,9 @@ import (
12 "github.com/stretchr/testify/require"12 "github.com/stretchr/testify/require"
13 13
14 pb "bnema.dev/ermite-api/gen/ermite"14 pb "bnema.dev/ermite-api/gen/ermite"
15- "bnema.dev/ermite/internal/adapters/rpc/mocks"
16- "bnema.dev/ermite/internal/app"
17 "bnema.dev/ermite/internal/domain"15 "bnema.dev/ermite/internal/domain"
16+ "bnema.dev/ermite/internal/ports"
17+ "bnema.dev/ermite/internal/ports/mocks"
18 )18 )
19 19
20 func TestSessionServiceCreateLoginLink(t *testing.T) {20 func TestSessionServiceCreateLoginLink(t *testing.T) {
@@ -32,9 +32,9 @@ func TestSessionServiceCreateLoginLink(t *testing.T) {
32 for _, tt := range tests {32 for _, tt := range tests {
33 t.Run(tt.name, func(t *testing.T) {33 t.Run(tt.name, func(t *testing.T) {
34 t.Parallel()34 t.Parallel()
35- sessions := mocks.NewSessions(t)35+ sessions := mocks.NewSessionService(t)
36 sessions.EXPECT().CreateLoginLink(mock.Anything, domain.Owner()).36 sessions.EXPECT().CreateLoginLink(mock.Anything, domain.Owner()).
37- Return(app.LoginLink{URL: "https://x/-/login/n", ExpiresAt: exp}, tt.usecase).Once()37+ Return(ports.LoginLink{URL: "https://x/-/login/n", ExpiresAt: exp}, tt.usecase).Once()
38 resp, err := (&sessionService{sessions}).CreateLoginLink(ownerCtx(), connect.NewRequest(&pb.CreateLoginLinkRequest{}))38 resp, err := (&sessionService{sessions}).CreateLoginLink(ownerCtx(), connect.NewRequest(&pb.CreateLoginLinkRequest{}))
39 if tt.wantCode != 0 {39 if tt.wantCode != 0 {
40 requireCode(t, tt.wantCode, err)40 requireCode(t, tt.wantCode, err)
@@ -60,7 +60,7 @@ func TestSessionServiceSignOutEverywhere(t *testing.T) {
60 for _, tt := range tests {60 for _, tt := range tests {
61 t.Run(tt.name, func(t *testing.T) {61 t.Run(tt.name, func(t *testing.T) {
62 t.Parallel()62 t.Parallel()
63- sessions := mocks.NewSessions(t)63+ sessions := mocks.NewSessionService(t)
64 sessions.EXPECT().SignOutEverywhere(mock.Anything, domain.Owner()).Return(tt.usecase).Once()64 sessions.EXPECT().SignOutEverywhere(mock.Anything, domain.Owner()).Return(tt.usecase).Once()
65 _, err := (&sessionService{sessions}).SignOutEverywhere(ownerCtx(), connect.NewRequest(&pb.SignOutEverywhereRequest{}))65 _, err := (&sessionService{sessions}).SignOutEverywhere(ownerCtx(), connect.NewRequest(&pb.SignOutEverywhereRequest{}))
66 if tt.wantCode != 0 {66 if tt.wantCode != 0 {
M internal/adapters/rpc/token.go +3 -3
@@ -8,15 +8,15 @@ import (
8 "connectrpc.com/connect"8 "connectrpc.com/connect"
9 9
10 pb "bnema.dev/ermite-api/gen/ermite"10 pb "bnema.dev/ermite-api/gen/ermite"
11- "bnema.dev/ermite/internal/app"
12 "bnema.dev/ermite/internal/domain"11 "bnema.dev/ermite/internal/domain"
12+ "bnema.dev/ermite/internal/ports"
13 )13 )
14 14
15 // tokenService implements ermiteconnect.TokenServiceHandler.15 // tokenService implements ermiteconnect.TokenServiceHandler.
16-type tokenService struct{ tokens Tokens }16+type tokenService struct{ tokens ports.TokenService }
17 17
18 func (s *tokenService) CreateToken(ctx context.Context, req *connect.Request[pb.CreateTokenRequest]) (*connect.Response[pb.CreateTokenResponse], error) {18 func (s *tokenService) CreateToken(ctx context.Context, req *connect.Request[pb.CreateTokenRequest]) (*connect.Response[pb.CreateTokenResponse], error) {
19- params := app.CreateTokenParams{19+ params := ports.CreateTokenParams{
20 Label: req.Msg.GetLabel(),20 Label: req.Msg.GetLabel(),
21 ExpiresAt: goTime(req.Msg.GetExpiresAt()),21 ExpiresAt: goTime(req.Msg.GetExpiresAt()),
22 }22 }
M internal/adapters/rpc/token_test.go +11 -11
@@ -11,9 +11,9 @@ import (
11 "github.com/stretchr/testify/require"11 "github.com/stretchr/testify/require"
12 12
13 pb "bnema.dev/ermite-api/gen/ermite"13 pb "bnema.dev/ermite-api/gen/ermite"
14- "bnema.dev/ermite/internal/adapters/rpc/mocks"
15- "bnema.dev/ermite/internal/app"
16 "bnema.dev/ermite/internal/domain"14 "bnema.dev/ermite/internal/domain"
15+ "bnema.dev/ermite/internal/ports"
16+ "bnema.dev/ermite/internal/ports/mocks"
17 )17 )
18 18
19 func TestTokenServiceCreateToken(t *testing.T) {19 func TestTokenServiceCreateToken(t *testing.T) {
@@ -35,10 +35,10 @@ func TestTokenServiceCreateToken(t *testing.T) {
35 for _, tt := range tests {35 for _, tt := range tests {
36 t.Run(tt.name, func(t *testing.T) {36 t.Run(tt.name, func(t *testing.T) {
37 t.Parallel()37 t.Parallel()
38- tokens := mocks.NewTokens(t)38+ tokens := mocks.NewTokenService(t)
39 if tt.wantCall {39 if tt.wantCall {
40 tokens.EXPECT().Create(mock.Anything, domain.Owner(), mock.Anything).40 tokens.EXPECT().Create(mock.Anything, domain.Owner(), mock.Anything).
41- Return(app.CreatedToken{Token: domain.Token{ID: "t1", Label: "ci"}, Secret: "s3cret"}, tt.usecase).Once()41+ Return(ports.CreatedToken{Token: domain.Token{ID: "t1", Label: "ci"}, Secret: "s3cret"}, tt.usecase).Once()
42 }42 }
43 resp, err := (&tokenService{tokens}).CreateToken(ownerCtx(), connect.NewRequest(tt.req))43 resp, err := (&tokenService{tokens}).CreateToken(ownerCtx(), connect.NewRequest(tt.req))
44 if tt.wantCode != 0 {44 if tt.wantCode != 0 {
@@ -54,10 +54,10 @@ func TestTokenServiceCreateToken(t *testing.T) {
54 54
55 func TestTokenServiceCreateTokenPassesParams(t *testing.T) {55 func TestTokenServiceCreateTokenPassesParams(t *testing.T) {
56 t.Parallel()56 t.Parallel()
57- tokens := mocks.NewTokens(t)57+ tokens := mocks.NewTokenService(t)
58- tokens.EXPECT().Create(mock.Anything, domain.Owner(), mock.MatchedBy(func(p app.CreateTokenParams) bool {58+ tokens.EXPECT().Create(mock.Anything, domain.Owner(), mock.MatchedBy(func(p ports.CreateTokenParams) bool {
59 return p.Label == "ci" && len(p.Scopes) == 1 && len(p.Repos) == 1 && p.Repos[0].String() == "demo"59 return p.Label == "ci" && len(p.Scopes) == 1 && len(p.Repos) == 1 && p.Repos[0].String() == "demo"
60- })).Return(app.CreatedToken{}, nil).Once()60+ })).Return(ports.CreatedToken{}, nil).Once()
61 _, err := (&tokenService{tokens}).CreateToken(ownerCtx(), connect.NewRequest(&pb.CreateTokenRequest{61 _, err := (&tokenService{tokens}).CreateToken(ownerCtx(), connect.NewRequest(&pb.CreateTokenRequest{
62 Label: "ci", Scopes: []pb.Scope{pb.Scope_SCOPE_REPO_READ}, Repos: []string{"demo"},62 Label: "ci", Scopes: []pb.Scope{pb.Scope_SCOPE_REPO_READ}, Repos: []string{"demo"},
63 }))63 }))
@@ -78,7 +78,7 @@ func TestTokenServiceRevokeToken(t *testing.T) {
78 for _, tt := range tests {78 for _, tt := range tests {
79 t.Run(tt.name, func(t *testing.T) {79 t.Run(tt.name, func(t *testing.T) {
80 t.Parallel()80 t.Parallel()
81- tokens := mocks.NewTokens(t)81+ tokens := mocks.NewTokenService(t)
82 tokens.EXPECT().Revoke(mock.Anything, domain.Owner(), "t1").Return(tt.usecase).Once()82 tokens.EXPECT().Revoke(mock.Anything, domain.Owner(), "t1").Return(tt.usecase).Once()
83 _, err := (&tokenService{tokens}).RevokeToken(ownerCtx(), connect.NewRequest(&pb.RevokeTokenRequest{Id: "t1"}))83 _, err := (&tokenService{tokens}).RevokeToken(ownerCtx(), connect.NewRequest(&pb.RevokeTokenRequest{Id: "t1"}))
84 if tt.wantCode != 0 {84 if tt.wantCode != 0 {
@@ -92,15 +92,15 @@ func TestTokenServiceRevokeToken(t *testing.T) {
92 92
93 func TestTokenServiceListTokens(t *testing.T) {93 func TestTokenServiceListTokens(t *testing.T) {
94 t.Parallel()94 t.Parallel()
95- tokens := mocks.NewTokens(t)95+ tokens := mocks.NewTokenService(t)
96 tokens.EXPECT().ListPage(mock.Anything, domain.Owner(), 5, "tok").96 tokens.EXPECT().ListPage(mock.Anything, domain.Owner(), 5, "tok").
97- Return(app.TokenPage{Tokens: []domain.Token{{ID: "a"}, {ID: "b"}}, NextPageToken: "n"}, nil).Once()97+ Return(ports.TokenPage{Tokens: []domain.Token{{ID: "a"}, {ID: "b"}}, NextPageToken: "n"}, nil).Once()
98 resp, err := (&tokenService{tokens}).ListTokens(ownerCtx(), connect.NewRequest(&pb.ListTokensRequest{PageSize: 5, PageToken: "tok"}))98 resp, err := (&tokenService{tokens}).ListTokens(ownerCtx(), connect.NewRequest(&pb.ListTokensRequest{PageSize: 5, PageToken: "tok"}))
99 require.NoError(t, err)99 require.NoError(t, err)
100 assert.Len(t, resp.Msg.GetTokens(), 2)100 assert.Len(t, resp.Msg.GetTokens(), 2)
101 assert.Equal(t, "n", resp.Msg.GetNextPageToken())101 assert.Equal(t, "n", resp.Msg.GetNextPageToken())
102 102
103- tokens.EXPECT().ListPage(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(app.TokenPage{}, domain.ErrInvalidPageToken).Once()103+ tokens.EXPECT().ListPage(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(ports.TokenPage{}, domain.ErrInvalidPageToken).Once()
104 _, err = (&tokenService{tokens}).ListTokens(ownerCtx(), connect.NewRequest(&pb.ListTokensRequest{}))104 _, err = (&tokenService{tokens}).ListTokens(ownerCtx(), connect.NewRequest(&pb.ListTokensRequest{}))
105 requireCode(t, connect.CodeInvalidArgument, err)105 requireCode(t, connect.CodeInvalidArgument, err)
106 }106 }
M internal/adapters/web/events_test.go +6 -5
@@ -20,7 +20,8 @@ import (
20 20
21 pb "bnema.dev/ermite-api/gen/ermite"21 pb "bnema.dev/ermite-api/gen/ermite"
22 "bnema.dev/ermite-api/gen/ermite/ermiteconnect"22 "bnema.dev/ermite-api/gen/ermite/ermiteconnect"
23- "bnema.dev/ermite/internal/adapters/web/mocks"23+ "bnema.dev/ermite/internal/ports"
24+ "bnema.dev/ermite/internal/ports/mocks"
24 )25 )
25 26
26 func TestFormatEvent(t *testing.T) {27 func TestFormatEvent(t *testing.T) {
@@ -115,7 +116,7 @@ func eventsServer(t *testing.T, f *fakeEvents, beat time.Duration, writeTimeout
115 }116 }
116 117
117 // eventsServerWith is eventsServer with sessions, when sess is not nil.118 // eventsServerWith is eventsServer with sessions, when sess is not nil.
118-func eventsServerWith(t *testing.T, f *fakeEvents, beat time.Duration, writeTimeout time.Duration, sess SessionAuth) *httptest.Server {119+func eventsServerWith(t *testing.T, f *fakeEvents, beat time.Duration, writeTimeout time.Duration, sess ports.WebSessionService) *httptest.Server {
119 t.Helper()120 t.Helper()
120 api := http.NewServeMux()121 api := http.NewServeMux()
121 api.Handle(ermiteconnect.NewEventServiceHandler(f))122 api.Handle(ermiteconnect.NewEventServiceHandler(f))
@@ -287,16 +288,16 @@ func TestRepoUpdatedReloadsSummaryAndRefs(t *testing.T) {
287 }288 }
288 }289 }
289 290
290-func limiterFor(sess SessionAuth) FailureLimiter {291+func limiterFor(sess ports.WebSessionService) ports.FailureLimiter {
291 if sess == nil {292 if sess == nil {
292 return nil293 return nil
293 }294 }
294 return &countingLimiter{}295 return &countingLimiter{}
295 }296 }
296 297
297-// newRevocable is a SessionAuth whose one session ends when the test sets298+// newRevocable is a ports.WebSessionService whose one session ends when the test sets
298 // ended, like SignOutEverywhere does.299 // ended, like SignOutEverywhere does.
299-func newRevocable(t *testing.T, valid string) (*mocks.SessionAuth, *atomic.Bool) {300+func newRevocable(t *testing.T, valid string) (*mocks.WebSessionService, *atomic.Bool) {
300 t.Helper()301 t.Helper()
301 var ended atomic.Bool302 var ended atomic.Bool
302 return newVerifier(t, valid, func(tok string) (bool, error) { return tok == valid && !ended.Load(), nil }), &ended303 return newVerifier(t, valid, func(tok string) (bool, error) { return tok == valid && !ended.Load(), nil }), &ended
D internal/adapters/web/mocks/FailureLimiter.go +0 -160
@@ -1,160 +0,0 @@
1-// Code generated by mockery; DO NOT EDIT.
2-// github.com/vektra/mockery
3-// template: testify
4-
5-package mocks
6-
7-import (
8- "context"
9- "time"
10-
11- mock "github.com/stretchr/testify/mock"
12-)
13-
14-// NewFailureLimiter creates a new instance of FailureLimiter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
15-// The first argument is typically a *testing.T value.
16-func NewFailureLimiter(t interface {
17- mock.TestingT
18- Cleanup(func())
19-}) *FailureLimiter {
20- if helper, ok := t.(interface{ Helper() }); ok {
21- helper.Helper()
22- }
23-
24- mock := &FailureLimiter{}
25- mock.Mock.Test(t)
26-
27- t.Cleanup(func() {
28- if helper, ok := t.(interface{ Helper() }); ok {
29- helper.Helper()
30- }
31- mock.AssertExpectations(t)
32- })
33-
34- return mock
35-}
36-
37-// FailureLimiter is an autogenerated mock type for the FailureLimiter type
38-type FailureLimiter struct {
39- mock.Mock
40-}
41-
42-type FailureLimiter_Expecter struct {
43- mock *mock.Mock
44-}
45-
46-func (_m *FailureLimiter) EXPECT() *FailureLimiter_Expecter {
47- return &FailureLimiter_Expecter{mock: &_m.Mock}
48-}
49-
50-// Blocked provides a mock function for the type FailureLimiter
51-func (_mock *FailureLimiter) Blocked(ctx context.Context, addr string) (bool, time.Duration) {
52- ret := _mock.Called(ctx, addr)
53-
54- if len(ret) == 0 {
55- panic("no return value specified for Blocked")
56- }
57-
58- var r0 bool
59- var r1 time.Duration
60- if returnFunc, ok := ret.Get(0).(func(context.Context, string) (bool, time.Duration)); ok {
61- return returnFunc(ctx, addr)
62- }
63- if returnFunc, ok := ret.Get(0).(func(context.Context, string) bool); ok {
64- r0 = returnFunc(ctx, addr)
65- } else {
66- r0 = ret.Get(0).(bool)
67- }
68- if returnFunc, ok := ret.Get(1).(func(context.Context, string) time.Duration); ok {
69- r1 = returnFunc(ctx, addr)
70- } else {
71- r1 = ret.Get(1).(time.Duration)
72- }
73- return r0, r1
74-}
75-
76-// FailureLimiter_Blocked_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Blocked'
77-type FailureLimiter_Blocked_Call struct {
78- *mock.Call
79-}
80-
81-// Blocked is a helper method to define mock.On call
82-// - ctx context.Context
83-// - addr string
84-func (_e *FailureLimiter_Expecter) Blocked(ctx any, addr any) *FailureLimiter_Blocked_Call {
85- return &FailureLimiter_Blocked_Call{Call: _e.mock.On("Blocked", ctx, addr)}
86-}
87-
88-func (_c *FailureLimiter_Blocked_Call) Run(run func(ctx context.Context, addr string)) *FailureLimiter_Blocked_Call {
89- _c.Call.Run(func(args mock.Arguments) {
90- var arg0 context.Context
91- if args[0] != nil {
92- arg0 = args[0].(context.Context)
93- }
94- var arg1 string
95- if args[1] != nil {
96- arg1 = args[1].(string)
97- }
98- run(
99- arg0,
100- arg1,
101- )
102- })
103- return _c
104-}
105-
106-func (_c *FailureLimiter_Blocked_Call) Return(b bool, duration time.Duration) *FailureLimiter_Blocked_Call {
107- _c.Call.Return(b, duration)
108- return _c
109-}
110-
111-func (_c *FailureLimiter_Blocked_Call) RunAndReturn(run func(ctx context.Context, addr string) (bool, time.Duration)) *FailureLimiter_Blocked_Call {
112- _c.Call.Return(run)
113- return _c
114-}
115-
116-// Fail provides a mock function for the type FailureLimiter
117-func (_mock *FailureLimiter) Fail(ctx context.Context, addr string) {
118- _mock.Called(ctx, addr)
119- return
120-}
121-
122-// FailureLimiter_Fail_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Fail'
123-type FailureLimiter_Fail_Call struct {
124- *mock.Call
125-}
126-
127-// Fail is a helper method to define mock.On call
128-// - ctx context.Context
129-// - addr string
130-func (_e *FailureLimiter_Expecter) Fail(ctx any, addr any) *FailureLimiter_Fail_Call {
131- return &FailureLimiter_Fail_Call{Call: _e.mock.On("Fail", ctx, addr)}
132-}
133-
134-func (_c *FailureLimiter_Fail_Call) Run(run func(ctx context.Context, addr string)) *FailureLimiter_Fail_Call {
135- _c.Call.Run(func(args mock.Arguments) {
136- var arg0 context.Context
137- if args[0] != nil {
138- arg0 = args[0].(context.Context)
139- }
140- var arg1 string
141- if args[1] != nil {
142- arg1 = args[1].(string)
143- }
144- run(
145- arg0,
146- arg1,
147- )
148- })
149- return _c
150-}
151-
152-func (_c *FailureLimiter_Fail_Call) Return() *FailureLimiter_Fail_Call {
153- _c.Call.Return()
154- return _c
155-}
156-
157-func (_c *FailureLimiter_Fail_Call) RunAndReturn(run func(ctx context.Context, addr string)) *FailureLimiter_Fail_Call {
158- _c.Run(run)
159- return _c
160-}
M internal/adapters/web/session.go +3 -27
@@ -16,6 +16,7 @@ import (
16 "github.com/bnema/zerowrap"16 "github.com/bnema/zerowrap"
17 17
18 "bnema.dev/ermite/internal/clientlimit"18 "bnema.dev/ermite/internal/clientlimit"
19+ "bnema.dev/ermite/internal/ports"
19 )20 )
20 21
21 // The owner's session on the web (ADR 0018).22 // The owner's session on the web (ADR 0018).
@@ -39,31 +40,6 @@ const CookieName = "__Host-ermite"
39 // loginPrefix is the path of login links, up to the nonce.40 // loginPrefix is the path of login links, up to the nonce.
40 const loginPrefix = "/-/login/"41 const loginPrefix = "/-/login/"
41 42
42-// ErrLinkRejected is what SessionAuth.Login returns for a link that is
43-// unknown, expired or already used.
44-var ErrLinkRejected = errors.New("login link rejected")
45-
46-// SessionAuth is the session side of the use cases, as the web needs it.
47-type SessionAuth interface {
48- // LoginLinkValid reports whether the link is pending, without using it.
49- LoginLinkValid(nonce string) bool
50- // Login uses the link and returns the session token and how long it
51- // lasts, or ErrLinkRejected.
52- Login(ctx context.Context, nonce string) (token string, lifetime time.Duration, err error)
53- // Verify checks a session token. When it is valid it returns the context
54- // to serve the request with, which carries the owner as the caller of the
55- // API; when it is not, ok is false and err nil. An error means the check
56- // could not be made (the request then runs as anonymous).
57- Verify(ctx context.Context, token string) (owner context.Context, ok bool, err error)
58-}
59-
60-// FailureLimiter counts failed logins per client, with the failed
61-// authentications of the other routes. *clientlimit.Limiter fits.
62-type FailureLimiter interface {
63- Blocked(ctx context.Context, addr string) (bool, time.Duration)
64- Fail(ctx context.Context, addr string)
65-}
66-
67 type signedInKey struct{}43 type signedInKey struct{}
68 44
69 // SignedIn reports whether the request carries a valid owner session. The45 // SignedIn reports whether the request carries a valid owner session. The
@@ -275,12 +251,12 @@ func (s *Server) loginPost(w http.ResponseWriter, r *http.Request) {
275 nonce := r.PathValue("nonce")251 nonce := r.PathValue("nonce")
276 var token string252 var token string
277 var lifetime time.Duration253 var lifetime time.Duration
278- err := ErrLinkRejected254+ err := ports.ErrLinkRejected
279 if validNonce(nonce) {255 if validNonce(nonce) {
280 token, lifetime, err = s.cfg.Sessions.Login(ctx, nonce)256 token, lifetime, err = s.cfg.Sessions.Login(ctx, nonce)
281 }257 }
282 switch {258 switch {
283- case errors.Is(err, ErrLinkRejected):259+ case errors.Is(err, ports.ErrLinkRejected):
284 s.cfg.Limiter.Fail(ctx, addr)260 s.cfg.Limiter.Fail(ctx, addr)
285 s.secretPage(w, r, http.StatusNotFound, s.loginRejectedPage())261 s.secretPage(w, r, http.StatusNotFound, s.loginRejectedPage())
286 return262 return
M internal/adapters/web/session_test.go +13 -12
@@ -17,21 +17,22 @@ import (
17 "github.com/stretchr/testify/require"17 "github.com/stretchr/testify/require"
18 18
19 "bnema.dev/ermite-api/gen/ermite/ermiteconnect"19 "bnema.dev/ermite-api/gen/ermite/ermiteconnect"
20- "bnema.dev/ermite/internal/adapters/web/mocks"20+ "bnema.dev/ermite/internal/ports"
21+ "bnema.dev/ermite/internal/ports/mocks"
21 )22 )
22 23
23 // nonceA is the one valid login link of the mocks.24 // nonceA is the one valid login link of the mocks.
24 var nonceA = strings.Repeat("a", 43)25 var nonceA = strings.Repeat("a", 43)
25 26
26-// newVerifier is a SessionAuth that accepts the session token valid, checked27+// newVerifier is a WebSessionService that accepts the session token valid, checked
27 // by verify when it is not nil, and that does not know any login link. A28 // by verify when it is not nil, and that does not know any login link. A
28 // call that is not expected fails the test.29 // call that is not expected fails the test.
29-func newVerifier(t *testing.T, valid string, verify func(token string) (bool, error)) *mocks.SessionAuth {30+func newVerifier(t *testing.T, valid string, verify func(token string) (bool, error)) *mocks.WebSessionService {
30 t.Helper()31 t.Helper()
31 if verify == nil {32 if verify == nil {
32 verify = func(token string) (bool, error) { return token == valid, nil }33 verify = func(token string) (bool, error) { return token == valid, nil }
33 }34 }
34- m := mocks.NewSessionAuth(t)35+ m := mocks.NewWebSessionService(t)
35 m.EXPECT().Verify(mock.Anything, mock.Anything).RunAndReturn(36 m.EXPECT().Verify(mock.Anything, mock.Anything).RunAndReturn(
36 func(ctx context.Context, token string) (context.Context, bool, error) {37 func(ctx context.Context, token string) (context.Context, bool, error) {
37 ok, err := verify(token)38 ok, err := verify(token)
@@ -41,22 +42,22 @@ func newVerifier(t *testing.T, valid string, verify func(token string) (bool, er
41 }42 }
42 43
43 // expectLogin makes nonceA the one link of m, answering with the token valid.44 // expectLogin makes nonceA the one link of m, answering with the token valid.
44-func expectLogin(m *mocks.SessionAuth, valid string) {45+func expectLogin(m *mocks.WebSessionService, valid string) {
45 m.EXPECT().LoginLinkValid(mock.Anything).RunAndReturn(func(n string) bool { return n == nonceA }).Maybe()46 m.EXPECT().LoginLinkValid(mock.Anything).RunAndReturn(func(n string) bool { return n == nonceA }).Maybe()
46 m.EXPECT().Login(mock.Anything, nonceA).Return(valid, time.Hour, nil).Maybe()47 m.EXPECT().Login(mock.Anything, nonceA).Return(valid, time.Hour, nil).Maybe()
47- m.EXPECT().Login(mock.Anything, mock.Anything).Return("", time.Duration(0), ErrLinkRejected).Maybe()48+ m.EXPECT().Login(mock.Anything, mock.Anything).Return("", time.Duration(0), ports.ErrLinkRejected).Maybe()
48 }49 }
49 50
50-// newSessions is a SessionAuth with one session token and one login link.51+// newSessions is a WebSessionService with one session token and one login link.
51-func newSessions(t *testing.T, valid string) *mocks.SessionAuth {52+func newSessions(t *testing.T, valid string) *mocks.WebSessionService {
52 t.Helper()53 t.Helper()
53 m := newVerifier(t, valid, nil)54 m := newVerifier(t, valid, nil)
54 expectLogin(m, valid)55 expectLogin(m, valid)
55 return m56 return m
56 }57 }
57 58
58-// newBrokenSessions is a SessionAuth whose check cannot be made.59+// newBrokenSessions is a WebSessionService whose check cannot be made.
59-func newBrokenSessions(t *testing.T, valid string) *mocks.SessionAuth {60+func newBrokenSessions(t *testing.T, valid string) *mocks.WebSessionService {
60 t.Helper()61 t.Helper()
61 m := newVerifier(t, valid, func(string) (bool, error) { return false, errors.New("kv down") })62 m := newVerifier(t, valid, func(string) (bool, error) { return false, errors.New("kv down") })
62 expectLogin(m, valid)63 expectLogin(m, valid)
@@ -68,7 +69,7 @@ type countingLimiter struct{ fails int }
68 func (*countingLimiter) Blocked(context.Context, string) (bool, time.Duration) { return false, 0 }69 func (*countingLimiter) Blocked(context.Context, string) (bool, time.Duration) { return false, 0 }
69 func (l *countingLimiter) Fail(context.Context, string) { l.fails++ }70 func (l *countingLimiter) Fail(context.Context, string) { l.fails++ }
70 71
71-func newSessionServer(t *testing.T, sess SessionAuth, publicURL string) (*Server, *countingLimiter) {72+func newSessionServer(t *testing.T, sess ports.WebSessionService, publicURL string) (*Server, *countingLimiter) {
72 t.Helper()73 t.Helper()
73 lim := &countingLimiter{}74 lim := &countingLimiter{}
74 srv, err := New(Config{75 srv, err := New(Config{
@@ -294,7 +295,7 @@ func TestNoSessionsNoRoutes(t *testing.T) {
294 295
295 _, err = New(Config{296 _, err = New(Config{
296 Repos: failing{}, Git: ermiteconnect.NewGitServiceClient(http.DefaultClient, "http://unused.invalid"),297 Repos: failing{}, Git: ermiteconnect.NewGitServiceClient(http.DefaultClient, "http://unused.invalid"),
297- PublicURL: "https://forge.example", Sessions: mocks.NewSessionAuth(t),298+ PublicURL: "https://forge.example", Sessions: mocks.NewWebSessionService(t),
298 })299 })
299 assert.Error(t, err, "sessions need a limiter")300 assert.Error(t, err, "sessions need a limiter")
300 }301 }
M internal/adapters/web/web.go +3 -2
@@ -33,6 +33,7 @@ import (
33 "github.com/bnema/zerowrap"33 "github.com/bnema/zerowrap"
34 34
35 "bnema.dev/ermite-api/gen/ermite/ermiteconnect"35 "bnema.dev/ermite-api/gen/ermite/ermiteconnect"
36+ "bnema.dev/ermite/internal/ports"
36 )37 )
37 38
38 // Patterns are the ServeMux patterns the handler serves: everything. The39 // Patterns are the ServeMux patterns the handler serves: everything. The
@@ -69,10 +70,10 @@ type Config struct {
69 Now func() time.Time70 Now func() time.Time
70 // Sessions signs the owner in (ADR 0018). Without it there are no login71 // Sessions signs the owner in (ADR 0018). Without it there are no login
71 // routes and every visitor is anonymous.72 // routes and every visitor is anonymous.
72- Sessions SessionAuth73+ Sessions ports.WebSessionService
73 // Limiter counts the failed logins; required with Sessions. Share it with74 // Limiter counts the failed logins; required with Sessions. Share it with
74 // the API and Git, so that a client has one budget of failures.75 // the API and Git, so that a client has one budget of failures.
75- Limiter FailureLimiter76+ Limiter ports.FailureLimiter
76 // TrustedProxies are the networks allowed to set X-Forwarded-For, to find77 // TrustedProxies are the networks allowed to set X-Forwarded-For, to find
77 // the address of the client that fails a login.78 // the address of the client that fails a login.
78 TrustedProxies []netip.Prefix79 TrustedProxies []netip.Prefix
M internal/app/browse.go +15 -52
@@ -31,6 +31,8 @@ type Browse struct {
31 history *historyCache31 history *historyCache
32 }32 }
33 33
34+var _ ports.BrowseService = (*Browse)(nil)
35+
34 // NewBrowse builds the read use cases over their ports, with a history cache36 // NewBrowse builds the read use cases over their ports, with a history cache
35 // of the default bounds.37 // of the default bounds.
36 func NewBrowse(meta ports.RepoMetaStore, reader ports.RepoReader) *Browse {38 func NewBrowse(meta ports.RepoMetaStore, reader ports.RepoReader) *Browse {
@@ -71,23 +73,6 @@ func repoEpoch(r domain.Repo) int64 {
71 return r.CreatedAt.UnixNano()73 return r.CreatedAt.UnixNano()
72 }74 }
73 75
74-// ListRefsParams selects the refs to list.
75-type ListRefsParams struct {
76- // Kinds restricts the result; empty means every kind.
77- Kinds []domain.RefKind
78- // PageSize is 0 for the default, and lowered to MaxRefPageSize.
79- PageSize int
80- // PageToken is the NextPageToken of the previous page, or empty.
81- PageToken string
82-}
83-
84-// RefPage is a page of refs.
85-type RefPage struct {
86- Refs []domain.Ref
87- // NextPageToken is empty on the last page.
88- NextPageToken string
89-}
90-
91 func kindRank(k domain.RefKind) int {76 func kindRank(k domain.RefKind) int {
92 switch k {77 switch k {
93 case domain.RefKindHead:78 case domain.RefKindHead:
@@ -101,26 +86,26 @@ func kindRank(k domain.RefKind) int {
101 // ListRefs lists HEAD, the branches and the tags, in that order, then by86 // ListRefs lists HEAD, the branches and the tags, in that order, then by
102 // name. The page token names the last ref of the previous page, so refs that87 // name. The page token names the last ref of the previous page, so refs that
103 // appear or disappear between two pages never shift the others.88 // appear or disappear between two pages never shift the others.
104-func (b *Browse) ListRefs(ctx context.Context, p domain.Principal, repo domain.RepoName, params ListRefsParams) (RefPage, error) {89+func (b *Browse) ListRefs(ctx context.Context, p domain.Principal, repo domain.RepoName, params ports.ListRefsParams) (ports.RefPage, error) {
105 size, err := pageSizeUpTo(params.PageSize, MaxRefPageSize)90 size, err := pageSizeUpTo(params.PageSize, MaxRefPageSize)
106 if err != nil {91 if err != nil {
107- return RefPage{}, err92+ return ports.RefPage{}, err
108 }93 }
109 for _, k := range params.Kinds {94 for _, k := range params.Kinds {
110 if k != domain.RefKindHead && k != domain.RefKindBranch && k != domain.RefKindTag {95 if k != domain.RefKindHead && k != domain.RefKindBranch && k != domain.RefKindTag {
111- return RefPage{}, fmt.Errorf("%w: %q", domain.ErrInvalidRefKind, k)96+ return ports.RefPage{}, fmt.Errorf("%w: %q", domain.ErrInvalidRefKind, k)
112 }97 }
113 }98 }
114 after, err := decodeRefToken(params.PageToken)99 after, err := decodeRefToken(params.PageToken)
115 if err != nil {100 if err != nil {
116- return RefPage{}, err101+ return ports.RefPage{}, err
117 }102 }
118 if _, err := b.authorize(ctx, p, repo); err != nil {103 if _, err := b.authorize(ctx, p, repo); err != nil {
119- return RefPage{}, err104+ return ports.RefPage{}, err
120 }105 }
121 all, err := b.reader.ListRefs(ctx, repo)106 all, err := b.reader.ListRefs(ctx, repo)
122 if err != nil {107 if err != nil {
123- return RefPage{}, err108+ return ports.RefPage{}, err
124 }109 }
125 110
126 var wanted []domain.Ref111 var wanted []domain.Ref
@@ -129,7 +114,7 @@ func (b *Browse) ListRefs(ctx context.Context, p domain.Principal, repo domain.R
129 wanted = append(wanted, r)114 wanted = append(wanted, r)
130 }115 }
131 }116 }
132- var page RefPage117+ var page ports.RefPage
133 page.Refs, page.NextPageToken = pageAfter(wanted, size,118 page.Refs, page.NextPageToken = pageAfter(wanted, size,
134 func(r domain.Ref) bool { return after == nil || refAfter(r, *after) },119 func(r domain.Ref) bool { return after == nil || refAfter(r, *after) },
135 encodeRefToken)120 encodeRefToken)
@@ -227,53 +212,31 @@ func (b *Browse) OpenBlob(ctx context.Context, p domain.Principal, repo domain.R
227 return b.reader.OpenBlob(ctx, repo, rev, path)212 return b.reader.OpenBlob(ctx, repo, rev, path)
228 }213 }
229 214
230-// ListCommitsParams selects a page of history.
231-type ListCommitsParams struct {
232- // Revision is where the history starts; the zero value is HEAD. Ignored
233- // when PageToken is set: the token pins the commit the first page started
234- // from, so every page walks the same history even if a branch moves.
235- Revision domain.Revision
236- // Path limits the history to the commits that changed it. It must be the
237- // same on every page of a listing.
238- Path domain.TreePath
239- // PageSize is 0 for the default, and lowered to MaxPageSize.
240- PageSize int
241- // PageToken is the NextPageToken of the previous page, or empty.
242- PageToken string
243-}
244-
245-// CommitPage is a page of history, newest first.
246-type CommitPage struct {
247- Commits []domain.Commit
248- // NextPageToken is empty on the last page.
249- NextPageToken string
250-}
251-
252 // ListCommits returns a page of history. The page token holds the commit the215 // ListCommits returns a page of history. The page token holds the commit the
253 // listing started from, how many commits were already returned and a digest216 // listing started from, how many commits were already returned and a digest
254 // of the path; a token used with another path is invalid.217 // of the path; a token used with another path is invalid.
255-func (b *Browse) ListCommits(ctx context.Context, p domain.Principal, repo domain.RepoName, params ListCommitsParams) (CommitPage, error) {218+func (b *Browse) ListCommits(ctx context.Context, p domain.Principal, repo domain.RepoName, params ports.ListCommitsParams) (ports.HistoryPage, error) {
256 size, err := pageSize(params.PageSize)219 size, err := pageSize(params.PageSize)
257 if err != nil {220 if err != nil {
258- return CommitPage{}, err221+ return ports.HistoryPage{}, err
259 }222 }
260 q := ports.CommitQuery{Revision: params.Revision, Path: params.Path, Limit: size}223 q := ports.CommitQuery{Revision: params.Revision, Path: params.Path, Limit: size}
261 if params.PageToken != "" {224 if params.PageToken != "" {
262 head, skip, err := decodeCommitToken(params.PageToken, params.Path)225 head, skip, err := decodeCommitToken(params.PageToken, params.Path)
263 if err != nil {226 if err != nil {
264- return CommitPage{}, err227+ return ports.HistoryPage{}, err
265 }228 }
266 q.Revision, q.Skip = domain.RevisionOf(head), skip229 q.Revision, q.Skip = domain.RevisionOf(head), skip
267 }230 }
268 r, err := b.authorize(ctx, p, repo)231 r, err := b.authorize(ctx, p, repo)
269 if err != nil {232 if err != nil {
270- return CommitPage{}, err233+ return ports.HistoryPage{}, err
271 }234 }
272 res, err := b.listCommits(ctx, r, q)235 res, err := b.listCommits(ctx, r, q)
273 if err != nil {236 if err != nil {
274- return CommitPage{}, err237+ return ports.HistoryPage{}, err
275 }238 }
276- out := CommitPage{Commits: res.Commits}239+ out := ports.HistoryPage{Commits: res.Commits}
277 if res.HasMore {240 if res.HasMore {
278 out.NextPageToken = encodeCommitToken(res.Head, q.Skip+len(res.Commits), params.Path)241 out.NextPageToken = encodeCommitToken(res.Head, q.Skip+len(res.Commits), params.Path)
279 }242 }
M internal/app/browse_test.go +24 -24
@@ -55,7 +55,7 @@ func browseOps() []browseOp {
55 root := domain.TreePath{}55 root := domain.TreePath{}
56 return []browseOp{56 return []browseOp{
57 {"ListRefs", func(b *app.Browse, ctx context.Context, p domain.Principal, repo domain.RepoName) error {57 {"ListRefs", func(b *app.Browse, ctx context.Context, p domain.Principal, repo domain.RepoName) error {
58- _, err := b.ListRefs(ctx, p, repo, app.ListRefsParams{})58+ _, err := b.ListRefs(ctx, p, repo, ports.ListRefsParams{})
59 return err59 return err
60 }, func(r *mocks.RepoReader) { r.EXPECT().ListRefs(mock.Anything, mock.Anything).Return(nil, nil) }},60 }, func(r *mocks.RepoReader) { r.EXPECT().ListRefs(mock.Anything, mock.Anything).Return(nil, nil) }},
61 {"ResolveRevision", func(b *app.Browse, ctx context.Context, p domain.Principal, repo domain.RepoName) error {61 {"ResolveRevision", func(b *app.Browse, ctx context.Context, p domain.Principal, repo domain.RepoName) error {
@@ -87,7 +87,7 @@ func browseOps() []browseOp {
87 Return(domain.BlobInfo{}, io.NopCloser(strings.NewReader("")), nil)87 Return(domain.BlobInfo{}, io.NopCloser(strings.NewReader("")), nil)
88 }},88 }},
89 {"ListCommits", func(b *app.Browse, ctx context.Context, p domain.Principal, repo domain.RepoName) error {89 {"ListCommits", func(b *app.Browse, ctx context.Context, p domain.Principal, repo domain.RepoName) error {
90- _, err := b.ListCommits(ctx, p, repo, app.ListCommitsParams{})90+ _, err := b.ListCommits(ctx, p, repo, ports.ListCommitsParams{})
91 return err91 return err
92 }, func(r *mocks.RepoReader) {92 }, func(r *mocks.RepoReader) {
93 r.EXPECT().ListCommits(mock.Anything, mock.Anything, mock.Anything).Return(ports.CommitPage{}, nil)93 r.EXPECT().ListCommits(mock.Anything, mock.Anything, mock.Anything).Return(ports.CommitPage{}, nil)
@@ -214,7 +214,7 @@ func TestListRefsPagingAndKinds(t *testing.T) {
214 tok := ""214 tok := ""
215 for pages := 0; ; pages++ {215 for pages := 0; ; pages++ {
216 require.Less(t, pages, 10)216 require.Less(t, pages, 10)
217- page, err := b.ListRefs(ctx, domain.Anonymous(), pub.Name, app.ListRefsParams{PageSize: 2, PageToken: tok})217+ page, err := b.ListRefs(ctx, domain.Anonymous(), pub.Name, ports.ListRefsParams{PageSize: 2, PageToken: tok})
218 require.NoError(t, err)218 require.NoError(t, err)
219 got = append(got, names(page.Refs)...)219 got = append(got, names(page.Refs)...)
220 if tok = page.NextPageToken; tok == "" {220 if tok = page.NextPageToken; tok == "" {
@@ -226,19 +226,19 @@ func TestListRefsPagingAndKinds(t *testing.T) {
226 226
227 t.Run("a ref that appears between pages does not shift the others", func(t *testing.T) {227 t.Run("a ref that appears between pages does not shift the others", func(t *testing.T) {
228 b := setup(t, all[:4])228 b := setup(t, all[:4])
229- page, err := b.ListRefs(ctx, domain.Anonymous(), pub.Name, app.ListRefsParams{PageSize: 2})229+ page, err := b.ListRefs(ctx, domain.Anonymous(), pub.Name, ports.ListRefsParams{PageSize: 2})
230 require.NoError(t, err)230 require.NoError(t, err)
231 require.Equal(t, []string{"HEAD", "refs/heads/a"}, names(page.Refs))231 require.Equal(t, []string{"HEAD", "refs/heads/a"}, names(page.Refs))
232 232
233 b2 := setup(t, append([]domain.Ref{all[0], ref(domain.RefKindBranch, "refs/heads/0new")}, all[1:4]...))233 b2 := setup(t, append([]domain.Ref{all[0], ref(domain.RefKindBranch, "refs/heads/0new")}, all[1:4]...))
234- next, err := b2.ListRefs(ctx, domain.Anonymous(), pub.Name, app.ListRefsParams{PageSize: 2, PageToken: page.NextPageToken})234+ next, err := b2.ListRefs(ctx, domain.Anonymous(), pub.Name, ports.ListRefsParams{PageSize: 2, PageToken: page.NextPageToken})
235 require.NoError(t, err)235 require.NoError(t, err)
236 assert.Equal(t, []string{"refs/heads/b", "refs/heads/c"}, names(next.Refs))236 assert.Equal(t, []string{"refs/heads/b", "refs/heads/c"}, names(next.Refs))
237 })237 })
238 238
239 t.Run("kinds filter", func(t *testing.T) {239 t.Run("kinds filter", func(t *testing.T) {
240 b := setup(t, all)240 b := setup(t, all)
241- page, err := b.ListRefs(ctx, domain.Anonymous(), pub.Name, app.ListRefsParams{Kinds: []domain.RefKind{domain.RefKindTag}})241+ page, err := b.ListRefs(ctx, domain.Anonymous(), pub.Name, ports.ListRefsParams{Kinds: []domain.RefKind{domain.RefKindTag}})
242 require.NoError(t, err)242 require.NoError(t, err)
243 assert.Equal(t, []string{"refs/tags/v1", "refs/tags/v2"}, names(page.Refs))243 assert.Equal(t, []string{"refs/tags/v1", "refs/tags/v2"}, names(page.Refs))
244 assert.Empty(t, page.NextPageToken)244 assert.Empty(t, page.NextPageToken)
@@ -251,13 +251,13 @@ func TestListRefsPagingAndKinds(t *testing.T) {
251 many = append(many, ref(domain.RefKindTag, fmt.Sprintf("refs/tags/t%04d", i)))251 many = append(many, ref(domain.RefKindTag, fmt.Sprintf("refs/tags/t%04d", i)))
252 }252 }
253 b := setup(t, many)253 b := setup(t, many)
254- page, err := b.ListRefs(ctx, domain.Anonymous(), pub.Name, app.ListRefsParams{})254+ page, err := b.ListRefs(ctx, domain.Anonymous(), pub.Name, ports.ListRefsParams{})
255 require.NoError(t, err)255 require.NoError(t, err)
256 assert.Len(t, page.Refs, app.DefaultPageSize)256 assert.Len(t, page.Refs, app.DefaultPageSize)
257 assert.NotEmpty(t, page.NextPageToken)257 assert.NotEmpty(t, page.NextPageToken)
258 258
259 b = setup(t, many)259 b = setup(t, many)
260- page, err = b.ListRefs(ctx, domain.Anonymous(), pub.Name, app.ListRefsParams{PageSize: 100000})260+ page, err = b.ListRefs(ctx, domain.Anonymous(), pub.Name, ports.ListRefsParams{PageSize: 100000})
261 require.NoError(t, err)261 require.NoError(t, err)
262 assert.Len(t, page.Refs, app.MaxRefPageSize, "a page holds up to MaxRefPageSize refs")262 assert.Len(t, page.Refs, app.MaxRefPageSize, "a page holds up to MaxRefPageSize refs")
263 assert.NotEmpty(t, page.NextPageToken)263 assert.NotEmpty(t, page.NextPageToken)
@@ -266,7 +266,7 @@ func TestListRefsPagingAndKinds(t *testing.T) {
266 t.Run("bad input never reaches the reader", func(t *testing.T) {266 t.Run("bad input never reaches the reader", func(t *testing.T) {
267 t.Parallel()267 t.Parallel()
268 f := newBrowseFixture(t)268 f := newBrowseFixture(t)
269- for _, params := range []app.ListRefsParams{269+ for _, params := range []ports.ListRefsParams{
270 {PageSize: -1},270 {PageSize: -1},
271 {Kinds: []domain.RefKind{"nope"}},271 {Kinds: []domain.RefKind{"nope"}},
272 {PageToken: "!!!"},272 {PageToken: "!!!"},
@@ -275,11 +275,11 @@ func TestListRefsPagingAndKinds(t *testing.T) {
275 _, err := f.browse.ListRefs(ctx, domain.Anonymous(), pub.Name, params)275 _, err := f.browse.ListRefs(ctx, domain.Anonymous(), pub.Name, params)
276 assert.Error(t, err, "%+v", params)276 assert.Error(t, err, "%+v", params)
277 }277 }
278- _, err := f.browse.ListRefs(ctx, domain.Anonymous(), pub.Name, app.ListRefsParams{PageSize: -1})278+ _, err := f.browse.ListRefs(ctx, domain.Anonymous(), pub.Name, ports.ListRefsParams{PageSize: -1})
279 assert.ErrorIs(t, err, domain.ErrInvalidPageSize)279 assert.ErrorIs(t, err, domain.ErrInvalidPageSize)
280- _, err = f.browse.ListRefs(ctx, domain.Anonymous(), pub.Name, app.ListRefsParams{Kinds: []domain.RefKind{"x"}})280+ _, err = f.browse.ListRefs(ctx, domain.Anonymous(), pub.Name, ports.ListRefsParams{Kinds: []domain.RefKind{"x"}})
281 assert.ErrorIs(t, err, domain.ErrInvalidRefKind)281 assert.ErrorIs(t, err, domain.ErrInvalidRefKind)
282- _, err = f.browse.ListRefs(ctx, domain.Anonymous(), pub.Name, app.ListRefsParams{PageToken: "!!!"})282+ _, err = f.browse.ListRefs(ctx, domain.Anonymous(), pub.Name, ports.ListRefsParams{PageToken: "!!!"})
283 assert.ErrorIs(t, err, domain.ErrInvalidPageToken)283 assert.ErrorIs(t, err, domain.ErrInvalidPageToken)
284 })284 })
285 }285 }
@@ -296,7 +296,7 @@ func TestListCommitsPaging(t *testing.T) {
296 f.meta.EXPECT().Get(ctx, pub.Name).Return(pub, nil)296 f.meta.EXPECT().Get(ctx, pub.Name).Return(pub, nil)
297 f.reader.EXPECT().ListCommits(ctx, pub.Name, ports.CommitQuery{Path: p, Limit: 10}).297 f.reader.EXPECT().ListCommits(ctx, pub.Name, ports.CommitQuery{Path: p, Limit: 10}).
298 Return(ports.CommitPage{Head: oid("a"), Commits: []domain.Commit{{ID: oid("b")}}, HasMore: true}, nil)298 Return(ports.CommitPage{Head: oid("a"), Commits: []domain.Commit{{ID: oid("b")}}, HasMore: true}, nil)
299- page, err := f.browse.ListCommits(ctx, domain.Anonymous(), pub.Name, app.ListCommitsParams{Path: p, PageSize: 10})299+ page, err := f.browse.ListCommits(ctx, domain.Anonymous(), pub.Name, ports.ListCommitsParams{Path: p, PageSize: 10})
300 require.NoError(t, err)300 require.NoError(t, err)
301 require.Len(t, page.Commits, 1)301 require.Len(t, page.Commits, 1)
302 require.NotEmpty(t, page.NextPageToken)302 require.NotEmpty(t, page.NextPageToken)
@@ -308,7 +308,7 @@ func TestListCommitsPaging(t *testing.T) {
308 f.meta.EXPECT().Get(ctx, pub.Name).Return(pub, nil)308 f.meta.EXPECT().Get(ctx, pub.Name).Return(pub, nil)
309 // The revision of the request is ignored once there is a token.309 // The revision of the request is ignored once there is a token.
310 rev, _ := domain.ParseRevision("moved")310 rev, _ := domain.ParseRevision("moved")
311- next, err := f.browse.ListCommits(ctx, domain.Anonymous(), pub.Name, app.ListCommitsParams{311+ next, err := f.browse.ListCommits(ctx, domain.Anonymous(), pub.Name, ports.ListCommitsParams{
312 Revision: rev, Path: p, PageSize: 10, PageToken: page.NextPageToken,312 Revision: rev, Path: p, PageSize: 10, PageToken: page.NextPageToken,
313 })313 })
314 require.NoError(t, err)314 require.NoError(t, err)
@@ -322,10 +322,10 @@ func TestListCommitsPaging(t *testing.T) {
322 f.meta.EXPECT().Get(ctx, pub.Name).Return(pub, nil)322 f.meta.EXPECT().Get(ctx, pub.Name).Return(pub, nil)
323 f.reader.EXPECT().ListCommits(ctx, pub.Name, mock.Anything).323 f.reader.EXPECT().ListCommits(ctx, pub.Name, mock.Anything).
324 Return(ports.CommitPage{Head: oid("a"), Commits: []domain.Commit{{ID: oid("b")}}, HasMore: true}, nil)324 Return(ports.CommitPage{Head: oid("a"), Commits: []domain.Commit{{ID: oid("b")}}, HasMore: true}, nil)
325- page, err := f.browse.ListCommits(ctx, domain.Anonymous(), pub.Name, app.ListCommitsParams{Path: p})325+ page, err := f.browse.ListCommits(ctx, domain.Anonymous(), pub.Name, ports.ListCommitsParams{Path: p})
326 require.NoError(t, err)326 require.NoError(t, err)
327 327
328- for _, params := range []app.ListCommitsParams{328+ for _, params := range []ports.ListCommitsParams{
329 {PageToken: page.NextPageToken}, // no path329 {PageToken: page.NextPageToken}, // no path
330 {Path: mustPath(t, "other"), PageToken: page.NextPageToken}, // another path330 {Path: mustPath(t, "other"), PageToken: page.NextPageToken}, // another path
331 {Path: p, PageToken: "garbage"},331 {Path: p, PageToken: "garbage"},
@@ -343,7 +343,7 @@ func TestListCommitsPaging(t *testing.T) {
343 f.meta.EXPECT().Get(ctx, pub.Name).Return(pub, nil).Maybe()343 f.meta.EXPECT().Get(ctx, pub.Name).Return(pub, nil).Maybe()
344 f.reader.EXPECT().ListCommits(ctx, pub.Name, mock.Anything).344 f.reader.EXPECT().ListCommits(ctx, pub.Name, mock.Anything).
345 Return(ports.CommitPage{Head: oid("a"), Commits: []domain.Commit{{ID: oid("b")}}, HasMore: true}, nil).Once()345 Return(ports.CommitPage{Head: oid("a"), Commits: []domain.Commit{{ID: oid("b")}}, HasMore: true}, nil).Once()
346- page, err := f.browse.ListCommits(ctx, domain.Anonymous(), pub.Name, app.ListCommitsParams{Path: p})346+ page, err := f.browse.ListCommits(ctx, domain.Anonymous(), pub.Name, ports.ListCommitsParams{Path: p})
347 require.NoError(t, err)347 require.NoError(t, err)
348 raw, err := base64.RawURLEncoding.DecodeString(page.NextPageToken)348 raw, err := base64.RawURLEncoding.DecodeString(page.NextPageToken)
349 require.NoError(t, err)349 require.NoError(t, err)
@@ -356,9 +356,9 @@ func TestListCommitsPaging(t *testing.T) {
356 356
357 f.reader.EXPECT().ListCommits(ctx, pub.Name, mock.MatchedBy(func(q ports.CommitQuery) bool { return q.Skip == app.MaxCommitSkip })).357 f.reader.EXPECT().ListCommits(ctx, pub.Name, mock.MatchedBy(func(q ports.CommitQuery) bool { return q.Skip == app.MaxCommitSkip })).
358 Return(ports.CommitPage{Head: oid("a")}, nil).Once()358 Return(ports.CommitPage{Head: oid("a")}, nil).Once()
359- _, err = f.browse.ListCommits(ctx, domain.Anonymous(), pub.Name, app.ListCommitsParams{Path: p, PageToken: withSkip(app.MaxCommitSkip)})359+ _, err = f.browse.ListCommits(ctx, domain.Anonymous(), pub.Name, ports.ListCommitsParams{Path: p, PageToken: withSkip(app.MaxCommitSkip)})
360 require.NoError(t, err, "the cap itself is allowed")360 require.NoError(t, err, "the cap itself is allowed")
361- _, err = f.browse.ListCommits(ctx, domain.Anonymous(), pub.Name, app.ListCommitsParams{Path: p, PageToken: withSkip(app.MaxCommitSkip + 1)})361+ _, err = f.browse.ListCommits(ctx, domain.Anonymous(), pub.Name, ports.ListCommitsParams{Path: p, PageToken: withSkip(app.MaxCommitSkip + 1)})
362 assert.ErrorIs(t, err, domain.ErrInvalidPageToken)362 assert.ErrorIs(t, err, domain.ErrInvalidPageToken)
363 })363 })
364 364
@@ -368,11 +368,11 @@ func TestListCommitsPaging(t *testing.T) {
368 f.meta.EXPECT().Get(ctx, pub.Name).Return(pub, nil).Times(2)368 f.meta.EXPECT().Get(ctx, pub.Name).Return(pub, nil).Times(2)
369 f.reader.EXPECT().ListCommits(ctx, pub.Name, ports.CommitQuery{Limit: app.DefaultPageSize}).Return(ports.CommitPage{}, nil)369 f.reader.EXPECT().ListCommits(ctx, pub.Name, ports.CommitQuery{Limit: app.DefaultPageSize}).Return(ports.CommitPage{}, nil)
370 f.reader.EXPECT().ListCommits(ctx, pub.Name, ports.CommitQuery{Limit: 100}).Return(ports.CommitPage{}, nil)370 f.reader.EXPECT().ListCommits(ctx, pub.Name, ports.CommitQuery{Limit: 100}).Return(ports.CommitPage{}, nil)
371- _, err := f.browse.ListCommits(ctx, domain.Anonymous(), pub.Name, app.ListCommitsParams{})371+ _, err := f.browse.ListCommits(ctx, domain.Anonymous(), pub.Name, ports.ListCommitsParams{})
372 require.NoError(t, err)372 require.NoError(t, err)
373- _, err = f.browse.ListCommits(ctx, domain.Anonymous(), pub.Name, app.ListCommitsParams{PageSize: 5000})373+ _, err = f.browse.ListCommits(ctx, domain.Anonymous(), pub.Name, ports.ListCommitsParams{PageSize: 5000})
374 require.NoError(t, err)374 require.NoError(t, err)
375- _, err = f.browse.ListCommits(ctx, domain.Anonymous(), pub.Name, app.ListCommitsParams{PageSize: -3})375+ _, err = f.browse.ListCommits(ctx, domain.Anonymous(), pub.Name, ports.ListCommitsParams{PageSize: -3})
376 assert.ErrorIs(t, err, domain.ErrInvalidPageSize)376 assert.ErrorIs(t, err, domain.ErrInvalidPageSize)
377 })377 })
378 378
@@ -382,10 +382,10 @@ func TestListCommitsPaging(t *testing.T) {
382 f.meta.EXPECT().Get(ctx, priv.Name).Return(priv, nil)382 f.meta.EXPECT().Get(ctx, priv.Name).Return(priv, nil)
383 f.reader.EXPECT().ListCommits(ctx, priv.Name, mock.Anything).383 f.reader.EXPECT().ListCommits(ctx, priv.Name, mock.Anything).
384 Return(ports.CommitPage{Head: oid("a"), Commits: []domain.Commit{{ID: oid("b")}}, HasMore: true}, nil)384 Return(ports.CommitPage{Head: oid("a"), Commits: []domain.Commit{{ID: oid("b")}}, HasMore: true}, nil)
385- page, err := f.browse.ListCommits(ctx, domain.Root(), priv.Name, app.ListCommitsParams{})385+ page, err := f.browse.ListCommits(ctx, domain.Root(), priv.Name, ports.ListCommitsParams{})
386 require.NoError(t, err)386 require.NoError(t, err)
387 f.meta.EXPECT().Get(ctx, priv.Name).Return(priv, nil)387 f.meta.EXPECT().Get(ctx, priv.Name).Return(priv, nil)
388- _, err = f.browse.ListCommits(ctx, domain.Anonymous(), priv.Name, app.ListCommitsParams{PageToken: page.NextPageToken})388+ _, err = f.browse.ListCommits(ctx, domain.Anonymous(), priv.Name, ports.ListCommitsParams{PageToken: page.NextPageToken})
389 assert.ErrorIs(t, err, domain.ErrNotFound)389 assert.ErrorIs(t, err, domain.ErrNotFound)
390 })390 })
391 }391 }
M internal/app/browsecache_test.go +4 -4
@@ -131,7 +131,7 @@ func TestHistoryCacheNotServedAcrossRecreate(t *testing.T) {
131 r.EXPECT().GetTree(mock.Anything, n, rev, dir, true).Return(domain.Tree{}, domain.ErrNotFound).Once()131 r.EXPECT().GetTree(mock.Anything, n, rev, dir, true).Return(domain.Tree{}, domain.ErrNotFound).Once()
132 }},132 }},
133 {"last change of a path", func(b *app.Browse) error {133 {"last change of a path", func(b *app.Browse) error {
134- _, err := b.ListCommits(ctx, domain.Anonymous(), n, app.ListCommitsParams{Revision: rev, Path: dir, PageSize: 1})134+ _, err := b.ListCommits(ctx, domain.Anonymous(), n, ports.ListCommitsParams{Revision: rev, Path: dir, PageSize: 1})
135 return err135 return err
136 }, func(r *mocks.RepoReader) {136 }, func(r *mocks.RepoReader) {
137 q := ports.CommitQuery{Revision: rev, Path: dir, Limit: 1}137 q := ports.CommitQuery{Revision: rev, Path: dir, Limit: 1}
@@ -305,7 +305,7 @@ func TestListCommitsLastChangeCache(t *testing.T) {
305 }, nil).Times(tc.calls)305 }, nil).Times(tc.calls)
306 for range 2 {306 for range 2 {
307 page, err := f.browse.ListCommits(ctx, domain.Anonymous(), pub.Name,307 page, err := f.browse.ListCommits(ctx, domain.Anonymous(), pub.Name,
308- app.ListCommitsParams{Revision: tc.rev, Path: tc.path, PageSize: tc.size})308+ ports.ListCommitsParams{Revision: tc.rev, Path: tc.path, PageSize: tc.size})
309 require.NoError(t, err)309 require.NoError(t, err)
310 require.Len(t, page.Commits, 1)310 require.Len(t, page.Commits, 1)
311 page.Commits[0].Parents[0] = oid("f") // must not leak into the cache311 page.Commits[0].Parents[0] = oid("f") // must not leak into the cache
@@ -319,13 +319,13 @@ func TestListCommitsLastChangeCache(t *testing.T) {
319 f.meta.EXPECT().Get(mock.Anything, pub.Name).Return(pub, nil)319 f.meta.EXPECT().Get(mock.Anything, pub.Name).Return(pub, nil)
320 f.reader.EXPECT().ListCommits(mock.Anything, pub.Name, ports.CommitQuery{Revision: full, Path: file, Limit: 1}).320 f.reader.EXPECT().ListCommits(mock.Anything, pub.Name, ports.CommitQuery{Revision: full, Path: file, Limit: 1}).
321 Return(ports.CommitPage{Head: oid("a"), Commits: []domain.Commit{{ID: oid("c")}}, HasMore: true}, nil).Once()321 Return(ports.CommitPage{Head: oid("a"), Commits: []domain.Commit{{ID: oid("c")}}, HasMore: true}, nil).Once()
322- first, err := f.browse.ListCommits(ctx, domain.Anonymous(), pub.Name, app.ListCommitsParams{Revision: full, Path: file, PageSize: 1})322+ first, err := f.browse.ListCommits(ctx, domain.Anonymous(), pub.Name, ports.ListCommitsParams{Revision: full, Path: file, PageSize: 1})
323 require.NoError(t, err)323 require.NoError(t, err)
324 require.NotEmpty(t, first.NextPageToken)324 require.NotEmpty(t, first.NextPageToken)
325 f.reader.EXPECT().ListCommits(mock.Anything, pub.Name, ports.CommitQuery{Revision: domain.RevisionOf(oid("a")), Path: file, Skip: 1, Limit: 1}).325 f.reader.EXPECT().ListCommits(mock.Anything, pub.Name, ports.CommitQuery{Revision: domain.RevisionOf(oid("a")), Path: file, Skip: 1, Limit: 1}).
326 Return(ports.CommitPage{Head: oid("a")}, nil).Twice()326 Return(ports.CommitPage{Head: oid("a")}, nil).Twice()
327 for range 2 {327 for range 2 {
328- _, err = f.browse.ListCommits(ctx, domain.Anonymous(), pub.Name, app.ListCommitsParams{Path: file, PageSize: 1, PageToken: first.NextPageToken})328+ _, err = f.browse.ListCommits(ctx, domain.Anonymous(), pub.Name, ports.ListCommitsParams{Path: file, PageSize: 1, PageToken: first.NextPageToken})
329 require.NoError(t, err)329 require.NoError(t, err)
330 }330 }
331 })331 })
M internal/app/events.go +13 -45
@@ -27,7 +27,7 @@ const (
27 // MaxWatchers is the most watches in progress at once.27 // MaxWatchers is the most watches in progress at once.
28 MaxWatchers = 25628 MaxWatchers = 256
29 // MaxWatchersPerClient is the most watches in progress for one client29 // MaxWatchersPerClient is the most watches in progress for one client
30- // address (see WatchParams.Client).30+ // address (see ports.WatchParams.Client).
31 MaxWatchersPerClient = 1631 MaxWatchersPerClient = 16
32 // DefaultRevalidateEvery is how long a watcher with a stored token goes32 // DefaultRevalidateEvery is how long a watcher with a stored token goes
33 // without asking the token store whether the token still exists.33 // without asking the token store whether the token still exists.
@@ -74,7 +74,7 @@ type bufferedEvent struct {
74 // NewEvents and lives until Close.74 // NewEvents and lives until Close.
75 type Events struct {75 type Events struct {
76 repos *Repos76 repos *Repos
77- auth Revalidator77+ auth ports.Revalidator
78 clock ports.Clock78 clock ports.Clock
79 boot string79 boot string
80 size int80 size int
@@ -92,13 +92,15 @@ type Events struct {
92 clients map[string]int // watches in progress by client92 clients map[string]int // watches in progress by client
93 }93 }
94 94
95+var _ ports.EventService = (*Events)(nil)
96+
95 // EventsConfig configures Events.97 // EventsConfig configures Events.
96 type EventsConfig struct {98 type EventsConfig struct {
97 // Size is the number of events remembered; DefaultEventBuffer when not99 // Size is the number of events remembered; DefaultEventBuffer when not
98 // positive.100 // positive.
99 Size int101 Size int
100 // Auth confirms that the token of a watcher still exists. Required.102 // Auth confirms that the token of a watcher still exists. Required.
101- Auth Revalidator103+ Auth ports.Revalidator
102 // RevalidateEvery is how long the answer of Auth is kept for a watcher;104 // RevalidateEvery is how long the answer of Auth is kept for a watcher;
103 // DefaultRevalidateEvery when zero, and negative to ask for every event.105 // DefaultRevalidateEvery when zero, and negative to ask for every event.
104 RevalidateEvery time.Duration106 RevalidateEvery time.Duration
@@ -246,40 +248,6 @@ func (e *Events) parseID(id string) (uint64, bool) {
246 return n, err == nil && n > 0248 return n, err == nil && n > 0
247 }249 }
248 250
249-// WatchParams select the events of a watch.
250-type WatchParams struct {
251- // Topics are TopicHome or "repo:<name>"; empty means TopicHome.
252- Topics []string
253- // ResumeAfter is the id of the last event the caller saw; empty to
254- // receive only new events.
255- ResumeAfter string
256- // Client identifies where the watch comes from, for the limit per client
257- // (MaxWatchersPerClient): an opaque key such as a normalized address; the
258- // web passes its visitor's. Empty only for callers with no client, which
259- // count against MaxWatchers alone.
260- Client string
261- // Ready, when not nil, is called once the topics are valid and the watch
262- // is registered: events published from then on are not missed. Nothing is
263- // sent before it returns.
264- Ready func()
265-}
266-
267-// WatchEvent is one item of a watch.
268-type WatchEvent struct {
269- // ID identifies the event, for resuming. Empty on a gap.
270- ID string
271- // Gap tells that events were lost: the caller must reload what it shows.
272- // Event is nil then.
273- Gap bool
274- // Event is a domain.RepoCreated, RepoUpdated, RepoDeleted, PushEvent,
275- // MergeRequestOpened, MergeRequestMerged, MergeRequestClosed or
276- // CommentAdded.
277- Event domain.Event
278- // Repo is the state of the repository at delivery, for RepoCreated and
279- // RepoUpdated.
280- Repo *RepoView
281-}
282-
283 // watch is the state of one Watch call.251 // watch is the state of one Watch call.
284 type watch struct {252 type watch struct {
285 p domain.Principal253 p domain.Principal
@@ -337,7 +305,7 @@ func (e *Events) parseTopics(ctx context.Context, p domain.Principal, topics []s
337 // buffered, or was not issued by this run, a gap comes first and the oldest305 // buffered, or was not issued by this run, a gap comes first and the oldest
338 // buffered event follows. A watcher that falls more than the buffer behind306 // buffered event follows. A watcher that falls more than the buffer behind
339 // gets a gap too, and carries on with the oldest buffered event.307 // gets a gap too, and carries on with the oldest buffered event.
340-func (e *Events) Watch(ctx context.Context, p domain.Principal, params WatchParams, send func(WatchEvent) error) error {308+func (e *Events) Watch(ctx context.Context, p domain.Principal, params ports.WatchParams, send func(ports.WatchEvent) error) error {
341 w, err := e.parseTopics(ctx, p, params.Topics)309 w, err := e.parseTopics(ctx, p, params.Topics)
342 if err != nil {310 if err != nil {
343 return err311 return err
@@ -382,7 +350,7 @@ func (e *Events) Watch(ctx context.Context, p domain.Principal, params WatchPara
382 log := zerowrap.FromCtx(ctx)350 log := zerowrap.FromCtx(ctx)
383 log.Debug().Strs("topics", params.Topics).Bool("resume", params.ResumeAfter != "").Bool("gap", gap).Msg("watch started")351 log.Debug().Strs("topics", params.Topics).Bool("resume", params.ResumeAfter != "").Bool("gap", gap).Msg("watch started")
384 if gap {352 if gap {
385- if err := send(WatchEvent{Gap: true}); err != nil {353+ if err := send(ports.WatchEvent{Gap: true}); err != nil {
386 return err354 return err
387 }355 }
388 }356 }
@@ -392,7 +360,7 @@ func (e *Events) Watch(ctx context.Context, p domain.Principal, params WatchPara
392 }360 }
393 batch, changed, oldest, lost, closed := e.since(next)361 batch, changed, oldest, lost, closed := e.since(next)
394 if lost {362 if lost {
395- if err := send(WatchEvent{Gap: true}); err != nil {363+ if err := send(ports.WatchEvent{Gap: true}); err != nil {
396 return err364 return err
397 }365 }
398 next = oldest366 next = oldest
@@ -482,15 +450,15 @@ func (e *Events) since(next uint64) (batch []bufferedEvent, changed <-chan struc
482 }450 }
483 451
484 // deliver sends the event if the watcher follows its topic and may see it.452 // deliver sends the event if the watcher follows its topic and may see it.
485-func (e *Events) deliver(ctx context.Context, w *watch, be bufferedEvent, send func(WatchEvent) error) error {453+func (e *Events) deliver(ctx context.Context, w *watch, be bufferedEvent, send func(ports.WatchEvent) error) error {
486 if err := e.stillAllowed(ctx, w); err != nil {454 if err := e.stillAllowed(ctx, w); err != nil {
487 return err455 return err
488 }456 }
489- out := WatchEvent{ID: e.id(be.seq), Event: be.ev}457+ out := ports.WatchEvent{ID: e.id(be.seq), Event: be.ev}
490 switch ev := be.ev.(type) {458 switch ev := be.ev.(type) {
491 case domain.EventGap:459 case domain.EventGap:
492 // The feeder lost events: tell every watcher.460 // The feeder lost events: tell every watcher.
493- return send(WatchEvent{Gap: true})461+ return send(ports.WatchEvent{Gap: true})
494 case domain.RepoDeleted:462 case domain.RepoDeleted:
495 // The repository is gone: use the visibility it had.463 // The repository is gone: use the visibility it had.
496 if !w.wants(ev.Repo) || !canReadAs(w.p, ev.Repo, ev.Visibility) {464 if !w.wants(ev.Repo) || !canReadAs(w.p, ev.Repo, ev.Visibility) {
@@ -523,7 +491,7 @@ func (e *Events) deliver(ctx context.Context, w *watch, be bufferedEvent, send f
523 // The exception is a repository that became private: the watchers who could491 // The exception is a repository that became private: the watchers who could
524 // read it before are told once, with its name and its new visibility only,492 // read it before are told once, with its name and its new visibility only,
525 // so that what they show can be removed.493 // so that what they show can be removed.
526-func (e *Events) deliverRepo(ctx context.Context, w *watch, name domain.RepoName, withRepo bool, be bufferedEvent, out WatchEvent, send func(WatchEvent) error) error {494+func (e *Events) deliverRepo(ctx context.Context, w *watch, name domain.RepoName, withRepo bool, be bufferedEvent, out ports.WatchEvent, send func(ports.WatchEvent) error) error {
527 if !w.wants(name) || !be.known {495 if !w.wants(name) || !be.known {
528 return nil496 return nil
529 }497 }
@@ -541,7 +509,7 @@ func (e *Events) deliverRepo(ctx context.Context, w *watch, name domain.RepoName
541 }509 }
542 if !canReadAs(w.p, name, be.vis) {510 if !canReadAs(w.p, name, be.vis) {
543 if up, ok := be.ev.(domain.RepoUpdated); ok && canReadAs(w.p, name, up.Previous) {511 if up, ok := be.ev.(domain.RepoUpdated); ok && canReadAs(w.p, name, up.Previous) {
544- out.Repo = &RepoView{Repo: domain.Repo{Name: name, Visibility: up.Visibility}}512+ out.Repo = &ports.RepoView{Repo: domain.Repo{Name: name, Visibility: up.Visibility}}
545 return send(out)513 return send(out)
546 }514 }
547 return nil515 return nil
M internal/app/events_test.go +60 -60
@@ -17,8 +17,8 @@ import (
17 "github.com/stretchr/testify/require"17 "github.com/stretchr/testify/require"
18 18
19 "bnema.dev/ermite/internal/app"19 "bnema.dev/ermite/internal/app"
20- appmocks "bnema.dev/ermite/internal/app/mocks"
21 "bnema.dev/ermite/internal/domain"20 "bnema.dev/ermite/internal/domain"
21+ "bnema.dev/ermite/internal/ports"
22 "bnema.dev/ermite/internal/ports/mocks"22 "bnema.dev/ermite/internal/ports/mocks"
23 )23 )
24 24
@@ -28,7 +28,7 @@ type eventRig struct {
28 meta *mocks.RepoMetaStore28 meta *mocks.RepoMetaStore
29 git *mocks.GitRepos29 git *mocks.GitRepos
30 feed chan domain.Event30 feed chan domain.Event
31- auth *appmocks.Revalidator31+ auth *mocks.Revalidator
32 revoked sync.Map // token IDs revoked by the test32 revoked sync.Map // token IDs revoked by the test
33 revalidations atomic.Int32 // calls to auth.Revalidate33 revalidations atomic.Int32 // calls to auth.Revalidate
34 events *app.Events34 events *app.Events
@@ -46,7 +46,7 @@ func newEventRigConfig(t *testing.T, cfg app.EventsConfig) *eventRig {
46 meta: mocks.NewRepoMetaStore(t),46 meta: mocks.NewRepoMetaStore(t),
47 git: mocks.NewGitRepos(t),47 git: mocks.NewGitRepos(t),
48 feed: make(chan domain.Event, 4096),48 feed: make(chan domain.Event, 4096),
49- auth: appmocks.NewRevalidator(t),49+ auth: mocks.NewRevalidator(t),
50 }50 }
51 // Tokens are valid until the test revokes them.51 // Tokens are valid until the test revokes them.
52 r.auth.EXPECT().Revalidate(mock.Anything, mock.Anything).RunAndReturn(func(_ context.Context, p domain.Principal) error {52 r.auth.EXPECT().Revalidate(mock.Anything, mock.Anything).RunAndReturn(func(_ context.Context, p domain.Principal) error {
@@ -95,14 +95,14 @@ func (r *eventRig) push(n domain.RepoName, i int) {
95 95
96 // watcher is a running Watch.96 // watcher is a running Watch.
97 type watcher struct {97 type watcher struct {
98- got chan app.WatchEvent98+ got chan ports.WatchEvent
99 done chan error99 done chan error
100 }100 }
101 101
102-func (r *eventRig) watch(ctx context.Context, p domain.Principal, params app.WatchParams) *watcher {102+func (r *eventRig) watch(ctx context.Context, p domain.Principal, params ports.WatchParams) *watcher {
103- w := &watcher{got: make(chan app.WatchEvent, 4096), done: make(chan error, 1)}103+ w := &watcher{got: make(chan ports.WatchEvent, 4096), done: make(chan error, 1)}
104 go func() {104 go func() {
105- w.done <- r.events.Watch(ctx, p, params, func(e app.WatchEvent) error {105+ w.done <- r.events.Watch(ctx, p, params, func(e ports.WatchEvent) error {
106 w.got <- e106 w.got <- e
107 return nil107 return nil
108 })108 })
@@ -110,7 +110,7 @@ func (r *eventRig) watch(ctx context.Context, p domain.Principal, params app.Wat
110 return w110 return w
111 }111 }
112 112
113-func (w *watcher) next(t *testing.T) app.WatchEvent {113+func (w *watcher) next(t *testing.T) ports.WatchEvent {
114 t.Helper()114 t.Helper()
115 synctest.Wait() // everything the event can reach has run115 synctest.Wait() // everything the event can reach has run
116 select {116 select {
@@ -121,7 +121,7 @@ func (w *watcher) next(t *testing.T) app.WatchEvent {
121 default:121 default:
122 t.Fatal("no event")122 t.Fatal("no event")
123 }123 }
124- return app.WatchEvent{}124+ return ports.WatchEvent{}
125 }125 }
126 126
127 // sentinelOID marks the push events that only prove the ones before them were127 // sentinelOID marks the push events that only prove the ones before them were
@@ -150,10 +150,10 @@ func TestWatchLiveAndFiltered(t *testing.T) {
150 ctx, cancel := context.WithCancel(context.Background())150 ctx, cancel := context.WithCancel(context.Background())
151 defer cancel()151 defer cancel()
152 152
153- anon := r.watch(ctx, domain.Anonymous(), app.WatchParams{})153+ anon := r.watch(ctx, domain.Anonymous(), ports.WatchParams{})
154- root := r.watch(ctx, domain.Root(), app.WatchParams{Topics: []string{"home"}})154+ root := r.watch(ctx, domain.Root(), ports.WatchParams{Topics: []string{"home"}})
155- scoped := r.watch(ctx, tokenPrincipal(domain.ScopeRepoRead, "other"), app.WatchParams{})155+ scoped := r.watch(ctx, tokenPrincipal(domain.ScopeRepoRead, "other"), ports.WatchParams{})
156- onlyPriv := r.watch(ctx, domain.Root(), app.WatchParams{Topics: []string{"repo:priv"}})156+ onlyPriv := r.watch(ctx, domain.Root(), ports.WatchParams{Topics: []string{"repo:priv"}})
157 synctest.Wait()157 synctest.Wait()
158 require.Equal(t, 4, r.events.Watchers())158 require.Equal(t, 4, r.events.Watchers())
159 159
@@ -191,7 +191,7 @@ func TestWatchRechecksAccessPerEvent(t *testing.T) {
191 })191 })
192 ctx, cancel := context.WithCancel(context.Background())192 ctx, cancel := context.WithCancel(context.Background())
193 defer cancel()193 defer cancel()
194- w := r.watch(ctx, domain.Anonymous(), app.WatchParams{})194+ w := r.watch(ctx, domain.Anonymous(), ports.WatchParams{})
195 synctest.Wait()195 synctest.Wait()
196 require.Equal(t, 1, r.events.Watchers())196 require.Equal(t, 1, r.events.Watchers())
197 197
@@ -217,7 +217,7 @@ func TestWatchResumeAndGap(t *testing.T) {
217 ctx, cancel := context.WithCancel(context.Background())217 ctx, cancel := context.WithCancel(context.Background())
218 t.Cleanup(cancel)218 t.Cleanup(cancel)
219 219
220- first := r.watch(ctx, domain.Root(), app.WatchParams{})220+ first := r.watch(ctx, domain.Root(), ports.WatchParams{})
221 synctest.Wait()221 synctest.Wait()
222 require.Equal(t, 1, r.events.Watchers())222 require.Equal(t, 1, r.events.Watchers())
223 // One at a time: this watcher must not fall behind the small ring.223 // One at a time: this watcher must not fall behind the small ring.
@@ -235,7 +235,7 @@ func TestWatchResumeAndGap(t *testing.T) {
235 r, _, ctx, ids := setup(t)235 r, _, ctx, ids := setup(t)
236 // 20 events were taken, the buffer holds the last 8: ids[12] is the236 // 20 events were taken, the buffer holds the last 8: ids[12] is the
237 // oldest still present; ids[0] is long gone.237 // oldest still present; ids[0] is long gone.
238- w := r.watch(ctx, domain.Root(), app.WatchParams{ResumeAfter: ids[0]})238+ w := r.watch(ctx, domain.Root(), ports.WatchParams{ResumeAfter: ids[0]})
239 gap := w.next(t)239 gap := w.next(t)
240 assert.True(t, gap.Gap)240 assert.True(t, gap.Gap)
241 assert.Empty(t, gap.ID)241 assert.Empty(t, gap.ID)
@@ -250,7 +250,7 @@ func TestWatchResumeAndGap(t *testing.T) {
250 synctest.Test(t, func(t *testing.T) {250 synctest.Test(t, func(t *testing.T) {
251 r, _, ctx, _ := setup(t)251 r, _, ctx, _ := setup(t)
252 for _, id := range []string{"nonsense", "zz-1", "-", "0-0"} {252 for _, id := range []string{"nonsense", "zz-1", "-", "0-0"} {
253- w := r.watch(ctx, domain.Root(), app.WatchParams{ResumeAfter: id})253+ w := r.watch(ctx, domain.Root(), ports.WatchParams{ResumeAfter: id})
254 assert.True(t, w.next(t).Gap, id)254 assert.True(t, w.next(t).Gap, id)
255 }255 }
256 })256 })
@@ -260,7 +260,7 @@ func TestWatchResumeAndGap(t *testing.T) {
260 t.Parallel()260 t.Parallel()
261 synctest.Test(t, func(t *testing.T) {261 synctest.Test(t, func(t *testing.T) {
262 r, _, ctx, _ := setup(t)262 r, _, ctx, _ := setup(t)
263- w := r.watch(ctx, domain.Root(), app.WatchParams{ResumeAfter: "otherboot-999"})263+ w := r.watch(ctx, domain.Root(), ports.WatchParams{ResumeAfter: "otherboot-999"})
264 assert.True(t, w.next(t).Gap)264 assert.True(t, w.next(t).Gap)
265 })265 })
266 })266 })
@@ -269,7 +269,7 @@ func TestWatchResumeAndGap(t *testing.T) {
269 t.Parallel()269 t.Parallel()
270 synctest.Test(t, func(t *testing.T) {270 synctest.Test(t, func(t *testing.T) {
271 r, n, ctx, ids := setup(t)271 r, n, ctx, ids := setup(t)
272- w := r.watch(ctx, domain.Root(), app.WatchParams{ResumeAfter: ids[16]})272+ w := r.watch(ctx, domain.Root(), ports.WatchParams{ResumeAfter: ids[16]})
273 for _, want := range ids[17:] {273 for _, want := range ids[17:] {
274 e := w.next(t)274 e := w.next(t)
275 assert.False(t, e.Gap)275 assert.False(t, e.Gap)
@@ -287,7 +287,7 @@ func TestWatchResumeAndGap(t *testing.T) {
287 t.Parallel()287 t.Parallel()
288 synctest.Test(t, func(t *testing.T) {288 synctest.Test(t, func(t *testing.T) {
289 r, n, ctx, ids := setup(t)289 r, n, ctx, ids := setup(t)
290- w := r.watch(ctx, domain.Root(), app.WatchParams{ResumeAfter: ids[len(ids)-1]})290+ w := r.watch(ctx, domain.Root(), ports.WatchParams{ResumeAfter: ids[len(ids)-1]})
291 r.push(n, 99)291 r.push(n, 99)
292 e := w.next(t)292 e := w.next(t)
293 assert.False(t, e.Gap)293 assert.False(t, e.Gap)
@@ -302,7 +302,7 @@ func TestWatchTopicErrors(t *testing.T) {
302 r.repo("pub", domain.VisibilityPublic)302 r.repo("pub", domain.VisibilityPublic)
303 r.repo("priv", domain.VisibilityPrivate)303 r.repo("priv", domain.VisibilityPrivate)
304 r.meta.EXPECT().Get(mock.Anything, name(t, "gone")).Return(domain.Repo{}, domain.ErrNotFound).Maybe()304 r.meta.EXPECT().Get(mock.Anything, name(t, "gone")).Return(domain.Repo{}, domain.ErrNotFound).Maybe()
305- noop := func(app.WatchEvent) error { return nil }305+ noop := func(ports.WatchEvent) error { return nil }
306 ctx := context.Background()306 ctx := context.Background()
307 307
308 for topic, want := range map[string]error{308 for topic, want := range map[string]error{
@@ -313,7 +313,7 @@ func TestWatchTopicErrors(t *testing.T) {
313 "repo:priv": domain.ErrNotFound, // hidden from anonymous313 "repo:priv": domain.ErrNotFound, // hidden from anonymous
314 "repo:pub:foo": domain.ErrInvalidName,314 "repo:pub:foo": domain.ErrInvalidName,
315 } {315 } {
316- err := r.events.Watch(ctx, domain.Anonymous(), app.WatchParams{Topics: []string{topic}}, noop)316+ err := r.events.Watch(ctx, domain.Anonymous(), ports.WatchParams{Topics: []string{topic}}, noop)
317 assert.ErrorIs(t, err, want, topic)317 assert.ErrorIs(t, err, want, topic)
318 }318 }
319 }319 }
@@ -325,7 +325,7 @@ func TestWatchEndsCleanly(t *testing.T) {
325 synctest.Test(t, func(t *testing.T) {325 synctest.Test(t, func(t *testing.T) {
326 r := newEventRig(t, 0)326 r := newEventRig(t, 0)
327 ctx, cancel := context.WithCancel(context.Background())327 ctx, cancel := context.WithCancel(context.Background())
328- w := r.watch(ctx, domain.Root(), app.WatchParams{})328+ w := r.watch(ctx, domain.Root(), ports.WatchParams{})
329 synctest.Wait()329 synctest.Wait()
330 require.Equal(t, 1, r.events.Watchers())330 require.Equal(t, 1, r.events.Watchers())
331 cancel()331 cancel()
@@ -344,7 +344,7 @@ func TestWatchEndsCleanly(t *testing.T) {
344 t.Parallel()344 t.Parallel()
345 synctest.Test(t, func(t *testing.T) {345 synctest.Test(t, func(t *testing.T) {
346 r := newEventRig(t, 0)346 r := newEventRig(t, 0)
347- w := r.watch(context.Background(), domain.Root(), app.WatchParams{})347+ w := r.watch(context.Background(), domain.Root(), ports.WatchParams{})
348 synctest.Wait()348 synctest.Wait()
349 require.Equal(t, 1, r.events.Watchers())349 require.Equal(t, 1, r.events.Watchers())
350 r.events.Close(context.Background())350 r.events.Close(context.Background())
@@ -367,7 +367,7 @@ func TestWatchEndsCleanly(t *testing.T) {
367 boom := errors.New("client went away")367 boom := errors.New("client went away")
368 done := make(chan error, 1)368 done := make(chan error, 1)
369 go func() {369 go func() {
370- done <- r.events.Watch(context.Background(), domain.Root(), app.WatchParams{}, func(app.WatchEvent) error { return boom })370+ done <- r.events.Watch(context.Background(), domain.Root(), ports.WatchParams{}, func(ports.WatchEvent) error { return boom })
371 }()371 }()
372 synctest.Wait()372 synctest.Wait()
373 require.Equal(t, 1, r.events.Watchers())373 require.Equal(t, 1, r.events.Watchers())
@@ -386,12 +386,12 @@ func TestWatchSlowWatcherGetsAGap(t *testing.T) {
386 386
387 release := make(chan struct{})387 release := make(chan struct{})
388 var mu sync.Mutex388 var mu sync.Mutex
389- var got []app.WatchEvent389+ var got []ports.WatchEvent
390 done := make(chan error, 1)390 done := make(chan error, 1)
391 blocked := make(chan struct{})391 blocked := make(chan struct{})
392 first := true392 first := true
393 go func() {393 go func() {
394- done <- r.events.Watch(context.Background(), domain.Root(), app.WatchParams{}, func(e app.WatchEvent) error {394+ done <- r.events.Watch(context.Background(), domain.Root(), ports.WatchParams{}, func(e ports.WatchEvent) error {
395 if first { // block on the first event while the ring turns over395 if first { // block on the first event while the ring turns over
396 first = false396 first = false
397 close(blocked)397 close(blocked)
@@ -405,7 +405,7 @@ func TestWatchSlowWatcherGetsAGap(t *testing.T) {
405 }()405 }()
406 // A second, fast watcher tells when the use case has taken every event:406 // A second, fast watcher tells when the use case has taken every event:
407 // it receives them in order.407 // it receives them in order.
408- fast := r.watch(context.Background(), domain.Root(), app.WatchParams{})408+ fast := r.watch(context.Background(), domain.Root(), ports.WatchParams{})
409 synctest.Wait()409 synctest.Wait()
410 require.Equal(t, 2, r.events.Watchers())410 require.Equal(t, 2, r.events.Watchers())
411 r.push(n, 0)411 r.push(n, 0)
@@ -444,8 +444,8 @@ func TestEventsRepoPayloads(t *testing.T) {
444 priv := r.repo("priv", domain.VisibilityPrivate)444 priv := r.repo("priv", domain.VisibilityPrivate)
445 ctx, cancel := context.WithCancel(context.Background())445 ctx, cancel := context.WithCancel(context.Background())
446 defer cancel()446 defer cancel()
447- anon := r.watch(ctx, domain.Anonymous(), app.WatchParams{})447+ anon := r.watch(ctx, domain.Anonymous(), ports.WatchParams{})
448- root := r.watch(ctx, domain.Root(), app.WatchParams{})448+ root := r.watch(ctx, domain.Root(), ports.WatchParams{})
449 synctest.Wait()449 synctest.Wait()
450 require.Equal(t, 2, r.events.Watchers())450 require.Equal(t, 2, r.events.Watchers())
451 451
@@ -493,7 +493,7 @@ func TestWatchEndsWhenTheTokenIsRevoked(t *testing.T) {
493 ctx, cancel := context.WithCancel(context.Background())493 ctx, cancel := context.WithCancel(context.Background())
494 defer cancel()494 defer cancel()
495 495
496- w := r.watch(ctx, p, app.WatchParams{})496+ w := r.watch(ctx, p, ports.WatchParams{})
497 synctest.Wait()497 synctest.Wait()
498 require.Equal(t, 1, r.events.Watchers())498 require.Equal(t, 1, r.events.Watchers())
499 r.push(pub, 1)499 r.push(pub, 1)
@@ -523,7 +523,7 @@ func TestWatchEndsWhenTheTokenExpires(t *testing.T) {
523 pub := r.repo("pub", domain.VisibilityPublic)523 pub := r.repo("pub", domain.VisibilityPublic)
524 // The rig's clock stands at now: this token expired at now.524 // The rig's clock stands at now: this token expired at now.
525 p := tokenWith("tok", now)525 p := tokenWith("tok", now)
526- w := r.watch(context.Background(), p, app.WatchParams{})526+ w := r.watch(context.Background(), p, ports.WatchParams{})
527 r.push(pub, 1) // whether or not the watch had started, it ends unauthenticated527 r.push(pub, 1) // whether or not the watch had started, it ends unauthenticated
528 synctest.Wait()528 synctest.Wait()
529 select {529 select {
@@ -542,7 +542,7 @@ func TestWatchRevalidatesIdleWatchersAndCachesTheAnswer(t *testing.T) {
542 t.Parallel()542 t.Parallel()
543 synctest.Test(t, func(t *testing.T) {543 synctest.Test(t, func(t *testing.T) {
544 r := newEventRigConfig(t, app.EventsConfig{}) // the production default interval544 r := newEventRigConfig(t, app.EventsConfig{}) // the production default interval
545- w := r.watch(context.Background(), tokenWith("tok", time.Time{}), app.WatchParams{})545+ w := r.watch(context.Background(), tokenWith("tok", time.Time{}), ports.WatchParams{})
546 synctest.Wait()546 synctest.Wait()
547 require.Equal(t, 1, r.events.Watchers())547 require.Equal(t, 1, r.events.Watchers())
548 r.revoked.Store("tok", true)548 r.revoked.Store("tok", true)
@@ -569,7 +569,7 @@ func TestWatchRevalidatesIdleWatchersAndCachesTheAnswer(t *testing.T) {
569 synctest.Test(t, func(t *testing.T) {569 synctest.Test(t, func(t *testing.T) {
570 r := newEventRigConfig(t, app.EventsConfig{RevalidateEvery: time.Hour})570 r := newEventRigConfig(t, app.EventsConfig{RevalidateEvery: time.Hour})
571 pub := r.repo("pub", domain.VisibilityPublic)571 pub := r.repo("pub", domain.VisibilityPublic)
572- w := r.watch(context.Background(), tokenWith("tok", time.Time{}), app.WatchParams{})572+ w := r.watch(context.Background(), tokenWith("tok", time.Time{}), ports.WatchParams{})
573 synctest.Wait()573 synctest.Wait()
574 require.Equal(t, 1, r.events.Watchers())574 require.Equal(t, 1, r.events.Watchers())
575 for i := range 20 {575 for i := range 20 {
@@ -592,8 +592,8 @@ func TestWatchRevalidatesIdleWatchersAndCachesTheAnswer(t *testing.T) {
592 synctest.Test(t, func(t *testing.T) {592 synctest.Test(t, func(t *testing.T) {
593 r := newEventRigConfig(t, app.EventsConfig{RevalidateEvery: -1})593 r := newEventRigConfig(t, app.EventsConfig{RevalidateEvery: -1})
594 pub := r.repo("pub", domain.VisibilityPublic)594 pub := r.repo("pub", domain.VisibilityPublic)
595- a := r.watch(context.Background(), domain.Anonymous(), app.WatchParams{})595+ a := r.watch(context.Background(), domain.Anonymous(), ports.WatchParams{})
596- b := r.watch(context.Background(), domain.Root(), app.WatchParams{})596+ b := r.watch(context.Background(), domain.Root(), ports.WatchParams{})
597 synctest.Wait()597 synctest.Wait()
598 require.Equal(t, 2, r.events.Watchers())598 require.Equal(t, 2, r.events.Watchers())
599 r.push(pub, 1)599 r.push(pub, 1)
@@ -606,7 +606,7 @@ func TestWatchRevalidatesIdleWatchersAndCachesTheAnswer(t *testing.T) {
606 606
607 func TestWatchLimits(t *testing.T) {607 func TestWatchLimits(t *testing.T) {
608 t.Parallel()608 t.Parallel()
609- noop := func(app.WatchEvent) error { return nil }609+ noop := func(ports.WatchEvent) error { return nil }
610 610
611 t.Run("too many topics", func(t *testing.T) {611 t.Run("too many topics", func(t *testing.T) {
612 t.Parallel()612 t.Parallel()
@@ -619,8 +619,8 @@ func TestWatchLimits(t *testing.T) {
619 }619 }
620 ctx, cancel := context.WithCancel(context.Background())620 ctx, cancel := context.WithCancel(context.Background())
621 cancel() // returns at once once the topics are accepted621 cancel() // returns at once once the topics are accepted
622- assert.NoError(t, r.events.Watch(ctx, domain.Root(), app.WatchParams{Topics: ok}, noop))622+ assert.NoError(t, r.events.Watch(ctx, domain.Root(), ports.WatchParams{Topics: ok}, noop))
623- err := r.events.Watch(context.Background(), domain.Root(), app.WatchParams{Topics: append(ok, "home")}, noop)623+ err := r.events.Watch(context.Background(), domain.Root(), ports.WatchParams{Topics: append(ok, "home")}, noop)
624 assert.ErrorIs(t, err, domain.ErrInvalid)624 assert.ErrorIs(t, err, domain.ErrInvalid)
625 })625 })
626 })626 })
@@ -632,11 +632,11 @@ func TestWatchLimits(t *testing.T) {
632 ctx, cancel := context.WithCancel(context.Background())632 ctx, cancel := context.WithCancel(context.Background())
633 defer cancel()633 defer cancel()
634 for range app.MaxWatchers {634 for range app.MaxWatchers {
635- r.watch(ctx, domain.Root(), app.WatchParams{})635+ r.watch(ctx, domain.Root(), ports.WatchParams{})
636 }636 }
637 synctest.Wait()637 synctest.Wait()
638 require.Equal(t, app.MaxWatchers, r.events.Watchers())638 require.Equal(t, app.MaxWatchers, r.events.Watchers())
639- err := r.events.Watch(ctx, domain.Root(), app.WatchParams{}, noop)639+ err := r.events.Watch(ctx, domain.Root(), ports.WatchParams{}, noop)
640 assert.ErrorIs(t, err, domain.ErrResourceExhausted)640 assert.ErrorIs(t, err, domain.ErrResourceExhausted)
641 assert.Equal(t, app.MaxWatchers, r.events.Watchers(), "a refused watch is not counted")641 assert.Equal(t, app.MaxWatchers, r.events.Watchers(), "a refused watch is not counted")
642 642
@@ -645,7 +645,7 @@ func TestWatchLimits(t *testing.T) {
645 require.Equal(t, 0, r.events.Watchers())645 require.Equal(t, 0, r.events.Watchers())
646 again, stop := context.WithCancel(context.Background())646 again, stop := context.WithCancel(context.Background())
647 stop()647 stop()
648- assert.NoError(t, r.events.Watch(again, domain.Root(), app.WatchParams{}, noop))648+ assert.NoError(t, r.events.Watch(again, domain.Root(), ports.WatchParams{}, noop))
649 })649 })
650 })650 })
651 651
@@ -656,17 +656,17 @@ func TestWatchLimits(t *testing.T) {
656 ctx, cancel := context.WithCancel(context.Background())656 ctx, cancel := context.WithCancel(context.Background())
657 defer cancel()657 defer cancel()
658 for range app.MaxWatchersPerClient {658 for range app.MaxWatchersPerClient {
659- r.watch(ctx, domain.Root(), app.WatchParams{Client: "198.51.100.1"})659+ r.watch(ctx, domain.Root(), ports.WatchParams{Client: "198.51.100.1"})
660 }660 }
661 synctest.Wait()661 synctest.Wait()
662 require.Equal(t, app.MaxWatchersPerClient, r.events.Watchers())662 require.Equal(t, app.MaxWatchersPerClient, r.events.Watchers())
663 663
664- err := r.events.Watch(ctx, domain.Root(), app.WatchParams{Client: "198.51.100.1"}, noop)664+ err := r.events.Watch(ctx, domain.Root(), ports.WatchParams{Client: "198.51.100.1"}, noop)
665 assert.ErrorIs(t, err, domain.ErrResourceExhausted)665 assert.ErrorIs(t, err, domain.ErrResourceExhausted)
666 666
667 // Another client, and callers in process, are not affected.667 // Another client, and callers in process, are not affected.
668- r.watch(ctx, domain.Root(), app.WatchParams{Client: "198.51.100.2"})668+ r.watch(ctx, domain.Root(), ports.WatchParams{Client: "198.51.100.2"})
669- r.watch(ctx, domain.Root(), app.WatchParams{})669+ r.watch(ctx, domain.Root(), ports.WatchParams{})
670 synctest.Wait()670 synctest.Wait()
671 require.Equal(t, app.MaxWatchersPerClient+2, r.events.Watchers())671 require.Equal(t, app.MaxWatchersPerClient+2, r.events.Watchers())
672 672
@@ -675,7 +675,7 @@ func TestWatchLimits(t *testing.T) {
675 require.Equal(t, 0, r.events.Watchers())675 require.Equal(t, 0, r.events.Watchers())
676 again, stop := context.WithCancel(context.Background())676 again, stop := context.WithCancel(context.Background())
677 stop()677 stop()
678- assert.NoError(t, r.events.Watch(again, domain.Root(), app.WatchParams{Client: "198.51.100.1"}, noop), "the slots were released")678+ assert.NoError(t, r.events.Watch(again, domain.Root(), ports.WatchParams{Client: "198.51.100.1"}, noop), "the slots were released")
679 })679 })
680 })680 })
681 }681 }
@@ -712,7 +712,7 @@ func TestEventsKeepTheVisibilityOfTheirTime(t *testing.T) {
712 defer cancel()712 defer cancel()
713 r := newEventRig(t, 0)713 r := newEventRig(t, 0)
714 l, n := r.liveRepo("secret", domain.VisibilityPrivate, now)714 l, n := r.liveRepo("secret", domain.VisibilityPrivate, now)
715- root := r.watch(ctx, domain.Root(), app.WatchParams{})715+ root := r.watch(ctx, domain.Root(), ports.WatchParams{})
716 synctest.Wait()716 synctest.Wait()
717 require.Equal(t, 1, r.events.Watchers())717 require.Equal(t, 1, r.events.Watchers())
718 r.push(n, 1)718 r.push(n, 1)
@@ -728,7 +728,7 @@ func TestEventsKeepTheVisibilityOfTheirTime(t *testing.T) {
728 // An anonymous caller that resumes with an id nobody issued gets what728 // An anonymous caller that resumes with an id nobody issued gets what
729 // is buffered, filtered: the public update and the public push, not the729 // is buffered, filtered: the public update and the public push, not the
730 // pushes of the private era.730 // pushes of the private era.
731- anon := r.watch(ctx, domain.Anonymous(), app.WatchParams{ResumeAfter: "bogus"})731+ anon := r.watch(ctx, domain.Anonymous(), ports.WatchParams{ResumeAfter: "bogus"})
732 assert.True(t, anon.next(t).Gap)732 assert.True(t, anon.next(t).Gap)
733 upd := anon.next(t)733 upd := anon.next(t)
734 require.IsType(t, domain.RepoUpdated{}, upd.Event)734 require.IsType(t, domain.RepoUpdated{}, upd.Event)
@@ -747,7 +747,7 @@ func TestEventsKeepTheVisibilityOfTheirTime(t *testing.T) {
747 r := newEventRig(t, 0)747 r := newEventRig(t, 0)
748 first := now.Add(-time.Hour)748 first := now.Add(-time.Hour)
749 l, n := r.liveRepo("again", domain.VisibilityPublic, first)749 l, n := r.liveRepo("again", domain.VisibilityPublic, first)
750- root := r.watch(ctx, domain.Root(), app.WatchParams{})750+ root := r.watch(ctx, domain.Root(), ports.WatchParams{})
751 synctest.Wait()751 synctest.Wait()
752 require.Equal(t, 1, r.events.Watchers())752 require.Equal(t, 1, r.events.Watchers())
753 r.push(n, 1)753 r.push(n, 1)
@@ -757,7 +757,7 @@ func TestEventsKeepTheVisibilityOfTheirTime(t *testing.T) {
757 r.push(n, 2)757 r.push(n, 2)
758 root.next(t)758 root.next(t)
759 759
760- anon := r.watch(ctx, domain.Anonymous(), app.WatchParams{ResumeAfter: "bogus"})760+ anon := r.watch(ctx, domain.Anonymous(), ports.WatchParams{ResumeAfter: "bogus"})
761 assert.True(t, anon.next(t).Gap)761 assert.True(t, anon.next(t).Gap)
762 e := anon.next(t)762 e := anon.next(t)
763 require.IsType(t, domain.PushEvent{}, e.Event)763 require.IsType(t, domain.PushEvent{}, e.Event)
@@ -773,10 +773,10 @@ func TestEventsKeepTheVisibilityOfTheirTime(t *testing.T) {
773 defer cancel()773 defer cancel()
774 r := newEventRig(t, 0)774 r := newEventRig(t, 0)
775 l, n := r.liveRepo("hide", domain.VisibilityPublic, now)775 l, n := r.liveRepo("hide", domain.VisibilityPublic, now)
776- anon := r.watch(ctx, domain.Anonymous(), app.WatchParams{})776+ anon := r.watch(ctx, domain.Anonymous(), ports.WatchParams{})
777- reader := r.watch(ctx, tokenPrincipal(domain.ScopeRepoRead, "hide"), app.WatchParams{})777+ reader := r.watch(ctx, tokenPrincipal(domain.ScopeRepoRead, "hide"), ports.WatchParams{})
778- other := r.watch(ctx, tokenPrincipal(domain.ScopeRepoRead, "elsewhere"), app.WatchParams{})778+ other := r.watch(ctx, tokenPrincipal(domain.ScopeRepoRead, "elsewhere"), ports.WatchParams{})
779- root := r.watch(ctx, domain.Root(), app.WatchParams{})779+ root := r.watch(ctx, domain.Root(), ports.WatchParams{})
780 synctest.Wait()780 synctest.Wait()
781 require.Equal(t, 4, r.events.Watchers())781 require.Equal(t, 4, r.events.Watchers())
782 782
@@ -809,7 +809,7 @@ func TestEventsKeepTheVisibilityOfTheirTime(t *testing.T) {
809 defer cancel()809 defer cancel()
810 r := newEventRig(t, 0)810 r := newEventRig(t, 0)
811 _, n := r.liveRepo("closed", domain.VisibilityPrivate, now)811 _, n := r.liveRepo("closed", domain.VisibilityPrivate, now)
812- anon := r.watch(ctx, domain.Anonymous(), app.WatchParams{})812+ anon := r.watch(ctx, domain.Anonymous(), ports.WatchParams{})
813 synctest.Wait()813 synctest.Wait()
814 require.Equal(t, 1, r.events.Watchers())814 require.Equal(t, 1, r.events.Watchers())
815 r.feed <- domain.RepoUpdated{Repo: n, At: now, Visibility: domain.VisibilityPrivate, Previous: domain.VisibilityPrivate, CreatedAt: now}815 r.feed <- domain.RepoUpdated{Repo: n, At: now, Visibility: domain.VisibilityPrivate, Previous: domain.VisibilityPrivate, CreatedAt: now}
@@ -842,8 +842,8 @@ func TestPushEventsKeepTheirSnapshot(t *testing.T) {
842 defer cancel()842 defer cancel()
843 r := newEventRig(t, 0)843 r := newEventRig(t, 0)
844 l, n := r.liveRepo("secret", domain.VisibilityPrivate, now)844 l, n := r.liveRepo("secret", domain.VisibilityPrivate, now)
845- anon := r.watch(ctx, domain.Anonymous(), app.WatchParams{})845+ anon := r.watch(ctx, domain.Anonymous(), ports.WatchParams{})
846- root := r.watch(ctx, domain.Root(), app.WatchParams{})846+ root := r.watch(ctx, domain.Root(), ports.WatchParams{})
847 synctest.Wait()847 synctest.Wait()
848 require.Equal(t, 2, r.events.Watchers())848 require.Equal(t, 2, r.events.Watchers())
849 849
@@ -859,7 +859,7 @@ func TestPushEventsKeepTheirSnapshot(t *testing.T) {
859 859
860 anon.quiet(t, r, r.repo("open", domain.VisibilityPublic))860 anon.quiet(t, r, r.repo("open", domain.VisibilityPublic))
861 861
862- late := r.watch(ctx, domain.Anonymous(), app.WatchParams{ResumeAfter: "bogus"})862+ late := r.watch(ctx, domain.Anonymous(), ports.WatchParams{ResumeAfter: "bogus"})
863 assert.True(t, late.next(t).Gap)863 assert.True(t, late.next(t).Gap)
864 late.quiet(t, r, r.repo("open2", domain.VisibilityPublic))864 late.quiet(t, r, r.repo("open2", domain.VisibilityPublic))
865 })865 })
@@ -873,7 +873,7 @@ func TestPushEventsKeepTheirSnapshot(t *testing.T) {
873 defer cancel()873 defer cancel()
874 r := newEventRig(t, 0)874 r := newEventRig(t, 0)
875 n := r.repo("ghost", domain.VisibilityPublic)875 n := r.repo("ghost", domain.VisibilityPublic)
876- root := r.watch(ctx, domain.Root(), app.WatchParams{})876+ root := r.watch(ctx, domain.Root(), ports.WatchParams{})
877 synctest.Wait()877 synctest.Wait()
878 require.Equal(t, 1, r.events.Watchers())878 require.Equal(t, 1, r.events.Watchers())
879 r.feed <- domain.PushEvent{Repo: n, Visibility: domain.VisibilityPublic, At: now}879 r.feed <- domain.PushEvent{Repo: n, Visibility: domain.VisibilityPublic, At: now}
M internal/app/maintenance.go +2 -0
@@ -38,6 +38,8 @@ type Maintainer struct {
38 wg sync.WaitGroup38 wg sync.WaitGroup
39 }39 }
40 40
41+var _ ports.MaintenanceScheduler = (*Maintainer)(nil)
42+
41 type maintEntry struct {43 type maintEntry struct {
42 timer *time.Timer // pending run, nil while running or idle44 timer *time.Timer // pending run, nil while running or idle
43 running bool45 running bool
M internal/app/mergerequests.go +29 -102
@@ -47,12 +47,14 @@ type MergeRequests struct {
47 committer domain.Identity47 committer domain.Identity
48 bus ports.EventBus48 bus ports.EventBus
49 clock ports.Clock49 clock ports.Clock
50- maint MaintenanceScheduler50+ maint ports.MaintenanceScheduler
51 51
52 mu sync.Mutex52 mu sync.Mutex
53 locks map[domain.RepoName]chan struct{}53 locks map[domain.RepoName]chan struct{}
54 }54 }
55 55
56+var _ ports.MergeRequestService = (*MergeRequests)(nil)
57+
56 // MergeRequestsConfig holds what the merge request use cases need. Every field58 // MergeRequestsConfig holds what the merge request use cases need. Every field
57 // is required.59 // is required.
58 type MergeRequestsConfig struct {60 type MergeRequestsConfig struct {
@@ -68,7 +70,7 @@ type MergeRequestsConfig struct {
68 Bus ports.EventBus70 Bus ports.EventBus
69 Clock ports.Clock71 Clock ports.Clock
70 // Maint queues the maintenance that follows a merge, as it does a push.72 // Maint queues the maintenance that follows a merge, as it does a push.
71- Maint MaintenanceScheduler73+ Maint ports.MaintenanceScheduler
72 }74 }
73 75
74 // NewMergeRequests builds the merge request use cases.76 // NewMergeRequests builds the merge request use cases.
@@ -161,20 +163,12 @@ func (m *MergeRequests) branchOrZero(ctx context.Context, repo domain.RepoName,
161 return id, err163 return id, err
162 }164 }
163 165
164-// CreateMergeRequestParams describes the merge request to open.
165-type CreateMergeRequestParams struct {
166- Title string
167- Description string
168- // Source is required; Target defaults to the default branch.
169- Source, Target string
170-}
171-
172 // Create opens a merge request and publishes MergeRequestOpened. The principal166 // Create opens a merge request and publishes MergeRequestOpened. The principal
173 // needs mr:write. A request that breaks a rule of ADR 0017 (the texts, a167 // needs mr:write. A request that breaks a rule of ADR 0017 (the texts, a
174 // branch that does not exist, source and target the same) is rejected whole168 // branch that does not exist, source and target the same) is rejected whole
175 // with an *domain.InvalidFieldsError naming the fields. An open request with169 // with an *domain.InvalidFieldsError naming the fields. An open request with
176 // the same source and target gives domain.ErrAlreadyExists.170 // the same source and target gives domain.ErrAlreadyExists.
177-func (m *MergeRequests) Create(ctx context.Context, p domain.Principal, name domain.RepoName, params CreateMergeRequestParams) (domain.MergeRequest, error) {171+func (m *MergeRequests) Create(ctx context.Context, p domain.Principal, name domain.RepoName, params ports.CreateMergeRequestParams) (domain.MergeRequest, error) {
178 repo, err := m.writable(ctx, p, name, domain.ScopeMRWrite)172 repo, err := m.writable(ctx, p, name, domain.ScopeMRWrite)
179 if err != nil {173 if err != nil {
180 return domain.MergeRequest{}, err174 return domain.MergeRequest{}, err
@@ -239,7 +233,7 @@ func (m *MergeRequests) Create(ctx context.Context, p domain.Principal, name dom
239 // Only a branch that does not exist is a violation. Any other failure to read233 // Only a branch that does not exist is a violation. Any other failure to read
240 // Git is returned as an error, so that it is logged and masked, not shown in234 // Git is returned as an error, so that it is logged and masked, not shown in
241 // the field violations.235 // the field violations.
242-func (m *MergeRequests) checkBranches(ctx context.Context, name domain.RepoName, params CreateMergeRequestParams, v *domain.Violations) (source, target string, err error) {236+func (m *MergeRequests) checkBranches(ctx context.Context, name domain.RepoName, params ports.CreateMergeRequestParams, v *domain.Violations) (source, target string, err error) {
243 source, target = params.Source, params.Target237 source, target = params.Source, params.Target
244 if source == "" {238 if source == "" {
245 v.Add("source_branch", "is required")239 v.Add("source_branch", "is required")
@@ -279,44 +273,27 @@ func (m *MergeRequests) checkBranches(ctx context.Context, name domain.RepoName,
279 return source, target, nil273 return source, target, nil
280 }274 }
281 275
282-// ListMergeRequestsParams selects a page of merge requests.
283-type ListMergeRequestsParams struct {
284- // State restricts the result; empty means every state.
285- State domain.MRState
286- // PageSize is 0 for the default, and lowered to MaxPageSize.
287- PageSize int
288- // PageToken is the NextPageToken of the previous page, or empty.
289- PageToken string
290-}
291-
292-// MergeRequestPage is a page of merge requests, highest number first.
293-type MergeRequestPage struct {
294- MergeRequests []domain.MergeRequest
295- // NextPageToken is empty on the last page.
296- NextPageToken string
297-}
298-
299 // List returns a page of the merge requests of a repository, highest number276 // List returns a page of the merge requests of a repository, highest number
300 // first. The open ones carry the head of their source branch. The page token277 // first. The open ones carry the head of their source branch. The page token
301 // names the last number of the previous page, so requests created between two278 // names the last number of the previous page, so requests created between two
302 // pages never shift the others.279 // pages never shift the others.
303-func (m *MergeRequests) List(ctx context.Context, p domain.Principal, name domain.RepoName, params ListMergeRequestsParams) (MergeRequestPage, error) {280+func (m *MergeRequests) List(ctx context.Context, p domain.Principal, name domain.RepoName, params ports.ListMergeRequestsParams) (ports.MergeRequestPage, error) {
304 size, err := pageSize(params.PageSize)281 size, err := pageSize(params.PageSize)
305 if err != nil {282 if err != nil {
306- return MergeRequestPage{}, err283+ return ports.MergeRequestPage{}, err
307 }284 }
308 after, err := decodeMRToken(params.PageToken)285 after, err := decodeMRToken(params.PageToken)
309 if err != nil {286 if err != nil {
310- return MergeRequestPage{}, err287+ return ports.MergeRequestPage{}, err
311 }288 }
312 if _, err := m.readable(ctx, p, name); err != nil {289 if _, err := m.readable(ctx, p, name); err != nil {
313- return MergeRequestPage{}, err290+ return ports.MergeRequestPage{}, err
314 }291 }
315 all, err := m.store.List(ctx, name)292 all, err := m.store.List(ctx, name)
316 if err != nil {293 if err != nil {
317- return MergeRequestPage{}, fmt.Errorf("list merge requests of %q: %w", name, err)294+ return ports.MergeRequestPage{}, fmt.Errorf("list merge requests of %q: %w", name, err)
318 }295 }
319- var page MergeRequestPage296+ var page ports.MergeRequestPage
320 page.MergeRequests, page.NextPageToken = pageAfter(all, size,297 page.MergeRequests, page.NextPageToken = pageAfter(all, size,
321 func(mr domain.MergeRequest) bool {298 func(mr domain.MergeRequest) bool {
322 return (params.State == "" || mr.State == params.State) && (after == 0 || mr.Number < after)299 return (params.State == "" || mr.State == params.State) && (after == 0 || mr.Number < after)
@@ -324,7 +301,7 @@ func (m *MergeRequests) List(ctx context.Context, p domain.Principal, name domai
324 encodeMRToken)301 encodeMRToken)
325 for i, mr := range page.MergeRequests {302 for i, mr := range page.MergeRequests {
326 if page.MergeRequests[i], err = m.withHead(ctx, mr); err != nil {303 if page.MergeRequests[i], err = m.withHead(ctx, mr); err != nil {
327- return MergeRequestPage{}, err304+ return ports.MergeRequestPage{}, err
328 }305 }
329 }306 }
330 return page, nil307 return page, nil
@@ -361,27 +338,6 @@ func (m *MergeRequests) get(ctx context.Context, p domain.Principal, name domain
361 return repo, mr, nil338 return repo, mr, nil
362 }339 }
363 340
364-// MergeRequestDetail is a merge request with its review data.
365-type MergeRequestDetail struct {
366- MergeRequest domain.MergeRequest
367- // Commits are the commits of the head that the target lacks, newest
368- // first, at most MaxMergeRequestCommits.
369- Commits []domain.Commit
370- CommitsTruncated bool
371- // DiffBaseID is the commit the diff compares from; zero when the source
372- // branch is gone, then the diff is empty.
373- DiffBaseID domain.ObjectID
374- Stats domain.DiffStats
375- Diff domain.Diff
376- // Comments are the comments, oldest first.
377- Comments []domain.Comment
378- // LastReadID is the head recorded by MarkRead; zero when none.
379- LastReadID domain.ObjectID
380- // LastReadLost tells that since-last-read was asked for, a head was
381- // recorded, but that commit is gone: the diff is against the target.
382- LastReadLost bool
383-}
384-
385 // Get returns a merge request with its commits, its diff, its comments and the341 // Get returns a merge request with its commits, its diff, its comments and the
386 // read position. The diff compares the merge base of the target and the head342 // read position. The diff compares the merge base of the target and the head
387 // with the head ("target...head"), within domain.DefaultDiffLimits. With343 // with the head ("target...head"), within domain.DefaultDiffLimits. With
@@ -390,25 +346,25 @@ type MergeRequestDetail struct {
390 // still exists; when none was recorded the diff is the default one, and when346 // still exists; when none was recorded the diff is the default one, and when
391 // the recorded commit is gone too, with LastReadLost set. A merged or closed347 // the recorded commit is gone too, with LastReadLost set. A merged or closed
392 // request keeps the head and the base it had.348 // request keeps the head and the base it had.
393-func (m *MergeRequests) Get(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, sinceLastRead bool) (MergeRequestDetail, error) {349+func (m *MergeRequests) Get(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, sinceLastRead bool) (ports.MergeRequestDetail, error) {
394 _, mr, err := m.get(ctx, p, name, number)350 _, mr, err := m.get(ctx, p, name, number)
395 if err != nil {351 if err != nil {
396- return MergeRequestDetail{}, err352+ return ports.MergeRequestDetail{}, err
397 }353 }
398 if mr, err = m.withHead(ctx, mr); err != nil {354 if mr, err = m.withHead(ctx, mr); err != nil {
399- return MergeRequestDetail{}, err355+ return ports.MergeRequestDetail{}, err
400 }356 }
401- d := MergeRequestDetail{MergeRequest: mr}357+ d := ports.MergeRequestDetail{MergeRequest: mr}
402 if d.Comments, err = m.store.ListComments(ctx, name, number); err != nil {358 if d.Comments, err = m.store.ListComments(ctx, name, number); err != nil {
403- return MergeRequestDetail{}, fmt.Errorf("list comments of !%d in %q: %w", number, name, err)359+ return ports.MergeRequestDetail{}, fmt.Errorf("list comments of !%d in %q: %w", number, name, err)
404 }360 }
405 if d.LastReadID, err = m.store.GetRead(ctx, name, number); err != nil {361 if d.LastReadID, err = m.store.GetRead(ctx, name, number); err != nil {
406- return MergeRequestDetail{}, err362+ return ports.MergeRequestDetail{}, err
407 }363 }
408 364
409 exclude, base, err := m.rangeOf(ctx, mr)365 exclude, base, err := m.rangeOf(ctx, mr)
410 if err != nil {366 if err != nil {
411- return MergeRequestDetail{}, err367+ return ports.MergeRequestDetail{}, err
412 }368 }
413 head := mr.HeadID369 head := mr.HeadID
414 if head.IsZero() {370 if head.IsZero() {
@@ -419,18 +375,18 @@ func (m *MergeRequests) Get(ctx context.Context, p domain.Principal, name domain
419 // source branch; once it is deleted, gc may prune them.375 // source branch; once it is deleted, gc may prune them.
420 switch ok, err := m.git.HasCommit(ctx, name, head); {376 switch ok, err := m.git.HasCommit(ctx, name, head); {
421 case err != nil:377 case err != nil:
422- return MergeRequestDetail{}, err378+ return ports.MergeRequestDetail{}, err
423 case !ok:379 case !ok:
424 return d, nil // the reviewed commits are gone: no commits, no diff380 return d, nil // the reviewed commits are gone: no commits, no diff
425 }381 }
426 }382 }
427 if d.Commits, d.CommitsTruncated, err = m.git.ListRange(ctx, name, exclude, head, MaxMergeRequestCommits); err != nil {383 if d.Commits, d.CommitsTruncated, err = m.git.ListRange(ctx, name, exclude, head, MaxMergeRequestCommits); err != nil {
428- return MergeRequestDetail{}, err384+ return ports.MergeRequestDetail{}, err
429 }385 }
430 if sinceLastRead && !d.LastReadID.IsZero() {386 if sinceLastRead && !d.LastReadID.IsZero() {
431 switch ok, err := m.git.HasCommit(ctx, name, d.LastReadID); {387 switch ok, err := m.git.HasCommit(ctx, name, d.LastReadID); {
432 case err != nil:388 case err != nil:
433- return MergeRequestDetail{}, err389+ return ports.MergeRequestDetail{}, err
434 case ok:390 case ok:
435 base = d.LastReadID391 base = d.LastReadID
436 default:392 default:
@@ -442,7 +398,7 @@ func (m *MergeRequests) Get(ctx context.Context, p domain.Principal, name domain
442 }398 }
443 d.DiffBaseID = base399 d.DiffBaseID = base
444 if d.Stats, d.Diff, err = m.git.DiffCommits(ctx, name, base, head, domain.DefaultDiffLimits()); err != nil {400 if d.Stats, d.Diff, err = m.git.DiffCommits(ctx, name, base, head, domain.DefaultDiffLimits()); err != nil {
445- return MergeRequestDetail{}, err401+ return ports.MergeRequestDetail{}, err
446 }402 }
447 return d, nil403 return d, nil
448 }404 }
@@ -470,23 +426,13 @@ func (m *MergeRequests) rangeOf(ctx context.Context, mr domain.MergeRequest) (ex
470 return tip, base, err426 return tip, base, err
471 }427 }
472 428
473-// AddCommentParams describes the comment to add.
474-type AddCommentParams struct {
475- // Path, Line and Side designate a line of the diff; all empty for a
476- // general comment.
477- Path string
478- Line int
479- Side domain.DiffSide
480- Text string
481-}
482-
483 // AddComment adds a comment and publishes CommentAdded. The principal needs429 // AddComment adds a comment and publishes CommentAdded. The principal needs
484 // mr:write. The text must follow the rules of ADR 0017 and not be blank; a430 // mr:write. The text must follow the rules of ADR 0017 and not be blank; a
485 // comment on a line must name a line of the current diff of the request (see431 // comment on a line must name a line of the current diff of the request (see
486 // domain.Diff.HasLine), else the call fails with domain.ErrInvalid. A request432 // domain.Diff.HasLine), else the call fails with domain.ErrInvalid. A request
487 // that is not open gives domain.ErrPreconditionFailed, and one that already433 // that is not open gives domain.ErrPreconditionFailed, and one that already
488 // holds MaxCommentsPerMergeRequest comments domain.ErrResourceExhausted.434 // holds MaxCommentsPerMergeRequest comments domain.ErrResourceExhausted.
489-func (m *MergeRequests) AddComment(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, params AddCommentParams) (domain.Comment, error) {435+func (m *MergeRequests) AddComment(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, params ports.AddCommentParams) (domain.Comment, error) {
490 repo, err := m.writable(ctx, p, name, domain.ScopeMRWrite)436 repo, err := m.writable(ctx, p, name, domain.ScopeMRWrite)
491 if err != nil {437 if err != nil {
492 return domain.Comment{}, err438 return domain.Comment{}, err
@@ -567,7 +513,7 @@ func (m *MergeRequests) AddComment(ctx context.Context, p domain.Principal, name
567 513
568 // checkLine verifies that a comment designates a line of the current diff of514 // checkLine verifies that a comment designates a line of the current diff of
569 // the request.515 // the request.
570-func (m *MergeRequests) checkLine(ctx context.Context, mr domain.MergeRequest, params AddCommentParams) error {516+func (m *MergeRequests) checkLine(ctx context.Context, mr domain.MergeRequest, params ports.AddCommentParams) error {
571 var v domain.Violations517 var v domain.Violations
572 _, base, err := m.rangeOf(ctx, mr)518 _, base, err := m.rangeOf(ctx, mr)
573 if err != nil {519 if err != nil {
@@ -706,25 +652,6 @@ func (m *MergeRequests) MarkRead(ctx context.Context, p domain.Principal, name d
706 return head, nil652 return head, nil
707 }653 }
708 654
709-// MergeParams describes the merge to perform.
710-type MergeParams struct {
711- // Mode defaults to fast-forward.
712- Mode domain.MergeMode
713- // HeadID is the commit the caller reviewed; the source branch must still
714- // point to it.
715- HeadID domain.ObjectID
716- // DeleteSource deletes the source branch after the merge.
717- DeleteSource bool
718-}
719-
720-// MergeResult is a merged request and what happened to its source branch.
721-type MergeResult struct {
722- domain.MergeRequest
723- // SourceDeleted is true when DeleteSource was asked and the branch is
724- // gone.
725- SourceDeleted bool
726-}
727-
728 // Merge merges an open merge request, publishes MergeRequestMerged then a655 // Merge merges an open merge request, publishes MergeRequestMerged then a
729 // PushEvent for the target branch, and returns the merged request. The656 // PushEvent for the target branch, and returns the merged request. The
730 // principal needs mr:merge. Merges of one repository run one at a time.657 // principal needs mr:merge. Merges of one repository run one at a time.
@@ -748,12 +675,12 @@ type MergeResult struct {
748 // request. After the merge the source is deleted if it still points to the675 // request. After the merge the source is deleted if it still points to the
749 // head; the PushEvent then carries the deletion too. A failed deletion is676 // head; the PushEvent then carries the deletion too. A failed deletion is
750 // logged and leaves the branch: the merge stands and SourceDeleted is false.677 // logged and leaves the branch: the merge stands and SourceDeleted is false.
751-func (m *MergeRequests) Merge(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, params MergeParams) (MergeResult, error) {678+func (m *MergeRequests) Merge(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, params ports.MergeParams) (ports.MergeResult, error) {
752 mr, deleted, err := m.merge(ctx, p, name, number, params)679 mr, deleted, err := m.merge(ctx, p, name, number, params)
753- return MergeResult{MergeRequest: mr, SourceDeleted: deleted}, err680+ return ports.MergeResult{MergeRequest: mr, SourceDeleted: deleted}, err
754 }681 }
755 682
756-func (m *MergeRequests) merge(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, params MergeParams) (domain.MergeRequest, bool, error) {683+func (m *MergeRequests) merge(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, params ports.MergeParams) (domain.MergeRequest, bool, error) {
757 repo, err := m.writable(ctx, p, name, domain.ScopeMRMerge)684 repo, err := m.writable(ctx, p, name, domain.ScopeMRMerge)
758 if err != nil {685 if err != nil {
759 return domain.MergeRequest{}, false, err686 return domain.MergeRequest{}, false, err
M internal/app/mergerequests_delete_test.go +13 -12
@@ -12,6 +12,7 @@ import (
12 "bnema.dev/ermite/internal/adapters/gitcli"12 "bnema.dev/ermite/internal/adapters/gitcli"
13 "bnema.dev/ermite/internal/app"13 "bnema.dev/ermite/internal/app"
14 "bnema.dev/ermite/internal/domain"14 "bnema.dev/ermite/internal/domain"
15+ "bnema.dev/ermite/internal/ports"
15 )16 )
16 17
17 // A merge can delete its source branch; the push event carries both refs.18 // A merge can delete its source branch; the push event carries both refs.
@@ -26,7 +27,7 @@ func TestMergeDeletesTheSourceBranch(t *testing.T) {
26 mr := r.open("feat")27 mr := r.open("feat")
27 r.drain()28 r.drain()
28 29
29- res, err := r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, app.MergeParams{Mode: mode, HeadID: head, DeleteSource: true})30+ res, err := r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, ports.MergeParams{Mode: mode, HeadID: head, DeleteSource: true})
30 require.NoError(t, err)31 require.NoError(t, err)
31 assert.True(t, res.SourceDeleted)32 assert.True(t, res.SourceDeleted)
32 assert.Equal(t, domain.MRStateMerged, res.State)33 assert.Equal(t, domain.MRStateMerged, res.State)
@@ -54,7 +55,7 @@ func TestMergeDeletesTheSourceBranch(t *testing.T) {
54 r.seed()55 r.seed()
55 head := r.feature("feat")56 head := r.feature("feat")
56 mr := r.open("feat")57 mr := r.open("feat")
57- res, err := r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, app.MergeParams{HeadID: head})58+ res, err := r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, ports.MergeParams{HeadID: head})
58 require.NoError(t, err)59 require.NoError(t, err)
59 assert.False(t, res.SourceDeleted)60 assert.False(t, res.SourceDeleted)
60 assert.Equal(t, head, r.branchHead("feat"))61 assert.Equal(t, head, r.branchHead("feat"))
@@ -73,10 +74,10 @@ func TestMergeRefusesToDeleteANeededBranch(t *testing.T) {
73 r.sh("checkout", "-q", "-B", "release", "main")74 r.sh("checkout", "-q", "-B", "release", "main")
74 r.push("release")75 r.push("release")
75 mr := r.open("feat")76 mr := r.open("feat")
76- _, err := r.mrs.Create(r.ctx, root, r.repo, app.CreateMergeRequestParams{Title: "to release", Source: "feat", Target: "release"})77+ _, err := r.mrs.Create(r.ctx, root, r.repo, ports.CreateMergeRequestParams{Title: "to release", Source: "feat", Target: "release"})
77 require.NoError(t, err)78 require.NoError(t, err)
78 79
79- _, err = r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, app.MergeParams{HeadID: head, DeleteSource: true})80+ _, err = r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, ports.MergeParams{HeadID: head, DeleteSource: true})
80 require.ErrorIs(t, err, domain.ErrPreconditionFailed)81 require.ErrorIs(t, err, domain.ErrPreconditionFailed)
81 assert.Contains(t, err.Error(), "!2 still uses")82 assert.Contains(t, err.Error(), "!2 still uses")
82 assert.Equal(t, main, r.branchHead("main"), "not merged")83 assert.Equal(t, main, r.branchHead("main"), "not merged")
@@ -95,9 +96,9 @@ func TestMergeRefusesToDeleteANeededBranch(t *testing.T) {
95 r.sh("checkout", "-q", "-B", "sub", "feat")96 r.sh("checkout", "-q", "-B", "sub", "feat")
96 r.commit(author, "d.txt", "dee\n", "add d")97 r.commit(author, "d.txt", "dee\n", "add d")
97 r.push("sub")98 r.push("sub")
98- _, err := r.mrs.Create(r.ctx, root, r.repo, app.CreateMergeRequestParams{Title: "stacked", Source: "sub", Target: "feat"})99+ _, err := r.mrs.Create(r.ctx, root, r.repo, ports.CreateMergeRequestParams{Title: "stacked", Source: "sub", Target: "feat"})
99 require.NoError(t, err)100 require.NoError(t, err)
100- _, err = r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, app.MergeParams{HeadID: head, DeleteSource: true})101+ _, err = r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, ports.MergeParams{HeadID: head, DeleteSource: true})
101 require.ErrorIs(t, err, domain.ErrPreconditionFailed)102 require.ErrorIs(t, err, domain.ErrPreconditionFailed)
102 assert.Equal(t, head, r.branchHead("feat"))103 assert.Equal(t, head, r.branchHead("feat"))
103 })104 })
@@ -109,9 +110,9 @@ func TestMergeRefusesToDeleteANeededBranch(t *testing.T) {
109 r.sh("checkout", "-q", "-B", "old", "main~1")110 r.sh("checkout", "-q", "-B", "old", "main~1")
110 r.push("old")111 r.push("old")
111 old := r.branchHead("old")112 old := r.branchHead("old")
112- mr, err := r.mrs.Create(r.ctx, root, r.repo, app.CreateMergeRequestParams{Title: "back", Source: "main", Target: "old"})113+ mr, err := r.mrs.Create(r.ctx, root, r.repo, ports.CreateMergeRequestParams{Title: "back", Source: "main", Target: "old"})
113 require.NoError(t, err)114 require.NoError(t, err)
114- _, err = r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, app.MergeParams{HeadID: main, DeleteSource: true})115+ _, err = r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, ports.MergeParams{HeadID: main, DeleteSource: true})
115 require.ErrorIs(t, err, domain.ErrPreconditionFailed)116 require.ErrorIs(t, err, domain.ErrPreconditionFailed)
116 assert.Contains(t, err.Error(), "default branch")117 assert.Contains(t, err.Error(), "default branch")
117 assert.Equal(t, old, r.branchHead("old"), "not merged")118 assert.Equal(t, old, r.branchHead("old"), "not merged")
@@ -127,7 +128,7 @@ func TestSquashMergedRequestReadsAfterItsCommitsArePruned(t *testing.T) {
127 r.seed()128 r.seed()
128 head := r.feature("feat")129 head := r.feature("feat")
129 mr := r.open("feat")130 mr := r.open("feat")
130- res, err := r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, app.MergeParams{Mode: domain.MergeModeSquash, HeadID: head, DeleteSource: true})131+ res, err := r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, ports.MergeParams{Mode: domain.MergeModeSquash, HeadID: head, DeleteSource: true})
131 require.NoError(t, err)132 require.NoError(t, err)
132 require.True(t, res.SourceDeleted)133 require.True(t, res.SourceDeleted)
133 134
@@ -154,9 +155,9 @@ func TestCreateAfterAMergeDeletedTheBranch(t *testing.T) {
154 r.seed()155 r.seed()
155 head := r.feature("feat")156 head := r.feature("feat")
156 mr := r.open("feat")157 mr := r.open("feat")
157- _, err := r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, app.MergeParams{HeadID: head, DeleteSource: true})158+ _, err := r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, ports.MergeParams{HeadID: head, DeleteSource: true})
158 require.NoError(t, err)159 require.NoError(t, err)
159- _, err = r.mrs.Create(r.ctx, root, r.repo, app.CreateMergeRequestParams{Title: "late", Source: "feat"})160+ _, err = r.mrs.Create(r.ctx, root, r.repo, ports.CreateMergeRequestParams{Title: "late", Source: "feat"})
160 require.ErrorIs(t, err, domain.ErrInvalid)161 require.ErrorIs(t, err, domain.ErrInvalid)
161 assert.Contains(t, fieldsOf(t, err), "source_branch")162 assert.Contains(t, fieldsOf(t, err), "source_branch")
162 }163 }
@@ -189,7 +190,7 @@ func TestMergeKeepsASourceThatMovedAfterTheMerge(t *testing.T) {
189 })190 })
190 r.drain()191 r.drain()
191 192
192- res, err := mrs.Merge(r.ctx, mrM, r.repo, mr.Number, app.MergeParams{HeadID: head, DeleteSource: true})193+ res, err := mrs.Merge(r.ctx, mrM, r.repo, mr.Number, ports.MergeParams{HeadID: head, DeleteSource: true})
193 require.NoError(t, err, "the merge stands")194 require.NoError(t, err, "the merge stands")
194 assert.False(t, res.SourceDeleted)195 assert.False(t, res.SourceDeleted)
195 assert.Equal(t, domain.MRStateMerged, res.State)196 assert.Equal(t, domain.MRStateMerged, res.State)
M internal/app/mergerequests_integration_test.go +116 -115
@@ -23,8 +23,9 @@ import (
23 "bnema.dev/ermite/internal/adapters/kvstore"23 "bnema.dev/ermite/internal/adapters/kvstore"
24 "bnema.dev/ermite/internal/adapters/sshsig"24 "bnema.dev/ermite/internal/adapters/sshsig"
25 "bnema.dev/ermite/internal/app"25 "bnema.dev/ermite/internal/app"
26- appmocks "bnema.dev/ermite/internal/app/mocks"
27 "bnema.dev/ermite/internal/domain"26 "bnema.dev/ermite/internal/domain"
27+ "bnema.dev/ermite/internal/ports"
28+ "bnema.dev/ermite/internal/ports/mocks"
28 )29 )
29 30
30 // queuedRepos records the repositories whose maintenance was queued.31 // queuedRepos records the repositories whose maintenance was queued.
@@ -58,7 +59,7 @@ type mrRig struct {
58 store *kvstore.Store59 store *kvstore.Store
59 git *gitcli.Git60 git *gitcli.Git
60 bus *eventbus.Bus61 bus *eventbus.Bus
61- sched *appmocks.MaintenanceScheduler62+ sched *mocks.MaintenanceScheduler
62 queued *queuedRepos63 queued *queuedRepos
63 events <-chan domain.Event64 events <-chan domain.Event
64 work string65 work string
@@ -94,7 +95,7 @@ func newMRRig(t *testing.T) *mrRig {
94 require.NoError(t, err)95 require.NoError(t, err)
95 bus := eventbus.NewWithBuffer(1024)96 bus := eventbus.NewWithBuffer(1024)
96 committer := domain.Identity{Name: "ermite", Email: "ermite@git.example.org"}97 committer := domain.Identity{Name: "ermite", Email: "ermite@git.example.org"}
97- sched := appmocks.NewMaintenanceScheduler(t)98+ sched := mocks.NewMaintenanceScheduler(t)
98 queued := &queuedRepos{}99 queued := &queuedRepos{}
99 sched.EXPECT().Schedule(mock.Anything, mock.Anything).Run(queued.add).Return().Maybe()100 sched.EXPECT().Schedule(mock.Anything, mock.Anything).Run(queued.add).Return().Maybe()
100 r := &mrRig{101 r := &mrRig{
@@ -109,7 +110,7 @@ func newMRRig(t *testing.T) *mrRig {
109 Meta: store, Store: store.MergeRequests(), Repos: git, Git: git, Signer: signer,110 Meta: store, Store: store.MergeRequests(), Repos: git, Git: git, Signer: signer,
110 Committer: committer, Bus: bus, Clock: fixedClock{}, Maint: r.sched,111 Committer: committer, Bus: bus, Clock: fixedClock{}, Maint: r.sched,
111 })112 })
112- _, err = r.repos.Create(ctx, root, app.CreateParams{Name: r.repo, Visibility: domain.VisibilityPrivate})113+ _, err = r.repos.Create(ctx, root, ports.CreateRepoParams{Name: r.repo, Visibility: domain.VisibilityPrivate})
113 require.NoError(t, err)114 require.NoError(t, err)
114 r.sh("init", "-q", "--initial-branch=main")115 r.sh("init", "-q", "--initial-branch=main")
115 return r116 return r
@@ -196,7 +197,7 @@ func (r *mrRig) feature(branch string) domain.ObjectID {
196 197
197 func (r *mrRig) open(branch string) domain.MergeRequest {198 func (r *mrRig) open(branch string) domain.MergeRequest {
198 r.t.Helper()199 r.t.Helper()
199- mr, err := r.mrs.Create(r.ctx, root, r.repo, app.CreateMergeRequestParams{Title: "Add " + branch, Source: branch})200+ mr, err := r.mrs.Create(r.ctx, root, r.repo, ports.CreateMergeRequestParams{Title: "Add " + branch, Source: branch})
200 require.NoError(r.t, err)201 require.NoError(r.t, err)
201 return mr202 return mr
202 }203 }
@@ -252,26 +253,26 @@ func TestCreateMergeRequestInputRules(t *testing.T) {
252 253
253 tests := []struct {254 tests := []struct {
254 name string255 name string
255- params app.CreateMergeRequestParams256+ params ports.CreateMergeRequestParams
256 field string // "" for valid257 field string // "" for valid
257 }{258 }{
258- {"valid, default target", app.CreateMergeRequestParams{Title: "t", Source: "feat"}, ""},259+ {"valid, default target", ports.CreateMergeRequestParams{Title: "t", Source: "feat"}, ""},
259- {"valid, explicit target and description", app.CreateMergeRequestParams{Title: "t", Description: "a\nb", Source: "other", Target: "main"}, ""},260+ {"valid, explicit target and description", ports.CreateMergeRequestParams{Title: "t", Description: "a\nb", Source: "other", Target: "main"}, ""},
260- {"empty title", app.CreateMergeRequestParams{Title: "", Source: "feat", Target: "other"}, "title"},261+ {"empty title", ports.CreateMergeRequestParams{Title: "", Source: "feat", Target: "other"}, "title"},
261- {"two-line title", app.CreateMergeRequestParams{Title: "a\nb", Source: "feat", Target: "other"}, "title"},262+ {"two-line title", ports.CreateMergeRequestParams{Title: "a\nb", Source: "feat", Target: "other"}, "title"},
262- {"title too long", app.CreateMergeRequestParams{Title: strings.Repeat("x", 121), Source: "feat", Target: "other"}, "title"},263+ {"title too long", ports.CreateMergeRequestParams{Title: strings.Repeat("x", 121), Source: "feat", Target: "other"}, "title"},
263- {"title with a bidi override", app.CreateMergeRequestParams{Title: "a\u202eb", Source: "feat", Target: "other"}, "title"},264+ {"title with a bidi override", ports.CreateMergeRequestParams{Title: "a\u202eb", Source: "feat", Target: "other"}, "title"},
264- {"title with an escape", app.CreateMergeRequestParams{Title: "\x1b[31m", Source: "feat", Target: "other"}, "title"},265+ {"title with an escape", ports.CreateMergeRequestParams{Title: "\x1b[31m", Source: "feat", Target: "other"}, "title"},
265- {"description with a control character", app.CreateMergeRequestParams{Title: "t", Description: "a\x07", Source: "feat", Target: "other"}, "description"},266+ {"description with a control character", ports.CreateMergeRequestParams{Title: "t", Description: "a\x07", Source: "feat", Target: "other"}, "description"},
266- {"description with a bidi isolate", app.CreateMergeRequestParams{Title: "t", Description: "a\u2066b", Source: "feat", Target: "other"}, "description"},267+ {"description with a bidi isolate", ports.CreateMergeRequestParams{Title: "t", Description: "a\u2066b", Source: "feat", Target: "other"}, "description"},
267- {"description too long", app.CreateMergeRequestParams{Title: "t", Description: strings.Repeat("x", 8001), Source: "feat", Target: "other"}, "description"},268+ {"description too long", ports.CreateMergeRequestParams{Title: "t", Description: strings.Repeat("x", 8001), Source: "feat", Target: "other"}, "description"},
268- {"description not utf-8", app.CreateMergeRequestParams{Title: "t", Description: "\xff", Source: "feat", Target: "other"}, "description"},269+ {"description not utf-8", ports.CreateMergeRequestParams{Title: "t", Description: "\xff", Source: "feat", Target: "other"}, "description"},
269- {"source missing", app.CreateMergeRequestParams{Title: "t", Source: ""}, "source_branch"},270+ {"source missing", ports.CreateMergeRequestParams{Title: "t", Source: ""}, "source_branch"},
270- {"source does not exist", app.CreateMergeRequestParams{Title: "t", Source: "nope", Target: "main"}, "source_branch"},271+ {"source does not exist", ports.CreateMergeRequestParams{Title: "t", Source: "nope", Target: "main"}, "source_branch"},
271- {"source is not a branch name", app.CreateMergeRequestParams{Title: "t", Source: "a..b", Target: "main"}, "source_branch"},272+ {"source is not a branch name", ports.CreateMergeRequestParams{Title: "t", Source: "a..b", Target: "main"}, "source_branch"},
272- {"source is a full ref", app.CreateMergeRequestParams{Title: "t", Source: "refs/heads/feat", Target: "main"}, "source_branch"},273+ {"source is a full ref", ports.CreateMergeRequestParams{Title: "t", Source: "refs/heads/feat", Target: "main"}, "source_branch"},
273- {"target does not exist", app.CreateMergeRequestParams{Title: "t", Source: "feat", Target: "nope"}, "target_branch"},274+ {"target does not exist", ports.CreateMergeRequestParams{Title: "t", Source: "feat", Target: "nope"}, "target_branch"},
274- {"same branch", app.CreateMergeRequestParams{Title: "t", Source: "feat", Target: "feat"}, "target_branch"},275+ {"same branch", ports.CreateMergeRequestParams{Title: "t", Source: "feat", Target: "feat"}, "target_branch"},
275 }276 }
276 for _, tc := range tests {277 for _, tc := range tests {
277 t.Run(tc.name, func(t *testing.T) {278 t.Run(tc.name, func(t *testing.T) {
@@ -290,7 +291,7 @@ func TestCreateMergeRequestInputRules(t *testing.T) {
290 }291 }
291 292
292 t.Run("a request is rejected whole with every field named", func(t *testing.T) {293 t.Run("a request is rejected whole with every field named", func(t *testing.T) {
293- _, err := r.mrs.Create(r.ctx, root, r.repo, app.CreateMergeRequestParams{Title: "a\nb", Description: "\x07", Source: "nope", Target: "feat"})294+ _, err := r.mrs.Create(r.ctx, root, r.repo, ports.CreateMergeRequestParams{Title: "a\nb", Description: "\x07", Source: "nope", Target: "feat"})
294 got := fieldsOf(t, err)295 got := fieldsOf(t, err)
295 assert.Contains(t, got, "title")296 assert.Contains(t, got, "title")
296 assert.Contains(t, got, "description")297 assert.Contains(t, got, "description")
@@ -309,22 +310,22 @@ func TestCreateMergeRequestNumbersAndDuplicates(t *testing.T) {
309 second := r.open("two")310 second := r.open("two")
310 assert.Equal(t, int64(2), second.Number)311 assert.Equal(t, int64(2), second.Number)
311 312
312- _, err := r.mrs.Create(r.ctx, root, r.repo, app.CreateMergeRequestParams{Title: "again", Source: "one", Target: "main"})313+ _, err := r.mrs.Create(r.ctx, root, r.repo, ports.CreateMergeRequestParams{Title: "again", Source: "one", Target: "main"})
313 require.ErrorIs(t, err, domain.ErrAlreadyExists, "same open source and target")314 require.ErrorIs(t, err, domain.ErrAlreadyExists, "same open source and target")
314- _, err = r.mrs.Create(r.ctx, root, r.repo, app.CreateMergeRequestParams{Title: "again", Source: "one"})315+ _, err = r.mrs.Create(r.ctx, root, r.repo, ports.CreateMergeRequestParams{Title: "again", Source: "one"})
315 require.ErrorIs(t, err, domain.ErrAlreadyExists, "the default target is the same target")316 require.ErrorIs(t, err, domain.ErrAlreadyExists, "the default target is the same target")
316 317
317 // Another target is another request.318 // Another target is another request.
318 r.sh("checkout", "-q", "-B", "stage", "main")319 r.sh("checkout", "-q", "-B", "stage", "main")
319 r.push("stage")320 r.push("stage")
320- third, err := r.mrs.Create(r.ctx, root, r.repo, app.CreateMergeRequestParams{Title: "to stage", Source: "one", Target: "stage"})321+ third, err := r.mrs.Create(r.ctx, root, r.repo, ports.CreateMergeRequestParams{Title: "to stage", Source: "one", Target: "stage"})
321 require.NoError(t, err)322 require.NoError(t, err)
322 assert.Equal(t, int64(3), third.Number)323 assert.Equal(t, int64(3), third.Number)
323 324
324 // Once the first is closed, the pair is free again, with a new number.325 // Once the first is closed, the pair is free again, with a new number.
325 _, err = r.mrs.Close(r.ctx, root, r.repo, first.Number)326 _, err = r.mrs.Close(r.ctx, root, r.repo, first.Number)
326 require.NoError(t, err)327 require.NoError(t, err)
327- fourth, err := r.mrs.Create(r.ctx, root, r.repo, app.CreateMergeRequestParams{Title: "retry", Source: "one"})328+ fourth, err := r.mrs.Create(r.ctx, root, r.repo, ports.CreateMergeRequestParams{Title: "retry", Source: "one"})
328 require.NoError(t, err)329 require.NoError(t, err)
329 assert.Equal(t, int64(4), fourth.Number, "numbers are never reused")330 assert.Equal(t, int64(4), fourth.Number, "numbers are never reused")
330 }331 }
@@ -340,7 +341,7 @@ func TestMergeRequestAccess(t *testing.T) {
340 341
341 reads := map[string]func(p domain.Principal) error{342 reads := map[string]func(p domain.Principal) error{
342 "List": func(p domain.Principal) error {343 "List": func(p domain.Principal) error {
343- _, err := r.mrs.List(r.ctx, p, r.repo, app.ListMergeRequestsParams{})344+ _, err := r.mrs.List(r.ctx, p, r.repo, ports.ListMergeRequestsParams{})
344 return err345 return err
345 },346 },
346 "Get": func(p domain.Principal) error {347 "Get": func(p domain.Principal) error {
@@ -357,16 +358,16 @@ func TestMergeRequestAccess(t *testing.T) {
357 }358 }
358 _, err := r.mrs.Get(r.ctx, reader, r.repo, n+1, false)359 _, err := r.mrs.Get(r.ctx, reader, r.repo, n+1, false)
359 assert.ErrorIs(t, err, domain.ErrNotFound, "unknown number")360 assert.ErrorIs(t, err, domain.ErrNotFound, "unknown number")
360- _, err = r.mrs.List(r.ctx, root, name(t, "ghost"), app.ListMergeRequestsParams{})361+ _, err = r.mrs.List(r.ctx, root, name(t, "ghost"), ports.ListMergeRequestsParams{})
361 assert.ErrorIs(t, err, domain.ErrNotFound, "unknown repository")362 assert.ErrorIs(t, err, domain.ErrNotFound, "unknown repository")
362 363
363 writes := map[string]func(p domain.Principal) error{364 writes := map[string]func(p domain.Principal) error{
364 "Create": func(p domain.Principal) error {365 "Create": func(p domain.Principal) error {
365- _, err := r.mrs.Create(r.ctx, p, r.repo, app.CreateMergeRequestParams{Title: "t", Source: "feat", Target: "feat"})366+ _, err := r.mrs.Create(r.ctx, p, r.repo, ports.CreateMergeRequestParams{Title: "t", Source: "feat", Target: "feat"})
366 return err367 return err
367 },368 },
368 "AddComment": func(p domain.Principal) error {369 "AddComment": func(p domain.Principal) error {
369- _, err := r.mrs.AddComment(r.ctx, p, r.repo, n, app.AddCommentParams{Text: "x"})370+ _, err := r.mrs.AddComment(r.ctx, p, r.repo, n, ports.AddCommentParams{Text: "x"})
370 return err371 return err
371 },372 },
372 "ResolveComment": func(p domain.Principal) error {373 "ResolveComment": func(p domain.Principal) error {
@@ -382,7 +383,7 @@ func TestMergeRequestAccess(t *testing.T) {
382 return err383 return err
383 },384 },
384 "Merge": func(p domain.Principal) error {385 "Merge": func(p domain.Principal) error {
385- _, err := r.mrs.Merge(r.ctx, p, r.repo, n+100, app.MergeParams{HeadID: mr.HeadID})386+ _, err := r.mrs.Merge(r.ctx, p, r.repo, n+100, ports.MergeParams{HeadID: mr.HeadID})
386 return err387 return err
387 },388 },
388 }389 }
@@ -413,11 +414,11 @@ func TestMergeRequestAccess(t *testing.T) {
413 // On a public repository an anonymous caller reads, but writing is414 // On a public repository an anonymous caller reads, but writing is
414 // unauthenticated rather than not found.415 // unauthenticated rather than not found.
415 pubName := name(t, "pub")416 pubName := name(t, "pub")
416- _, err = r.repos.Create(r.ctx, root, app.CreateParams{Name: pubName, Visibility: domain.VisibilityPublic})417+ _, err = r.repos.Create(r.ctx, root, ports.CreateRepoParams{Name: pubName, Visibility: domain.VisibilityPublic})
417 require.NoError(t, err)418 require.NoError(t, err)
418- _, err = r.mrs.List(r.ctx, domain.Anonymous(), pubName, app.ListMergeRequestsParams{})419+ _, err = r.mrs.List(r.ctx, domain.Anonymous(), pubName, ports.ListMergeRequestsParams{})
419 assert.NoError(t, err)420 assert.NoError(t, err)
420- _, err = r.mrs.Create(r.ctx, domain.Anonymous(), pubName, app.CreateMergeRequestParams{Title: "t", Source: "a", Target: "b"})421+ _, err = r.mrs.Create(r.ctx, domain.Anonymous(), pubName, ports.CreateMergeRequestParams{Title: "t", Source: "a", Target: "b"})
421 assert.ErrorIs(t, err, domain.ErrUnauthenticated)422 assert.ErrorIs(t, err, domain.ErrUnauthenticated)
422 }423 }
423 424
@@ -434,24 +435,24 @@ func TestListMergeRequests(t *testing.T) {
434 require.NoError(t, err)435 require.NoError(t, err)
435 r.sh("checkout", "-q", "main")436 r.sh("checkout", "-q", "main")
436 merged := r.feature("f0")437 merged := r.feature("f0")
437- _, err = r.mrs.Merge(r.ctx, root, r.repo, 1, app.MergeParams{HeadID: merged})438+ _, err = r.mrs.Merge(r.ctx, root, r.repo, 1, ports.MergeParams{HeadID: merged})
438 require.NoError(t, err)439 require.NoError(t, err)
439 r.sh("branch", "-D", "f4")440 r.sh("branch", "-D", "f4")
440 r.run(r.bare(), r.env(author), "branch", "-D", "f4")441 r.run(r.bare(), r.env(author), "branch", "-D", "f4")
441 442
442- numbers := func(p app.MergeRequestPage) []int64 {443+ numbers := func(p ports.MergeRequestPage) []int64 {
443 var out []int64444 var out []int64
444 for _, m := range p.MergeRequests {445 for _, m := range p.MergeRequests {
445 out = append(out, m.Number)446 out = append(out, m.Number)
446 }447 }
447 return out448 return out
448 }449 }
449- all, err := r.mrs.List(r.ctx, root, r.repo, app.ListMergeRequestsParams{})450+ all, err := r.mrs.List(r.ctx, root, r.repo, ports.ListMergeRequestsParams{})
450 require.NoError(t, err)451 require.NoError(t, err)
451 assert.Equal(t, []int64{5, 4, 3, 2, 1}, numbers(all), "highest number first")452 assert.Equal(t, []int64{5, 4, 3, 2, 1}, numbers(all), "highest number first")
452 assert.Empty(t, all.NextPageToken)453 assert.Empty(t, all.NextPageToken)
453 454
454- open, err := r.mrs.List(r.ctx, root, r.repo, app.ListMergeRequestsParams{State: domain.MRStateOpen})455+ open, err := r.mrs.List(r.ctx, root, r.repo, ports.ListMergeRequestsParams{State: domain.MRStateOpen})
455 require.NoError(t, err)456 require.NoError(t, err)
456 assert.Equal(t, []int64{5, 4, 3}, numbers(open))457 assert.Equal(t, []int64{5, 4, 3}, numbers(open))
457 for _, m := range open.MergeRequests {458 for _, m := range open.MergeRequests {
@@ -461,35 +462,35 @@ func TestListMergeRequests(t *testing.T) {
461 assert.False(t, m.HeadID.IsZero(), "an open request carries its head")462 assert.False(t, m.HeadID.IsZero(), "an open request carries its head")
462 }463 }
463 }464 }
464- closed, err := r.mrs.List(r.ctx, root, r.repo, app.ListMergeRequestsParams{State: domain.MRStateClosed})465+ closed, err := r.mrs.List(r.ctx, root, r.repo, ports.ListMergeRequestsParams{State: domain.MRStateClosed})
465 require.NoError(t, err)466 require.NoError(t, err)
466 assert.Equal(t, []int64{2}, numbers(closed))467 assert.Equal(t, []int64{2}, numbers(closed))
467- mergedPage, err := r.mrs.List(r.ctx, root, r.repo, app.ListMergeRequestsParams{State: domain.MRStateMerged})468+ mergedPage, err := r.mrs.List(r.ctx, root, r.repo, ports.ListMergeRequestsParams{State: domain.MRStateMerged})
468 require.NoError(t, err)469 require.NoError(t, err)
469 assert.Equal(t, []int64{1}, numbers(mergedPage))470 assert.Equal(t, []int64{1}, numbers(mergedPage))
470 assert.Equal(t, merged, mergedPage.MergeRequests[0].HeadID, "a merged request keeps the head it had")471 assert.Equal(t, merged, mergedPage.MergeRequests[0].HeadID, "a merged request keeps the head it had")
471 472
472 // Paging: the token names the last number.473 // Paging: the token names the last number.
473- p1, err := r.mrs.List(r.ctx, root, r.repo, app.ListMergeRequestsParams{PageSize: 2})474+ p1, err := r.mrs.List(r.ctx, root, r.repo, ports.ListMergeRequestsParams{PageSize: 2})
474 require.NoError(t, err)475 require.NoError(t, err)
475 assert.Equal(t, []int64{5, 4}, numbers(p1))476 assert.Equal(t, []int64{5, 4}, numbers(p1))
476 require.NotEmpty(t, p1.NextPageToken)477 require.NotEmpty(t, p1.NextPageToken)
477 // A request created between the pages does not shift them.478 // A request created between the pages does not shift them.
478 r.feature("late")479 r.feature("late")
479 r.open("late")480 r.open("late")
480- p2, err := r.mrs.List(r.ctx, root, r.repo, app.ListMergeRequestsParams{PageSize: 2, PageToken: p1.NextPageToken})481+ p2, err := r.mrs.List(r.ctx, root, r.repo, ports.ListMergeRequestsParams{PageSize: 2, PageToken: p1.NextPageToken})
481 require.NoError(t, err)482 require.NoError(t, err)
482 assert.Equal(t, []int64{3, 2}, numbers(p2))483 assert.Equal(t, []int64{3, 2}, numbers(p2))
483- p3, err := r.mrs.List(r.ctx, root, r.repo, app.ListMergeRequestsParams{PageSize: 2, PageToken: p2.NextPageToken})484+ p3, err := r.mrs.List(r.ctx, root, r.repo, ports.ListMergeRequestsParams{PageSize: 2, PageToken: p2.NextPageToken})
484 require.NoError(t, err)485 require.NoError(t, err)
485 assert.Equal(t, []int64{1}, numbers(p3))486 assert.Equal(t, []int64{1}, numbers(p3))
486 assert.Empty(t, p3.NextPageToken)487 assert.Empty(t, p3.NextPageToken)
487 488
488 for _, tok := range []string{"garbage", "bW9yZQ"} {489 for _, tok := range []string{"garbage", "bW9yZQ"} {
489- _, err = r.mrs.List(r.ctx, root, r.repo, app.ListMergeRequestsParams{PageToken: tok})490+ _, err = r.mrs.List(r.ctx, root, r.repo, ports.ListMergeRequestsParams{PageToken: tok})
490 assert.ErrorIs(t, err, domain.ErrInvalidPageToken)491 assert.ErrorIs(t, err, domain.ErrInvalidPageToken)
491 }492 }
492- _, err = r.mrs.List(r.ctx, root, r.repo, app.ListMergeRequestsParams{PageSize: -1})493+ _, err = r.mrs.List(r.ctx, root, r.repo, ports.ListMergeRequestsParams{PageSize: -1})
493 assert.ErrorIs(t, err, domain.ErrInvalidPageSize)494 assert.ErrorIs(t, err, domain.ErrInvalidPageSize)
494 _ = nums495 _ = nums
495 }496 }
@@ -499,9 +500,9 @@ func TestGetMergeRequest(t *testing.T) {
499 main := r.seed()500 main := r.seed()
500 head := r.feature("feat")501 head := r.feature("feat")
501 mr := r.open("feat")502 mr := r.open("feat")
502- _, err := r.mrs.AddComment(r.ctx, root, r.repo, mr.Number, app.AddCommentParams{Text: "first"})503+ _, err := r.mrs.AddComment(r.ctx, root, r.repo, mr.Number, ports.AddCommentParams{Text: "first"})
503 require.NoError(t, err)504 require.NoError(t, err)
504- _, err = r.mrs.AddComment(r.ctx, root, r.repo, mr.Number, app.AddCommentParams{Text: "second"})505+ _, err = r.mrs.AddComment(r.ctx, root, r.repo, mr.Number, ports.AddCommentParams{Text: "second"})
505 require.NoError(t, err)506 require.NoError(t, err)
506 507
507 d, err := r.mrs.Get(r.ctx, reader, r.repo, mr.Number, false)508 d, err := r.mrs.Get(r.ctx, reader, r.repo, mr.Number, false)
@@ -565,30 +566,30 @@ func TestAddComment(t *testing.T) {
565 566
566 tests := []struct {567 tests := []struct {
567 name string568 name string
568- params app.AddCommentParams569+ params ports.AddCommentParams
569 field string // "" for valid570 field string // "" for valid
570 }{571 }{
571- {"general", app.AddCommentParams{Text: "looks good"}, ""},572+ {"general", ports.AddCommentParams{Text: "looks good"}, ""},
572- {"multi-line general", app.AddCommentParams{Text: "a\n\nb"}, ""},573+ {"multi-line general", ports.AddCommentParams{Text: "a\n\nb"}, ""},
573- {"new side of a changed line", app.AddCommentParams{Path: "a.txt", Line: 2, Side: domain.SideNew, Text: "x"}, ""},574+ {"new side of a changed line", ports.AddCommentParams{Path: "a.txt", Line: 2, Side: domain.SideNew, Text: "x"}, ""},
574- {"old side of a changed line", app.AddCommentParams{Path: "a.txt", Line: 2, Side: domain.SideOld, Text: "x"}, ""},575+ {"old side of a changed line", ports.AddCommentParams{Path: "a.txt", Line: 2, Side: domain.SideOld, Text: "x"}, ""},
575- {"context line", app.AddCommentParams{Path: "a.txt", Line: 3, Side: domain.SideNew, Text: "x"}, ""},576+ {"context line", ports.AddCommentParams{Path: "a.txt", Line: 3, Side: domain.SideNew, Text: "x"}, ""},
576- {"added file", app.AddCommentParams{Path: "c.txt", Line: 1, Side: domain.SideNew, Text: "x"}, ""},577+ {"added file", ports.AddCommentParams{Path: "c.txt", Line: 1, Side: domain.SideNew, Text: "x"}, ""},
577- {"empty text", app.AddCommentParams{Text: ""}, "text"},578+ {"empty text", ports.AddCommentParams{Text: ""}, "text"},
578- {"blank text", app.AddCommentParams{Text: " \n "}, "text"},579+ {"blank text", ports.AddCommentParams{Text: " \n "}, "text"},
579- {"text too long", app.AddCommentParams{Text: strings.Repeat("x", 8001)}, "text"},580+ {"text too long", ports.AddCommentParams{Text: strings.Repeat("x", 8001)}, "text"},
580- {"text with a control character", app.AddCommentParams{Text: "a\x1b[0m"}, "text"},581+ {"text with a control character", ports.AddCommentParams{Text: "a\x1b[0m"}, "text"},
581- {"text with a tab", app.AddCommentParams{Text: "a\tb"}, "text"},582+ {"text with a tab", ports.AddCommentParams{Text: "a\tb"}, "text"},
582- {"text with a bidi override", app.AddCommentParams{Text: "a\u202eb"}, "text"},583+ {"text with a bidi override", ports.AddCommentParams{Text: "a\u202eb"}, "text"},
583- {"line outside the diff", app.AddCommentParams{Path: "a.txt", Line: 40, Side: domain.SideNew, Text: "x"}, "path"},584+ {"line outside the diff", ports.AddCommentParams{Path: "a.txt", Line: 40, Side: domain.SideNew, Text: "x"}, "path"},
584- {"unchanged file", app.AddCommentParams{Path: "b.txt", Line: 1, Side: domain.SideNew, Text: "x"}, "path"},585+ {"unchanged file", ports.AddCommentParams{Path: "b.txt", Line: 1, Side: domain.SideNew, Text: "x"}, "path"},
585- {"unknown path", app.AddCommentParams{Path: "nope.txt", Line: 1, Side: domain.SideNew, Text: "x"}, "path"},586+ {"unknown path", ports.AddCommentParams{Path: "nope.txt", Line: 1, Side: domain.SideNew, Text: "x"}, "path"},
586- {"deleted line on the wrong side", app.AddCommentParams{Path: "c.txt", Line: 1, Side: domain.SideOld, Text: "x"}, "path"},587+ {"deleted line on the wrong side", ports.AddCommentParams{Path: "c.txt", Line: 1, Side: domain.SideOld, Text: "x"}, "path"},
587- {"path without a line", app.AddCommentParams{Path: "a.txt", Side: domain.SideNew, Text: "x"}, "line"},588+ {"path without a line", ports.AddCommentParams{Path: "a.txt", Side: domain.SideNew, Text: "x"}, "line"},
588- {"path without a side", app.AddCommentParams{Path: "a.txt", Line: 2, Text: "x"}, "side"},589+ {"path without a side", ports.AddCommentParams{Path: "a.txt", Line: 2, Text: "x"}, "side"},
589- {"line without a path", app.AddCommentParams{Line: 2, Side: domain.SideNew, Text: "x"}, "path"},590+ {"line without a path", ports.AddCommentParams{Line: 2, Side: domain.SideNew, Text: "x"}, "path"},
590- {"side without a path", app.AddCommentParams{Side: domain.SideOld, Text: "x"}, "path"},591+ {"side without a path", ports.AddCommentParams{Side: domain.SideOld, Text: "x"}, "path"},
591- {"negative line", app.AddCommentParams{Path: "a.txt", Line: -2, Side: domain.SideNew, Text: "x"}, "line"},592+ {"negative line", ports.AddCommentParams{Path: "a.txt", Line: -2, Side: domain.SideNew, Text: "x"}, "line"},
592 }593 }
593 for _, tc := range tests {594 for _, tc := range tests {
594 t.Run(tc.name, func(t *testing.T) {595 t.Run(tc.name, func(t *testing.T) {
@@ -625,7 +626,7 @@ func TestAddComment(t *testing.T) {
625 t.Run("not open", func(t *testing.T) {626 t.Run("not open", func(t *testing.T) {
626 _, err := r.mrs.Close(r.ctx, mrW, r.repo, n)627 _, err := r.mrs.Close(r.ctx, mrW, r.repo, n)
627 require.NoError(t, err)628 require.NoError(t, err)
628- _, err = r.mrs.AddComment(r.ctx, mrW, r.repo, n, app.AddCommentParams{Text: "late"})629+ _, err = r.mrs.AddComment(r.ctx, mrW, r.repo, n, ports.AddCommentParams{Text: "late"})
629 assert.ErrorIs(t, err, domain.ErrPreconditionFailed)630 assert.ErrorIs(t, err, domain.ErrPreconditionFailed)
630 _, err = r.mrs.ResolveComment(r.ctx, mrW, r.repo, n, 1, true)631 _, err = r.mrs.ResolveComment(r.ctx, mrW, r.repo, n, 1, true)
631 assert.ErrorIs(t, err, domain.ErrPreconditionFailed)632 assert.ErrorIs(t, err, domain.ErrPreconditionFailed)
@@ -642,7 +643,7 @@ func TestConcurrentComments(t *testing.T) {
642 ids := make(chan int64, workers)643 ids := make(chan int64, workers)
643 for i := range workers {644 for i := range workers {
644 wg.Go(func() {645 wg.Go(func() {
645- c, err := r.mrs.AddComment(r.ctx, mrW, r.repo, mr.Number, app.AddCommentParams{Text: fmt.Sprintf("c%d", i)})646+ c, err := r.mrs.AddComment(r.ctx, mrW, r.repo, mr.Number, ports.AddCommentParams{Text: fmt.Sprintf("c%d", i)})
646 if assert.NoError(t, err) {647 if assert.NoError(t, err) {
647 ids <- c.ID648 ids <- c.ID
648 }649 }
@@ -666,7 +667,7 @@ func TestResolveComment(t *testing.T) {
666 r.seed()667 r.seed()
667 r.feature("feat")668 r.feature("feat")
668 mr := r.open("feat")669 mr := r.open("feat")
669- c, err := r.mrs.AddComment(r.ctx, mrW, r.repo, mr.Number, app.AddCommentParams{Text: "fix this"})670+ c, err := r.mrs.AddComment(r.ctx, mrW, r.repo, mr.Number, ports.AddCommentParams{Text: "fix this"})
670 require.NoError(t, err)671 require.NoError(t, err)
671 r.drain()672 r.drain()
672 673
@@ -698,9 +699,9 @@ func TestEventsOfAMergeRequest(t *testing.T) {
698 r.drain()699 r.drain()
699 700
700 mr := r.open("feat")701 mr := r.open("feat")
701- c, err := r.mrs.AddComment(r.ctx, mrW, r.repo, mr.Number, app.AddCommentParams{Text: "note"})702+ c, err := r.mrs.AddComment(r.ctx, mrW, r.repo, mr.Number, ports.AddCommentParams{Text: "note"})
702 require.NoError(t, err)703 require.NoError(t, err)
703- _, err = r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, app.MergeParams{HeadID: head})704+ _, err = r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, ports.MergeParams{HeadID: head})
704 require.NoError(t, err)705 require.NoError(t, err)
705 706
706 r.feature("other")707 r.feature("other")
@@ -732,7 +733,7 @@ func TestEventsOfAMergeRequest(t *testing.T) {
732 // Failed calls publish nothing.733 // Failed calls publish nothing.
733 _, err = r.mrs.Close(r.ctx, mrW, r.repo, other.Number)734 _, err = r.mrs.Close(r.ctx, mrW, r.repo, other.Number)
734 require.Error(t, err)735 require.Error(t, err)
735- _, err = r.mrs.AddComment(r.ctx, mrW, r.repo, other.Number, app.AddCommentParams{Text: "x"})736+ _, err = r.mrs.AddComment(r.ctx, mrW, r.repo, other.Number, ports.AddCommentParams{Text: "x"})
736 require.Error(t, err)737 require.Error(t, err)
737 assert.Empty(t, r.drain())738 assert.Empty(t, r.drain())
738 }739 }
@@ -859,7 +860,7 @@ func TestMergeFastForward(t *testing.T) {
859 mr := r.open("feat")860 mr := r.open("feat")
860 r.drain()861 r.drain()
861 862
862- merged, err := r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, app.MergeParams{HeadID: head})863+ merged, err := r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, ports.MergeParams{HeadID: head})
863 require.NoError(t, err)864 require.NoError(t, err)
864 assert.Equal(t, domain.MRStateMerged, merged.State)865 assert.Equal(t, domain.MRStateMerged, merged.State)
865 assert.Equal(t, domain.MergeModeFastForward, merged.MergeMode, "the default mode")866 assert.Equal(t, domain.MergeModeFastForward, merged.MergeMode, "the default mode")
@@ -880,11 +881,11 @@ func TestMergeFastForward(t *testing.T) {
880 assert.Len(t, d.Diff.Files, 2)881 assert.Len(t, d.Diff.Files, 2)
881 882
882 // Repeating it, or touching a final request, is a failed precondition.883 // Repeating it, or touching a final request, is a failed precondition.
883- _, err = r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, app.MergeParams{HeadID: head})884+ _, err = r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, ports.MergeParams{HeadID: head})
884 assert.ErrorIs(t, err, domain.ErrPreconditionFailed)885 assert.ErrorIs(t, err, domain.ErrPreconditionFailed)
885 _, err = r.mrs.Close(r.ctx, mrW, r.repo, mr.Number)886 _, err = r.mrs.Close(r.ctx, mrW, r.repo, mr.Number)
886 assert.ErrorIs(t, err, domain.ErrPreconditionFailed)887 assert.ErrorIs(t, err, domain.ErrPreconditionFailed)
887- _, err = r.mrs.AddComment(r.ctx, mrW, r.repo, mr.Number, app.AddCommentParams{Text: "x"})888+ _, err = r.mrs.AddComment(r.ctx, mrW, r.repo, mr.Number, ports.AddCommentParams{Text: "x"})
888 assert.ErrorIs(t, err, domain.ErrPreconditionFailed)889 assert.ErrorIs(t, err, domain.ErrPreconditionFailed)
889 }890 }
890 891
@@ -897,14 +898,14 @@ func TestMergeRefusals(t *testing.T) {
897 mr := r.open("feat")898 mr := r.open("feat")
898 r.commit(author, "more.txt", "m\n", "pushed after the review")899 r.commit(author, "more.txt", "m\n", "pushed after the review")
899 r.push("feat")900 r.push("feat")
900- _, err := r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, app.MergeParams{HeadID: head})901+ _, err := r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, ports.MergeParams{HeadID: head})
901 require.ErrorIs(t, err, domain.ErrPreconditionFailed)902 require.ErrorIs(t, err, domain.ErrPreconditionFailed)
902 assert.Contains(t, err.Error(), "branch moved, read again")903 assert.Contains(t, err.Error(), "branch moved, read again")
903 assert.NotEqual(t, head, r.branchHead("main"), "main did not move")904 assert.NotEqual(t, head, r.branchHead("main"), "main did not move")
904 // Reading again gives the new head, which merges.905 // Reading again gives the new head, which merges.
905 d, err := r.mrs.Get(r.ctx, mrM, r.repo, mr.Number, false)906 d, err := r.mrs.Get(r.ctx, mrM, r.repo, mr.Number, false)
906 require.NoError(t, err)907 require.NoError(t, err)
907- _, err = r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, app.MergeParams{HeadID: d.MergeRequest.HeadID})908+ _, err = r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, ports.MergeParams{HeadID: d.MergeRequest.HeadID})
908 require.NoError(t, err)909 require.NoError(t, err)
909 })910 })
910 911
@@ -919,7 +920,7 @@ func TestMergeRefusals(t *testing.T) {
919 tip := r.branchHead("main")920 tip := r.branchHead("main")
920 921
921 for _, mode := range []domain.MergeMode{domain.MergeModeFastForward, domain.MergeModeSquash} {922 for _, mode := range []domain.MergeMode{domain.MergeModeFastForward, domain.MergeModeSquash} {
922- _, err := r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, app.MergeParams{HeadID: head, Mode: mode})923+ _, err := r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, ports.MergeParams{HeadID: head, Mode: mode})
923 require.ErrorIs(t, err, domain.ErrPreconditionFailed, mode)924 require.ErrorIs(t, err, domain.ErrPreconditionFailed, mode)
924 assert.Contains(t, err.Error(), "rebase", mode)925 assert.Contains(t, err.Error(), "rebase", mode)
925 }926 }
@@ -932,7 +933,7 @@ func TestMergeRefusals(t *testing.T) {
932 r.sh("checkout", "-q", "feat")933 r.sh("checkout", "-q", "feat")
933 r.sh("rebase", "-q", "main")934 r.sh("rebase", "-q", "main")
934 r.push("feat")935 r.push("feat")
935- _, err = r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, app.MergeParams{HeadID: r.head()})936+ _, err = r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, ports.MergeParams{HeadID: r.head()})
936 require.NoError(t, err)937 require.NoError(t, err)
937 })938 })
938 939
@@ -942,13 +943,13 @@ func TestMergeRefusals(t *testing.T) {
942 head := r.feature("feat")943 head := r.feature("feat")
943 mr := r.open("feat")944 mr := r.open("feat")
944 modes := []domain.MergeMode{domain.MergeModeFastForward}945 modes := []domain.MergeMode{domain.MergeModeFastForward}
945- _, err := r.repos.Update(r.ctx, root, r.repo, app.UpdateParams{AllowedMergeModes: &modes})946+ _, err := r.repos.Update(r.ctx, root, r.repo, ports.UpdateRepoParams{AllowedMergeModes: &modes})
946 require.NoError(t, err)947 require.NoError(t, err)
947 948
948- _, err = r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, app.MergeParams{HeadID: head, Mode: domain.MergeModeSquash})949+ _, err = r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, ports.MergeParams{HeadID: head, Mode: domain.MergeModeSquash})
949 require.ErrorIs(t, err, domain.ErrPreconditionFailed)950 require.ErrorIs(t, err, domain.ErrPreconditionFailed)
950 assert.Contains(t, err.Error(), "squash")951 assert.Contains(t, err.Error(), "squash")
951- _, err = r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, app.MergeParams{HeadID: head})952+ _, err = r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, ports.MergeParams{HeadID: head})
952 require.NoError(t, err, "fast-forward is allowed")953 require.NoError(t, err, "fast-forward is allowed")
953 })954 })
954 955
@@ -958,11 +959,11 @@ func TestMergeRefusals(t *testing.T) {
958 head := r.feature("feat")959 head := r.feature("feat")
959 mr := r.open("feat")960 mr := r.open("feat")
960 modes := []domain.MergeMode{domain.MergeModeSquash}961 modes := []domain.MergeMode{domain.MergeModeSquash}
961- _, err := r.repos.Update(r.ctx, root, r.repo, app.UpdateParams{AllowedMergeModes: &modes})962+ _, err := r.repos.Update(r.ctx, root, r.repo, ports.UpdateRepoParams{AllowedMergeModes: &modes})
962 require.NoError(t, err)963 require.NoError(t, err)
963- _, err = r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, app.MergeParams{HeadID: head})964+ _, err = r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, ports.MergeParams{HeadID: head})
964 require.ErrorIs(t, err, domain.ErrPreconditionFailed)965 require.ErrorIs(t, err, domain.ErrPreconditionFailed)
965- _, err = r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, app.MergeParams{HeadID: head, Mode: domain.MergeModeSquash})966+ _, err = r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, ports.MergeParams{HeadID: head, Mode: domain.MergeModeSquash})
966 require.NoError(t, err)967 require.NoError(t, err)
967 })968 })
968 969
@@ -971,13 +972,13 @@ func TestMergeRefusals(t *testing.T) {
971 r.seed()972 r.seed()
972 head := r.feature("feat")973 head := r.feature("feat")
973 mr := r.open("feat")974 mr := r.open("feat")
974- _, err := r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, app.MergeParams{})975+ _, err := r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, ports.MergeParams{})
975 require.ErrorIs(t, err, domain.ErrInvalid)976 require.ErrorIs(t, err, domain.ErrInvalid)
976 assert.Contains(t, fieldsOf(t, err), "head_id")977 assert.Contains(t, fieldsOf(t, err), "head_id")
977- _, err = r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, app.MergeParams{HeadID: head, Mode: "rebase"})978+ _, err = r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, ports.MergeParams{HeadID: head, Mode: "rebase"})
978 require.ErrorIs(t, err, domain.ErrInvalid)979 require.ErrorIs(t, err, domain.ErrInvalid)
979 assert.Contains(t, fieldsOf(t, err), "mode")980 assert.Contains(t, fieldsOf(t, err), "mode")
980- _, err = r.mrs.Merge(r.ctx, mrM, r.repo, 99, app.MergeParams{HeadID: head})981+ _, err = r.mrs.Merge(r.ctx, mrM, r.repo, 99, ports.MergeParams{HeadID: head})
981 assert.ErrorIs(t, err, domain.ErrNotFound)982 assert.ErrorIs(t, err, domain.ErrNotFound)
982 })983 })
983 984
@@ -987,13 +988,13 @@ func TestMergeRefusals(t *testing.T) {
987 head := r.feature("feat")988 head := r.feature("feat")
988 mr := r.open("feat")989 mr := r.open("feat")
989 r.run(r.bare(), r.env(author), "branch", "-D", "feat")990 r.run(r.bare(), r.env(author), "branch", "-D", "feat")
990- _, err := r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, app.MergeParams{HeadID: head})991+ _, err := r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, ports.MergeParams{HeadID: head})
991 require.ErrorIs(t, err, domain.ErrPreconditionFailed)992 require.ErrorIs(t, err, domain.ErrPreconditionFailed)
992 assert.Contains(t, err.Error(), "no longer exists")993 assert.Contains(t, err.Error(), "no longer exists")
993 994
994 r.push("feat")995 r.push("feat")
995 r.run(r.bare(), r.env(author), "update-ref", "refs/heads/main", head.String())996 r.run(r.bare(), r.env(author), "update-ref", "refs/heads/main", head.String())
996- _, err = r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, app.MergeParams{HeadID: head})997+ _, err = r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, ports.MergeParams{HeadID: head})
997 require.ErrorIs(t, err, domain.ErrPreconditionFailed, "the target already is at the head")998 require.ErrorIs(t, err, domain.ErrPreconditionFailed, "the target already is at the head")
998 assert.Contains(t, err.Error(), "nothing to merge")999 assert.Contains(t, err.Error(), "nothing to merge")
999 })1000 })
@@ -1022,7 +1023,7 @@ func TestConcurrentMergesSerialize(t *testing.T) {
1022 continue1023 continue
1023 }1024 }
1024 wg.Go(func() {1025 wg.Go(func() {
1025- _, err := r.mrs.Merge(r.ctx, mrM, r.repo, number, app.MergeParams{HeadID: head, Mode: mode})1026+ _, err := r.mrs.Merge(r.ctx, mrM, r.repo, number, ports.MergeParams{HeadID: head, Mode: mode})
1026 results <- outcome{number, err}1027 results <- outcome{number, err}
1027 })1028 })
1028 }1029 }
@@ -1079,11 +1080,11 @@ func TestSquashMerge(t *testing.T) {
1079 r.commit(bob, "w.txt", "w\n", "bob again")1080 r.commit(bob, "w.txt", "w\n", "bob again")
1080 r.push("feat")1081 r.push("feat")
1081 head := r.branchHead("feat")1082 head := r.branchHead("feat")
1082- mr, err := r.mrs.Create(r.ctx, root, r.repo, app.CreateMergeRequestParams{Title: "Add things", Source: "feat"})1083+ mr, err := r.mrs.Create(r.ctx, root, r.repo, ports.CreateMergeRequestParams{Title: "Add things", Source: "feat"})
1083 require.NoError(t, err)1084 require.NoError(t, err)
1084 r.drain()1085 r.drain()
1085 1086
1086- merged, err := r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, app.MergeParams{HeadID: head, Mode: domain.MergeModeSquash})1087+ merged, err := r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, ports.MergeParams{HeadID: head, Mode: domain.MergeModeSquash})
1087 require.NoError(t, err)1088 require.NoError(t, err)
1088 assert.Equal(t, domain.MergeModeSquash, merged.MergeMode)1089 assert.Equal(t, domain.MergeModeSquash, merged.MergeMode)
1089 require.False(t, merged.MergedCommitID.IsZero())1090 require.False(t, merged.MergedCommitID.IsZero())
@@ -1144,7 +1145,7 @@ func TestSquashSingleCommitHasNoTrailer(t *testing.T) {
1144 r.commit(author, "m.txt", "m\n", "only")1145 r.commit(author, "m.txt", "m\n", "only")
1145 r.push("mine")1146 r.push("mine")
1146 mr := r.open("mine")1147 mr := r.open("mine")
1147- merged, err := r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, app.MergeParams{HeadID: r.branchHead("mine"), Mode: domain.MergeModeSquash})1148+ merged, err := r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, ports.MergeParams{HeadID: r.branchHead("mine"), Mode: domain.MergeModeSquash})
1148 require.NoError(t, err)1149 require.NoError(t, err)
1149 msg := r.run(r.bare(), r.env(author), "log", "-1", "--format=%B", merged.MergedCommitID.String())1150 msg := r.run(r.bare(), r.env(author), "log", "-1", "--format=%B", merged.MergedCommitID.String())
1150 assert.Equal(t, "Add mine (!1)\n\n- only", msg)1151 assert.Equal(t, "Add mine (!1)\n\n- only", msg)
@@ -1159,7 +1160,7 @@ func TestUpdateRepoSettings(t *testing.T) {
1159 1160
1160 modes := []domain.MergeMode{domain.MergeModeSquash, domain.MergeModeSquash}1161 modes := []domain.MergeMode{domain.MergeModeSquash, domain.MergeModeSquash}
1161 off := false1162 off := false
1162- got, err := r.repos.Update(r.ctx, root, r.repo, app.UpdateParams{AllowedMergeModes: &modes, FailingChecksBlock: &off})1163+ got, err := r.repos.Update(r.ctx, root, r.repo, ports.UpdateRepoParams{AllowedMergeModes: &modes, FailingChecksBlock: &off})
1163 require.NoError(t, err)1164 require.NoError(t, err)
1164 assert.Equal(t, domain.RepoSettings{AllowedMergeModes: []domain.MergeMode{domain.MergeModeSquash}, FailingChecksBlock: false}, got.Settings)1165 assert.Equal(t, domain.RepoSettings{AllowedMergeModes: []domain.MergeMode{domain.MergeModeSquash}, FailingChecksBlock: false}, got.Settings)
1165 again, err := r.repos.Get(r.ctx, root, r.repo)1166 again, err := r.repos.Get(r.ctx, root, r.repo)
@@ -1168,14 +1169,14 @@ func TestUpdateRepoSettings(t *testing.T) {
1168 1169
1169 // Only the named setting changes.1170 // Only the named setting changes.
1170 on := true1171 on := true
1171- got, err = r.repos.Update(r.ctx, root, r.repo, app.UpdateParams{FailingChecksBlock: &on})1172+ got, err = r.repos.Update(r.ctx, root, r.repo, ports.UpdateRepoParams{FailingChecksBlock: &on})
1172 require.NoError(t, err)1173 require.NoError(t, err)
1173 assert.Equal(t, []domain.MergeMode{domain.MergeModeSquash}, got.Settings.AllowedMergeModes)1174 assert.Equal(t, []domain.MergeMode{domain.MergeModeSquash}, got.Settings.AllowedMergeModes)
1174 assert.True(t, got.Settings.FailingChecksBlock)1175 assert.True(t, got.Settings.FailingChecksBlock)
1175 1176
1176 for _, bad := range [][]domain.MergeMode{{}, nil, {"rebase"}} {1177 for _, bad := range [][]domain.MergeMode{{}, nil, {"rebase"}} {
1177 bad := bad1178 bad := bad
1178- _, err = r.repos.Update(r.ctx, root, r.repo, app.UpdateParams{AllowedMergeModes: &bad})1179+ _, err = r.repos.Update(r.ctx, root, r.repo, ports.UpdateRepoParams{AllowedMergeModes: &bad})
1179 assert.ErrorIs(t, err, domain.ErrInvalid, "%v", bad)1180 assert.ErrorIs(t, err, domain.ErrInvalid, "%v", bad)
1180 }1181 }
1181 again, err = r.repos.Get(r.ctx, root, r.repo)1182 again, err = r.repos.Get(r.ctx, root, r.repo)
@@ -1183,7 +1184,7 @@ func TestUpdateRepoSettings(t *testing.T) {
1183 assert.Equal(t, got.Settings, again.Settings, "a refused change stores nothing")1184 assert.Equal(t, got.Settings, again.Settings, "a refused change stores nothing")
1184 1185
1185 // repo:admin is needed, like for any other field.1186 // repo:admin is needed, like for any other field.
1186- _, err = r.repos.Update(r.ctx, writer(domain.ScopeRepoRead, domain.ScopeMRMerge), r.repo, app.UpdateParams{FailingChecksBlock: &off})1187+ _, err = r.repos.Update(r.ctx, writer(domain.ScopeRepoRead, domain.ScopeMRMerge), r.repo, ports.UpdateRepoParams{FailingChecksBlock: &off})
1187 assert.ErrorIs(t, err, domain.ErrForbidden)1188 assert.ErrorIs(t, err, domain.ErrForbidden)
1188 }1189 }
1189 1190
@@ -1194,13 +1195,13 @@ func TestDeletingARepositoryDropsItsMergeRequests(t *testing.T) {
1194 r.open("feat")1195 r.open("feat")
1195 require.NoError(t, r.repos.Delete(r.ctx, root, r.repo))1196 require.NoError(t, r.repos.Delete(r.ctx, root, r.repo))
1196 1197
1197- _, err := r.mrs.List(r.ctx, root, r.repo, app.ListMergeRequestsParams{})1198+ _, err := r.mrs.List(r.ctx, root, r.repo, ports.ListMergeRequestsParams{})
1198 assert.ErrorIs(t, err, domain.ErrNotFound)1199 assert.ErrorIs(t, err, domain.ErrNotFound)
1199 1200
1200 // Created again under the same name, it starts empty.1201 // Created again under the same name, it starts empty.
1201- _, err = r.repos.Create(r.ctx, root, app.CreateParams{Name: r.repo})1202+ _, err = r.repos.Create(r.ctx, root, ports.CreateRepoParams{Name: r.repo})
1202 require.NoError(t, err)1203 require.NoError(t, err)
1203- page, err := r.mrs.List(r.ctx, root, r.repo, app.ListMergeRequestsParams{})1204+ page, err := r.mrs.List(r.ctx, root, r.repo, ports.ListMergeRequestsParams{})
1204 require.NoError(t, err)1205 require.NoError(t, err)
1205 assert.Empty(t, page.MergeRequests)1206 assert.Empty(t, page.MergeRequests)
1206 }1207 }
@@ -1222,12 +1223,12 @@ func TestAgentPresetWorksOnItsPrivateRepository(t *testing.T) {
1222 r.seed()1223 r.seed()
1223 head := r.feature("feat")1224 head := r.feature("feat")
1224 1225
1225- mr, err := r.mrs.Create(r.ctx, agent, r.repo, app.CreateMergeRequestParams{Title: "From the agent", Source: "feat"})1226+ mr, err := r.mrs.Create(r.ctx, agent, r.repo, ports.CreateMergeRequestParams{Title: "From the agent", Source: "feat"})
1226 require.NoError(t, err)1227 require.NoError(t, err)
1227 assert.Equal(t, head, mr.HeadID)1228 assert.Equal(t, head, mr.HeadID)
1228 assert.Equal(t, "agent-1", mr.Author)1229 assert.Equal(t, "agent-1", mr.Author)
1229 1230
1230- c, err := r.mrs.AddComment(r.ctx, agent, r.repo, mr.Number, app.AddCommentParams{Text: "note"})1231+ c, err := r.mrs.AddComment(r.ctx, agent, r.repo, mr.Number, ports.AddCommentParams{Text: "note"})
1231 require.NoError(t, err)1232 require.NoError(t, err)
1232 _, err = r.mrs.ResolveComment(r.ctx, agent, r.repo, mr.Number, c.ID, true)1233 _, err = r.mrs.ResolveComment(r.ctx, agent, r.repo, mr.Number, c.ID, true)
1233 require.NoError(t, err)1234 require.NoError(t, err)
@@ -1235,7 +1236,7 @@ func TestAgentPresetWorksOnItsPrivateRepository(t *testing.T) {
1235 require.NoError(t, err)1236 require.NoError(t, err)
1236 1237
1237 // repo:write alone does not read merge requests.1238 // repo:write alone does not read merge requests.
1238- _, err = r.mrs.List(r.ctx, agent, r.repo, app.ListMergeRequestsParams{})1239+ _, err = r.mrs.List(r.ctx, agent, r.repo, ports.ListMergeRequestsParams{})
1239 assert.ErrorIs(t, err, domain.ErrNotFound)1240 assert.ErrorIs(t, err, domain.ErrNotFound)
1240 _, err = r.mrs.Get(r.ctx, agent, r.repo, mr.Number, false)1241 _, err = r.mrs.Get(r.ctx, agent, r.repo, mr.Number, false)
1241 assert.ErrorIs(t, err, domain.ErrNotFound)1242 assert.ErrorIs(t, err, domain.ErrNotFound)
@@ -1245,11 +1246,11 @@ func TestAgentPresetWorksOnItsPrivateRepository(t *testing.T) {
1245 assert.Equal(t, domain.MRStateClosed, closed.State)1246 assert.Equal(t, domain.MRStateClosed, closed.State)
1246 1247
1247 // An unknown repository is not found whatever the scopes.1248 // An unknown repository is not found whatever the scopes.
1248- _, err = r.mrs.Create(r.ctx, agent, name(t, "ghost"), app.CreateMergeRequestParams{Title: "t", Source: "feat"})1249+ _, err = r.mrs.Create(r.ctx, agent, name(t, "ghost"), ports.CreateMergeRequestParams{Title: "t", Source: "feat"})
1249 assert.ErrorIs(t, err, domain.ErrNotFound)1250 assert.ErrorIs(t, err, domain.ErrNotFound)
1250 // A token limited to other repositories does not get the scope here.1251 // A token limited to other repositories does not get the scope here.
1251 elsewhere := domain.TokenPrincipal(domain.Token{ID: "e", Scopes: []domain.Scope{domain.ScopeRepoWrite, domain.ScopeMRWrite}, Repos: []domain.RepoName{name(t, "else")}})1252 elsewhere := domain.TokenPrincipal(domain.Token{ID: "e", Scopes: []domain.Scope{domain.ScopeRepoWrite, domain.ScopeMRWrite}, Repos: []domain.RepoName{name(t, "else")}})
1252- _, err = r.mrs.Create(r.ctx, elsewhere, r.repo, app.CreateMergeRequestParams{Title: "t", Source: "feat"})1253+ _, err = r.mrs.Create(r.ctx, elsewhere, r.repo, ports.CreateMergeRequestParams{Title: "t", Source: "feat"})
1253 assert.ErrorIs(t, err, domain.ErrNotFound)1254 assert.ErrorIs(t, err, domain.ErrNotFound)
1254 }1255 }
1255 1256
@@ -1267,7 +1268,7 @@ func TestMergeRecordsAPush(t *testing.T) {
1267 require.True(t, before.PushedAt.IsZero(), "no push recorded yet")1268 require.True(t, before.PushedAt.IsZero(), "no push recorded yet")
1268 assert.Empty(t, r.queued.names())1269 assert.Empty(t, r.queued.names())
1269 1270
1270- _, err = r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, app.MergeParams{Mode: mode, HeadID: head})1271+ _, err = r.mrs.Merge(r.ctx, mrM, r.repo, mr.Number, ports.MergeParams{Mode: mode, HeadID: head})
1271 require.NoError(t, err)1272 require.NoError(t, err)
1272 1273
1273 after, err := r.repos.Get(r.ctx, root, r.repo)1274 after, err := r.repos.Get(r.ctx, root, r.repo)
@@ -1285,10 +1286,10 @@ func TestCommentsPerMergeRequestAreBounded(t *testing.T) {
1285 r.feature("feat")1286 r.feature("feat")
1286 mr := r.open("feat")1287 mr := r.open("feat")
1287 for i := range app.MaxCommentsPerMergeRequest {1288 for i := range app.MaxCommentsPerMergeRequest {
1288- _, err := r.mrs.AddComment(r.ctx, mrW, r.repo, mr.Number, app.AddCommentParams{Text: fmt.Sprintf("c%d", i)})1289+ _, err := r.mrs.AddComment(r.ctx, mrW, r.repo, mr.Number, ports.AddCommentParams{Text: fmt.Sprintf("c%d", i)})
1289 require.NoError(t, err, "comment %d", i+1)1290 require.NoError(t, err, "comment %d", i+1)
1290 }1291 }
1291- _, err := r.mrs.AddComment(r.ctx, mrW, r.repo, mr.Number, app.AddCommentParams{Text: "one too many"})1292+ _, err := r.mrs.AddComment(r.ctx, mrW, r.repo, mr.Number, ports.AddCommentParams{Text: "one too many"})
1292 assert.ErrorIs(t, err, domain.ErrResourceExhausted)1293 assert.ErrorIs(t, err, domain.ErrResourceExhausted)
1293 d, err := r.mrs.Get(r.ctx, root, r.repo, mr.Number, false)1294 d, err := r.mrs.Get(r.ctx, root, r.repo, mr.Number, false)
1294 require.NoError(t, err)1295 require.NoError(t, err)
@@ -1301,6 +1302,6 @@ func TestCommentsPerMergeRequestAreBounded(t *testing.T) {
1301 r.commit(author, "o.txt", "o\n", "other")1302 r.commit(author, "o.txt", "o\n", "other")
1302 r.push("other")1303 r.push("other")
1303 other := r.open("other")1304 other := r.open("other")
1304- _, err = r.mrs.AddComment(r.ctx, mrW, r.repo, other.Number, app.AddCommentParams{Text: "fine"})1305+ _, err = r.mrs.AddComment(r.ctx, mrW, r.repo, other.Number, ports.AddCommentParams{Text: "fine"})
1305 require.NoError(t, err)1306 require.NoError(t, err)
1306 }1307 }
M internal/app/mergerequests_test.go +5 -5
@@ -12,8 +12,8 @@ import (
12 "github.com/stretchr/testify/require"12 "github.com/stretchr/testify/require"
13 13
14 "bnema.dev/ermite/internal/app"14 "bnema.dev/ermite/internal/app"
15- appmocks "bnema.dev/ermite/internal/app/mocks"
16 "bnema.dev/ermite/internal/domain"15 "bnema.dev/ermite/internal/domain"
16+ "bnema.dev/ermite/internal/ports"
17 "bnema.dev/ermite/internal/ports/mocks"17 "bnema.dev/ermite/internal/ports/mocks"
18 )18 )
19 19
@@ -37,7 +37,7 @@ func newMRUnit(t *testing.T) *mrUnit {
37 }37 }
38 clock := mocks.NewClock(t)38 clock := mocks.NewClock(t)
39 clock.EXPECT().Now().Return(now).Maybe()39 clock.EXPECT().Now().Return(now).Maybe()
40- sched := appmocks.NewMaintenanceScheduler(t)40+ sched := mocks.NewMaintenanceScheduler(t)
41 sched.EXPECT().Schedule(mock.Anything, mock.Anything).Return().Maybe()41 sched.EXPECT().Schedule(mock.Anything, mock.Anything).Return().Maybe()
42 u.mrs = app.NewMergeRequests(app.MergeRequestsConfig{42 u.mrs = app.NewMergeRequests(app.MergeRequestsConfig{
43 Meta: u.meta, Store: u.store, Repos: u.repos, Git: u.git,43 Meta: u.meta, Store: u.store, Repos: u.repos, Git: u.git,
@@ -51,7 +51,7 @@ func newMRUnit(t *testing.T) *mrUnit {
51 func TestCreateReturnsGitFailuresAsErrors(t *testing.T) {51 func TestCreateReturnsGitFailuresAsErrors(t *testing.T) {
52 t.Parallel()52 t.Parallel()
53 boom := errors.New("fatal: /srv/ermite/repos/proj.git: permission denied")53 boom := errors.New("fatal: /srv/ermite/repos/proj.git: permission denied")
54- params := app.CreateMergeRequestParams{Title: "t", Source: "feat", Target: "main"}54+ params := ports.CreateMergeRequestParams{Title: "t", Source: "feat", Target: "main"}
55 55
56 t.Run("reading a branch", func(t *testing.T) {56 t.Run("reading a branch", func(t *testing.T) {
57 t.Parallel()57 t.Parallel()
@@ -70,7 +70,7 @@ func TestCreateReturnsGitFailuresAsErrors(t *testing.T) {
70 u := newMRUnit(t)70 u := newMRUnit(t)
71 u.meta.EXPECT().Get(mock.Anything, u.repo.Name).Return(u.repo, nil)71 u.meta.EXPECT().Get(mock.Anything, u.repo.Name).Return(u.repo, nil)
72 u.repos.EXPECT().DefaultBranch(mock.Anything, u.repo.Name).Return("", false, boom)72 u.repos.EXPECT().DefaultBranch(mock.Anything, u.repo.Name).Return("", false, boom)
73- _, err := u.mrs.Create(context.Background(), domain.Root(), u.repo.Name, app.CreateMergeRequestParams{Title: "t", Source: "feat"})73+ _, err := u.mrs.Create(context.Background(), domain.Root(), u.repo.Name, ports.CreateMergeRequestParams{Title: "t", Source: "feat"})
74 require.ErrorIs(t, err, boom)74 require.ErrorIs(t, err, boom)
75 var fe *domain.InvalidFieldsError75 var fe *domain.InvalidFieldsError
76 assert.False(t, errors.As(err, &fe), "not a field violation: %v", err)76 assert.False(t, errors.As(err, &fe), "not a field violation: %v", err)
@@ -109,7 +109,7 @@ func TestMergeReturnsTheFailureToReadTheSettings(t *testing.T) {
109 u.store.EXPECT().Get(mock.Anything, u.repo.Name, int64(1)).Return(domain.MergeRequest{109 u.store.EXPECT().Get(mock.Anything, u.repo.Name, int64(1)).Return(domain.MergeRequest{
110 Repo: u.repo.Name, Number: 1, State: domain.MRStateOpen, Source: "feat", Target: "main",110 Repo: u.repo.Name, Number: 1, State: domain.MRStateOpen, Source: "feat", Target: "main",
111 }, nil)111 }, nil)
112- _, err := u.mrs.Merge(context.Background(), domain.Root(), u.repo.Name, 1, app.MergeParams{HeadID: someID(t)})112+ _, err := u.mrs.Merge(context.Background(), domain.Root(), u.repo.Name, 1, ports.MergeParams{HeadID: someID(t)})
113 require.ErrorIs(t, err, tc.want)113 require.ErrorIs(t, err, tc.want)
114 if tc.want == boom {114 if tc.want == boom {
115 assert.NotErrorIs(t, err, domain.ErrNotFound)115 assert.NotErrorIs(t, err, domain.ErrNotFound)
M internal/app/paging.go +11 -24
@@ -11,6 +11,7 @@ import (
11 "strings"11 "strings"
12 12
13 "bnema.dev/ermite/internal/domain"13 "bnema.dev/ermite/internal/domain"
14+ "bnema.dev/ermite/internal/ports"
14 )15 )
15 16
16 const (17 const (
@@ -83,31 +84,24 @@ func pageAfter[T any](items []T, size int, after func(T) bool, token func(T) str
83 return page, ""84 return page, ""
84 }85 }
85 86
86-// RepoPage is a page of repositories ordered by name.
87-type RepoPage struct {
88- Repos []domain.Repo
89- // NextPageToken is empty on the last page.
90- NextPageToken string
91-}
92-
93 // ListPage returns a page of the repositories the principal may read, ordered87 // ListPage returns a page of the repositories the principal may read, ordered
94 // by name. size is 0 for DefaultPageSize and lowered to MaxRepoPageSize; the page88 // by name. size is 0 for DefaultPageSize and lowered to MaxRepoPageSize; the page
95 // token names the last repository of the previous page, so repositories that89 // token names the last repository of the previous page, so repositories that
96 // appear or disappear between two pages never shift the others.90 // appear or disappear between two pages never shift the others.
97-func (r *Repos) ListPage(ctx context.Context, p domain.Principal, size int, token string) (RepoPage, error) {91+func (r *Repos) ListPage(ctx context.Context, p domain.Principal, size int, token string) (ports.RepoPage, error) {
98 n, err := pageSizeUpTo(size, MaxRepoPageSize)92 n, err := pageSizeUpTo(size, MaxRepoPageSize)
99 if err != nil {93 if err != nil {
100- return RepoPage{}, err94+ return ports.RepoPage{}, err
101 }95 }
102 after, err := decodeRepoToken(token)96 after, err := decodeRepoToken(token)
103 if err != nil {97 if err != nil {
104- return RepoPage{}, err98+ return ports.RepoPage{}, err
105 }99 }
106 all, err := r.List(ctx, p)100 all, err := r.List(ctx, p)
107 if err != nil {101 if err != nil {
108- return RepoPage{}, err102+ return ports.RepoPage{}, err
109 }103 }
110- var page RepoPage104+ var page ports.RepoPage
111 page.Repos, page.NextPageToken = pageAfter(all, n,105 page.Repos, page.NextPageToken = pageAfter(all, n,
112 func(repo domain.Repo) bool { return after == "" || repo.Name.String() > after },106 func(repo domain.Repo) bool { return after == "" || repo.Name.String() > after },
113 func(repo domain.Repo) string { return encodeRepoToken(repo.Name) })107 func(repo domain.Repo) string { return encodeRepoToken(repo.Name) })
@@ -130,28 +124,21 @@ func decodeRepoToken(tok string) (string, error) {
130 return raw, nil124 return raw, nil
131 }125 }
132 126
133-// TokenPage is a page of stored tokens, newest first.
134-type TokenPage struct {
135- Tokens []domain.Token
136- // NextPageToken is empty on the last page.
137- NextPageToken string
138-}
139-
140 // ListPage returns a page of the tokens List returns, newest first (then by127 // ListPage returns a page of the tokens List returns, newest first (then by
141 // id). size and the page token work like Repos.ListPage; the token names the128 // id). size and the page token work like Repos.ListPage; the token names the
142 // last token of the previous page.129 // last token of the previous page.
143-func (t *Tokens) ListPage(ctx context.Context, p domain.Principal, size int, token string) (TokenPage, error) {130+func (t *Tokens) ListPage(ctx context.Context, p domain.Principal, size int, token string) (ports.TokenPage, error) {
144 n, err := pageSize(size)131 n, err := pageSize(size)
145 if err != nil {132 if err != nil {
146- return TokenPage{}, err133+ return ports.TokenPage{}, err
147 }134 }
148 after, err := decodeTokenCursor(token)135 after, err := decodeTokenCursor(token)
149 if err != nil {136 if err != nil {
150- return TokenPage{}, err137+ return ports.TokenPage{}, err
151 }138 }
152 all, err := t.List(ctx, p)139 all, err := t.List(ctx, p)
153 if err != nil {140 if err != nil {
154- return TokenPage{}, err141+ return ports.TokenPage{}, err
155 }142 }
156 slices.SortFunc(all, func(a, b domain.Token) int {143 slices.SortFunc(all, func(a, b domain.Token) int {
157 if c := b.CreatedAt.Compare(a.CreatedAt); c != 0 {144 if c := b.CreatedAt.Compare(a.CreatedAt); c != 0 {
@@ -159,7 +146,7 @@ func (t *Tokens) ListPage(ctx context.Context, p domain.Principal, size int, tok
159 }146 }
160 return strings.Compare(b.ID, a.ID)147 return strings.Compare(b.ID, a.ID)
161 })148 })
162- var page TokenPage149+ var page ports.TokenPage
163 page.Tokens, page.NextPageToken = pageAfter(all, n,150 page.Tokens, page.NextPageToken = pageAfter(all, n,
164 func(tok domain.Token) bool { return after == nil || tokenAfter(tok, *after) },151 func(tok domain.Token) bool { return after == nil || tokenAfter(tok, *after) },
165 encodeTokenCursor)152 encodeTokenCursor)
M internal/app/pushes.go +9 -15
@@ -18,17 +18,6 @@ import (
18 // pushRetries bounds the retries of a metadata update that loses a race.18 // pushRetries bounds the retries of a metadata update that loses a race.
19 const pushRetries = 519 const pushRetries = 5
20 20
21-// MaintenanceScheduler queues repository maintenance. *Maintainer fits.
22-type MaintenanceScheduler interface {
23- Schedule(ctx context.Context, name domain.RepoName)
24-}
25-
26-// Revalidator confirms that a principal obtained earlier is still
27-// authorized. *Auth fits.
28-type Revalidator interface {
29- Revalidate(ctx context.Context, p domain.Principal) error
30-}
31-
32 // Pushes are the use cases around a Git push.21 // Pushes are the use cases around a Git push.
33 type Pushes struct {22 type Pushes struct {
34 meta ports.RepoMetaStore23 meta ports.RepoMetaStore
@@ -36,15 +25,20 @@ type Pushes struct {
36 git ports.GitRepos25 git ports.GitRepos
37 bus ports.EventBus26 bus ports.EventBus
38 clock ports.Clock27 clock ports.Clock
39- maint MaintenanceScheduler28+ maint ports.MaintenanceScheduler
40- auth Revalidator29+ auth ports.Revalidator
41 fresh ports.RepoFreshener // nil: the reader needs no telling30 fresh ports.RepoFreshener // nil: the reader needs no telling
42 }31 }
43 32
33+var (
34+ _ ports.PushService = (*Pushes)(nil)
35+ _ ports.PushHookService = (*Pushes)(nil)
36+)
37+
44 // NewPushes builds the push use cases. Every argument is required: auth38 // NewPushes builds the push use cases. Every argument is required: auth
45 // re-validates the token of a push when its pre-receive hook runs, maint39 // re-validates the token of a push when its pre-receive hook runs, maint
46 // queues the maintenance that follows a push.40 // queues the maintenance that follows a push.
47-func NewPushes(meta ports.RepoMetaStore, repos *Repos, git ports.GitRepos, bus ports.EventBus, clock ports.Clock, maint MaintenanceScheduler, auth Revalidator) *Pushes {41+func NewPushes(meta ports.RepoMetaStore, repos *Repos, git ports.GitRepos, bus ports.EventBus, clock ports.Clock, maint ports.MaintenanceScheduler, auth ports.Revalidator) *Pushes {
48 return &Pushes{meta: meta, repos: repos, git: git, bus: bus, clock: clock, maint: maint, auth: auth}42 return &Pushes{meta: meta, repos: repos, git: git, bus: bus, clock: clock, maint: maint, auth: auth}
49 }43 }
50 44
@@ -78,7 +72,7 @@ func (u *Pushes) Open(ctx context.Context, p domain.Principal, name domain.RepoN
78 if !create || !canWrite(p, name) {72 if !create || !canWrite(p, name) {
79 return domain.ErrNotFound73 return domain.ErrNotFound
80 }74 }
81- _, err = u.repos.Create(ctx, p, CreateParams{Name: name, Visibility: domain.VisibilityPrivate})75+ _, err = u.repos.Create(ctx, p, ports.CreateRepoParams{Name: name, Visibility: domain.VisibilityPrivate})
82 switch {76 switch {
83 case err == nil:77 case err == nil:
84 return nil // Create published RepoCreated78 return nil // Create published RepoCreated
M internal/app/pushes_test.go +2 -3
@@ -13,7 +13,6 @@ import (
13 "github.com/stretchr/testify/require"13 "github.com/stretchr/testify/require"
14 14
15 "bnema.dev/ermite/internal/app"15 "bnema.dev/ermite/internal/app"
16- appmocks "bnema.dev/ermite/internal/app/mocks"
17 "bnema.dev/ermite/internal/domain"16 "bnema.dev/ermite/internal/domain"
18 "bnema.dev/ermite/internal/ports"17 "bnema.dev/ermite/internal/ports"
19 "bnema.dev/ermite/internal/ports/mocks"18 "bnema.dev/ermite/internal/ports/mocks"
@@ -25,7 +24,7 @@ type pushFixture struct {
25 bus *mocks.EventBus24 bus *mocks.EventBus
26 clock *mocks.Clock25 clock *mocks.Clock
27 tokens *mocks.TokenStore26 tokens *mocks.TokenStore
28- sched *appmocks.MaintenanceScheduler27+ sched *mocks.MaintenanceScheduler
29 pushes *app.Pushes28 pushes *app.Pushes
30 }29 }
31 30
@@ -37,7 +36,7 @@ func newPushFixture(t *testing.T) *pushFixture {
37 bus: mocks.NewEventBus(t),36 bus: mocks.NewEventBus(t),
38 clock: mocks.NewClock(t),37 clock: mocks.NewClock(t),
39 tokens: mocks.NewTokenStore(t),38 tokens: mocks.NewTokenStore(t),
40- sched: appmocks.NewMaintenanceScheduler(t),39+ sched: mocks.NewMaintenanceScheduler(t),
41 }40 }
42 f.clock.EXPECT().Now().Return(now).Maybe()41 f.clock.EXPECT().Now().Return(now).Maybe()
43 repos := app.NewRepos(f.meta, f.git, f.bus, f.clock)42 repos := app.NewRepos(f.meta, f.git, f.bus, f.clock)
M internal/app/repos.go +3 -9
@@ -23,20 +23,14 @@ type Repos struct {
23 clock ports.Clock23 clock ports.Clock
24 }24 }
25 25
26+var _ ports.RepoService = (*Repos)(nil)
27+
26 // NewRepos builds the repository use cases over their ports. Changes are28 // NewRepos builds the repository use cases over their ports. Changes are
27 // published on bus.29 // published on bus.
28 func NewRepos(meta ports.RepoMetaStore, git ports.GitRepos, bus ports.EventBus, clock ports.Clock) *Repos {30 func NewRepos(meta ports.RepoMetaStore, git ports.GitRepos, bus ports.EventBus, clock ports.Clock) *Repos {
29 return &Repos{meta: meta, git: git, bus: bus, clock: clock}31 return &Repos{meta: meta, git: git, bus: bus, clock: clock}
30 }32 }
31 33
32-// CreateParams are the settings of a new repository.
33-type CreateParams struct {
34- Name domain.RepoName
35- Description string
36- // Visibility defaults to private when empty.
37- Visibility domain.Visibility
38-}
39-
40 // Create makes a repository and publishes RepoCreated: it stages a bare repository, records the34 // Create makes a repository and publishes RepoCreated: it stages a bare repository, records the
41 // metadata (which fails with domain.ErrAlreadyExists if the name is taken,35 // metadata (which fails with domain.ErrAlreadyExists if the name is taken,
42 // also when a concurrent Create wins) and publishes the directory. A failure36 // also when a concurrent Create wins) and publishes the directory. A failure
@@ -45,7 +39,7 @@ type CreateParams struct {
45 //39 //
46 // The principal needs repo:write on the name: anonymous callers get40 // The principal needs repo:write on the name: anonymous callers get
47 // domain.ErrUnauthenticated, other callers without it domain.ErrForbidden.41 // domain.ErrUnauthenticated, other callers without it domain.ErrForbidden.
48-func (r *Repos) Create(ctx context.Context, p domain.Principal, params CreateParams) (domain.Repo, error) {42+func (r *Repos) Create(ctx context.Context, p domain.Principal, params ports.CreateRepoParams) (domain.Repo, error) {
49 if params.Name.IsZero() {43 if params.Name.IsZero() {
50 return domain.Repo{}, fmt.Errorf("%w: empty name", domain.ErrInvalidName)44 return domain.Repo{}, fmt.Errorf("%w: empty name", domain.ErrInvalidName)
51 }45 }
M internal/app/repos_integration_test.go +2 -1
@@ -20,6 +20,7 @@ import (
20 "bnema.dev/ermite/internal/adapters/kvstore"20 "bnema.dev/ermite/internal/adapters/kvstore"
21 "bnema.dev/ermite/internal/app"21 "bnema.dev/ermite/internal/app"
22 "bnema.dev/ermite/internal/domain"22 "bnema.dev/ermite/internal/domain"
23+ "bnema.dev/ermite/internal/ports"
23 )24 )
24 25
25 type fixedClock struct{}26 type fixedClock struct{}
@@ -56,7 +57,7 @@ func TestCreateConcurrentSameNameRealAdapters(t *testing.T) {
56 wg.Add(1)57 wg.Add(1)
57 go func() {58 go func() {
58 defer wg.Done()59 defer wg.Done()
59- _, err := repos.Create(ctx, domain.Root(), app.CreateParams{Name: n})60+ _, err := repos.Create(ctx, domain.Root(), ports.CreateRepoParams{Name: n})
60 errs <- err61 errs <- err
61 }()62 }()
62 }63 }
M internal/app/repos_test.go +11 -11
@@ -63,7 +63,7 @@ func TestCreate(t *testing.T) {
63 f.git.EXPECT().Publish(ctx, ports.StagedID("s1"), n).Return(nil)63 f.git.EXPECT().Publish(ctx, ports.StagedID("s1"), n).Return(nil)
64 f.bus.EXPECT().Publish(ctx, domain.RepoCreated{Repo: n, Visibility: domain.VisibilityPrivate, At: now}).Return().Once()64 f.bus.EXPECT().Publish(ctx, domain.RepoCreated{Repo: n, Visibility: domain.VisibilityPrivate, At: now}).Return().Once()
65 65
66- got, err := f.repos.Create(ctx, domain.Root(), app.CreateParams{Name: n, Description: "d"})66+ got, err := f.repos.Create(ctx, domain.Root(), ports.CreateRepoParams{Name: n, Description: "d"})
67 require.NoError(t, err)67 require.NoError(t, err)
68 assert.Equal(t, want, got)68 assert.Equal(t, want, got)
69 })69 })
@@ -71,7 +71,7 @@ func TestCreate(t *testing.T) {
71 t.Run("anonymous is refused before anything runs", func(t *testing.T) {71 t.Run("anonymous is refused before anything runs", func(t *testing.T) {
72 t.Parallel()72 t.Parallel()
73 f := newFixture(t)73 f := newFixture(t)
74- _, err := f.repos.Create(ctx, domain.Anonymous(), app.CreateParams{Name: name(t, "tools")})74+ _, err := f.repos.Create(ctx, domain.Anonymous(), ports.CreateRepoParams{Name: name(t, "tools")})
75 assert.ErrorIs(t, err, domain.ErrUnauthenticated)75 assert.ErrorIs(t, err, domain.ErrUnauthenticated)
76 })76 })
77 77
@@ -81,7 +81,7 @@ func TestCreate(t *testing.T) {
81 tokenPrincipal(domain.ScopeRepoWrite, "other"),81 tokenPrincipal(domain.ScopeRepoWrite, "other"),
82 } {82 } {
83 f := newFixture(t)83 f := newFixture(t)
84- _, err := f.repos.Create(ctx, p, app.CreateParams{Name: name(t, "tools")})84+ _, err := f.repos.Create(ctx, p, ports.CreateRepoParams{Name: name(t, "tools")})
85 assert.ErrorIs(t, err, domain.ErrForbidden)85 assert.ErrorIs(t, err, domain.ErrForbidden)
86 }86 }
87 })87 })
@@ -94,21 +94,21 @@ func TestCreate(t *testing.T) {
94 f.meta.EXPECT().Create(ctx, mock.Anything).Return(nil)94 f.meta.EXPECT().Create(ctx, mock.Anything).Return(nil)
95 f.git.EXPECT().Publish(ctx, ports.StagedID("s1"), n).Return(nil)95 f.git.EXPECT().Publish(ctx, ports.StagedID("s1"), n).Return(nil)
96 f.bus.EXPECT().Publish(ctx, mock.Anything).Return().Once()96 f.bus.EXPECT().Publish(ctx, mock.Anything).Return().Once()
97- _, err := f.repos.Create(ctx, tokenPrincipal(domain.ScopeRepoWrite, "tools"), app.CreateParams{Name: n})97+ _, err := f.repos.Create(ctx, tokenPrincipal(domain.ScopeRepoWrite, "tools"), ports.CreateRepoParams{Name: n})
98 require.NoError(t, err)98 require.NoError(t, err)
99 })99 })
100 100
101 t.Run("zero name is invalid", func(t *testing.T) {101 t.Run("zero name is invalid", func(t *testing.T) {
102 t.Parallel()102 t.Parallel()
103 f := newFixture(t)103 f := newFixture(t)
104- _, err := f.repos.Create(ctx, domain.Root(), app.CreateParams{})104+ _, err := f.repos.Create(ctx, domain.Root(), ports.CreateRepoParams{})
105 assert.ErrorIs(t, err, domain.ErrInvalidName)105 assert.ErrorIs(t, err, domain.ErrInvalidName)
106 })106 })
107 107
108 t.Run("unknown visibility is invalid", func(t *testing.T) {108 t.Run("unknown visibility is invalid", func(t *testing.T) {
109 t.Parallel()109 t.Parallel()
110 f := newFixture(t)110 f := newFixture(t)
111- _, err := f.repos.Create(ctx, domain.Root(), app.CreateParams{Name: name(t, "tools"), Visibility: "secret"})111+ _, err := f.repos.Create(ctx, domain.Root(), ports.CreateRepoParams{Name: name(t, "tools"), Visibility: "secret"})
112 assert.ErrorIs(t, err, domain.ErrInvalidVisibility)112 assert.ErrorIs(t, err, domain.ErrInvalidVisibility)
113 })113 })
114 114
@@ -116,7 +116,7 @@ func TestCreate(t *testing.T) {
116 t.Parallel()116 t.Parallel()
117 f := newFixture(t)117 f := newFixture(t)
118 f.git.EXPECT().Stage(ctx).Return("", errBoom)118 f.git.EXPECT().Stage(ctx).Return("", errBoom)
119- _, err := f.repos.Create(ctx, domain.Root(), app.CreateParams{Name: name(t, "tools")})119+ _, err := f.repos.Create(ctx, domain.Root(), ports.CreateRepoParams{Name: name(t, "tools")})
120 assert.ErrorIs(t, err, errBoom)120 assert.ErrorIs(t, err, errBoom)
121 })121 })
122 122
@@ -126,7 +126,7 @@ func TestCreate(t *testing.T) {
126 f.git.EXPECT().Stage(ctx).Return(ports.StagedID("s1"), nil)126 f.git.EXPECT().Stage(ctx).Return(ports.StagedID("s1"), nil)
127 f.meta.EXPECT().Create(ctx, mock.Anything).Return(domain.ErrAlreadyExists)127 f.meta.EXPECT().Create(ctx, mock.Anything).Return(domain.ErrAlreadyExists)
128 f.git.EXPECT().Discard(mock.Anything, ports.StagedID("s1")).Return(nil)128 f.git.EXPECT().Discard(mock.Anything, ports.StagedID("s1")).Return(nil)
129- _, err := f.repos.Create(ctx, domain.Root(), app.CreateParams{Name: name(t, "tools")})129+ _, err := f.repos.Create(ctx, domain.Root(), ports.CreateRepoParams{Name: name(t, "tools")})
130 assert.ErrorIs(t, err, domain.ErrAlreadyExists)130 assert.ErrorIs(t, err, domain.ErrAlreadyExists)
131 })131 })
132 132
@@ -139,7 +139,7 @@ func TestCreate(t *testing.T) {
139 f.git.EXPECT().Publish(ctx, ports.StagedID("s1"), n).Return(domain.ErrAlreadyExists)139 f.git.EXPECT().Publish(ctx, ports.StagedID("s1"), n).Return(domain.ErrAlreadyExists)
140 f.meta.EXPECT().Delete(mock.Anything, n).Return(nil)140 f.meta.EXPECT().Delete(mock.Anything, n).Return(nil)
141 f.git.EXPECT().Discard(mock.Anything, ports.StagedID("s1")).Return(nil)141 f.git.EXPECT().Discard(mock.Anything, ports.StagedID("s1")).Return(nil)
142- _, err := f.repos.Create(ctx, domain.Root(), app.CreateParams{Name: n})142+ _, err := f.repos.Create(ctx, domain.Root(), ports.CreateRepoParams{Name: n})
143 assert.ErrorIs(t, err, domain.ErrAlreadyExists)143 assert.ErrorIs(t, err, domain.ErrAlreadyExists)
144 })144 })
145 145
@@ -152,7 +152,7 @@ func TestCreate(t *testing.T) {
152 f.git.EXPECT().Publish(ctx, ports.StagedID("s1"), n).Return(errBoom)152 f.git.EXPECT().Publish(ctx, ports.StagedID("s1"), n).Return(errBoom)
153 f.meta.EXPECT().Delete(mock.Anything, n).Return(nil)153 f.meta.EXPECT().Delete(mock.Anything, n).Return(nil)
154 f.git.EXPECT().Discard(mock.Anything, ports.StagedID("s1")).Return(nil)154 f.git.EXPECT().Discard(mock.Anything, ports.StagedID("s1")).Return(nil)
155- _, err := f.repos.Create(ctx, domain.Root(), app.CreateParams{Name: n})155+ _, err := f.repos.Create(ctx, domain.Root(), ports.CreateRepoParams{Name: n})
156 assert.ErrorIs(t, err, errBoom)156 assert.ErrorIs(t, err, errBoom)
157 })157 })
158 158
@@ -174,7 +174,7 @@ func TestCreate(t *testing.T) {
174 f.git.EXPECT().Discard(mock.Anything, ports.StagedID("s1")).RunAndReturn(func(c context.Context, _ ports.StagedID) error {174 f.git.EXPECT().Discard(mock.Anything, ports.StagedID("s1")).RunAndReturn(func(c context.Context, _ ports.StagedID) error {
175 return c.Err()175 return c.Err()
176 })176 })
177- _, err := f.repos.Create(cctx, domain.Root(), app.CreateParams{Name: n})177+ _, err := f.repos.Create(cctx, domain.Root(), ports.CreateRepoParams{Name: n})
178 assert.ErrorIs(t, err, errBoom)178 assert.ErrorIs(t, err, errBoom)
179 })179 })
180 }180 }
M internal/app/repos_write.go +6 -25
@@ -12,6 +12,7 @@ import (
12 "github.com/bnema/zerowrap"12 "github.com/bnema/zerowrap"
13 13
14 "bnema.dev/ermite/internal/domain"14 "bnema.dev/ermite/internal/domain"
15+ "bnema.dev/ermite/internal/ports"
15 )16 )
16 17
17 // MaxDescriptionLen is the longest repository description, in bytes.18 // MaxDescriptionLen is the longest repository description, in bytes.
@@ -33,34 +34,14 @@ func validateDescription(s string) error {
33 return nil34 return nil
34 }35 }
35 36
36-// RepoView is a repository with the Git state that belongs in its summary.
37-type RepoView struct {
38- domain.Repo
39- // DefaultBranch is the branch HEAD points to, even when it does not exist
40- // yet; empty when HEAD is detached or the directory is missing.
41- DefaultBranch string
42-}
43-
44 // View adds the default branch to a repository the caller has already been37 // View adds the default branch to a repository the caller has already been
45 // allowed to see. A repository without a directory has no default branch.38 // allowed to see. A repository without a directory has no default branch.
46-func (r *Repos) View(ctx context.Context, repo domain.Repo) (RepoView, error) {39+func (r *Repos) View(ctx context.Context, repo domain.Repo) (ports.RepoView, error) {
47 branch, err := r.git.HeadBranch(ctx, repo.Name)40 branch, err := r.git.HeadBranch(ctx, repo.Name)
48 if err != nil && !errors.Is(err, domain.ErrNotFound) {41 if err != nil && !errors.Is(err, domain.ErrNotFound) {
49- return RepoView{}, fmt.Errorf("read default branch of %q: %w", repo.Name, err)42+ return ports.RepoView{}, fmt.Errorf("read default branch of %q: %w", repo.Name, err)
50 }43 }
51- return RepoView{Repo: repo, DefaultBranch: branch}, nil44+ return ports.RepoView{Repo: repo, DefaultBranch: branch}, nil
52-}
53-
54-// UpdateParams are the changes of Update; a nil field is left alone.
55-type UpdateParams struct {
56- Description *string
57- Visibility *domain.Visibility
58- DefaultBranch *string
59- // AllowedMergeModes replaces the allowed merge modes; it must hold at
60- // least one known mode.
61- AllowedMergeModes *[]domain.MergeMode
62- // FailingChecksBlock replaces the setting of that name.
63- FailingChecksBlock *bool
64 }45 }
65 46
66 // Update changes the description, the visibility, the default branch and the47 // Update changes the description, the visibility, the default branch and the
@@ -71,7 +52,7 @@ type UpdateParams struct {
71 // The metadata is written first, then HEAD, and RepoUpdated is published after52 // The metadata is written first, then HEAD, and RepoUpdated is published after
72 // both. When only the HEAD move fails, RepoUpdated is still published for the53 // both. When only the HEAD move fails, RepoUpdated is still published for the
73 // stored metadata and the HEAD error is returned.54 // stored metadata and the HEAD error is returned.
74-func (r *Repos) Update(ctx context.Context, p domain.Principal, name domain.RepoName, params UpdateParams) (domain.Repo, error) {55+func (r *Repos) Update(ctx context.Context, p domain.Principal, name domain.RepoName, params ports.UpdateRepoParams) (domain.Repo, error) {
75 repo, err := lookupRepo(ctx, r.meta, name)56 repo, err := lookupRepo(ctx, r.meta, name)
76 if err != nil {57 if err != nil {
77 return domain.Repo{}, err58 return domain.Repo{}, err
@@ -158,7 +139,7 @@ func (r *Repos) Update(ctx context.Context, p domain.Principal, name domain.Repo
158 139
159 // updateMeta applies the metadata fields, retrying when a write (a push140 // updateMeta applies the metadata fields, retrying when a write (a push
160 // recording pushed_at) gets in between.141 // recording pushed_at) gets in between.
161-func (r *Repos) updateMeta(ctx context.Context, name domain.RepoName, params UpdateParams) (domain.Repo, error) {142+func (r *Repos) updateMeta(ctx context.Context, name domain.RepoName, params ports.UpdateRepoParams) (domain.Repo, error) {
162 for range pushRetries {143 for range pushRetries {
163 repo, err := r.meta.Get(ctx, name)144 repo, err := r.meta.Get(ctx, name)
164 if err != nil {145 if err != nil {
M internal/app/repos_write_test.go +11 -10
@@ -15,6 +15,7 @@ import (
15 15
16 "bnema.dev/ermite/internal/app"16 "bnema.dev/ermite/internal/app"
17 "bnema.dev/ermite/internal/domain"17 "bnema.dev/ermite/internal/domain"
18+ "bnema.dev/ermite/internal/ports"
18 )19 )
19 20
20 func ptr[T any](v T) *T { return &v }21 func ptr[T any](v T) *T { return &v }
@@ -36,7 +37,7 @@ func TestUpdate(t *testing.T) {
36 Repo: n, At: now, Visibility: domain.VisibilityPublic, Previous: domain.VisibilityPrivate,37 Repo: n, At: now, Visibility: domain.VisibilityPublic, Previous: domain.VisibilityPrivate,
37 }).Return().Once()38 }).Return().Once()
38 39
39- got, err := f.repos.Update(ctx, admin, n, app.UpdateParams{40+ got, err := f.repos.Update(ctx, admin, n, ports.UpdateRepoParams{
40 Description: ptr("new"), Visibility: ptr(domain.VisibilityPublic),41 Description: ptr("new"), Visibility: ptr(domain.VisibilityPublic),
41 })42 })
42 require.NoError(t, err)43 require.NoError(t, err)
@@ -50,7 +51,7 @@ func TestUpdate(t *testing.T) {
50 f.meta.EXPECT().Update(ctx, mock.Anything).Return(domain.ErrConflict).Once()51 f.meta.EXPECT().Update(ctx, mock.Anything).Return(domain.ErrConflict).Once()
51 f.meta.EXPECT().Update(ctx, mock.Anything).Return(nil).Once()52 f.meta.EXPECT().Update(ctx, mock.Anything).Return(nil).Once()
52 f.bus.EXPECT().Publish(ctx, mock.Anything).Return().Once()53 f.bus.EXPECT().Publish(ctx, mock.Anything).Return().Once()
53- _, err := f.repos.Update(ctx, admin, n, app.UpdateParams{Description: ptr("x")})54+ _, err := f.repos.Update(ctx, admin, n, ports.UpdateRepoParams{Description: ptr("x")})
54 require.NoError(t, err)55 require.NoError(t, err)
55 })56 })
56 57
@@ -61,7 +62,7 @@ func TestUpdate(t *testing.T) {
61 f.git.EXPECT().HasBranch(ctx, n, "dev").Return(true, nil)62 f.git.EXPECT().HasBranch(ctx, n, "dev").Return(true, nil)
62 f.git.EXPECT().SetDefaultBranch(ctx, n, "dev").Return(nil)63 f.git.EXPECT().SetDefaultBranch(ctx, n, "dev").Return(nil)
63 f.bus.EXPECT().Publish(ctx, mock.Anything).Return().Once()64 f.bus.EXPECT().Publish(ctx, mock.Anything).Return().Once()
64- got, err := f.repos.Update(ctx, admin, n, app.UpdateParams{DefaultBranch: ptr("dev")})65+ got, err := f.repos.Update(ctx, admin, n, ports.UpdateRepoParams{DefaultBranch: ptr("dev")})
65 require.NoError(t, err)66 require.NoError(t, err)
66 assert.Equal(t, priv, got)67 assert.Equal(t, priv, got)
67 })68 })
@@ -81,7 +82,7 @@ func TestUpdate(t *testing.T) {
81 return nil82 return nil
82 })83 })
83 f.bus.EXPECT().Publish(ctx, mock.Anything).Run(func(context.Context, domain.Event) { order = append(order, "event") }).Return().Once()84 f.bus.EXPECT().Publish(ctx, mock.Anything).Run(func(context.Context, domain.Event) { order = append(order, "event") }).Return().Once()
84- _, err := f.repos.Update(ctx, admin, n, app.UpdateParams{Description: ptr("x"), DefaultBranch: ptr("dev")})85+ _, err := f.repos.Update(ctx, admin, n, ports.UpdateRepoParams{Description: ptr("x"), DefaultBranch: ptr("dev")})
85 require.NoError(t, err)86 require.NoError(t, err)
86 assert.Equal(t, []string{"meta", "head", "event"}, order)87 assert.Equal(t, []string{"meta", "head", "event"}, order)
87 })88 })
@@ -98,7 +99,7 @@ func TestUpdate(t *testing.T) {
98 f.bus.EXPECT().Publish(ctx, domain.RepoUpdated{99 f.bus.EXPECT().Publish(ctx, domain.RepoUpdated{
99 Repo: n, At: now, Visibility: domain.VisibilityPrivate, Previous: domain.VisibilityPublic,100 Repo: n, At: now, Visibility: domain.VisibilityPrivate, Previous: domain.VisibilityPublic,
100 }).Return().Once()101 }).Return().Once()
101- _, err := f.repos.Update(ctx, admin, n, app.UpdateParams{Visibility: ptr(domain.VisibilityPrivate), DefaultBranch: ptr("dev")})102+ _, err := f.repos.Update(ctx, admin, n, ports.UpdateRepoParams{Visibility: ptr(domain.VisibilityPrivate), DefaultBranch: ptr("dev")})
102 assert.ErrorIs(t, err, boom)103 assert.ErrorIs(t, err, boom)
103 })104 })
104 105
@@ -109,7 +110,7 @@ func TestUpdate(t *testing.T) {
109 f.meta.EXPECT().Get(ctx, n).Return(priv, nil)110 f.meta.EXPECT().Get(ctx, n).Return(priv, nil)
110 f.git.EXPECT().HasBranch(ctx, n, "dev").Return(true, nil)111 f.git.EXPECT().HasBranch(ctx, n, "dev").Return(true, nil)
111 f.git.EXPECT().SetDefaultBranch(ctx, n, "dev").Return(boom)112 f.git.EXPECT().SetDefaultBranch(ctx, n, "dev").Return(boom)
112- _, err := f.repos.Update(ctx, admin, n, app.UpdateParams{DefaultBranch: ptr("dev")})113+ _, err := f.repos.Update(ctx, admin, n, ports.UpdateRepoParams{DefaultBranch: ptr("dev")})
113 assert.ErrorIs(t, err, boom)114 assert.ErrorIs(t, err, boom)
114 })115 })
115 116
@@ -118,12 +119,12 @@ func TestUpdate(t *testing.T) {
118 f := newFixture(t)119 f := newFixture(t)
119 f.meta.EXPECT().Get(ctx, n).Return(priv, nil)120 f.meta.EXPECT().Get(ctx, n).Return(priv, nil)
120 f.git.EXPECT().HasBranch(ctx, n, "nope").Return(false, nil)121 f.git.EXPECT().HasBranch(ctx, n, "nope").Return(false, nil)
121- _, err := f.repos.Update(ctx, admin, n, app.UpdateParams{Description: ptr("x"), DefaultBranch: ptr("nope")})122+ _, err := f.repos.Update(ctx, admin, n, ports.UpdateRepoParams{Description: ptr("x"), DefaultBranch: ptr("nope")})
122 assert.ErrorIs(t, err, domain.ErrPreconditionFailed)123 assert.ErrorIs(t, err, domain.ErrPreconditionFailed)
123 })124 })
124 125
125 t.Run("invalid input changes nothing", func(t *testing.T) {126 t.Run("invalid input changes nothing", func(t *testing.T) {
126- for name, params := range map[string]app.UpdateParams{127+ for name, params := range map[string]ports.UpdateRepoParams{
127 "empty": {},128 "empty": {},
128 "empty branch": {DefaultBranch: ptr("")},129 "empty branch": {DefaultBranch: ptr("")},
129 "multi-line": {Description: ptr("a\nb")},130 "multi-line": {Description: ptr("a\nb")},
@@ -157,7 +158,7 @@ func TestUpdate(t *testing.T) {
157 t.Parallel()158 t.Parallel()
158 f := newFixture(t)159 f := newFixture(t)
159 f.meta.EXPECT().Get(ctx, n).Return(tt.repo, nil)160 f.meta.EXPECT().Get(ctx, n).Return(tt.repo, nil)
160- _, err := f.repos.Update(ctx, tt.p, n, app.UpdateParams{Description: ptr("x")})161+ _, err := f.repos.Update(ctx, tt.p, n, ports.UpdateRepoParams{Description: ptr("x")})
161 assert.ErrorIs(t, err, tt.want)162 assert.ErrorIs(t, err, tt.want)
162 err = f.repos.Delete(ctx, tt.p, n)163 err = f.repos.Delete(ctx, tt.p, n)
163 assert.ErrorIs(t, err, tt.want)164 assert.ErrorIs(t, err, tt.want)
@@ -168,7 +169,7 @@ func TestUpdate(t *testing.T) {
168 t.Parallel()169 t.Parallel()
169 f := newFixture(t)170 f := newFixture(t)
170 f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{}, domain.ErrNotFound)171 f.meta.EXPECT().Get(ctx, n).Return(domain.Repo{}, domain.ErrNotFound)
171- _, err := f.repos.Update(ctx, domain.Root(), n, app.UpdateParams{Description: ptr("x")})172+ _, err := f.repos.Update(ctx, domain.Root(), n, ports.UpdateRepoParams{Description: ptr("x")})
172 assert.ErrorIs(t, err, domain.ErrNotFound)173 assert.ErrorIs(t, err, domain.ErrNotFound)
173 })174 })
174 }175 }
M internal/app/sessions.go +7 -12
@@ -39,13 +39,6 @@ const (
39 LoginPath = "/-/login/"39 LoginPath = "/-/login/"
40 )40 )
41 41
42-// LoginLink is a login link as the owner receives it.
43-type LoginLink struct {
44- // URL holds the secret: show it once, never log it.
45- URL string
46- ExpiresAt time.Time
47-}
48-
49 // Sessions are the use cases of the owner's browser sessions: login links,42 // Sessions are the use cases of the owner's browser sessions: login links,
50 // session tokens and signing out everywhere. Login links live in memory only,43 // session tokens and signing out everywhere. Login links live in memory only,
51 // so a restart voids them; the signing key and the epoch are in the store.44 // so a restart voids them; the signing key and the epoch are in the store.
@@ -59,6 +52,8 @@ type Sessions struct {
59 links map[[sha256.Size]byte]pendingLink // by hash of the nonce52 links map[[sha256.Size]byte]pendingLink // by hash of the nonce
60 }53 }
61 54
55+var _ ports.SessionService = (*Sessions)(nil)
56+
62 // pendingLink is a login link that waits to be used.57 // pendingLink is a login link that waits to be used.
63 type pendingLink struct {58 type pendingLink struct {
64 expires time.Time59 expires time.Time
@@ -95,13 +90,13 @@ func requireOwner(p domain.Principal) error {
95 // created it: it is capped at that token's expiry (no cap for root, or a token90 // created it: it is capped at that token's expiry (no cap for root, or a token
96 // that never expires). It gives domain.ErrResourceExhausted when MaxPendingLoginLinks91 // that never expires). It gives domain.ErrResourceExhausted when MaxPendingLoginLinks
97 // links wait. The nonce is never logged.92 // links wait. The nonce is never logged.
98-func (s *Sessions) CreateLoginLink(ctx context.Context, p domain.Principal) (LoginLink, error) {93+func (s *Sessions) CreateLoginLink(ctx context.Context, p domain.Principal) (ports.LoginLink, error) {
99 if err := requireOwner(p); err != nil {94 if err := requireOwner(p); err != nil {
100- return LoginLink{}, err95+ return ports.LoginLink{}, err
101 }96 }
102 var raw [loginNonceBytes]byte97 var raw [loginNonceBytes]byte
103 if _, err := rand.Read(raw[:]); err != nil {98 if _, err := rand.Read(raw[:]); err != nil {
104- return LoginLink{}, fmt.Errorf("generate login link: %w", err)99+ return ports.LoginLink{}, fmt.Errorf("generate login link: %w", err)
105 }100 }
106 nonce := base64.RawURLEncoding.EncodeToString(raw[:])101 nonce := base64.RawURLEncoding.EncodeToString(raw[:])
107 now := s.clock.Now()102 now := s.clock.Now()
@@ -115,14 +110,14 @@ func (s *Sessions) CreateLoginLink(ctx context.Context, p domain.Principal) (Log
115 }110 }
116 if len(s.links) >= MaxPendingLoginLinks {111 if len(s.links) >= MaxPendingLoginLinks {
117 s.mu.Unlock()112 s.mu.Unlock()
118- return LoginLink{}, fmt.Errorf("%w: %d login links are already pending, use one or wait a minute", domain.ErrResourceExhausted, MaxPendingLoginLinks)113+ return ports.LoginLink{}, fmt.Errorf("%w: %d login links are already pending, use one or wait a minute", domain.ErrResourceExhausted, MaxPendingLoginLinks)
119 }114 }
120 s.links[sha256.Sum256([]byte(nonce))] = pendingLink{expires: exp, sessionCap: p.ExpiresAt()}115 s.links[sha256.Sum256([]byte(nonce))] = pendingLink{expires: exp, sessionCap: p.ExpiresAt()}
121 s.mu.Unlock()116 s.mu.Unlock()
122 117
123 log := zerowrap.FromCtx(ctx)118 log := zerowrap.FromCtx(ctx)
124 log.Info().Str("created_by", p.Label()).Str("created_by_token", p.TokenID()).Time("expires_at", exp).Msg("login link created")119 log.Info().Str("created_by", p.Label()).Str("created_by_token", p.TokenID()).Time("expires_at", exp).Msg("login link created")
125- return LoginLink{URL: s.publicURL + LoginPath + nonce, ExpiresAt: exp.UTC()}, nil120+ return ports.LoginLink{URL: s.publicURL + LoginPath + nonce, ExpiresAt: exp.UTC()}, nil
126 }121 }
127 122
128 // LoginLinkValid reports whether the nonce is a pending link, without using123 // LoginLinkValid reports whether the nonce is a pending link, without using
M internal/app/signing.go +2 -0
@@ -14,6 +14,8 @@ type SigningKey struct {
14 committer domain.Identity14 committer domain.Identity
15 }15 }
16 16
17+var _ ports.SigningKeyService = (*SigningKey)(nil)
18+
17 // NewSigningKey builds the use case over the signer and the identity recorded19 // NewSigningKey builds the use case over the signer and the identity recorded
18 // as the committer of the signed commits.20 // as the committer of the signed commits.
19 func NewSigningKey(signer ports.CommitSigner, committer domain.Identity) *SigningKey {21 func NewSigningKey(signer ports.CommitSigner, committer domain.Identity) *SigningKey {
M internal/app/tokens.go +16 -27
@@ -40,30 +40,14 @@ type Tokens struct {
40 sessions ports.SessionStore40 sessions ports.SessionStore
41 }41 }
42 42
43+var _ ports.TokenService = (*Tokens)(nil)
44+
43 // NewTokens builds the token use cases over their ports. sessions is where45 // NewTokens builds the token use cases over their ports. sessions is where
44 // revoking a token with owner rights ends every web session (see Revoke).46 // revoking a token with owner rights ends every web session (see Revoke).
45 func NewTokens(store ports.TokenStore, clock ports.Clock, sessions ports.SessionStore) *Tokens {47 func NewTokens(store ports.TokenStore, clock ports.Clock, sessions ports.SessionStore) *Tokens {
46 return &Tokens{store: store, clock: clock, sessions: sessions}48 return &Tokens{store: store, clock: clock, sessions: sessions}
47 }49 }
48 50
49-// CreateTokenParams are the settings of a new token.
50-type CreateTokenParams struct {
51- Label string
52- // Scopes must not be empty.
53- Scopes []domain.Scope
54- // Repos limits the token to these repositories; empty means every one.
55- Repos []domain.RepoName
56- // ExpiresAt is the zero time for a token that never expires.
57- ExpiresAt time.Time
58-}
59-
60-// CreatedToken is a new token with its secret, which is shown once and never
61-// stored.
62-type CreatedToken struct {
63- Token domain.Token
64- Secret string
65-}
66-
67 // Create makes a stored token and returns it with its secret.51 // Create makes a stored token and returns it with its secret.
68 //52 //
69 // The principal needs token:admin: anonymous callers get53 // The principal needs token:admin: anonymous callers get
@@ -75,20 +59,20 @@ type CreatedToken struct {
75 // domain.ErrInvalidToken (this includes the labels "owner" and "root", which59 // domain.ErrInvalidToken (this includes the labels "owner" and "root", which
76 // are reserved, in any case and around spaces), an unknown scope60 // are reserved, in any case and around spaces), an unknown scope
77 // domain.ErrInvalidScope.61 // domain.ErrInvalidScope.
78-func (t *Tokens) Create(ctx context.Context, p domain.Principal, params CreateTokenParams) (CreatedToken, error) {62+func (t *Tokens) Create(ctx context.Context, p domain.Principal, params ports.CreateTokenParams) (ports.CreatedToken, error) {
79 if p.IsAnonymous() {63 if p.IsAnonymous() {
80- return CreatedToken{}, domain.ErrUnauthenticated64+ return ports.CreatedToken{}, domain.ErrUnauthenticated
81 }65 }
82 if !canManageTokens(p) {66 if !canManageTokens(p) {
83- return CreatedToken{}, fmt.Errorf("%w: creating tokens needs %s", domain.ErrForbidden, domain.ScopeTokenAdmin)67+ return ports.CreatedToken{}, fmt.Errorf("%w: creating tokens needs %s", domain.ErrForbidden, domain.ScopeTokenAdmin)
84 }68 }
85 now := t.clock.Now().UTC()69 now := t.clock.Now().UTC()
86 tok, err := buildToken(params, now)70 tok, err := buildToken(params, now)
87 if err != nil {71 if err != nil {
88- return CreatedToken{}, err72+ return ports.CreatedToken{}, err
89 }73 }
90 if err := checkNoEscalation(p, tok); err != nil {74 if err := checkNoEscalation(p, tok); err != nil {
91- return CreatedToken{}, err75+ return ports.CreatedToken{}, err
92 }76 }
93 77
94 for range 3 {78 for range 3 {
@@ -99,7 +83,7 @@ func (t *Tokens) Create(ctx context.Context, p domain.Principal, params CreateTo
99 continue // a random id or secret collided: practically never83 continue // a random id or secret collided: practically never
100 }84 }
101 if err != nil {85 if err != nil {
102- return CreatedToken{}, fmt.Errorf("store token: %w", err)86+ return ports.CreatedToken{}, fmt.Errorf("store token: %w", err)
103 }87 }
104 log := zerowrap.FromCtx(ctx)88 log := zerowrap.FromCtx(ctx)
105 log.Info().89 log.Info().
@@ -107,13 +91,13 @@ func (t *Tokens) Create(ctx context.Context, p domain.Principal, params CreateTo
107 Int("scopes", len(tok.Scopes)).Int("repos", len(tok.Repos)).91 Int("scopes", len(tok.Scopes)).Int("repos", len(tok.Repos)).
108 Str("created_by", p.Label()).Str("created_by_token", p.TokenID()).92 Str("created_by", p.Label()).Str("created_by_token", p.TokenID()).
109 Msg("token created")93 Msg("token created")
110- return CreatedToken{Token: tok, Secret: secret}, nil94+ return ports.CreatedToken{Token: tok, Secret: secret}, nil
111 }95 }
112- return CreatedToken{}, errors.New("store token: could not find a free id")96+ return ports.CreatedToken{}, errors.New("store token: could not find a free id")
113 }97 }
114 98
115 // buildToken validates the settings and returns the token without id and hash.99 // buildToken validates the settings and returns the token without id and hash.
116-func buildToken(params CreateTokenParams, now time.Time) (domain.Token, error) {100+func buildToken(params ports.CreateTokenParams, now time.Time) (domain.Token, error) {
117 if params.Label == "" || len(params.Label) > MaxTokenLabelLen || !utf8.ValidString(params.Label) {101 if params.Label == "" || len(params.Label) > MaxTokenLabelLen || !utf8.ValidString(params.Label) {
118 return domain.Token{}, fmt.Errorf("%w: label must be 1 to %d bytes of UTF-8", domain.ErrInvalidToken, MaxTokenLabelLen)102 return domain.Token{}, fmt.Errorf("%w: label must be 1 to %d bytes of UTF-8", domain.ErrInvalidToken, MaxTokenLabelLen)
119 }103 }
@@ -274,6 +258,11 @@ type Auth struct {
274 clock ports.Clock258 clock ports.Clock
275 }259 }
276 260
261+var (
262+ _ ports.AuthService = (*Auth)(nil)
263+ _ ports.Revalidator = (*Auth)(nil)
264+)
265+
277 // NewAuth builds the authentication use case. rootHash is the SHA-256 of the266 // NewAuth builds the authentication use case. rootHash is the SHA-256 of the
278 // admin token.267 // admin token.
279 func NewAuth(rootHash domain.TokenHash, tokens ports.TokenStore, clock ports.Clock) *Auth {268 func NewAuth(rootHash domain.TokenHash, tokens ports.TokenStore, clock ports.Clock) *Auth {
M internal/app/tokens_test.go +35 -34
@@ -16,6 +16,7 @@ import (
16 16
17 "bnema.dev/ermite/internal/app"17 "bnema.dev/ermite/internal/app"
18 "bnema.dev/ermite/internal/domain"18 "bnema.dev/ermite/internal/domain"
19+ "bnema.dev/ermite/internal/ports"
19 "bnema.dev/ermite/internal/ports/mocks"20 "bnema.dev/ermite/internal/ports/mocks"
20 )21 )
21 22
@@ -65,7 +66,7 @@ func TestTokensCreateAllowsLabelsNearReserved(t *testing.T) {
65 t.Parallel()66 t.Parallel()
66 f := newTokenFixture(t)67 f := newTokenFixture(t)
67 f.store.EXPECT().Create(ctx, mock.Anything).Return(nil)68 f.store.EXPECT().Create(ctx, mock.Anything).Return(nil)
68- _, err := f.tokens.Create(ctx, domain.Root(), app.CreateTokenParams{Label: tt.label, Scopes: scopes(domain.ScopeRepoRead)})69+ _, err := f.tokens.Create(ctx, domain.Root(), ports.CreateTokenParams{Label: tt.label, Scopes: scopes(domain.ScopeRepoRead)})
69 assert.NoError(t, err)70 assert.NoError(t, err)
70 })71 })
71 }72 }
@@ -82,7 +83,7 @@ func TestTokensCreate(t *testing.T) {
82 f.store.EXPECT().Create(ctx, mock.Anything).Run(func(_ context.Context, tok domain.Token) { stored = tok }).Return(nil)83 f.store.EXPECT().Create(ctx, mock.Anything).Run(func(_ context.Context, tok domain.Token) { stored = tok }).Return(nil)
83 84
84 exp := now.Add(48 * time.Hour)85 exp := now.Add(48 * time.Hour)
85- got, err := f.tokens.Create(ctx, domain.Root(), app.CreateTokenParams{86+ got, err := f.tokens.Create(ctx, domain.Root(), ports.CreateTokenParams{
86 Label: "ci",87 Label: "ci",
87 Scopes: scopes(domain.ScopeRepoWrite, domain.ScopeRepoRead, domain.ScopeRepoRead),88 Scopes: scopes(domain.ScopeRepoWrite, domain.ScopeRepoRead, domain.ScopeRepoRead),
88 Repos: names(t, "b", "a", "b"),89 Repos: names(t, "b", "a", "b"),
@@ -109,9 +110,9 @@ func TestTokensCreate(t *testing.T) {
109 t.Parallel()110 t.Parallel()
110 f := newTokenFixture(t)111 f := newTokenFixture(t)
111 f.store.EXPECT().Create(ctx, mock.Anything).Return(nil)112 f.store.EXPECT().Create(ctx, mock.Anything).Return(nil)
112- a, err := f.tokens.Create(ctx, domain.Root(), app.CreateTokenParams{Label: "a", Scopes: scopes(domain.ScopeRepoRead)})113+ a, err := f.tokens.Create(ctx, domain.Root(), ports.CreateTokenParams{Label: "a", Scopes: scopes(domain.ScopeRepoRead)})
113 require.NoError(t, err)114 require.NoError(t, err)
114- b, err := f.tokens.Create(ctx, domain.Root(), app.CreateTokenParams{Label: "a", Scopes: scopes(domain.ScopeRepoRead)})115+ b, err := f.tokens.Create(ctx, domain.Root(), ports.CreateTokenParams{Label: "a", Scopes: scopes(domain.ScopeRepoRead)})
115 require.NoError(t, err)116 require.NoError(t, err)
116 assert.NotEqual(t, a.Secret, b.Secret)117 assert.NotEqual(t, a.Secret, b.Secret)
117 assert.NotEqual(t, a.Token.ID, b.Token.ID)118 assert.NotEqual(t, a.Token.ID, b.Token.ID)
@@ -122,7 +123,7 @@ func TestTokensCreate(t *testing.T) {
122 f := newTokenFixture(t)123 f := newTokenFixture(t)
123 f.store.EXPECT().Create(ctx, mock.Anything).Return(domain.ErrAlreadyExists).Once()124 f.store.EXPECT().Create(ctx, mock.Anything).Return(domain.ErrAlreadyExists).Once()
124 f.store.EXPECT().Create(ctx, mock.Anything).Return(nil).Once()125 f.store.EXPECT().Create(ctx, mock.Anything).Return(nil).Once()
125- _, err := f.tokens.Create(ctx, domain.Root(), app.CreateTokenParams{Label: "a", Scopes: scopes(domain.ScopeRepoRead)})126+ _, err := f.tokens.Create(ctx, domain.Root(), ports.CreateTokenParams{Label: "a", Scopes: scopes(domain.ScopeRepoRead)})
126 require.NoError(t, err)127 require.NoError(t, err)
127 })128 })
128 129
@@ -131,27 +132,27 @@ func TestTokensCreate(t *testing.T) {
131 f := newTokenFixture(t)132 f := newTokenFixture(t)
132 boom := errors.New("boom")133 boom := errors.New("boom")
133 f.store.EXPECT().Create(ctx, mock.Anything).Return(boom)134 f.store.EXPECT().Create(ctx, mock.Anything).Return(boom)
134- _, err := f.tokens.Create(ctx, domain.Root(), app.CreateTokenParams{Label: "a", Scopes: scopes(domain.ScopeRepoRead)})135+ _, err := f.tokens.Create(ctx, domain.Root(), ports.CreateTokenParams{Label: "a", Scopes: scopes(domain.ScopeRepoRead)})
135 assert.ErrorIs(t, err, boom)136 assert.ErrorIs(t, err, boom)
136 })137 })
137 138
138 invalid := []struct {139 invalid := []struct {
139 name string140 name string
140- params app.CreateTokenParams141+ params ports.CreateTokenParams
141 want error142 want error
142 }{143 }{
143- {"empty label", app.CreateTokenParams{Scopes: scopes(domain.ScopeRepoRead)}, domain.ErrInvalidToken},144+ {"empty label", ports.CreateTokenParams{Scopes: scopes(domain.ScopeRepoRead)}, domain.ErrInvalidToken},
144- {"long label", app.CreateTokenParams{Label: strings.Repeat("x", 101), Scopes: scopes(domain.ScopeRepoRead)}, domain.ErrInvalidToken},145+ {"long label", ports.CreateTokenParams{Label: strings.Repeat("x", 101), Scopes: scopes(domain.ScopeRepoRead)}, domain.ErrInvalidToken},
145- {"bad utf8 label", app.CreateTokenParams{Label: "\xff", Scopes: scopes(domain.ScopeRepoRead)}, domain.ErrInvalidToken},146+ {"bad utf8 label", ports.CreateTokenParams{Label: "\xff", Scopes: scopes(domain.ScopeRepoRead)}, domain.ErrInvalidToken},
146- {"reserved owner", app.CreateTokenParams{Label: "owner", Scopes: scopes(domain.ScopeRepoRead)}, domain.ErrInvalidToken},147+ {"reserved owner", ports.CreateTokenParams{Label: "owner", Scopes: scopes(domain.ScopeRepoRead)}, domain.ErrInvalidToken},
147- {"reserved root", app.CreateTokenParams{Label: "root", Scopes: scopes(domain.ScopeRepoRead)}, domain.ErrInvalidToken},148+ {"reserved root", ports.CreateTokenParams{Label: "root", Scopes: scopes(domain.ScopeRepoRead)}, domain.ErrInvalidToken},
148- {"reserved, upper case", app.CreateTokenParams{Label: "OWNER", Scopes: scopes(domain.ScopeRepoRead)}, domain.ErrInvalidToken},149+ {"reserved, upper case", ports.CreateTokenParams{Label: "OWNER", Scopes: scopes(domain.ScopeRepoRead)}, domain.ErrInvalidToken},
149- {"reserved, mixed case", app.CreateTokenParams{Label: "Root", Scopes: scopes(domain.ScopeRepoRead)}, domain.ErrInvalidToken},150+ {"reserved, mixed case", ports.CreateTokenParams{Label: "Root", Scopes: scopes(domain.ScopeRepoRead)}, domain.ErrInvalidToken},
150- {"reserved, padded", app.CreateTokenParams{Label: " \towner \n", Scopes: scopes(domain.ScopeRepoRead)}, domain.ErrInvalidToken},151+ {"reserved, padded", ports.CreateTokenParams{Label: " \towner \n", Scopes: scopes(domain.ScopeRepoRead)}, domain.ErrInvalidToken},
151- {"no scopes", app.CreateTokenParams{Label: "x"}, domain.ErrInvalidToken},152+ {"no scopes", ports.CreateTokenParams{Label: "x"}, domain.ErrInvalidToken},
152- {"unknown scope", app.CreateTokenParams{Label: "x", Scopes: scopes("nope")}, domain.ErrInvalidScope},153+ {"unknown scope", ports.CreateTokenParams{Label: "x", Scopes: scopes("nope")}, domain.ErrInvalidScope},
153- {"past expiry", app.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoRead), ExpiresAt: now}, domain.ErrInvalidToken},154+ {"past expiry", ports.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoRead), ExpiresAt: now}, domain.ErrInvalidToken},
154- {"zero repo name", app.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoRead), Repos: []domain.RepoName{{}}}, domain.ErrInvalidToken},155+ {"zero repo name", ports.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoRead), Repos: []domain.RepoName{{}}}, domain.ErrInvalidToken},
155 }156 }
156 for _, tt := range invalid {157 for _, tt := range invalid {
157 t.Run("invalid/"+tt.name, func(t *testing.T) {158 t.Run("invalid/"+tt.name, func(t *testing.T) {
@@ -179,37 +180,37 @@ func TestTokensCreateNoEscalation(t *testing.T) {
179 tests := []struct {180 tests := []struct {
180 name string181 name string
181 principal domain.Principal182 principal domain.Principal
182- params app.CreateTokenParams183+ params ports.CreateTokenParams
183 want error // nil = allowed184 want error // nil = allowed
184 }{185 }{
185- {"anonymous", domain.Anonymous(), app.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoRead)}, domain.ErrUnauthenticated},186+ {"anonymous", domain.Anonymous(), ports.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoRead)}, domain.ErrUnauthenticated},
186 {"token without token:admin",187 {"token without token:admin",
187 domain.TokenPrincipal(domain.Token{Scopes: scopes(domain.ScopeRepoWrite)}),188 domain.TokenPrincipal(domain.Token{Scopes: scopes(domain.ScopeRepoWrite)}),
188- app.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoRead)}, domain.ErrForbidden},189+ ports.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoRead)}, domain.ErrForbidden},
189 {"subset of scopes", admin(nil, time.Time{}),190 {"subset of scopes", admin(nil, time.Time{}),
190- app.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoRead)}, nil},191+ ports.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoRead)}, nil},
191 {"same scopes incl. token:admin", admin(nil, time.Time{}),192 {"same scopes incl. token:admin", admin(nil, time.Time{}),
192- app.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeTokenAdmin, domain.ScopeRepoWrite)}, nil},193+ ports.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeTokenAdmin, domain.ScopeRepoWrite)}, nil},
193 {"scope it lacks", admin(nil, time.Time{}),194 {"scope it lacks", admin(nil, time.Time{}),
194- app.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoRead, domain.ScopeRepoAdmin)}, domain.ErrForbidden},195+ ports.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoRead, domain.ScopeRepoAdmin)}, domain.ErrForbidden},
195 {"limited creator, same repos", admin(names(t, "a", "b"), time.Time{}),196 {"limited creator, same repos", admin(names(t, "a", "b"), time.Time{}),
196- app.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoRead), Repos: names(t, "a")}, nil},197+ ports.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoRead), Repos: names(t, "a")}, nil},
197 {"limited creator, repo outside its list", admin(names(t, "a"), time.Time{}),198 {"limited creator, repo outside its list", admin(names(t, "a"), time.Time{}),
198- app.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoRead), Repos: names(t, "a", "z")}, domain.ErrForbidden},199+ ports.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoRead), Repos: names(t, "a", "z")}, domain.ErrForbidden},
199 {"limited creator, unlimited child", admin(names(t, "a"), time.Time{}),200 {"limited creator, unlimited child", admin(names(t, "a"), time.Time{}),
200- app.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoRead)}, domain.ErrForbidden},201+ ports.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoRead)}, domain.ErrForbidden},
201 {"unlimited creator may limit", admin(nil, time.Time{}),202 {"unlimited creator may limit", admin(nil, time.Time{}),
202- app.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoRead), Repos: names(t, "z")}, nil},203+ ports.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoRead), Repos: names(t, "z")}, nil},
203 {"expiring creator, child never expires", admin(nil, exp),204 {"expiring creator, child never expires", admin(nil, exp),
204- app.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoRead)}, domain.ErrForbidden},205+ ports.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoRead)}, domain.ErrForbidden},
205 {"expiring creator, child outlasts", admin(nil, exp),206 {"expiring creator, child outlasts", admin(nil, exp),
206- app.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoRead), ExpiresAt: exp.Add(time.Second)}, domain.ErrForbidden},207+ ports.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoRead), ExpiresAt: exp.Add(time.Second)}, domain.ErrForbidden},
207 {"expiring creator, child within", admin(nil, exp),208 {"expiring creator, child within", admin(nil, exp),
208- app.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoRead), ExpiresAt: exp.Add(-time.Second)}, nil},209+ ports.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoRead), ExpiresAt: exp.Add(-time.Second)}, nil},
209 {"expiring creator, child same expiry", admin(nil, exp),210 {"expiring creator, child same expiry", admin(nil, exp),
210- app.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoRead), ExpiresAt: exp}, nil},211+ ports.CreateTokenParams{Label: "x", Scopes: scopes(domain.ScopeRepoRead), ExpiresAt: exp}, nil},
211 {"root: everything", domain.Root(),212 {"root: everything", domain.Root(),
212- app.CreateTokenParams{Label: "x", Scopes: domain.AllScopes()}, nil},213+ ports.CreateTokenParams{Label: "x", Scopes: domain.AllScopes()}, nil},
213 }214 }
214 for _, tt := range tests {215 for _, tt := range tests {
215 t.Run(tt.name, func(t *testing.T) {216 t.Run(tt.name, func(t *testing.T) {
M internal/clientlimit/limiter.go +4 -0
@@ -16,6 +16,8 @@ import (
16 "time"16 "time"
17 17
18 "github.com/bnema/zerowrap"18 "github.com/bnema/zerowrap"
19+
20+ "bnema.dev/ermite/internal/ports"
19 )21 )
20 22
21 // Limiter defaults: 10 failed authentications in 10 minutes block the client23 // Limiter defaults: 10 failed authentications in 10 minutes block the client
@@ -54,6 +56,8 @@ type Limiter struct {
54 lastSweep time.Time56 lastSweep time.Time
55 }57 }
56 58
59+var _ ports.FailureLimiter = (*Limiter)(nil)
60+
57 // NewLimiter builds a limiter. now defaults to time.Now.61 // NewLimiter builds a limiter. now defaults to time.Now.
58 func NewLimiter(now func() time.Time) *Limiter {62 func NewLimiter(now func() time.Time) *Limiter {
59 if now == nil {63 if now == nil {
A internal/ports/dto.go +228 -0
@@ -0,0 +1,228 @@
1+// SPDX-License-Identifier: AGPL-3.0-or-later
2+
3+package ports
4+
5+import (
6+ "time"
7+
8+ "bnema.dev/ermite/internal/domain"
9+)
10+
11+// The parameters and results of the use case interfaces (services.go). They
12+// live here so that the adapters that call the use cases and the use cases
13+// themselves share them without importing each other.
14+
15+// RepoPage is a page of repositories ordered by name.
16+type RepoPage struct {
17+ Repos []domain.Repo
18+ // NextPageToken is empty on the last page.
19+ NextPageToken string
20+}
21+
22+// RepoView is a repository with the Git state that belongs in its summary.
23+type RepoView struct {
24+ domain.Repo
25+ // DefaultBranch is the branch HEAD points to, even when it does not exist
26+ // yet; empty when HEAD is detached or the directory is missing.
27+ DefaultBranch string
28+}
29+
30+// CreateRepoParams are the settings of a new repository.
31+type CreateRepoParams struct {
32+ Name domain.RepoName
33+ Description string
34+ // Visibility defaults to private when empty.
35+ Visibility domain.Visibility
36+}
37+
38+// UpdateRepoParams are the changes of Update; a nil field is left alone.
39+type UpdateRepoParams struct {
40+ Description *string
41+ Visibility *domain.Visibility
42+ DefaultBranch *string
43+ // AllowedMergeModes replaces the allowed merge modes; it must hold at
44+ // least one known mode.
45+ AllowedMergeModes *[]domain.MergeMode
46+ // FailingChecksBlock replaces the setting of that name.
47+ FailingChecksBlock *bool
48+}
49+
50+// TokenPage is a page of stored tokens, newest first.
51+type TokenPage struct {
52+ Tokens []domain.Token
53+ // NextPageToken is empty on the last page.
54+ NextPageToken string
55+}
56+
57+// CreateTokenParams are the settings of a new token.
58+type CreateTokenParams struct {
59+ Label string
60+ // Scopes must not be empty.
61+ Scopes []domain.Scope
62+ // Repos limits the token to these repositories; empty means every one.
63+ Repos []domain.RepoName
64+ // ExpiresAt is the zero time for a token that never expires.
65+ ExpiresAt time.Time
66+}
67+
68+// CreatedToken is a new token with its secret, which is shown once and never
69+// stored.
70+type CreatedToken struct {
71+ Token domain.Token
72+ Secret string
73+}
74+
75+// ListRefsParams selects the refs to list.
76+type ListRefsParams struct {
77+ // Kinds restricts the result; empty means every kind.
78+ Kinds []domain.RefKind
79+ // PageSize is 0 for the default, and lowered to MaxRefPageSize.
80+ PageSize int
81+ // PageToken is the NextPageToken of the previous page, or empty.
82+ PageToken string
83+}
84+
85+// RefPage is a page of refs.
86+type RefPage struct {
87+ Refs []domain.Ref
88+ // NextPageToken is empty on the last page.
89+ NextPageToken string
90+}
91+
92+// ListCommitsParams selects a page of history.
93+type ListCommitsParams struct {
94+ // Revision is where the history starts; the zero value is HEAD. Ignored
95+ // when PageToken is set: the token pins the commit the first page started
96+ // from, so every page walks the same history even if a branch moves.
97+ Revision domain.Revision
98+ // Path limits the history to the commits that changed it. It must be the
99+ // same on every page of a listing.
100+ Path domain.TreePath
101+ // PageSize is 0 for the default, and lowered to MaxPageSize.
102+ PageSize int
103+ // PageToken is the NextPageToken of the previous page, or empty.
104+ PageToken string
105+}
106+
107+// HistoryPage is a page of history, newest first.
108+type HistoryPage struct {
109+ Commits []domain.Commit
110+ // NextPageToken is empty on the last page.
111+ NextPageToken string
112+}
113+
114+// WatchParams select the events of a watch.
115+type WatchParams struct {
116+ // Topics are TopicHome or "repo:<name>"; empty means TopicHome.
117+ Topics []string
118+ // ResumeAfter is the id of the last event the caller saw; empty to
119+ // receive only new events.
120+ ResumeAfter string
121+ // Client identifies where the watch comes from, for the limit per client
122+ // (MaxWatchersPerClient): an opaque key such as a normalized address; the
123+ // web passes its visitor's. Empty only for callers with no client, which
124+ // count against MaxWatchers alone.
125+ Client string
126+ // Ready, when not nil, is called once the topics are valid and the watch
127+ // is registered: events published from then on are not missed. Nothing is
128+ // sent before it returns.
129+ Ready func()
130+}
131+
132+// WatchEvent is one item of a watch.
133+type WatchEvent struct {
134+ // ID identifies the event, for resuming. Empty on a gap.
135+ ID string
136+ // Gap tells that events were lost: the caller must reload what it shows.
137+ // Event is nil then.
138+ Gap bool
139+ // Event is a domain.RepoCreated, RepoUpdated, RepoDeleted, PushEvent,
140+ // MergeRequestOpened, MergeRequestMerged, MergeRequestClosed or
141+ // CommentAdded.
142+ Event domain.Event
143+ // Repo is the state of the repository at delivery, for RepoCreated and
144+ // RepoUpdated.
145+ Repo *RepoView
146+}
147+
148+// CreateMergeRequestParams describes the merge request to open.
149+type CreateMergeRequestParams struct {
150+ Title string
151+ Description string
152+ // Source is required; Target defaults to the default branch.
153+ Source, Target string
154+}
155+
156+// ListMergeRequestsParams selects a page of merge requests.
157+type ListMergeRequestsParams struct {
158+ // State restricts the result; empty means every state.
159+ State domain.MRState
160+ // PageSize is 0 for the default, and lowered to MaxPageSize.
161+ PageSize int
162+ // PageToken is the NextPageToken of the previous page, or empty.
163+ PageToken string
164+}
165+
166+// MergeRequestPage is a page of merge requests, highest number first.
167+type MergeRequestPage struct {
168+ MergeRequests []domain.MergeRequest
169+ // NextPageToken is empty on the last page.
170+ NextPageToken string
171+}
172+
173+// MergeRequestDetail is a merge request with its review data.
174+type MergeRequestDetail struct {
175+ MergeRequest domain.MergeRequest
176+ // Commits are the commits of the head that the target lacks, newest
177+ // first, at most MaxMergeRequestCommits.
178+ Commits []domain.Commit
179+ CommitsTruncated bool
180+ // DiffBaseID is the commit the diff compares from; zero when the source
181+ // branch is gone, then the diff is empty.
182+ DiffBaseID domain.ObjectID
183+ Stats domain.DiffStats
184+ Diff domain.Diff
185+ // Comments are the comments, oldest first.
186+ Comments []domain.Comment
187+ // LastReadID is the head recorded by MarkRead; zero when none.
188+ LastReadID domain.ObjectID
189+ // LastReadLost tells that since-last-read was asked for, a head was
190+ // recorded, but that commit is gone: the diff is against the target.
191+ LastReadLost bool
192+}
193+
194+// AddCommentParams describes the comment to add.
195+type AddCommentParams struct {
196+ // Path, Line and Side designate a line of the diff; all empty for a
197+ // general comment.
198+ Path string
199+ Line int
200+ Side domain.DiffSide
201+ Text string
202+}
203+
204+// MergeParams describes the merge to perform.
205+type MergeParams struct {
206+ // Mode defaults to fast-forward.
207+ Mode domain.MergeMode
208+ // HeadID is the commit the caller reviewed; the source branch must still
209+ // point to it.
210+ HeadID domain.ObjectID
211+ // DeleteSource deletes the source branch after the merge.
212+ DeleteSource bool
213+}
214+
215+// MergeResult is a merged request and what happened to its source branch.
216+type MergeResult struct {
217+ domain.MergeRequest
218+ // SourceDeleted is true when DeleteSource was asked and the branch is
219+ // gone.
220+ SourceDeleted bool
221+}
222+
223+// LoginLink is a login link as the owner receives it.
224+type LoginLink struct {
225+ // URL holds the secret: show it once, never log it.
226+ URL string
227+ ExpiresAt time.Time
228+}
R internal/adapters/githttp/mocks/Authenticator.go → internal/ports/mocks/AuthService.go +18 -18 renamed, 52% similar
@@ -11,17 +11,17 @@ import (
11 mock "github.com/stretchr/testify/mock"11 mock "github.com/stretchr/testify/mock"
12 )12 )
13 13
14-// NewAuthenticator creates a new instance of Authenticator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.14+// NewAuthService creates a new instance of AuthService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
15 // The first argument is typically a *testing.T value.15 // The first argument is typically a *testing.T value.
16-func NewAuthenticator(t interface {16+func NewAuthService(t interface {
17 mock.TestingT17 mock.TestingT
18 Cleanup(func())18 Cleanup(func())
19-}) *Authenticator {19+}) *AuthService {
20 if helper, ok := t.(interface{ Helper() }); ok {20 if helper, ok := t.(interface{ Helper() }); ok {
21 helper.Helper()21 helper.Helper()
22 }22 }
23 23
24- mock := &Authenticator{}24+ mock := &AuthService{}
25 mock.Mock.Test(t)25 mock.Mock.Test(t)
26 26
27 t.Cleanup(func() {27 t.Cleanup(func() {
@@ -34,21 +34,21 @@ func NewAuthenticator(t interface {
34 return mock34 return mock
35 }35 }
36 36
37-// Authenticator is an autogenerated mock type for the Authenticator type37+// AuthService is an autogenerated mock type for the AuthService type
38-type Authenticator struct {38+type AuthService struct {
39 mock.Mock39 mock.Mock
40 }40 }
41 41
42-type Authenticator_Expecter struct {42+type AuthService_Expecter struct {
43 mock *mock.Mock43 mock *mock.Mock
44 }44 }
45 45
46-func (_m *Authenticator) EXPECT() *Authenticator_Expecter {46+func (_m *AuthService) EXPECT() *AuthService_Expecter {
47- return &Authenticator_Expecter{mock: &_m.Mock}47+ return &AuthService_Expecter{mock: &_m.Mock}
48 }48 }
49 49
50-// Authenticate provides a mock function for the type Authenticator50+// Authenticate provides a mock function for the type AuthService
51-func (_mock *Authenticator) Authenticate(ctx context.Context, secret string) (domain.Principal, error) {51+func (_mock *AuthService) Authenticate(ctx context.Context, secret string) (domain.Principal, error) {
52 ret := _mock.Called(ctx, secret)52 ret := _mock.Called(ctx, secret)
53 53
54 if len(ret) == 0 {54 if len(ret) == 0 {
@@ -73,19 +73,19 @@ func (_mock *Authenticator) Authenticate(ctx context.Context, secret string) (do
73 return r0, r173 return r0, r1
74 }74 }
75 75
76-// Authenticator_Authenticate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Authenticate'76+// AuthService_Authenticate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Authenticate'
77-type Authenticator_Authenticate_Call struct {77+type AuthService_Authenticate_Call struct {
78 *mock.Call78 *mock.Call
79 }79 }
80 80
81 // Authenticate is a helper method to define mock.On call81 // Authenticate is a helper method to define mock.On call
82 // - ctx context.Context82 // - ctx context.Context
83 // - secret string83 // - secret string
84-func (_e *Authenticator_Expecter) Authenticate(ctx any, secret any) *Authenticator_Authenticate_Call {84+func (_e *AuthService_Expecter) Authenticate(ctx any, secret any) *AuthService_Authenticate_Call {
85- return &Authenticator_Authenticate_Call{Call: _e.mock.On("Authenticate", ctx, secret)}85+ return &AuthService_Authenticate_Call{Call: _e.mock.On("Authenticate", ctx, secret)}
86 }86 }
87 87
88-func (_c *Authenticator_Authenticate_Call) Run(run func(ctx context.Context, secret string)) *Authenticator_Authenticate_Call {88+func (_c *AuthService_Authenticate_Call) Run(run func(ctx context.Context, secret string)) *AuthService_Authenticate_Call {
89 _c.Call.Run(func(args mock.Arguments) {89 _c.Call.Run(func(args mock.Arguments) {
90 var arg0 context.Context90 var arg0 context.Context
91 if args[0] != nil {91 if args[0] != nil {
@@ -103,12 +103,12 @@ func (_c *Authenticator_Authenticate_Call) Run(run func(ctx context.Context, sec
103 return _c103 return _c
104 }104 }
105 105
106-func (_c *Authenticator_Authenticate_Call) Return(principal domain.Principal, err error) *Authenticator_Authenticate_Call {106+func (_c *AuthService_Authenticate_Call) Return(principal domain.Principal, err error) *AuthService_Authenticate_Call {
107 _c.Call.Return(principal, err)107 _c.Call.Return(principal, err)
108 return _c108 return _c
109 }109 }
110 110
111-func (_c *Authenticator_Authenticate_Call) RunAndReturn(run func(ctx context.Context, secret string) (domain.Principal, error)) *Authenticator_Authenticate_Call {111+func (_c *AuthService_Authenticate_Call) RunAndReturn(run func(ctx context.Context, secret string) (domain.Principal, error)) *AuthService_Authenticate_Call {
112 _c.Call.Return(run)112 _c.Call.Return(run)
113 return _c113 return _c
114 }114 }
R internal/adapters/rpc/mocks/Browse.go → internal/ports/mocks/BrowseService.go +81 -81 renamed, 55% similar
@@ -8,22 +8,22 @@ import (
8 "context"8 "context"
9 "io"9 "io"
10 10
11- "bnema.dev/ermite/internal/app"
12 "bnema.dev/ermite/internal/domain"11 "bnema.dev/ermite/internal/domain"
12+ "bnema.dev/ermite/internal/ports"
13 mock "github.com/stretchr/testify/mock"13 mock "github.com/stretchr/testify/mock"
14 )14 )
15 15
16-// NewBrowse creates a new instance of Browse. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.16+// NewBrowseService creates a new instance of BrowseService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
17 // The first argument is typically a *testing.T value.17 // The first argument is typically a *testing.T value.
18-func NewBrowse(t interface {18+func NewBrowseService(t interface {
19 mock.TestingT19 mock.TestingT
20 Cleanup(func())20 Cleanup(func())
21-}) *Browse {21+}) *BrowseService {
22 if helper, ok := t.(interface{ Helper() }); ok {22 if helper, ok := t.(interface{ Helper() }); ok {
23 helper.Helper()23 helper.Helper()
24 }24 }
25 25
26- mock := &Browse{}26+ mock := &BrowseService{}
27 mock.Mock.Test(t)27 mock.Mock.Test(t)
28 28
29 t.Cleanup(func() {29 t.Cleanup(func() {
@@ -36,21 +36,21 @@ func NewBrowse(t interface {
36 return mock36 return mock
37 }37 }
38 38
39-// Browse is an autogenerated mock type for the Browse type39+// BrowseService is an autogenerated mock type for the BrowseService type
40-type Browse struct {40+type BrowseService struct {
41 mock.Mock41 mock.Mock
42 }42 }
43 43
44-type Browse_Expecter struct {44+type BrowseService_Expecter struct {
45 mock *mock.Mock45 mock *mock.Mock
46 }46 }
47 47
48-func (_m *Browse) EXPECT() *Browse_Expecter {48+func (_m *BrowseService) EXPECT() *BrowseService_Expecter {
49- return &Browse_Expecter{mock: &_m.Mock}49+ return &BrowseService_Expecter{mock: &_m.Mock}
50 }50 }
51 51
52-// GetCommit provides a mock function for the type Browse52+// GetCommit provides a mock function for the type BrowseService
53-func (_mock *Browse) GetCommit(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision) (domain.CommitDetail, error) {53+func (_mock *BrowseService) GetCommit(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision) (domain.CommitDetail, error) {
54 ret := _mock.Called(ctx, p, repo, rev)54 ret := _mock.Called(ctx, p, repo, rev)
55 55
56 if len(ret) == 0 {56 if len(ret) == 0 {
@@ -75,8 +75,8 @@ func (_mock *Browse) GetCommit(ctx context.Context, p domain.Principal, repo dom
75 return r0, r175 return r0, r1
76 }76 }
77 77
78-// Browse_GetCommit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetCommit'78+// BrowseService_GetCommit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetCommit'
79-type Browse_GetCommit_Call struct {79+type BrowseService_GetCommit_Call struct {
80 *mock.Call80 *mock.Call
81 }81 }
82 82
@@ -85,11 +85,11 @@ type Browse_GetCommit_Call struct {
85 // - p domain.Principal85 // - p domain.Principal
86 // - repo domain.RepoName86 // - repo domain.RepoName
87 // - rev domain.Revision87 // - rev domain.Revision
88-func (_e *Browse_Expecter) GetCommit(ctx any, p any, repo any, rev any) *Browse_GetCommit_Call {88+func (_e *BrowseService_Expecter) GetCommit(ctx any, p any, repo any, rev any) *BrowseService_GetCommit_Call {
89- return &Browse_GetCommit_Call{Call: _e.mock.On("GetCommit", ctx, p, repo, rev)}89+ return &BrowseService_GetCommit_Call{Call: _e.mock.On("GetCommit", ctx, p, repo, rev)}
90 }90 }
91 91
92-func (_c *Browse_GetCommit_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision)) *Browse_GetCommit_Call {92+func (_c *BrowseService_GetCommit_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision)) *BrowseService_GetCommit_Call {
93 _c.Call.Run(func(args mock.Arguments) {93 _c.Call.Run(func(args mock.Arguments) {
94 var arg0 context.Context94 var arg0 context.Context
95 if args[0] != nil {95 if args[0] != nil {
@@ -117,18 +117,18 @@ func (_c *Browse_GetCommit_Call) Run(run func(ctx context.Context, p domain.Prin
117 return _c117 return _c
118 }118 }
119 119
120-func (_c *Browse_GetCommit_Call) Return(commitDetail domain.CommitDetail, err error) *Browse_GetCommit_Call {120+func (_c *BrowseService_GetCommit_Call) Return(commitDetail domain.CommitDetail, err error) *BrowseService_GetCommit_Call {
121 _c.Call.Return(commitDetail, err)121 _c.Call.Return(commitDetail, err)
122 return _c122 return _c
123 }123 }
124 124
125-func (_c *Browse_GetCommit_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision) (domain.CommitDetail, error)) *Browse_GetCommit_Call {125+func (_c *BrowseService_GetCommit_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision) (domain.CommitDetail, error)) *BrowseService_GetCommit_Call {
126 _c.Call.Return(run)126 _c.Call.Return(run)
127 return _c127 return _c
128 }128 }
129 129
130-// GetTree provides a mock function for the type Browse130+// GetTree provides a mock function for the type BrowseService
131-func (_mock *Browse) GetTree(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, path domain.TreePath, includeLastCommit bool) (domain.Tree, error) {131+func (_mock *BrowseService) GetTree(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, path domain.TreePath, includeLastCommit bool) (domain.Tree, error) {
132 ret := _mock.Called(ctx, p, repo, rev, path, includeLastCommit)132 ret := _mock.Called(ctx, p, repo, rev, path, includeLastCommit)
133 133
134 if len(ret) == 0 {134 if len(ret) == 0 {
@@ -153,8 +153,8 @@ func (_mock *Browse) GetTree(ctx context.Context, p domain.Principal, repo domai
153 return r0, r1153 return r0, r1
154 }154 }
155 155
156-// Browse_GetTree_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetTree'156+// BrowseService_GetTree_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetTree'
157-type Browse_GetTree_Call struct {157+type BrowseService_GetTree_Call struct {
158 *mock.Call158 *mock.Call
159 }159 }
160 160
@@ -165,11 +165,11 @@ type Browse_GetTree_Call struct {
165 // - rev domain.Revision165 // - rev domain.Revision
166 // - path domain.TreePath166 // - path domain.TreePath
167 // - includeLastCommit bool167 // - includeLastCommit bool
168-func (_e *Browse_Expecter) GetTree(ctx any, p any, repo any, rev any, path any, includeLastCommit any) *Browse_GetTree_Call {168+func (_e *BrowseService_Expecter) GetTree(ctx any, p any, repo any, rev any, path any, includeLastCommit any) *BrowseService_GetTree_Call {
169- return &Browse_GetTree_Call{Call: _e.mock.On("GetTree", ctx, p, repo, rev, path, includeLastCommit)}169+ return &BrowseService_GetTree_Call{Call: _e.mock.On("GetTree", ctx, p, repo, rev, path, includeLastCommit)}
170 }170 }
171 171
172-func (_c *Browse_GetTree_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, path domain.TreePath, includeLastCommit bool)) *Browse_GetTree_Call {172+func (_c *BrowseService_GetTree_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, path domain.TreePath, includeLastCommit bool)) *BrowseService_GetTree_Call {
173 _c.Call.Run(func(args mock.Arguments) {173 _c.Call.Run(func(args mock.Arguments) {
174 var arg0 context.Context174 var arg0 context.Context
175 if args[0] != nil {175 if args[0] != nil {
@@ -207,35 +207,35 @@ func (_c *Browse_GetTree_Call) Run(run func(ctx context.Context, p domain.Princi
207 return _c207 return _c
208 }208 }
209 209
210-func (_c *Browse_GetTree_Call) Return(tree domain.Tree, err error) *Browse_GetTree_Call {210+func (_c *BrowseService_GetTree_Call) Return(tree domain.Tree, err error) *BrowseService_GetTree_Call {
211 _c.Call.Return(tree, err)211 _c.Call.Return(tree, err)
212 return _c212 return _c
213 }213 }
214 214
215-func (_c *Browse_GetTree_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, path domain.TreePath, includeLastCommit bool) (domain.Tree, error)) *Browse_GetTree_Call {215+func (_c *BrowseService_GetTree_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, path domain.TreePath, includeLastCommit bool) (domain.Tree, error)) *BrowseService_GetTree_Call {
216 _c.Call.Return(run)216 _c.Call.Return(run)
217 return _c217 return _c
218 }218 }
219 219
220-// ListCommits provides a mock function for the type Browse220+// ListCommits provides a mock function for the type BrowseService
221-func (_mock *Browse) ListCommits(ctx context.Context, p domain.Principal, repo domain.RepoName, params app.ListCommitsParams) (app.CommitPage, error) {221+func (_mock *BrowseService) ListCommits(ctx context.Context, p domain.Principal, repo domain.RepoName, params ports.ListCommitsParams) (ports.HistoryPage, error) {
222 ret := _mock.Called(ctx, p, repo, params)222 ret := _mock.Called(ctx, p, repo, params)
223 223
224 if len(ret) == 0 {224 if len(ret) == 0 {
225 panic("no return value specified for ListCommits")225 panic("no return value specified for ListCommits")
226 }226 }
227 227
228- var r0 app.CommitPage228+ var r0 ports.HistoryPage
229 var r1 error229 var r1 error
230- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, app.ListCommitsParams) (app.CommitPage, error)); ok {230+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, ports.ListCommitsParams) (ports.HistoryPage, error)); ok {
231 return returnFunc(ctx, p, repo, params)231 return returnFunc(ctx, p, repo, params)
232 }232 }
233- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, app.ListCommitsParams) app.CommitPage); ok {233+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, ports.ListCommitsParams) ports.HistoryPage); ok {
234 r0 = returnFunc(ctx, p, repo, params)234 r0 = returnFunc(ctx, p, repo, params)
235 } else {235 } else {
236- r0 = ret.Get(0).(app.CommitPage)236+ r0 = ret.Get(0).(ports.HistoryPage)
237 }237 }
238- if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, app.ListCommitsParams) error); ok {238+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, ports.ListCommitsParams) error); ok {
239 r1 = returnFunc(ctx, p, repo, params)239 r1 = returnFunc(ctx, p, repo, params)
240 } else {240 } else {
241 r1 = ret.Error(1)241 r1 = ret.Error(1)
@@ -243,8 +243,8 @@ func (_mock *Browse) ListCommits(ctx context.Context, p domain.Principal, repo d
243 return r0, r1243 return r0, r1
244 }244 }
245 245
246-// Browse_ListCommits_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListCommits'246+// BrowseService_ListCommits_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListCommits'
247-type Browse_ListCommits_Call struct {247+type BrowseService_ListCommits_Call struct {
248 *mock.Call248 *mock.Call
249 }249 }
250 250
@@ -252,12 +252,12 @@ type Browse_ListCommits_Call struct {
252 // - ctx context.Context252 // - ctx context.Context
253 // - p domain.Principal253 // - p domain.Principal
254 // - repo domain.RepoName254 // - repo domain.RepoName
255-// - params app.ListCommitsParams255+// - params ports.ListCommitsParams
256-func (_e *Browse_Expecter) ListCommits(ctx any, p any, repo any, params any) *Browse_ListCommits_Call {256+func (_e *BrowseService_Expecter) ListCommits(ctx any, p any, repo any, params any) *BrowseService_ListCommits_Call {
257- return &Browse_ListCommits_Call{Call: _e.mock.On("ListCommits", ctx, p, repo, params)}257+ return &BrowseService_ListCommits_Call{Call: _e.mock.On("ListCommits", ctx, p, repo, params)}
258 }258 }
259 259
260-func (_c *Browse_ListCommits_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, params app.ListCommitsParams)) *Browse_ListCommits_Call {260+func (_c *BrowseService_ListCommits_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, params ports.ListCommitsParams)) *BrowseService_ListCommits_Call {
261 _c.Call.Run(func(args mock.Arguments) {261 _c.Call.Run(func(args mock.Arguments) {
262 var arg0 context.Context262 var arg0 context.Context
263 if args[0] != nil {263 if args[0] != nil {
@@ -271,9 +271,9 @@ func (_c *Browse_ListCommits_Call) Run(run func(ctx context.Context, p domain.Pr
271 if args[2] != nil {271 if args[2] != nil {
272 arg2 = args[2].(domain.RepoName)272 arg2 = args[2].(domain.RepoName)
273 }273 }
274- var arg3 app.ListCommitsParams274+ var arg3 ports.ListCommitsParams
275 if args[3] != nil {275 if args[3] != nil {
276- arg3 = args[3].(app.ListCommitsParams)276+ arg3 = args[3].(ports.ListCommitsParams)
277 }277 }
278 run(278 run(
279 arg0,279 arg0,
@@ -285,35 +285,35 @@ func (_c *Browse_ListCommits_Call) Run(run func(ctx context.Context, p domain.Pr
285 return _c285 return _c
286 }286 }
287 287
288-func (_c *Browse_ListCommits_Call) Return(commitPage app.CommitPage, err error) *Browse_ListCommits_Call {288+func (_c *BrowseService_ListCommits_Call) Return(historyPage ports.HistoryPage, err error) *BrowseService_ListCommits_Call {
289- _c.Call.Return(commitPage, err)289+ _c.Call.Return(historyPage, err)
290 return _c290 return _c
291 }291 }
292 292
293-func (_c *Browse_ListCommits_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, params app.ListCommitsParams) (app.CommitPage, error)) *Browse_ListCommits_Call {293+func (_c *BrowseService_ListCommits_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, params ports.ListCommitsParams) (ports.HistoryPage, error)) *BrowseService_ListCommits_Call {
294 _c.Call.Return(run)294 _c.Call.Return(run)
295 return _c295 return _c
296 }296 }
297 297
298-// ListRefs provides a mock function for the type Browse298+// ListRefs provides a mock function for the type BrowseService
299-func (_mock *Browse) ListRefs(ctx context.Context, p domain.Principal, repo domain.RepoName, params app.ListRefsParams) (app.RefPage, error) {299+func (_mock *BrowseService) ListRefs(ctx context.Context, p domain.Principal, repo domain.RepoName, params ports.ListRefsParams) (ports.RefPage, error) {
300 ret := _mock.Called(ctx, p, repo, params)300 ret := _mock.Called(ctx, p, repo, params)
301 301
302 if len(ret) == 0 {302 if len(ret) == 0 {
303 panic("no return value specified for ListRefs")303 panic("no return value specified for ListRefs")
304 }304 }
305 305
306- var r0 app.RefPage306+ var r0 ports.RefPage
307 var r1 error307 var r1 error
308- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, app.ListRefsParams) (app.RefPage, error)); ok {308+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, ports.ListRefsParams) (ports.RefPage, error)); ok {
309 return returnFunc(ctx, p, repo, params)309 return returnFunc(ctx, p, repo, params)
310 }310 }
311- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, app.ListRefsParams) app.RefPage); ok {311+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, ports.ListRefsParams) ports.RefPage); ok {
312 r0 = returnFunc(ctx, p, repo, params)312 r0 = returnFunc(ctx, p, repo, params)
313 } else {313 } else {
314- r0 = ret.Get(0).(app.RefPage)314+ r0 = ret.Get(0).(ports.RefPage)
315 }315 }
316- if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, app.ListRefsParams) error); ok {316+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, ports.ListRefsParams) error); ok {
317 r1 = returnFunc(ctx, p, repo, params)317 r1 = returnFunc(ctx, p, repo, params)
318 } else {318 } else {
319 r1 = ret.Error(1)319 r1 = ret.Error(1)
@@ -321,8 +321,8 @@ func (_mock *Browse) ListRefs(ctx context.Context, p domain.Principal, repo doma
321 return r0, r1321 return r0, r1
322 }322 }
323 323
324-// Browse_ListRefs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListRefs'324+// BrowseService_ListRefs_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListRefs'
325-type Browse_ListRefs_Call struct {325+type BrowseService_ListRefs_Call struct {
326 *mock.Call326 *mock.Call
327 }327 }
328 328
@@ -330,12 +330,12 @@ type Browse_ListRefs_Call struct {
330 // - ctx context.Context330 // - ctx context.Context
331 // - p domain.Principal331 // - p domain.Principal
332 // - repo domain.RepoName332 // - repo domain.RepoName
333-// - params app.ListRefsParams333+// - params ports.ListRefsParams
334-func (_e *Browse_Expecter) ListRefs(ctx any, p any, repo any, params any) *Browse_ListRefs_Call {334+func (_e *BrowseService_Expecter) ListRefs(ctx any, p any, repo any, params any) *BrowseService_ListRefs_Call {
335- return &Browse_ListRefs_Call{Call: _e.mock.On("ListRefs", ctx, p, repo, params)}335+ return &BrowseService_ListRefs_Call{Call: _e.mock.On("ListRefs", ctx, p, repo, params)}
336 }336 }
337 337
338-func (_c *Browse_ListRefs_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, params app.ListRefsParams)) *Browse_ListRefs_Call {338+func (_c *BrowseService_ListRefs_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, params ports.ListRefsParams)) *BrowseService_ListRefs_Call {
339 _c.Call.Run(func(args mock.Arguments) {339 _c.Call.Run(func(args mock.Arguments) {
340 var arg0 context.Context340 var arg0 context.Context
341 if args[0] != nil {341 if args[0] != nil {
@@ -349,9 +349,9 @@ func (_c *Browse_ListRefs_Call) Run(run func(ctx context.Context, p domain.Princ
349 if args[2] != nil {349 if args[2] != nil {
350 arg2 = args[2].(domain.RepoName)350 arg2 = args[2].(domain.RepoName)
351 }351 }
352- var arg3 app.ListRefsParams352+ var arg3 ports.ListRefsParams
353 if args[3] != nil {353 if args[3] != nil {
354- arg3 = args[3].(app.ListRefsParams)354+ arg3 = args[3].(ports.ListRefsParams)
355 }355 }
356 run(356 run(
357 arg0,357 arg0,
@@ -363,18 +363,18 @@ func (_c *Browse_ListRefs_Call) Run(run func(ctx context.Context, p domain.Princ
363 return _c363 return _c
364 }364 }
365 365
366-func (_c *Browse_ListRefs_Call) Return(refPage app.RefPage, err error) *Browse_ListRefs_Call {366+func (_c *BrowseService_ListRefs_Call) Return(refPage ports.RefPage, err error) *BrowseService_ListRefs_Call {
367 _c.Call.Return(refPage, err)367 _c.Call.Return(refPage, err)
368 return _c368 return _c
369 }369 }
370 370
371-func (_c *Browse_ListRefs_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, params app.ListRefsParams) (app.RefPage, error)) *Browse_ListRefs_Call {371+func (_c *BrowseService_ListRefs_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, params ports.ListRefsParams) (ports.RefPage, error)) *BrowseService_ListRefs_Call {
372 _c.Call.Return(run)372 _c.Call.Return(run)
373 return _c373 return _c
374 }374 }
375 375
376-// OpenBlob provides a mock function for the type Browse376+// OpenBlob provides a mock function for the type BrowseService
377-func (_mock *Browse) OpenBlob(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, path domain.TreePath) (domain.BlobInfo, io.ReadCloser, error) {377+func (_mock *BrowseService) OpenBlob(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, path domain.TreePath) (domain.BlobInfo, io.ReadCloser, error) {
378 ret := _mock.Called(ctx, p, repo, rev, path)378 ret := _mock.Called(ctx, p, repo, rev, path)
379 379
380 if len(ret) == 0 {380 if len(ret) == 0 {
@@ -407,8 +407,8 @@ func (_mock *Browse) OpenBlob(ctx context.Context, p domain.Principal, repo doma
407 return r0, r1, r2407 return r0, r1, r2
408 }408 }
409 409
410-// Browse_OpenBlob_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OpenBlob'410+// BrowseService_OpenBlob_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'OpenBlob'
411-type Browse_OpenBlob_Call struct {411+type BrowseService_OpenBlob_Call struct {
412 *mock.Call412 *mock.Call
413 }413 }
414 414
@@ -418,11 +418,11 @@ type Browse_OpenBlob_Call struct {
418 // - repo domain.RepoName418 // - repo domain.RepoName
419 // - rev domain.Revision419 // - rev domain.Revision
420 // - path domain.TreePath420 // - path domain.TreePath
421-func (_e *Browse_Expecter) OpenBlob(ctx any, p any, repo any, rev any, path any) *Browse_OpenBlob_Call {421+func (_e *BrowseService_Expecter) OpenBlob(ctx any, p any, repo any, rev any, path any) *BrowseService_OpenBlob_Call {
422- return &Browse_OpenBlob_Call{Call: _e.mock.On("OpenBlob", ctx, p, repo, rev, path)}422+ return &BrowseService_OpenBlob_Call{Call: _e.mock.On("OpenBlob", ctx, p, repo, rev, path)}
423 }423 }
424 424
425-func (_c *Browse_OpenBlob_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, path domain.TreePath)) *Browse_OpenBlob_Call {425+func (_c *BrowseService_OpenBlob_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, path domain.TreePath)) *BrowseService_OpenBlob_Call {
426 _c.Call.Run(func(args mock.Arguments) {426 _c.Call.Run(func(args mock.Arguments) {
427 var arg0 context.Context427 var arg0 context.Context
428 if args[0] != nil {428 if args[0] != nil {
@@ -455,18 +455,18 @@ func (_c *Browse_OpenBlob_Call) Run(run func(ctx context.Context, p domain.Princ
455 return _c455 return _c
456 }456 }
457 457
458-func (_c *Browse_OpenBlob_Call) Return(blobInfo domain.BlobInfo, readCloser io.ReadCloser, err error) *Browse_OpenBlob_Call {458+func (_c *BrowseService_OpenBlob_Call) Return(blobInfo domain.BlobInfo, readCloser io.ReadCloser, err error) *BrowseService_OpenBlob_Call {
459 _c.Call.Return(blobInfo, readCloser, err)459 _c.Call.Return(blobInfo, readCloser, err)
460 return _c460 return _c
461 }461 }
462 462
463-func (_c *Browse_OpenBlob_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, path domain.TreePath) (domain.BlobInfo, io.ReadCloser, error)) *Browse_OpenBlob_Call {463+func (_c *BrowseService_OpenBlob_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, path domain.TreePath) (domain.BlobInfo, io.ReadCloser, error)) *BrowseService_OpenBlob_Call {
464 _c.Call.Return(run)464 _c.Call.Return(run)
465 return _c465 return _c
466 }466 }
467 467
468-// ResolveRevision provides a mock function for the type Browse468+// ResolveRevision provides a mock function for the type BrowseService
469-func (_mock *Browse) ResolveRevision(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision) (domain.ResolvedRevision, error) {469+func (_mock *BrowseService) ResolveRevision(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision) (domain.ResolvedRevision, error) {
470 ret := _mock.Called(ctx, p, repo, rev)470 ret := _mock.Called(ctx, p, repo, rev)
471 471
472 if len(ret) == 0 {472 if len(ret) == 0 {
@@ -491,8 +491,8 @@ func (_mock *Browse) ResolveRevision(ctx context.Context, p domain.Principal, re
491 return r0, r1491 return r0, r1
492 }492 }
493 493
494-// Browse_ResolveRevision_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ResolveRevision'494+// BrowseService_ResolveRevision_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ResolveRevision'
495-type Browse_ResolveRevision_Call struct {495+type BrowseService_ResolveRevision_Call struct {
496 *mock.Call496 *mock.Call
497 }497 }
498 498
@@ -501,11 +501,11 @@ type Browse_ResolveRevision_Call struct {
501 // - p domain.Principal501 // - p domain.Principal
502 // - repo domain.RepoName502 // - repo domain.RepoName
503 // - rev domain.Revision503 // - rev domain.Revision
504-func (_e *Browse_Expecter) ResolveRevision(ctx any, p any, repo any, rev any) *Browse_ResolveRevision_Call {504+func (_e *BrowseService_Expecter) ResolveRevision(ctx any, p any, repo any, rev any) *BrowseService_ResolveRevision_Call {
505- return &Browse_ResolveRevision_Call{Call: _e.mock.On("ResolveRevision", ctx, p, repo, rev)}505+ return &BrowseService_ResolveRevision_Call{Call: _e.mock.On("ResolveRevision", ctx, p, repo, rev)}
506 }506 }
507 507
508-func (_c *Browse_ResolveRevision_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision)) *Browse_ResolveRevision_Call {508+func (_c *BrowseService_ResolveRevision_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision)) *BrowseService_ResolveRevision_Call {
509 _c.Call.Run(func(args mock.Arguments) {509 _c.Call.Run(func(args mock.Arguments) {
510 var arg0 context.Context510 var arg0 context.Context
511 if args[0] != nil {511 if args[0] != nil {
@@ -533,12 +533,12 @@ func (_c *Browse_ResolveRevision_Call) Run(run func(ctx context.Context, p domai
533 return _c533 return _c
534 }534 }
535 535
536-func (_c *Browse_ResolveRevision_Call) Return(resolvedRevision domain.ResolvedRevision, err error) *Browse_ResolveRevision_Call {536+func (_c *BrowseService_ResolveRevision_Call) Return(resolvedRevision domain.ResolvedRevision, err error) *BrowseService_ResolveRevision_Call {
537 _c.Call.Return(resolvedRevision, err)537 _c.Call.Return(resolvedRevision, err)
538 return _c538 return _c
539 }539 }
540 540
541-func (_c *Browse_ResolveRevision_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision) (domain.ResolvedRevision, error)) *Browse_ResolveRevision_Call {541+func (_c *BrowseService_ResolveRevision_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision) (domain.ResolvedRevision, error)) *BrowseService_ResolveRevision_Call {
542 _c.Call.Return(run)542 _c.Call.Return(run)
543 return _c543 return _c
544 }544 }
A internal/ports/mocks/EventService.go +118 -0
@@ -0,0 +1,118 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package mocks
6+
7+import (
8+ "context"
9+
10+ "bnema.dev/ermite/internal/domain"
11+ "bnema.dev/ermite/internal/ports"
12+ mock "github.com/stretchr/testify/mock"
13+)
14+
15+// NewEventService creates a new instance of EventService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
16+// The first argument is typically a *testing.T value.
17+func NewEventService(t interface {
18+ mock.TestingT
19+ Cleanup(func())
20+}) *EventService {
21+ if helper, ok := t.(interface{ Helper() }); ok {
22+ helper.Helper()
23+ }
24+
25+ mock := &EventService{}
26+ mock.Mock.Test(t)
27+
28+ t.Cleanup(func() {
29+ if helper, ok := t.(interface{ Helper() }); ok {
30+ helper.Helper()
31+ }
32+ mock.AssertExpectations(t)
33+ })
34+
35+ return mock
36+}
37+
38+// EventService is an autogenerated mock type for the EventService type
39+type EventService struct {
40+ mock.Mock
41+}
42+
43+type EventService_Expecter struct {
44+ mock *mock.Mock
45+}
46+
47+func (_m *EventService) EXPECT() *EventService_Expecter {
48+ return &EventService_Expecter{mock: &_m.Mock}
49+}
50+
51+// Watch provides a mock function for the type EventService
52+func (_mock *EventService) Watch(ctx context.Context, p domain.Principal, params ports.WatchParams, send func(ports.WatchEvent) error) error {
53+ ret := _mock.Called(ctx, p, params, send)
54+
55+ if len(ret) == 0 {
56+ panic("no return value specified for Watch")
57+ }
58+
59+ var r0 error
60+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, ports.WatchParams, func(ports.WatchEvent) error) error); ok {
61+ r0 = returnFunc(ctx, p, params, send)
62+ } else {
63+ r0 = ret.Error(0)
64+ }
65+ return r0
66+}
67+
68+// EventService_Watch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Watch'
69+type EventService_Watch_Call struct {
70+ *mock.Call
71+}
72+
73+// Watch is a helper method to define mock.On call
74+// - ctx context.Context
75+// - p domain.Principal
76+// - params ports.WatchParams
77+// - send func(ports.WatchEvent) error
78+func (_e *EventService_Expecter) Watch(ctx any, p any, params any, send any) *EventService_Watch_Call {
79+ return &EventService_Watch_Call{Call: _e.mock.On("Watch", ctx, p, params, send)}
80+}
81+
82+func (_c *EventService_Watch_Call) Run(run func(ctx context.Context, p domain.Principal, params ports.WatchParams, send func(ports.WatchEvent) error)) *EventService_Watch_Call {
83+ _c.Call.Run(func(args mock.Arguments) {
84+ var arg0 context.Context
85+ if args[0] != nil {
86+ arg0 = args[0].(context.Context)
87+ }
88+ var arg1 domain.Principal
89+ if args[1] != nil {
90+ arg1 = args[1].(domain.Principal)
91+ }
92+ var arg2 ports.WatchParams
93+ if args[2] != nil {
94+ arg2 = args[2].(ports.WatchParams)
95+ }
96+ var arg3 func(ports.WatchEvent) error
97+ if args[3] != nil {
98+ arg3 = args[3].(func(ports.WatchEvent) error)
99+ }
100+ run(
101+ arg0,
102+ arg1,
103+ arg2,
104+ arg3,
105+ )
106+ })
107+ return _c
108+}
109+
110+func (_c *EventService_Watch_Call) Return(err error) *EventService_Watch_Call {
111+ _c.Call.Return(err)
112+ return _c
113+}
114+
115+func (_c *EventService_Watch_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, params ports.WatchParams, send func(ports.WatchEvent) error) error) *EventService_Watch_Call {
116+ _c.Call.Return(run)
117+ return _c
118+}
R internal/adapters/githttp/mocks/FailureLimiter.go → internal/ports/mocks/FailureLimiter.go +0 -0 renamed, 100% similar

renamed, 100% similar

R internal/adapters/githttp/mocks/HookTokens.go → internal/ports/mocks/HookTokens.go +0 -0 renamed, 100% similar

renamed, 100% similar

R internal/app/mocks/MaintenanceScheduler.go → internal/ports/mocks/MaintenanceScheduler.go +0 -0 renamed, 100% similar

renamed, 100% similar

R internal/adapters/rpc/mocks/MergeRequests.go → internal/ports/mocks/MergeRequestService.go +114 -114 renamed, 50% similar
@@ -7,22 +7,22 @@ package mocks
7 import (7 import (
8 "context"8 "context"
9 9
10- "bnema.dev/ermite/internal/app"
11 "bnema.dev/ermite/internal/domain"10 "bnema.dev/ermite/internal/domain"
11+ "bnema.dev/ermite/internal/ports"
12 mock "github.com/stretchr/testify/mock"12 mock "github.com/stretchr/testify/mock"
13 )13 )
14 14
15-// NewMergeRequests creates a new instance of MergeRequests. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.15+// NewMergeRequestService creates a new instance of MergeRequestService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
16 // The first argument is typically a *testing.T value.16 // The first argument is typically a *testing.T value.
17-func NewMergeRequests(t interface {17+func NewMergeRequestService(t interface {
18 mock.TestingT18 mock.TestingT
19 Cleanup(func())19 Cleanup(func())
20-}) *MergeRequests {20+}) *MergeRequestService {
21 if helper, ok := t.(interface{ Helper() }); ok {21 if helper, ok := t.(interface{ Helper() }); ok {
22 helper.Helper()22 helper.Helper()
23 }23 }
24 24
25- mock := &MergeRequests{}25+ mock := &MergeRequestService{}
26 mock.Mock.Test(t)26 mock.Mock.Test(t)
27 27
28 t.Cleanup(func() {28 t.Cleanup(func() {
@@ -35,21 +35,21 @@ func NewMergeRequests(t interface {
35 return mock35 return mock
36 }36 }
37 37
38-// MergeRequests is an autogenerated mock type for the MergeRequests type38+// MergeRequestService is an autogenerated mock type for the MergeRequestService type
39-type MergeRequests struct {39+type MergeRequestService struct {
40 mock.Mock40 mock.Mock
41 }41 }
42 42
43-type MergeRequests_Expecter struct {43+type MergeRequestService_Expecter struct {
44 mock *mock.Mock44 mock *mock.Mock
45 }45 }
46 46
47-func (_m *MergeRequests) EXPECT() *MergeRequests_Expecter {47+func (_m *MergeRequestService) EXPECT() *MergeRequestService_Expecter {
48- return &MergeRequests_Expecter{mock: &_m.Mock}48+ return &MergeRequestService_Expecter{mock: &_m.Mock}
49 }49 }
50 50
51-// AddComment provides a mock function for the type MergeRequests51+// AddComment provides a mock function for the type MergeRequestService
52-func (_mock *MergeRequests) AddComment(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, params app.AddCommentParams) (domain.Comment, error) {52+func (_mock *MergeRequestService) AddComment(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, params ports.AddCommentParams) (domain.Comment, error) {
53 ret := _mock.Called(ctx, p, name, number, params)53 ret := _mock.Called(ctx, p, name, number, params)
54 54
55 if len(ret) == 0 {55 if len(ret) == 0 {
@@ -58,15 +58,15 @@ func (_mock *MergeRequests) AddComment(ctx context.Context, p domain.Principal,
58 58
59 var r0 domain.Comment59 var r0 domain.Comment
60 var r1 error60 var r1 error
61- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, int64, app.AddCommentParams) (domain.Comment, error)); ok {61+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, int64, ports.AddCommentParams) (domain.Comment, error)); ok {
62 return returnFunc(ctx, p, name, number, params)62 return returnFunc(ctx, p, name, number, params)
63 }63 }
64- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, int64, app.AddCommentParams) domain.Comment); ok {64+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, int64, ports.AddCommentParams) domain.Comment); ok {
65 r0 = returnFunc(ctx, p, name, number, params)65 r0 = returnFunc(ctx, p, name, number, params)
66 } else {66 } else {
67 r0 = ret.Get(0).(domain.Comment)67 r0 = ret.Get(0).(domain.Comment)
68 }68 }
69- if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, int64, app.AddCommentParams) error); ok {69+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, int64, ports.AddCommentParams) error); ok {
70 r1 = returnFunc(ctx, p, name, number, params)70 r1 = returnFunc(ctx, p, name, number, params)
71 } else {71 } else {
72 r1 = ret.Error(1)72 r1 = ret.Error(1)
@@ -74,8 +74,8 @@ func (_mock *MergeRequests) AddComment(ctx context.Context, p domain.Principal,
74 return r0, r174 return r0, r1
75 }75 }
76 76
77-// MergeRequests_AddComment_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddComment'77+// MergeRequestService_AddComment_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddComment'
78-type MergeRequests_AddComment_Call struct {78+type MergeRequestService_AddComment_Call struct {
79 *mock.Call79 *mock.Call
80 }80 }
81 81
@@ -84,12 +84,12 @@ type MergeRequests_AddComment_Call struct {
84 // - p domain.Principal84 // - p domain.Principal
85 // - name domain.RepoName85 // - name domain.RepoName
86 // - number int6486 // - number int64
87-// - params app.AddCommentParams87+// - params ports.AddCommentParams
88-func (_e *MergeRequests_Expecter) AddComment(ctx any, p any, name any, number any, params any) *MergeRequests_AddComment_Call {88+func (_e *MergeRequestService_Expecter) AddComment(ctx any, p any, name any, number any, params any) *MergeRequestService_AddComment_Call {
89- return &MergeRequests_AddComment_Call{Call: _e.mock.On("AddComment", ctx, p, name, number, params)}89+ return &MergeRequestService_AddComment_Call{Call: _e.mock.On("AddComment", ctx, p, name, number, params)}
90 }90 }
91 91
92-func (_c *MergeRequests_AddComment_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, params app.AddCommentParams)) *MergeRequests_AddComment_Call {92+func (_c *MergeRequestService_AddComment_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, params ports.AddCommentParams)) *MergeRequestService_AddComment_Call {
93 _c.Call.Run(func(args mock.Arguments) {93 _c.Call.Run(func(args mock.Arguments) {
94 var arg0 context.Context94 var arg0 context.Context
95 if args[0] != nil {95 if args[0] != nil {
@@ -107,9 +107,9 @@ func (_c *MergeRequests_AddComment_Call) Run(run func(ctx context.Context, p dom
107 if args[3] != nil {107 if args[3] != nil {
108 arg3 = args[3].(int64)108 arg3 = args[3].(int64)
109 }109 }
110- var arg4 app.AddCommentParams110+ var arg4 ports.AddCommentParams
111 if args[4] != nil {111 if args[4] != nil {
112- arg4 = args[4].(app.AddCommentParams)112+ arg4 = args[4].(ports.AddCommentParams)
113 }113 }
114 run(114 run(
115 arg0,115 arg0,
@@ -122,18 +122,18 @@ func (_c *MergeRequests_AddComment_Call) Run(run func(ctx context.Context, p dom
122 return _c122 return _c
123 }123 }
124 124
125-func (_c *MergeRequests_AddComment_Call) Return(comment domain.Comment, err error) *MergeRequests_AddComment_Call {125+func (_c *MergeRequestService_AddComment_Call) Return(comment domain.Comment, err error) *MergeRequestService_AddComment_Call {
126 _c.Call.Return(comment, err)126 _c.Call.Return(comment, err)
127 return _c127 return _c
128 }128 }
129 129
130-func (_c *MergeRequests_AddComment_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, params app.AddCommentParams) (domain.Comment, error)) *MergeRequests_AddComment_Call {130+func (_c *MergeRequestService_AddComment_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, params ports.AddCommentParams) (domain.Comment, error)) *MergeRequestService_AddComment_Call {
131 _c.Call.Return(run)131 _c.Call.Return(run)
132 return _c132 return _c
133 }133 }
134 134
135-// Close provides a mock function for the type MergeRequests135+// Close provides a mock function for the type MergeRequestService
136-func (_mock *MergeRequests) Close(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (domain.MergeRequest, error) {136+func (_mock *MergeRequestService) Close(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (domain.MergeRequest, error) {
137 ret := _mock.Called(ctx, p, name, number)137 ret := _mock.Called(ctx, p, name, number)
138 138
139 if len(ret) == 0 {139 if len(ret) == 0 {
@@ -158,8 +158,8 @@ func (_mock *MergeRequests) Close(ctx context.Context, p domain.Principal, name
158 return r0, r1158 return r0, r1
159 }159 }
160 160
161-// MergeRequests_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close'161+// MergeRequestService_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close'
162-type MergeRequests_Close_Call struct {162+type MergeRequestService_Close_Call struct {
163 *mock.Call163 *mock.Call
164 }164 }
165 165
@@ -168,11 +168,11 @@ type MergeRequests_Close_Call struct {
168 // - p domain.Principal168 // - p domain.Principal
169 // - name domain.RepoName169 // - name domain.RepoName
170 // - number int64170 // - number int64
171-func (_e *MergeRequests_Expecter) Close(ctx any, p any, name any, number any) *MergeRequests_Close_Call {171+func (_e *MergeRequestService_Expecter) Close(ctx any, p any, name any, number any) *MergeRequestService_Close_Call {
172- return &MergeRequests_Close_Call{Call: _e.mock.On("Close", ctx, p, name, number)}172+ return &MergeRequestService_Close_Call{Call: _e.mock.On("Close", ctx, p, name, number)}
173 }173 }
174 174
175-func (_c *MergeRequests_Close_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName, number int64)) *MergeRequests_Close_Call {175+func (_c *MergeRequestService_Close_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName, number int64)) *MergeRequestService_Close_Call {
176 _c.Call.Run(func(args mock.Arguments) {176 _c.Call.Run(func(args mock.Arguments) {
177 var arg0 context.Context177 var arg0 context.Context
178 if args[0] != nil {178 if args[0] != nil {
@@ -200,18 +200,18 @@ func (_c *MergeRequests_Close_Call) Run(run func(ctx context.Context, p domain.P
200 return _c200 return _c
201 }201 }
202 202
203-func (_c *MergeRequests_Close_Call) Return(mergeRequest domain.MergeRequest, err error) *MergeRequests_Close_Call {203+func (_c *MergeRequestService_Close_Call) Return(mergeRequest domain.MergeRequest, err error) *MergeRequestService_Close_Call {
204 _c.Call.Return(mergeRequest, err)204 _c.Call.Return(mergeRequest, err)
205 return _c205 return _c
206 }206 }
207 207
208-func (_c *MergeRequests_Close_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (domain.MergeRequest, error)) *MergeRequests_Close_Call {208+func (_c *MergeRequestService_Close_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (domain.MergeRequest, error)) *MergeRequestService_Close_Call {
209 _c.Call.Return(run)209 _c.Call.Return(run)
210 return _c210 return _c
211 }211 }
212 212
213-// Create provides a mock function for the type MergeRequests213+// Create provides a mock function for the type MergeRequestService
214-func (_mock *MergeRequests) Create(ctx context.Context, p domain.Principal, name domain.RepoName, params app.CreateMergeRequestParams) (domain.MergeRequest, error) {214+func (_mock *MergeRequestService) Create(ctx context.Context, p domain.Principal, name domain.RepoName, params ports.CreateMergeRequestParams) (domain.MergeRequest, error) {
215 ret := _mock.Called(ctx, p, name, params)215 ret := _mock.Called(ctx, p, name, params)
216 216
217 if len(ret) == 0 {217 if len(ret) == 0 {
@@ -220,15 +220,15 @@ func (_mock *MergeRequests) Create(ctx context.Context, p domain.Principal, name
220 220
221 var r0 domain.MergeRequest221 var r0 domain.MergeRequest
222 var r1 error222 var r1 error
223- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, app.CreateMergeRequestParams) (domain.MergeRequest, error)); ok {223+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, ports.CreateMergeRequestParams) (domain.MergeRequest, error)); ok {
224 return returnFunc(ctx, p, name, params)224 return returnFunc(ctx, p, name, params)
225 }225 }
226- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, app.CreateMergeRequestParams) domain.MergeRequest); ok {226+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, ports.CreateMergeRequestParams) domain.MergeRequest); ok {
227 r0 = returnFunc(ctx, p, name, params)227 r0 = returnFunc(ctx, p, name, params)
228 } else {228 } else {
229 r0 = ret.Get(0).(domain.MergeRequest)229 r0 = ret.Get(0).(domain.MergeRequest)
230 }230 }
231- if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, app.CreateMergeRequestParams) error); ok {231+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, ports.CreateMergeRequestParams) error); ok {
232 r1 = returnFunc(ctx, p, name, params)232 r1 = returnFunc(ctx, p, name, params)
233 } else {233 } else {
234 r1 = ret.Error(1)234 r1 = ret.Error(1)
@@ -236,8 +236,8 @@ func (_mock *MergeRequests) Create(ctx context.Context, p domain.Principal, name
236 return r0, r1236 return r0, r1
237 }237 }
238 238
239-// MergeRequests_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create'239+// MergeRequestService_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create'
240-type MergeRequests_Create_Call struct {240+type MergeRequestService_Create_Call struct {
241 *mock.Call241 *mock.Call
242 }242 }
243 243
@@ -245,12 +245,12 @@ type MergeRequests_Create_Call struct {
245 // - ctx context.Context245 // - ctx context.Context
246 // - p domain.Principal246 // - p domain.Principal
247 // - name domain.RepoName247 // - name domain.RepoName
248-// - params app.CreateMergeRequestParams248+// - params ports.CreateMergeRequestParams
249-func (_e *MergeRequests_Expecter) Create(ctx any, p any, name any, params any) *MergeRequests_Create_Call {249+func (_e *MergeRequestService_Expecter) Create(ctx any, p any, name any, params any) *MergeRequestService_Create_Call {
250- return &MergeRequests_Create_Call{Call: _e.mock.On("Create", ctx, p, name, params)}250+ return &MergeRequestService_Create_Call{Call: _e.mock.On("Create", ctx, p, name, params)}
251 }251 }
252 252
253-func (_c *MergeRequests_Create_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName, params app.CreateMergeRequestParams)) *MergeRequests_Create_Call {253+func (_c *MergeRequestService_Create_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName, params ports.CreateMergeRequestParams)) *MergeRequestService_Create_Call {
254 _c.Call.Run(func(args mock.Arguments) {254 _c.Call.Run(func(args mock.Arguments) {
255 var arg0 context.Context255 var arg0 context.Context
256 if args[0] != nil {256 if args[0] != nil {
@@ -264,9 +264,9 @@ func (_c *MergeRequests_Create_Call) Run(run func(ctx context.Context, p domain.
264 if args[2] != nil {264 if args[2] != nil {
265 arg2 = args[2].(domain.RepoName)265 arg2 = args[2].(domain.RepoName)
266 }266 }
267- var arg3 app.CreateMergeRequestParams267+ var arg3 ports.CreateMergeRequestParams
268 if args[3] != nil {268 if args[3] != nil {
269- arg3 = args[3].(app.CreateMergeRequestParams)269+ arg3 = args[3].(ports.CreateMergeRequestParams)
270 }270 }
271 run(271 run(
272 arg0,272 arg0,
@@ -278,33 +278,33 @@ func (_c *MergeRequests_Create_Call) Run(run func(ctx context.Context, p domain.
278 return _c278 return _c
279 }279 }
280 280
281-func (_c *MergeRequests_Create_Call) Return(mergeRequest domain.MergeRequest, err error) *MergeRequests_Create_Call {281+func (_c *MergeRequestService_Create_Call) Return(mergeRequest domain.MergeRequest, err error) *MergeRequestService_Create_Call {
282 _c.Call.Return(mergeRequest, err)282 _c.Call.Return(mergeRequest, err)
283 return _c283 return _c
284 }284 }
285 285
286-func (_c *MergeRequests_Create_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName, params app.CreateMergeRequestParams) (domain.MergeRequest, error)) *MergeRequests_Create_Call {286+func (_c *MergeRequestService_Create_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName, params ports.CreateMergeRequestParams) (domain.MergeRequest, error)) *MergeRequestService_Create_Call {
287 _c.Call.Return(run)287 _c.Call.Return(run)
288 return _c288 return _c
289 }289 }
290 290
291-// Get provides a mock function for the type MergeRequests291+// Get provides a mock function for the type MergeRequestService
292-func (_mock *MergeRequests) Get(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, sinceLastRead bool) (app.MergeRequestDetail, error) {292+func (_mock *MergeRequestService) Get(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, sinceLastRead bool) (ports.MergeRequestDetail, error) {
293 ret := _mock.Called(ctx, p, name, number, sinceLastRead)293 ret := _mock.Called(ctx, p, name, number, sinceLastRead)
294 294
295 if len(ret) == 0 {295 if len(ret) == 0 {
296 panic("no return value specified for Get")296 panic("no return value specified for Get")
297 }297 }
298 298
299- var r0 app.MergeRequestDetail299+ var r0 ports.MergeRequestDetail
300 var r1 error300 var r1 error
301- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, int64, bool) (app.MergeRequestDetail, error)); ok {301+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, int64, bool) (ports.MergeRequestDetail, error)); ok {
302 return returnFunc(ctx, p, name, number, sinceLastRead)302 return returnFunc(ctx, p, name, number, sinceLastRead)
303 }303 }
304- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, int64, bool) app.MergeRequestDetail); ok {304+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, int64, bool) ports.MergeRequestDetail); ok {
305 r0 = returnFunc(ctx, p, name, number, sinceLastRead)305 r0 = returnFunc(ctx, p, name, number, sinceLastRead)
306 } else {306 } else {
307- r0 = ret.Get(0).(app.MergeRequestDetail)307+ r0 = ret.Get(0).(ports.MergeRequestDetail)
308 }308 }
309 if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, int64, bool) error); ok {309 if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, int64, bool) error); ok {
310 r1 = returnFunc(ctx, p, name, number, sinceLastRead)310 r1 = returnFunc(ctx, p, name, number, sinceLastRead)
@@ -314,8 +314,8 @@ func (_mock *MergeRequests) Get(ctx context.Context, p domain.Principal, name do
314 return r0, r1314 return r0, r1
315 }315 }
316 316
317-// MergeRequests_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get'317+// MergeRequestService_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get'
318-type MergeRequests_Get_Call struct {318+type MergeRequestService_Get_Call struct {
319 *mock.Call319 *mock.Call
320 }320 }
321 321
@@ -325,11 +325,11 @@ type MergeRequests_Get_Call struct {
325 // - name domain.RepoName325 // - name domain.RepoName
326 // - number int64326 // - number int64
327 // - sinceLastRead bool327 // - sinceLastRead bool
328-func (_e *MergeRequests_Expecter) Get(ctx any, p any, name any, number any, sinceLastRead any) *MergeRequests_Get_Call {328+func (_e *MergeRequestService_Expecter) Get(ctx any, p any, name any, number any, sinceLastRead any) *MergeRequestService_Get_Call {
329- return &MergeRequests_Get_Call{Call: _e.mock.On("Get", ctx, p, name, number, sinceLastRead)}329+ return &MergeRequestService_Get_Call{Call: _e.mock.On("Get", ctx, p, name, number, sinceLastRead)}
330 }330 }
331 331
332-func (_c *MergeRequests_Get_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, sinceLastRead bool)) *MergeRequests_Get_Call {332+func (_c *MergeRequestService_Get_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, sinceLastRead bool)) *MergeRequestService_Get_Call {
333 _c.Call.Run(func(args mock.Arguments) {333 _c.Call.Run(func(args mock.Arguments) {
334 var arg0 context.Context334 var arg0 context.Context
335 if args[0] != nil {335 if args[0] != nil {
@@ -362,35 +362,35 @@ func (_c *MergeRequests_Get_Call) Run(run func(ctx context.Context, p domain.Pri
362 return _c362 return _c
363 }363 }
364 364
365-func (_c *MergeRequests_Get_Call) Return(mergeRequestDetail app.MergeRequestDetail, err error) *MergeRequests_Get_Call {365+func (_c *MergeRequestService_Get_Call) Return(mergeRequestDetail ports.MergeRequestDetail, err error) *MergeRequestService_Get_Call {
366 _c.Call.Return(mergeRequestDetail, err)366 _c.Call.Return(mergeRequestDetail, err)
367 return _c367 return _c
368 }368 }
369 369
370-func (_c *MergeRequests_Get_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, sinceLastRead bool) (app.MergeRequestDetail, error)) *MergeRequests_Get_Call {370+func (_c *MergeRequestService_Get_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, sinceLastRead bool) (ports.MergeRequestDetail, error)) *MergeRequestService_Get_Call {
371 _c.Call.Return(run)371 _c.Call.Return(run)
372 return _c372 return _c
373 }373 }
374 374
375-// List provides a mock function for the type MergeRequests375+// List provides a mock function for the type MergeRequestService
376-func (_mock *MergeRequests) List(ctx context.Context, p domain.Principal, name domain.RepoName, params app.ListMergeRequestsParams) (app.MergeRequestPage, error) {376+func (_mock *MergeRequestService) List(ctx context.Context, p domain.Principal, name domain.RepoName, params ports.ListMergeRequestsParams) (ports.MergeRequestPage, error) {
377 ret := _mock.Called(ctx, p, name, params)377 ret := _mock.Called(ctx, p, name, params)
378 378
379 if len(ret) == 0 {379 if len(ret) == 0 {
380 panic("no return value specified for List")380 panic("no return value specified for List")
381 }381 }
382 382
383- var r0 app.MergeRequestPage383+ var r0 ports.MergeRequestPage
384 var r1 error384 var r1 error
385- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, app.ListMergeRequestsParams) (app.MergeRequestPage, error)); ok {385+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, ports.ListMergeRequestsParams) (ports.MergeRequestPage, error)); ok {
386 return returnFunc(ctx, p, name, params)386 return returnFunc(ctx, p, name, params)
387 }387 }
388- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, app.ListMergeRequestsParams) app.MergeRequestPage); ok {388+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, ports.ListMergeRequestsParams) ports.MergeRequestPage); ok {
389 r0 = returnFunc(ctx, p, name, params)389 r0 = returnFunc(ctx, p, name, params)
390 } else {390 } else {
391- r0 = ret.Get(0).(app.MergeRequestPage)391+ r0 = ret.Get(0).(ports.MergeRequestPage)
392 }392 }
393- if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, app.ListMergeRequestsParams) error); ok {393+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, ports.ListMergeRequestsParams) error); ok {
394 r1 = returnFunc(ctx, p, name, params)394 r1 = returnFunc(ctx, p, name, params)
395 } else {395 } else {
396 r1 = ret.Error(1)396 r1 = ret.Error(1)
@@ -398,8 +398,8 @@ func (_mock *MergeRequests) List(ctx context.Context, p domain.Principal, name d
398 return r0, r1398 return r0, r1
399 }399 }
400 400
401-// MergeRequests_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List'401+// MergeRequestService_List_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'List'
402-type MergeRequests_List_Call struct {402+type MergeRequestService_List_Call struct {
403 *mock.Call403 *mock.Call
404 }404 }
405 405
@@ -407,12 +407,12 @@ type MergeRequests_List_Call struct {
407 // - ctx context.Context407 // - ctx context.Context
408 // - p domain.Principal408 // - p domain.Principal
409 // - name domain.RepoName409 // - name domain.RepoName
410-// - params app.ListMergeRequestsParams410+// - params ports.ListMergeRequestsParams
411-func (_e *MergeRequests_Expecter) List(ctx any, p any, name any, params any) *MergeRequests_List_Call {411+func (_e *MergeRequestService_Expecter) List(ctx any, p any, name any, params any) *MergeRequestService_List_Call {
412- return &MergeRequests_List_Call{Call: _e.mock.On("List", ctx, p, name, params)}412+ return &MergeRequestService_List_Call{Call: _e.mock.On("List", ctx, p, name, params)}
413 }413 }
414 414
415-func (_c *MergeRequests_List_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName, params app.ListMergeRequestsParams)) *MergeRequests_List_Call {415+func (_c *MergeRequestService_List_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName, params ports.ListMergeRequestsParams)) *MergeRequestService_List_Call {
416 _c.Call.Run(func(args mock.Arguments) {416 _c.Call.Run(func(args mock.Arguments) {
417 var arg0 context.Context417 var arg0 context.Context
418 if args[0] != nil {418 if args[0] != nil {
@@ -426,9 +426,9 @@ func (_c *MergeRequests_List_Call) Run(run func(ctx context.Context, p domain.Pr
426 if args[2] != nil {426 if args[2] != nil {
427 arg2 = args[2].(domain.RepoName)427 arg2 = args[2].(domain.RepoName)
428 }428 }
429- var arg3 app.ListMergeRequestsParams429+ var arg3 ports.ListMergeRequestsParams
430 if args[3] != nil {430 if args[3] != nil {
431- arg3 = args[3].(app.ListMergeRequestsParams)431+ arg3 = args[3].(ports.ListMergeRequestsParams)
432 }432 }
433 run(433 run(
434 arg0,434 arg0,
@@ -440,18 +440,18 @@ func (_c *MergeRequests_List_Call) Run(run func(ctx context.Context, p domain.Pr
440 return _c440 return _c
441 }441 }
442 442
443-func (_c *MergeRequests_List_Call) Return(mergeRequestPage app.MergeRequestPage, err error) *MergeRequests_List_Call {443+func (_c *MergeRequestService_List_Call) Return(mergeRequestPage ports.MergeRequestPage, err error) *MergeRequestService_List_Call {
444 _c.Call.Return(mergeRequestPage, err)444 _c.Call.Return(mergeRequestPage, err)
445 return _c445 return _c
446 }446 }
447 447
448-func (_c *MergeRequests_List_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName, params app.ListMergeRequestsParams) (app.MergeRequestPage, error)) *MergeRequests_List_Call {448+func (_c *MergeRequestService_List_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName, params ports.ListMergeRequestsParams) (ports.MergeRequestPage, error)) *MergeRequestService_List_Call {
449 _c.Call.Return(run)449 _c.Call.Return(run)
450 return _c450 return _c
451 }451 }
452 452
453-// MarkRead provides a mock function for the type MergeRequests453+// MarkRead provides a mock function for the type MergeRequestService
454-func (_mock *MergeRequests) MarkRead(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, head domain.ObjectID) (domain.ObjectID, error) {454+func (_mock *MergeRequestService) MarkRead(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, head domain.ObjectID) (domain.ObjectID, error) {
455 ret := _mock.Called(ctx, p, name, number, head)455 ret := _mock.Called(ctx, p, name, number, head)
456 456
457 if len(ret) == 0 {457 if len(ret) == 0 {
@@ -476,8 +476,8 @@ func (_mock *MergeRequests) MarkRead(ctx context.Context, p domain.Principal, na
476 return r0, r1476 return r0, r1
477 }477 }
478 478
479-// MergeRequests_MarkRead_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MarkRead'479+// MergeRequestService_MarkRead_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MarkRead'
480-type MergeRequests_MarkRead_Call struct {480+type MergeRequestService_MarkRead_Call struct {
481 *mock.Call481 *mock.Call
482 }482 }
483 483
@@ -487,11 +487,11 @@ type MergeRequests_MarkRead_Call struct {
487 // - name domain.RepoName487 // - name domain.RepoName
488 // - number int64488 // - number int64
489 // - head domain.ObjectID489 // - head domain.ObjectID
490-func (_e *MergeRequests_Expecter) MarkRead(ctx any, p any, name any, number any, head any) *MergeRequests_MarkRead_Call {490+func (_e *MergeRequestService_Expecter) MarkRead(ctx any, p any, name any, number any, head any) *MergeRequestService_MarkRead_Call {
491- return &MergeRequests_MarkRead_Call{Call: _e.mock.On("MarkRead", ctx, p, name, number, head)}491+ return &MergeRequestService_MarkRead_Call{Call: _e.mock.On("MarkRead", ctx, p, name, number, head)}
492 }492 }
493 493
494-func (_c *MergeRequests_MarkRead_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, head domain.ObjectID)) *MergeRequests_MarkRead_Call {494+func (_c *MergeRequestService_MarkRead_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, head domain.ObjectID)) *MergeRequestService_MarkRead_Call {
495 _c.Call.Run(func(args mock.Arguments) {495 _c.Call.Run(func(args mock.Arguments) {
496 var arg0 context.Context496 var arg0 context.Context
497 if args[0] != nil {497 if args[0] != nil {
@@ -524,35 +524,35 @@ func (_c *MergeRequests_MarkRead_Call) Run(run func(ctx context.Context, p domai
524 return _c524 return _c
525 }525 }
526 526
527-func (_c *MergeRequests_MarkRead_Call) Return(objectID domain.ObjectID, err error) *MergeRequests_MarkRead_Call {527+func (_c *MergeRequestService_MarkRead_Call) Return(objectID domain.ObjectID, err error) *MergeRequestService_MarkRead_Call {
528 _c.Call.Return(objectID, err)528 _c.Call.Return(objectID, err)
529 return _c529 return _c
530 }530 }
531 531
532-func (_c *MergeRequests_MarkRead_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, head domain.ObjectID) (domain.ObjectID, error)) *MergeRequests_MarkRead_Call {532+func (_c *MergeRequestService_MarkRead_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, head domain.ObjectID) (domain.ObjectID, error)) *MergeRequestService_MarkRead_Call {
533 _c.Call.Return(run)533 _c.Call.Return(run)
534 return _c534 return _c
535 }535 }
536 536
537-// Merge provides a mock function for the type MergeRequests537+// Merge provides a mock function for the type MergeRequestService
538-func (_mock *MergeRequests) Merge(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, params app.MergeParams) (app.MergeResult, error) {538+func (_mock *MergeRequestService) Merge(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, params ports.MergeParams) (ports.MergeResult, error) {
539 ret := _mock.Called(ctx, p, name, number, params)539 ret := _mock.Called(ctx, p, name, number, params)
540 540
541 if len(ret) == 0 {541 if len(ret) == 0 {
542 panic("no return value specified for Merge")542 panic("no return value specified for Merge")
543 }543 }
544 544
545- var r0 app.MergeResult545+ var r0 ports.MergeResult
546 var r1 error546 var r1 error
547- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, int64, app.MergeParams) (app.MergeResult, error)); ok {547+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, int64, ports.MergeParams) (ports.MergeResult, error)); ok {
548 return returnFunc(ctx, p, name, number, params)548 return returnFunc(ctx, p, name, number, params)
549 }549 }
550- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, int64, app.MergeParams) app.MergeResult); ok {550+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, int64, ports.MergeParams) ports.MergeResult); ok {
551 r0 = returnFunc(ctx, p, name, number, params)551 r0 = returnFunc(ctx, p, name, number, params)
552 } else {552 } else {
553- r0 = ret.Get(0).(app.MergeResult)553+ r0 = ret.Get(0).(ports.MergeResult)
554 }554 }
555- if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, int64, app.MergeParams) error); ok {555+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, int64, ports.MergeParams) error); ok {
556 r1 = returnFunc(ctx, p, name, number, params)556 r1 = returnFunc(ctx, p, name, number, params)
557 } else {557 } else {
558 r1 = ret.Error(1)558 r1 = ret.Error(1)
@@ -560,8 +560,8 @@ func (_mock *MergeRequests) Merge(ctx context.Context, p domain.Principal, name
560 return r0, r1560 return r0, r1
561 }561 }
562 562
563-// MergeRequests_Merge_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Merge'563+// MergeRequestService_Merge_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Merge'
564-type MergeRequests_Merge_Call struct {564+type MergeRequestService_Merge_Call struct {
565 *mock.Call565 *mock.Call
566 }566 }
567 567
@@ -570,12 +570,12 @@ type MergeRequests_Merge_Call struct {
570 // - p domain.Principal570 // - p domain.Principal
571 // - name domain.RepoName571 // - name domain.RepoName
572 // - number int64572 // - number int64
573-// - params app.MergeParams573+// - params ports.MergeParams
574-func (_e *MergeRequests_Expecter) Merge(ctx any, p any, name any, number any, params any) *MergeRequests_Merge_Call {574+func (_e *MergeRequestService_Expecter) Merge(ctx any, p any, name any, number any, params any) *MergeRequestService_Merge_Call {
575- return &MergeRequests_Merge_Call{Call: _e.mock.On("Merge", ctx, p, name, number, params)}575+ return &MergeRequestService_Merge_Call{Call: _e.mock.On("Merge", ctx, p, name, number, params)}
576 }576 }
577 577
578-func (_c *MergeRequests_Merge_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, params app.MergeParams)) *MergeRequests_Merge_Call {578+func (_c *MergeRequestService_Merge_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, params ports.MergeParams)) *MergeRequestService_Merge_Call {
579 _c.Call.Run(func(args mock.Arguments) {579 _c.Call.Run(func(args mock.Arguments) {
580 var arg0 context.Context580 var arg0 context.Context
581 if args[0] != nil {581 if args[0] != nil {
@@ -593,9 +593,9 @@ func (_c *MergeRequests_Merge_Call) Run(run func(ctx context.Context, p domain.P
593 if args[3] != nil {593 if args[3] != nil {
594 arg3 = args[3].(int64)594 arg3 = args[3].(int64)
595 }595 }
596- var arg4 app.MergeParams596+ var arg4 ports.MergeParams
597 if args[4] != nil {597 if args[4] != nil {
598- arg4 = args[4].(app.MergeParams)598+ arg4 = args[4].(ports.MergeParams)
599 }599 }
600 run(600 run(
601 arg0,601 arg0,
@@ -608,18 +608,18 @@ func (_c *MergeRequests_Merge_Call) Run(run func(ctx context.Context, p domain.P
608 return _c608 return _c
609 }609 }
610 610
611-func (_c *MergeRequests_Merge_Call) Return(mergeResult app.MergeResult, err error) *MergeRequests_Merge_Call {611+func (_c *MergeRequestService_Merge_Call) Return(mergeResult ports.MergeResult, err error) *MergeRequestService_Merge_Call {
612 _c.Call.Return(mergeResult, err)612 _c.Call.Return(mergeResult, err)
613 return _c613 return _c
614 }614 }
615 615
616-func (_c *MergeRequests_Merge_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, params app.MergeParams) (app.MergeResult, error)) *MergeRequests_Merge_Call {616+func (_c *MergeRequestService_Merge_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, params ports.MergeParams) (ports.MergeResult, error)) *MergeRequestService_Merge_Call {
617 _c.Call.Return(run)617 _c.Call.Return(run)
618 return _c618 return _c
619 }619 }
620 620
621-// ResolveComment provides a mock function for the type MergeRequests621+// ResolveComment provides a mock function for the type MergeRequestService
622-func (_mock *MergeRequests) ResolveComment(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, commentID int64, resolved bool) (domain.Comment, error) {622+func (_mock *MergeRequestService) ResolveComment(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, commentID int64, resolved bool) (domain.Comment, error) {
623 ret := _mock.Called(ctx, p, name, number, commentID, resolved)623 ret := _mock.Called(ctx, p, name, number, commentID, resolved)
624 624
625 if len(ret) == 0 {625 if len(ret) == 0 {
@@ -644,8 +644,8 @@ func (_mock *MergeRequests) ResolveComment(ctx context.Context, p domain.Princip
644 return r0, r1644 return r0, r1
645 }645 }
646 646
647-// MergeRequests_ResolveComment_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ResolveComment'647+// MergeRequestService_ResolveComment_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ResolveComment'
648-type MergeRequests_ResolveComment_Call struct {648+type MergeRequestService_ResolveComment_Call struct {
649 *mock.Call649 *mock.Call
650 }650 }
651 651
@@ -656,11 +656,11 @@ type MergeRequests_ResolveComment_Call struct {
656 // - number int64656 // - number int64
657 // - commentID int64657 // - commentID int64
658 // - resolved bool658 // - resolved bool
659-func (_e *MergeRequests_Expecter) ResolveComment(ctx any, p any, name any, number any, commentID any, resolved any) *MergeRequests_ResolveComment_Call {659+func (_e *MergeRequestService_Expecter) ResolveComment(ctx any, p any, name any, number any, commentID any, resolved any) *MergeRequestService_ResolveComment_Call {
660- return &MergeRequests_ResolveComment_Call{Call: _e.mock.On("ResolveComment", ctx, p, name, number, commentID, resolved)}660+ return &MergeRequestService_ResolveComment_Call{Call: _e.mock.On("ResolveComment", ctx, p, name, number, commentID, resolved)}
661 }661 }
662 662
663-func (_c *MergeRequests_ResolveComment_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, commentID int64, resolved bool)) *MergeRequests_ResolveComment_Call {663+func (_c *MergeRequestService_ResolveComment_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, commentID int64, resolved bool)) *MergeRequestService_ResolveComment_Call {
664 _c.Call.Run(func(args mock.Arguments) {664 _c.Call.Run(func(args mock.Arguments) {
665 var arg0 context.Context665 var arg0 context.Context
666 if args[0] != nil {666 if args[0] != nil {
@@ -698,12 +698,12 @@ func (_c *MergeRequests_ResolveComment_Call) Run(run func(ctx context.Context, p
698 return _c698 return _c
699 }699 }
700 700
701-func (_c *MergeRequests_ResolveComment_Call) Return(comment domain.Comment, err error) *MergeRequests_ResolveComment_Call {701+func (_c *MergeRequestService_ResolveComment_Call) Return(comment domain.Comment, err error) *MergeRequestService_ResolveComment_Call {
702 _c.Call.Return(comment, err)702 _c.Call.Return(comment, err)
703 return _c703 return _c
704 }704 }
705 705
706-func (_c *MergeRequests_ResolveComment_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, commentID int64, resolved bool) (domain.Comment, error)) *MergeRequests_ResolveComment_Call {706+func (_c *MergeRequestService_ResolveComment_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, commentID int64, resolved bool) (domain.Comment, error)) *MergeRequestService_ResolveComment_Call {
707 _c.Call.Return(run)707 _c.Call.Return(run)
708 return _c708 return _c
709 }709 }
R internal/adapters/githttp/mocks/PushHooks.go → internal/ports/mocks/PushHookService.go +27 -27 renamed, 51% similar
@@ -11,17 +11,17 @@ import (
11 mock "github.com/stretchr/testify/mock"11 mock "github.com/stretchr/testify/mock"
12 )12 )
13 13
14-// NewPushHooks creates a new instance of PushHooks. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.14+// NewPushHookService creates a new instance of PushHookService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
15 // The first argument is typically a *testing.T value.15 // The first argument is typically a *testing.T value.
16-func NewPushHooks(t interface {16+func NewPushHookService(t interface {
17 mock.TestingT17 mock.TestingT
18 Cleanup(func())18 Cleanup(func())
19-}) *PushHooks {19+}) *PushHookService {
20 if helper, ok := t.(interface{ Helper() }); ok {20 if helper, ok := t.(interface{ Helper() }); ok {
21 helper.Helper()21 helper.Helper()
22 }22 }
23 23
24- mock := &PushHooks{}24+ mock := &PushHookService{}
25 mock.Mock.Test(t)25 mock.Mock.Test(t)
26 26
27 t.Cleanup(func() {27 t.Cleanup(func() {
@@ -34,21 +34,21 @@ func NewPushHooks(t interface {
34 return mock34 return mock
35 }35 }
36 36
37-// PushHooks is an autogenerated mock type for the PushHooks type37+// PushHookService is an autogenerated mock type for the PushHookService type
38-type PushHooks struct {38+type PushHookService struct {
39 mock.Mock39 mock.Mock
40 }40 }
41 41
42-type PushHooks_Expecter struct {42+type PushHookService_Expecter struct {
43 mock *mock.Mock43 mock *mock.Mock
44 }44 }
45 45
46-func (_m *PushHooks) EXPECT() *PushHooks_Expecter {46+func (_m *PushHookService) EXPECT() *PushHookService_Expecter {
47- return &PushHooks_Expecter{mock: &_m.Mock}47+ return &PushHookService_Expecter{mock: &_m.Mock}
48 }48 }
49 49
50-// Check provides a mock function for the type PushHooks50+// Check provides a mock function for the type PushHookService
51-func (_mock *PushHooks) Check(ctx context.Context, p domain.Principal, name domain.RepoName, updates []domain.RefUpdate) error {51+func (_mock *PushHookService) Check(ctx context.Context, p domain.Principal, name domain.RepoName, updates []domain.RefUpdate) error {
52 ret := _mock.Called(ctx, p, name, updates)52 ret := _mock.Called(ctx, p, name, updates)
53 53
54 if len(ret) == 0 {54 if len(ret) == 0 {
@@ -64,8 +64,8 @@ func (_mock *PushHooks) Check(ctx context.Context, p domain.Principal, name doma
64 return r064 return r0
65 }65 }
66 66
67-// PushHooks_Check_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Check'67+// PushHookService_Check_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Check'
68-type PushHooks_Check_Call struct {68+type PushHookService_Check_Call struct {
69 *mock.Call69 *mock.Call
70 }70 }
71 71
@@ -74,11 +74,11 @@ type PushHooks_Check_Call struct {
74 // - p domain.Principal74 // - p domain.Principal
75 // - name domain.RepoName75 // - name domain.RepoName
76 // - updates []domain.RefUpdate76 // - updates []domain.RefUpdate
77-func (_e *PushHooks_Expecter) Check(ctx any, p any, name any, updates any) *PushHooks_Check_Call {77+func (_e *PushHookService_Expecter) Check(ctx any, p any, name any, updates any) *PushHookService_Check_Call {
78- return &PushHooks_Check_Call{Call: _e.mock.On("Check", ctx, p, name, updates)}78+ return &PushHookService_Check_Call{Call: _e.mock.On("Check", ctx, p, name, updates)}
79 }79 }
80 80
81-func (_c *PushHooks_Check_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName, updates []domain.RefUpdate)) *PushHooks_Check_Call {81+func (_c *PushHookService_Check_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName, updates []domain.RefUpdate)) *PushHookService_Check_Call {
82 _c.Call.Run(func(args mock.Arguments) {82 _c.Call.Run(func(args mock.Arguments) {
83 var arg0 context.Context83 var arg0 context.Context
84 if args[0] != nil {84 if args[0] != nil {
@@ -106,18 +106,18 @@ func (_c *PushHooks_Check_Call) Run(run func(ctx context.Context, p domain.Princ
106 return _c106 return _c
107 }107 }
108 108
109-func (_c *PushHooks_Check_Call) Return(err error) *PushHooks_Check_Call {109+func (_c *PushHookService_Check_Call) Return(err error) *PushHookService_Check_Call {
110 _c.Call.Return(err)110 _c.Call.Return(err)
111 return _c111 return _c
112 }112 }
113 113
114-func (_c *PushHooks_Check_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName, updates []domain.RefUpdate) error) *PushHooks_Check_Call {114+func (_c *PushHookService_Check_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName, updates []domain.RefUpdate) error) *PushHookService_Check_Call {
115 _c.Call.Return(run)115 _c.Call.Return(run)
116 return _c116 return _c
117 }117 }
118 118
119-// Record provides a mock function for the type PushHooks119+// Record provides a mock function for the type PushHookService
120-func (_mock *PushHooks) Record(ctx context.Context, p domain.Principal, name domain.RepoName, updates []domain.RefUpdate) error {120+func (_mock *PushHookService) Record(ctx context.Context, p domain.Principal, name domain.RepoName, updates []domain.RefUpdate) error {
121 ret := _mock.Called(ctx, p, name, updates)121 ret := _mock.Called(ctx, p, name, updates)
122 122
123 if len(ret) == 0 {123 if len(ret) == 0 {
@@ -133,8 +133,8 @@ func (_mock *PushHooks) Record(ctx context.Context, p domain.Principal, name dom
133 return r0133 return r0
134 }134 }
135 135
136-// PushHooks_Record_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Record'136+// PushHookService_Record_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Record'
137-type PushHooks_Record_Call struct {137+type PushHookService_Record_Call struct {
138 *mock.Call138 *mock.Call
139 }139 }
140 140
@@ -143,11 +143,11 @@ type PushHooks_Record_Call struct {
143 // - p domain.Principal143 // - p domain.Principal
144 // - name domain.RepoName144 // - name domain.RepoName
145 // - updates []domain.RefUpdate145 // - updates []domain.RefUpdate
146-func (_e *PushHooks_Expecter) Record(ctx any, p any, name any, updates any) *PushHooks_Record_Call {146+func (_e *PushHookService_Expecter) Record(ctx any, p any, name any, updates any) *PushHookService_Record_Call {
147- return &PushHooks_Record_Call{Call: _e.mock.On("Record", ctx, p, name, updates)}147+ return &PushHookService_Record_Call{Call: _e.mock.On("Record", ctx, p, name, updates)}
148 }148 }
149 149
150-func (_c *PushHooks_Record_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName, updates []domain.RefUpdate)) *PushHooks_Record_Call {150+func (_c *PushHookService_Record_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName, updates []domain.RefUpdate)) *PushHookService_Record_Call {
151 _c.Call.Run(func(args mock.Arguments) {151 _c.Call.Run(func(args mock.Arguments) {
152 var arg0 context.Context152 var arg0 context.Context
153 if args[0] != nil {153 if args[0] != nil {
@@ -175,12 +175,12 @@ func (_c *PushHooks_Record_Call) Run(run func(ctx context.Context, p domain.Prin
175 return _c175 return _c
176 }176 }
177 177
178-func (_c *PushHooks_Record_Call) Return(err error) *PushHooks_Record_Call {178+func (_c *PushHookService_Record_Call) Return(err error) *PushHookService_Record_Call {
179 _c.Call.Return(err)179 _c.Call.Return(err)
180 return _c180 return _c
181 }181 }
182 182
183-func (_c *PushHooks_Record_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName, updates []domain.RefUpdate) error) *PushHooks_Record_Call {183+func (_c *PushHookService_Record_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName, updates []domain.RefUpdate) error) *PushHookService_Record_Call {
184 _c.Call.Return(run)184 _c.Call.Return(run)
185 return _c185 return _c
186 }186 }
R internal/adapters/githttp/mocks/Pushes.go → internal/ports/mocks/PushService.go +18 -18 renamed, 53% similar
@@ -11,17 +11,17 @@ import (
11 mock "github.com/stretchr/testify/mock"11 mock "github.com/stretchr/testify/mock"
12 )12 )
13 13
14-// NewPushes creates a new instance of Pushes. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.14+// NewPushService creates a new instance of PushService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
15 // The first argument is typically a *testing.T value.15 // The first argument is typically a *testing.T value.
16-func NewPushes(t interface {16+func NewPushService(t interface {
17 mock.TestingT17 mock.TestingT
18 Cleanup(func())18 Cleanup(func())
19-}) *Pushes {19+}) *PushService {
20 if helper, ok := t.(interface{ Helper() }); ok {20 if helper, ok := t.(interface{ Helper() }); ok {
21 helper.Helper()21 helper.Helper()
22 }22 }
23 23
24- mock := &Pushes{}24+ mock := &PushService{}
25 mock.Mock.Test(t)25 mock.Mock.Test(t)
26 26
27 t.Cleanup(func() {27 t.Cleanup(func() {
@@ -34,21 +34,21 @@ func NewPushes(t interface {
34 return mock34 return mock
35 }35 }
36 36
37-// Pushes is an autogenerated mock type for the Pushes type37+// PushService is an autogenerated mock type for the PushService type
38-type Pushes struct {38+type PushService struct {
39 mock.Mock39 mock.Mock
40 }40 }
41 41
42-type Pushes_Expecter struct {42+type PushService_Expecter struct {
43 mock *mock.Mock43 mock *mock.Mock
44 }44 }
45 45
46-func (_m *Pushes) EXPECT() *Pushes_Expecter {46+func (_m *PushService) EXPECT() *PushService_Expecter {
47- return &Pushes_Expecter{mock: &_m.Mock}47+ return &PushService_Expecter{mock: &_m.Mock}
48 }48 }
49 49
50-// Open provides a mock function for the type Pushes50+// Open provides a mock function for the type PushService
51-func (_mock *Pushes) Open(ctx context.Context, p domain.Principal, name domain.RepoName, create bool) error {51+func (_mock *PushService) Open(ctx context.Context, p domain.Principal, name domain.RepoName, create bool) error {
52 ret := _mock.Called(ctx, p, name, create)52 ret := _mock.Called(ctx, p, name, create)
53 53
54 if len(ret) == 0 {54 if len(ret) == 0 {
@@ -64,8 +64,8 @@ func (_mock *Pushes) Open(ctx context.Context, p domain.Principal, name domain.R
64 return r064 return r0
65 }65 }
66 66
67-// Pushes_Open_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Open'67+// PushService_Open_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Open'
68-type Pushes_Open_Call struct {68+type PushService_Open_Call struct {
69 *mock.Call69 *mock.Call
70 }70 }
71 71
@@ -74,11 +74,11 @@ type Pushes_Open_Call struct {
74 // - p domain.Principal74 // - p domain.Principal
75 // - name domain.RepoName75 // - name domain.RepoName
76 // - create bool76 // - create bool
77-func (_e *Pushes_Expecter) Open(ctx any, p any, name any, create any) *Pushes_Open_Call {77+func (_e *PushService_Expecter) Open(ctx any, p any, name any, create any) *PushService_Open_Call {
78- return &Pushes_Open_Call{Call: _e.mock.On("Open", ctx, p, name, create)}78+ return &PushService_Open_Call{Call: _e.mock.On("Open", ctx, p, name, create)}
79 }79 }
80 80
81-func (_c *Pushes_Open_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName, create bool)) *Pushes_Open_Call {81+func (_c *PushService_Open_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName, create bool)) *PushService_Open_Call {
82 _c.Call.Run(func(args mock.Arguments) {82 _c.Call.Run(func(args mock.Arguments) {
83 var arg0 context.Context83 var arg0 context.Context
84 if args[0] != nil {84 if args[0] != nil {
@@ -106,12 +106,12 @@ func (_c *Pushes_Open_Call) Run(run func(ctx context.Context, p domain.Principal
106 return _c106 return _c
107 }107 }
108 108
109-func (_c *Pushes_Open_Call) Return(err error) *Pushes_Open_Call {109+func (_c *PushService_Open_Call) Return(err error) *PushService_Open_Call {
110 _c.Call.Return(err)110 _c.Call.Return(err)
111 return _c111 return _c
112 }112 }
113 113
114-func (_c *Pushes_Open_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName, create bool) error) *Pushes_Open_Call {114+func (_c *PushService_Open_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName, create bool) error) *PushService_Open_Call {
115 _c.Call.Return(run)115 _c.Call.Return(run)
116 return _c116 return _c
117 }117 }
R internal/adapters/githttp/mocks/Repos.go → internal/ports/mocks/RepoLookup.go +18 -18 renamed, 58% similar
@@ -11,17 +11,17 @@ import (
11 mock "github.com/stretchr/testify/mock"11 mock "github.com/stretchr/testify/mock"
12 )12 )
13 13
14-// NewRepos creates a new instance of Repos. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.14+// NewRepoLookup creates a new instance of RepoLookup. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
15 // The first argument is typically a *testing.T value.15 // The first argument is typically a *testing.T value.
16-func NewRepos(t interface {16+func NewRepoLookup(t interface {
17 mock.TestingT17 mock.TestingT
18 Cleanup(func())18 Cleanup(func())
19-}) *Repos {19+}) *RepoLookup {
20 if helper, ok := t.(interface{ Helper() }); ok {20 if helper, ok := t.(interface{ Helper() }); ok {
21 helper.Helper()21 helper.Helper()
22 }22 }
23 23
24- mock := &Repos{}24+ mock := &RepoLookup{}
25 mock.Mock.Test(t)25 mock.Mock.Test(t)
26 26
27 t.Cleanup(func() {27 t.Cleanup(func() {
@@ -34,21 +34,21 @@ func NewRepos(t interface {
34 return mock34 return mock
35 }35 }
36 36
37-// Repos is an autogenerated mock type for the Repos type37+// RepoLookup is an autogenerated mock type for the RepoLookup type
38-type Repos struct {38+type RepoLookup struct {
39 mock.Mock39 mock.Mock
40 }40 }
41 41
42-type Repos_Expecter struct {42+type RepoLookup_Expecter struct {
43 mock *mock.Mock43 mock *mock.Mock
44 }44 }
45 45
46-func (_m *Repos) EXPECT() *Repos_Expecter {46+func (_m *RepoLookup) EXPECT() *RepoLookup_Expecter {
47- return &Repos_Expecter{mock: &_m.Mock}47+ return &RepoLookup_Expecter{mock: &_m.Mock}
48 }48 }
49 49
50-// Get provides a mock function for the type Repos50+// Get provides a mock function for the type RepoLookup
51-func (_mock *Repos) Get(ctx context.Context, p domain.Principal, name domain.RepoName) (domain.Repo, error) {51+func (_mock *RepoLookup) Get(ctx context.Context, p domain.Principal, name domain.RepoName) (domain.Repo, error) {
52 ret := _mock.Called(ctx, p, name)52 ret := _mock.Called(ctx, p, name)
53 53
54 if len(ret) == 0 {54 if len(ret) == 0 {
@@ -73,8 +73,8 @@ func (_mock *Repos) Get(ctx context.Context, p domain.Principal, name domain.Rep
73 return r0, r173 return r0, r1
74 }74 }
75 75
76-// Repos_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get'76+// RepoLookup_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get'
77-type Repos_Get_Call struct {77+type RepoLookup_Get_Call struct {
78 *mock.Call78 *mock.Call
79 }79 }
80 80
@@ -82,11 +82,11 @@ type Repos_Get_Call struct {
82 // - ctx context.Context82 // - ctx context.Context
83 // - p domain.Principal83 // - p domain.Principal
84 // - name domain.RepoName84 // - name domain.RepoName
85-func (_e *Repos_Expecter) Get(ctx any, p any, name any) *Repos_Get_Call {85+func (_e *RepoLookup_Expecter) Get(ctx any, p any, name any) *RepoLookup_Get_Call {
86- return &Repos_Get_Call{Call: _e.mock.On("Get", ctx, p, name)}86+ return &RepoLookup_Get_Call{Call: _e.mock.On("Get", ctx, p, name)}
87 }87 }
88 88
89-func (_c *Repos_Get_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName)) *Repos_Get_Call {89+func (_c *RepoLookup_Get_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName)) *RepoLookup_Get_Call {
90 _c.Call.Run(func(args mock.Arguments) {90 _c.Call.Run(func(args mock.Arguments) {
91 var arg0 context.Context91 var arg0 context.Context
92 if args[0] != nil {92 if args[0] != nil {
@@ -109,12 +109,12 @@ func (_c *Repos_Get_Call) Run(run func(ctx context.Context, p domain.Principal,
109 return _c109 return _c
110 }110 }
111 111
112-func (_c *Repos_Get_Call) Return(repo domain.Repo, err error) *Repos_Get_Call {112+func (_c *RepoLookup_Get_Call) Return(repo domain.Repo, err error) *RepoLookup_Get_Call {
113 _c.Call.Return(repo, err)113 _c.Call.Return(repo, err)
114 return _c114 return _c
115 }115 }
116 116
117-func (_c *Repos_Get_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName) (domain.Repo, error)) *Repos_Get_Call {117+func (_c *RepoLookup_Get_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName) (domain.Repo, error)) *RepoLookup_Get_Call {
118 _c.Call.Return(run)118 _c.Call.Return(run)
119 return _c119 return _c
120 }120 }
R internal/adapters/rpc/mocks/Repos.go → internal/ports/mocks/RepoService.go +84 -84 renamed, 50% similar
@@ -7,22 +7,22 @@ package mocks
7 import (7 import (
8 "context"8 "context"
9 9
10- "bnema.dev/ermite/internal/app"
11 "bnema.dev/ermite/internal/domain"10 "bnema.dev/ermite/internal/domain"
11+ "bnema.dev/ermite/internal/ports"
12 mock "github.com/stretchr/testify/mock"12 mock "github.com/stretchr/testify/mock"
13 )13 )
14 14
15-// NewRepos creates a new instance of Repos. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.15+// NewRepoService creates a new instance of RepoService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
16 // The first argument is typically a *testing.T value.16 // The first argument is typically a *testing.T value.
17-func NewRepos(t interface {17+func NewRepoService(t interface {
18 mock.TestingT18 mock.TestingT
19 Cleanup(func())19 Cleanup(func())
20-}) *Repos {20+}) *RepoService {
21 if helper, ok := t.(interface{ Helper() }); ok {21 if helper, ok := t.(interface{ Helper() }); ok {
22 helper.Helper()22 helper.Helper()
23 }23 }
24 24
25- mock := &Repos{}25+ mock := &RepoService{}
26 mock.Mock.Test(t)26 mock.Mock.Test(t)
27 27
28 t.Cleanup(func() {28 t.Cleanup(func() {
@@ -35,21 +35,21 @@ func NewRepos(t interface {
35 return mock35 return mock
36 }36 }
37 37
38-// Repos is an autogenerated mock type for the Repos type38+// RepoService is an autogenerated mock type for the RepoService type
39-type Repos struct {39+type RepoService struct {
40 mock.Mock40 mock.Mock
41 }41 }
42 42
43-type Repos_Expecter struct {43+type RepoService_Expecter struct {
44 mock *mock.Mock44 mock *mock.Mock
45 }45 }
46 46
47-func (_m *Repos) EXPECT() *Repos_Expecter {47+func (_m *RepoService) EXPECT() *RepoService_Expecter {
48- return &Repos_Expecter{mock: &_m.Mock}48+ return &RepoService_Expecter{mock: &_m.Mock}
49 }49 }
50 50
51-// Create provides a mock function for the type Repos51+// Create provides a mock function for the type RepoService
52-func (_mock *Repos) Create(ctx context.Context, p domain.Principal, params app.CreateParams) (domain.Repo, error) {52+func (_mock *RepoService) Create(ctx context.Context, p domain.Principal, params ports.CreateRepoParams) (domain.Repo, error) {
53 ret := _mock.Called(ctx, p, params)53 ret := _mock.Called(ctx, p, params)
54 54
55 if len(ret) == 0 {55 if len(ret) == 0 {
@@ -58,15 +58,15 @@ func (_mock *Repos) Create(ctx context.Context, p domain.Principal, params app.C
58 58
59 var r0 domain.Repo59 var r0 domain.Repo
60 var r1 error60 var r1 error
61- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, app.CreateParams) (domain.Repo, error)); ok {61+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, ports.CreateRepoParams) (domain.Repo, error)); ok {
62 return returnFunc(ctx, p, params)62 return returnFunc(ctx, p, params)
63 }63 }
64- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, app.CreateParams) domain.Repo); ok {64+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, ports.CreateRepoParams) domain.Repo); ok {
65 r0 = returnFunc(ctx, p, params)65 r0 = returnFunc(ctx, p, params)
66 } else {66 } else {
67 r0 = ret.Get(0).(domain.Repo)67 r0 = ret.Get(0).(domain.Repo)
68 }68 }
69- if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, app.CreateParams) error); ok {69+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, ports.CreateRepoParams) error); ok {
70 r1 = returnFunc(ctx, p, params)70 r1 = returnFunc(ctx, p, params)
71 } else {71 } else {
72 r1 = ret.Error(1)72 r1 = ret.Error(1)
@@ -74,20 +74,20 @@ func (_mock *Repos) Create(ctx context.Context, p domain.Principal, params app.C
74 return r0, r174 return r0, r1
75 }75 }
76 76
77-// Repos_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create'77+// RepoService_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create'
78-type Repos_Create_Call struct {78+type RepoService_Create_Call struct {
79 *mock.Call79 *mock.Call
80 }80 }
81 81
82 // Create is a helper method to define mock.On call82 // Create is a helper method to define mock.On call
83 // - ctx context.Context83 // - ctx context.Context
84 // - p domain.Principal84 // - p domain.Principal
85-// - params app.CreateParams85+// - params ports.CreateRepoParams
86-func (_e *Repos_Expecter) Create(ctx any, p any, params any) *Repos_Create_Call {86+func (_e *RepoService_Expecter) Create(ctx any, p any, params any) *RepoService_Create_Call {
87- return &Repos_Create_Call{Call: _e.mock.On("Create", ctx, p, params)}87+ return &RepoService_Create_Call{Call: _e.mock.On("Create", ctx, p, params)}
88 }88 }
89 89
90-func (_c *Repos_Create_Call) Run(run func(ctx context.Context, p domain.Principal, params app.CreateParams)) *Repos_Create_Call {90+func (_c *RepoService_Create_Call) Run(run func(ctx context.Context, p domain.Principal, params ports.CreateRepoParams)) *RepoService_Create_Call {
91 _c.Call.Run(func(args mock.Arguments) {91 _c.Call.Run(func(args mock.Arguments) {
92 var arg0 context.Context92 var arg0 context.Context
93 if args[0] != nil {93 if args[0] != nil {
@@ -97,9 +97,9 @@ func (_c *Repos_Create_Call) Run(run func(ctx context.Context, p domain.Principa
97 if args[1] != nil {97 if args[1] != nil {
98 arg1 = args[1].(domain.Principal)98 arg1 = args[1].(domain.Principal)
99 }99 }
100- var arg2 app.CreateParams100+ var arg2 ports.CreateRepoParams
101 if args[2] != nil {101 if args[2] != nil {
102- arg2 = args[2].(app.CreateParams)102+ arg2 = args[2].(ports.CreateRepoParams)
103 }103 }
104 run(104 run(
105 arg0,105 arg0,
@@ -110,18 +110,18 @@ func (_c *Repos_Create_Call) Run(run func(ctx context.Context, p domain.Principa
110 return _c110 return _c
111 }111 }
112 112
113-func (_c *Repos_Create_Call) Return(repo domain.Repo, err error) *Repos_Create_Call {113+func (_c *RepoService_Create_Call) Return(repo domain.Repo, err error) *RepoService_Create_Call {
114 _c.Call.Return(repo, err)114 _c.Call.Return(repo, err)
115 return _c115 return _c
116 }116 }
117 117
118-func (_c *Repos_Create_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, params app.CreateParams) (domain.Repo, error)) *Repos_Create_Call {118+func (_c *RepoService_Create_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, params ports.CreateRepoParams) (domain.Repo, error)) *RepoService_Create_Call {
119 _c.Call.Return(run)119 _c.Call.Return(run)
120 return _c120 return _c
121 }121 }
122 122
123-// Delete provides a mock function for the type Repos123+// Delete provides a mock function for the type RepoService
124-func (_mock *Repos) Delete(ctx context.Context, p domain.Principal, name domain.RepoName) error {124+func (_mock *RepoService) Delete(ctx context.Context, p domain.Principal, name domain.RepoName) error {
125 ret := _mock.Called(ctx, p, name)125 ret := _mock.Called(ctx, p, name)
126 126
127 if len(ret) == 0 {127 if len(ret) == 0 {
@@ -137,8 +137,8 @@ func (_mock *Repos) Delete(ctx context.Context, p domain.Principal, name domain.
137 return r0137 return r0
138 }138 }
139 139
140-// Repos_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete'140+// RepoService_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete'
141-type Repos_Delete_Call struct {141+type RepoService_Delete_Call struct {
142 *mock.Call142 *mock.Call
143 }143 }
144 144
@@ -146,11 +146,11 @@ type Repos_Delete_Call struct {
146 // - ctx context.Context146 // - ctx context.Context
147 // - p domain.Principal147 // - p domain.Principal
148 // - name domain.RepoName148 // - name domain.RepoName
149-func (_e *Repos_Expecter) Delete(ctx any, p any, name any) *Repos_Delete_Call {149+func (_e *RepoService_Expecter) Delete(ctx any, p any, name any) *RepoService_Delete_Call {
150- return &Repos_Delete_Call{Call: _e.mock.On("Delete", ctx, p, name)}150+ return &RepoService_Delete_Call{Call: _e.mock.On("Delete", ctx, p, name)}
151 }151 }
152 152
153-func (_c *Repos_Delete_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName)) *Repos_Delete_Call {153+func (_c *RepoService_Delete_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName)) *RepoService_Delete_Call {
154 _c.Call.Run(func(args mock.Arguments) {154 _c.Call.Run(func(args mock.Arguments) {
155 var arg0 context.Context155 var arg0 context.Context
156 if args[0] != nil {156 if args[0] != nil {
@@ -173,18 +173,18 @@ func (_c *Repos_Delete_Call) Run(run func(ctx context.Context, p domain.Principa
173 return _c173 return _c
174 }174 }
175 175
176-func (_c *Repos_Delete_Call) Return(err error) *Repos_Delete_Call {176+func (_c *RepoService_Delete_Call) Return(err error) *RepoService_Delete_Call {
177 _c.Call.Return(err)177 _c.Call.Return(err)
178 return _c178 return _c
179 }179 }
180 180
181-func (_c *Repos_Delete_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName) error) *Repos_Delete_Call {181+func (_c *RepoService_Delete_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName) error) *RepoService_Delete_Call {
182 _c.Call.Return(run)182 _c.Call.Return(run)
183 return _c183 return _c
184 }184 }
185 185
186-// Get provides a mock function for the type Repos186+// Get provides a mock function for the type RepoService
187-func (_mock *Repos) Get(ctx context.Context, p domain.Principal, name domain.RepoName) (domain.Repo, error) {187+func (_mock *RepoService) Get(ctx context.Context, p domain.Principal, name domain.RepoName) (domain.Repo, error) {
188 ret := _mock.Called(ctx, p, name)188 ret := _mock.Called(ctx, p, name)
189 189
190 if len(ret) == 0 {190 if len(ret) == 0 {
@@ -209,8 +209,8 @@ func (_mock *Repos) Get(ctx context.Context, p domain.Principal, name domain.Rep
209 return r0, r1209 return r0, r1
210 }210 }
211 211
212-// Repos_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get'212+// RepoService_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get'
213-type Repos_Get_Call struct {213+type RepoService_Get_Call struct {
214 *mock.Call214 *mock.Call
215 }215 }
216 216
@@ -218,11 +218,11 @@ type Repos_Get_Call struct {
218 // - ctx context.Context218 // - ctx context.Context
219 // - p domain.Principal219 // - p domain.Principal
220 // - name domain.RepoName220 // - name domain.RepoName
221-func (_e *Repos_Expecter) Get(ctx any, p any, name any) *Repos_Get_Call {221+func (_e *RepoService_Expecter) Get(ctx any, p any, name any) *RepoService_Get_Call {
222- return &Repos_Get_Call{Call: _e.mock.On("Get", ctx, p, name)}222+ return &RepoService_Get_Call{Call: _e.mock.On("Get", ctx, p, name)}
223 }223 }
224 224
225-func (_c *Repos_Get_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName)) *Repos_Get_Call {225+func (_c *RepoService_Get_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName)) *RepoService_Get_Call {
226 _c.Call.Run(func(args mock.Arguments) {226 _c.Call.Run(func(args mock.Arguments) {
227 var arg0 context.Context227 var arg0 context.Context
228 if args[0] != nil {228 if args[0] != nil {
@@ -245,33 +245,33 @@ func (_c *Repos_Get_Call) Run(run func(ctx context.Context, p domain.Principal,
245 return _c245 return _c
246 }246 }
247 247
248-func (_c *Repos_Get_Call) Return(repo domain.Repo, err error) *Repos_Get_Call {248+func (_c *RepoService_Get_Call) Return(repo domain.Repo, err error) *RepoService_Get_Call {
249 _c.Call.Return(repo, err)249 _c.Call.Return(repo, err)
250 return _c250 return _c
251 }251 }
252 252
253-func (_c *Repos_Get_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName) (domain.Repo, error)) *Repos_Get_Call {253+func (_c *RepoService_Get_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName) (domain.Repo, error)) *RepoService_Get_Call {
254 _c.Call.Return(run)254 _c.Call.Return(run)
255 return _c255 return _c
256 }256 }
257 257
258-// ListPage provides a mock function for the type Repos258+// ListPage provides a mock function for the type RepoService
259-func (_mock *Repos) ListPage(ctx context.Context, p domain.Principal, size int, token string) (app.RepoPage, error) {259+func (_mock *RepoService) ListPage(ctx context.Context, p domain.Principal, size int, token string) (ports.RepoPage, error) {
260 ret := _mock.Called(ctx, p, size, token)260 ret := _mock.Called(ctx, p, size, token)
261 261
262 if len(ret) == 0 {262 if len(ret) == 0 {
263 panic("no return value specified for ListPage")263 panic("no return value specified for ListPage")
264 }264 }
265 265
266- var r0 app.RepoPage266+ var r0 ports.RepoPage
267 var r1 error267 var r1 error
268- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, int, string) (app.RepoPage, error)); ok {268+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, int, string) (ports.RepoPage, error)); ok {
269 return returnFunc(ctx, p, size, token)269 return returnFunc(ctx, p, size, token)
270 }270 }
271- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, int, string) app.RepoPage); ok {271+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, int, string) ports.RepoPage); ok {
272 r0 = returnFunc(ctx, p, size, token)272 r0 = returnFunc(ctx, p, size, token)
273 } else {273 } else {
274- r0 = ret.Get(0).(app.RepoPage)274+ r0 = ret.Get(0).(ports.RepoPage)
275 }275 }
276 if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, int, string) error); ok {276 if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, int, string) error); ok {
277 r1 = returnFunc(ctx, p, size, token)277 r1 = returnFunc(ctx, p, size, token)
@@ -281,8 +281,8 @@ func (_mock *Repos) ListPage(ctx context.Context, p domain.Principal, size int,
281 return r0, r1281 return r0, r1
282 }282 }
283 283
284-// Repos_ListPage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListPage'284+// RepoService_ListPage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListPage'
285-type Repos_ListPage_Call struct {285+type RepoService_ListPage_Call struct {
286 *mock.Call286 *mock.Call
287 }287 }
288 288
@@ -291,11 +291,11 @@ type Repos_ListPage_Call struct {
291 // - p domain.Principal291 // - p domain.Principal
292 // - size int292 // - size int
293 // - token string293 // - token string
294-func (_e *Repos_Expecter) ListPage(ctx any, p any, size any, token any) *Repos_ListPage_Call {294+func (_e *RepoService_Expecter) ListPage(ctx any, p any, size any, token any) *RepoService_ListPage_Call {
295- return &Repos_ListPage_Call{Call: _e.mock.On("ListPage", ctx, p, size, token)}295+ return &RepoService_ListPage_Call{Call: _e.mock.On("ListPage", ctx, p, size, token)}
296 }296 }
297 297
298-func (_c *Repos_ListPage_Call) Run(run func(ctx context.Context, p domain.Principal, size int, token string)) *Repos_ListPage_Call {298+func (_c *RepoService_ListPage_Call) Run(run func(ctx context.Context, p domain.Principal, size int, token string)) *RepoService_ListPage_Call {
299 _c.Call.Run(func(args mock.Arguments) {299 _c.Call.Run(func(args mock.Arguments) {
300 var arg0 context.Context300 var arg0 context.Context
301 if args[0] != nil {301 if args[0] != nil {
@@ -323,18 +323,18 @@ func (_c *Repos_ListPage_Call) Run(run func(ctx context.Context, p domain.Princi
323 return _c323 return _c
324 }324 }
325 325
326-func (_c *Repos_ListPage_Call) Return(repoPage app.RepoPage, err error) *Repos_ListPage_Call {326+func (_c *RepoService_ListPage_Call) Return(repoPage ports.RepoPage, err error) *RepoService_ListPage_Call {
327 _c.Call.Return(repoPage, err)327 _c.Call.Return(repoPage, err)
328 return _c328 return _c
329 }329 }
330 330
331-func (_c *Repos_ListPage_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, size int, token string) (app.RepoPage, error)) *Repos_ListPage_Call {331+func (_c *RepoService_ListPage_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, size int, token string) (ports.RepoPage, error)) *RepoService_ListPage_Call {
332 _c.Call.Return(run)332 _c.Call.Return(run)
333 return _c333 return _c
334 }334 }
335 335
336-// Update provides a mock function for the type Repos336+// Update provides a mock function for the type RepoService
337-func (_mock *Repos) Update(ctx context.Context, p domain.Principal, name domain.RepoName, params app.UpdateParams) (domain.Repo, error) {337+func (_mock *RepoService) Update(ctx context.Context, p domain.Principal, name domain.RepoName, params ports.UpdateRepoParams) (domain.Repo, error) {
338 ret := _mock.Called(ctx, p, name, params)338 ret := _mock.Called(ctx, p, name, params)
339 339
340 if len(ret) == 0 {340 if len(ret) == 0 {
@@ -343,15 +343,15 @@ func (_mock *Repos) Update(ctx context.Context, p domain.Principal, name domain.
343 343
344 var r0 domain.Repo344 var r0 domain.Repo
345 var r1 error345 var r1 error
346- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, app.UpdateParams) (domain.Repo, error)); ok {346+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, ports.UpdateRepoParams) (domain.Repo, error)); ok {
347 return returnFunc(ctx, p, name, params)347 return returnFunc(ctx, p, name, params)
348 }348 }
349- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, app.UpdateParams) domain.Repo); ok {349+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, ports.UpdateRepoParams) domain.Repo); ok {
350 r0 = returnFunc(ctx, p, name, params)350 r0 = returnFunc(ctx, p, name, params)
351 } else {351 } else {
352 r0 = ret.Get(0).(domain.Repo)352 r0 = ret.Get(0).(domain.Repo)
353 }353 }
354- if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, app.UpdateParams) error); ok {354+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, ports.UpdateRepoParams) error); ok {
355 r1 = returnFunc(ctx, p, name, params)355 r1 = returnFunc(ctx, p, name, params)
356 } else {356 } else {
357 r1 = ret.Error(1)357 r1 = ret.Error(1)
@@ -359,8 +359,8 @@ func (_mock *Repos) Update(ctx context.Context, p domain.Principal, name domain.
359 return r0, r1359 return r0, r1
360 }360 }
361 361
362-// Repos_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update'362+// RepoService_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update'
363-type Repos_Update_Call struct {363+type RepoService_Update_Call struct {
364 *mock.Call364 *mock.Call
365 }365 }
366 366
@@ -368,12 +368,12 @@ type Repos_Update_Call struct {
368 // - ctx context.Context368 // - ctx context.Context
369 // - p domain.Principal369 // - p domain.Principal
370 // - name domain.RepoName370 // - name domain.RepoName
371-// - params app.UpdateParams371+// - params ports.UpdateRepoParams
372-func (_e *Repos_Expecter) Update(ctx any, p any, name any, params any) *Repos_Update_Call {372+func (_e *RepoService_Expecter) Update(ctx any, p any, name any, params any) *RepoService_Update_Call {
373- return &Repos_Update_Call{Call: _e.mock.On("Update", ctx, p, name, params)}373+ return &RepoService_Update_Call{Call: _e.mock.On("Update", ctx, p, name, params)}
374 }374 }
375 375
376-func (_c *Repos_Update_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName, params app.UpdateParams)) *Repos_Update_Call {376+func (_c *RepoService_Update_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName, params ports.UpdateRepoParams)) *RepoService_Update_Call {
377 _c.Call.Run(func(args mock.Arguments) {377 _c.Call.Run(func(args mock.Arguments) {
378 var arg0 context.Context378 var arg0 context.Context
379 if args[0] != nil {379 if args[0] != nil {
@@ -387,9 +387,9 @@ func (_c *Repos_Update_Call) Run(run func(ctx context.Context, p domain.Principa
387 if args[2] != nil {387 if args[2] != nil {
388 arg2 = args[2].(domain.RepoName)388 arg2 = args[2].(domain.RepoName)
389 }389 }
390- var arg3 app.UpdateParams390+ var arg3 ports.UpdateRepoParams
391 if args[3] != nil {391 if args[3] != nil {
392- arg3 = args[3].(app.UpdateParams)392+ arg3 = args[3].(ports.UpdateRepoParams)
393 }393 }
394 run(394 run(
395 arg0,395 arg0,
@@ -401,33 +401,33 @@ func (_c *Repos_Update_Call) Run(run func(ctx context.Context, p domain.Principa
401 return _c401 return _c
402 }402 }
403 403
404-func (_c *Repos_Update_Call) Return(repo domain.Repo, err error) *Repos_Update_Call {404+func (_c *RepoService_Update_Call) Return(repo domain.Repo, err error) *RepoService_Update_Call {
405 _c.Call.Return(repo, err)405 _c.Call.Return(repo, err)
406 return _c406 return _c
407 }407 }
408 408
409-func (_c *Repos_Update_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName, params app.UpdateParams) (domain.Repo, error)) *Repos_Update_Call {409+func (_c *RepoService_Update_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName, params ports.UpdateRepoParams) (domain.Repo, error)) *RepoService_Update_Call {
410 _c.Call.Return(run)410 _c.Call.Return(run)
411 return _c411 return _c
412 }412 }
413 413
414-// View provides a mock function for the type Repos414+// View provides a mock function for the type RepoService
415-func (_mock *Repos) View(ctx context.Context, repo domain.Repo) (app.RepoView, error) {415+func (_mock *RepoService) View(ctx context.Context, repo domain.Repo) (ports.RepoView, error) {
416 ret := _mock.Called(ctx, repo)416 ret := _mock.Called(ctx, repo)
417 417
418 if len(ret) == 0 {418 if len(ret) == 0 {
419 panic("no return value specified for View")419 panic("no return value specified for View")
420 }420 }
421 421
422- var r0 app.RepoView422+ var r0 ports.RepoView
423 var r1 error423 var r1 error
424- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Repo) (app.RepoView, error)); ok {424+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Repo) (ports.RepoView, error)); ok {
425 return returnFunc(ctx, repo)425 return returnFunc(ctx, repo)
426 }426 }
427- if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Repo) app.RepoView); ok {427+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Repo) ports.RepoView); ok {
428 r0 = returnFunc(ctx, repo)428 r0 = returnFunc(ctx, repo)
429 } else {429 } else {
430- r0 = ret.Get(0).(app.RepoView)430+ r0 = ret.Get(0).(ports.RepoView)
431 }431 }
432 if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Repo) error); ok {432 if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Repo) error); ok {
433 r1 = returnFunc(ctx, repo)433 r1 = returnFunc(ctx, repo)
@@ -437,19 +437,19 @@ func (_mock *Repos) View(ctx context.Context, repo domain.Repo) (app.RepoView, e
437 return r0, r1437 return r0, r1
438 }438 }
439 439
440-// Repos_View_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'View'440+// RepoService_View_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'View'
441-type Repos_View_Call struct {441+type RepoService_View_Call struct {
442 *mock.Call442 *mock.Call
443 }443 }
444 444
445 // View is a helper method to define mock.On call445 // View is a helper method to define mock.On call
446 // - ctx context.Context446 // - ctx context.Context
447 // - repo domain.Repo447 // - repo domain.Repo
448-func (_e *Repos_Expecter) View(ctx any, repo any) *Repos_View_Call {448+func (_e *RepoService_Expecter) View(ctx any, repo any) *RepoService_View_Call {
449- return &Repos_View_Call{Call: _e.mock.On("View", ctx, repo)}449+ return &RepoService_View_Call{Call: _e.mock.On("View", ctx, repo)}
450 }450 }
451 451
452-func (_c *Repos_View_Call) Run(run func(ctx context.Context, repo domain.Repo)) *Repos_View_Call {452+func (_c *RepoService_View_Call) Run(run func(ctx context.Context, repo domain.Repo)) *RepoService_View_Call {
453 _c.Call.Run(func(args mock.Arguments) {453 _c.Call.Run(func(args mock.Arguments) {
454 var arg0 context.Context454 var arg0 context.Context
455 if args[0] != nil {455 if args[0] != nil {
@@ -467,12 +467,12 @@ func (_c *Repos_View_Call) Run(run func(ctx context.Context, repo domain.Repo))
467 return _c467 return _c
468 }468 }
469 469
470-func (_c *Repos_View_Call) Return(repoView app.RepoView, err error) *Repos_View_Call {470+func (_c *RepoService_View_Call) Return(repoView ports.RepoView, err error) *RepoService_View_Call {
471 _c.Call.Return(repoView, err)471 _c.Call.Return(repoView, err)
472 return _c472 return _c
473 }473 }
474 474
475-func (_c *Repos_View_Call) RunAndReturn(run func(ctx context.Context, repo domain.Repo) (app.RepoView, error)) *Repos_View_Call {475+func (_c *RepoService_View_Call) RunAndReturn(run func(ctx context.Context, repo domain.Repo) (ports.RepoView, error)) *RepoService_View_Call {
476 _c.Call.Return(run)476 _c.Call.Return(run)
477 return _c477 return _c
478 }478 }
R internal/app/mocks/Revalidator.go → internal/ports/mocks/Revalidator.go +0 -0 renamed, 100% similar

renamed, 100% similar

A internal/ports/mocks/SessionService.go +172 -0
@@ -0,0 +1,172 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package mocks
6+
7+import (
8+ "context"
9+
10+ "bnema.dev/ermite/internal/domain"
11+ "bnema.dev/ermite/internal/ports"
12+ mock "github.com/stretchr/testify/mock"
13+)
14+
15+// NewSessionService creates a new instance of SessionService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
16+// The first argument is typically a *testing.T value.
17+func NewSessionService(t interface {
18+ mock.TestingT
19+ Cleanup(func())
20+}) *SessionService {
21+ if helper, ok := t.(interface{ Helper() }); ok {
22+ helper.Helper()
23+ }
24+
25+ mock := &SessionService{}
26+ mock.Mock.Test(t)
27+
28+ t.Cleanup(func() {
29+ if helper, ok := t.(interface{ Helper() }); ok {
30+ helper.Helper()
31+ }
32+ mock.AssertExpectations(t)
33+ })
34+
35+ return mock
36+}
37+
38+// SessionService is an autogenerated mock type for the SessionService type
39+type SessionService struct {
40+ mock.Mock
41+}
42+
43+type SessionService_Expecter struct {
44+ mock *mock.Mock
45+}
46+
47+func (_m *SessionService) EXPECT() *SessionService_Expecter {
48+ return &SessionService_Expecter{mock: &_m.Mock}
49+}
50+
51+// CreateLoginLink provides a mock function for the type SessionService
52+func (_mock *SessionService) CreateLoginLink(ctx context.Context, p domain.Principal) (ports.LoginLink, error) {
53+ ret := _mock.Called(ctx, p)
54+
55+ if len(ret) == 0 {
56+ panic("no return value specified for CreateLoginLink")
57+ }
58+
59+ var r0 ports.LoginLink
60+ var r1 error
61+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal) (ports.LoginLink, error)); ok {
62+ return returnFunc(ctx, p)
63+ }
64+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal) ports.LoginLink); ok {
65+ r0 = returnFunc(ctx, p)
66+ } else {
67+ r0 = ret.Get(0).(ports.LoginLink)
68+ }
69+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal) error); ok {
70+ r1 = returnFunc(ctx, p)
71+ } else {
72+ r1 = ret.Error(1)
73+ }
74+ return r0, r1
75+}
76+
77+// SessionService_CreateLoginLink_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateLoginLink'
78+type SessionService_CreateLoginLink_Call struct {
79+ *mock.Call
80+}
81+
82+// CreateLoginLink is a helper method to define mock.On call
83+// - ctx context.Context
84+// - p domain.Principal
85+func (_e *SessionService_Expecter) CreateLoginLink(ctx any, p any) *SessionService_CreateLoginLink_Call {
86+ return &SessionService_CreateLoginLink_Call{Call: _e.mock.On("CreateLoginLink", ctx, p)}
87+}
88+
89+func (_c *SessionService_CreateLoginLink_Call) Run(run func(ctx context.Context, p domain.Principal)) *SessionService_CreateLoginLink_Call {
90+ _c.Call.Run(func(args mock.Arguments) {
91+ var arg0 context.Context
92+ if args[0] != nil {
93+ arg0 = args[0].(context.Context)
94+ }
95+ var arg1 domain.Principal
96+ if args[1] != nil {
97+ arg1 = args[1].(domain.Principal)
98+ }
99+ run(
100+ arg0,
101+ arg1,
102+ )
103+ })
104+ return _c
105+}
106+
107+func (_c *SessionService_CreateLoginLink_Call) Return(loginLink ports.LoginLink, err error) *SessionService_CreateLoginLink_Call {
108+ _c.Call.Return(loginLink, err)
109+ return _c
110+}
111+
112+func (_c *SessionService_CreateLoginLink_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal) (ports.LoginLink, error)) *SessionService_CreateLoginLink_Call {
113+ _c.Call.Return(run)
114+ return _c
115+}
116+
117+// SignOutEverywhere provides a mock function for the type SessionService
118+func (_mock *SessionService) SignOutEverywhere(ctx context.Context, p domain.Principal) error {
119+ ret := _mock.Called(ctx, p)
120+
121+ if len(ret) == 0 {
122+ panic("no return value specified for SignOutEverywhere")
123+ }
124+
125+ var r0 error
126+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal) error); ok {
127+ r0 = returnFunc(ctx, p)
128+ } else {
129+ r0 = ret.Error(0)
130+ }
131+ return r0
132+}
133+
134+// SessionService_SignOutEverywhere_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SignOutEverywhere'
135+type SessionService_SignOutEverywhere_Call struct {
136+ *mock.Call
137+}
138+
139+// SignOutEverywhere is a helper method to define mock.On call
140+// - ctx context.Context
141+// - p domain.Principal
142+func (_e *SessionService_Expecter) SignOutEverywhere(ctx any, p any) *SessionService_SignOutEverywhere_Call {
143+ return &SessionService_SignOutEverywhere_Call{Call: _e.mock.On("SignOutEverywhere", ctx, p)}
144+}
145+
146+func (_c *SessionService_SignOutEverywhere_Call) Run(run func(ctx context.Context, p domain.Principal)) *SessionService_SignOutEverywhere_Call {
147+ _c.Call.Run(func(args mock.Arguments) {
148+ var arg0 context.Context
149+ if args[0] != nil {
150+ arg0 = args[0].(context.Context)
151+ }
152+ var arg1 domain.Principal
153+ if args[1] != nil {
154+ arg1 = args[1].(domain.Principal)
155+ }
156+ run(
157+ arg0,
158+ arg1,
159+ )
160+ })
161+ return _c
162+}
163+
164+func (_c *SessionService_SignOutEverywhere_Call) Return(err error) *SessionService_SignOutEverywhere_Call {
165+ _c.Call.Return(err)
166+ return _c
167+}
168+
169+func (_c *SessionService_SignOutEverywhere_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal) error) *SessionService_SignOutEverywhere_Call {
170+ _c.Call.Return(run)
171+ return _c
172+}
A internal/ports/mocks/SigningKeyService.go +90 -0
@@ -0,0 +1,90 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package mocks
6+
7+import (
8+ "bnema.dev/ermite/internal/domain"
9+ mock "github.com/stretchr/testify/mock"
10+)
11+
12+// NewSigningKeyService creates a new instance of SigningKeyService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
13+// The first argument is typically a *testing.T value.
14+func NewSigningKeyService(t interface {
15+ mock.TestingT
16+ Cleanup(func())
17+}) *SigningKeyService {
18+ if helper, ok := t.(interface{ Helper() }); ok {
19+ helper.Helper()
20+ }
21+
22+ mock := &SigningKeyService{}
23+ mock.Mock.Test(t)
24+
25+ t.Cleanup(func() {
26+ if helper, ok := t.(interface{ Helper() }); ok {
27+ helper.Helper()
28+ }
29+ mock.AssertExpectations(t)
30+ })
31+
32+ return mock
33+}
34+
35+// SigningKeyService is an autogenerated mock type for the SigningKeyService type
36+type SigningKeyService struct {
37+ mock.Mock
38+}
39+
40+type SigningKeyService_Expecter struct {
41+ mock *mock.Mock
42+}
43+
44+func (_m *SigningKeyService) EXPECT() *SigningKeyService_Expecter {
45+ return &SigningKeyService_Expecter{mock: &_m.Mock}
46+}
47+
48+// Info provides a mock function for the type SigningKeyService
49+func (_mock *SigningKeyService) Info() domain.SigningKeyInfo {
50+ ret := _mock.Called()
51+
52+ if len(ret) == 0 {
53+ panic("no return value specified for Info")
54+ }
55+
56+ var r0 domain.SigningKeyInfo
57+ if returnFunc, ok := ret.Get(0).(func() domain.SigningKeyInfo); ok {
58+ r0 = returnFunc()
59+ } else {
60+ r0 = ret.Get(0).(domain.SigningKeyInfo)
61+ }
62+ return r0
63+}
64+
65+// SigningKeyService_Info_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Info'
66+type SigningKeyService_Info_Call struct {
67+ *mock.Call
68+}
69+
70+// Info is a helper method to define mock.On call
71+func (_e *SigningKeyService_Expecter) Info() *SigningKeyService_Info_Call {
72+ return &SigningKeyService_Info_Call{Call: _e.mock.On("Info")}
73+}
74+
75+func (_c *SigningKeyService_Info_Call) Run(run func()) *SigningKeyService_Info_Call {
76+ _c.Call.Run(func(args mock.Arguments) {
77+ run()
78+ })
79+ return _c
80+}
81+
82+func (_c *SigningKeyService_Info_Call) Return(signingKeyInfo domain.SigningKeyInfo) *SigningKeyService_Info_Call {
83+ _c.Call.Return(signingKeyInfo)
84+ return _c
85+}
86+
87+func (_c *SigningKeyService_Info_Call) RunAndReturn(run func() domain.SigningKeyInfo) *SigningKeyService_Info_Call {
88+ _c.Call.Return(run)
89+ return _c
90+}
A internal/ports/mocks/TokenService.go +262 -0
@@ -0,0 +1,262 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package mocks
6+
7+import (
8+ "context"
9+
10+ "bnema.dev/ermite/internal/domain"
11+ "bnema.dev/ermite/internal/ports"
12+ mock "github.com/stretchr/testify/mock"
13+)
14+
15+// NewTokenService creates a new instance of TokenService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
16+// The first argument is typically a *testing.T value.
17+func NewTokenService(t interface {
18+ mock.TestingT
19+ Cleanup(func())
20+}) *TokenService {
21+ if helper, ok := t.(interface{ Helper() }); ok {
22+ helper.Helper()
23+ }
24+
25+ mock := &TokenService{}
26+ mock.Mock.Test(t)
27+
28+ t.Cleanup(func() {
29+ if helper, ok := t.(interface{ Helper() }); ok {
30+ helper.Helper()
31+ }
32+ mock.AssertExpectations(t)
33+ })
34+
35+ return mock
36+}
37+
38+// TokenService is an autogenerated mock type for the TokenService type
39+type TokenService struct {
40+ mock.Mock
41+}
42+
43+type TokenService_Expecter struct {
44+ mock *mock.Mock
45+}
46+
47+func (_m *TokenService) EXPECT() *TokenService_Expecter {
48+ return &TokenService_Expecter{mock: &_m.Mock}
49+}
50+
51+// Create provides a mock function for the type TokenService
52+func (_mock *TokenService) Create(ctx context.Context, p domain.Principal, params ports.CreateTokenParams) (ports.CreatedToken, error) {
53+ ret := _mock.Called(ctx, p, params)
54+
55+ if len(ret) == 0 {
56+ panic("no return value specified for Create")
57+ }
58+
59+ var r0 ports.CreatedToken
60+ var r1 error
61+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, ports.CreateTokenParams) (ports.CreatedToken, error)); ok {
62+ return returnFunc(ctx, p, params)
63+ }
64+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, ports.CreateTokenParams) ports.CreatedToken); ok {
65+ r0 = returnFunc(ctx, p, params)
66+ } else {
67+ r0 = ret.Get(0).(ports.CreatedToken)
68+ }
69+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, ports.CreateTokenParams) error); ok {
70+ r1 = returnFunc(ctx, p, params)
71+ } else {
72+ r1 = ret.Error(1)
73+ }
74+ return r0, r1
75+}
76+
77+// TokenService_Create_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Create'
78+type TokenService_Create_Call struct {
79+ *mock.Call
80+}
81+
82+// Create is a helper method to define mock.On call
83+// - ctx context.Context
84+// - p domain.Principal
85+// - params ports.CreateTokenParams
86+func (_e *TokenService_Expecter) Create(ctx any, p any, params any) *TokenService_Create_Call {
87+ return &TokenService_Create_Call{Call: _e.mock.On("Create", ctx, p, params)}
88+}
89+
90+func (_c *TokenService_Create_Call) Run(run func(ctx context.Context, p domain.Principal, params ports.CreateTokenParams)) *TokenService_Create_Call {
91+ _c.Call.Run(func(args mock.Arguments) {
92+ var arg0 context.Context
93+ if args[0] != nil {
94+ arg0 = args[0].(context.Context)
95+ }
96+ var arg1 domain.Principal
97+ if args[1] != nil {
98+ arg1 = args[1].(domain.Principal)
99+ }
100+ var arg2 ports.CreateTokenParams
101+ if args[2] != nil {
102+ arg2 = args[2].(ports.CreateTokenParams)
103+ }
104+ run(
105+ arg0,
106+ arg1,
107+ arg2,
108+ )
109+ })
110+ return _c
111+}
112+
113+func (_c *TokenService_Create_Call) Return(createdToken ports.CreatedToken, err error) *TokenService_Create_Call {
114+ _c.Call.Return(createdToken, err)
115+ return _c
116+}
117+
118+func (_c *TokenService_Create_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, params ports.CreateTokenParams) (ports.CreatedToken, error)) *TokenService_Create_Call {
119+ _c.Call.Return(run)
120+ return _c
121+}
122+
123+// ListPage provides a mock function for the type TokenService
124+func (_mock *TokenService) ListPage(ctx context.Context, p domain.Principal, size int, token string) (ports.TokenPage, error) {
125+ ret := _mock.Called(ctx, p, size, token)
126+
127+ if len(ret) == 0 {
128+ panic("no return value specified for ListPage")
129+ }
130+
131+ var r0 ports.TokenPage
132+ var r1 error
133+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, int, string) (ports.TokenPage, error)); ok {
134+ return returnFunc(ctx, p, size, token)
135+ }
136+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, int, string) ports.TokenPage); ok {
137+ r0 = returnFunc(ctx, p, size, token)
138+ } else {
139+ r0 = ret.Get(0).(ports.TokenPage)
140+ }
141+ if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, int, string) error); ok {
142+ r1 = returnFunc(ctx, p, size, token)
143+ } else {
144+ r1 = ret.Error(1)
145+ }
146+ return r0, r1
147+}
148+
149+// TokenService_ListPage_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListPage'
150+type TokenService_ListPage_Call struct {
151+ *mock.Call
152+}
153+
154+// ListPage is a helper method to define mock.On call
155+// - ctx context.Context
156+// - p domain.Principal
157+// - size int
158+// - token string
159+func (_e *TokenService_Expecter) ListPage(ctx any, p any, size any, token any) *TokenService_ListPage_Call {
160+ return &TokenService_ListPage_Call{Call: _e.mock.On("ListPage", ctx, p, size, token)}
161+}
162+
163+func (_c *TokenService_ListPage_Call) Run(run func(ctx context.Context, p domain.Principal, size int, token string)) *TokenService_ListPage_Call {
164+ _c.Call.Run(func(args mock.Arguments) {
165+ var arg0 context.Context
166+ if args[0] != nil {
167+ arg0 = args[0].(context.Context)
168+ }
169+ var arg1 domain.Principal
170+ if args[1] != nil {
171+ arg1 = args[1].(domain.Principal)
172+ }
173+ var arg2 int
174+ if args[2] != nil {
175+ arg2 = args[2].(int)
176+ }
177+ var arg3 string
178+ if args[3] != nil {
179+ arg3 = args[3].(string)
180+ }
181+ run(
182+ arg0,
183+ arg1,
184+ arg2,
185+ arg3,
186+ )
187+ })
188+ return _c
189+}
190+
191+func (_c *TokenService_ListPage_Call) Return(tokenPage ports.TokenPage, err error) *TokenService_ListPage_Call {
192+ _c.Call.Return(tokenPage, err)
193+ return _c
194+}
195+
196+func (_c *TokenService_ListPage_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, size int, token string) (ports.TokenPage, error)) *TokenService_ListPage_Call {
197+ _c.Call.Return(run)
198+ return _c
199+}
200+
201+// Revoke provides a mock function for the type TokenService
202+func (_mock *TokenService) Revoke(ctx context.Context, p domain.Principal, id string) error {
203+ ret := _mock.Called(ctx, p, id)
204+
205+ if len(ret) == 0 {
206+ panic("no return value specified for Revoke")
207+ }
208+
209+ var r0 error
210+ if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, string) error); ok {
211+ r0 = returnFunc(ctx, p, id)
212+ } else {
213+ r0 = ret.Error(0)
214+ }
215+ return r0
216+}
217+
218+// TokenService_Revoke_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Revoke'
219+type TokenService_Revoke_Call struct {
220+ *mock.Call
221+}
222+
223+// Revoke is a helper method to define mock.On call
224+// - ctx context.Context
225+// - p domain.Principal
226+// - id string
227+func (_e *TokenService_Expecter) Revoke(ctx any, p any, id any) *TokenService_Revoke_Call {
228+ return &TokenService_Revoke_Call{Call: _e.mock.On("Revoke", ctx, p, id)}
229+}
230+
231+func (_c *TokenService_Revoke_Call) Run(run func(ctx context.Context, p domain.Principal, id string)) *TokenService_Revoke_Call {
232+ _c.Call.Run(func(args mock.Arguments) {
233+ var arg0 context.Context
234+ if args[0] != nil {
235+ arg0 = args[0].(context.Context)
236+ }
237+ var arg1 domain.Principal
238+ if args[1] != nil {
239+ arg1 = args[1].(domain.Principal)
240+ }
241+ var arg2 string
242+ if args[2] != nil {
243+ arg2 = args[2].(string)
244+ }
245+ run(
246+ arg0,
247+ arg1,
248+ arg2,
249+ )
250+ })
251+ return _c
252+}
253+
254+func (_c *TokenService_Revoke_Call) Return(err error) *TokenService_Revoke_Call {
255+ _c.Call.Return(err)
256+ return _c
257+}
258+
259+func (_c *TokenService_Revoke_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, id string) error) *TokenService_Revoke_Call {
260+ _c.Call.Return(run)
261+ return _c
262+}
R internal/adapters/web/mocks/SessionAuth.go → internal/ports/mocks/WebSessionService.go +36 -36 renamed, 52% similar
@@ -11,17 +11,17 @@ import (
11 mock "github.com/stretchr/testify/mock"11 mock "github.com/stretchr/testify/mock"
12 )12 )
13 13
14-// NewSessionAuth creates a new instance of SessionAuth. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.14+// NewWebSessionService creates a new instance of WebSessionService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
15 // The first argument is typically a *testing.T value.15 // The first argument is typically a *testing.T value.
16-func NewSessionAuth(t interface {16+func NewWebSessionService(t interface {
17 mock.TestingT17 mock.TestingT
18 Cleanup(func())18 Cleanup(func())
19-}) *SessionAuth {19+}) *WebSessionService {
20 if helper, ok := t.(interface{ Helper() }); ok {20 if helper, ok := t.(interface{ Helper() }); ok {
21 helper.Helper()21 helper.Helper()
22 }22 }
23 23
24- mock := &SessionAuth{}24+ mock := &WebSessionService{}
25 mock.Mock.Test(t)25 mock.Mock.Test(t)
26 26
27 t.Cleanup(func() {27 t.Cleanup(func() {
@@ -34,21 +34,21 @@ func NewSessionAuth(t interface {
34 return mock34 return mock
35 }35 }
36 36
37-// SessionAuth is an autogenerated mock type for the SessionAuth type37+// WebSessionService is an autogenerated mock type for the WebSessionService type
38-type SessionAuth struct {38+type WebSessionService struct {
39 mock.Mock39 mock.Mock
40 }40 }
41 41
42-type SessionAuth_Expecter struct {42+type WebSessionService_Expecter struct {
43 mock *mock.Mock43 mock *mock.Mock
44 }44 }
45 45
46-func (_m *SessionAuth) EXPECT() *SessionAuth_Expecter {46+func (_m *WebSessionService) EXPECT() *WebSessionService_Expecter {
47- return &SessionAuth_Expecter{mock: &_m.Mock}47+ return &WebSessionService_Expecter{mock: &_m.Mock}
48 }48 }
49 49
50-// Login provides a mock function for the type SessionAuth50+// Login provides a mock function for the type WebSessionService
51-func (_mock *SessionAuth) Login(ctx context.Context, nonce string) (string, time.Duration, error) {51+func (_mock *WebSessionService) Login(ctx context.Context, nonce string) (string, time.Duration, error) {
52 ret := _mock.Called(ctx, nonce)52 ret := _mock.Called(ctx, nonce)
53 53
54 if len(ret) == 0 {54 if len(ret) == 0 {
@@ -79,19 +79,19 @@ func (_mock *SessionAuth) Login(ctx context.Context, nonce string) (string, time
79 return r0, r1, r279 return r0, r1, r2
80 }80 }
81 81
82-// SessionAuth_Login_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Login'82+// WebSessionService_Login_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Login'
83-type SessionAuth_Login_Call struct {83+type WebSessionService_Login_Call struct {
84 *mock.Call84 *mock.Call
85 }85 }
86 86
87 // Login is a helper method to define mock.On call87 // Login is a helper method to define mock.On call
88 // - ctx context.Context88 // - ctx context.Context
89 // - nonce string89 // - nonce string
90-func (_e *SessionAuth_Expecter) Login(ctx any, nonce any) *SessionAuth_Login_Call {90+func (_e *WebSessionService_Expecter) Login(ctx any, nonce any) *WebSessionService_Login_Call {
91- return &SessionAuth_Login_Call{Call: _e.mock.On("Login", ctx, nonce)}91+ return &WebSessionService_Login_Call{Call: _e.mock.On("Login", ctx, nonce)}
92 }92 }
93 93
94-func (_c *SessionAuth_Login_Call) Run(run func(ctx context.Context, nonce string)) *SessionAuth_Login_Call {94+func (_c *WebSessionService_Login_Call) Run(run func(ctx context.Context, nonce string)) *WebSessionService_Login_Call {
95 _c.Call.Run(func(args mock.Arguments) {95 _c.Call.Run(func(args mock.Arguments) {
96 var arg0 context.Context96 var arg0 context.Context
97 if args[0] != nil {97 if args[0] != nil {
@@ -109,18 +109,18 @@ func (_c *SessionAuth_Login_Call) Run(run func(ctx context.Context, nonce string
109 return _c109 return _c
110 }110 }
111 111
112-func (_c *SessionAuth_Login_Call) Return(token string, lifetime time.Duration, err error) *SessionAuth_Login_Call {112+func (_c *WebSessionService_Login_Call) Return(token string, lifetime time.Duration, err error) *WebSessionService_Login_Call {
113 _c.Call.Return(token, lifetime, err)113 _c.Call.Return(token, lifetime, err)
114 return _c114 return _c
115 }115 }
116 116
117-func (_c *SessionAuth_Login_Call) RunAndReturn(run func(ctx context.Context, nonce string) (string, time.Duration, error)) *SessionAuth_Login_Call {117+func (_c *WebSessionService_Login_Call) RunAndReturn(run func(ctx context.Context, nonce string) (string, time.Duration, error)) *WebSessionService_Login_Call {
118 _c.Call.Return(run)118 _c.Call.Return(run)
119 return _c119 return _c
120 }120 }
121 121
122-// LoginLinkValid provides a mock function for the type SessionAuth122+// LoginLinkValid provides a mock function for the type WebSessionService
123-func (_mock *SessionAuth) LoginLinkValid(nonce string) bool {123+func (_mock *WebSessionService) LoginLinkValid(nonce string) bool {
124 ret := _mock.Called(nonce)124 ret := _mock.Called(nonce)
125 125
126 if len(ret) == 0 {126 if len(ret) == 0 {
@@ -136,18 +136,18 @@ func (_mock *SessionAuth) LoginLinkValid(nonce string) bool {
136 return r0136 return r0
137 }137 }
138 138
139-// SessionAuth_LoginLinkValid_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LoginLinkValid'139+// WebSessionService_LoginLinkValid_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LoginLinkValid'
140-type SessionAuth_LoginLinkValid_Call struct {140+type WebSessionService_LoginLinkValid_Call struct {
141 *mock.Call141 *mock.Call
142 }142 }
143 143
144 // LoginLinkValid is a helper method to define mock.On call144 // LoginLinkValid is a helper method to define mock.On call
145 // - nonce string145 // - nonce string
146-func (_e *SessionAuth_Expecter) LoginLinkValid(nonce any) *SessionAuth_LoginLinkValid_Call {146+func (_e *WebSessionService_Expecter) LoginLinkValid(nonce any) *WebSessionService_LoginLinkValid_Call {
147- return &SessionAuth_LoginLinkValid_Call{Call: _e.mock.On("LoginLinkValid", nonce)}147+ return &WebSessionService_LoginLinkValid_Call{Call: _e.mock.On("LoginLinkValid", nonce)}
148 }148 }
149 149
150-func (_c *SessionAuth_LoginLinkValid_Call) Run(run func(nonce string)) *SessionAuth_LoginLinkValid_Call {150+func (_c *WebSessionService_LoginLinkValid_Call) Run(run func(nonce string)) *WebSessionService_LoginLinkValid_Call {
151 _c.Call.Run(func(args mock.Arguments) {151 _c.Call.Run(func(args mock.Arguments) {
152 var arg0 string152 var arg0 string
153 if args[0] != nil {153 if args[0] != nil {
@@ -160,18 +160,18 @@ func (_c *SessionAuth_LoginLinkValid_Call) Run(run func(nonce string)) *SessionA
160 return _c160 return _c
161 }161 }
162 162
163-func (_c *SessionAuth_LoginLinkValid_Call) Return(b bool) *SessionAuth_LoginLinkValid_Call {163+func (_c *WebSessionService_LoginLinkValid_Call) Return(b bool) *WebSessionService_LoginLinkValid_Call {
164 _c.Call.Return(b)164 _c.Call.Return(b)
165 return _c165 return _c
166 }166 }
167 167
168-func (_c *SessionAuth_LoginLinkValid_Call) RunAndReturn(run func(nonce string) bool) *SessionAuth_LoginLinkValid_Call {168+func (_c *WebSessionService_LoginLinkValid_Call) RunAndReturn(run func(nonce string) bool) *WebSessionService_LoginLinkValid_Call {
169 _c.Call.Return(run)169 _c.Call.Return(run)
170 return _c170 return _c
171 }171 }
172 172
173-// Verify provides a mock function for the type SessionAuth173+// Verify provides a mock function for the type WebSessionService
174-func (_mock *SessionAuth) Verify(ctx context.Context, token string) (context.Context, bool, error) {174+func (_mock *WebSessionService) Verify(ctx context.Context, token string) (context.Context, bool, error) {
175 ret := _mock.Called(ctx, token)175 ret := _mock.Called(ctx, token)
176 176
177 if len(ret) == 0 {177 if len(ret) == 0 {
@@ -204,19 +204,19 @@ func (_mock *SessionAuth) Verify(ctx context.Context, token string) (context.Con
204 return r0, r1, r2204 return r0, r1, r2
205 }205 }
206 206
207-// SessionAuth_Verify_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Verify'207+// WebSessionService_Verify_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Verify'
208-type SessionAuth_Verify_Call struct {208+type WebSessionService_Verify_Call struct {
209 *mock.Call209 *mock.Call
210 }210 }
211 211
212 // Verify is a helper method to define mock.On call212 // Verify is a helper method to define mock.On call
213 // - ctx context.Context213 // - ctx context.Context
214 // - token string214 // - token string
215-func (_e *SessionAuth_Expecter) Verify(ctx any, token any) *SessionAuth_Verify_Call {215+func (_e *WebSessionService_Expecter) Verify(ctx any, token any) *WebSessionService_Verify_Call {
216- return &SessionAuth_Verify_Call{Call: _e.mock.On("Verify", ctx, token)}216+ return &WebSessionService_Verify_Call{Call: _e.mock.On("Verify", ctx, token)}
217 }217 }
218 218
219-func (_c *SessionAuth_Verify_Call) Run(run func(ctx context.Context, token string)) *SessionAuth_Verify_Call {219+func (_c *WebSessionService_Verify_Call) Run(run func(ctx context.Context, token string)) *WebSessionService_Verify_Call {
220 _c.Call.Run(func(args mock.Arguments) {220 _c.Call.Run(func(args mock.Arguments) {
221 var arg0 context.Context221 var arg0 context.Context
222 if args[0] != nil {222 if args[0] != nil {
@@ -234,12 +234,12 @@ func (_c *SessionAuth_Verify_Call) Run(run func(ctx context.Context, token strin
234 return _c234 return _c
235 }235 }
236 236
237-func (_c *SessionAuth_Verify_Call) Return(owner context.Context, ok bool, err error) *SessionAuth_Verify_Call {237+func (_c *WebSessionService_Verify_Call) Return(owner context.Context, ok bool, err error) *WebSessionService_Verify_Call {
238 _c.Call.Return(owner, ok, err)238 _c.Call.Return(owner, ok, err)
239 return _c239 return _c
240 }240 }
241 241
242-func (_c *SessionAuth_Verify_Call) RunAndReturn(run func(ctx context.Context, token string) (context.Context, bool, error)) *SessionAuth_Verify_Call {242+func (_c *WebSessionService_Verify_Call) RunAndReturn(run func(ctx context.Context, token string) (context.Context, bool, error)) *WebSessionService_Verify_Call {
243 _c.Call.Return(run)243 _c.Call.Return(run)
244 return _c244 return _c
245 }245 }
M internal/ports/ports.go +9 -2
@@ -1,7 +1,14 @@
1 // SPDX-License-Identifier: AGPL-3.0-or-later1 // SPDX-License-Identifier: AGPL-3.0-or-later
2 2
3-// Package ports declares the interfaces the use cases need from the outside3+// Package ports declares every interface that crosses a layer, and the
4-// world. Adapters implement them; cmd wires them.4+// parameter and result types of the use case interfaces.
5+//
6+// Outbound ports (ports.go) are what the use cases need from the outside
7+// world: adapters implement them. Inbound ports (services.go) are the use
8+// cases as the adapters that serve requests see them: the types of package app
9+// implement them. Adapters and app depend on this package and on domain, never
10+// on each other; cmd wires the implementations. All the mocks are generated in
11+// ports/mocks.
5 package ports12 package ports
6 13
7 import (14 import (
A internal/ports/services.go +162 -0
@@ -0,0 +1,162 @@
1+// SPDX-License-Identifier: AGPL-3.0-or-later
2+
3+package ports
4+
5+import (
6+ "context"
7+ "errors"
8+ "io"
9+ "time"
10+
11+ "bnema.dev/ermite/internal/domain"
12+)
13+
14+// The interfaces below are the use cases as the inbound adapters (the API,
15+// Git over HTTP, the web) and the entrypoint see them. The types of package
16+// app implement them, each assertion being made in app; the mocks in
17+// ports/mocks stand in for them in the tests of the adapters.
18+
19+// AuthService turns the secret of a request into a principal. It returns
20+// domain.ErrUnauthenticated for a secret that is not valid. *app.Auth
21+// implements it.
22+type AuthService interface {
23+ Authenticate(ctx context.Context, secret string) (domain.Principal, error)
24+}
25+
26+// Revalidator confirms that a principal obtained earlier is still
27+// authorized. *app.Auth implements it.
28+type Revalidator interface {
29+ Revalidate(ctx context.Context, p domain.Principal) error
30+}
31+
32+// RepoLookup is the read side of a single repository. A repository the
33+// principal may not read is domain.ErrNotFound. *app.Repos implements it, as
34+// part of RepoService.
35+type RepoLookup interface {
36+ Get(ctx context.Context, p domain.Principal, name domain.RepoName) (domain.Repo, error)
37+}
38+
39+// RepoService is the repository use cases the API serves. *app.Repos
40+// implements it.
41+type RepoService interface {
42+ RepoLookup
43+ ListPage(ctx context.Context, p domain.Principal, size int, token string) (RepoPage, error)
44+ View(ctx context.Context, repo domain.Repo) (RepoView, error)
45+ Create(ctx context.Context, p domain.Principal, params CreateRepoParams) (domain.Repo, error)
46+ Update(ctx context.Context, p domain.Principal, name domain.RepoName, params UpdateRepoParams) (domain.Repo, error)
47+ Delete(ctx context.Context, p domain.Principal, name domain.RepoName) error
48+}
49+
50+// TokenService is the access token use cases the API serves. *app.Tokens
51+// implements it.
52+type TokenService interface {
53+ Create(ctx context.Context, p domain.Principal, params CreateTokenParams) (CreatedToken, error)
54+ ListPage(ctx context.Context, p domain.Principal, size int, token string) (TokenPage, error)
55+ Revoke(ctx context.Context, p domain.Principal, id string) error
56+}
57+
58+// BrowseService is the read side of a repository's content. *app.Browse
59+// implements it.
60+type BrowseService interface {
61+ ListRefs(ctx context.Context, p domain.Principal, repo domain.RepoName, params ListRefsParams) (RefPage, error)
62+ ResolveRevision(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision) (domain.ResolvedRevision, error)
63+ GetTree(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, path domain.TreePath, includeLastCommit bool) (domain.Tree, error)
64+ OpenBlob(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, path domain.TreePath) (domain.BlobInfo, io.ReadCloser, error)
65+ ListCommits(ctx context.Context, p domain.Principal, repo domain.RepoName, params ListCommitsParams) (HistoryPage, error)
66+ GetCommit(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision) (domain.CommitDetail, error)
67+}
68+
69+// EventService streams the events of the forge. *app.Events implements it.
70+type EventService interface {
71+ Watch(ctx context.Context, p domain.Principal, params WatchParams, send func(WatchEvent) error) error
72+}
73+
74+// MergeRequestService is the merge request use cases. *app.MergeRequests
75+// implements it.
76+type MergeRequestService interface {
77+ Create(ctx context.Context, p domain.Principal, name domain.RepoName, params CreateMergeRequestParams) (domain.MergeRequest, error)
78+ List(ctx context.Context, p domain.Principal, name domain.RepoName, params ListMergeRequestsParams) (MergeRequestPage, error)
79+ Get(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, sinceLastRead bool) (MergeRequestDetail, error)
80+ AddComment(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, params AddCommentParams) (domain.Comment, error)
81+ ResolveComment(ctx context.Context, p domain.Principal, name domain.RepoName, number, commentID int64, resolved bool) (domain.Comment, error)
82+ Close(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (domain.MergeRequest, error)
83+ MarkRead(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, head domain.ObjectID) (domain.ObjectID, error)
84+ Merge(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, params MergeParams) (MergeResult, error)
85+}
86+
87+// SigningKeyService tells which key signs the commits of the forge.
88+// *app.SigningKey implements it.
89+type SigningKeyService interface {
90+ Info() domain.SigningKeyInfo
91+}
92+
93+// SessionService is the owner's web session use cases the API serves.
94+// *app.Sessions implements it.
95+type SessionService interface {
96+ CreateLoginLink(ctx context.Context, p domain.Principal) (LoginLink, error)
97+ SignOutEverywhere(ctx context.Context, p domain.Principal) error
98+}
99+
100+// ErrLinkRejected is what WebSessionService.Login returns for a link that is
101+// unknown, expired or already used.
102+var ErrLinkRejected = errors.New("login link rejected")
103+
104+// WebSessionService is the session side of the use cases, as the web needs
105+// it. The entrypoint implements it over *app.Sessions.
106+type WebSessionService interface {
107+ // LoginLinkValid reports whether the link is pending, without using it.
108+ LoginLinkValid(nonce string) bool
109+ // Login uses the link and returns the session token and how long it
110+ // lasts, or ErrLinkRejected.
111+ Login(ctx context.Context, nonce string) (token string, lifetime time.Duration, err error)
112+ // Verify checks a session token. When it is valid it returns the context
113+ // to serve the request with, which carries the owner as the caller of the
114+ // API; when it is not, ok is false and err nil. An error means the check
115+ // could not be made (the request then runs as anonymous).
116+ Verify(ctx context.Context, token string) (owner context.Context, ok bool, err error)
117+}
118+
119+// PushService opens a repository for a push: it is domain.ErrUnauthenticated
120+// for an anonymous caller, domain.ErrNotFound for a repository the caller may
121+// not write and cannot see (or that is missing and may not be created),
122+// domain.ErrForbidden for one it can see but not write. With create, a
123+// missing repository the principal may write is created. *app.Pushes
124+// implements it.
125+type PushService interface {
126+ Open(ctx context.Context, p domain.Principal, name domain.RepoName, create bool) error
127+}
128+
129+// PushHookService is what the hook socket asks of the use cases.
130+// *app.Pushes implements it.
131+type PushHookService interface {
132+ // Check decides a push before Git applies it; an error matching
133+ // domain.ErrRejected refuses it, any other error refuses it as a failure.
134+ Check(ctx context.Context, p domain.Principal, name domain.RepoName, updates []domain.RefUpdate) error
135+ // Record handles an accepted push.
136+ Record(ctx context.Context, p domain.Principal, name domain.RepoName, updates []domain.RefUpdate) error
137+}
138+
139+// MaintenanceScheduler queues repository maintenance. *app.Maintainer
140+// implements it.
141+type MaintenanceScheduler interface {
142+ Schedule(ctx context.Context, name domain.RepoName)
143+}
144+
145+// HookTokens hands out the per-request tokens the hooks of a push present
146+// back to ermite, and the socket they present them on. The Git HTTP adapter
147+// implements it.
148+type HookTokens interface {
149+ // Register binds a new token to the repository and principal of a push.
150+ // release revokes it and must be called when the request ends.
151+ Register(ctx context.Context, repo domain.RepoName, p domain.Principal) (token string, release func())
152+ // Socket is the path of the hook socket.
153+ Socket() string
154+}
155+
156+// FailureLimiter counts the failed authentications per client. The adapters
157+// that authenticate share one, so that a client has one budget of failures.
158+// *clientlimit.Limiter implements it.
159+type FailureLimiter interface {
160+ Blocked(ctx context.Context, addr string) (bool, time.Duration)
161+ Fail(ctx context.Context, addr string)
162+}

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 3

Tags 44

↑↓ moveenter openesc close