1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package app
4
5import (
6 "context"
7 "testing"
8
9 "connectrpc.com/connect"
10 "github.com/stretchr/testify/assert"
11
12 "bnema.dev/ermite-api/gen/ermite"
13)
14
15// The RunService calls of the `run` and `runner` commands answer with empty
16// results: these tests look at which address and token reach the server, not
17// at the output.
18func (f *fakeAPI) ListRuns(_ context.Context, r *connect.Request[ermite.ListRunsRequest]) (*connect.Response[ermite.ListRunsResponse], error) {
19 return respond(f, "ListRuns", r, func(*ermite.ListRunsRequest) (*ermite.ListRunsResponse, error) {
20 return &ermite.ListRunsResponse{}, nil
21 })
22}
23
24func (f *fakeAPI) CancelRun(_ context.Context, r *connect.Request[ermite.CancelRunRequest]) (*connect.Response[ermite.CancelRunResponse], error) {
25 return respond(f, "CancelRun", r, func(*ermite.CancelRunRequest) (*ermite.CancelRunResponse, error) {
26 return &ermite.CancelRunResponse{}, nil
27 })
28}
29
30func (f *fakeAPI) RerunRun(_ context.Context, r *connect.Request[ermite.RerunRunRequest]) (*connect.Response[ermite.RerunRunResponse], error) {
31 return respond(f, "RerunRun", r, func(*ermite.RerunRunRequest) (*ermite.RerunRunResponse, error) {
32 return &ermite.RerunRunResponse{}, nil
33 })
34}
35
36func (f *fakeAPI) ListRunners(_ context.Context, r *connect.Request[ermite.ListRunnersRequest]) (*connect.Response[ermite.ListRunnersResponse], error) {
37 return respond(f, "ListRunners", r, func(*ermite.ListRunnersRequest) (*ermite.ListRunnersResponse, error) {
38 return &ermite.ListRunnersResponse{Runners: []*ermite.Runner{{Id: "r1", Name: "alpha"}}}, nil
39 })
40}
41
42func (f *fakeAPI) RemoveRunner(_ context.Context, r *connect.Request[ermite.RemoveRunnerRequest]) (*connect.Response[ermite.RemoveRunnerResponse], error) {
43 return respond(f, "RemoveRunner", r, func(*ermite.RemoveRunnerRequest) (*ermite.RemoveRunnerResponse, error) {
44 return &ermite.RemoveRunnerResponse{}, nil
45 })
46}
47
48// The `run` and `runner` commands read the server address and the token like
49// every other remote command: the config file (url, token.cmd), with
50// ERMITE_URL / ERMITE_TOKEN winning, and token.cmd run once and only for the
51// file's own url.
52func TestCIcommandsUseTheConfigFile(t *testing.T) {
53 commands := map[string][]string{
54 "run list": {"run", "list", "proj"},
55 "run cancel": {"run", "cancel", "proj", "1"},
56 "run rerun": {"run", "rerun", "proj", "1"},
57 "runner list": {"runner", "list"},
58 "runner remove": {"runner", "remove", "r1"},
59 }
60 for name, args := range commands {
61 t.Run(name, func(t *testing.T) {
62 f := &fakeAPI{}
63 url := f.serve(t)
64
65 // url and token.cmd from the file
66 env := testEnv(t, nil)
67 writeConfigFile(t, env, "url = "+url+"\ntoken.cmd = get-token\n", 0o600)
68 ran := tokenCmdStub(env, "ermite_cmd\n", nil)
69 runCLIWith(t, env, "", args...).ok(t)
70 assert.Equal(t, "Bearer ermite_cmd", lastAuth(f))
71 assert.Len(t, *ran, 1)
72
73 // ERMITE_TOKEN wins and token.cmd stays idle
74 env = testEnv(t, map[string]string{"ERMITE_TOKEN": "ermite_env"})
75 writeConfigFile(t, env, "url = "+url+"\ntoken.cmd = get-token\n", 0o600)
76 ran = tokenCmdStub(env, "ermite_cmd", nil)
77 runCLIWith(t, env, "", args...).ok(t)
78 assert.Equal(t, "Bearer ermite_env", lastAuth(f))
79 assert.Empty(t, *ran)
80
81 // another ERMITE_URL never receives the file's token
82 other := &fakeAPI{}
83 env = testEnv(t, map[string]string{"ERMITE_URL": other.serve(t)})
84 writeConfigFile(t, env, "url = "+url+"\ntoken.cmd = get-token\n", 0o600)
85 ran = tokenCmdStub(env, "ermite_cmd", nil)
86 runCLIWith(t, env, "", args...).ok(t)
87 assert.Empty(t, lastAuth(other))
88 assert.Empty(t, *ran)
89 })
90 }
91}
92
93// Completing a repository name for `run` or a runner id for `runner remove`
94// must not run token.cmd: it may prompt, and a completion script cannot answer.
95func TestCICompletionNeverRunsTokenCmd(t *testing.T) {
96 f := &fakeAPI{listRepos: func(*ermite.ListReposRequest) (*ermite.ListReposResponse, error) {
97 return &ermite.ListReposResponse{Repos: []*ermite.Repo{repoAlpha}}, nil
98 }}
99 url := f.serve(t)
100 for name, args := range map[string][]string{
101 "run list": {"__complete", "run", "list", ""},
102 "run cancel": {"__complete", "run", "cancel", ""},
103 "runner remove": {"__complete", "runner", "remove", ""},
104 } {
105 t.Run(name, func(t *testing.T) {
106 env := testEnv(t, nil)
107 writeConfigFile(t, env, "url = "+url+"\ntoken.cmd = get-token\n", 0o600)
108 ran := tokenCmdStub(env, "ermite_cmd", nil)
109 runCLIWith(t, env, "", args...).ok(t)
110 assert.Empty(t, *ran)
111 assert.Empty(t, lastAuth(f))
112 })
113 }
114}