feat(issues): resolved state, issue event and merge request issue link contracts
- commit
- 7288afe3322bbe150e4c427f3b495c6a2ab79a62
- author
- brice · · 2026-10-22 18:08 UTC
- committer
- brice · · 2026-10-22 18:41 UTC
- parent
- 1fb2421
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
22 files · +688 -103
M gen/ermite/storage/issue.pb.go +200 -27 generated file: collapsed
M internal/adapters/kvstore/issue_links.go +6 -1
| @@ -39,7 +39,7 @@ func issueRefKey(repo domain.RepoName, issue, mr int64) []byte { | |||
| 39 | 39 | } | |
| 40 | 40 | ||
| 41 | 41 | // ReplaceLinks atomically replaces both directions of the index. | |
| 42 | - | func (l *IssueLinks) ReplaceLinks(ctx context.Context, repo domain.RepoName, mr int64, issues, closes []int64) (added, removed []domain.IssueLink, err error) { | |
| 42 | + | func (l *IssueLinks) ReplaceLinks(ctx context.Context, repo domain.RepoName, mr int64, issues, closes []int64, ref ports.ReferenceEvent) (added, removed []domain.IssueLink, err error) { | |
| 43 | 43 | key := mrRefKey(repo, mr) | |
| 44 | 44 | item, exists := l.db.Get(ctx, key) | |
| 45 | 45 | var old storagepb.MergeRequestReferences | |
| @@ -149,3 +149,8 @@ func toSet(numbers []int64) map[int64]struct{} { | |||
| 149 | 149 | } | |
| 150 | 150 | return set | |
| 151 | 151 | } | |
| 152 | + | ||
| 153 | + | // EventsOfMergeRequest returns MR issue history. Persistence is implemented in Phase A. | |
| 154 | + | func (l *IssueLinks) EventsOfMergeRequest(ctx context.Context, repo domain.RepoName, mr int64) ([]domain.IssueEvent, error) { | |
| 155 | + | return nil, nil | |
| 156 | + | } | |
| @@ -39,7 +39,7 @@ func issueRefKey(repo domain.RepoName, issue, mr int64) []byte { | |||||
| 39 | } | 39 | } | ||
| 40 | 40 | ||||
| 41 | // ReplaceLinks atomically replaces both directions of the index. | 41 | // ReplaceLinks atomically replaces both directions of the index. | ||
| 42 | - | func (l *IssueLinks) ReplaceLinks(ctx context.Context, repo domain.RepoName, mr int64, issues, closes []int64) (added, removed []domain.IssueLink, err error) { | 42 | + | func (l *IssueLinks) ReplaceLinks(ctx context.Context, repo domain.RepoName, mr int64, issues, closes []int64, ref ports.ReferenceEvent) (added, removed []domain.IssueLink, err error) { |
| 43 | key := mrRefKey(repo, mr) | 43 | key := mrRefKey(repo, mr) | ||
| 44 | item, exists := l.db.Get(ctx, key) | 44 | item, exists := l.db.Get(ctx, key) | ||
| 45 | var old storagepb.MergeRequestReferences | 45 | var old storagepb.MergeRequestReferences | ||
| @@ -149,3 +149,8 @@ func toSet(numbers []int64) map[int64]struct{} { | |||||
| 149 | } | 149 | } | ||
| 150 | return set | 150 | return set | ||
| 151 | } | 151 | } | ||
| 152 | + | ||||
| 153 | + | // EventsOfMergeRequest returns MR issue history. Persistence is implemented in Phase A. | |||
| 154 | + | func (l *IssueLinks) EventsOfMergeRequest(ctx context.Context, repo domain.RepoName, mr int64) ([]domain.IssueEvent, error) { | |||
| 155 | + | return nil, nil | |||
| 156 | + | } | |||
M internal/adapters/kvstore/issue_links_test.go +7 -6
| @@ -3,6 +3,7 @@ package kvstore_test | |||
| 3 | 3 | ||
| 4 | 4 | import ( | |
| 5 | 5 | "bnema.dev/ermite/internal/domain" | |
| 6 | + | "bnema.dev/ermite/internal/ports" | |
| 6 | 7 | "context" | |
| 7 | 8 | "github.com/stretchr/testify/assert" | |
| 8 | 9 | "github.com/stretchr/testify/require" | |
| @@ -13,30 +14,30 @@ func TestIssueLinksReplace(t *testing.T) { | |||
| 13 | 14 | ctx := context.Background() | |
| 14 | 15 | s, _, repo := repoWithMRs(t) | |
| 15 | 16 | links := s.IssueLinks() | |
| 16 | - | added, removed, err := links.ReplaceLinks(ctx, repo, 9, []int64{3, 2, 3}, []int64{2}) | |
| 17 | + | added, removed, err := links.ReplaceLinks(ctx, repo, 9, []int64{3, 2, 3}, []int64{2}, ports.ReferenceEvent{}) | |
| 17 | 18 | require.NoError(t, err) | |
| 18 | 19 | assert.Equal(t, []domain.IssueLink{{Issue: 2, MergeRequest: 9, Closes: true}, {Issue: 3, MergeRequest: 9}}, added) | |
| 19 | 20 | assert.Empty(t, removed) | |
| 20 | 21 | got, err := links.LinksOfMergeRequest(ctx, repo, 9) | |
| 21 | 22 | require.NoError(t, err) | |
| 22 | 23 | assert.Equal(t, added, got) | |
| 23 | - | added, removed, err = links.ReplaceLinks(ctx, repo, 9, []int64{2, 3}, []int64{2}) | |
| 24 | + | added, removed, err = links.ReplaceLinks(ctx, repo, 9, []int64{2, 3}, []int64{2}, ports.ReferenceEvent{}) | |
| 24 | 25 | require.NoError(t, err) | |
| 25 | 26 | assert.Empty(t, added) | |
| 26 | 27 | assert.Empty(t, removed) | |
| 27 | - | added, removed, err = links.ReplaceLinks(ctx, repo, 9, []int64{3, 4}, []int64{3}) | |
| 28 | + | added, removed, err = links.ReplaceLinks(ctx, repo, 9, []int64{3, 4}, []int64{3}, ports.ReferenceEvent{}) | |
| 28 | 29 | require.NoError(t, err) | |
| 29 | 30 | assert.Equal(t, []domain.IssueLink{{Issue: 3, MergeRequest: 9, Closes: true}, {Issue: 4, MergeRequest: 9}}, added) | |
| 30 | 31 | assert.Equal(t, []domain.IssueLink{{Issue: 2, MergeRequest: 9, Closes: true}}, removed) | |
| 31 | 32 | got, err = links.LinksOfIssue(ctx, repo, 2, 100) | |
| 32 | 33 | require.NoError(t, err) | |
| 33 | 34 | assert.Empty(t, got) | |
| 34 | - | added, removed, err = links.ReplaceLinks(ctx, repo, 9, []int64{3}, nil) | |
| 35 | + | added, removed, err = links.ReplaceLinks(ctx, repo, 9, []int64{3}, nil, ports.ReferenceEvent{}) | |
| 35 | 36 | require.NoError(t, err) | |
| 36 | 37 | assert.Empty(t, added) | |
| 37 | 38 | assert.Equal(t, []domain.IssueLink{{Issue: 3, MergeRequest: 9, Closes: true}, {Issue: 4, MergeRequest: 9}}, removed) | |
| 38 | 39 | for _, mr := range []int64{12, 1, 5} { | |
| 39 | - | _, _, err = links.ReplaceLinks(ctx, repo, mr, []int64{3}, nil) | |
| 40 | + | _, _, err = links.ReplaceLinks(ctx, repo, mr, []int64{3}, nil, ports.ReferenceEvent{}) | |
| 40 | 41 | require.NoError(t, err) | |
| 41 | 42 | } | |
| 42 | 43 | got, err = links.LinksOfIssue(ctx, repo, 3, 2) | |
| @@ -45,7 +46,7 @@ func TestIssueLinksReplace(t *testing.T) { | |||
| 45 | 46 | require.NoError(t, s.Delete(ctx, repo)) | |
| 46 | 47 | assert.Zero(t, countKeys(t, s, "isref/"+repo.String()+"/")) | |
| 47 | 48 | assert.Zero(t, countKeys(t, s, "mrref/"+repo.String()+"/")) | |
| 48 | - | _, _, err = links.ReplaceLinks(ctx, repo, 9, []int64{2}, nil) | |
| 49 | + | _, _, err = links.ReplaceLinks(ctx, repo, 9, []int64{2}, nil, ports.ReferenceEvent{}) | |
| 49 | 50 | assert.ErrorIs(t, err, domain.ErrNotFound) | |
| 50 | 51 | } | |
| 51 | 52 | ||
| @@ -3,6 +3,7 @@ package kvstore_test | |||||
| 3 | 3 | ||||
| 4 | import ( | 4 | import ( | ||
| 5 | "bnema.dev/ermite/internal/domain" | 5 | "bnema.dev/ermite/internal/domain" | ||
| 6 | + | "bnema.dev/ermite/internal/ports" | |||
| 6 | "context" | 7 | "context" | ||
| 7 | "github.com/stretchr/testify/assert" | 8 | "github.com/stretchr/testify/assert" | ||
| 8 | "github.com/stretchr/testify/require" | 9 | "github.com/stretchr/testify/require" | ||
| @@ -13,30 +14,30 @@ func TestIssueLinksReplace(t *testing.T) { | |||||
| 13 | ctx := context.Background() | 14 | ctx := context.Background() | ||
| 14 | s, _, repo := repoWithMRs(t) | 15 | s, _, repo := repoWithMRs(t) | ||
| 15 | links := s.IssueLinks() | 16 | links := s.IssueLinks() | ||
| 16 | - | added, removed, err := links.ReplaceLinks(ctx, repo, 9, []int64{3, 2, 3}, []int64{2}) | 17 | + | added, removed, err := links.ReplaceLinks(ctx, repo, 9, []int64{3, 2, 3}, []int64{2}, ports.ReferenceEvent{}) |
| 17 | require.NoError(t, err) | 18 | require.NoError(t, err) | ||
| 18 | assert.Equal(t, []domain.IssueLink{{Issue: 2, MergeRequest: 9, Closes: true}, {Issue: 3, MergeRequest: 9}}, added) | 19 | assert.Equal(t, []domain.IssueLink{{Issue: 2, MergeRequest: 9, Closes: true}, {Issue: 3, MergeRequest: 9}}, added) | ||
| 19 | assert.Empty(t, removed) | 20 | assert.Empty(t, removed) | ||
| 20 | got, err := links.LinksOfMergeRequest(ctx, repo, 9) | 21 | got, err := links.LinksOfMergeRequest(ctx, repo, 9) | ||
| 21 | require.NoError(t, err) | 22 | require.NoError(t, err) | ||
| 22 | assert.Equal(t, added, got) | 23 | assert.Equal(t, added, got) | ||
| 23 | - | added, removed, err = links.ReplaceLinks(ctx, repo, 9, []int64{2, 3}, []int64{2}) | 24 | + | added, removed, err = links.ReplaceLinks(ctx, repo, 9, []int64{2, 3}, []int64{2}, ports.ReferenceEvent{}) |
| 24 | require.NoError(t, err) | 25 | require.NoError(t, err) | ||
| 25 | assert.Empty(t, added) | 26 | assert.Empty(t, added) | ||
| 26 | assert.Empty(t, removed) | 27 | assert.Empty(t, removed) | ||
| 27 | - | added, removed, err = links.ReplaceLinks(ctx, repo, 9, []int64{3, 4}, []int64{3}) | 28 | + | added, removed, err = links.ReplaceLinks(ctx, repo, 9, []int64{3, 4}, []int64{3}, ports.ReferenceEvent{}) |
| 28 | require.NoError(t, err) | 29 | require.NoError(t, err) | ||
| 29 | assert.Equal(t, []domain.IssueLink{{Issue: 3, MergeRequest: 9, Closes: true}, {Issue: 4, MergeRequest: 9}}, added) | 30 | assert.Equal(t, []domain.IssueLink{{Issue: 3, MergeRequest: 9, Closes: true}, {Issue: 4, MergeRequest: 9}}, added) | ||
| 30 | assert.Equal(t, []domain.IssueLink{{Issue: 2, MergeRequest: 9, Closes: true}}, removed) | 31 | assert.Equal(t, []domain.IssueLink{{Issue: 2, MergeRequest: 9, Closes: true}}, removed) | ||
| 31 | got, err = links.LinksOfIssue(ctx, repo, 2, 100) | 32 | got, err = links.LinksOfIssue(ctx, repo, 2, 100) | ||
| 32 | require.NoError(t, err) | 33 | require.NoError(t, err) | ||
| 33 | assert.Empty(t, got) | 34 | assert.Empty(t, got) | ||
| 34 | - | added, removed, err = links.ReplaceLinks(ctx, repo, 9, []int64{3}, nil) | 35 | + | added, removed, err = links.ReplaceLinks(ctx, repo, 9, []int64{3}, nil, ports.ReferenceEvent{}) |
| 35 | require.NoError(t, err) | 36 | require.NoError(t, err) | ||
| 36 | assert.Empty(t, added) | 37 | assert.Empty(t, added) | ||
| 37 | assert.Equal(t, []domain.IssueLink{{Issue: 3, MergeRequest: 9, Closes: true}, {Issue: 4, MergeRequest: 9}}, removed) | 38 | assert.Equal(t, []domain.IssueLink{{Issue: 3, MergeRequest: 9, Closes: true}, {Issue: 4, MergeRequest: 9}}, removed) | ||
| 38 | for _, mr := range []int64{12, 1, 5} { | 39 | for _, mr := range []int64{12, 1, 5} { | ||
| 39 | - | _, _, err = links.ReplaceLinks(ctx, repo, mr, []int64{3}, nil) | 40 | + | _, _, err = links.ReplaceLinks(ctx, repo, mr, []int64{3}, nil, ports.ReferenceEvent{}) |
| 40 | require.NoError(t, err) | 41 | require.NoError(t, err) | ||
| 41 | } | 42 | } | ||
| 42 | got, err = links.LinksOfIssue(ctx, repo, 3, 2) | 43 | got, err = links.LinksOfIssue(ctx, repo, 3, 2) | ||
| @@ -45,7 +46,7 @@ func TestIssueLinksReplace(t *testing.T) { | |||||
| 45 | require.NoError(t, s.Delete(ctx, repo)) | 46 | require.NoError(t, s.Delete(ctx, repo)) | ||
| 46 | assert.Zero(t, countKeys(t, s, "isref/"+repo.String()+"/")) | 47 | assert.Zero(t, countKeys(t, s, "isref/"+repo.String()+"/")) | ||
| 47 | assert.Zero(t, countKeys(t, s, "mrref/"+repo.String()+"/")) | 48 | assert.Zero(t, countKeys(t, s, "mrref/"+repo.String()+"/")) | ||
| 48 | - | _, _, err = links.ReplaceLinks(ctx, repo, 9, []int64{2}, nil) | 49 | + | _, _, err = links.ReplaceLinks(ctx, repo, 9, []int64{2}, nil, ports.ReferenceEvent{}) |
| 49 | assert.ErrorIs(t, err, domain.ErrNotFound) | 50 | assert.ErrorIs(t, err, domain.ErrNotFound) | ||
| 50 | } | 51 | } | ||
| 51 | 52 | ||||
M internal/adapters/kvstore/issues.go +10 -1
| @@ -221,7 +221,7 @@ func (i *Issues) ListOpenAll(ctx context.Context) ([]domain.Issue, error) { | |||
| 221 | 221 | // Update replaces the issue with a version precondition and moves its index key | |
| 222 | 222 | // in the same batch. The repository must exist (see guardRepoKey), so that a | |
| 223 | 223 | // concurrent delete of the repository leaves no index key behind. | |
| 224 | - | func (i *Issues) Update(ctx context.Context, issue domain.Issue) error { | |
| 224 | + | func (i *Issues) Update(ctx context.Context, issue domain.Issue, events ...domain.IssueEvent) error { | |
| 225 | 225 | val, err := encodeIssue(issue) | |
| 226 | 226 | if err != nil { | |
| 227 | 227 | return err | |
| @@ -429,3 +429,12 @@ func decodeIssuesMode(m storagepb.IssuesMode) domain.IssuesMode { | |||
| 429 | 429 | } | |
| 430 | 430 | return domain.IssuesOff | |
| 431 | 431 | } | |
| 432 | + | ||
| 433 | + | // MaxIssueEvents caps the append-only history of each issue. Further events | |
| 434 | + | // are silently dropped without preventing the associated write. | |
| 435 | + | const MaxIssueEvents = 500 | |
| 436 | + | ||
| 437 | + | // ListEvents returns issue history. Persistence is implemented in Phase A. | |
| 438 | + | func (i *Issues) ListEvents(ctx context.Context, repo domain.RepoName, number int64) ([]domain.IssueEvent, error) { | |
| 439 | + | return nil, nil | |
| 440 | + | } | |
| @@ -221,7 +221,7 @@ func (i *Issues) ListOpenAll(ctx context.Context) ([]domain.Issue, error) { | |||||
| 221 | // Update replaces the issue with a version precondition and moves its index key | 221 | // Update replaces the issue with a version precondition and moves its index key | ||
| 222 | // in the same batch. The repository must exist (see guardRepoKey), so that a | 222 | // in the same batch. The repository must exist (see guardRepoKey), so that a | ||
| 223 | // concurrent delete of the repository leaves no index key behind. | 223 | // concurrent delete of the repository leaves no index key behind. | ||
| 224 | - | func (i *Issues) Update(ctx context.Context, issue domain.Issue) error { | 224 | + | func (i *Issues) Update(ctx context.Context, issue domain.Issue, events ...domain.IssueEvent) error { |
| 225 | val, err := encodeIssue(issue) | 225 | val, err := encodeIssue(issue) | ||
| 226 | if err != nil { | 226 | if err != nil { | ||
| 227 | return err | 227 | return err | ||
| @@ -429,3 +429,12 @@ func decodeIssuesMode(m storagepb.IssuesMode) domain.IssuesMode { | |||||
| 429 | } | 429 | } | ||
| 430 | return domain.IssuesOff | 430 | return domain.IssuesOff | ||
| 431 | } | 431 | } | ||
| 432 | + | ||||
| 433 | + | // MaxIssueEvents caps the append-only history of each issue. Further events | |||
| 434 | + | // are silently dropped without preventing the associated write. | |||
| 435 | + | const MaxIssueEvents = 500 | |||
| 436 | + | ||||
| 437 | + | // ListEvents returns issue history. Persistence is implemented in Phase A. | |||
| 438 | + | func (i *Issues) ListEvents(ctx context.Context, repo domain.RepoName, number int64) ([]domain.IssueEvent, error) { | |||
| 439 | + | return nil, nil | |||
| 440 | + | } | |||
M internal/adapters/rpc/issue.go +5 -0
| @@ -270,3 +270,8 @@ func (s *issueService) RejectIssue(ctx context.Context, req *connect.Request[pb. | |||
| 270 | 270 | } | |
| 271 | 271 | return connect.NewResponse(&pb.RejectIssueResponse{}), nil | |
| 272 | 272 | } | |
| 273 | + | ||
| 274 | + | // ResolveIssue is reserved for Phase A; the RPC is not implemented yet. | |
| 275 | + | func (s *issueService) ResolveIssue(ctx context.Context, req *connect.Request[pb.ResolveIssueRequest]) (*connect.Response[pb.ResolveIssueResponse], error) { | |
| 276 | + | return nil, connect.NewError(connect.CodeUnimplemented, fmt.Errorf("ResolveIssue is not implemented")) | |
| 277 | + | } | |
| @@ -270,3 +270,8 @@ func (s *issueService) RejectIssue(ctx context.Context, req *connect.Request[pb. | |||||
| 270 | } | 270 | } | ||
| 271 | return connect.NewResponse(&pb.RejectIssueResponse{}), nil | 271 | return connect.NewResponse(&pb.RejectIssueResponse{}), nil | ||
| 272 | } | 272 | } | ||
| 273 | + | ||||
| 274 | + | // ResolveIssue is reserved for Phase A; the RPC is not implemented yet. | |||
| 275 | + | func (s *issueService) ResolveIssue(ctx context.Context, req *connect.Request[pb.ResolveIssueRequest]) (*connect.Response[pb.ResolveIssueResponse], error) { | |||
| 276 | + | return nil, connect.NewError(connect.CodeUnimplemented, fmt.Errorf("ResolveIssue is not implemented")) | |||
| 277 | + | } | |||
M internal/domain/event.go +11 -0
| @@ -237,6 +237,17 @@ type IssueClosed struct { | |||
| 237 | 237 | // EventKind implements Event. | |
| 238 | 238 | func (IssueClosed) EventKind() string { return "issue.closed" } | |
| 239 | 239 | ||
| 240 | + | // IssueResolved is published when an issue was resolved. | |
| 241 | + | type IssueResolved struct { | |
| 242 | + | Issue Issue | |
| 243 | + | At time.Time | |
| 244 | + | Visibility Visibility | |
| 245 | + | CreatedAt time.Time | |
| 246 | + | } | |
| 247 | + | ||
| 248 | + | // EventKind implements Event. | |
| 249 | + | func (IssueResolved) EventKind() string { return "issue.resolved" } | |
| 250 | + | ||
| 240 | 251 | // IssueReopened is published when a closed issue was reopened. | |
| 241 | 252 | type IssueReopened struct { | |
| 242 | 253 | Issue Issue | |
| @@ -237,6 +237,17 @@ type IssueClosed struct { | |||||
| 237 | // EventKind implements Event. | 237 | // EventKind implements Event. | ||
| 238 | func (IssueClosed) EventKind() string { return "issue.closed" } | 238 | func (IssueClosed) EventKind() string { return "issue.closed" } | ||
| 239 | 239 | ||||
| 240 | + | // IssueResolved is published when an issue was resolved. | |||
| 241 | + | type IssueResolved struct { | |||
| 242 | + | Issue Issue | |||
| 243 | + | At time.Time | |||
| 244 | + | Visibility Visibility | |||
| 245 | + | CreatedAt time.Time | |||
| 246 | + | } | |||
| 247 | + | ||||
| 248 | + | // EventKind implements Event. | |||
| 249 | + | func (IssueResolved) EventKind() string { return "issue.resolved" } | |||
| 250 | + | ||||
| 240 | // IssueReopened is published when a closed issue was reopened. | 251 | // IssueReopened is published when a closed issue was reopened. | ||
| 241 | type IssueReopened struct { | 252 | type IssueReopened struct { | ||
| 242 | Issue Issue | 253 | Issue Issue | ||
M internal/domain/event_test.go +6 -0
| @@ -59,3 +59,9 @@ func TestAutoMergeEventKinds(t *testing.T) { | |||
| 59 | 59 | assert.Equal(t, "mr.auto_merge_cancelled", domain.MergeRequestAutoMergeCancelled{}.EventKind()) | |
| 60 | 60 | assert.Equal(t, "mr.merged", domain.MergeRequestMerged{Auto: true}.EventKind()) | |
| 61 | 61 | } | |
| 62 | + | ||
| 63 | + | func TestIssueResolvedEventKind(t *testing.T) { | |
| 64 | + | t.Parallel() | |
| 65 | + | var ev domain.Event = domain.IssueResolved{} | |
| 66 | + | assert.Equal(t, "issue.resolved", ev.EventKind()) | |
| 67 | + | } | |
| @@ -59,3 +59,9 @@ func TestAutoMergeEventKinds(t *testing.T) { | |||||
| 59 | assert.Equal(t, "mr.auto_merge_cancelled", domain.MergeRequestAutoMergeCancelled{}.EventKind()) | 59 | assert.Equal(t, "mr.auto_merge_cancelled", domain.MergeRequestAutoMergeCancelled{}.EventKind()) | ||
| 60 | assert.Equal(t, "mr.merged", domain.MergeRequestMerged{Auto: true}.EventKind()) | 60 | assert.Equal(t, "mr.merged", domain.MergeRequestMerged{Auto: true}.EventKind()) | ||
| 61 | } | 61 | } | ||
| 62 | + | ||||
| 63 | + | func TestIssueResolvedEventKind(t *testing.T) { | |||
| 64 | + | t.Parallel() | |||
| 65 | + | var ev domain.Event = domain.IssueResolved{} | |||
| 66 | + | assert.Equal(t, "issue.resolved", ev.EventKind()) | |||
| 67 | + | } | |||
M internal/domain/issue.go +29 -2
| @@ -51,8 +51,10 @@ type IssueState string | |||
| 51 | 51 | const ( | |
| 52 | 52 | // IssueStateOpen is being worked on. | |
| 53 | 53 | IssueStateOpen IssueState = "open" | |
| 54 | - | // IssueStateClosed is done with; it can be reopened. | |
| 54 | + | // IssueStateClosed is closed without being resolved; it can be reopened. | |
| 55 | 55 | IssueStateClosed IssueState = "closed" | |
| 56 | + | // IssueStateResolved is resolved; it can be reopened. | |
| 57 | + | IssueStateResolved IssueState = "resolved" | |
| 56 | 58 | ) | |
| 57 | 59 | ||
| 58 | 60 | // Issue is a problem or a request about a repository. It shares its number | |
| @@ -73,7 +75,7 @@ type Issue struct { | |||
| 73 | 75 | // External is true for an issue approved from the public form: its text | |
| 74 | 76 | // comes from an anonymous visitor and is rendered as untrusted. | |
| 75 | 77 | External bool | |
| 76 | - | // ClosedByMergeRequest is zero unless a merge closed the issue. | |
| 78 | + | // ClosedByMergeRequest is set only while resolved by a merge. | |
| 77 | 79 | ClosedByMergeRequest int64 | |
| 78 | 80 | // Version is set by the store on read: Update fails with ErrConflict when | |
| 79 | 81 | // the stored issue changed since. Opaque to everyone else. | |
| @@ -112,3 +114,28 @@ const ( | |||
| 112 | 114 | // NumberMergeRequest: the number names a merge request. | |
| 113 | 115 | NumberMergeRequest | |
| 114 | 116 | ) | |
| 117 | + | ||
| 118 | + | // IsDone reports whether the issue is closed or resolved. | |
| 119 | + | func (i Issue) IsDone() bool { return i.State == IssueStateClosed || i.State == IssueStateResolved } | |
| 120 | + | ||
| 121 | + | // IssueEventKind describes an entry in an issue's append-only history. | |
| 122 | + | type IssueEventKind string | |
| 123 | + | ||
| 124 | + | // Issue event kinds. | |
| 125 | + | const ( | |
| 126 | + | IssueEventReferenced IssueEventKind = "referenced" | |
| 127 | + | IssueEventClosed IssueEventKind = "closed" | |
| 128 | + | IssueEventReopened IssueEventKind = "reopened" | |
| 129 | + | IssueEventResolved IssueEventKind = "resolved" | |
| 130 | + | ) | |
| 131 | + | ||
| 132 | + | // IssueEvent is a timestamped history entry with a per-issue sequence ID. | |
| 133 | + | type IssueEvent struct { | |
| 134 | + | ID int64 | |
| 135 | + | Kind IssueEventKind | |
| 136 | + | Issue int64 | |
| 137 | + | MergeRequest int64 | |
| 138 | + | Closes bool | |
| 139 | + | Actor string | |
| 140 | + | CreatedAt time.Time | |
| 141 | + | } | |
| @@ -51,8 +51,10 @@ type IssueState string | |||||
| 51 | const ( | 51 | const ( | ||
| 52 | // IssueStateOpen is being worked on. | 52 | // IssueStateOpen is being worked on. | ||
| 53 | IssueStateOpen IssueState = "open" | 53 | IssueStateOpen IssueState = "open" | ||
| 54 | - | // IssueStateClosed is done with; it can be reopened. | 54 | + | // IssueStateClosed is closed without being resolved; it can be reopened. |
| 55 | IssueStateClosed IssueState = "closed" | 55 | IssueStateClosed IssueState = "closed" | ||
| 56 | + | // IssueStateResolved is resolved; it can be reopened. | |||
| 57 | + | IssueStateResolved IssueState = "resolved" | |||
| 56 | ) | 58 | ) | ||
| 57 | 59 | ||||
| 58 | // Issue is a problem or a request about a repository. It shares its number | 60 | // Issue is a problem or a request about a repository. It shares its number | ||
| @@ -73,7 +75,7 @@ type Issue struct { | |||||
| 73 | // External is true for an issue approved from the public form: its text | 75 | // External is true for an issue approved from the public form: its text | ||
| 74 | // comes from an anonymous visitor and is rendered as untrusted. | 76 | // comes from an anonymous visitor and is rendered as untrusted. | ||
| 75 | External bool | 77 | External bool | ||
| 76 | - | // ClosedByMergeRequest is zero unless a merge closed the issue. | 78 | + | // ClosedByMergeRequest is set only while resolved by a merge. |
| 77 | ClosedByMergeRequest int64 | 79 | ClosedByMergeRequest int64 | ||
| 78 | // Version is set by the store on read: Update fails with ErrConflict when | 80 | // Version is set by the store on read: Update fails with ErrConflict when | ||
| 79 | // the stored issue changed since. Opaque to everyone else. | 81 | // the stored issue changed since. Opaque to everyone else. | ||
| @@ -112,3 +114,28 @@ const ( | |||||
| 112 | // NumberMergeRequest: the number names a merge request. | 114 | // NumberMergeRequest: the number names a merge request. | ||
| 113 | NumberMergeRequest | 115 | NumberMergeRequest | ||
| 114 | ) | 116 | ) | ||
| 117 | + | ||||
| 118 | + | // IsDone reports whether the issue is closed or resolved. | |||
| 119 | + | func (i Issue) IsDone() bool { return i.State == IssueStateClosed || i.State == IssueStateResolved } | |||
| 120 | + | ||||
| 121 | + | // IssueEventKind describes an entry in an issue's append-only history. | |||
| 122 | + | type IssueEventKind string | |||
| 123 | + | ||||
| 124 | + | // Issue event kinds. | |||
| 125 | + | const ( | |||
| 126 | + | IssueEventReferenced IssueEventKind = "referenced" | |||
| 127 | + | IssueEventClosed IssueEventKind = "closed" | |||
| 128 | + | IssueEventReopened IssueEventKind = "reopened" | |||
| 129 | + | IssueEventResolved IssueEventKind = "resolved" | |||
| 130 | + | ) | |||
| 131 | + | ||||
| 132 | + | // IssueEvent is a timestamped history entry with a per-issue sequence ID. | |||
| 133 | + | type IssueEvent struct { | |||
| 134 | + | ID int64 | |||
| 135 | + | Kind IssueEventKind | |||
| 136 | + | Issue int64 | |||
| 137 | + | MergeRequest int64 | |||
| 138 | + | Closes bool | |||
| 139 | + | Actor string | |||
| 140 | + | CreatedAt time.Time | |||
| 141 | + | } | |||
M internal/domain/issue_test.go +19 -0
| @@ -136,3 +136,22 @@ func TestIssueEventKinds(t *testing.T) { | |||
| 136 | 136 | assert.Equal(t, "issue.reopened", domain.IssueReopened{}.EventKind()) | |
| 137 | 137 | assert.Equal(t, "issue.commented", domain.IssueCommented{}.EventKind()) | |
| 138 | 138 | } | |
| 139 | + | ||
| 140 | + | func TestIssueIsDone(t *testing.T) { | |
| 141 | + | t.Parallel() | |
| 142 | + | for _, state := range []domain.IssueState{"", "unknown", domain.IssueStateOpen, domain.IssueStateClosed, domain.IssueStateResolved} { | |
| 143 | + | t.Run(string(state), func(t *testing.T) { | |
| 144 | + | issue := domain.Issue{State: state} | |
| 145 | + | assert.Equal(t, state == domain.IssueStateClosed || state == domain.IssueStateResolved, issue.IsDone()) | |
| 146 | + | assert.Equal(t, state == domain.IssueStateOpen, issue.IsOpen()) | |
| 147 | + | }) | |
| 148 | + | } | |
| 149 | + | } | |
| 150 | + | ||
| 151 | + | func TestIssueHistoryEventKinds(t *testing.T) { | |
| 152 | + | t.Parallel() | |
| 153 | + | assert.Equal(t, "referenced", string(domain.IssueEventReferenced)) | |
| 154 | + | assert.Equal(t, "closed", string(domain.IssueEventClosed)) | |
| 155 | + | assert.Equal(t, "reopened", string(domain.IssueEventReopened)) | |
| 156 | + | assert.Equal(t, "resolved", string(domain.IssueEventResolved)) | |
| 157 | + | } | |
| @@ -136,3 +136,22 @@ func TestIssueEventKinds(t *testing.T) { | |||||
| 136 | assert.Equal(t, "issue.reopened", domain.IssueReopened{}.EventKind()) | 136 | assert.Equal(t, "issue.reopened", domain.IssueReopened{}.EventKind()) | ||
| 137 | assert.Equal(t, "issue.commented", domain.IssueCommented{}.EventKind()) | 137 | assert.Equal(t, "issue.commented", domain.IssueCommented{}.EventKind()) | ||
| 138 | } | 138 | } | ||
| 139 | + | ||||
| 140 | + | func TestIssueIsDone(t *testing.T) { | |||
| 141 | + | t.Parallel() | |||
| 142 | + | for _, state := range []domain.IssueState{"", "unknown", domain.IssueStateOpen, domain.IssueStateClosed, domain.IssueStateResolved} { | |||
| 143 | + | t.Run(string(state), func(t *testing.T) { | |||
| 144 | + | issue := domain.Issue{State: state} | |||
| 145 | + | assert.Equal(t, state == domain.IssueStateClosed || state == domain.IssueStateResolved, issue.IsDone()) | |||
| 146 | + | assert.Equal(t, state == domain.IssueStateOpen, issue.IsOpen()) | |||
| 147 | + | }) | |||
| 148 | + | } | |||
| 149 | + | } | |||
| 150 | + | ||||
| 151 | + | func TestIssueHistoryEventKinds(t *testing.T) { | |||
| 152 | + | t.Parallel() | |||
| 153 | + | assert.Equal(t, "referenced", string(domain.IssueEventReferenced)) | |||
| 154 | + | assert.Equal(t, "closed", string(domain.IssueEventClosed)) | |||
| 155 | + | assert.Equal(t, "reopened", string(domain.IssueEventReopened)) | |||
| 156 | + | assert.Equal(t, "resolved", string(domain.IssueEventResolved)) | |||
| 157 | + | } | |||
M internal/ports/dto.go +26 -0
| @@ -261,6 +261,8 @@ type IssueReference struct { | |||
| 261 | 261 | ||
| 262 | 262 | // IssueDetail is an issue with its comments and referencing merge requests. | |
| 263 | 263 | type IssueDetail struct { | |
| 264 | + | // Events are oldest first. | |
| 265 | + | Events []IssueEventDetail | |
| 264 | 266 | // References are ascending by number, at most 100. | |
| 265 | 267 | References []IssueReference | |
| 266 | 268 | Issue domain.Issue | |
| @@ -284,6 +286,10 @@ type PushSummary struct { | |||
| 284 | 286 | ||
| 285 | 287 | // MergeRequestDetail is a merge request with its review data. | |
| 286 | 288 | type MergeRequestDetail struct { | |
| 289 | + | // Issues are current links ascending by number, at most 100; empty when issues are off. | |
| 290 | + | Issues []MergeRequestIssue | |
| 291 | + | // IssueEvents are append-only history, including links since removed. | |
| 292 | + | IssueEvents []IssueEventDetail | |
| 287 | 293 | MergeRequest domain.MergeRequest | |
| 288 | 294 | // NewCommits is the unread prefix of Commits; zero if the read head is absent. | |
| 289 | 295 | NewCommits int | |
| @@ -439,3 +445,23 @@ const ( | |||
| 439 | 445 | // MaxPushSummaryCommits bounds the commits counted in a push summary. | |
| 440 | 446 | MaxPushSummaryCommits = 100 | |
| 441 | 447 | ) | |
| 448 | + | ||
| 449 | + | // ReferenceEvent says who and when a reference was recorded. | |
| 450 | + | type ReferenceEvent struct { | |
| 451 | + | Actor string | |
| 452 | + | At time.Time | |
| 453 | + | } | |
| 454 | + | ||
| 455 | + | // IssueEventDetail includes the current MR title, empty if the MR is gone. | |
| 456 | + | type IssueEventDetail struct { | |
| 457 | + | Event domain.IssueEvent | |
| 458 | + | MergeRequestTitle domain.MRTitle | |
| 459 | + | } | |
| 460 | + | ||
| 461 | + | // MergeRequestIssue is a current issue link on a merge request. | |
| 462 | + | type MergeRequestIssue struct { | |
| 463 | + | Number int64 | |
| 464 | + | Title domain.IssueTitle | |
| 465 | + | State domain.IssueState | |
| 466 | + | Closes bool | |
| 467 | + | } | |
| @@ -261,6 +261,8 @@ type IssueReference struct { | |||||
| 261 | 261 | ||||
| 262 | // IssueDetail is an issue with its comments and referencing merge requests. | 262 | // IssueDetail is an issue with its comments and referencing merge requests. | ||
| 263 | type IssueDetail struct { | 263 | type IssueDetail struct { | ||
| 264 | + | // Events are oldest first. | |||
| 265 | + | Events []IssueEventDetail | |||
| 264 | // References are ascending by number, at most 100. | 266 | // References are ascending by number, at most 100. | ||
| 265 | References []IssueReference | 267 | References []IssueReference | ||
| 266 | Issue domain.Issue | 268 | Issue domain.Issue | ||
| @@ -284,6 +286,10 @@ type PushSummary struct { | |||||
| 284 | 286 | ||||
| 285 | // MergeRequestDetail is a merge request with its review data. | 287 | // MergeRequestDetail is a merge request with its review data. | ||
| 286 | type MergeRequestDetail struct { | 288 | type MergeRequestDetail struct { | ||
| 289 | + | // Issues are current links ascending by number, at most 100; empty when issues are off. | |||
| 290 | + | Issues []MergeRequestIssue | |||
| 291 | + | // IssueEvents are append-only history, including links since removed. | |||
| 292 | + | IssueEvents []IssueEventDetail | |||
| 287 | MergeRequest domain.MergeRequest | 293 | MergeRequest domain.MergeRequest | ||
| 288 | // NewCommits is the unread prefix of Commits; zero if the read head is absent. | 294 | // NewCommits is the unread prefix of Commits; zero if the read head is absent. | ||
| 289 | NewCommits int | 295 | NewCommits int | ||
| @@ -439,3 +445,23 @@ const ( | |||||
| 439 | // MaxPushSummaryCommits bounds the commits counted in a push summary. | 445 | // MaxPushSummaryCommits bounds the commits counted in a push summary. | ||
| 440 | MaxPushSummaryCommits = 100 | 446 | MaxPushSummaryCommits = 100 | ||
| 441 | ) | 447 | ) | ||
| 448 | + | ||||
| 449 | + | // ReferenceEvent says who and when a reference was recorded. | |||
| 450 | + | type ReferenceEvent struct { | |||
| 451 | + | Actor string | |||
| 452 | + | At time.Time | |||
| 453 | + | } | |||
| 454 | + | ||||
| 455 | + | // IssueEventDetail includes the current MR title, empty if the MR is gone. | |||
| 456 | + | type IssueEventDetail struct { | |||
| 457 | + | Event domain.IssueEvent | |||
| 458 | + | MergeRequestTitle domain.MRTitle | |||
| 459 | + | } | |||
| 460 | + | ||||
| 461 | + | // MergeRequestIssue is a current issue link on a merge request. | |||
| 462 | + | type MergeRequestIssue struct { | |||
| 463 | + | Number int64 | |||
| 464 | + | Title domain.IssueTitle | |||
| 465 | + | State domain.IssueState | |||
| 466 | + | Closes bool | |||
| 467 | + | } | |||
M internal/ports/mocks/IssueLinkStore.go +95 -14
| @@ -8,6 +8,7 @@ import ( | |||
| 8 | 8 | "context" | |
| 9 | 9 | ||
| 10 | 10 | "bnema.dev/ermite/internal/domain" | |
| 11 | + | "bnema.dev/ermite/internal/ports" | |
| 11 | 12 | mock "github.com/stretchr/testify/mock" | |
| 12 | 13 | ) | |
| 13 | 14 | ||
| @@ -47,6 +48,80 @@ func (_m *IssueLinkStore) EXPECT() *IssueLinkStore_Expecter { | |||
| 47 | 48 | return &IssueLinkStore_Expecter{mock: &_m.Mock} | |
| 48 | 49 | } | |
| 49 | 50 | ||
| 51 | + | // EventsOfMergeRequest provides a mock function for the type IssueLinkStore | |
| 52 | + | func (_mock *IssueLinkStore) EventsOfMergeRequest(ctx context.Context, repo domain.RepoName, mr int64) ([]domain.IssueEvent, error) { | |
| 53 | + | ret := _mock.Called(ctx, repo, mr) | |
| 54 | + | ||
| 55 | + | if len(ret) == 0 { | |
| 56 | + | panic("no return value specified for EventsOfMergeRequest") | |
| 57 | + | } | |
| 58 | + | ||
| 59 | + | var r0 []domain.IssueEvent | |
| 60 | + | var r1 error | |
| 61 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, int64) ([]domain.IssueEvent, error)); ok { | |
| 62 | + | return returnFunc(ctx, repo, mr) | |
| 63 | + | } | |
| 64 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, int64) []domain.IssueEvent); ok { | |
| 65 | + | r0 = returnFunc(ctx, repo, mr) | |
| 66 | + | } else { | |
| 67 | + | if ret.Get(0) != nil { | |
| 68 | + | r0 = ret.Get(0).([]domain.IssueEvent) | |
| 69 | + | } | |
| 70 | + | } | |
| 71 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, domain.RepoName, int64) error); ok { | |
| 72 | + | r1 = returnFunc(ctx, repo, mr) | |
| 73 | + | } else { | |
| 74 | + | r1 = ret.Error(1) | |
| 75 | + | } | |
| 76 | + | return r0, r1 | |
| 77 | + | } | |
| 78 | + | ||
| 79 | + | // IssueLinkStore_EventsOfMergeRequest_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EventsOfMergeRequest' | |
| 80 | + | type IssueLinkStore_EventsOfMergeRequest_Call struct { | |
| 81 | + | *mock.Call | |
| 82 | + | } | |
| 83 | + | ||
| 84 | + | // EventsOfMergeRequest is a helper method to define mock.On call | |
| 85 | + | // - ctx context.Context | |
| 86 | + | // - repo domain.RepoName | |
| 87 | + | // - mr int64 | |
| 88 | + | func (_e *IssueLinkStore_Expecter) EventsOfMergeRequest(ctx any, repo any, mr any) *IssueLinkStore_EventsOfMergeRequest_Call { | |
| 89 | + | return &IssueLinkStore_EventsOfMergeRequest_Call{Call: _e.mock.On("EventsOfMergeRequest", ctx, repo, mr)} | |
| 90 | + | } | |
| 91 | + | ||
| 92 | + | func (_c *IssueLinkStore_EventsOfMergeRequest_Call) Run(run func(ctx context.Context, repo domain.RepoName, mr int64)) *IssueLinkStore_EventsOfMergeRequest_Call { | |
| 93 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 94 | + | var arg0 context.Context | |
| 95 | + | if args[0] != nil { | |
| 96 | + | arg0 = args[0].(context.Context) | |
| 97 | + | } | |
| 98 | + | var arg1 domain.RepoName | |
| 99 | + | if args[1] != nil { | |
| 100 | + | arg1 = args[1].(domain.RepoName) | |
| 101 | + | } | |
| 102 | + | var arg2 int64 | |
| 103 | + | if args[2] != nil { | |
| 104 | + | arg2 = args[2].(int64) | |
| 105 | + | } | |
| 106 | + | run( | |
| 107 | + | arg0, | |
| 108 | + | arg1, | |
| 109 | + | arg2, | |
| 110 | + | ) | |
| 111 | + | }) | |
| 112 | + | return _c | |
| 113 | + | } | |
| 114 | + | ||
| 115 | + | func (_c *IssueLinkStore_EventsOfMergeRequest_Call) Return(issueEvents []domain.IssueEvent, err error) *IssueLinkStore_EventsOfMergeRequest_Call { | |
| 116 | + | _c.Call.Return(issueEvents, err) | |
| 117 | + | return _c | |
| 118 | + | } | |
| 119 | + | ||
| 120 | + | func (_c *IssueLinkStore_EventsOfMergeRequest_Call) RunAndReturn(run func(ctx context.Context, repo domain.RepoName, mr int64) ([]domain.IssueEvent, error)) *IssueLinkStore_EventsOfMergeRequest_Call { | |
| 121 | + | _c.Call.Return(run) | |
| 122 | + | return _c | |
| 123 | + | } | |
| 124 | + | ||
| 50 | 125 | // LinksOfIssue provides a mock function for the type IssueLinkStore | |
| 51 | 126 | func (_mock *IssueLinkStore) LinksOfIssue(ctx context.Context, repo domain.RepoName, issue int64, limit int) ([]domain.IssueLink, error) { | |
| 52 | 127 | ret := _mock.Called(ctx, repo, issue, limit) | |
| @@ -202,8 +277,8 @@ func (_c *IssueLinkStore_LinksOfMergeRequest_Call) RunAndReturn(run func(ctx con | |||
| 202 | 277 | } | |
| 203 | 278 | ||
| 204 | 279 | // ReplaceLinks provides a mock function for the type IssueLinkStore | |
| 205 | - | func (_mock *IssueLinkStore) ReplaceLinks(ctx context.Context, repo domain.RepoName, mr int64, issues []int64, closes []int64) ([]domain.IssueLink, []domain.IssueLink, error) { | |
| 206 | - | ret := _mock.Called(ctx, repo, mr, issues, closes) | |
| 280 | + | func (_mock *IssueLinkStore) ReplaceLinks(ctx context.Context, repo domain.RepoName, mr int64, issues []int64, closes []int64, ref ports.ReferenceEvent) ([]domain.IssueLink, []domain.IssueLink, error) { | |
| 281 | + | ret := _mock.Called(ctx, repo, mr, issues, closes, ref) | |
| 207 | 282 | ||
| 208 | 283 | if len(ret) == 0 { | |
| 209 | 284 | panic("no return value specified for ReplaceLinks") | |
| @@ -212,25 +287,25 @@ func (_mock *IssueLinkStore) ReplaceLinks(ctx context.Context, repo domain.RepoN | |||
| 212 | 287 | var r0 []domain.IssueLink | |
| 213 | 288 | var r1 []domain.IssueLink | |
| 214 | 289 | var r2 error | |
| 215 | - | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, int64, []int64, []int64) ([]domain.IssueLink, []domain.IssueLink, error)); ok { | |
| 216 | - | return returnFunc(ctx, repo, mr, issues, closes) | |
| 290 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, int64, []int64, []int64, ports.ReferenceEvent) ([]domain.IssueLink, []domain.IssueLink, error)); ok { | |
| 291 | + | return returnFunc(ctx, repo, mr, issues, closes, ref) | |
| 217 | 292 | } | |
| 218 | - | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, int64, []int64, []int64) []domain.IssueLink); ok { | |
| 219 | - | r0 = returnFunc(ctx, repo, mr, issues, closes) | |
| 293 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, int64, []int64, []int64, ports.ReferenceEvent) []domain.IssueLink); ok { | |
| 294 | + | r0 = returnFunc(ctx, repo, mr, issues, closes, ref) | |
| 220 | 295 | } else { | |
| 221 | 296 | if ret.Get(0) != nil { | |
| 222 | 297 | r0 = ret.Get(0).([]domain.IssueLink) | |
| 223 | 298 | } | |
| 224 | 299 | } | |
| 225 | - | if returnFunc, ok := ret.Get(1).(func(context.Context, domain.RepoName, int64, []int64, []int64) []domain.IssueLink); ok { | |
| 226 | - | r1 = returnFunc(ctx, repo, mr, issues, closes) | |
| 300 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, domain.RepoName, int64, []int64, []int64, ports.ReferenceEvent) []domain.IssueLink); ok { | |
| 301 | + | r1 = returnFunc(ctx, repo, mr, issues, closes, ref) | |
| 227 | 302 | } else { | |
| 228 | 303 | if ret.Get(1) != nil { | |
| 229 | 304 | r1 = ret.Get(1).([]domain.IssueLink) | |
| 230 | 305 | } | |
| 231 | 306 | } | |
| 232 | - | if returnFunc, ok := ret.Get(2).(func(context.Context, domain.RepoName, int64, []int64, []int64) error); ok { | |
| 233 | - | r2 = returnFunc(ctx, repo, mr, issues, closes) | |
| 307 | + | if returnFunc, ok := ret.Get(2).(func(context.Context, domain.RepoName, int64, []int64, []int64, ports.ReferenceEvent) error); ok { | |
| 308 | + | r2 = returnFunc(ctx, repo, mr, issues, closes, ref) | |
| 234 | 309 | } else { | |
| 235 | 310 | r2 = ret.Error(2) | |
| 236 | 311 | } | |
| @@ -248,11 +323,12 @@ type IssueLinkStore_ReplaceLinks_Call struct { | |||
| 248 | 323 | // - mr int64 | |
| 249 | 324 | // - issues []int64 | |
| 250 | 325 | // - closes []int64 | |
| 251 | - | func (_e *IssueLinkStore_Expecter) ReplaceLinks(ctx any, repo any, mr any, issues any, closes any) *IssueLinkStore_ReplaceLinks_Call { | |
| 252 | - | return &IssueLinkStore_ReplaceLinks_Call{Call: _e.mock.On("ReplaceLinks", ctx, repo, mr, issues, closes)} | |
| 326 | + | // - ref ports.ReferenceEvent | |
| 327 | + | func (_e *IssueLinkStore_Expecter) ReplaceLinks(ctx any, repo any, mr any, issues any, closes any, ref any) *IssueLinkStore_ReplaceLinks_Call { | |
| 328 | + | return &IssueLinkStore_ReplaceLinks_Call{Call: _e.mock.On("ReplaceLinks", ctx, repo, mr, issues, closes, ref)} | |
| 253 | 329 | } | |
| 254 | 330 | ||
| 255 | - | func (_c *IssueLinkStore_ReplaceLinks_Call) Run(run func(ctx context.Context, repo domain.RepoName, mr int64, issues []int64, closes []int64)) *IssueLinkStore_ReplaceLinks_Call { | |
| 331 | + | func (_c *IssueLinkStore_ReplaceLinks_Call) Run(run func(ctx context.Context, repo domain.RepoName, mr int64, issues []int64, closes []int64, ref ports.ReferenceEvent)) *IssueLinkStore_ReplaceLinks_Call { | |
| 256 | 332 | _c.Call.Run(func(args mock.Arguments) { | |
| 257 | 333 | var arg0 context.Context | |
| 258 | 334 | if args[0] != nil { | |
| @@ -274,12 +350,17 @@ func (_c *IssueLinkStore_ReplaceLinks_Call) Run(run func(ctx context.Context, re | |||
| 274 | 350 | if args[4] != nil { | |
| 275 | 351 | arg4 = args[4].([]int64) | |
| 276 | 352 | } | |
| 353 | + | var arg5 ports.ReferenceEvent | |
| 354 | + | if args[5] != nil { | |
| 355 | + | arg5 = args[5].(ports.ReferenceEvent) | |
| 356 | + | } | |
| 277 | 357 | run( | |
| 278 | 358 | arg0, | |
| 279 | 359 | arg1, | |
| 280 | 360 | arg2, | |
| 281 | 361 | arg3, | |
| 282 | 362 | arg4, | |
| 363 | + | arg5, | |
| 283 | 364 | ) | |
| 284 | 365 | }) | |
| 285 | 366 | return _c | |
| @@ -290,7 +371,7 @@ func (_c *IssueLinkStore_ReplaceLinks_Call) Return(added []domain.IssueLink, rem | |||
| 290 | 371 | return _c | |
| 291 | 372 | } | |
| 292 | 373 | ||
| 293 | - | func (_c *IssueLinkStore_ReplaceLinks_Call) RunAndReturn(run func(ctx context.Context, repo domain.RepoName, mr int64, issues []int64, closes []int64) ([]domain.IssueLink, []domain.IssueLink, error)) *IssueLinkStore_ReplaceLinks_Call { | |
| 374 | + | func (_c *IssueLinkStore_ReplaceLinks_Call) RunAndReturn(run func(ctx context.Context, repo domain.RepoName, mr int64, issues []int64, closes []int64, ref ports.ReferenceEvent) ([]domain.IssueLink, []domain.IssueLink, error)) *IssueLinkStore_ReplaceLinks_Call { | |
| 294 | 375 | _c.Call.Return(run) | |
| 295 | 376 | return _c | |
| 296 | 377 | } | |
| @@ -8,6 +8,7 @@ import ( | |||||
| 8 | "context" | 8 | "context" | ||
| 9 | 9 | ||||
| 10 | "bnema.dev/ermite/internal/domain" | 10 | "bnema.dev/ermite/internal/domain" | ||
| 11 | + | "bnema.dev/ermite/internal/ports" | |||
| 11 | mock "github.com/stretchr/testify/mock" | 12 | mock "github.com/stretchr/testify/mock" | ||
| 12 | ) | 13 | ) | ||
| 13 | 14 | ||||
| @@ -47,6 +48,80 @@ func (_m *IssueLinkStore) EXPECT() *IssueLinkStore_Expecter { | |||||
| 47 | return &IssueLinkStore_Expecter{mock: &_m.Mock} | 48 | return &IssueLinkStore_Expecter{mock: &_m.Mock} | ||
| 48 | } | 49 | } | ||
| 49 | 50 | ||||
| 51 | + | // EventsOfMergeRequest provides a mock function for the type IssueLinkStore | |||
| 52 | + | func (_mock *IssueLinkStore) EventsOfMergeRequest(ctx context.Context, repo domain.RepoName, mr int64) ([]domain.IssueEvent, error) { | |||
| 53 | + | ret := _mock.Called(ctx, repo, mr) | |||
| 54 | + | ||||
| 55 | + | if len(ret) == 0 { | |||
| 56 | + | panic("no return value specified for EventsOfMergeRequest") | |||
| 57 | + | } | |||
| 58 | + | ||||
| 59 | + | var r0 []domain.IssueEvent | |||
| 60 | + | var r1 error | |||
| 61 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, int64) ([]domain.IssueEvent, error)); ok { | |||
| 62 | + | return returnFunc(ctx, repo, mr) | |||
| 63 | + | } | |||
| 64 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, int64) []domain.IssueEvent); ok { | |||
| 65 | + | r0 = returnFunc(ctx, repo, mr) | |||
| 66 | + | } else { | |||
| 67 | + | if ret.Get(0) != nil { | |||
| 68 | + | r0 = ret.Get(0).([]domain.IssueEvent) | |||
| 69 | + | } | |||
| 70 | + | } | |||
| 71 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, domain.RepoName, int64) error); ok { | |||
| 72 | + | r1 = returnFunc(ctx, repo, mr) | |||
| 73 | + | } else { | |||
| 74 | + | r1 = ret.Error(1) | |||
| 75 | + | } | |||
| 76 | + | return r0, r1 | |||
| 77 | + | } | |||
| 78 | + | ||||
| 79 | + | // IssueLinkStore_EventsOfMergeRequest_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EventsOfMergeRequest' | |||
| 80 | + | type IssueLinkStore_EventsOfMergeRequest_Call struct { | |||
| 81 | + | *mock.Call | |||
| 82 | + | } | |||
| 83 | + | ||||
| 84 | + | // EventsOfMergeRequest is a helper method to define mock.On call | |||
| 85 | + | // - ctx context.Context | |||
| 86 | + | // - repo domain.RepoName | |||
| 87 | + | // - mr int64 | |||
| 88 | + | func (_e *IssueLinkStore_Expecter) EventsOfMergeRequest(ctx any, repo any, mr any) *IssueLinkStore_EventsOfMergeRequest_Call { | |||
| 89 | + | return &IssueLinkStore_EventsOfMergeRequest_Call{Call: _e.mock.On("EventsOfMergeRequest", ctx, repo, mr)} | |||
| 90 | + | } | |||
| 91 | + | ||||
| 92 | + | func (_c *IssueLinkStore_EventsOfMergeRequest_Call) Run(run func(ctx context.Context, repo domain.RepoName, mr int64)) *IssueLinkStore_EventsOfMergeRequest_Call { | |||
| 93 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 94 | + | var arg0 context.Context | |||
| 95 | + | if args[0] != nil { | |||
| 96 | + | arg0 = args[0].(context.Context) | |||
| 97 | + | } | |||
| 98 | + | var arg1 domain.RepoName | |||
| 99 | + | if args[1] != nil { | |||
| 100 | + | arg1 = args[1].(domain.RepoName) | |||
| 101 | + | } | |||
| 102 | + | var arg2 int64 | |||
| 103 | + | if args[2] != nil { | |||
| 104 | + | arg2 = args[2].(int64) | |||
| 105 | + | } | |||
| 106 | + | run( | |||
| 107 | + | arg0, | |||
| 108 | + | arg1, | |||
| 109 | + | arg2, | |||
| 110 | + | ) | |||
| 111 | + | }) | |||
| 112 | + | return _c | |||
| 113 | + | } | |||
| 114 | + | ||||
| 115 | + | func (_c *IssueLinkStore_EventsOfMergeRequest_Call) Return(issueEvents []domain.IssueEvent, err error) *IssueLinkStore_EventsOfMergeRequest_Call { | |||
| 116 | + | _c.Call.Return(issueEvents, err) | |||
| 117 | + | return _c | |||
| 118 | + | } | |||
| 119 | + | ||||
| 120 | + | func (_c *IssueLinkStore_EventsOfMergeRequest_Call) RunAndReturn(run func(ctx context.Context, repo domain.RepoName, mr int64) ([]domain.IssueEvent, error)) *IssueLinkStore_EventsOfMergeRequest_Call { | |||
| 121 | + | _c.Call.Return(run) | |||
| 122 | + | return _c | |||
| 123 | + | } | |||
| 124 | + | ||||
| 50 | // LinksOfIssue provides a mock function for the type IssueLinkStore | 125 | // LinksOfIssue provides a mock function for the type IssueLinkStore | ||
| 51 | func (_mock *IssueLinkStore) LinksOfIssue(ctx context.Context, repo domain.RepoName, issue int64, limit int) ([]domain.IssueLink, error) { | 126 | func (_mock *IssueLinkStore) LinksOfIssue(ctx context.Context, repo domain.RepoName, issue int64, limit int) ([]domain.IssueLink, error) { | ||
| 52 | ret := _mock.Called(ctx, repo, issue, limit) | 127 | ret := _mock.Called(ctx, repo, issue, limit) | ||
| @@ -202,8 +277,8 @@ func (_c *IssueLinkStore_LinksOfMergeRequest_Call) RunAndReturn(run func(ctx con | |||||
| 202 | } | 277 | } | ||
| 203 | 278 | ||||
| 204 | // ReplaceLinks provides a mock function for the type IssueLinkStore | 279 | // ReplaceLinks provides a mock function for the type IssueLinkStore | ||
| 205 | - | func (_mock *IssueLinkStore) ReplaceLinks(ctx context.Context, repo domain.RepoName, mr int64, issues []int64, closes []int64) ([]domain.IssueLink, []domain.IssueLink, error) { | 280 | + | func (_mock *IssueLinkStore) ReplaceLinks(ctx context.Context, repo domain.RepoName, mr int64, issues []int64, closes []int64, ref ports.ReferenceEvent) ([]domain.IssueLink, []domain.IssueLink, error) { |
| 206 | - | ret := _mock.Called(ctx, repo, mr, issues, closes) | 281 | + | ret := _mock.Called(ctx, repo, mr, issues, closes, ref) |
| 207 | 282 | ||||
| 208 | if len(ret) == 0 { | 283 | if len(ret) == 0 { | ||
| 209 | panic("no return value specified for ReplaceLinks") | 284 | panic("no return value specified for ReplaceLinks") | ||
| @@ -212,25 +287,25 @@ func (_mock *IssueLinkStore) ReplaceLinks(ctx context.Context, repo domain.RepoN | |||||
| 212 | var r0 []domain.IssueLink | 287 | var r0 []domain.IssueLink | ||
| 213 | var r1 []domain.IssueLink | 288 | var r1 []domain.IssueLink | ||
| 214 | var r2 error | 289 | var r2 error | ||
| 215 | - | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, int64, []int64, []int64) ([]domain.IssueLink, []domain.IssueLink, error)); ok { | 290 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, int64, []int64, []int64, ports.ReferenceEvent) ([]domain.IssueLink, []domain.IssueLink, error)); ok { |
| 216 | - | return returnFunc(ctx, repo, mr, issues, closes) | 291 | + | return returnFunc(ctx, repo, mr, issues, closes, ref) |
| 217 | } | 292 | } | ||
| 218 | - | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, int64, []int64, []int64) []domain.IssueLink); ok { | 293 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, int64, []int64, []int64, ports.ReferenceEvent) []domain.IssueLink); ok { |
| 219 | - | r0 = returnFunc(ctx, repo, mr, issues, closes) | 294 | + | r0 = returnFunc(ctx, repo, mr, issues, closes, ref) |
| 220 | } else { | 295 | } else { | ||
| 221 | if ret.Get(0) != nil { | 296 | if ret.Get(0) != nil { | ||
| 222 | r0 = ret.Get(0).([]domain.IssueLink) | 297 | r0 = ret.Get(0).([]domain.IssueLink) | ||
| 223 | } | 298 | } | ||
| 224 | } | 299 | } | ||
| 225 | - | if returnFunc, ok := ret.Get(1).(func(context.Context, domain.RepoName, int64, []int64, []int64) []domain.IssueLink); ok { | 300 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, domain.RepoName, int64, []int64, []int64, ports.ReferenceEvent) []domain.IssueLink); ok { |
| 226 | - | r1 = returnFunc(ctx, repo, mr, issues, closes) | 301 | + | r1 = returnFunc(ctx, repo, mr, issues, closes, ref) |
| 227 | } else { | 302 | } else { | ||
| 228 | if ret.Get(1) != nil { | 303 | if ret.Get(1) != nil { | ||
| 229 | r1 = ret.Get(1).([]domain.IssueLink) | 304 | r1 = ret.Get(1).([]domain.IssueLink) | ||
| 230 | } | 305 | } | ||
| 231 | } | 306 | } | ||
| 232 | - | if returnFunc, ok := ret.Get(2).(func(context.Context, domain.RepoName, int64, []int64, []int64) error); ok { | 307 | + | if returnFunc, ok := ret.Get(2).(func(context.Context, domain.RepoName, int64, []int64, []int64, ports.ReferenceEvent) error); ok { |
| 233 | - | r2 = returnFunc(ctx, repo, mr, issues, closes) | 308 | + | r2 = returnFunc(ctx, repo, mr, issues, closes, ref) |
| 234 | } else { | 309 | } else { | ||
| 235 | r2 = ret.Error(2) | 310 | r2 = ret.Error(2) | ||
| 236 | } | 311 | } | ||
| @@ -248,11 +323,12 @@ type IssueLinkStore_ReplaceLinks_Call struct { | |||||
| 248 | // - mr int64 | 323 | // - mr int64 | ||
| 249 | // - issues []int64 | 324 | // - issues []int64 | ||
| 250 | // - closes []int64 | 325 | // - closes []int64 | ||
| 251 | - | func (_e *IssueLinkStore_Expecter) ReplaceLinks(ctx any, repo any, mr any, issues any, closes any) *IssueLinkStore_ReplaceLinks_Call { | 326 | + | // - ref ports.ReferenceEvent |
| 252 | - | return &IssueLinkStore_ReplaceLinks_Call{Call: _e.mock.On("ReplaceLinks", ctx, repo, mr, issues, closes)} | 327 | + | func (_e *IssueLinkStore_Expecter) ReplaceLinks(ctx any, repo any, mr any, issues any, closes any, ref any) *IssueLinkStore_ReplaceLinks_Call { |
| 328 | + | return &IssueLinkStore_ReplaceLinks_Call{Call: _e.mock.On("ReplaceLinks", ctx, repo, mr, issues, closes, ref)} | |||
| 253 | } | 329 | } | ||
| 254 | 330 | ||||
| 255 | - | func (_c *IssueLinkStore_ReplaceLinks_Call) Run(run func(ctx context.Context, repo domain.RepoName, mr int64, issues []int64, closes []int64)) *IssueLinkStore_ReplaceLinks_Call { | 331 | + | func (_c *IssueLinkStore_ReplaceLinks_Call) Run(run func(ctx context.Context, repo domain.RepoName, mr int64, issues []int64, closes []int64, ref ports.ReferenceEvent)) *IssueLinkStore_ReplaceLinks_Call { |
| 256 | _c.Call.Run(func(args mock.Arguments) { | 332 | _c.Call.Run(func(args mock.Arguments) { | ||
| 257 | var arg0 context.Context | 333 | var arg0 context.Context | ||
| 258 | if args[0] != nil { | 334 | if args[0] != nil { | ||
| @@ -274,12 +350,17 @@ func (_c *IssueLinkStore_ReplaceLinks_Call) Run(run func(ctx context.Context, re | |||||
| 274 | if args[4] != nil { | 350 | if args[4] != nil { | ||
| 275 | arg4 = args[4].([]int64) | 351 | arg4 = args[4].([]int64) | ||
| 276 | } | 352 | } | ||
| 353 | + | var arg5 ports.ReferenceEvent | |||
| 354 | + | if args[5] != nil { | |||
| 355 | + | arg5 = args[5].(ports.ReferenceEvent) | |||
| 356 | + | } | |||
| 277 | run( | 357 | run( | ||
| 278 | arg0, | 358 | arg0, | ||
| 279 | arg1, | 359 | arg1, | ||
| 280 | arg2, | 360 | arg2, | ||
| 281 | arg3, | 361 | arg3, | ||
| 282 | arg4, | 362 | arg4, | ||
| 363 | + | arg5, | |||
| 283 | ) | 364 | ) | ||
| 284 | }) | 365 | }) | ||
| 285 | return _c | 366 | return _c | ||
| @@ -290,7 +371,7 @@ func (_c *IssueLinkStore_ReplaceLinks_Call) Return(added []domain.IssueLink, rem | |||||
| 290 | return _c | 371 | return _c | ||
| 291 | } | 372 | } | ||
| 292 | 373 | ||||
| 293 | - | func (_c *IssueLinkStore_ReplaceLinks_Call) RunAndReturn(run func(ctx context.Context, repo domain.RepoName, mr int64, issues []int64, closes []int64) ([]domain.IssueLink, []domain.IssueLink, error)) *IssueLinkStore_ReplaceLinks_Call { | 374 | + | func (_c *IssueLinkStore_ReplaceLinks_Call) RunAndReturn(run func(ctx context.Context, repo domain.RepoName, mr int64, issues []int64, closes []int64, ref ports.ReferenceEvent) ([]domain.IssueLink, []domain.IssueLink, error)) *IssueLinkStore_ReplaceLinks_Call { |
| 294 | _c.Call.Return(run) | 375 | _c.Call.Return(run) | ||
| 295 | return _c | 376 | return _c | ||
| 296 | } | 377 | } | ||
M internal/ports/mocks/IssueLinkTrigger.go +32 -20
| @@ -48,8 +48,8 @@ func (_m *IssueLinkTrigger) EXPECT() *IssueLinkTrigger_Expecter { | |||
| 48 | 48 | } | |
| 49 | 49 | ||
| 50 | 50 | // OnBranchesMoved provides a mock function for the type IssueLinkTrigger | |
| 51 | - | func (_mock *IssueLinkTrigger) OnBranchesMoved(context1 context.Context, repoName domain.RepoName, refUpdates []domain.RefUpdate) { | |
| 52 | - | _mock.Called(context1, repoName, refUpdates) | |
| 51 | + | func (_mock *IssueLinkTrigger) OnBranchesMoved(context1 context.Context, principal domain.Principal, repoName domain.RepoName, refUpdates []domain.RefUpdate) { | |
| 52 | + | _mock.Called(context1, principal, repoName, refUpdates) | |
| 53 | 53 | return | |
| 54 | 54 | } | |
| 55 | 55 | ||
| @@ -60,30 +60,36 @@ type IssueLinkTrigger_OnBranchesMoved_Call struct { | |||
| 60 | 60 | ||
| 61 | 61 | // OnBranchesMoved is a helper method to define mock.On call | |
| 62 | 62 | // - context1 context.Context | |
| 63 | + | // - principal domain.Principal | |
| 63 | 64 | // - repoName domain.RepoName | |
| 64 | 65 | // - refUpdates []domain.RefUpdate | |
| 65 | - | func (_e *IssueLinkTrigger_Expecter) OnBranchesMoved(context1 any, repoName any, refUpdates any) *IssueLinkTrigger_OnBranchesMoved_Call { | |
| 66 | - | return &IssueLinkTrigger_OnBranchesMoved_Call{Call: _e.mock.On("OnBranchesMoved", context1, repoName, refUpdates)} | |
| 66 | + | func (_e *IssueLinkTrigger_Expecter) OnBranchesMoved(context1 any, principal any, repoName any, refUpdates any) *IssueLinkTrigger_OnBranchesMoved_Call { | |
| 67 | + | return &IssueLinkTrigger_OnBranchesMoved_Call{Call: _e.mock.On("OnBranchesMoved", context1, principal, repoName, refUpdates)} | |
| 67 | 68 | } | |
| 68 | 69 | ||
| 69 | - | func (_c *IssueLinkTrigger_OnBranchesMoved_Call) Run(run func(context1 context.Context, repoName domain.RepoName, refUpdates []domain.RefUpdate)) *IssueLinkTrigger_OnBranchesMoved_Call { | |
| 70 | + | func (_c *IssueLinkTrigger_OnBranchesMoved_Call) Run(run func(context1 context.Context, principal domain.Principal, repoName domain.RepoName, refUpdates []domain.RefUpdate)) *IssueLinkTrigger_OnBranchesMoved_Call { | |
| 70 | 71 | _c.Call.Run(func(args mock.Arguments) { | |
| 71 | 72 | var arg0 context.Context | |
| 72 | 73 | if args[0] != nil { | |
| 73 | 74 | arg0 = args[0].(context.Context) | |
| 74 | 75 | } | |
| 75 | - | var arg1 domain.RepoName | |
| 76 | + | var arg1 domain.Principal | |
| 76 | 77 | if args[1] != nil { | |
| 77 | - | arg1 = args[1].(domain.RepoName) | |
| 78 | + | arg1 = args[1].(domain.Principal) | |
| 78 | 79 | } | |
| 79 | - | var arg2 []domain.RefUpdate | |
| 80 | + | var arg2 domain.RepoName | |
| 80 | 81 | if args[2] != nil { | |
| 81 | - | arg2 = args[2].([]domain.RefUpdate) | |
| 82 | + | arg2 = args[2].(domain.RepoName) | |
| 83 | + | } | |
| 84 | + | var arg3 []domain.RefUpdate | |
| 85 | + | if args[3] != nil { | |
| 86 | + | arg3 = args[3].([]domain.RefUpdate) | |
| 82 | 87 | } | |
| 83 | 88 | run( | |
| 84 | 89 | arg0, | |
| 85 | 90 | arg1, | |
| 86 | 91 | arg2, | |
| 92 | + | arg3, | |
| 87 | 93 | ) | |
| 88 | 94 | }) | |
| 89 | 95 | return _c | |
| @@ -94,14 +100,14 @@ func (_c *IssueLinkTrigger_OnBranchesMoved_Call) Return() *IssueLinkTrigger_OnBr | |||
| 94 | 100 | return _c | |
| 95 | 101 | } | |
| 96 | 102 | ||
| 97 | - | func (_c *IssueLinkTrigger_OnBranchesMoved_Call) RunAndReturn(run func(context1 context.Context, repoName domain.RepoName, refUpdates []domain.RefUpdate)) *IssueLinkTrigger_OnBranchesMoved_Call { | |
| 103 | + | func (_c *IssueLinkTrigger_OnBranchesMoved_Call) RunAndReturn(run func(context1 context.Context, principal domain.Principal, repoName domain.RepoName, refUpdates []domain.RefUpdate)) *IssueLinkTrigger_OnBranchesMoved_Call { | |
| 98 | 104 | _c.Run(run) | |
| 99 | 105 | return _c | |
| 100 | 106 | } | |
| 101 | 107 | ||
| 102 | 108 | // OnMergeRequestChanged provides a mock function for the type IssueLinkTrigger | |
| 103 | - | func (_mock *IssueLinkTrigger) OnMergeRequestChanged(context1 context.Context, repo domain.Repo, n int64) { | |
| 104 | - | _mock.Called(context1, repo, n) | |
| 109 | + | func (_mock *IssueLinkTrigger) OnMergeRequestChanged(context1 context.Context, principal domain.Principal, repo domain.Repo, n int64) { | |
| 110 | + | _mock.Called(context1, principal, repo, n) | |
| 105 | 111 | return | |
| 106 | 112 | } | |
| 107 | 113 | ||
| @@ -112,30 +118,36 @@ type IssueLinkTrigger_OnMergeRequestChanged_Call struct { | |||
| 112 | 118 | ||
| 113 | 119 | // OnMergeRequestChanged is a helper method to define mock.On call | |
| 114 | 120 | // - context1 context.Context | |
| 121 | + | // - principal domain.Principal | |
| 115 | 122 | // - repo domain.Repo | |
| 116 | 123 | // - n int64 | |
| 117 | - | func (_e *IssueLinkTrigger_Expecter) OnMergeRequestChanged(context1 any, repo any, n any) *IssueLinkTrigger_OnMergeRequestChanged_Call { | |
| 118 | - | return &IssueLinkTrigger_OnMergeRequestChanged_Call{Call: _e.mock.On("OnMergeRequestChanged", context1, repo, n)} | |
| 124 | + | func (_e *IssueLinkTrigger_Expecter) OnMergeRequestChanged(context1 any, principal any, repo any, n any) *IssueLinkTrigger_OnMergeRequestChanged_Call { | |
| 125 | + | return &IssueLinkTrigger_OnMergeRequestChanged_Call{Call: _e.mock.On("OnMergeRequestChanged", context1, principal, repo, n)} | |
| 119 | 126 | } | |
| 120 | 127 | ||
| 121 | - | func (_c *IssueLinkTrigger_OnMergeRequestChanged_Call) Run(run func(context1 context.Context, repo domain.Repo, n int64)) *IssueLinkTrigger_OnMergeRequestChanged_Call { | |
| 128 | + | func (_c *IssueLinkTrigger_OnMergeRequestChanged_Call) Run(run func(context1 context.Context, principal domain.Principal, repo domain.Repo, n int64)) *IssueLinkTrigger_OnMergeRequestChanged_Call { | |
| 122 | 129 | _c.Call.Run(func(args mock.Arguments) { | |
| 123 | 130 | var arg0 context.Context | |
| 124 | 131 | if args[0] != nil { | |
| 125 | 132 | arg0 = args[0].(context.Context) | |
| 126 | 133 | } | |
| 127 | - | var arg1 domain.Repo | |
| 134 | + | var arg1 domain.Principal | |
| 128 | 135 | if args[1] != nil { | |
| 129 | - | arg1 = args[1].(domain.Repo) | |
| 136 | + | arg1 = args[1].(domain.Principal) | |
| 130 | 137 | } | |
| 131 | - | var arg2 int64 | |
| 138 | + | var arg2 domain.Repo | |
| 132 | 139 | if args[2] != nil { | |
| 133 | - | arg2 = args[2].(int64) | |
| 140 | + | arg2 = args[2].(domain.Repo) | |
| 141 | + | } | |
| 142 | + | var arg3 int64 | |
| 143 | + | if args[3] != nil { | |
| 144 | + | arg3 = args[3].(int64) | |
| 134 | 145 | } | |
| 135 | 146 | run( | |
| 136 | 147 | arg0, | |
| 137 | 148 | arg1, | |
| 138 | 149 | arg2, | |
| 150 | + | arg3, | |
| 139 | 151 | ) | |
| 140 | 152 | }) | |
| 141 | 153 | return _c | |
| @@ -146,7 +158,7 @@ func (_c *IssueLinkTrigger_OnMergeRequestChanged_Call) Return() *IssueLinkTrigge | |||
| 146 | 158 | return _c | |
| 147 | 159 | } | |
| 148 | 160 | ||
| 149 | - | func (_c *IssueLinkTrigger_OnMergeRequestChanged_Call) RunAndReturn(run func(context1 context.Context, repo domain.Repo, n int64)) *IssueLinkTrigger_OnMergeRequestChanged_Call { | |
| 161 | + | func (_c *IssueLinkTrigger_OnMergeRequestChanged_Call) RunAndReturn(run func(context1 context.Context, principal domain.Principal, repo domain.Repo, n int64)) *IssueLinkTrigger_OnMergeRequestChanged_Call { | |
| 150 | 162 | _c.Run(run) | |
| 151 | 163 | return _c | |
| 152 | 164 | } | |
| @@ -48,8 +48,8 @@ func (_m *IssueLinkTrigger) EXPECT() *IssueLinkTrigger_Expecter { | |||||
| 48 | } | 48 | } | ||
| 49 | 49 | ||||
| 50 | // OnBranchesMoved provides a mock function for the type IssueLinkTrigger | 50 | // OnBranchesMoved provides a mock function for the type IssueLinkTrigger | ||
| 51 | - | func (_mock *IssueLinkTrigger) OnBranchesMoved(context1 context.Context, repoName domain.RepoName, refUpdates []domain.RefUpdate) { | 51 | + | func (_mock *IssueLinkTrigger) OnBranchesMoved(context1 context.Context, principal domain.Principal, repoName domain.RepoName, refUpdates []domain.RefUpdate) { |
| 52 | - | _mock.Called(context1, repoName, refUpdates) | 52 | + | _mock.Called(context1, principal, repoName, refUpdates) |
| 53 | return | 53 | return | ||
| 54 | } | 54 | } | ||
| 55 | 55 | ||||
| @@ -60,30 +60,36 @@ type IssueLinkTrigger_OnBranchesMoved_Call struct { | |||||
| 60 | 60 | ||||
| 61 | // OnBranchesMoved is a helper method to define mock.On call | 61 | // OnBranchesMoved is a helper method to define mock.On call | ||
| 62 | // - context1 context.Context | 62 | // - context1 context.Context | ||
| 63 | + | // - principal domain.Principal | |||
| 63 | // - repoName domain.RepoName | 64 | // - repoName domain.RepoName | ||
| 64 | // - refUpdates []domain.RefUpdate | 65 | // - refUpdates []domain.RefUpdate | ||
| 65 | - | func (_e *IssueLinkTrigger_Expecter) OnBranchesMoved(context1 any, repoName any, refUpdates any) *IssueLinkTrigger_OnBranchesMoved_Call { | 66 | + | func (_e *IssueLinkTrigger_Expecter) OnBranchesMoved(context1 any, principal any, repoName any, refUpdates any) *IssueLinkTrigger_OnBranchesMoved_Call { |
| 66 | - | return &IssueLinkTrigger_OnBranchesMoved_Call{Call: _e.mock.On("OnBranchesMoved", context1, repoName, refUpdates)} | 67 | + | return &IssueLinkTrigger_OnBranchesMoved_Call{Call: _e.mock.On("OnBranchesMoved", context1, principal, repoName, refUpdates)} |
| 67 | } | 68 | } | ||
| 68 | 69 | ||||
| 69 | - | func (_c *IssueLinkTrigger_OnBranchesMoved_Call) Run(run func(context1 context.Context, repoName domain.RepoName, refUpdates []domain.RefUpdate)) *IssueLinkTrigger_OnBranchesMoved_Call { | 70 | + | func (_c *IssueLinkTrigger_OnBranchesMoved_Call) Run(run func(context1 context.Context, principal domain.Principal, repoName domain.RepoName, refUpdates []domain.RefUpdate)) *IssueLinkTrigger_OnBranchesMoved_Call { |
| 70 | _c.Call.Run(func(args mock.Arguments) { | 71 | _c.Call.Run(func(args mock.Arguments) { | ||
| 71 | var arg0 context.Context | 72 | var arg0 context.Context | ||
| 72 | if args[0] != nil { | 73 | if args[0] != nil { | ||
| 73 | arg0 = args[0].(context.Context) | 74 | arg0 = args[0].(context.Context) | ||
| 74 | } | 75 | } | ||
| 75 | - | var arg1 domain.RepoName | 76 | + | var arg1 domain.Principal |
| 76 | if args[1] != nil { | 77 | if args[1] != nil { | ||
| 77 | - | arg1 = args[1].(domain.RepoName) | 78 | + | arg1 = args[1].(domain.Principal) |
| 78 | } | 79 | } | ||
| 79 | - | var arg2 []domain.RefUpdate | 80 | + | var arg2 domain.RepoName |
| 80 | if args[2] != nil { | 81 | if args[2] != nil { | ||
| 81 | - | arg2 = args[2].([]domain.RefUpdate) | 82 | + | arg2 = args[2].(domain.RepoName) |
| 83 | + | } | |||
| 84 | + | var arg3 []domain.RefUpdate | |||
| 85 | + | if args[3] != nil { | |||
| 86 | + | arg3 = args[3].([]domain.RefUpdate) | |||
| 82 | } | 87 | } | ||
| 83 | run( | 88 | run( | ||
| 84 | arg0, | 89 | arg0, | ||
| 85 | arg1, | 90 | arg1, | ||
| 86 | arg2, | 91 | arg2, | ||
| 92 | + | arg3, | |||
| 87 | ) | 93 | ) | ||
| 88 | }) | 94 | }) | ||
| 89 | return _c | 95 | return _c | ||
| @@ -94,14 +100,14 @@ func (_c *IssueLinkTrigger_OnBranchesMoved_Call) Return() *IssueLinkTrigger_OnBr | |||||
| 94 | return _c | 100 | return _c | ||
| 95 | } | 101 | } | ||
| 96 | 102 | ||||
| 97 | - | func (_c *IssueLinkTrigger_OnBranchesMoved_Call) RunAndReturn(run func(context1 context.Context, repoName domain.RepoName, refUpdates []domain.RefUpdate)) *IssueLinkTrigger_OnBranchesMoved_Call { | 103 | + | func (_c *IssueLinkTrigger_OnBranchesMoved_Call) RunAndReturn(run func(context1 context.Context, principal domain.Principal, repoName domain.RepoName, refUpdates []domain.RefUpdate)) *IssueLinkTrigger_OnBranchesMoved_Call { |
| 98 | _c.Run(run) | 104 | _c.Run(run) | ||
| 99 | return _c | 105 | return _c | ||
| 100 | } | 106 | } | ||
| 101 | 107 | ||||
| 102 | // OnMergeRequestChanged provides a mock function for the type IssueLinkTrigger | 108 | // OnMergeRequestChanged provides a mock function for the type IssueLinkTrigger | ||
| 103 | - | func (_mock *IssueLinkTrigger) OnMergeRequestChanged(context1 context.Context, repo domain.Repo, n int64) { | 109 | + | func (_mock *IssueLinkTrigger) OnMergeRequestChanged(context1 context.Context, principal domain.Principal, repo domain.Repo, n int64) { |
| 104 | - | _mock.Called(context1, repo, n) | 110 | + | _mock.Called(context1, principal, repo, n) |
| 105 | return | 111 | return | ||
| 106 | } | 112 | } | ||
| 107 | 113 | ||||
| @@ -112,30 +118,36 @@ type IssueLinkTrigger_OnMergeRequestChanged_Call struct { | |||||
| 112 | 118 | ||||
| 113 | // OnMergeRequestChanged is a helper method to define mock.On call | 119 | // OnMergeRequestChanged is a helper method to define mock.On call | ||
| 114 | // - context1 context.Context | 120 | // - context1 context.Context | ||
| 121 | + | // - principal domain.Principal | |||
| 115 | // - repo domain.Repo | 122 | // - repo domain.Repo | ||
| 116 | // - n int64 | 123 | // - n int64 | ||
| 117 | - | func (_e *IssueLinkTrigger_Expecter) OnMergeRequestChanged(context1 any, repo any, n any) *IssueLinkTrigger_OnMergeRequestChanged_Call { | 124 | + | func (_e *IssueLinkTrigger_Expecter) OnMergeRequestChanged(context1 any, principal any, repo any, n any) *IssueLinkTrigger_OnMergeRequestChanged_Call { |
| 118 | - | return &IssueLinkTrigger_OnMergeRequestChanged_Call{Call: _e.mock.On("OnMergeRequestChanged", context1, repo, n)} | 125 | + | return &IssueLinkTrigger_OnMergeRequestChanged_Call{Call: _e.mock.On("OnMergeRequestChanged", context1, principal, repo, n)} |
| 119 | } | 126 | } | ||
| 120 | 127 | ||||
| 121 | - | func (_c *IssueLinkTrigger_OnMergeRequestChanged_Call) Run(run func(context1 context.Context, repo domain.Repo, n int64)) *IssueLinkTrigger_OnMergeRequestChanged_Call { | 128 | + | func (_c *IssueLinkTrigger_OnMergeRequestChanged_Call) Run(run func(context1 context.Context, principal domain.Principal, repo domain.Repo, n int64)) *IssueLinkTrigger_OnMergeRequestChanged_Call { |
| 122 | _c.Call.Run(func(args mock.Arguments) { | 129 | _c.Call.Run(func(args mock.Arguments) { | ||
| 123 | var arg0 context.Context | 130 | var arg0 context.Context | ||
| 124 | if args[0] != nil { | 131 | if args[0] != nil { | ||
| 125 | arg0 = args[0].(context.Context) | 132 | arg0 = args[0].(context.Context) | ||
| 126 | } | 133 | } | ||
| 127 | - | var arg1 domain.Repo | 134 | + | var arg1 domain.Principal |
| 128 | if args[1] != nil { | 135 | if args[1] != nil { | ||
| 129 | - | arg1 = args[1].(domain.Repo) | 136 | + | arg1 = args[1].(domain.Principal) |
| 130 | } | 137 | } | ||
| 131 | - | var arg2 int64 | 138 | + | var arg2 domain.Repo |
| 132 | if args[2] != nil { | 139 | if args[2] != nil { | ||
| 133 | - | arg2 = args[2].(int64) | 140 | + | arg2 = args[2].(domain.Repo) |
| 141 | + | } | |||
| 142 | + | var arg3 int64 | |||
| 143 | + | if args[3] != nil { | |||
| 144 | + | arg3 = args[3].(int64) | |||
| 134 | } | 145 | } | ||
| 135 | run( | 146 | run( | ||
| 136 | arg0, | 147 | arg0, | ||
| 137 | arg1, | 148 | arg1, | ||
| 138 | arg2, | 149 | arg2, | ||
| 150 | + | arg3, | |||
| 139 | ) | 151 | ) | ||
| 140 | }) | 152 | }) | ||
| 141 | return _c | 153 | return _c | ||
| @@ -146,7 +158,7 @@ func (_c *IssueLinkTrigger_OnMergeRequestChanged_Call) Return() *IssueLinkTrigge | |||||
| 146 | return _c | 158 | return _c | ||
| 147 | } | 159 | } | ||
| 148 | 160 | ||||
| 149 | - | func (_c *IssueLinkTrigger_OnMergeRequestChanged_Call) RunAndReturn(run func(context1 context.Context, repo domain.Repo, n int64)) *IssueLinkTrigger_OnMergeRequestChanged_Call { | 161 | + | func (_c *IssueLinkTrigger_OnMergeRequestChanged_Call) RunAndReturn(run func(context1 context.Context, principal domain.Principal, repo domain.Repo, n int64)) *IssueLinkTrigger_OnMergeRequestChanged_Call { |
| 150 | _c.Run(run) | 162 | _c.Run(run) | ||
| 151 | return _c | 163 | return _c | ||
| 152 | } | 164 | } | ||
M internal/ports/mocks/IssueService.go +78 -0
| @@ -521,3 +521,81 @@ func (_c *IssueService_Reopen_Call) RunAndReturn(run func(ctx context.Context, p | |||
| 521 | 521 | _c.Call.Return(run) | |
| 522 | 522 | return _c | |
| 523 | 523 | } | |
| 524 | + | ||
| 525 | + | // Resolve provides a mock function for the type IssueService | |
| 526 | + | func (_mock *IssueService) Resolve(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (domain.Issue, error) { | |
| 527 | + | ret := _mock.Called(ctx, p, name, number) | |
| 528 | + | ||
| 529 | + | if len(ret) == 0 { | |
| 530 | + | panic("no return value specified for Resolve") | |
| 531 | + | } | |
| 532 | + | ||
| 533 | + | var r0 domain.Issue | |
| 534 | + | var r1 error | |
| 535 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, int64) (domain.Issue, error)); ok { | |
| 536 | + | return returnFunc(ctx, p, name, number) | |
| 537 | + | } | |
| 538 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, int64) domain.Issue); ok { | |
| 539 | + | r0 = returnFunc(ctx, p, name, number) | |
| 540 | + | } else { | |
| 541 | + | r0 = ret.Get(0).(domain.Issue) | |
| 542 | + | } | |
| 543 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, int64) error); ok { | |
| 544 | + | r1 = returnFunc(ctx, p, name, number) | |
| 545 | + | } else { | |
| 546 | + | r1 = ret.Error(1) | |
| 547 | + | } | |
| 548 | + | return r0, r1 | |
| 549 | + | } | |
| 550 | + | ||
| 551 | + | // IssueService_Resolve_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Resolve' | |
| 552 | + | type IssueService_Resolve_Call struct { | |
| 553 | + | *mock.Call | |
| 554 | + | } | |
| 555 | + | ||
| 556 | + | // Resolve is a helper method to define mock.On call | |
| 557 | + | // - ctx context.Context | |
| 558 | + | // - p domain.Principal | |
| 559 | + | // - name domain.RepoName | |
| 560 | + | // - number int64 | |
| 561 | + | func (_e *IssueService_Expecter) Resolve(ctx any, p any, name any, number any) *IssueService_Resolve_Call { | |
| 562 | + | return &IssueService_Resolve_Call{Call: _e.mock.On("Resolve", ctx, p, name, number)} | |
| 563 | + | } | |
| 564 | + | ||
| 565 | + | func (_c *IssueService_Resolve_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName, number int64)) *IssueService_Resolve_Call { | |
| 566 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 567 | + | var arg0 context.Context | |
| 568 | + | if args[0] != nil { | |
| 569 | + | arg0 = args[0].(context.Context) | |
| 570 | + | } | |
| 571 | + | var arg1 domain.Principal | |
| 572 | + | if args[1] != nil { | |
| 573 | + | arg1 = args[1].(domain.Principal) | |
| 574 | + | } | |
| 575 | + | var arg2 domain.RepoName | |
| 576 | + | if args[2] != nil { | |
| 577 | + | arg2 = args[2].(domain.RepoName) | |
| 578 | + | } | |
| 579 | + | var arg3 int64 | |
| 580 | + | if args[3] != nil { | |
| 581 | + | arg3 = args[3].(int64) | |
| 582 | + | } | |
| 583 | + | run( | |
| 584 | + | arg0, | |
| 585 | + | arg1, | |
| 586 | + | arg2, | |
| 587 | + | arg3, | |
| 588 | + | ) | |
| 589 | + | }) | |
| 590 | + | return _c | |
| 591 | + | } | |
| 592 | + | ||
| 593 | + | func (_c *IssueService_Resolve_Call) Return(issue domain.Issue, err error) *IssueService_Resolve_Call { | |
| 594 | + | _c.Call.Return(issue, err) | |
| 595 | + | return _c | |
| 596 | + | } | |
| 597 | + | ||
| 598 | + | func (_c *IssueService_Resolve_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (domain.Issue, error)) *IssueService_Resolve_Call { | |
| 599 | + | _c.Call.Return(run) | |
| 600 | + | return _c | |
| 601 | + | } | |
| @@ -521,3 +521,81 @@ func (_c *IssueService_Reopen_Call) RunAndReturn(run func(ctx context.Context, p | |||||
| 521 | _c.Call.Return(run) | 521 | _c.Call.Return(run) | ||
| 522 | return _c | 522 | return _c | ||
| 523 | } | 523 | } | ||
| 524 | + | ||||
| 525 | + | // Resolve provides a mock function for the type IssueService | |||
| 526 | + | func (_mock *IssueService) Resolve(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (domain.Issue, error) { | |||
| 527 | + | ret := _mock.Called(ctx, p, name, number) | |||
| 528 | + | ||||
| 529 | + | if len(ret) == 0 { | |||
| 530 | + | panic("no return value specified for Resolve") | |||
| 531 | + | } | |||
| 532 | + | ||||
| 533 | + | var r0 domain.Issue | |||
| 534 | + | var r1 error | |||
| 535 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, int64) (domain.Issue, error)); ok { | |||
| 536 | + | return returnFunc(ctx, p, name, number) | |||
| 537 | + | } | |||
| 538 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, int64) domain.Issue); ok { | |||
| 539 | + | r0 = returnFunc(ctx, p, name, number) | |||
| 540 | + | } else { | |||
| 541 | + | r0 = ret.Get(0).(domain.Issue) | |||
| 542 | + | } | |||
| 543 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, int64) error); ok { | |||
| 544 | + | r1 = returnFunc(ctx, p, name, number) | |||
| 545 | + | } else { | |||
| 546 | + | r1 = ret.Error(1) | |||
| 547 | + | } | |||
| 548 | + | return r0, r1 | |||
| 549 | + | } | |||
| 550 | + | ||||
| 551 | + | // IssueService_Resolve_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Resolve' | |||
| 552 | + | type IssueService_Resolve_Call struct { | |||
| 553 | + | *mock.Call | |||
| 554 | + | } | |||
| 555 | + | ||||
| 556 | + | // Resolve is a helper method to define mock.On call | |||
| 557 | + | // - ctx context.Context | |||
| 558 | + | // - p domain.Principal | |||
| 559 | + | // - name domain.RepoName | |||
| 560 | + | // - number int64 | |||
| 561 | + | func (_e *IssueService_Expecter) Resolve(ctx any, p any, name any, number any) *IssueService_Resolve_Call { | |||
| 562 | + | return &IssueService_Resolve_Call{Call: _e.mock.On("Resolve", ctx, p, name, number)} | |||
| 563 | + | } | |||
| 564 | + | ||||
| 565 | + | func (_c *IssueService_Resolve_Call) Run(run func(ctx context.Context, p domain.Principal, name domain.RepoName, number int64)) *IssueService_Resolve_Call { | |||
| 566 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 567 | + | var arg0 context.Context | |||
| 568 | + | if args[0] != nil { | |||
| 569 | + | arg0 = args[0].(context.Context) | |||
| 570 | + | } | |||
| 571 | + | var arg1 domain.Principal | |||
| 572 | + | if args[1] != nil { | |||
| 573 | + | arg1 = args[1].(domain.Principal) | |||
| 574 | + | } | |||
| 575 | + | var arg2 domain.RepoName | |||
| 576 | + | if args[2] != nil { | |||
| 577 | + | arg2 = args[2].(domain.RepoName) | |||
| 578 | + | } | |||
| 579 | + | var arg3 int64 | |||
| 580 | + | if args[3] != nil { | |||
| 581 | + | arg3 = args[3].(int64) | |||
| 582 | + | } | |||
| 583 | + | run( | |||
| 584 | + | arg0, | |||
| 585 | + | arg1, | |||
| 586 | + | arg2, | |||
| 587 | + | arg3, | |||
| 588 | + | ) | |||
| 589 | + | }) | |||
| 590 | + | return _c | |||
| 591 | + | } | |||
| 592 | + | ||||
| 593 | + | func (_c *IssueService_Resolve_Call) Return(issue domain.Issue, err error) *IssueService_Resolve_Call { | |||
| 594 | + | _c.Call.Return(issue, err) | |||
| 595 | + | return _c | |||
| 596 | + | } | |||
| 597 | + | ||||
| 598 | + | func (_c *IssueService_Resolve_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (domain.Issue, error)) *IssueService_Resolve_Call { | |||
| 599 | + | _c.Call.Return(run) | |||
| 600 | + | return _c | |||
| 601 | + | } | |||
M internal/ports/mocks/IssueStore.go +97 -8
| @@ -332,6 +332,80 @@ func (_c *IssueStore_ListComments_Call) RunAndReturn(run func(ctx context.Contex | |||
| 332 | 332 | return _c | |
| 333 | 333 | } | |
| 334 | 334 | ||
| 335 | + | // ListEvents provides a mock function for the type IssueStore | |
| 336 | + | func (_mock *IssueStore) ListEvents(ctx context.Context, repo domain.RepoName, number int64) ([]domain.IssueEvent, error) { | |
| 337 | + | ret := _mock.Called(ctx, repo, number) | |
| 338 | + | ||
| 339 | + | if len(ret) == 0 { | |
| 340 | + | panic("no return value specified for ListEvents") | |
| 341 | + | } | |
| 342 | + | ||
| 343 | + | var r0 []domain.IssueEvent | |
| 344 | + | var r1 error | |
| 345 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, int64) ([]domain.IssueEvent, error)); ok { | |
| 346 | + | return returnFunc(ctx, repo, number) | |
| 347 | + | } | |
| 348 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, int64) []domain.IssueEvent); ok { | |
| 349 | + | r0 = returnFunc(ctx, repo, number) | |
| 350 | + | } else { | |
| 351 | + | if ret.Get(0) != nil { | |
| 352 | + | r0 = ret.Get(0).([]domain.IssueEvent) | |
| 353 | + | } | |
| 354 | + | } | |
| 355 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, domain.RepoName, int64) error); ok { | |
| 356 | + | r1 = returnFunc(ctx, repo, number) | |
| 357 | + | } else { | |
| 358 | + | r1 = ret.Error(1) | |
| 359 | + | } | |
| 360 | + | return r0, r1 | |
| 361 | + | } | |
| 362 | + | ||
| 363 | + | // IssueStore_ListEvents_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListEvents' | |
| 364 | + | type IssueStore_ListEvents_Call struct { | |
| 365 | + | *mock.Call | |
| 366 | + | } | |
| 367 | + | ||
| 368 | + | // ListEvents is a helper method to define mock.On call | |
| 369 | + | // - ctx context.Context | |
| 370 | + | // - repo domain.RepoName | |
| 371 | + | // - number int64 | |
| 372 | + | func (_e *IssueStore_Expecter) ListEvents(ctx any, repo any, number any) *IssueStore_ListEvents_Call { | |
| 373 | + | return &IssueStore_ListEvents_Call{Call: _e.mock.On("ListEvents", ctx, repo, number)} | |
| 374 | + | } | |
| 375 | + | ||
| 376 | + | func (_c *IssueStore_ListEvents_Call) Run(run func(ctx context.Context, repo domain.RepoName, number int64)) *IssueStore_ListEvents_Call { | |
| 377 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 378 | + | var arg0 context.Context | |
| 379 | + | if args[0] != nil { | |
| 380 | + | arg0 = args[0].(context.Context) | |
| 381 | + | } | |
| 382 | + | var arg1 domain.RepoName | |
| 383 | + | if args[1] != nil { | |
| 384 | + | arg1 = args[1].(domain.RepoName) | |
| 385 | + | } | |
| 386 | + | var arg2 int64 | |
| 387 | + | if args[2] != nil { | |
| 388 | + | arg2 = args[2].(int64) | |
| 389 | + | } | |
| 390 | + | run( | |
| 391 | + | arg0, | |
| 392 | + | arg1, | |
| 393 | + | arg2, | |
| 394 | + | ) | |
| 395 | + | }) | |
| 396 | + | return _c | |
| 397 | + | } | |
| 398 | + | ||
| 399 | + | func (_c *IssueStore_ListEvents_Call) Return(issueEvents []domain.IssueEvent, err error) *IssueStore_ListEvents_Call { | |
| 400 | + | _c.Call.Return(issueEvents, err) | |
| 401 | + | return _c | |
| 402 | + | } | |
| 403 | + | ||
| 404 | + | func (_c *IssueStore_ListEvents_Call) RunAndReturn(run func(ctx context.Context, repo domain.RepoName, number int64) ([]domain.IssueEvent, error)) *IssueStore_ListEvents_Call { | |
| 405 | + | _c.Call.Return(run) | |
| 406 | + | return _c | |
| 407 | + | } | |
| 408 | + | ||
| 335 | 409 | // ListOpenAll provides a mock function for the type IssueStore | |
| 336 | 410 | func (_mock *IssueStore) ListOpenAll(ctx context.Context) ([]domain.Issue, error) { | |
| 337 | 411 | ret := _mock.Called(ctx) | |
| @@ -458,16 +532,22 @@ func (_c *IssueStore_SaveComment_Call) RunAndReturn(run func(ctx context.Context | |||
| 458 | 532 | } | |
| 459 | 533 | ||
| 460 | 534 | // Update provides a mock function for the type IssueStore | |
| 461 | - | func (_mock *IssueStore) Update(ctx context.Context, issue domain.Issue) error { | |
| 462 | - | ret := _mock.Called(ctx, issue) | |
| 535 | + | func (_mock *IssueStore) Update(ctx context.Context, issue domain.Issue, events ...domain.IssueEvent) error { | |
| 536 | + | var tmpRet mock.Arguments | |
| 537 | + | if len(events) > 0 { | |
| 538 | + | tmpRet = _mock.Called(ctx, issue, events) | |
| 539 | + | } else { | |
| 540 | + | tmpRet = _mock.Called(ctx, issue) | |
| 541 | + | } | |
| 542 | + | ret := tmpRet | |
| 463 | 543 | ||
| 464 | 544 | if len(ret) == 0 { | |
| 465 | 545 | panic("no return value specified for Update") | |
| 466 | 546 | } | |
| 467 | 547 | ||
| 468 | 548 | var r0 error | |
| 469 | - | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Issue) error); ok { | |
| 470 | - | r0 = returnFunc(ctx, issue) | |
| 549 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Issue, ...domain.IssueEvent) error); ok { | |
| 550 | + | r0 = returnFunc(ctx, issue, events...) | |
| 471 | 551 | } else { | |
| 472 | 552 | r0 = ret.Error(0) | |
| 473 | 553 | } | |
| @@ -482,11 +562,13 @@ type IssueStore_Update_Call struct { | |||
| 482 | 562 | // Update is a helper method to define mock.On call | |
| 483 | 563 | // - ctx context.Context | |
| 484 | 564 | // - issue domain.Issue | |
| 485 | - | func (_e *IssueStore_Expecter) Update(ctx any, issue any) *IssueStore_Update_Call { | |
| 486 | - | return &IssueStore_Update_Call{Call: _e.mock.On("Update", ctx, issue)} | |
| 565 | + | // - events ...domain.IssueEvent | |
| 566 | + | func (_e *IssueStore_Expecter) Update(ctx any, issue any, events ...any) *IssueStore_Update_Call { | |
| 567 | + | return &IssueStore_Update_Call{Call: _e.mock.On("Update", | |
| 568 | + | append([]any{ctx, issue}, events...)...)} | |
| 487 | 569 | } | |
| 488 | 570 | ||
| 489 | - | func (_c *IssueStore_Update_Call) Run(run func(ctx context.Context, issue domain.Issue)) *IssueStore_Update_Call { | |
| 571 | + | func (_c *IssueStore_Update_Call) Run(run func(ctx context.Context, issue domain.Issue, events ...domain.IssueEvent)) *IssueStore_Update_Call { | |
| 490 | 572 | _c.Call.Run(func(args mock.Arguments) { | |
| 491 | 573 | var arg0 context.Context | |
| 492 | 574 | if args[0] != nil { | |
| @@ -496,9 +578,16 @@ func (_c *IssueStore_Update_Call) Run(run func(ctx context.Context, issue domain | |||
| 496 | 578 | if args[1] != nil { | |
| 497 | 579 | arg1 = args[1].(domain.Issue) | |
| 498 | 580 | } | |
| 581 | + | var arg2 []domain.IssueEvent | |
| 582 | + | var variadicArgs []domain.IssueEvent | |
| 583 | + | if len(args) > 2 { | |
| 584 | + | variadicArgs = args[2].([]domain.IssueEvent) | |
| 585 | + | } | |
| 586 | + | arg2 = variadicArgs | |
| 499 | 587 | run( | |
| 500 | 588 | arg0, | |
| 501 | 589 | arg1, | |
| 590 | + | arg2..., | |
| 502 | 591 | ) | |
| 503 | 592 | }) | |
| 504 | 593 | return _c | |
| @@ -509,7 +598,7 @@ func (_c *IssueStore_Update_Call) Return(err error) *IssueStore_Update_Call { | |||
| 509 | 598 | return _c | |
| 510 | 599 | } | |
| 511 | 600 | ||
| 512 | - | func (_c *IssueStore_Update_Call) RunAndReturn(run func(ctx context.Context, issue domain.Issue) error) *IssueStore_Update_Call { | |
| 601 | + | func (_c *IssueStore_Update_Call) RunAndReturn(run func(ctx context.Context, issue domain.Issue, events ...domain.IssueEvent) error) *IssueStore_Update_Call { | |
| 513 | 602 | _c.Call.Return(run) | |
| 514 | 603 | return _c | |
| 515 | 604 | } | |
| @@ -332,6 +332,80 @@ func (_c *IssueStore_ListComments_Call) RunAndReturn(run func(ctx context.Contex | |||||
| 332 | return _c | 332 | return _c | ||
| 333 | } | 333 | } | ||
| 334 | 334 | ||||
| 335 | + | // ListEvents provides a mock function for the type IssueStore | |||
| 336 | + | func (_mock *IssueStore) ListEvents(ctx context.Context, repo domain.RepoName, number int64) ([]domain.IssueEvent, error) { | |||
| 337 | + | ret := _mock.Called(ctx, repo, number) | |||
| 338 | + | ||||
| 339 | + | if len(ret) == 0 { | |||
| 340 | + | panic("no return value specified for ListEvents") | |||
| 341 | + | } | |||
| 342 | + | ||||
| 343 | + | var r0 []domain.IssueEvent | |||
| 344 | + | var r1 error | |||
| 345 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, int64) ([]domain.IssueEvent, error)); ok { | |||
| 346 | + | return returnFunc(ctx, repo, number) | |||
| 347 | + | } | |||
| 348 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, int64) []domain.IssueEvent); ok { | |||
| 349 | + | r0 = returnFunc(ctx, repo, number) | |||
| 350 | + | } else { | |||
| 351 | + | if ret.Get(0) != nil { | |||
| 352 | + | r0 = ret.Get(0).([]domain.IssueEvent) | |||
| 353 | + | } | |||
| 354 | + | } | |||
| 355 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, domain.RepoName, int64) error); ok { | |||
| 356 | + | r1 = returnFunc(ctx, repo, number) | |||
| 357 | + | } else { | |||
| 358 | + | r1 = ret.Error(1) | |||
| 359 | + | } | |||
| 360 | + | return r0, r1 | |||
| 361 | + | } | |||
| 362 | + | ||||
| 363 | + | // IssueStore_ListEvents_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListEvents' | |||
| 364 | + | type IssueStore_ListEvents_Call struct { | |||
| 365 | + | *mock.Call | |||
| 366 | + | } | |||
| 367 | + | ||||
| 368 | + | // ListEvents is a helper method to define mock.On call | |||
| 369 | + | // - ctx context.Context | |||
| 370 | + | // - repo domain.RepoName | |||
| 371 | + | // - number int64 | |||
| 372 | + | func (_e *IssueStore_Expecter) ListEvents(ctx any, repo any, number any) *IssueStore_ListEvents_Call { | |||
| 373 | + | return &IssueStore_ListEvents_Call{Call: _e.mock.On("ListEvents", ctx, repo, number)} | |||
| 374 | + | } | |||
| 375 | + | ||||
| 376 | + | func (_c *IssueStore_ListEvents_Call) Run(run func(ctx context.Context, repo domain.RepoName, number int64)) *IssueStore_ListEvents_Call { | |||
| 377 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 378 | + | var arg0 context.Context | |||
| 379 | + | if args[0] != nil { | |||
| 380 | + | arg0 = args[0].(context.Context) | |||
| 381 | + | } | |||
| 382 | + | var arg1 domain.RepoName | |||
| 383 | + | if args[1] != nil { | |||
| 384 | + | arg1 = args[1].(domain.RepoName) | |||
| 385 | + | } | |||
| 386 | + | var arg2 int64 | |||
| 387 | + | if args[2] != nil { | |||
| 388 | + | arg2 = args[2].(int64) | |||
| 389 | + | } | |||
| 390 | + | run( | |||
| 391 | + | arg0, | |||
| 392 | + | arg1, | |||
| 393 | + | arg2, | |||
| 394 | + | ) | |||
| 395 | + | }) | |||
| 396 | + | return _c | |||
| 397 | + | } | |||
| 398 | + | ||||
| 399 | + | func (_c *IssueStore_ListEvents_Call) Return(issueEvents []domain.IssueEvent, err error) *IssueStore_ListEvents_Call { | |||
| 400 | + | _c.Call.Return(issueEvents, err) | |||
| 401 | + | return _c | |||
| 402 | + | } | |||
| 403 | + | ||||
| 404 | + | func (_c *IssueStore_ListEvents_Call) RunAndReturn(run func(ctx context.Context, repo domain.RepoName, number int64) ([]domain.IssueEvent, error)) *IssueStore_ListEvents_Call { | |||
| 405 | + | _c.Call.Return(run) | |||
| 406 | + | return _c | |||
| 407 | + | } | |||
| 408 | + | ||||
| 335 | // ListOpenAll provides a mock function for the type IssueStore | 409 | // ListOpenAll provides a mock function for the type IssueStore | ||
| 336 | func (_mock *IssueStore) ListOpenAll(ctx context.Context) ([]domain.Issue, error) { | 410 | func (_mock *IssueStore) ListOpenAll(ctx context.Context) ([]domain.Issue, error) { | ||
| 337 | ret := _mock.Called(ctx) | 411 | ret := _mock.Called(ctx) | ||
| @@ -458,16 +532,22 @@ func (_c *IssueStore_SaveComment_Call) RunAndReturn(run func(ctx context.Context | |||||
| 458 | } | 532 | } | ||
| 459 | 533 | ||||
| 460 | // Update provides a mock function for the type IssueStore | 534 | // Update provides a mock function for the type IssueStore | ||
| 461 | - | func (_mock *IssueStore) Update(ctx context.Context, issue domain.Issue) error { | 535 | + | func (_mock *IssueStore) Update(ctx context.Context, issue domain.Issue, events ...domain.IssueEvent) error { |
| 462 | - | ret := _mock.Called(ctx, issue) | 536 | + | var tmpRet mock.Arguments |
| 537 | + | if len(events) > 0 { | |||
| 538 | + | tmpRet = _mock.Called(ctx, issue, events) | |||
| 539 | + | } else { | |||
| 540 | + | tmpRet = _mock.Called(ctx, issue) | |||
| 541 | + | } | |||
| 542 | + | ret := tmpRet | |||
| 463 | 543 | ||||
| 464 | if len(ret) == 0 { | 544 | if len(ret) == 0 { | ||
| 465 | panic("no return value specified for Update") | 545 | panic("no return value specified for Update") | ||
| 466 | } | 546 | } | ||
| 467 | 547 | ||||
| 468 | var r0 error | 548 | var r0 error | ||
| 469 | - | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Issue) error); ok { | 549 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Issue, ...domain.IssueEvent) error); ok { |
| 470 | - | r0 = returnFunc(ctx, issue) | 550 | + | r0 = returnFunc(ctx, issue, events...) |
| 471 | } else { | 551 | } else { | ||
| 472 | r0 = ret.Error(0) | 552 | r0 = ret.Error(0) | ||
| 473 | } | 553 | } | ||
| @@ -482,11 +562,13 @@ type IssueStore_Update_Call struct { | |||||
| 482 | // Update is a helper method to define mock.On call | 562 | // Update is a helper method to define mock.On call | ||
| 483 | // - ctx context.Context | 563 | // - ctx context.Context | ||
| 484 | // - issue domain.Issue | 564 | // - issue domain.Issue | ||
| 485 | - | func (_e *IssueStore_Expecter) Update(ctx any, issue any) *IssueStore_Update_Call { | 565 | + | // - events ...domain.IssueEvent |
| 486 | - | return &IssueStore_Update_Call{Call: _e.mock.On("Update", ctx, issue)} | 566 | + | func (_e *IssueStore_Expecter) Update(ctx any, issue any, events ...any) *IssueStore_Update_Call { |
| 567 | + | return &IssueStore_Update_Call{Call: _e.mock.On("Update", | |||
| 568 | + | append([]any{ctx, issue}, events...)...)} | |||
| 487 | } | 569 | } | ||
| 488 | 570 | ||||
| 489 | - | func (_c *IssueStore_Update_Call) Run(run func(ctx context.Context, issue domain.Issue)) *IssueStore_Update_Call { | 571 | + | func (_c *IssueStore_Update_Call) Run(run func(ctx context.Context, issue domain.Issue, events ...domain.IssueEvent)) *IssueStore_Update_Call { |
| 490 | _c.Call.Run(func(args mock.Arguments) { | 572 | _c.Call.Run(func(args mock.Arguments) { | ||
| 491 | var arg0 context.Context | 573 | var arg0 context.Context | ||
| 492 | if args[0] != nil { | 574 | if args[0] != nil { | ||
| @@ -496,9 +578,16 @@ func (_c *IssueStore_Update_Call) Run(run func(ctx context.Context, issue domain | |||||
| 496 | if args[1] != nil { | 578 | if args[1] != nil { | ||
| 497 | arg1 = args[1].(domain.Issue) | 579 | arg1 = args[1].(domain.Issue) | ||
| 498 | } | 580 | } | ||
| 581 | + | var arg2 []domain.IssueEvent | |||
| 582 | + | var variadicArgs []domain.IssueEvent | |||
| 583 | + | if len(args) > 2 { | |||
| 584 | + | variadicArgs = args[2].([]domain.IssueEvent) | |||
| 585 | + | } | |||
| 586 | + | arg2 = variadicArgs | |||
| 499 | run( | 587 | run( | ||
| 500 | arg0, | 588 | arg0, | ||
| 501 | arg1, | 589 | arg1, | ||
| 590 | + | arg2..., | |||
| 502 | ) | 591 | ) | ||
| 503 | }) | 592 | }) | ||
| 504 | return _c | 593 | return _c | ||
| @@ -509,7 +598,7 @@ func (_c *IssueStore_Update_Call) Return(err error) *IssueStore_Update_Call { | |||||
| 509 | return _c | 598 | return _c | ||
| 510 | } | 599 | } | ||
| 511 | 600 | ||||
| 512 | - | func (_c *IssueStore_Update_Call) RunAndReturn(run func(ctx context.Context, issue domain.Issue) error) *IssueStore_Update_Call { | 601 | + | func (_c *IssueStore_Update_Call) RunAndReturn(run func(ctx context.Context, issue domain.Issue, events ...domain.IssueEvent) error) *IssueStore_Update_Call { |
| 513 | _c.Call.Return(run) | 602 | _c.Call.Return(run) | ||
| 514 | return _c | 603 | return _c | ||
| 515 | } | 604 | } | ||
M internal/ports/ports.go +12 -4
| @@ -267,7 +267,11 @@ type IssueStore interface { | |||
| 267 | 267 | // Update replaces the stored issue and keeps the index of the open issues | |
| 268 | 268 | // in step. It returns domain.ErrNotFound if there is none and | |
| 269 | 269 | // domain.ErrConflict if another write changed it since issue was read. | |
| 270 | - | Update(ctx context.Context, issue domain.Issue) error | |
| 270 | + | // Events are appended atomically with the write, with sequence IDs assigned | |
| 271 | + | // by the store. Events beyond kvstore.MaxIssueEvents (500) are silently dropped. | |
| 272 | + | Update(ctx context.Context, issue domain.Issue, events ...domain.IssueEvent) error | |
| 273 | + | // ListEvents returns history oldest first. | |
| 274 | + | ListEvents(ctx context.Context, repo domain.RepoName, number int64) ([]domain.IssueEvent, error) | |
| 271 | 275 | // SaveComment writes a comment and the issue it belongs to in one batch: a | |
| 272 | 276 | // new comment (Version 0) must not exist yet, an existing one must be | |
| 273 | 277 | // unchanged since it was read, and so must issue. It returns | |
| @@ -548,7 +552,11 @@ type IssueLinkStore interface { | |||
| 548 | 552 | // ReplaceLinks atomically replaces an MR's links (closes is a subset of issues). | |
| 549 | 553 | // Added includes new links and links whose closes flag turned on. | |
| 550 | 554 | // Returns ErrNotFound for a missing repository, ErrConflict on a race. | |
| 551 | - | ReplaceLinks(ctx context.Context, repo domain.RepoName, mr int64, issues, closes []int64) (added, removed []domain.IssueLink, err error) | |
| 555 | + | ReplaceLinks(ctx context.Context, repo domain.RepoName, mr int64, issues, closes []int64, ref ReferenceEvent) (added, removed []domain.IssueLink, err error) | |
| 556 | + | // EventsOfMergeRequest returns at most 500 events, oldest CreatedAt first, | |
| 557 | + | // breaking ties by issue number then sequence ID. Events can name issues | |
| 558 | + | // absent from the current links. | |
| 559 | + | EventsOfMergeRequest(ctx context.Context, repo domain.RepoName, mr int64) ([]domain.IssueEvent, error) | |
| 552 | 560 | // LinksOfIssue returns links ascending by MR number, at most limit. | |
| 553 | 561 | LinksOfIssue(ctx context.Context, repo domain.RepoName, issue int64, limit int) ([]domain.IssueLink, error) | |
| 554 | 562 | // LinksOfMergeRequest returns the issues named by an MR. | |
| @@ -562,8 +570,8 @@ type Clock interface { | |||
| 562 | 570 | ||
| 563 | 571 | // IssueLinkTrigger updates derived links without failing the primary operation. | |
| 564 | 572 | type IssueLinkTrigger interface { | |
| 565 | - | OnMergeRequestChanged(context.Context, domain.Repo, int64) | |
| 573 | + | OnMergeRequestChanged(context.Context, domain.Principal, domain.Repo, int64) | |
| 566 | 574 | OnMergeRequestSettled(context.Context, domain.Repo, domain.MergeRequest) | |
| 567 | - | OnBranchesMoved(context.Context, domain.RepoName, []domain.RefUpdate) | |
| 575 | + | OnBranchesMoved(context.Context, domain.Principal, domain.RepoName, []domain.RefUpdate) | |
| 568 | 576 | OnMerged(context.Context, domain.Principal, domain.Repo, domain.MergeRequest) | |
| 569 | 577 | } | |
| @@ -267,7 +267,11 @@ type IssueStore interface { | |||||
| 267 | // Update replaces the stored issue and keeps the index of the open issues | 267 | // Update replaces the stored issue and keeps the index of the open issues | ||
| 268 | // in step. It returns domain.ErrNotFound if there is none and | 268 | // in step. It returns domain.ErrNotFound if there is none and | ||
| 269 | // domain.ErrConflict if another write changed it since issue was read. | 269 | // domain.ErrConflict if another write changed it since issue was read. | ||
| 270 | - | Update(ctx context.Context, issue domain.Issue) error | 270 | + | // Events are appended atomically with the write, with sequence IDs assigned |
| 271 | + | // by the store. Events beyond kvstore.MaxIssueEvents (500) are silently dropped. | |||
| 272 | + | Update(ctx context.Context, issue domain.Issue, events ...domain.IssueEvent) error | |||
| 273 | + | // ListEvents returns history oldest first. | |||
| 274 | + | ListEvents(ctx context.Context, repo domain.RepoName, number int64) ([]domain.IssueEvent, error) | |||
| 271 | // SaveComment writes a comment and the issue it belongs to in one batch: a | 275 | // SaveComment writes a comment and the issue it belongs to in one batch: a | ||
| 272 | // new comment (Version 0) must not exist yet, an existing one must be | 276 | // new comment (Version 0) must not exist yet, an existing one must be | ||
| 273 | // unchanged since it was read, and so must issue. It returns | 277 | // unchanged since it was read, and so must issue. It returns | ||
| @@ -548,7 +552,11 @@ type IssueLinkStore interface { | |||||
| 548 | // ReplaceLinks atomically replaces an MR's links (closes is a subset of issues). | 552 | // ReplaceLinks atomically replaces an MR's links (closes is a subset of issues). | ||
| 549 | // Added includes new links and links whose closes flag turned on. | 553 | // Added includes new links and links whose closes flag turned on. | ||
| 550 | // Returns ErrNotFound for a missing repository, ErrConflict on a race. | 554 | // Returns ErrNotFound for a missing repository, ErrConflict on a race. | ||
| 551 | - | ReplaceLinks(ctx context.Context, repo domain.RepoName, mr int64, issues, closes []int64) (added, removed []domain.IssueLink, err error) | 555 | + | ReplaceLinks(ctx context.Context, repo domain.RepoName, mr int64, issues, closes []int64, ref ReferenceEvent) (added, removed []domain.IssueLink, err error) |
| 556 | + | // EventsOfMergeRequest returns at most 500 events, oldest CreatedAt first, | |||
| 557 | + | // breaking ties by issue number then sequence ID. Events can name issues | |||
| 558 | + | // absent from the current links. | |||
| 559 | + | EventsOfMergeRequest(ctx context.Context, repo domain.RepoName, mr int64) ([]domain.IssueEvent, error) | |||
| 552 | // LinksOfIssue returns links ascending by MR number, at most limit. | 560 | // LinksOfIssue returns links ascending by MR number, at most limit. | ||
| 553 | LinksOfIssue(ctx context.Context, repo domain.RepoName, issue int64, limit int) ([]domain.IssueLink, error) | 561 | LinksOfIssue(ctx context.Context, repo domain.RepoName, issue int64, limit int) ([]domain.IssueLink, error) | ||
| 554 | // LinksOfMergeRequest returns the issues named by an MR. | 562 | // LinksOfMergeRequest returns the issues named by an MR. | ||
| @@ -562,8 +570,8 @@ type Clock interface { | |||||
| 562 | 570 | ||||
| 563 | // IssueLinkTrigger updates derived links without failing the primary operation. | 571 | // IssueLinkTrigger updates derived links without failing the primary operation. | ||
| 564 | type IssueLinkTrigger interface { | 572 | type IssueLinkTrigger interface { | ||
| 565 | - | OnMergeRequestChanged(context.Context, domain.Repo, int64) | 573 | + | OnMergeRequestChanged(context.Context, domain.Principal, domain.Repo, int64) |
| 566 | OnMergeRequestSettled(context.Context, domain.Repo, domain.MergeRequest) | 574 | OnMergeRequestSettled(context.Context, domain.Repo, domain.MergeRequest) | ||
| 567 | - | OnBranchesMoved(context.Context, domain.RepoName, []domain.RefUpdate) | 575 | + | OnBranchesMoved(context.Context, domain.Principal, domain.RepoName, []domain.RefUpdate) |
| 568 | OnMerged(context.Context, domain.Principal, domain.Repo, domain.MergeRequest) | 576 | OnMerged(context.Context, domain.Principal, domain.Repo, domain.MergeRequest) | ||
| 569 | } | 577 | } | ||
M internal/ports/services.go +1 -0
| @@ -146,6 +146,7 @@ type IssueService interface { | |||
| 146 | 146 | // Get returns the issue with its comments, oldest first. | |
| 147 | 147 | Get(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (IssueDetail, error) | |
| 148 | 148 | AddComment(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, text string) (domain.IssueComment, error) | |
| 149 | + | Resolve(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (domain.Issue, error) | |
| 149 | 150 | Close(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (domain.Issue, error) | |
| 150 | 151 | Reopen(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (domain.Issue, error) | |
| 151 | 152 | } | |
| @@ -146,6 +146,7 @@ type IssueService interface { | |||||
| 146 | // Get returns the issue with its comments, oldest first. | 146 | // Get returns the issue with its comments, oldest first. | ||
| 147 | Get(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (IssueDetail, error) | 147 | Get(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (IssueDetail, error) | ||
| 148 | AddComment(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, text string) (domain.IssueComment, error) | 148 | AddComment(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, text string) (domain.IssueComment, error) | ||
| 149 | + | Resolve(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (domain.Issue, error) | |||
| 149 | Close(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (domain.Issue, error) | 150 | Close(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (domain.Issue, error) | ||
| 150 | Reopen(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (domain.Issue, error) | 151 | Reopen(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (domain.Issue, error) | ||
| 151 | } | 152 | } | ||
M internal/usecase/issue_links.go +9 -9
| @@ -81,11 +81,11 @@ func NewIssueLinks(cfg IssueLinksConfig) *IssueLinks { | |||
| 81 | 81 | } | |
| 82 | 82 | ||
| 83 | 83 | // Reindex replaces links from the current texts and bounded commit range. | |
| 84 | - | func (l *IssueLinks) Reindex(ctx context.Context, repo domain.Repo, number int64) error { | |
| 85 | - | _, err := l.reindex(ctx, repo, number) | |
| 84 | + | func (l *IssueLinks) Reindex(ctx context.Context, p domain.Principal, repo domain.Repo, number int64) error { | |
| 85 | + | _, err := l.reindex(ctx, p, repo, number) | |
| 86 | 86 | return err | |
| 87 | 87 | } | |
| 88 | - | func (l *IssueLinks) reindex(ctx context.Context, repo domain.Repo, number int64) ([]int64, error) { | |
| 88 | + | func (l *IssueLinks) reindex(ctx context.Context, p domain.Principal, repo domain.Repo, number int64) ([]int64, error) { | |
| 89 | 89 | unlock, err := l.lockMR(ctx, repo.Name, number) | |
| 90 | 90 | if err != nil { | |
| 91 | 91 | return nil, err | |
| @@ -153,7 +153,7 @@ func (l *IssueLinks) reindex(ctx context.Context, repo domain.Repo, number int64 | |||
| 153 | 153 | } | |
| 154 | 154 | } | |
| 155 | 155 | for range maxWriteRetries { | |
| 156 | - | added, removed, err := l.cfg.Links.ReplaceLinks(ctx, repo.Name, number, mentions, closes) | |
| 156 | + | added, removed, err := l.cfg.Links.ReplaceLinks(ctx, repo.Name, number, mentions, closes, ports.ReferenceEvent{}) | |
| 157 | 157 | if errors.Is(err, domain.ErrConflict) { | |
| 158 | 158 | continue | |
| 159 | 159 | } | |
| @@ -186,10 +186,10 @@ func linkWarning(ctx context.Context, err error) { | |||
| 186 | 186 | log.Warn().Err(err).Msg("issue links not updated") | |
| 187 | 187 | } | |
| 188 | 188 | } | |
| 189 | - | func (l *IssueLinks) OnMergeRequestChanged(ctx context.Context, repo domain.Repo, number int64) { | |
| 189 | + | func (l *IssueLinks) OnMergeRequestChanged(ctx context.Context, p domain.Principal, repo domain.Repo, number int64) { | |
| 190 | 190 | ctx, cancel := linkContext(ctx) | |
| 191 | 191 | defer cancel() | |
| 192 | - | linkWarning(ctx, l.Reindex(ctx, repo, number)) | |
| 192 | + | linkWarning(ctx, l.Reindex(ctx, p, repo, number)) | |
| 193 | 193 | } | |
| 194 | 194 | func (l *IssueLinks) settled(ctx context.Context, repo domain.Repo, mr domain.MergeRequest) { | |
| 195 | 195 | links, err := l.cfg.Links.LinksOfMergeRequest(ctx, repo.Name, mr.Number) | |
| @@ -205,7 +205,7 @@ func (l *IssueLinks) OnMergeRequestSettled(ctx context.Context, repo domain.Repo | |||
| 205 | 205 | defer cancel() | |
| 206 | 206 | l.settled(ctx, repo, mr) | |
| 207 | 207 | } | |
| 208 | - | func (l *IssueLinks) OnBranchesMoved(ctx context.Context, name domain.RepoName, updates []domain.RefUpdate) { | |
| 208 | + | func (l *IssueLinks) OnBranchesMoved(ctx context.Context, p domain.Principal, name domain.RepoName, updates []domain.RefUpdate) { | |
| 209 | 209 | ctx, cancel := linkContext(ctx) | |
| 210 | 210 | defer cancel() | |
| 211 | 211 | var branches []string | |
| @@ -232,14 +232,14 @@ func (l *IssueLinks) OnBranchesMoved(ctx context.Context, name domain.RepoName, | |||
| 232 | 232 | log.Warn().Str("repo", name.String()).Msg("issue link merge requests truncated") | |
| 233 | 233 | } | |
| 234 | 234 | for _, n := range numbers { | |
| 235 | - | linkWarning(ctx, l.Reindex(ctx, repo, n)) | |
| 235 | + | linkWarning(ctx, l.Reindex(ctx, p, repo, n)) | |
| 236 | 236 | } | |
| 237 | 237 | } | |
| 238 | 238 | func (l *IssueLinks) OnMerged(ctx context.Context, p domain.Principal, repo domain.Repo, mr domain.MergeRequest) { | |
| 239 | 239 | ctx, cancel := linkContext(ctx) | |
| 240 | 240 | defer cancel() | |
| 241 | 241 | defer l.settled(ctx, repo, mr) | |
| 242 | - | closes, err := l.reindex(ctx, repo, mr.Number) | |
| 242 | + | closes, err := l.reindex(ctx, p, repo, mr.Number) | |
| 243 | 243 | if err != nil { | |
| 244 | 244 | linkWarning(ctx, err) | |
| 245 | 245 | return | |
| @@ -81,11 +81,11 @@ func NewIssueLinks(cfg IssueLinksConfig) *IssueLinks { | |||||
| 81 | } | 81 | } | ||
| 82 | 82 | ||||
| 83 | // Reindex replaces links from the current texts and bounded commit range. | 83 | // Reindex replaces links from the current texts and bounded commit range. | ||
| 84 | - | func (l *IssueLinks) Reindex(ctx context.Context, repo domain.Repo, number int64) error { | 84 | + | func (l *IssueLinks) Reindex(ctx context.Context, p domain.Principal, repo domain.Repo, number int64) error { |
| 85 | - | _, err := l.reindex(ctx, repo, number) | 85 | + | _, err := l.reindex(ctx, p, repo, number) |
| 86 | return err | 86 | return err | ||
| 87 | } | 87 | } | ||
| 88 | - | func (l *IssueLinks) reindex(ctx context.Context, repo domain.Repo, number int64) ([]int64, error) { | 88 | + | func (l *IssueLinks) reindex(ctx context.Context, p domain.Principal, repo domain.Repo, number int64) ([]int64, error) { |
| 89 | unlock, err := l.lockMR(ctx, repo.Name, number) | 89 | unlock, err := l.lockMR(ctx, repo.Name, number) | ||
| 90 | if err != nil { | 90 | if err != nil { | ||
| 91 | return nil, err | 91 | return nil, err | ||
| @@ -153,7 +153,7 @@ func (l *IssueLinks) reindex(ctx context.Context, repo domain.Repo, number int64 | |||||
| 153 | } | 153 | } | ||
| 154 | } | 154 | } | ||
| 155 | for range maxWriteRetries { | 155 | for range maxWriteRetries { | ||
| 156 | - | added, removed, err := l.cfg.Links.ReplaceLinks(ctx, repo.Name, number, mentions, closes) | 156 | + | added, removed, err := l.cfg.Links.ReplaceLinks(ctx, repo.Name, number, mentions, closes, ports.ReferenceEvent{}) |
| 157 | if errors.Is(err, domain.ErrConflict) { | 157 | if errors.Is(err, domain.ErrConflict) { | ||
| 158 | continue | 158 | continue | ||
| 159 | } | 159 | } | ||
| @@ -186,10 +186,10 @@ func linkWarning(ctx context.Context, err error) { | |||||
| 186 | log.Warn().Err(err).Msg("issue links not updated") | 186 | log.Warn().Err(err).Msg("issue links not updated") | ||
| 187 | } | 187 | } | ||
| 188 | } | 188 | } | ||
| 189 | - | func (l *IssueLinks) OnMergeRequestChanged(ctx context.Context, repo domain.Repo, number int64) { | 189 | + | func (l *IssueLinks) OnMergeRequestChanged(ctx context.Context, p domain.Principal, repo domain.Repo, number int64) { |
| 190 | ctx, cancel := linkContext(ctx) | 190 | ctx, cancel := linkContext(ctx) | ||
| 191 | defer cancel() | 191 | defer cancel() | ||
| 192 | - | linkWarning(ctx, l.Reindex(ctx, repo, number)) | 192 | + | linkWarning(ctx, l.Reindex(ctx, p, repo, number)) |
| 193 | } | 193 | } | ||
| 194 | func (l *IssueLinks) settled(ctx context.Context, repo domain.Repo, mr domain.MergeRequest) { | 194 | func (l *IssueLinks) settled(ctx context.Context, repo domain.Repo, mr domain.MergeRequest) { | ||
| 195 | links, err := l.cfg.Links.LinksOfMergeRequest(ctx, repo.Name, mr.Number) | 195 | links, err := l.cfg.Links.LinksOfMergeRequest(ctx, repo.Name, mr.Number) | ||
| @@ -205,7 +205,7 @@ func (l *IssueLinks) OnMergeRequestSettled(ctx context.Context, repo domain.Repo | |||||
| 205 | defer cancel() | 205 | defer cancel() | ||
| 206 | l.settled(ctx, repo, mr) | 206 | l.settled(ctx, repo, mr) | ||
| 207 | } | 207 | } | ||
| 208 | - | func (l *IssueLinks) OnBranchesMoved(ctx context.Context, name domain.RepoName, updates []domain.RefUpdate) { | 208 | + | func (l *IssueLinks) OnBranchesMoved(ctx context.Context, p domain.Principal, name domain.RepoName, updates []domain.RefUpdate) { |
| 209 | ctx, cancel := linkContext(ctx) | 209 | ctx, cancel := linkContext(ctx) | ||
| 210 | defer cancel() | 210 | defer cancel() | ||
| 211 | var branches []string | 211 | var branches []string | ||
| @@ -232,14 +232,14 @@ func (l *IssueLinks) OnBranchesMoved(ctx context.Context, name domain.RepoName, | |||||
| 232 | log.Warn().Str("repo", name.String()).Msg("issue link merge requests truncated") | 232 | log.Warn().Str("repo", name.String()).Msg("issue link merge requests truncated") | ||
| 233 | } | 233 | } | ||
| 234 | for _, n := range numbers { | 234 | for _, n := range numbers { | ||
| 235 | - | linkWarning(ctx, l.Reindex(ctx, repo, n)) | 235 | + | linkWarning(ctx, l.Reindex(ctx, p, repo, n)) |
| 236 | } | 236 | } | ||
| 237 | } | 237 | } | ||
| 238 | func (l *IssueLinks) OnMerged(ctx context.Context, p domain.Principal, repo domain.Repo, mr domain.MergeRequest) { | 238 | func (l *IssueLinks) OnMerged(ctx context.Context, p domain.Principal, repo domain.Repo, mr domain.MergeRequest) { | ||
| 239 | ctx, cancel := linkContext(ctx) | 239 | ctx, cancel := linkContext(ctx) | ||
| 240 | defer cancel() | 240 | defer cancel() | ||
| 241 | defer l.settled(ctx, repo, mr) | 241 | defer l.settled(ctx, repo, mr) | ||
| 242 | - | closes, err := l.reindex(ctx, repo, mr.Number) | 242 | + | closes, err := l.reindex(ctx, p, repo, mr.Number) |
| 243 | if err != nil { | 243 | if err != nil { | ||
| 244 | linkWarning(ctx, err) | 244 | linkWarning(ctx, err) | ||
| 245 | return | 245 | return | ||
M internal/usecase/issue_links_test.go +5 -5
| @@ -59,7 +59,7 @@ func TestIssueLinksTextsPushAndReferences(t *testing.T) { | |||
| 59 | 59 | assert.Empty(t, got.References) | |
| 60 | 60 | head := r.commit(author, "push.txt", "push", fmt.Sprintf("Fixes #%d", other.Number)) | |
| 61 | 61 | r.push("feat") | |
| 62 | - | links.OnBranchesMoved(r.ctx, r.repo, []domain.RefUpdate{{Ref: "refs/heads/feat", New: head.String()}}) | |
| 62 | + | links.OnBranchesMoved(r.ctx, domain.Principal{}, r.repo, []domain.RefUpdate{{Ref: "refs/heads/feat", New: head.String()}}) | |
| 63 | 63 | got, err = issues.Get(r.ctx, root, r.repo, other.Number) | |
| 64 | 64 | require.NoError(t, err) | |
| 65 | 65 | require.Len(t, got.References, 1) | |
| @@ -161,12 +161,12 @@ func TestIssueLinksReindexSerializedPerMergeRequest(t *testing.T) { | |||
| 161 | 161 | repo, err := r.store.Get(r.ctx, r.repo) | |
| 162 | 162 | require.NoError(t, err) | |
| 163 | 163 | done := make(chan error, 2) | |
| 164 | - | go func() { done <- links.Reindex(r.ctx, repo, mr.Number) }() | |
| 164 | + | go func() { done <- links.Reindex(r.ctx, domain.Principal{}, repo, mr.Number) }() | |
| 165 | 165 | <-g.started | |
| 166 | 166 | // The first reindex read the old texts; a note is added afterwards. | |
| 167 | 167 | _, err = r.mrs.AddComment(r.ctx, root, r.repo, mr.Number, ports.AddCommentParams{Text: fmt.Sprintf("see #%d", issue.Number)}) | |
| 168 | 168 | require.NoError(t, err) | |
| 169 | - | go func() { done <- links.Reindex(r.ctx, repo, mr.Number) }() | |
| 169 | + | go func() { done <- links.Reindex(r.ctx, domain.Principal{}, repo, mr.Number) }() | |
| 170 | 170 | time.Sleep(100 * time.Millisecond) | |
| 171 | 171 | assert.EqualValues(t, 1, g.entered.Load(), "the second reindex waits for the first") | |
| 172 | 172 | close(g.gate) | |
| @@ -202,11 +202,11 @@ func TestIssueLinksNotifiesRemovalsAndClosingFlips(t *testing.T) { | |||
| 202 | 202 | // then is rewritten to a plain mention. | |
| 203 | 203 | head := r.commit(author, "fix.txt", "fix", fmt.Sprintf("Fixes #%d", issue.Number)) | |
| 204 | 204 | r.push("feat") | |
| 205 | - | links.OnBranchesMoved(r.ctx, r.repo, []domain.RefUpdate{{Ref: "refs/heads/feat", New: head.String()}}) | |
| 205 | + | links.OnBranchesMoved(r.ctx, domain.Principal{}, r.repo, []domain.RefUpdate{{Ref: "refs/heads/feat", New: head.String()}}) | |
| 206 | 206 | referenced() | |
| 207 | 207 | r.sh("commit", "-q", "--amend", "-m", fmt.Sprintf("See #%d", issue.Number)) | |
| 208 | 208 | r.push("feat") | |
| 209 | - | links.OnBranchesMoved(r.ctx, r.repo, []domain.RefUpdate{{Ref: "refs/heads/feat", New: r.branchHead("feat").String()}}) | |
| 209 | + | links.OnBranchesMoved(r.ctx, domain.Principal{}, r.repo, []domain.RefUpdate{{Ref: "refs/heads/feat", New: r.branchHead("feat").String()}}) | |
| 210 | 210 | assert.Equal(t, 1, referenced(), "a closing flip notifies once") | |
| 211 | 211 | } | |
| 212 | 212 | ||
| @@ -59,7 +59,7 @@ func TestIssueLinksTextsPushAndReferences(t *testing.T) { | |||||
| 59 | assert.Empty(t, got.References) | 59 | assert.Empty(t, got.References) | ||
| 60 | head := r.commit(author, "push.txt", "push", fmt.Sprintf("Fixes #%d", other.Number)) | 60 | head := r.commit(author, "push.txt", "push", fmt.Sprintf("Fixes #%d", other.Number)) | ||
| 61 | r.push("feat") | 61 | r.push("feat") | ||
| 62 | - | links.OnBranchesMoved(r.ctx, r.repo, []domain.RefUpdate{{Ref: "refs/heads/feat", New: head.String()}}) | 62 | + | links.OnBranchesMoved(r.ctx, domain.Principal{}, r.repo, []domain.RefUpdate{{Ref: "refs/heads/feat", New: head.String()}}) |
| 63 | got, err = issues.Get(r.ctx, root, r.repo, other.Number) | 63 | got, err = issues.Get(r.ctx, root, r.repo, other.Number) | ||
| 64 | require.NoError(t, err) | 64 | require.NoError(t, err) | ||
| 65 | require.Len(t, got.References, 1) | 65 | require.Len(t, got.References, 1) | ||
| @@ -161,12 +161,12 @@ func TestIssueLinksReindexSerializedPerMergeRequest(t *testing.T) { | |||||
| 161 | repo, err := r.store.Get(r.ctx, r.repo) | 161 | repo, err := r.store.Get(r.ctx, r.repo) | ||
| 162 | require.NoError(t, err) | 162 | require.NoError(t, err) | ||
| 163 | done := make(chan error, 2) | 163 | done := make(chan error, 2) | ||
| 164 | - | go func() { done <- links.Reindex(r.ctx, repo, mr.Number) }() | 164 | + | go func() { done <- links.Reindex(r.ctx, domain.Principal{}, repo, mr.Number) }() |
| 165 | <-g.started | 165 | <-g.started | ||
| 166 | // The first reindex read the old texts; a note is added afterwards. | 166 | // The first reindex read the old texts; a note is added afterwards. | ||
| 167 | _, err = r.mrs.AddComment(r.ctx, root, r.repo, mr.Number, ports.AddCommentParams{Text: fmt.Sprintf("see #%d", issue.Number)}) | 167 | _, err = r.mrs.AddComment(r.ctx, root, r.repo, mr.Number, ports.AddCommentParams{Text: fmt.Sprintf("see #%d", issue.Number)}) | ||
| 168 | require.NoError(t, err) | 168 | require.NoError(t, err) | ||
| 169 | - | go func() { done <- links.Reindex(r.ctx, repo, mr.Number) }() | 169 | + | go func() { done <- links.Reindex(r.ctx, domain.Principal{}, repo, mr.Number) }() |
| 170 | time.Sleep(100 * time.Millisecond) | 170 | time.Sleep(100 * time.Millisecond) | ||
| 171 | assert.EqualValues(t, 1, g.entered.Load(), "the second reindex waits for the first") | 171 | assert.EqualValues(t, 1, g.entered.Load(), "the second reindex waits for the first") | ||
| 172 | close(g.gate) | 172 | close(g.gate) | ||
| @@ -202,11 +202,11 @@ func TestIssueLinksNotifiesRemovalsAndClosingFlips(t *testing.T) { | |||||
| 202 | // then is rewritten to a plain mention. | 202 | // then is rewritten to a plain mention. | ||
| 203 | head := r.commit(author, "fix.txt", "fix", fmt.Sprintf("Fixes #%d", issue.Number)) | 203 | head := r.commit(author, "fix.txt", "fix", fmt.Sprintf("Fixes #%d", issue.Number)) | ||
| 204 | r.push("feat") | 204 | r.push("feat") | ||
| 205 | - | links.OnBranchesMoved(r.ctx, r.repo, []domain.RefUpdate{{Ref: "refs/heads/feat", New: head.String()}}) | 205 | + | links.OnBranchesMoved(r.ctx, domain.Principal{}, r.repo, []domain.RefUpdate{{Ref: "refs/heads/feat", New: head.String()}}) |
| 206 | referenced() | 206 | referenced() | ||
| 207 | r.sh("commit", "-q", "--amend", "-m", fmt.Sprintf("See #%d", issue.Number)) | 207 | r.sh("commit", "-q", "--amend", "-m", fmt.Sprintf("See #%d", issue.Number)) | ||
| 208 | r.push("feat") | 208 | r.push("feat") | ||
| 209 | - | links.OnBranchesMoved(r.ctx, r.repo, []domain.RefUpdate{{Ref: "refs/heads/feat", New: r.branchHead("feat").String()}}) | 209 | + | links.OnBranchesMoved(r.ctx, domain.Principal{}, r.repo, []domain.RefUpdate{{Ref: "refs/heads/feat", New: r.branchHead("feat").String()}}) |
| 210 | assert.Equal(t, 1, referenced(), "a closing flip notifies once") | 210 | assert.Equal(t, 1, referenced(), "a closing flip notifies once") | ||
| 211 | } | 211 | } | ||
| 212 | 212 | ||||
M internal/usecase/issues.go +5 -0
| @@ -318,3 +318,8 @@ func (i *Issues) Kind(ctx context.Context, p domain.Principal, name domain.RepoN | |||
| 318 | 318 | } | |
| 319 | 319 | return domain.NumberNone, nil | |
| 320 | 320 | } | |
| 321 | + | ||
| 322 | + | // Resolve is reserved for Phase A; no state is changed by this contract stub. | |
| 323 | + | func (i *Issues) Resolve(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (domain.Issue, error) { | |
| 324 | + | return domain.Issue{}, domain.ErrUnavailable | |
| 325 | + | } | |
| @@ -318,3 +318,8 @@ func (i *Issues) Kind(ctx context.Context, p domain.Principal, name domain.RepoN | |||||
| 318 | } | 318 | } | ||
| 319 | return domain.NumberNone, nil | 319 | return domain.NumberNone, nil | ||
| 320 | } | 320 | } | ||
| 321 | + | ||||
| 322 | + | // Resolve is reserved for Phase A; no state is changed by this contract stub. | |||
| 323 | + | func (i *Issues) Resolve(ctx context.Context, p domain.Principal, name domain.RepoName, number int64) (domain.Issue, error) { | |||
| 324 | + | return domain.Issue{}, domain.ErrUnavailable | |||
| 325 | + | } | |||
M internal/usecase/mergerequests.go +5 -5
| @@ -295,7 +295,7 @@ func (m *MergeRequests) Create(ctx context.Context, p domain.Principal, name dom | |||
| 295 | 295 | recordActivity(ctx, m.meta, repo.Name, now) | |
| 296 | 296 | m.bus.Publish(ctx, domain.MergeRequestOpened{MergeRequest: mr, At: now, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt}) | |
| 297 | 297 | if m.links != nil { | |
| 298 | - | m.links.OnMergeRequestChanged(ctx, repo, mr.Number) | |
| 298 | + | m.links.OnMergeRequestChanged(ctx, p, repo, mr.Number) | |
| 299 | 299 | } | |
| 300 | 300 | if m.mergeab != nil { | |
| 301 | 301 | m.mergeab.OnOpened(ctx, mr) | |
| @@ -440,7 +440,7 @@ func (m *MergeRequests) MarkReady(ctx context.Context, p domain.Principal, name | |||
| 440 | 440 | recordActivity(ctx, m.meta, repo.Name, mr.UpdatedAt) | |
| 441 | 441 | m.bus.Publish(ctx, domain.MergeRequestReady{MergeRequest: mr, At: mr.UpdatedAt, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt}) | |
| 442 | 442 | if m.links != nil { | |
| 443 | - | m.links.OnMergeRequestChanged(ctx, repo, number) | |
| 443 | + | m.links.OnMergeRequestChanged(ctx, p, repo, number) | |
| 444 | 444 | } | |
| 445 | 445 | return m.withHead(ctx, mr) | |
| 446 | 446 | } | |
| @@ -797,7 +797,7 @@ func (m *MergeRequests) AddComment(ctx context.Context, p domain.Principal, name | |||
| 797 | 797 | Repo: name, Number: number, Comment: c, At: c.CreatedAt, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt, | |
| 798 | 798 | }) | |
| 799 | 799 | if m.links != nil { | |
| 800 | - | m.links.OnMergeRequestChanged(ctx, repo, number) | |
| 800 | + | m.links.OnMergeRequestChanged(ctx, p, repo, number) | |
| 801 | 801 | } | |
| 802 | 802 | return c, nil | |
| 803 | 803 | } | |
| @@ -1276,7 +1276,7 @@ func (m *MergeRequests) afterMerge(ctx context.Context, p domain.Principal, repo | |||
| 1276 | 1276 | m.mergeab.OnBranchesMoved(ctx, name, updates) // the other requests of the moved target | |
| 1277 | 1277 | } | |
| 1278 | 1278 | if m.links != nil { | |
| 1279 | - | m.links.OnBranchesMoved(ctx, name, updates) // open requests whose source moved | |
| 1279 | + | m.links.OnBranchesMoved(ctx, p, name, updates) // open requests whose source moved | |
| 1280 | 1280 | } | |
| 1281 | 1281 | } | |
| 1282 | 1282 | ||
| @@ -1553,7 +1553,7 @@ func (m *MergeRequests) changeComment(ctx context.Context, p domain.Principal, n | |||
| 1553 | 1553 | m.bus.Publish(ctx, domain.CommentEdited{Repo: name, Number: number, Comment: c, At: c.EditedAt, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt}) | |
| 1554 | 1554 | } | |
| 1555 | 1555 | if m.links != nil { | |
| 1556 | - | m.links.OnMergeRequestChanged(ctx, repo, number) | |
| 1556 | + | m.links.OnMergeRequestChanged(ctx, p, repo, number) | |
| 1557 | 1557 | } | |
| 1558 | 1558 | return c, nil | |
| 1559 | 1559 | } | |
| @@ -295,7 +295,7 @@ func (m *MergeRequests) Create(ctx context.Context, p domain.Principal, name dom | |||||
| 295 | recordActivity(ctx, m.meta, repo.Name, now) | 295 | recordActivity(ctx, m.meta, repo.Name, now) | ||
| 296 | m.bus.Publish(ctx, domain.MergeRequestOpened{MergeRequest: mr, At: now, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt}) | 296 | m.bus.Publish(ctx, domain.MergeRequestOpened{MergeRequest: mr, At: now, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt}) | ||
| 297 | if m.links != nil { | 297 | if m.links != nil { | ||
| 298 | - | m.links.OnMergeRequestChanged(ctx, repo, mr.Number) | 298 | + | m.links.OnMergeRequestChanged(ctx, p, repo, mr.Number) |
| 299 | } | 299 | } | ||
| 300 | if m.mergeab != nil { | 300 | if m.mergeab != nil { | ||
| 301 | m.mergeab.OnOpened(ctx, mr) | 301 | m.mergeab.OnOpened(ctx, mr) | ||
| @@ -440,7 +440,7 @@ func (m *MergeRequests) MarkReady(ctx context.Context, p domain.Principal, name | |||||
| 440 | recordActivity(ctx, m.meta, repo.Name, mr.UpdatedAt) | 440 | recordActivity(ctx, m.meta, repo.Name, mr.UpdatedAt) | ||
| 441 | m.bus.Publish(ctx, domain.MergeRequestReady{MergeRequest: mr, At: mr.UpdatedAt, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt}) | 441 | m.bus.Publish(ctx, domain.MergeRequestReady{MergeRequest: mr, At: mr.UpdatedAt, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt}) | ||
| 442 | if m.links != nil { | 442 | if m.links != nil { | ||
| 443 | - | m.links.OnMergeRequestChanged(ctx, repo, number) | 443 | + | m.links.OnMergeRequestChanged(ctx, p, repo, number) |
| 444 | } | 444 | } | ||
| 445 | return m.withHead(ctx, mr) | 445 | return m.withHead(ctx, mr) | ||
| 446 | } | 446 | } | ||
| @@ -797,7 +797,7 @@ func (m *MergeRequests) AddComment(ctx context.Context, p domain.Principal, name | |||||
| 797 | Repo: name, Number: number, Comment: c, At: c.CreatedAt, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt, | 797 | Repo: name, Number: number, Comment: c, At: c.CreatedAt, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt, | ||
| 798 | }) | 798 | }) | ||
| 799 | if m.links != nil { | 799 | if m.links != nil { | ||
| 800 | - | m.links.OnMergeRequestChanged(ctx, repo, number) | 800 | + | m.links.OnMergeRequestChanged(ctx, p, repo, number) |
| 801 | } | 801 | } | ||
| 802 | return c, nil | 802 | return c, nil | ||
| 803 | } | 803 | } | ||
| @@ -1276,7 +1276,7 @@ func (m *MergeRequests) afterMerge(ctx context.Context, p domain.Principal, repo | |||||
| 1276 | m.mergeab.OnBranchesMoved(ctx, name, updates) // the other requests of the moved target | 1276 | m.mergeab.OnBranchesMoved(ctx, name, updates) // the other requests of the moved target | ||
| 1277 | } | 1277 | } | ||
| 1278 | if m.links != nil { | 1278 | if m.links != nil { | ||
| 1279 | - | m.links.OnBranchesMoved(ctx, name, updates) // open requests whose source moved | 1279 | + | m.links.OnBranchesMoved(ctx, p, name, updates) // open requests whose source moved |
| 1280 | } | 1280 | } | ||
| 1281 | } | 1281 | } | ||
| 1282 | 1282 | ||||
| @@ -1553,7 +1553,7 @@ func (m *MergeRequests) changeComment(ctx context.Context, p domain.Principal, n | |||||
| 1553 | m.bus.Publish(ctx, domain.CommentEdited{Repo: name, Number: number, Comment: c, At: c.EditedAt, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt}) | 1553 | m.bus.Publish(ctx, domain.CommentEdited{Repo: name, Number: number, Comment: c, At: c.EditedAt, Visibility: repo.Visibility, CreatedAt: repo.CreatedAt}) | ||
| 1554 | } | 1554 | } | ||
| 1555 | if m.links != nil { | 1555 | if m.links != nil { | ||
| 1556 | - | m.links.OnMergeRequestChanged(ctx, repo, number) | 1556 | + | m.links.OnMergeRequestChanged(ctx, p, repo, number) |
| 1557 | } | 1557 | } | ||
| 1558 | return c, nil | 1558 | return c, nil | ||
| 1559 | } | 1559 | } | ||
M internal/usecase/pushes.go +1 -1
| @@ -274,7 +274,7 @@ func (u *Pushes) Record(ctx context.Context, p domain.Principal, name domain.Rep | |||
| 274 | 274 | u.mergeab.OnBranchesMoved(ctx, name, updates) // queues only | |
| 275 | 275 | } | |
| 276 | 276 | if u.links != nil { | |
| 277 | - | u.links.OnBranchesMoved(ctx, name, updates) | |
| 277 | + | u.links.OnBranchesMoved(ctx, p, name, updates) | |
| 278 | 278 | } | |
| 279 | 279 | if u.releases != nil { | |
| 280 | 280 | u.releases.OnRefsMoved(ctx, name, updates) | |
| @@ -274,7 +274,7 @@ func (u *Pushes) Record(ctx context.Context, p domain.Principal, name domain.Rep | |||||
| 274 | u.mergeab.OnBranchesMoved(ctx, name, updates) // queues only | 274 | u.mergeab.OnBranchesMoved(ctx, name, updates) // queues only | ||
| 275 | } | 275 | } | ||
| 276 | if u.links != nil { | 276 | if u.links != nil { | ||
| 277 | - | u.links.OnBranchesMoved(ctx, name, updates) | 277 | + | u.links.OnBranchesMoved(ctx, p, name, updates) |
| 278 | } | 278 | } | ||
| 279 | if u.releases != nil { | 279 | if u.releases != nil { | ||
| 280 | u.releases.OnRefsMoved(ctx, name, updates) | 280 | u.releases.OnRefsMoved(ctx, name, updates) | ||
M proto/ermite/storage/issue.proto +29 -0
| @@ -17,6 +17,7 @@ enum IssueState { | |||
| 17 | 17 | ISSUE_STATE_CLOSED = 2; | |
| 18 | 18 | // Awaiting moderation. Not stored yet. | |
| 19 | 19 | ISSUE_STATE_PENDING = 3; | |
| 20 | + | ISSUE_STATE_RESOLVED = 4; | |
| 20 | 21 | } | |
| 21 | 22 | ||
| 22 | 23 | // Issue is stored under the key "is/<repo>/<number:010d>". The numbers come | |
| @@ -36,6 +37,7 @@ message Issue { | |||
| 36 | 37 | // True for an issue approved from the public form: its text comes from | |
| 37 | 38 | // an anonymous visitor and is rendered as untrusted. | |
| 38 | 39 | bool external = 9; | |
| 40 | + | // Set only while RESOLVED by a merge. | |
| 39 | 41 | int64 closed_by_merge_request = 10; | |
| 40 | 42 | } | |
| 41 | 43 | ||
| @@ -81,3 +83,30 @@ message IssueBan { | |||
| 81 | 83 | message IssueKey { | |
| 82 | 84 | bytes key = 1; | |
| 83 | 85 | } | |
| 86 | + | ||
| 87 | + | // IssueEventKind describes an issue history entry. | |
| 88 | + | enum IssueEventKind { | |
| 89 | + | ISSUE_EVENT_KIND_UNSPECIFIED = 0; | |
| 90 | + | // Closes says whether this is a closing reference. | |
| 91 | + | ISSUE_EVENT_KIND_REFERENCED = 1; | |
| 92 | + | ISSUE_EVENT_KIND_CLOSED = 2; | |
| 93 | + | ISSUE_EVENT_KIND_REOPENED = 3; | |
| 94 | + | // Merge_request is set when a merge resolved the issue. | |
| 95 | + | ISSUE_EVENT_KIND_RESOLVED = 4; | |
| 96 | + | } | |
| 97 | + | ||
| 98 | + | // IssueEvent is stored under "isev/<repo>/<issue:010d>/<seq:010d>" and, | |
| 99 | + | // when merge_request != 0, copied verbatim under | |
| 100 | + | // "mrev/<repo>/<mr:010d>/<issue:010d>/<seq:010d>". Append-only: never | |
| 101 | + | // updated or deleted except with the repository. | |
| 102 | + | message IssueEvent { | |
| 103 | + | int64 seq = 1; | |
| 104 | + | IssueEventKind kind = 2; | |
| 105 | + | // Zero if none. | |
| 106 | + | int64 merge_request = 3; | |
| 107 | + | // REFERENCED only. | |
| 108 | + | bool closes = 4; | |
| 109 | + | // Label, like Issue.author. | |
| 110 | + | string actor = 5; | |
| 111 | + | google.protobuf.Timestamp created_at = 6; | |
| 112 | + | } | |
| @@ -17,6 +17,7 @@ enum IssueState { | |||||
| 17 | ISSUE_STATE_CLOSED = 2; | 17 | ISSUE_STATE_CLOSED = 2; | ||
| 18 | // Awaiting moderation. Not stored yet. | 18 | // Awaiting moderation. Not stored yet. | ||
| 19 | ISSUE_STATE_PENDING = 3; | 19 | ISSUE_STATE_PENDING = 3; | ||
| 20 | + | ISSUE_STATE_RESOLVED = 4; | |||
| 20 | } | 21 | } | ||
| 21 | 22 | ||||
| 22 | // Issue is stored under the key "is/<repo>/<number:010d>". The numbers come | 23 | // Issue is stored under the key "is/<repo>/<number:010d>". The numbers come | ||
| @@ -36,6 +37,7 @@ message Issue { | |||||
| 36 | // True for an issue approved from the public form: its text comes from | 37 | // True for an issue approved from the public form: its text comes from | ||
| 37 | // an anonymous visitor and is rendered as untrusted. | 38 | // an anonymous visitor and is rendered as untrusted. | ||
| 38 | bool external = 9; | 39 | bool external = 9; | ||
| 40 | + | // Set only while RESOLVED by a merge. | |||
| 39 | int64 closed_by_merge_request = 10; | 41 | int64 closed_by_merge_request = 10; | ||
| 40 | } | 42 | } | ||
| 41 | 43 | ||||
| @@ -81,3 +83,30 @@ message IssueBan { | |||||
| 81 | message IssueKey { | 83 | message IssueKey { | ||
| 82 | bytes key = 1; | 84 | bytes key = 1; | ||
| 83 | } | 85 | } | ||
| 86 | + | ||||
| 87 | + | // IssueEventKind describes an issue history entry. | |||
| 88 | + | enum IssueEventKind { | |||
| 89 | + | ISSUE_EVENT_KIND_UNSPECIFIED = 0; | |||
| 90 | + | // Closes says whether this is a closing reference. | |||
| 91 | + | ISSUE_EVENT_KIND_REFERENCED = 1; | |||
| 92 | + | ISSUE_EVENT_KIND_CLOSED = 2; | |||
| 93 | + | ISSUE_EVENT_KIND_REOPENED = 3; | |||
| 94 | + | // Merge_request is set when a merge resolved the issue. | |||
| 95 | + | ISSUE_EVENT_KIND_RESOLVED = 4; | |||
| 96 | + | } | |||
| 97 | + | ||||
| 98 | + | // IssueEvent is stored under "isev/<repo>/<issue:010d>/<seq:010d>" and, | |||
| 99 | + | // when merge_request != 0, copied verbatim under | |||
| 100 | + | // "mrev/<repo>/<mr:010d>/<issue:010d>/<seq:010d>". Append-only: never | |||
| 101 | + | // updated or deleted except with the repository. | |||
| 102 | + | message IssueEvent { | |||
| 103 | + | int64 seq = 1; | |||
| 104 | + | IssueEventKind kind = 2; | |||
| 105 | + | // Zero if none. | |||
| 106 | + | int64 merge_request = 3; | |||
| 107 | + | // REFERENCED only. | |||
| 108 | + | bool closes = 4; | |||
| 109 | + | // Label, like Issue.author. | |||
| 110 | + | string actor = 5; | |||
| 111 | + | google.protobuf.Timestamp created_at = 6; | |||
| 112 | + | } | |||