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  }