1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package app
4
5import (
6 "errors"
7 "testing"
8
9 "connectrpc.com/connect"
10 "github.com/stretchr/testify/assert"
11 "github.com/stretchr/testify/require"
12 "google.golang.org/protobuf/proto"
13
14 "bnema.dev/ermite-api/gen/ermite"
15)
16
17var (
18 repoAlpha = &ermite.Repo{
19 Name: "alpha", Description: "The first one", Topics: []string{"cli", "go"}, Visibility: ermite.Visibility_VISIBILITY_PUBLIC,
20 DefaultBranch: "main", CreatedAt: ts("2026-01-02T03:04:05Z"), PushedAt: ts("2026-03-04T05:06:07Z"),
21 Settings: &ermite.RepoSettings{
22 AllowedMergeModes: []ermite.MergeMode{ermite.MergeMode_MERGE_MODE_FAST_FORWARD, ermite.MergeMode_MERGE_MODE_SQUASH},
23 FailingChecksBlock: true,
24 },
25 }
26 repoBeta = &ermite.Repo{
27 Name: "beta", Visibility: ermite.Visibility_VISIBILITY_PRIVATE,
28 DefaultBranch: "trunk", CreatedAt: ts("2026-02-02T00:00:00Z"),
29 Settings: &ermite.RepoSettings{AllowedMergeModes: []ermite.MergeMode{ermite.MergeMode_MERGE_MODE_SQUASH}},
30 }
31)
32
33func envFor(url string) map[string]string {
34 return map[string]string{"ERMITE_URL": url, "ERMITE_TOKEN": "ermite_tok"}
35}
36
37func TestRepoList(t *testing.T) {
38 both := []*ermite.Repo{repoAlpha, repoBeta}
39 tests := []struct {
40 name string
41 repos []*ermite.Repo
42 args []string
43 wantStdout string
44 wantJSON bool // compare stdout as JSON rather than byte for byte
45 wantStderr string
46 }{
47 {
48 name: "table", repos: both, args: []string{"repo", "list"},
49 wantStdout: "" +
50 "NAME VISIBILITY BRANCH LAST PUSH DESCRIPTION\n" +
51 "alpha public main 2026-03-04 05:06 The first one\n" +
52 "beta private trunk never \n",
53 },
54 {
55 name: "json", repos: both, args: []string{"repo", "list", "--json"}, wantJSON: true,
56 wantStdout: `[
57 {"name":"alpha","description":"The first one","topics":["cli","go"],"visibility":"public","default_branch":"main","created_at":"2026-01-02T03:04:05Z","pushed_at":"2026-03-04T05:06:07Z","merge_modes":["fast-forward","squash"],"failing_checks_block":true,"allow_auto_merge":false,"issues":"off"},
58 {"name":"beta","description":"","topics":[],"visibility":"private","default_branch":"trunk","created_at":"2026-02-02T00:00:00Z","pushed_at":null,"merge_modes":["squash"],"failing_checks_block":false,"allow_auto_merge":false,"issues":"off"}
59 ]`,
60 },
61 {
62 name: "empty, table", args: []string{"repo", "list"},
63 wantStderr: "ermite: no repositories\n",
64 },
65 {
66 name: "empty, json is [] not null", args: []string{"repo", "list", "--json"},
67 wantStdout: "[]\n",
68 },
69 }
70 for _, tt := range tests {
71 t.Run(tt.name, func(t *testing.T) {
72 f := &fakeAPI{listRepos: func(*ermite.ListReposRequest) (*ermite.ListReposResponse, error) {
73 return &ermite.ListReposResponse{Repos: tt.repos}, nil
74 }}
75 res := runCLI(t, envFor(f.serve(t)), "", tt.args...).ok(t)
76 if tt.wantJSON {
77 assert.JSONEq(t, tt.wantStdout, res.stdout)
78 } else {
79 assert.Equal(t, tt.wantStdout, res.stdout)
80 }
81 assert.Equal(t, tt.wantStderr, res.stderr)
82 assert.Equal(t, "Bearer ermite_tok", f.header[0].Get("Authorization"))
83 assert.Equal(t, "ermite/v0.0.1 (cli)", f.header[0].Get("User-Agent"))
84 })
85 }
86}
87
88func TestRepoListFollowsPages(t *testing.T) {
89 f := &fakeAPI{listRepos: func(r *ermite.ListReposRequest) (*ermite.ListReposResponse, error) {
90 if r.GetPageToken() == "" {
91 return &ermite.ListReposResponse{Repos: []*ermite.Repo{repoAlpha}, NextPageToken: "p2"}, nil
92 }
93 return &ermite.ListReposResponse{Repos: []*ermite.Repo{repoBeta}}, nil
94 }}
95 res := runCLI(t, envFor(f.serve(t)), "", "repo", "list", "--json").ok(t)
96 assert.Contains(t, res.stdout, `"alpha"`)
97 assert.Contains(t, res.stdout, `"beta"`)
98 _, reqs := f.snapshot()
99 require.Len(t, reqs, 2)
100 assert.Equal(t, "p2", reqs[1].(*ermite.ListReposRequest).GetPageToken())
101}
102
103func TestRepoShow(t *testing.T) {
104 f := &fakeAPI{getRepo: func(r *ermite.GetRepoRequest) (*ermite.GetRepoResponse, error) {
105 return &ermite.GetRepoResponse{Repo: repoAlpha}, nil
106 }}
107 url := f.serve(t)
108 res := runCLI(t, envFor(url), "", "repo", "show", "alpha").ok(t)
109 assert.Equal(t, ""+
110 "name alpha\n"+
111 "visibility public\n"+
112 "default branch main\n"+
113 "description The first one\n"+
114 "topics cli, go\n"+
115 "created 2026-01-02 03:04\n"+
116 "last push 2026-03-04 05:06\n"+
117 "merge modes fast-forward, squash\n"+
118 "failing checks block true\n"+
119 "allow auto-merge false\n"+
120 "issues off\n", res.stdout)
121 _, reqs := f.snapshot()
122 assert.Equal(t, "alpha", reqs[0].(*ermite.GetRepoRequest).GetRepo())
123
124 res = runCLI(t, envFor(url), "", "repo", "show", "alpha", "--json").ok(t)
125 assert.JSONEq(t, `{"name":"alpha","description":"The first one","topics":["cli","go"],"visibility":"public","default_branch":"main","created_at":"2026-01-02T03:04:05Z","pushed_at":"2026-03-04T05:06:07Z","merge_modes":["fast-forward","squash"],"failing_checks_block":true,"allow_auto_merge":false,"issues":"off"}`, res.stdout)
126}
127
128func TestRepoShowNeedsOneArg(t *testing.T) {
129 res := runCLI(t, envFor("http://127.0.0.1:1"), "", "repo", "show")
130 require.Error(t, res.err)
131}
132
133func TestRepoCreateMapsFlags(t *testing.T) {
134 tests := []struct {
135 name string
136 args []string
137 want ermite.Visibility
138 desc string
139 topics []string
140 }{
141 {"default is private", []string{"x"}, ermite.Visibility_VISIBILITY_PRIVATE, "", nil},
142 {"--visibility public", []string{"x", "--visibility", "public", "--description", "d d"}, ermite.Visibility_VISIBILITY_PUBLIC, "d d", nil},
143 {"case insensitive", []string{"x", "--visibility", "Public"}, ermite.Visibility_VISIBILITY_PUBLIC, "", nil},
144 {"--topics", []string{"x", "--topics", "go,cli"}, ermite.Visibility_VISIBILITY_PRIVATE, "", []string{"go", "cli"}},
145 }
146 for _, tt := range tests {
147 t.Run(tt.name, func(t *testing.T) {
148 f := &fakeAPI{createRepo: func(r *ermite.CreateRepoRequest) (*ermite.CreateRepoResponse, error) {
149 return &ermite.CreateRepoResponse{Repo: repoAlpha}, nil
150 }}
151 runCLI(t, envFor(f.serve(t)), "", append([]string{"repo", "create"}, tt.args...)...).ok(t)
152 _, reqs := f.snapshot()
153 require.Len(t, reqs, 1)
154 got := reqs[0].(*ermite.CreateRepoRequest)
155 assert.Equal(t, "x", got.GetName())
156 assert.Equal(t, tt.want, got.GetVisibility())
157 assert.Equal(t, tt.desc, got.GetDescription())
158 assert.Equal(t, tt.topics, got.GetTopics())
159 })
160 }
161}
162
163func TestRepoCreateRejectsBadFlagsWithoutCalling(t *testing.T) {
164 f := &fakeAPI{}
165 url := f.serve(t)
166 for _, args := range [][]string{
167 {"repo", "create", "x", "--visibility", "secret"},
168 } {
169 res := runCLI(t, envFor(url), "", args...)
170 require.Error(t, res.err, "%v", args)
171 }
172 calls, _ := f.snapshot()
173 assert.Empty(t, calls)
174}
175
176func TestRepoCreateJSON(t *testing.T) {
177 f := &fakeAPI{createRepo: func(r *ermite.CreateRepoRequest) (*ermite.CreateRepoResponse, error) {
178 return &ermite.CreateRepoResponse{Repo: repoBeta}, nil
179 }}
180 res := runCLI(t, envFor(f.serve(t)), "", "repo", "create", "beta", "--json").ok(t)
181 assert.JSONEq(t, `{"name":"beta","description":"","topics":[],"visibility":"private","default_branch":"trunk","created_at":"2026-02-02T00:00:00Z","pushed_at":null,"merge_modes":["squash"],"failing_checks_block":false,"allow_auto_merge":false,"issues":"off"}`, res.stdout)
182}
183
184func TestRepoEditMask(t *testing.T) {
185 tests := []struct {
186 name string
187 args []string
188 paths []string
189 check func(*testing.T, *ermite.UpdateRepoRequest)
190 }{
191 {"description only", []string{"--description", "new"}, []string{"description"}, func(t *testing.T, r *ermite.UpdateRepoRequest) {
192 assert.Equal(t, "new", r.GetDescription())
193 }},
194 {"clear description", []string{"--description", ""}, []string{"description"}, func(t *testing.T, r *ermite.UpdateRepoRequest) {
195 assert.Empty(t, r.GetDescription())
196 }},
197 {"topics", []string{"--topics", "Go,cli"}, []string{"topics"}, func(t *testing.T, r *ermite.UpdateRepoRequest) {
198 assert.Equal(t, []string{"Go", "cli"}, r.GetTopics())
199 }},
200 {"clear topics", []string{"--topics", ""}, []string{"topics"}, func(t *testing.T, r *ermite.UpdateRepoRequest) {
201 assert.Empty(t, r.GetTopics())
202 }},
203 {"visibility", []string{"--visibility", "public"}, []string{"visibility"}, func(t *testing.T, r *ermite.UpdateRepoRequest) {
204 assert.Equal(t, ermite.Visibility_VISIBILITY_PUBLIC, r.GetVisibility())
205 }},
206 {"default branch", []string{"--default-branch", "refs/heads/dev"}, []string{"default_branch"}, func(t *testing.T, r *ermite.UpdateRepoRequest) {
207 assert.Equal(t, "dev", r.GetDefaultBranch())
208 }},
209 {"merge modes", []string{"--merge-modes", "squash,ff"}, []string{"settings.allowed_merge_modes"}, func(t *testing.T, r *ermite.UpdateRepoRequest) {
210 assert.Equal(t, []ermite.MergeMode{ermite.MergeMode_MERGE_MODE_SQUASH, ermite.MergeMode_MERGE_MODE_FAST_FORWARD}, r.GetSettings().GetAllowedMergeModes())
211 }},
212 {"failing checks do not block", []string{"--failing-checks-block=false"}, []string{"settings.failing_checks_block"}, func(t *testing.T, r *ermite.UpdateRepoRequest) {
213 assert.False(t, r.GetSettings().GetFailingChecksBlock())
214 assert.Empty(t, r.GetSettings().GetAllowedMergeModes())
215 }},
216 {"failing checks block", []string{"--failing-checks-block"}, []string{"settings.failing_checks_block"}, func(t *testing.T, r *ermite.UpdateRepoRequest) {
217 assert.True(t, r.GetSettings().GetFailingChecksBlock())
218 }},
219 {"allow auto-merge", []string{"--allow-auto-merge"}, []string{"settings.allow_auto_merge"}, func(t *testing.T, r *ermite.UpdateRepoRequest) {
220 assert.True(t, r.GetSettings().GetAllowAutoMerge())
221 assert.False(t, r.GetSettings().GetFailingChecksBlock())
222 }},
223 {"forbid auto-merge", []string{"--allow-auto-merge=false"}, []string{"settings.allow_auto_merge"}, func(t *testing.T, r *ermite.UpdateRepoRequest) {
224 assert.False(t, r.GetSettings().GetAllowAutoMerge())
225 }},
226 {"all", []string{"--default-branch", "dev", "--visibility", "private", "--description", "d"},
227 []string{"description", "visibility", "default_branch"}, func(t *testing.T, r *ermite.UpdateRepoRequest) {}},
228 }
229 for _, tt := range tests {
230 t.Run(tt.name, func(t *testing.T) {
231 f := &fakeAPI{updateRepo: func(r *ermite.UpdateRepoRequest) (*ermite.UpdateRepoResponse, error) {
232 return &ermite.UpdateRepoResponse{Repo: repoAlpha}, nil
233 }}
234 runCLI(t, envFor(f.serve(t)), "", append([]string{"repo", "edit", "alpha"}, tt.args...)...).ok(t)
235 _, reqs := f.snapshot()
236 require.Len(t, reqs, 1)
237 got := reqs[0].(*ermite.UpdateRepoRequest)
238 assert.Equal(t, "alpha", got.GetRepo())
239 assert.ElementsMatch(t, tt.paths, got.GetUpdateMask().GetPaths())
240 tt.check(t, got)
241 })
242 }
243}
244
245func TestRepoEditRejects(t *testing.T) {
246 f := &fakeAPI{}
247 url := f.serve(t)
248 for _, tt := range []struct {
249 args []string
250 want string
251 }{
252 {[]string{"repo", "edit", "alpha"}, "nothing to change"},
253 {[]string{"repo", "edit", "alpha", "--visibility", "x"}, "invalid visibility"},
254 {[]string{"repo", "edit", "alpha", "--default-branch", " "}, "must not be empty"},
255 {[]string{"repo", "edit", "alpha", "--merge-modes", "rebase"}, "invalid merge mode"},
256 {[]string{"repo", "edit", "alpha", "--merge-modes", ""}, "at least one mode"},
257 } {
258 res := runCLI(t, envFor(url), "", tt.args...)
259 require.Error(t, res.err)
260 assert.Contains(t, res.err.Error(), tt.want)
261 }
262 calls, _ := f.snapshot()
263 assert.Empty(t, calls)
264}
265
266func TestRepoDeleteConfirmation(t *testing.T) {
267 newFake := func() *fakeAPI {
268 return &fakeAPI{deleteRepo: func(*ermite.DeleteRepoRequest) (*ermite.DeleteRepoResponse, error) {
269 return &ermite.DeleteRepoResponse{}, nil
270 }}
271 }
272 t.Run("typed name deletes", func(t *testing.T) {
273 f := newFake()
274 res := runCLI(t, envFor(f.serve(t)), "alpha\n", "repo", "delete", "alpha").ok(t)
275 assert.Equal(t, "deleted alpha\n", res.stdout)
276 assert.Contains(t, res.stderr, "Type its name to confirm")
277 calls, reqs := f.snapshot()
278 assert.Equal(t, []string{"DeleteRepo"}, calls)
279 assert.Equal(t, "alpha", reqs[0].(*ermite.DeleteRepoRequest).GetRepo())
280 })
281 t.Run("last line without newline is accepted", func(t *testing.T) {
282 f := newFake()
283 runCLI(t, envFor(f.serve(t)), "alpha", "repo", "delete", "alpha").ok(t)
284 calls, _ := f.snapshot()
285 assert.Equal(t, []string{"DeleteRepo"}, calls)
286 })
287 t.Run("wrong name aborts", func(t *testing.T) {
288 f := newFake()
289 res := runCLI(t, envFor(f.serve(t)), "beta\n", "repo", "delete", "alpha")
290 require.Error(t, res.err)
291 assert.Contains(t, res.err.Error(), "did not match")
292 calls, _ := f.snapshot()
293 assert.Empty(t, calls)
294 })
295 t.Run("empty stdin aborts", func(t *testing.T) {
296 f := newFake()
297 res := runCLI(t, envFor(f.serve(t)), "", "repo", "delete", "alpha")
298 require.Error(t, res.err)
299 assert.Contains(t, res.err.Error(), "--yes")
300 calls, _ := f.snapshot()
301 assert.Empty(t, calls)
302 })
303 t.Run("--yes skips the question", func(t *testing.T) {
304 f := newFake()
305 res := runCLI(t, envFor(f.serve(t)), "", "repo", "delete", "alpha", "--yes").ok(t)
306 assert.Equal(t, "deleted alpha\n", res.stdout)
307 assert.NotContains(t, res.stderr, "confirm")
308 })
309 t.Run("-y", func(t *testing.T) {
310 f := newFake()
311 runCLI(t, envFor(f.serve(t)), "", "repo", "delete", "alpha", "-y").ok(t)
312 })
313}
314
315func TestRepoErrorsAreFriendly(t *testing.T) {
316 f := &fakeAPI{getRepo: func(*ermite.GetRepoRequest) (*ermite.GetRepoResponse, error) {
317 return nil, connect.NewError(connect.CodeNotFound, errors.New("repository \"nope\" not found"))
318 }}
319 res := runCLI(t, envFor(f.serve(t)), "", "repo", "show", "nope")
320 require.Error(t, res.err)
321 assert.Equal(t, "not found: repository \"nope\" not found", res.err.Error())
322}
323
324func TestUnauthenticatedHintsAtToken(t *testing.T) {
325 f := &fakeAPI{listRepos: func(*ermite.ListReposRequest) (*ermite.ListReposResponse, error) {
326 return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("invalid token"))
327 }}
328 res := runCLI(t, map[string]string{"ERMITE_URL": f.serve(t)}, "", "repo", "list")
329 require.Error(t, res.err)
330 assert.Contains(t, res.err.Error(), "ERMITE_TOKEN")
331}
332
333func TestMissingURL(t *testing.T) {
334 res := runCLI(t, nil, "", "repo", "list")
335 require.Error(t, res.err)
336 assert.Contains(t, res.err.Error(), "ERMITE_URL is not set")
337}
338
339func TestInvalidURL(t *testing.T) {
340 res := runCLI(t, map[string]string{"ERMITE_URL": "git.example.com"}, "", "repo", "list")
341 require.Error(t, res.err)
342 assert.Contains(t, res.err.Error(), "ERMITE_URL")
343 assert.Contains(t, res.err.Error(), "http")
344}
345
346func TestUnreachableServer(t *testing.T) {
347 res := runCLI(t, envFor("http://127.0.0.1:1"), "", "repo", "list")
348 require.Error(t, res.err)
349 assert.Contains(t, res.err.Error(), "cannot reach the server at http://127.0.0.1:1")
350 assert.NotContains(t, res.err.Error(), "\n")
351}
352
353func TestInsecureTokenWarningOnStderr(t *testing.T) {
354 // A non-loopback http URL: nothing listens there, the warning comes first.
355 res := runCLI(t, map[string]string{"ERMITE_URL": "http://ermite.invalid:1", "ERMITE_TOKEN": "ermite_tok"}, "", "repo", "list")
356 require.Error(t, res.err)
357 assert.Contains(t, res.stderr, "ermite: warning: ERMITE_TOKEN is sent over plain http to ermite.invalid")
358 assert.NotContains(t, res.stderr, "ermite_tok")
359}
360
361func TestLoopbackHTTPDoesNotWarn(t *testing.T) {
362 f := &fakeAPI{listRepos: func(*ermite.ListReposRequest) (*ermite.ListReposResponse, error) {
363 return &ermite.ListReposResponse{Repos: []*ermite.Repo{repoAlpha}}, nil
364 }}
365 res := runCLI(t, envFor(f.serve(t)), "", "repo", "list").ok(t)
366 assert.Empty(t, res.stderr)
367}
368
369func TestTerminalControlCharactersAreNeutralised(t *testing.T) {
370 evil := &ermite.Repo{Name: "x", Description: "a\x1b[31mred\nline", Visibility: ermite.Visibility_VISIBILITY_PUBLIC}
371 f := &fakeAPI{listRepos: func(*ermite.ListReposRequest) (*ermite.ListReposResponse, error) {
372 return &ermite.ListReposResponse{Repos: []*ermite.Repo{evil}}, nil
373 }}
374 res := runCLI(t, envFor(f.serve(t)), "", "repo", "list").ok(t)
375 assert.NotContains(t, res.stdout, "\x1b")
376 assert.Equal(t, 2, len(splitLines(res.stdout)), "header and one row")
377}
378
379func splitLines(s string) []string {
380 var out []string
381 cur := ""
382 for _, r := range s {
383 if r == '\n' {
384 out = append(out, cur)
385 cur = ""
386 continue
387 }
388 cur += string(r)
389 }
390 if cur != "" {
391 out = append(out, cur)
392 }
393 return out
394}
395
396func TestRepoNameCompletion(t *testing.T) {
397 f := &fakeAPI{listRepos: func(*ermite.ListReposRequest) (*ermite.ListReposResponse, error) {
398 return &ermite.ListReposResponse{Repos: []*ermite.Repo{repoAlpha, repoBeta}}, nil
399 }}
400 vars := envFor(f.serve(t))
401 res := runCLI(t, vars, "", "__complete", "repo", "show", "a").ok(t)
402 assert.Contains(t, res.stdout, "alpha\tpublic, The first one")
403 assert.NotContains(t, res.stdout, "beta")
404
405 res = runCLI(t, vars, "", "__complete", "repo", "delete", "").ok(t)
406 assert.Contains(t, res.stdout, "alpha")
407 assert.Contains(t, res.stdout, "beta")
408
409 // A second argument gets no repository names.
410 res = runCLI(t, vars, "", "__complete", "repo", "show", "alpha", "").ok(t)
411 assert.NotContains(t, res.stdout, "beta")
412
413 res = runCLI(t, vars, "", "__complete", "watch", "").ok(t)
414 assert.Contains(t, res.stdout, "alpha")
415}
416
417func TestCompletionIsSilentOnErrors(t *testing.T) {
418 denied := &fakeAPI{listRepos: func(*ermite.ListReposRequest) (*ermite.ListReposResponse, error) {
419 return nil, connect.NewError(connect.CodeUnauthenticated, nil)
420 }}
421 for name, vars := range map[string]map[string]string{
422 "no config": nil,
423 "bad url": {"ERMITE_URL": "nope"},
424 "unreachable": envFor("http://ermite.invalid:1"),
425 "rejected": envFor(denied.serve(t)),
426 } {
427 t.Run(name, func(t *testing.T) {
428 res := runCLI(t, vars, "", "__complete", "repo", "show", "").ok(t)
429 assert.NotContains(t, res.stdout, "alpha")
430 assert.NotContains(t, res.stderr, "warning", "completion must not print warnings")
431 assert.Contains(t, res.stdout, ":4", "cobra's no-file-completion directive")
432 })
433 }
434}
435
436func TestShellCompletionScriptIsAvailable(t *testing.T) {
437 res := runCLI(t, nil, "", "completion", "bash").ok(t)
438 assert.Contains(t, res.stdout, "bash completion")
439}
440
441func TestToJSONRepoFromProto(t *testing.T) {
442 r := toJSONRepo(proto.Clone(repoBeta).(*ermite.Repo))
443 assert.Nil(t, r.PushedAt)
444 assert.Equal(t, "private", r.Visibility)
445 assert.Equal(t, "unknown", toJSONRepo(&ermite.Repo{}).Visibility)
446}
447
448func TestRepoEditAllowAutoMerge(t *testing.T) {
449 updated := &ermite.Repo{Name: "alpha", Visibility: ermite.Visibility_VISIBILITY_PUBLIC, DefaultBranch: "main",
450 Settings: &ermite.RepoSettings{AllowAutoMerge: true}}
451 f := &fakeAPI{updateRepo: func(*ermite.UpdateRepoRequest) (*ermite.UpdateRepoResponse, error) {
452 return &ermite.UpdateRepoResponse{Repo: updated}, nil
453 }}
454 url := f.serve(t)
455
456 res := runCLI(t, envFor(url), "", "repo", "edit", "alpha", "--allow-auto-merge").ok(t)
457 assert.Regexp(t, `(?m)^allow auto-merge\s+true$`, res.stdout)
458 _, reqs := f.snapshot()
459 got := reqs[0].(*ermite.UpdateRepoRequest)
460 assert.Equal(t, []string{"settings.allow_auto_merge"}, got.GetUpdateMask().GetPaths())
461 assert.True(t, got.GetSettings().GetAllowAutoMerge())
462
463 res = runCLI(t, envFor(url), "", "repo", "edit", "alpha", "--allow-auto-merge", "--failing-checks-block=false", "--json").ok(t)
464 assert.Contains(t, res.stdout, `"allow_auto_merge": true`)
465 _, reqs = f.snapshot()
466 got = reqs[1].(*ermite.UpdateRepoRequest)
467 assert.ElementsMatch(t, []string{"settings.allow_auto_merge", "settings.failing_checks_block"}, got.GetUpdateMask().GetPaths())
468}