v0.34.0 7c1886f

automerge_e2e_test.go RawHistoryFolder

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package app
4
5import (
6	"testing"
7	"time"
8
9	"connectrpc.com/connect"
10	"github.com/stretchr/testify/assert"
11	"github.com/stretchr/testify/require"
12	"google.golang.org/protobuf/types/known/fieldmaskpb"
13
14	pb "bnema.dev/ermite-api/gen/ermite"
15	"bnema.dev/ermite/internal/domain"
16	"bnema.dev/ermite/internal/ports"
17)
18
19// This test exercises the composition root through use cases and domain
20// events; TestAutoMergeEndToEndAPI does the same through the RPC API.
21func TestAutoMergeEndToEnd(t *testing.T) {
22	c := newCIHarness(t, nil)
23	runner := c.registerRunner("alpha", "ubuntu-latest")
24	c.push("main", map[string]string{".github/workflows/ci.yml": `name: ci
25on:
26  push:
27    branches: [feat]
28  pull_request:
29jobs:
30  build:
31    runs-on: ubuntu-latest
32    steps:
33      - run: echo hello
34`}, "")
35	head := c.push("feat", map[string]string{"change.txt": "feature\n"}, "")
36	number := c.openMR(c.root, "feat")
37	repo := c.name("proj")
38	allow := true
39	_, err := c.forge.Repos.Update(c.ctx, domain.Root(), repo, ports.UpdateRepoParams{AllowAutoMerge: &allow})
40	require.NoError(t, err)
41	headID, err := domain.ParseObjectID(head)
42	require.NoError(t, err)
43	events := c.forge.Bus.Subscribe(c.ctx)
44	enabled, err := c.forge.MergeRequests.EnableAutoMerge(c.ctx, domain.Owner(), repo, number, ports.EnableAutoMergeParams{HeadID: headID})
45	require.NoError(t, err)
46	require.NotNil(t, enabled.AutoMerge)
47	assert.Equal(t, head, enabled.AutoMerge.HeadID.String())
48	enabledDeadline := time.After(5 * time.Second)
49waitEnabled:
50	for {
51		select {
52		case ev := <-events:
53			if enabled, ok := ev.(domain.MergeRequestAutoMergeEnabled); ok && enabled.MergeRequest.Repo == repo && enabled.MergeRequest.Number == number {
54				break waitEnabled
55			}
56		case <-enabledDeadline:
57			t.Fatal("no enabled event")
58		}
59	}
60	c.finish(runner)
61	// A successful push check alone must not merge while the pull run is queued.
62	got, err := c.forge.MergeRequests.Get(c.ctx, domain.Root(), repo, number, ports.GetMergeRequestOptions{})
63	require.NoError(t, err)
64	assert.True(t, got.MergeRequest.IsOpen())
65	c.finish(runner)
66	deadline := time.After(10 * time.Second)
67	for {
68		select {
69		case ev := <-events:
70			if merged, ok := ev.(domain.MergeRequestMerged); ok && merged.MergeRequest.Number == number {
71				assert.True(t, merged.Auto)
72				assert.True(t, merged.MergeRequest.AutoMerged)
73				assert.Nil(t, merged.MergeRequest.AutoMerge)
74				return
75			}
76		case <-deadline:
77			t.Fatal("no automatic merged event")
78		}
79	}
80}
81
82// TestAutoMergeEndToEndAPI enables auto-merge through the API client, lets CI
83// succeed, and reads the Watch stream: enabled, then merged with auto=true.
84func TestAutoMergeEndToEndAPI(t *testing.T) {
85	c := newCIHarness(t, nil)
86	runner := c.registerRunner("alpha", "ubuntu-latest")
87	c.push("main", map[string]string{".github/workflows/ci.yml": `name: ci
88on:
89  push:
90    branches: [feat]
91  pull_request:
92jobs:
93  build:
94    runs-on: ubuntu-latest
95    steps:
96      - run: echo hello
97`}, "")
98	head := c.push("feat", map[string]string{"change.txt": "feature\n"}, "")
99	number := c.openMR(c.root, "feat")
100
101	// Off by default: the server refuses.
102	_, err := c.root.mr.EnableAutoMerge(c.ctx, connect.NewRequest(&pb.EnableAutoMergeRequest{Repo: c.repo, Number: number, HeadId: head}))
103	require.Error(t, err)
104	assert.Equal(t, connect.CodeFailedPrecondition, connect.CodeOf(err))
105
106	ver, err := c.root.server.GetVersion(c.ctx, connect.NewRequest(&pb.GetVersionRequest{}))
107	require.NoError(t, err)
108	assert.Contains(t, ver.Msg.GetFeatures(), "merge_requests.auto_merge")
109
110	up, err := c.root.repo.UpdateRepo(c.ctx, connect.NewRequest(&pb.UpdateRepoRequest{
111		Repo:       c.repo,
112		Settings:   &pb.RepoSettings{AllowAutoMerge: true},
113		UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"settings.allow_auto_merge"}},
114	}))
115	require.NoError(t, err)
116	assert.True(t, up.Msg.GetRepo().GetSettings().GetAllowAutoMerge())
117
118	w := c.watch(c.root, "", "home")
119	res, err := c.root.mr.EnableAutoMerge(c.ctx, connect.NewRequest(&pb.EnableAutoMergeRequest{
120		Repo: c.repo, Number: number, Mode: pb.MergeMode_MERGE_MODE_SQUASH, HeadId: head,
121	}))
122	require.NoError(t, err)
123	am := res.Msg.GetMergeRequest().GetAutoMerge()
124	require.NotNil(t, am)
125	assert.Equal(t, pb.MergeMode_MERGE_MODE_SQUASH, am.GetMode())
126	assert.Equal(t, res.Msg.GetMergeRequest().GetHeadId(), am.GetHeadId())
127	assert.NotEmpty(t, am.GetEnabledBy())
128	assert.NotNil(t, am.GetEnabledAt())
129
130	got, err := c.root.mr.GetMergeRequest(c.ctx, connect.NewRequest(&pb.GetMergeRequestRequest{Repo: c.repo, Number: number}))
131	require.NoError(t, err)
132	assert.NotNil(t, got.Msg.GetMergeRequest().GetAutoMerge(), "Get reports the pending auto-merge")
133
134	c.finish(runner)
135	c.finish(runner)
136
137	var sawEnabled bool
138	deadline := time.After(15 * time.Second)
139	for {
140		select {
141		case ev, ok := <-w.events:
142			require.True(t, ok, "the stream ended: %v", w.err)
143			if e := ev.GetMergeRequestAutoMergeEnabled(); e != nil {
144				assert.EqualValues(t, number, e.GetMergeRequest().GetNumber())
145				assert.NotNil(t, e.GetMergeRequest().GetAutoMerge())
146				sawEnabled = true
147			}
148			if m := ev.GetMergeRequestMerged(); m != nil && m.GetMergeRequest().GetNumber() == number {
149				assert.True(t, sawEnabled, "enabled comes before merged")
150				assert.True(t, m.GetAuto())
151				assert.True(t, m.GetMergeRequest().GetAutoMerged())
152				assert.Nil(t, m.GetMergeRequest().GetAutoMerge())
153				final, err := c.root.mr.GetMergeRequest(c.ctx, connect.NewRequest(&pb.GetMergeRequestRequest{Repo: c.repo, Number: number}))
154				require.NoError(t, err)
155				assert.Equal(t, pb.MergeRequestState_MERGE_REQUEST_STATE_MERGED, final.Msg.GetMergeRequest().GetState())
156				assert.True(t, final.Msg.GetMergeRequest().GetAutoMerged())
157				return
158			}
159		case <-deadline:
160			t.Fatal("no automatic merged event")
161		}
162	}
163}

Keyboard shortcuts

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

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

Branches 2

Tags 44

↑↓ moveenter openesc close