github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/run/download/download_test.go (about) 1 package download 2 3 import ( 4 "bytes" 5 "io" 6 "net/http" 7 "path/filepath" 8 "testing" 9 10 "github.com/ungtb10d/cli/v2/internal/ghrepo" 11 "github.com/ungtb10d/cli/v2/pkg/cmd/run/shared" 12 "github.com/ungtb10d/cli/v2/pkg/cmdutil" 13 "github.com/ungtb10d/cli/v2/pkg/iostreams" 14 "github.com/google/shlex" 15 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/mock" 17 "github.com/stretchr/testify/require" 18 ) 19 20 func Test_NewCmdDownload(t *testing.T) { 21 tests := []struct { 22 name string 23 args string 24 isTTY bool 25 want DownloadOptions 26 wantErr string 27 }{ 28 { 29 name: "empty", 30 args: "", 31 isTTY: true, 32 want: DownloadOptions{ 33 RunID: "", 34 DoPrompt: true, 35 Names: []string(nil), 36 DestinationDir: ".", 37 }, 38 }, 39 { 40 name: "with run ID", 41 args: "2345", 42 isTTY: true, 43 want: DownloadOptions{ 44 RunID: "2345", 45 DoPrompt: false, 46 Names: []string(nil), 47 DestinationDir: ".", 48 }, 49 }, 50 { 51 name: "to destination", 52 args: "2345 -D tmp/dest", 53 isTTY: true, 54 want: DownloadOptions{ 55 RunID: "2345", 56 DoPrompt: false, 57 Names: []string(nil), 58 DestinationDir: "tmp/dest", 59 }, 60 }, 61 { 62 name: "repo level with names", 63 args: "-n one -n two", 64 isTTY: true, 65 want: DownloadOptions{ 66 RunID: "", 67 DoPrompt: false, 68 Names: []string{"one", "two"}, 69 DestinationDir: ".", 70 }, 71 }, 72 { 73 name: "repo level with patterns", 74 args: "-p o*e -p tw*", 75 isTTY: true, 76 want: DownloadOptions{ 77 RunID: "", 78 DoPrompt: false, 79 FilePatterns: []string{"o*e", "tw*"}, 80 DestinationDir: ".", 81 }, 82 }, 83 { 84 name: "repo level with names and patterns", 85 args: "-p o*e -p tw* -n three -n four", 86 isTTY: true, 87 want: DownloadOptions{ 88 RunID: "", 89 DoPrompt: false, 90 Names: []string{"three", "four"}, 91 FilePatterns: []string{"o*e", "tw*"}, 92 DestinationDir: ".", 93 }, 94 }, 95 } 96 for _, tt := range tests { 97 t.Run(tt.name, func(t *testing.T) { 98 ios, _, _, _ := iostreams.Test() 99 ios.SetStdoutTTY(tt.isTTY) 100 ios.SetStdinTTY(tt.isTTY) 101 ios.SetStderrTTY(tt.isTTY) 102 103 f := &cmdutil.Factory{ 104 IOStreams: ios, 105 HttpClient: func() (*http.Client, error) { 106 return nil, nil 107 }, 108 BaseRepo: func() (ghrepo.Interface, error) { 109 return nil, nil 110 }, 111 } 112 113 var opts *DownloadOptions 114 cmd := NewCmdDownload(f, func(o *DownloadOptions) error { 115 opts = o 116 return nil 117 }) 118 cmd.PersistentFlags().StringP("repo", "R", "", "") 119 120 argv, err := shlex.Split(tt.args) 121 require.NoError(t, err) 122 cmd.SetArgs(argv) 123 124 cmd.SetIn(&bytes.Buffer{}) 125 cmd.SetOut(io.Discard) 126 cmd.SetErr(io.Discard) 127 128 _, err = cmd.ExecuteC() 129 if tt.wantErr != "" { 130 require.EqualError(t, err, tt.wantErr) 131 return 132 } else { 133 require.NoError(t, err) 134 } 135 136 assert.Equal(t, tt.want.RunID, opts.RunID) 137 assert.Equal(t, tt.want.Names, opts.Names) 138 assert.Equal(t, tt.want.FilePatterns, opts.FilePatterns) 139 assert.Equal(t, tt.want.DestinationDir, opts.DestinationDir) 140 assert.Equal(t, tt.want.DoPrompt, opts.DoPrompt) 141 }) 142 } 143 } 144 145 func Test_runDownload(t *testing.T) { 146 tests := []struct { 147 name string 148 opts DownloadOptions 149 mockAPI func(*mockPlatform) 150 mockPrompt func(*mockPrompter) 151 wantErr string 152 }{ 153 { 154 name: "download non-expired", 155 opts: DownloadOptions{ 156 RunID: "2345", 157 DestinationDir: "./tmp", 158 Names: []string(nil), 159 }, 160 mockAPI: func(p *mockPlatform) { 161 p.On("List", "2345").Return([]shared.Artifact{ 162 { 163 Name: "artifact-1", 164 DownloadURL: "http://download.com/artifact1.zip", 165 Expired: false, 166 }, 167 { 168 Name: "expired-artifact", 169 DownloadURL: "http://download.com/expired.zip", 170 Expired: true, 171 }, 172 { 173 Name: "artifact-2", 174 DownloadURL: "http://download.com/artifact2.zip", 175 Expired: false, 176 }, 177 }, nil) 178 p.On("Download", "http://download.com/artifact1.zip", filepath.FromSlash("tmp/artifact-1")).Return(nil) 179 p.On("Download", "http://download.com/artifact2.zip", filepath.FromSlash("tmp/artifact-2")).Return(nil) 180 }, 181 }, 182 { 183 name: "no valid artifacts", 184 opts: DownloadOptions{ 185 RunID: "2345", 186 DestinationDir: ".", 187 Names: []string(nil), 188 }, 189 mockAPI: func(p *mockPlatform) { 190 p.On("List", "2345").Return([]shared.Artifact{ 191 { 192 Name: "artifact-1", 193 DownloadURL: "http://download.com/artifact1.zip", 194 Expired: true, 195 }, 196 { 197 Name: "artifact-2", 198 DownloadURL: "http://download.com/artifact2.zip", 199 Expired: true, 200 }, 201 }, nil) 202 }, 203 wantErr: "no valid artifacts found to download", 204 }, 205 { 206 name: "no name matches", 207 opts: DownloadOptions{ 208 RunID: "2345", 209 DestinationDir: ".", 210 Names: []string{"artifact-3"}, 211 }, 212 mockAPI: func(p *mockPlatform) { 213 p.On("List", "2345").Return([]shared.Artifact{ 214 { 215 Name: "artifact-1", 216 DownloadURL: "http://download.com/artifact1.zip", 217 Expired: false, 218 }, 219 { 220 Name: "artifact-2", 221 DownloadURL: "http://download.com/artifact2.zip", 222 Expired: false, 223 }, 224 }, nil) 225 }, 226 wantErr: "no artifact matches any of the names or patterns provided", 227 }, 228 { 229 name: "no pattern matches", 230 opts: DownloadOptions{ 231 RunID: "2345", 232 DestinationDir: ".", 233 FilePatterns: []string{"artifiction-*"}, 234 }, 235 mockAPI: func(p *mockPlatform) { 236 p.On("List", "2345").Return([]shared.Artifact{ 237 { 238 Name: "artifact-1", 239 DownloadURL: "http://download.com/artifact1.zip", 240 Expired: false, 241 }, 242 { 243 Name: "artifact-2", 244 DownloadURL: "http://download.com/artifact2.zip", 245 Expired: false, 246 }, 247 }, nil) 248 }, 249 wantErr: "no artifact matches any of the names or patterns provided", 250 }, 251 { 252 name: "prompt to select artifact", 253 opts: DownloadOptions{ 254 RunID: "", 255 DoPrompt: true, 256 DestinationDir: ".", 257 Names: []string(nil), 258 }, 259 mockAPI: func(p *mockPlatform) { 260 p.On("List", "").Return([]shared.Artifact{ 261 { 262 Name: "artifact-1", 263 DownloadURL: "http://download.com/artifact1.zip", 264 Expired: false, 265 }, 266 { 267 Name: "expired-artifact", 268 DownloadURL: "http://download.com/expired.zip", 269 Expired: true, 270 }, 271 { 272 Name: "artifact-2", 273 DownloadURL: "http://download.com/artifact2.zip", 274 Expired: false, 275 }, 276 { 277 Name: "artifact-2", 278 DownloadURL: "http://download.com/artifact2.also.zip", 279 Expired: false, 280 }, 281 }, nil) 282 p.On("Download", "http://download.com/artifact2.zip", ".").Return(nil) 283 }, 284 mockPrompt: func(p *mockPrompter) { 285 p.On("Prompt", "Select artifacts to download:", []string{"artifact-1", "artifact-2"}, mock.AnythingOfType("*[]string")). 286 Run(func(args mock.Arguments) { 287 result := args.Get(2).(*[]string) 288 *result = []string{"artifact-2"} 289 }). 290 Return(nil) 291 }, 292 }, 293 } 294 for _, tt := range tests { 295 t.Run(tt.name, func(t *testing.T) { 296 opts := &tt.opts 297 ios, _, stdout, stderr := iostreams.Test() 298 opts.IO = ios 299 opts.Platform = newMockPlatform(t, tt.mockAPI) 300 opts.Prompter = newMockPrompter(t, tt.mockPrompt) 301 302 err := runDownload(opts) 303 if tt.wantErr != "" { 304 require.EqualError(t, err, tt.wantErr) 305 } else { 306 require.NoError(t, err) 307 } 308 309 assert.Equal(t, "", stdout.String()) 310 assert.Equal(t, "", stderr.String()) 311 }) 312 } 313 } 314 315 type mockPlatform struct { 316 mock.Mock 317 } 318 319 func newMockPlatform(t *testing.T, config func(*mockPlatform)) *mockPlatform { 320 m := &mockPlatform{} 321 m.Test(t) 322 t.Cleanup(func() { 323 m.AssertExpectations(t) 324 }) 325 if config != nil { 326 config(m) 327 } 328 return m 329 } 330 331 func (p *mockPlatform) List(runID string) ([]shared.Artifact, error) { 332 args := p.Called(runID) 333 return args.Get(0).([]shared.Artifact), args.Error(1) 334 } 335 336 func (p *mockPlatform) Download(url string, dir string) error { 337 args := p.Called(url, dir) 338 return args.Error(0) 339 } 340 341 type mockPrompter struct { 342 mock.Mock 343 } 344 345 func newMockPrompter(t *testing.T, config func(*mockPrompter)) *mockPrompter { 346 m := &mockPrompter{} 347 m.Test(t) 348 t.Cleanup(func() { 349 m.AssertExpectations(t) 350 }) 351 if config != nil { 352 config(m) 353 } 354 return m 355 } 356 357 func (p *mockPrompter) Prompt(msg string, opts []string, res interface{}) error { 358 args := p.Called(msg, opts, res) 359 return args.Error(0) 360 }