feat(rpc): serve merge request auto-merge and its events
- commit
- d1f49e581aa34a06632406e669c56bc9dd3f03fe
- author
- brice · · 2026-10-20 16:17 UTC
- parent
- ba47d48
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
8 files · +160 -19
M internal/adapters/client/client_test.go +3 -3
| @@ -139,16 +139,16 @@ func TestDescribeBoundsLongMessages(t *testing.T) { | |||
| 139 | 139 | func TestMissingFeatures(t *testing.T) { | |
| 140 | 140 | assert.Empty(t, client.MissingFeatures(client.UsedFeatures())) | |
| 141 | 141 | assert.Equal(t, | |
| 142 | - | []string{client.FeatureEventsResume, client.FeatureMergeRequests, client.FeatureMergeDeleteBranch, client.FeatureIssues, client.FeatureMergeability, client.FeatureSigningKey, client.FeatureTokenBranchPrefixes, client.FeatureCIRuns, client.FeatureCIRunners, client.FeatureCISecrets, client.FeatureCIDispatch, client.FeatureCIWorkflows, client.FeatureCIArtifacts, client.FeatureCommitSignatures}, | |
| 142 | + | []string{client.FeatureEventsResume, client.FeatureMergeRequests, client.FeatureMergeDeleteBranch, client.FeatureIssues, client.FeatureMergeability, client.FeatureSigningKey, client.FeatureTokenBranchPrefixes, client.FeatureCIRuns, client.FeatureCIRunners, client.FeatureCISecrets, client.FeatureCIDispatch, client.FeatureCIWorkflows, client.FeatureCIArtifacts, client.FeatureCommitSignatures, client.FeatureAutoMerge}, | |
| 143 | 143 | client.MissingFeatures([]string{client.FeatureRepoUpdate, client.FeatureSession, client.FeaturePasskeys, "other"})) | |
| 144 | 144 | assert.Equal(t, []string{client.FeatureSigningKey}, client.MissingFeatures([]string{ | |
| 145 | 145 | client.FeatureRepoUpdate, client.FeatureEventsResume, client.FeatureMergeRequests, client.FeatureMergeDeleteBranch, client.FeatureIssues, client.FeatureMergeability, client.FeatureSession, | |
| 146 | 146 | client.FeatureTokenBranchPrefixes, client.FeatureCIRuns, client.FeatureCIRunners, client.FeatureCISecrets, client.FeatureCIDispatch, client.FeatureCIWorkflows, | |
| 147 | - | client.FeatureCIArtifacts, client.FeaturePasskeys, client.FeatureCommitSignatures, | |
| 147 | + | client.FeatureCIArtifacts, client.FeaturePasskeys, client.FeatureCommitSignatures, client.FeatureAutoMerge, | |
| 148 | 148 | })) | |
| 149 | 149 | assert.Equal(t, []string{client.FeaturePasskeys}, client.MissingFeatures([]string{ | |
| 150 | 150 | client.FeatureRepoUpdate, client.FeatureEventsResume, client.FeatureMergeRequests, client.FeatureMergeDeleteBranch, client.FeatureIssues, client.FeatureMergeability, client.FeatureSigningKey, client.FeatureSession, | |
| 151 | - | client.FeatureTokenBranchPrefixes, client.FeatureCIRuns, client.FeatureCIRunners, client.FeatureCISecrets, client.FeatureCIDispatch, client.FeatureCIWorkflows, client.FeatureCIArtifacts, client.FeatureCommitSignatures, | |
| 151 | + | client.FeatureTokenBranchPrefixes, client.FeatureCIRuns, client.FeatureCIRunners, client.FeatureCISecrets, client.FeatureCIDispatch, client.FeatureCIWorkflows, client.FeatureCIArtifacts, client.FeatureCommitSignatures, client.FeatureAutoMerge, | |
| 152 | 152 | })) | |
| 153 | 153 | assert.Equal(t, client.UsedFeatures(), client.MissingFeatures(nil)) | |
| 154 | 154 | } | |
| @@ -139,16 +139,16 @@ func TestDescribeBoundsLongMessages(t *testing.T) { | |||||
| 139 | func TestMissingFeatures(t *testing.T) { | 139 | func TestMissingFeatures(t *testing.T) { | ||
| 140 | assert.Empty(t, client.MissingFeatures(client.UsedFeatures())) | 140 | assert.Empty(t, client.MissingFeatures(client.UsedFeatures())) | ||
| 141 | assert.Equal(t, | 141 | assert.Equal(t, | ||
| 142 | - | []string{client.FeatureEventsResume, client.FeatureMergeRequests, client.FeatureMergeDeleteBranch, client.FeatureIssues, client.FeatureMergeability, client.FeatureSigningKey, client.FeatureTokenBranchPrefixes, client.FeatureCIRuns, client.FeatureCIRunners, client.FeatureCISecrets, client.FeatureCIDispatch, client.FeatureCIWorkflows, client.FeatureCIArtifacts, client.FeatureCommitSignatures}, | 142 | + | []string{client.FeatureEventsResume, client.FeatureMergeRequests, client.FeatureMergeDeleteBranch, client.FeatureIssues, client.FeatureMergeability, client.FeatureSigningKey, client.FeatureTokenBranchPrefixes, client.FeatureCIRuns, client.FeatureCIRunners, client.FeatureCISecrets, client.FeatureCIDispatch, client.FeatureCIWorkflows, client.FeatureCIArtifacts, client.FeatureCommitSignatures, client.FeatureAutoMerge}, |
| 143 | client.MissingFeatures([]string{client.FeatureRepoUpdate, client.FeatureSession, client.FeaturePasskeys, "other"})) | 143 | client.MissingFeatures([]string{client.FeatureRepoUpdate, client.FeatureSession, client.FeaturePasskeys, "other"})) | ||
| 144 | assert.Equal(t, []string{client.FeatureSigningKey}, client.MissingFeatures([]string{ | 144 | assert.Equal(t, []string{client.FeatureSigningKey}, client.MissingFeatures([]string{ | ||
| 145 | client.FeatureRepoUpdate, client.FeatureEventsResume, client.FeatureMergeRequests, client.FeatureMergeDeleteBranch, client.FeatureIssues, client.FeatureMergeability, client.FeatureSession, | 145 | client.FeatureRepoUpdate, client.FeatureEventsResume, client.FeatureMergeRequests, client.FeatureMergeDeleteBranch, client.FeatureIssues, client.FeatureMergeability, client.FeatureSession, | ||
| 146 | client.FeatureTokenBranchPrefixes, client.FeatureCIRuns, client.FeatureCIRunners, client.FeatureCISecrets, client.FeatureCIDispatch, client.FeatureCIWorkflows, | 146 | client.FeatureTokenBranchPrefixes, client.FeatureCIRuns, client.FeatureCIRunners, client.FeatureCISecrets, client.FeatureCIDispatch, client.FeatureCIWorkflows, | ||
| 147 | - | client.FeatureCIArtifacts, client.FeaturePasskeys, client.FeatureCommitSignatures, | 147 | + | client.FeatureCIArtifacts, client.FeaturePasskeys, client.FeatureCommitSignatures, client.FeatureAutoMerge, |
| 148 | })) | 148 | })) | ||
| 149 | assert.Equal(t, []string{client.FeaturePasskeys}, client.MissingFeatures([]string{ | 149 | assert.Equal(t, []string{client.FeaturePasskeys}, client.MissingFeatures([]string{ | ||
| 150 | client.FeatureRepoUpdate, client.FeatureEventsResume, client.FeatureMergeRequests, client.FeatureMergeDeleteBranch, client.FeatureIssues, client.FeatureMergeability, client.FeatureSigningKey, client.FeatureSession, | 150 | client.FeatureRepoUpdate, client.FeatureEventsResume, client.FeatureMergeRequests, client.FeatureMergeDeleteBranch, client.FeatureIssues, client.FeatureMergeability, client.FeatureSigningKey, client.FeatureSession, | ||
| 151 | - | client.FeatureTokenBranchPrefixes, client.FeatureCIRuns, client.FeatureCIRunners, client.FeatureCISecrets, client.FeatureCIDispatch, client.FeatureCIWorkflows, client.FeatureCIArtifacts, client.FeatureCommitSignatures, | 151 | + | client.FeatureTokenBranchPrefixes, client.FeatureCIRuns, client.FeatureCIRunners, client.FeatureCISecrets, client.FeatureCIDispatch, client.FeatureCIWorkflows, client.FeatureCIArtifacts, client.FeatureCommitSignatures, client.FeatureAutoMerge, |
| 152 | })) | 152 | })) | ||
| 153 | assert.Equal(t, client.UsedFeatures(), client.MissingFeatures(nil)) | 153 | assert.Equal(t, client.UsedFeatures(), client.MissingFeatures(nil)) | ||
| 154 | } | 154 | } | ||
M internal/adapters/client/features.go +5 -1
| @@ -57,11 +57,15 @@ const ( | |||
| 57 | 57 | // FeatureMergeability is MergeRequest.mergeability and the | |
| 58 | 58 | // merge_request_mergeability event. | |
| 59 | 59 | FeatureMergeability = "merge_requests.mergeability" | |
| 60 | + | // FeatureAutoMerge is MergeRequestService.EnableAutoMerge and | |
| 61 | + | // CancelAutoMerge, RepoSettings.allow_auto_merge and the auto-merge events | |
| 62 | + | // (`ermite mr merge --auto`). | |
| 63 | + | FeatureAutoMerge = "merge_requests.auto_merge" | |
| 60 | 64 | ) | |
| 61 | 65 | ||
| 62 | 66 | // UsedFeatures lists the features the CLI uses, in the order they are reported. | |
| 63 | 67 | func UsedFeatures() []string { | |
| 64 | - | return []string{FeatureRepoUpdate, FeatureEventsResume, FeatureMergeRequests, FeatureMergeDeleteBranch, FeatureIssues, FeatureMergeability, FeatureSigningKey, FeatureSession, FeatureTokenBranchPrefixes, FeatureCIRuns, FeatureCIRunners, FeatureCISecrets, FeatureCIDispatch, FeatureCIWorkflows, FeatureCIArtifacts, FeaturePasskeys, FeatureCommitSignatures} | |
| 68 | + | return []string{FeatureRepoUpdate, FeatureEventsResume, FeatureMergeRequests, FeatureMergeDeleteBranch, FeatureIssues, FeatureMergeability, FeatureSigningKey, FeatureSession, FeatureTokenBranchPrefixes, FeatureCIRuns, FeatureCIRunners, FeatureCISecrets, FeatureCIDispatch, FeatureCIWorkflows, FeatureCIArtifacts, FeaturePasskeys, FeatureCommitSignatures, FeatureAutoMerge} | |
| 65 | 69 | } | |
| 66 | 70 | ||
| 67 | 71 | // MissingFeatures returns the features the CLI uses that have is lacking. | |
| @@ -57,11 +57,15 @@ const ( | |||||
| 57 | // FeatureMergeability is MergeRequest.mergeability and the | 57 | // FeatureMergeability is MergeRequest.mergeability and the | ||
| 58 | // merge_request_mergeability event. | 58 | // merge_request_mergeability event. | ||
| 59 | FeatureMergeability = "merge_requests.mergeability" | 59 | FeatureMergeability = "merge_requests.mergeability" | ||
| 60 | + | // FeatureAutoMerge is MergeRequestService.EnableAutoMerge and | |||
| 61 | + | // CancelAutoMerge, RepoSettings.allow_auto_merge and the auto-merge events | |||
| 62 | + | // (`ermite mr merge --auto`). | |||
| 63 | + | FeatureAutoMerge = "merge_requests.auto_merge" | |||
| 60 | ) | 64 | ) | ||
| 61 | 65 | ||||
| 62 | // UsedFeatures lists the features the CLI uses, in the order they are reported. | 66 | // UsedFeatures lists the features the CLI uses, in the order they are reported. | ||
| 63 | func UsedFeatures() []string { | 67 | func UsedFeatures() []string { | ||
| 64 | - | return []string{FeatureRepoUpdate, FeatureEventsResume, FeatureMergeRequests, FeatureMergeDeleteBranch, FeatureIssues, FeatureMergeability, FeatureSigningKey, FeatureSession, FeatureTokenBranchPrefixes, FeatureCIRuns, FeatureCIRunners, FeatureCISecrets, FeatureCIDispatch, FeatureCIWorkflows, FeatureCIArtifacts, FeaturePasskeys, FeatureCommitSignatures} | 68 | + | return []string{FeatureRepoUpdate, FeatureEventsResume, FeatureMergeRequests, FeatureMergeDeleteBranch, FeatureIssues, FeatureMergeability, FeatureSigningKey, FeatureSession, FeatureTokenBranchPrefixes, FeatureCIRuns, FeatureCIRunners, FeatureCISecrets, FeatureCIDispatch, FeatureCIWorkflows, FeatureCIArtifacts, FeaturePasskeys, FeatureCommitSignatures, FeatureAutoMerge} |
| 65 | } | 69 | } | ||
| 66 | 70 | ||||
| 67 | // MissingFeatures returns the features the CLI uses that have is lacking. | 71 | // MissingFeatures returns the features the CLI uses that have is lacking. | ||
M internal/adapters/rpc/api.go +1 -1
| @@ -35,7 +35,7 @@ const RunnerMaxRequestBytes = MaxRequestBytes + 64<<10 | |||
| 35 | 35 | // always implements, as GetVersion lists them. FeaturePasskeys comes on top | |
| 36 | 36 | // when Config.Passkeys is set, and FeatureCISecrets when the settings services | |
| 37 | 37 | // are, and FeatureCommitSignatures when Config.TrustedKeys is set. | |
| 38 | - | var Features = []string{"repo.update", "events.resume", "merge_requests", "merge_requests.delete_branch", "issues", "merge_requests.mergeability", "signing_key", "session", "git.list_paths", "token.branch_prefixes", "ci.runs", "ci.runs.filters", "ci.runners", "ci.actions", "ci.annotations", "ci.summaries", "ci.concurrency", "ci.dispatch", "ci.cache", "ci.workflows"} | |
| 38 | + | var Features = []string{"repo.update", "events.resume", "merge_requests", "merge_requests.delete_branch", "issues", "merge_requests.mergeability", "signing_key", "session", "git.list_paths", "token.branch_prefixes", "ci.runs", "ci.runs.filters", "ci.runners", "ci.actions", "ci.annotations", "ci.summaries", "ci.concurrency", "ci.dispatch", "ci.cache", "ci.workflows", "merge_requests.auto_merge"} | |
| 39 | 39 | ||
| 40 | 40 | // FeaturePasskeys is listed by GetVersion when passkeys are available. | |
| 41 | 41 | // FeatureCIArtifacts (artifact.go) when the artifact services are served. | |
| @@ -35,7 +35,7 @@ const RunnerMaxRequestBytes = MaxRequestBytes + 64<<10 | |||||
| 35 | // always implements, as GetVersion lists them. FeaturePasskeys comes on top | 35 | // always implements, as GetVersion lists them. FeaturePasskeys comes on top | ||
| 36 | // when Config.Passkeys is set, and FeatureCISecrets when the settings services | 36 | // when Config.Passkeys is set, and FeatureCISecrets when the settings services | ||
| 37 | // are, and FeatureCommitSignatures when Config.TrustedKeys is set. | 37 | // are, and FeatureCommitSignatures when Config.TrustedKeys is set. | ||
| 38 | - | var Features = []string{"repo.update", "events.resume", "merge_requests", "merge_requests.delete_branch", "issues", "merge_requests.mergeability", "signing_key", "session", "git.list_paths", "token.branch_prefixes", "ci.runs", "ci.runs.filters", "ci.runners", "ci.actions", "ci.annotations", "ci.summaries", "ci.concurrency", "ci.dispatch", "ci.cache", "ci.workflows"} | 38 | + | var Features = []string{"repo.update", "events.resume", "merge_requests", "merge_requests.delete_branch", "issues", "merge_requests.mergeability", "signing_key", "session", "git.list_paths", "token.branch_prefixes", "ci.runs", "ci.runs.filters", "ci.runners", "ci.actions", "ci.annotations", "ci.summaries", "ci.concurrency", "ci.dispatch", "ci.cache", "ci.workflows", "merge_requests.auto_merge"} |
| 39 | 39 | ||||
| 40 | // FeaturePasskeys is listed by GetVersion when passkeys are available. | 40 | // FeaturePasskeys is listed by GetVersion when passkeys are available. | ||
| 41 | // FeatureCIArtifacts (artifact.go) when the artifact services are served. | 41 | // FeatureCIArtifacts (artifact.go) when the artifact services are served. | ||
M internal/adapters/rpc/convert.go +8 -2
| @@ -72,7 +72,7 @@ func repoToPB(v ports.RepoView) *pb.Repo { | |||
| 72 | 72 | } | |
| 73 | 73 | ||
| 74 | 74 | func settingsToPB(s domain.RepoSettings) *pb.RepoSettings { | |
| 75 | - | out := &pb.RepoSettings{FailingChecksBlock: s.FailingChecksBlock, Issues: issuesModeToPB(s.Issues)} | |
| 75 | + | out := &pb.RepoSettings{FailingChecksBlock: s.FailingChecksBlock, AllowAutoMerge: s.AllowAutoMerge, Issues: issuesModeToPB(s.Issues)} | |
| 76 | 76 | for _, m := range s.AllowedMergeModes { | |
| 77 | 77 | out.AllowedMergeModes = append(out.AllowedMergeModes, mergeModeToPB(m)) | |
| 78 | 78 | } | |
| @@ -483,7 +483,13 @@ func watchEventToPB(we ports.WatchEvent) *pb.Event { | |||
| 483 | 483 | out.Kind = &pb.Event_MergeRequestOpened{MergeRequestOpened: &pb.MergeRequestOpened{MergeRequest: mergeRequestToPB(ev.MergeRequest)}} | |
| 484 | 484 | case domain.MergeRequestMerged: | |
| 485 | 485 | out.Time = timestamp(ev.At) | |
| 486 | - | out.Kind = &pb.Event_MergeRequestMerged{MergeRequestMerged: &pb.MergeRequestMerged{MergeRequest: mergeRequestToPB(ev.MergeRequest)}} | |
| 486 | + | out.Kind = &pb.Event_MergeRequestMerged{MergeRequestMerged: &pb.MergeRequestMerged{MergeRequest: mergeRequestToPB(ev.MergeRequest), Auto: ev.Auto}} | |
| 487 | + | case domain.MergeRequestAutoMergeEnabled: | |
| 488 | + | out.Time = timestamp(ev.At) | |
| 489 | + | out.Kind = &pb.Event_MergeRequestAutoMergeEnabled{MergeRequestAutoMergeEnabled: &pb.MergeRequestAutoMergeEnabled{MergeRequest: mergeRequestToPB(ev.MergeRequest)}} | |
| 490 | + | case domain.MergeRequestAutoMergeCancelled: | |
| 491 | + | out.Time = timestamp(ev.At) | |
| 492 | + | out.Kind = &pb.Event_MergeRequestAutoMergeCancelled{MergeRequestAutoMergeCancelled: &pb.MergeRequestAutoMergeCancelled{MergeRequest: mergeRequestToPB(ev.MergeRequest), Reason: ev.Reason}} | |
| 487 | 493 | case domain.MergeRequestClosed: | |
| 488 | 494 | out.Time = timestamp(ev.At) | |
| 489 | 495 | out.Kind = &pb.Event_MergeRequestClosed{MergeRequestClosed: &pb.MergeRequestClosed{MergeRequest: mergeRequestToPB(ev.MergeRequest)}} | |
| @@ -72,7 +72,7 @@ func repoToPB(v ports.RepoView) *pb.Repo { | |||||
| 72 | } | 72 | } | ||
| 73 | 73 | ||||
| 74 | func settingsToPB(s domain.RepoSettings) *pb.RepoSettings { | 74 | func settingsToPB(s domain.RepoSettings) *pb.RepoSettings { | ||
| 75 | - | out := &pb.RepoSettings{FailingChecksBlock: s.FailingChecksBlock, Issues: issuesModeToPB(s.Issues)} | 75 | + | out := &pb.RepoSettings{FailingChecksBlock: s.FailingChecksBlock, AllowAutoMerge: s.AllowAutoMerge, Issues: issuesModeToPB(s.Issues)} |
| 76 | for _, m := range s.AllowedMergeModes { | 76 | for _, m := range s.AllowedMergeModes { | ||
| 77 | out.AllowedMergeModes = append(out.AllowedMergeModes, mergeModeToPB(m)) | 77 | out.AllowedMergeModes = append(out.AllowedMergeModes, mergeModeToPB(m)) | ||
| 78 | } | 78 | } | ||
| @@ -483,7 +483,13 @@ func watchEventToPB(we ports.WatchEvent) *pb.Event { | |||||
| 483 | out.Kind = &pb.Event_MergeRequestOpened{MergeRequestOpened: &pb.MergeRequestOpened{MergeRequest: mergeRequestToPB(ev.MergeRequest)}} | 483 | out.Kind = &pb.Event_MergeRequestOpened{MergeRequestOpened: &pb.MergeRequestOpened{MergeRequest: mergeRequestToPB(ev.MergeRequest)}} | ||
| 484 | case domain.MergeRequestMerged: | 484 | case domain.MergeRequestMerged: | ||
| 485 | out.Time = timestamp(ev.At) | 485 | out.Time = timestamp(ev.At) | ||
| 486 | - | out.Kind = &pb.Event_MergeRequestMerged{MergeRequestMerged: &pb.MergeRequestMerged{MergeRequest: mergeRequestToPB(ev.MergeRequest)}} | 486 | + | out.Kind = &pb.Event_MergeRequestMerged{MergeRequestMerged: &pb.MergeRequestMerged{MergeRequest: mergeRequestToPB(ev.MergeRequest), Auto: ev.Auto}} |
| 487 | + | case domain.MergeRequestAutoMergeEnabled: | |||
| 488 | + | out.Time = timestamp(ev.At) | |||
| 489 | + | out.Kind = &pb.Event_MergeRequestAutoMergeEnabled{MergeRequestAutoMergeEnabled: &pb.MergeRequestAutoMergeEnabled{MergeRequest: mergeRequestToPB(ev.MergeRequest)}} | |||
| 490 | + | case domain.MergeRequestAutoMergeCancelled: | |||
| 491 | + | out.Time = timestamp(ev.At) | |||
| 492 | + | out.Kind = &pb.Event_MergeRequestAutoMergeCancelled{MergeRequestAutoMergeCancelled: &pb.MergeRequestAutoMergeCancelled{MergeRequest: mergeRequestToPB(ev.MergeRequest), Reason: ev.Reason}} | |||
| 487 | case domain.MergeRequestClosed: | 493 | case domain.MergeRequestClosed: | ||
| 488 | out.Time = timestamp(ev.At) | 494 | out.Time = timestamp(ev.At) | ||
| 489 | out.Kind = &pb.Event_MergeRequestClosed{MergeRequestClosed: &pb.MergeRequestClosed{MergeRequest: mergeRequestToPB(ev.MergeRequest)}} | 495 | out.Kind = &pb.Event_MergeRequestClosed{MergeRequestClosed: &pb.MergeRequestClosed{MergeRequest: mergeRequestToPB(ev.MergeRequest)}} | ||
M internal/adapters/rpc/mergerequest.go +31 -7
| @@ -4,7 +4,6 @@ package rpc | |||
| 4 | 4 | ||
| 5 | 5 | import ( | |
| 6 | 6 | "context" | |
| 7 | - | "errors" | |
| 8 | 7 | "fmt" | |
| 9 | 8 | "strconv" | |
| 10 | 9 | ||
| @@ -20,14 +19,32 @@ type mergeRequestService struct { | |||
| 20 | 19 | mrs ports.MergeRequestService | |
| 21 | 20 | } | |
| 22 | 21 | ||
| 23 | - | // EnableAutoMerge is not wired yet. | |
| 24 | - | func (s *mergeRequestService) EnableAutoMerge(context.Context, *connect.Request[pb.EnableAutoMergeRequest]) (*connect.Response[pb.EnableAutoMergeResponse], error) { | |
| 25 | - | return nil, connect.NewError(connect.CodeUnimplemented, errors.New("auto-merge is not implemented yet")) | |
| 22 | + | func (s *mergeRequestService) EnableAutoMerge(ctx context.Context, req *connect.Request[pb.EnableAutoMergeRequest]) (*connect.Response[pb.EnableAutoMergeResponse], error) { | |
| 23 | + | name, err := domain.ParseRepoName(req.Msg.GetRepo()) | |
| 24 | + | if err != nil { | |
| 25 | + | return nil, connectError(ctx, err) | |
| 26 | + | } | |
| 27 | + | mode, err := mergeModeFromPB(req.Msg.GetMode()) | |
| 28 | + | if err != nil { | |
| 29 | + | return nil, connectError(ctx, err) | |
| 30 | + | } | |
| 31 | + | mr, err := s.mrs.EnableAutoMerge(ctx, caller(ctx), name, req.Msg.GetNumber(), ports.EnableAutoMergeParams{Mode: mode}) | |
| 32 | + | if err != nil { | |
| 33 | + | return nil, connectError(ctx, err) | |
| 34 | + | } | |
| 35 | + | return connect.NewResponse(&pb.EnableAutoMergeResponse{MergeRequest: mergeRequestToPB(mr)}), nil | |
| 26 | 36 | } | |
| 27 | 37 | ||
| 28 | - | // CancelAutoMerge is not wired yet. | |
| 29 | - | func (s *mergeRequestService) CancelAutoMerge(context.Context, *connect.Request[pb.CancelAutoMergeRequest]) (*connect.Response[pb.CancelAutoMergeResponse], error) { | |
| 30 | - | return nil, connect.NewError(connect.CodeUnimplemented, errors.New("auto-merge is not implemented yet")) | |
| 38 | + | func (s *mergeRequestService) CancelAutoMerge(ctx context.Context, req *connect.Request[pb.CancelAutoMergeRequest]) (*connect.Response[pb.CancelAutoMergeResponse], error) { | |
| 39 | + | name, err := domain.ParseRepoName(req.Msg.GetRepo()) | |
| 40 | + | if err != nil { | |
| 41 | + | return nil, connectError(ctx, err) | |
| 42 | + | } | |
| 43 | + | mr, err := s.mrs.CancelAutoMerge(ctx, caller(ctx), name, req.Msg.GetNumber()) | |
| 44 | + | if err != nil { | |
| 45 | + | return nil, connectError(ctx, err) | |
| 46 | + | } | |
| 47 | + | return connect.NewResponse(&pb.CancelAutoMergeResponse{MergeRequest: mergeRequestToPB(mr)}), nil | |
| 31 | 48 | } | |
| 32 | 49 | ||
| 33 | 50 | func (s *mergeRequestService) EditComment(ctx context.Context, req *connect.Request[pb.EditCommentRequest]) (*connect.Response[pb.EditCommentResponse], error) { | |
| @@ -144,6 +161,13 @@ func mergeRequestToPB(mr domain.MergeRequest) *pb.MergeRequest { | |||
| 144 | 161 | HeadId: mr.HeadID.String(), | |
| 145 | 162 | MergedCommitId: mr.MergedCommitID.String(), | |
| 146 | 163 | MergeMode: mergeModeToPB(mr.MergeMode), | |
| 164 | + | AutoMerged: mr.AutoMerged, | |
| 165 | + | } | |
| 166 | + | if am := mr.AutoMerge; am != nil { | |
| 167 | + | out.AutoMerge = &pb.AutoMerge{ | |
| 168 | + | Mode: mergeModeToPB(am.Mode), HeadId: am.HeadID.String(), | |
| 169 | + | EnabledBy: am.EnablerLabel, EnabledAt: timestamp(am.EnabledAt), | |
| 170 | + | } | |
| 147 | 171 | } | |
| 148 | 172 | if mr.IsOpen() { | |
| 149 | 173 | out.Mergeability = &pb.Mergeability{State: pb.MergeabilityState_MERGEABILITY_STATE_CHECKING} | |
| @@ -4,7 +4,6 @@ package rpc | |||||
| 4 | 4 | ||||
| 5 | import ( | 5 | import ( | ||
| 6 | "context" | 6 | "context" | ||
| 7 | - | "errors" | |||
| 8 | "fmt" | 7 | "fmt" | ||
| 9 | "strconv" | 8 | "strconv" | ||
| 10 | 9 | ||||
| @@ -20,14 +19,32 @@ type mergeRequestService struct { | |||||
| 20 | mrs ports.MergeRequestService | 19 | mrs ports.MergeRequestService | ||
| 21 | } | 20 | } | ||
| 22 | 21 | ||||
| 23 | - | // EnableAutoMerge is not wired yet. | 22 | + | func (s *mergeRequestService) EnableAutoMerge(ctx context.Context, req *connect.Request[pb.EnableAutoMergeRequest]) (*connect.Response[pb.EnableAutoMergeResponse], error) { |
| 24 | - | func (s *mergeRequestService) EnableAutoMerge(context.Context, *connect.Request[pb.EnableAutoMergeRequest]) (*connect.Response[pb.EnableAutoMergeResponse], error) { | 23 | + | name, err := domain.ParseRepoName(req.Msg.GetRepo()) |
| 25 | - | return nil, connect.NewError(connect.CodeUnimplemented, errors.New("auto-merge is not implemented yet")) | 24 | + | if err != nil { |
| 25 | + | return nil, connectError(ctx, err) | |||
| 26 | + | } | |||
| 27 | + | mode, err := mergeModeFromPB(req.Msg.GetMode()) | |||
| 28 | + | if err != nil { | |||
| 29 | + | return nil, connectError(ctx, err) | |||
| 30 | + | } | |||
| 31 | + | mr, err := s.mrs.EnableAutoMerge(ctx, caller(ctx), name, req.Msg.GetNumber(), ports.EnableAutoMergeParams{Mode: mode}) | |||
| 32 | + | if err != nil { | |||
| 33 | + | return nil, connectError(ctx, err) | |||
| 34 | + | } | |||
| 35 | + | return connect.NewResponse(&pb.EnableAutoMergeResponse{MergeRequest: mergeRequestToPB(mr)}), nil | |||
| 26 | } | 36 | } | ||
| 27 | 37 | ||||
| 28 | - | // CancelAutoMerge is not wired yet. | 38 | + | func (s *mergeRequestService) CancelAutoMerge(ctx context.Context, req *connect.Request[pb.CancelAutoMergeRequest]) (*connect.Response[pb.CancelAutoMergeResponse], error) { |
| 29 | - | func (s *mergeRequestService) CancelAutoMerge(context.Context, *connect.Request[pb.CancelAutoMergeRequest]) (*connect.Response[pb.CancelAutoMergeResponse], error) { | 39 | + | name, err := domain.ParseRepoName(req.Msg.GetRepo()) |
| 30 | - | return nil, connect.NewError(connect.CodeUnimplemented, errors.New("auto-merge is not implemented yet")) | 40 | + | if err != nil { |
| 41 | + | return nil, connectError(ctx, err) | |||
| 42 | + | } | |||
| 43 | + | mr, err := s.mrs.CancelAutoMerge(ctx, caller(ctx), name, req.Msg.GetNumber()) | |||
| 44 | + | if err != nil { | |||
| 45 | + | return nil, connectError(ctx, err) | |||
| 46 | + | } | |||
| 47 | + | return connect.NewResponse(&pb.CancelAutoMergeResponse{MergeRequest: mergeRequestToPB(mr)}), nil | |||
| 31 | } | 48 | } | ||
| 32 | 49 | ||||
| 33 | func (s *mergeRequestService) EditComment(ctx context.Context, req *connect.Request[pb.EditCommentRequest]) (*connect.Response[pb.EditCommentResponse], error) { | 50 | func (s *mergeRequestService) EditComment(ctx context.Context, req *connect.Request[pb.EditCommentRequest]) (*connect.Response[pb.EditCommentResponse], error) { | ||
| @@ -144,6 +161,13 @@ func mergeRequestToPB(mr domain.MergeRequest) *pb.MergeRequest { | |||||
| 144 | HeadId: mr.HeadID.String(), | 161 | HeadId: mr.HeadID.String(), | ||
| 145 | MergedCommitId: mr.MergedCommitID.String(), | 162 | MergedCommitId: mr.MergedCommitID.String(), | ||
| 146 | MergeMode: mergeModeToPB(mr.MergeMode), | 163 | MergeMode: mergeModeToPB(mr.MergeMode), | ||
| 164 | + | AutoMerged: mr.AutoMerged, | |||
| 165 | + | } | |||
| 166 | + | if am := mr.AutoMerge; am != nil { | |||
| 167 | + | out.AutoMerge = &pb.AutoMerge{ | |||
| 168 | + | Mode: mergeModeToPB(am.Mode), HeadId: am.HeadID.String(), | |||
| 169 | + | EnabledBy: am.EnablerLabel, EnabledAt: timestamp(am.EnabledAt), | |||
| 170 | + | } | |||
| 147 | } | 171 | } | ||
| 148 | if mr.IsOpen() { | 172 | if mr.IsOpen() { | ||
| 149 | out.Mergeability = &pb.Mergeability{State: pb.MergeabilityState_MERGEABILITY_STATE_CHECKING} | 173 | out.Mergeability = &pb.Mergeability{State: pb.MergeabilityState_MERGEABILITY_STATE_CHECKING} | ||
M internal/adapters/rpc/mr_api_test.go +78 -0
| @@ -3,6 +3,7 @@ | |||
| 3 | 3 | package rpc | |
| 4 | 4 | ||
| 5 | 5 | import ( | |
| 6 | + | "strings" | |
| 6 | 7 | "testing" | |
| 7 | 8 | "time" | |
| 8 | 9 | ||
| @@ -122,3 +123,80 @@ func TestNoteVerdictsDetailAndScope(t *testing.T) { | |||
| 122 | 123 | _, err = (&mergeRequestService{m}).AddComment(ownerCtx(), connect.NewRequest(&pb.AddCommentRequest{Repo: "demo", Number: 1, Verdict: pb.Verdict_VERDICT_APPROVE})) | |
| 123 | 124 | requireCode(t, connect.CodePermissionDenied, err) | |
| 124 | 125 | } | |
| 126 | + | ||
| 127 | + | func TestAutoMergeRPCCodesAndFields(t *testing.T) { | |
| 128 | + | now := time.Date(2026, 10, 20, 9, 0, 0, 0, time.UTC) | |
| 129 | + | head, err := domain.ParseObjectID(strings.Repeat("a", 40)) | |
| 130 | + | require.NoError(t, err) | |
| 131 | + | armed := domain.MergeRequest{ | |
| 132 | + | Repo: mustRepoName(t, "demo"), Number: 1, State: domain.MRStateOpen, Source: "feat", Target: "main", HeadID: head, | |
| 133 | + | AutoMerge: &domain.AutoMerge{Mode: domain.MergeModeSquash, HeadID: head, EnablerLabel: "owner", EnabledAt: now}, | |
| 134 | + | } | |
| 135 | + | idle := armed | |
| 136 | + | idle.AutoMerge = nil | |
| 137 | + | ||
| 138 | + | pbArmed := mergeRequestToPB(armed) | |
| 139 | + | require.NotNil(t, pbArmed.GetAutoMerge()) | |
| 140 | + | assert.Equal(t, pb.MergeMode_MERGE_MODE_SQUASH, pbArmed.GetAutoMerge().GetMode()) | |
| 141 | + | assert.Equal(t, head.String(), pbArmed.GetAutoMerge().GetHeadId()) | |
| 142 | + | assert.Equal(t, "owner", pbArmed.GetAutoMerge().GetEnabledBy()) | |
| 143 | + | assert.Equal(t, now, pbArmed.GetAutoMerge().GetEnabledAt().AsTime()) | |
| 144 | + | assert.False(t, pbArmed.GetAutoMerged()) | |
| 145 | + | assert.Nil(t, mergeRequestToPB(idle).GetAutoMerge()) | |
| 146 | + | done := idle | |
| 147 | + | done.State, done.AutoMerged = domain.MRStateMerged, true | |
| 148 | + | assert.True(t, mergeRequestToPB(done).GetAutoMerged()) | |
| 149 | + | ||
| 150 | + | // Success: the mode and the request reach the service, the fields come back. | |
| 151 | + | m := mocks.NewMergeRequestService(t) | |
| 152 | + | m.EXPECT().EnableAutoMerge(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1), ports.EnableAutoMergeParams{Mode: domain.MergeModeSquash}).Return(armed, nil).Once() | |
| 153 | + | res, err := (&mergeRequestService{m}).EnableAutoMerge(ownerCtx(), connect.NewRequest(&pb.EnableAutoMergeRequest{Repo: "demo", Number: 1, Mode: pb.MergeMode_MERGE_MODE_SQUASH})) | |
| 154 | + | require.NoError(t, err) | |
| 155 | + | assert.Equal(t, pbArmed, res.Msg.GetMergeRequest()) | |
| 156 | + | m.EXPECT().CancelAutoMerge(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1)).Return(idle, nil).Once() | |
| 157 | + | cres, err := (&mergeRequestService{m}).CancelAutoMerge(ownerCtx(), connect.NewRequest(&pb.CancelAutoMergeRequest{Repo: "demo", Number: 1})) | |
| 158 | + | require.NoError(t, err) | |
| 159 | + | assert.Nil(t, cres.Msg.GetMergeRequest().GetAutoMerge()) | |
| 160 | + | ||
| 161 | + | // Bad input never reaches the service. | |
| 162 | + | _, err = (&mergeRequestService{m}).EnableAutoMerge(ownerCtx(), connect.NewRequest(&pb.EnableAutoMergeRequest{Repo: "demo", Number: 1, Mode: pb.MergeMode(99)})) | |
| 163 | + | requireCode(t, connect.CodeInvalidArgument, err) | |
| 164 | + | _, err = (&mergeRequestService{m}).CancelAutoMerge(ownerCtx(), connect.NewRequest(&pb.CancelAutoMergeRequest{Repo: "", Number: 1})) | |
| 165 | + | requireCode(t, connect.CodeInvalidArgument, err) | |
| 166 | + | ||
| 167 | + | // Domain errors map like the other merge request calls. | |
| 168 | + | for _, tc := range []struct { | |
| 169 | + | err error | |
| 170 | + | code connect.Code | |
| 171 | + | }{ | |
| 172 | + | {domain.ErrPreconditionFailed, connect.CodeFailedPrecondition}, | |
| 173 | + | {domain.ErrForbidden, connect.CodePermissionDenied}, | |
| 174 | + | {domain.ErrNotFound, connect.CodeNotFound}, | |
| 175 | + | } { | |
| 176 | + | m := mocks.NewMergeRequestService(t) | |
| 177 | + | m.EXPECT().EnableAutoMerge(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1), mock.Anything).Return(domain.MergeRequest{}, tc.err).Once() | |
| 178 | + | _, err := (&mergeRequestService{m}).EnableAutoMerge(ownerCtx(), connect.NewRequest(&pb.EnableAutoMergeRequest{Repo: "demo", Number: 1})) | |
| 179 | + | requireCode(t, tc.code, err) | |
| 180 | + | m.EXPECT().CancelAutoMerge(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1)).Return(domain.MergeRequest{}, tc.err).Once() | |
| 181 | + | _, err = (&mergeRequestService{m}).CancelAutoMerge(ownerCtx(), connect.NewRequest(&pb.CancelAutoMergeRequest{Repo: "demo", Number: 1})) | |
| 182 | + | requireCode(t, tc.code, err) | |
| 183 | + | } | |
| 184 | + | } | |
| 185 | + | ||
| 186 | + | func TestAutoMergeWatchEventsAndSettingsToPB(t *testing.T) { | |
| 187 | + | armed := domain.MergeRequest{Repo: mustRepoName(t, "demo"), Number: 1, State: domain.MRStateOpen, | |
| 188 | + | AutoMerge: &domain.AutoMerge{Mode: domain.MergeModeMerge, EnablerLabel: "owner"}} | |
| 189 | + | got := watchEventToPB(ports.WatchEvent{ID: "1", Event: domain.MergeRequestAutoMergeEnabled{MergeRequest: armed}}) | |
| 190 | + | require.NotNil(t, got.GetMergeRequestAutoMergeEnabled()) | |
| 191 | + | assert.Equal(t, pb.MergeMode_MERGE_MODE_MERGE_COMMIT, got.GetMergeRequestAutoMergeEnabled().GetMergeRequest().GetAutoMerge().GetMode()) | |
| 192 | + | got = watchEventToPB(ports.WatchEvent{ID: "2", Event: domain.MergeRequestAutoMergeCancelled{MergeRequest: armed, Reason: "the branch moved"}}) | |
| 193 | + | require.NotNil(t, got.GetMergeRequestAutoMergeCancelled()) | |
| 194 | + | assert.Equal(t, "the branch moved", got.GetMergeRequestAutoMergeCancelled().GetReason()) | |
| 195 | + | got = watchEventToPB(ports.WatchEvent{ID: "3", Event: domain.MergeRequestMerged{MergeRequest: armed, Auto: true}}) | |
| 196 | + | assert.True(t, got.GetMergeRequestMerged().GetAuto()) | |
| 197 | + | got = watchEventToPB(ports.WatchEvent{ID: "4", Event: domain.MergeRequestMerged{MergeRequest: armed}}) | |
| 198 | + | assert.False(t, got.GetMergeRequestMerged().GetAuto()) | |
| 199 | + | ||
| 200 | + | assert.True(t, settingsToPB(domain.RepoSettings{AllowAutoMerge: true}).GetAllowAutoMerge()) | |
| 201 | + | assert.False(t, settingsToPB(domain.RepoSettings{}).GetAllowAutoMerge()) | |
| 202 | + | } | |
| @@ -3,6 +3,7 @@ | |||||
| 3 | package rpc | 3 | package rpc | ||
| 4 | 4 | ||||
| 5 | import ( | 5 | import ( | ||
| 6 | + | "strings" | |||
| 6 | "testing" | 7 | "testing" | ||
| 7 | "time" | 8 | "time" | ||
| 8 | 9 | ||||
| @@ -122,3 +123,80 @@ func TestNoteVerdictsDetailAndScope(t *testing.T) { | |||||
| 122 | _, err = (&mergeRequestService{m}).AddComment(ownerCtx(), connect.NewRequest(&pb.AddCommentRequest{Repo: "demo", Number: 1, Verdict: pb.Verdict_VERDICT_APPROVE})) | 123 | _, err = (&mergeRequestService{m}).AddComment(ownerCtx(), connect.NewRequest(&pb.AddCommentRequest{Repo: "demo", Number: 1, Verdict: pb.Verdict_VERDICT_APPROVE})) | ||
| 123 | requireCode(t, connect.CodePermissionDenied, err) | 124 | requireCode(t, connect.CodePermissionDenied, err) | ||
| 124 | } | 125 | } | ||
| 126 | + | ||||
| 127 | + | func TestAutoMergeRPCCodesAndFields(t *testing.T) { | |||
| 128 | + | now := time.Date(2026, 10, 20, 9, 0, 0, 0, time.UTC) | |||
| 129 | + | head, err := domain.ParseObjectID(strings.Repeat("a", 40)) | |||
| 130 | + | require.NoError(t, err) | |||
| 131 | + | armed := domain.MergeRequest{ | |||
| 132 | + | Repo: mustRepoName(t, "demo"), Number: 1, State: domain.MRStateOpen, Source: "feat", Target: "main", HeadID: head, | |||
| 133 | + | AutoMerge: &domain.AutoMerge{Mode: domain.MergeModeSquash, HeadID: head, EnablerLabel: "owner", EnabledAt: now}, | |||
| 134 | + | } | |||
| 135 | + | idle := armed | |||
| 136 | + | idle.AutoMerge = nil | |||
| 137 | + | ||||
| 138 | + | pbArmed := mergeRequestToPB(armed) | |||
| 139 | + | require.NotNil(t, pbArmed.GetAutoMerge()) | |||
| 140 | + | assert.Equal(t, pb.MergeMode_MERGE_MODE_SQUASH, pbArmed.GetAutoMerge().GetMode()) | |||
| 141 | + | assert.Equal(t, head.String(), pbArmed.GetAutoMerge().GetHeadId()) | |||
| 142 | + | assert.Equal(t, "owner", pbArmed.GetAutoMerge().GetEnabledBy()) | |||
| 143 | + | assert.Equal(t, now, pbArmed.GetAutoMerge().GetEnabledAt().AsTime()) | |||
| 144 | + | assert.False(t, pbArmed.GetAutoMerged()) | |||
| 145 | + | assert.Nil(t, mergeRequestToPB(idle).GetAutoMerge()) | |||
| 146 | + | done := idle | |||
| 147 | + | done.State, done.AutoMerged = domain.MRStateMerged, true | |||
| 148 | + | assert.True(t, mergeRequestToPB(done).GetAutoMerged()) | |||
| 149 | + | ||||
| 150 | + | // Success: the mode and the request reach the service, the fields come back. | |||
| 151 | + | m := mocks.NewMergeRequestService(t) | |||
| 152 | + | m.EXPECT().EnableAutoMerge(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1), ports.EnableAutoMergeParams{Mode: domain.MergeModeSquash}).Return(armed, nil).Once() | |||
| 153 | + | res, err := (&mergeRequestService{m}).EnableAutoMerge(ownerCtx(), connect.NewRequest(&pb.EnableAutoMergeRequest{Repo: "demo", Number: 1, Mode: pb.MergeMode_MERGE_MODE_SQUASH})) | |||
| 154 | + | require.NoError(t, err) | |||
| 155 | + | assert.Equal(t, pbArmed, res.Msg.GetMergeRequest()) | |||
| 156 | + | m.EXPECT().CancelAutoMerge(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1)).Return(idle, nil).Once() | |||
| 157 | + | cres, err := (&mergeRequestService{m}).CancelAutoMerge(ownerCtx(), connect.NewRequest(&pb.CancelAutoMergeRequest{Repo: "demo", Number: 1})) | |||
| 158 | + | require.NoError(t, err) | |||
| 159 | + | assert.Nil(t, cres.Msg.GetMergeRequest().GetAutoMerge()) | |||
| 160 | + | ||||
| 161 | + | // Bad input never reaches the service. | |||
| 162 | + | _, err = (&mergeRequestService{m}).EnableAutoMerge(ownerCtx(), connect.NewRequest(&pb.EnableAutoMergeRequest{Repo: "demo", Number: 1, Mode: pb.MergeMode(99)})) | |||
| 163 | + | requireCode(t, connect.CodeInvalidArgument, err) | |||
| 164 | + | _, err = (&mergeRequestService{m}).CancelAutoMerge(ownerCtx(), connect.NewRequest(&pb.CancelAutoMergeRequest{Repo: "", Number: 1})) | |||
| 165 | + | requireCode(t, connect.CodeInvalidArgument, err) | |||
| 166 | + | ||||
| 167 | + | // Domain errors map like the other merge request calls. | |||
| 168 | + | for _, tc := range []struct { | |||
| 169 | + | err error | |||
| 170 | + | code connect.Code | |||
| 171 | + | }{ | |||
| 172 | + | {domain.ErrPreconditionFailed, connect.CodeFailedPrecondition}, | |||
| 173 | + | {domain.ErrForbidden, connect.CodePermissionDenied}, | |||
| 174 | + | {domain.ErrNotFound, connect.CodeNotFound}, | |||
| 175 | + | } { | |||
| 176 | + | m := mocks.NewMergeRequestService(t) | |||
| 177 | + | m.EXPECT().EnableAutoMerge(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1), mock.Anything).Return(domain.MergeRequest{}, tc.err).Once() | |||
| 178 | + | _, err := (&mergeRequestService{m}).EnableAutoMerge(ownerCtx(), connect.NewRequest(&pb.EnableAutoMergeRequest{Repo: "demo", Number: 1})) | |||
| 179 | + | requireCode(t, tc.code, err) | |||
| 180 | + | m.EXPECT().CancelAutoMerge(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), int64(1)).Return(domain.MergeRequest{}, tc.err).Once() | |||
| 181 | + | _, err = (&mergeRequestService{m}).CancelAutoMerge(ownerCtx(), connect.NewRequest(&pb.CancelAutoMergeRequest{Repo: "demo", Number: 1})) | |||
| 182 | + | requireCode(t, tc.code, err) | |||
| 183 | + | } | |||
| 184 | + | } | |||
| 185 | + | ||||
| 186 | + | func TestAutoMergeWatchEventsAndSettingsToPB(t *testing.T) { | |||
| 187 | + | armed := domain.MergeRequest{Repo: mustRepoName(t, "demo"), Number: 1, State: domain.MRStateOpen, | |||
| 188 | + | AutoMerge: &domain.AutoMerge{Mode: domain.MergeModeMerge, EnablerLabel: "owner"}} | |||
| 189 | + | got := watchEventToPB(ports.WatchEvent{ID: "1", Event: domain.MergeRequestAutoMergeEnabled{MergeRequest: armed}}) | |||
| 190 | + | require.NotNil(t, got.GetMergeRequestAutoMergeEnabled()) | |||
| 191 | + | assert.Equal(t, pb.MergeMode_MERGE_MODE_MERGE_COMMIT, got.GetMergeRequestAutoMergeEnabled().GetMergeRequest().GetAutoMerge().GetMode()) | |||
| 192 | + | got = watchEventToPB(ports.WatchEvent{ID: "2", Event: domain.MergeRequestAutoMergeCancelled{MergeRequest: armed, Reason: "the branch moved"}}) | |||
| 193 | + | require.NotNil(t, got.GetMergeRequestAutoMergeCancelled()) | |||
| 194 | + | assert.Equal(t, "the branch moved", got.GetMergeRequestAutoMergeCancelled().GetReason()) | |||
| 195 | + | got = watchEventToPB(ports.WatchEvent{ID: "3", Event: domain.MergeRequestMerged{MergeRequest: armed, Auto: true}}) | |||
| 196 | + | assert.True(t, got.GetMergeRequestMerged().GetAuto()) | |||
| 197 | + | got = watchEventToPB(ports.WatchEvent{ID: "4", Event: domain.MergeRequestMerged{MergeRequest: armed}}) | |||
| 198 | + | assert.False(t, got.GetMergeRequestMerged().GetAuto()) | |||
| 199 | + | ||||
| 200 | + | assert.True(t, settingsToPB(domain.RepoSettings{AllowAutoMerge: true}).GetAllowAutoMerge()) | |||
| 201 | + | assert.False(t, settingsToPB(domain.RepoSettings{}).GetAllowAutoMerge()) | |||
| 202 | + | } | |||
M internal/adapters/rpc/repo.go +9 -5
| @@ -80,8 +80,8 @@ func (s *repoService) UpdateRepo(ctx context.Context, req *connect.Request[pb.Up | |||
| 80 | 80 | ||
| 81 | 81 | // updateParams maps the field mask of an update. The mask must name at least | |
| 82 | 82 | // one of "description", "topics", "visibility", "default_branch", "settings", | |
| 83 | - | // "settings.allowed_merge_modes" and "settings.failing_checks_block", and | |
| 84 | - | // nothing else; the optional issues field stands outside the mask, and alone | |
| 83 | + | // "settings.allowed_merge_modes", "settings.failing_checks_block" and | |
| 84 | + | // "settings.allow_auto_merge", and nothing else; the optional issues field stands outside the mask, and alone | |
| 85 | 85 | // it is a valid update. | |
| 86 | 86 | func updateParams(msg *pb.UpdateRepoRequest) (ports.UpdateRepoParams, error) { | |
| 87 | 87 | var params ports.UpdateRepoParams | |
| @@ -113,17 +113,21 @@ func updateParams(msg *pb.UpdateRepoRequest) (ports.UpdateRepoParams, error) { | |||
| 113 | 113 | params.Visibility = &vis | |
| 114 | 114 | case "default_branch": | |
| 115 | 115 | params.DefaultBranch = new(msg.GetDefaultBranch()) | |
| 116 | - | case "settings", "settings.allowed_merge_modes", "settings.failing_checks_block": | |
| 117 | - | if path != "settings.failing_checks_block" { | |
| 116 | + | case "settings", "settings.allowed_merge_modes", "settings.failing_checks_block", "settings.allow_auto_merge": | |
| 117 | + | all := path == "settings" | |
| 118 | + | if all || path == "settings.allowed_merge_modes" { | |
| 118 | 119 | modes, err := mergeModesFromPB(msg.GetSettings().GetAllowedMergeModes()) | |
| 119 | 120 | if err != nil { | |
| 120 | 121 | return params, err | |
| 121 | 122 | } | |
| 122 | 123 | params.AllowedMergeModes = &modes | |
| 123 | 124 | } | |
| 124 | - | if path != "settings.allowed_merge_modes" { | |
| 125 | + | if all || path == "settings.failing_checks_block" { | |
| 125 | 126 | params.FailingChecksBlock = new(msg.GetSettings().GetFailingChecksBlock()) | |
| 126 | 127 | } | |
| 128 | + | if all || path == "settings.allow_auto_merge" { | |
| 129 | + | params.AllowAutoMerge = new(msg.GetSettings().GetAllowAutoMerge()) | |
| 130 | + | } | |
| 127 | 131 | default: | |
| 128 | 132 | return params, fmt.Errorf("%w: unknown path %q in update_mask", domain.ErrInvalid, path) | |
| 129 | 133 | } | |
| @@ -80,8 +80,8 @@ func (s *repoService) UpdateRepo(ctx context.Context, req *connect.Request[pb.Up | |||||
| 80 | 80 | ||||
| 81 | // updateParams maps the field mask of an update. The mask must name at least | 81 | // updateParams maps the field mask of an update. The mask must name at least | ||
| 82 | // one of "description", "topics", "visibility", "default_branch", "settings", | 82 | // one of "description", "topics", "visibility", "default_branch", "settings", | ||
| 83 | - | // "settings.allowed_merge_modes" and "settings.failing_checks_block", and | 83 | + | // "settings.allowed_merge_modes", "settings.failing_checks_block" and |
| 84 | - | // nothing else; the optional issues field stands outside the mask, and alone | 84 | + | // "settings.allow_auto_merge", and nothing else; the optional issues field stands outside the mask, and alone |
| 85 | // it is a valid update. | 85 | // it is a valid update. | ||
| 86 | func updateParams(msg *pb.UpdateRepoRequest) (ports.UpdateRepoParams, error) { | 86 | func updateParams(msg *pb.UpdateRepoRequest) (ports.UpdateRepoParams, error) { | ||
| 87 | var params ports.UpdateRepoParams | 87 | var params ports.UpdateRepoParams | ||
| @@ -113,17 +113,21 @@ func updateParams(msg *pb.UpdateRepoRequest) (ports.UpdateRepoParams, error) { | |||||
| 113 | params.Visibility = &vis | 113 | params.Visibility = &vis | ||
| 114 | case "default_branch": | 114 | case "default_branch": | ||
| 115 | params.DefaultBranch = new(msg.GetDefaultBranch()) | 115 | params.DefaultBranch = new(msg.GetDefaultBranch()) | ||
| 116 | - | case "settings", "settings.allowed_merge_modes", "settings.failing_checks_block": | 116 | + | case "settings", "settings.allowed_merge_modes", "settings.failing_checks_block", "settings.allow_auto_merge": |
| 117 | - | if path != "settings.failing_checks_block" { | 117 | + | all := path == "settings" |
| 118 | + | if all || path == "settings.allowed_merge_modes" { | |||
| 118 | modes, err := mergeModesFromPB(msg.GetSettings().GetAllowedMergeModes()) | 119 | modes, err := mergeModesFromPB(msg.GetSettings().GetAllowedMergeModes()) | ||
| 119 | if err != nil { | 120 | if err != nil { | ||
| 120 | return params, err | 121 | return params, err | ||
| 121 | } | 122 | } | ||
| 122 | params.AllowedMergeModes = &modes | 123 | params.AllowedMergeModes = &modes | ||
| 123 | } | 124 | } | ||
| 124 | - | if path != "settings.allowed_merge_modes" { | 125 | + | if all || path == "settings.failing_checks_block" { |
| 125 | params.FailingChecksBlock = new(msg.GetSettings().GetFailingChecksBlock()) | 126 | params.FailingChecksBlock = new(msg.GetSettings().GetFailingChecksBlock()) | ||
| 126 | } | 127 | } | ||
| 128 | + | if all || path == "settings.allow_auto_merge" { | |||
| 129 | + | params.AllowAutoMerge = new(msg.GetSettings().GetAllowAutoMerge()) | |||
| 130 | + | } | |||
| 127 | default: | 131 | default: | ||
| 128 | return params, fmt.Errorf("%w: unknown path %q in update_mask", domain.ErrInvalid, path) | 132 | return params, fmt.Errorf("%w: unknown path %q in update_mask", domain.ErrInvalid, path) | ||
| 129 | } | 133 | } | ||
M internal/adapters/rpc/repo_test.go +25 -0
| @@ -280,3 +280,28 @@ func TestRepoToPBTopics(t *testing.T) { | |||
| 280 | 280 | assert.Equal(t, []string{"cli", "go"}, got.GetTopics()) | |
| 281 | 281 | assert.Empty(t, repoToPB(ports.RepoView{Repo: domain.Repo{Name: mustRepoName(t, "demo")}}).GetTopics()) | |
| 282 | 282 | } | |
| 283 | + | ||
| 284 | + | func TestUpdateParamsAllowAutoMerge(t *testing.T) { | |
| 285 | + | t.Parallel() | |
| 286 | + | mask := func(paths ...string) *fieldmaskpb.FieldMask { return &fieldmaskpb.FieldMask{Paths: paths} } | |
| 287 | + | settings := &pb.RepoSettings{AllowAutoMerge: true, FailingChecksBlock: true, AllowedMergeModes: []pb.MergeMode{pb.MergeMode_MERGE_MODE_SQUASH}} | |
| 288 | + | ||
| 289 | + | params, err := updateParams(&pb.UpdateRepoRequest{UpdateMask: mask("settings.allow_auto_merge"), Settings: settings}) | |
| 290 | + | require.NoError(t, err) | |
| 291 | + | require.NotNil(t, params.AllowAutoMerge) | |
| 292 | + | assert.True(t, *params.AllowAutoMerge) | |
| 293 | + | assert.Nil(t, params.FailingChecksBlock, "only the masked setting is set") | |
| 294 | + | assert.Nil(t, params.AllowedMergeModes) | |
| 295 | + | ||
| 296 | + | params, err = updateParams(&pb.UpdateRepoRequest{UpdateMask: mask("settings"), Settings: settings}) | |
| 297 | + | require.NoError(t, err) | |
| 298 | + | require.NotNil(t, params.AllowAutoMerge, "settings covers allow_auto_merge") | |
| 299 | + | assert.True(t, *params.AllowAutoMerge) | |
| 300 | + | require.NotNil(t, params.FailingChecksBlock) | |
| 301 | + | require.NotNil(t, params.AllowedMergeModes) | |
| 302 | + | ||
| 303 | + | params, err = updateParams(&pb.UpdateRepoRequest{UpdateMask: mask("settings.failing_checks_block"), Settings: settings}) | |
| 304 | + | require.NoError(t, err) | |
| 305 | + | assert.Nil(t, params.AllowAutoMerge) | |
| 306 | + | require.NotNil(t, params.FailingChecksBlock) | |
| 307 | + | } | |
| @@ -280,3 +280,28 @@ func TestRepoToPBTopics(t *testing.T) { | |||||
| 280 | assert.Equal(t, []string{"cli", "go"}, got.GetTopics()) | 280 | assert.Equal(t, []string{"cli", "go"}, got.GetTopics()) | ||
| 281 | assert.Empty(t, repoToPB(ports.RepoView{Repo: domain.Repo{Name: mustRepoName(t, "demo")}}).GetTopics()) | 281 | assert.Empty(t, repoToPB(ports.RepoView{Repo: domain.Repo{Name: mustRepoName(t, "demo")}}).GetTopics()) | ||
| 282 | } | 282 | } | ||
| 283 | + | ||||
| 284 | + | func TestUpdateParamsAllowAutoMerge(t *testing.T) { | |||
| 285 | + | t.Parallel() | |||
| 286 | + | mask := func(paths ...string) *fieldmaskpb.FieldMask { return &fieldmaskpb.FieldMask{Paths: paths} } | |||
| 287 | + | settings := &pb.RepoSettings{AllowAutoMerge: true, FailingChecksBlock: true, AllowedMergeModes: []pb.MergeMode{pb.MergeMode_MERGE_MODE_SQUASH}} | |||
| 288 | + | ||||
| 289 | + | params, err := updateParams(&pb.UpdateRepoRequest{UpdateMask: mask("settings.allow_auto_merge"), Settings: settings}) | |||
| 290 | + | require.NoError(t, err) | |||
| 291 | + | require.NotNil(t, params.AllowAutoMerge) | |||
| 292 | + | assert.True(t, *params.AllowAutoMerge) | |||
| 293 | + | assert.Nil(t, params.FailingChecksBlock, "only the masked setting is set") | |||
| 294 | + | assert.Nil(t, params.AllowedMergeModes) | |||
| 295 | + | ||||
| 296 | + | params, err = updateParams(&pb.UpdateRepoRequest{UpdateMask: mask("settings"), Settings: settings}) | |||
| 297 | + | require.NoError(t, err) | |||
| 298 | + | require.NotNil(t, params.AllowAutoMerge, "settings covers allow_auto_merge") | |||
| 299 | + | assert.True(t, *params.AllowAutoMerge) | |||
| 300 | + | require.NotNil(t, params.FailingChecksBlock) | |||
| 301 | + | require.NotNil(t, params.AllowedMergeModes) | |||
| 302 | + | ||||
| 303 | + | params, err = updateParams(&pb.UpdateRepoRequest{UpdateMask: mask("settings.failing_checks_block"), Settings: settings}) | |||
| 304 | + | require.NoError(t, err) | |||
| 305 | + | assert.Nil(t, params.AllowAutoMerge) | |||
| 306 | + | require.NotNil(t, params.FailingChecksBlock) | |||
| 307 | + | } | |||