1package list
2
3import (
4	"bytes"
5	"fmt"
6	"io/ioutil"
7	"net/http"
8	"strings"
9	"testing"
10	"time"
11
12	"github.com/cli/cli/v2/internal/config"
13	"github.com/cli/cli/v2/internal/ghrepo"
14	"github.com/cli/cli/v2/pkg/cmd/secret/shared"
15	"github.com/cli/cli/v2/pkg/cmdutil"
16	"github.com/cli/cli/v2/pkg/httpmock"
17	"github.com/cli/cli/v2/pkg/iostreams"
18	"github.com/cli/cli/v2/test"
19	"github.com/google/shlex"
20	"github.com/stretchr/testify/assert"
21)
22
23func Test_NewCmdList(t *testing.T) {
24	tests := []struct {
25		name  string
26		cli   string
27		wants ListOptions
28	}{
29		{
30			name: "repo",
31			cli:  "",
32			wants: ListOptions{
33				OrgName: "",
34			},
35		},
36		{
37			name: "org",
38			cli:  "-oUmbrellaCorporation",
39			wants: ListOptions{
40				OrgName: "UmbrellaCorporation",
41			},
42		},
43		{
44			name: "env",
45			cli:  "-eDevelopment",
46			wants: ListOptions{
47				EnvName: "Development",
48			},
49		},
50		{
51			name: "user",
52			cli:  "-u",
53			wants: ListOptions{
54				UserSecrets: true,
55			},
56		},
57	}
58
59	for _, tt := range tests {
60		t.Run(tt.name, func(t *testing.T) {
61			io, _, _, _ := iostreams.Test()
62			f := &cmdutil.Factory{
63				IOStreams: io,
64			}
65
66			argv, err := shlex.Split(tt.cli)
67			assert.NoError(t, err)
68
69			var gotOpts *ListOptions
70			cmd := NewCmdList(f, func(opts *ListOptions) error {
71				gotOpts = opts
72				return nil
73			})
74			cmd.SetArgs(argv)
75			cmd.SetIn(&bytes.Buffer{})
76			cmd.SetOut(&bytes.Buffer{})
77			cmd.SetErr(&bytes.Buffer{})
78
79			_, err = cmd.ExecuteC()
80			assert.NoError(t, err)
81
82			assert.Equal(t, tt.wants.OrgName, gotOpts.OrgName)
83			assert.Equal(t, tt.wants.EnvName, gotOpts.EnvName)
84		})
85	}
86}
87
88func Test_listRun(t *testing.T) {
89	tests := []struct {
90		name    string
91		tty     bool
92		opts    *ListOptions
93		wantOut []string
94	}{
95		{
96			name: "repo tty",
97			tty:  true,
98			opts: &ListOptions{},
99			wantOut: []string{
100				"SECRET_ONE.*Updated 1988-10-11",
101				"SECRET_TWO.*Updated 2020-12-04",
102				"SECRET_THREE.*Updated 1975-11-30",
103			},
104		},
105		{
106			name: "repo not tty",
107			tty:  false,
108			opts: &ListOptions{},
109			wantOut: []string{
110				"SECRET_ONE\t1988-10-11",
111				"SECRET_TWO\t2020-12-04",
112				"SECRET_THREE\t1975-11-30",
113			},
114		},
115		{
116			name: "org tty",
117			tty:  true,
118			opts: &ListOptions{
119				OrgName: "UmbrellaCorporation",
120			},
121			wantOut: []string{
122				"SECRET_ONE.*Updated 1988-10-11.*Visible to all repositories",
123				"SECRET_TWO.*Updated 2020-12-04.*Visible to private repositories",
124				"SECRET_THREE.*Updated 1975-11-30.*Visible to 2 selected repositories",
125			},
126		},
127		{
128			name: "org not tty",
129			tty:  false,
130			opts: &ListOptions{
131				OrgName: "UmbrellaCorporation",
132			},
133			wantOut: []string{
134				"SECRET_ONE\t1988-10-11\tALL",
135				"SECRET_TWO\t2020-12-04\tPRIVATE",
136				"SECRET_THREE\t1975-11-30\tSELECTED",
137			},
138		},
139		{
140			name: "env tty",
141			tty:  true,
142			opts: &ListOptions{
143				EnvName: "Development",
144			},
145			wantOut: []string{
146				"SECRET_ONE.*Updated 1988-10-11",
147				"SECRET_TWO.*Updated 2020-12-04",
148				"SECRET_THREE.*Updated 1975-11-30",
149			},
150		},
151		{
152			name: "env not tty",
153			tty:  false,
154			opts: &ListOptions{
155				EnvName: "Development",
156			},
157			wantOut: []string{
158				"SECRET_ONE\t1988-10-11",
159				"SECRET_TWO\t2020-12-04",
160				"SECRET_THREE\t1975-11-30",
161			},
162		},
163		{
164			name: "user tty",
165			tty:  true,
166			opts: &ListOptions{
167				UserSecrets: true,
168			},
169			wantOut: []string{
170				"SECRET_ONE.*Updated 1988-10-11.*Visible to 1 selected repository",
171				"SECRET_TWO.*Updated 2020-12-04.*Visible to 2 selected repositories",
172				"SECRET_THREE.*Updated 1975-11-30.*Visible to 3 selected repositories",
173			},
174		},
175		{
176			name: "user not tty",
177			tty:  false,
178			opts: &ListOptions{
179				UserSecrets: true,
180			},
181			wantOut: []string{
182				"SECRET_ONE\t1988-10-11\t",
183				"SECRET_TWO\t2020-12-04\t",
184				"SECRET_THREE\t1975-11-30\t",
185			},
186		},
187	}
188
189	for _, tt := range tests {
190		t.Run(tt.name, func(t *testing.T) {
191			reg := &httpmock.Registry{}
192
193			path := "repos/owner/repo/actions/secrets"
194			if tt.opts.EnvName != "" {
195				path = fmt.Sprintf("repos/owner/repo/environments/%s/secrets", tt.opts.EnvName)
196			}
197
198			t0, _ := time.Parse("2006-01-02", "1988-10-11")
199			t1, _ := time.Parse("2006-01-02", "2020-12-04")
200			t2, _ := time.Parse("2006-01-02", "1975-11-30")
201			payload := secretsPayload{}
202			payload.Secrets = []*Secret{
203				{
204					Name:      "SECRET_ONE",
205					UpdatedAt: t0,
206				},
207				{
208					Name:      "SECRET_TWO",
209					UpdatedAt: t1,
210				},
211				{
212					Name:      "SECRET_THREE",
213					UpdatedAt: t2,
214				},
215			}
216			if tt.opts.OrgName != "" {
217				payload.Secrets = []*Secret{
218					{
219						Name:       "SECRET_ONE",
220						UpdatedAt:  t0,
221						Visibility: shared.All,
222					},
223					{
224						Name:       "SECRET_TWO",
225						UpdatedAt:  t1,
226						Visibility: shared.Private,
227					},
228					{
229						Name:             "SECRET_THREE",
230						UpdatedAt:        t2,
231						Visibility:       shared.Selected,
232						SelectedReposURL: fmt.Sprintf("https://api.github.com/orgs/%s/actions/secrets/SECRET_THREE/repositories", tt.opts.OrgName),
233					},
234				}
235				path = fmt.Sprintf("orgs/%s/actions/secrets", tt.opts.OrgName)
236
237				if tt.tty {
238					reg.Register(
239						httpmock.REST("GET", fmt.Sprintf("orgs/%s/actions/secrets/SECRET_THREE/repositories", tt.opts.OrgName)),
240						httpmock.JSONResponse(struct {
241							TotalCount int `json:"total_count"`
242						}{2}))
243				}
244			}
245
246			if tt.opts.UserSecrets {
247				payload.Secrets = []*Secret{
248					{
249						Name:             "SECRET_ONE",
250						UpdatedAt:        t0,
251						Visibility:       shared.Selected,
252						SelectedReposURL: "https://api.github.com/user/codespaces/secrets/SECRET_ONE/repositories",
253					},
254					{
255						Name:             "SECRET_TWO",
256						UpdatedAt:        t1,
257						Visibility:       shared.Selected,
258						SelectedReposURL: "https://api.github.com/user/codespaces/secrets/SECRET_TWO/repositories",
259					},
260					{
261						Name:             "SECRET_THREE",
262						UpdatedAt:        t2,
263						Visibility:       shared.Selected,
264						SelectedReposURL: "https://api.github.com/user/codespaces/secrets/SECRET_THREE/repositories",
265					},
266				}
267
268				path = "user/codespaces/secrets"
269				if tt.tty {
270					for i, secret := range payload.Secrets {
271						hostLen := len("https://api.github.com/")
272						path := secret.SelectedReposURL[hostLen:len(secret.SelectedReposURL)]
273						repositoryCount := i + 1
274						reg.Register(
275							httpmock.REST("GET", path),
276							httpmock.JSONResponse(struct {
277								TotalCount int `json:"total_count"`
278							}{repositoryCount}))
279					}
280				}
281			}
282
283			reg.Register(httpmock.REST("GET", path), httpmock.JSONResponse(payload))
284
285			io, _, stdout, _ := iostreams.Test()
286
287			io.SetStdoutTTY(tt.tty)
288
289			tt.opts.IO = io
290			tt.opts.BaseRepo = func() (ghrepo.Interface, error) {
291				return ghrepo.FromFullName("owner/repo")
292			}
293			tt.opts.HttpClient = func() (*http.Client, error) {
294				return &http.Client{Transport: reg}, nil
295			}
296			tt.opts.Config = func() (config.Config, error) {
297				return config.NewBlankConfig(), nil
298			}
299
300			err := listRun(tt.opts)
301			assert.NoError(t, err)
302
303			reg.Verify(t)
304
305			//nolint:staticcheck // prefer exact matchers over ExpectLines
306			test.ExpectLines(t, stdout.String(), tt.wantOut...)
307		})
308	}
309}
310
311func Test_getSecrets_pagination(t *testing.T) {
312	var requests []*http.Request
313	var client testClient = func(req *http.Request) (*http.Response, error) {
314		header := make(map[string][]string)
315		if len(requests) == 0 {
316			header["Link"] = []string{`<http://example.com/page/0>; rel="previous", <http://example.com/page/2>; rel="next"`}
317		}
318		requests = append(requests, req)
319		return &http.Response{
320			Request: req,
321			Body:    ioutil.NopCloser(strings.NewReader(`{"secrets":[{},{}]}`)),
322			Header:  header,
323		}, nil
324	}
325
326	secrets, err := getSecrets(client, "github.com", "path/to")
327	assert.NoError(t, err)
328	assert.Equal(t, 2, len(requests))
329	assert.Equal(t, 4, len(secrets))
330	assert.Equal(t, "https://api.github.com/path/to?per_page=100", requests[0].URL.String())
331	assert.Equal(t, "http://example.com/page/2", requests[1].URL.String())
332}
333
334type testClient func(*http.Request) (*http.Response, error)
335
336func (c testClient) Do(req *http.Request) (*http.Response, error) {
337	return c(req)
338}
339