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