cli/pkg/cmd/release/download/download_test.go
2021-11-30 15:52:51 +01:00

335 lines
8 KiB
Go

package download
import (
"bytes"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"testing"
"github.com/cli/cli/v2/internal/ghrepo"
"github.com/cli/cli/v2/pkg/cmdutil"
"github.com/cli/cli/v2/pkg/httpmock"
"github.com/cli/cli/v2/pkg/iostreams"
"github.com/google/shlex"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_NewCmdDownload(t *testing.T) {
tests := []struct {
name string
args string
isTTY bool
want DownloadOptions
wantErr string
}{
{
name: "version argument",
args: "v1.2.3",
isTTY: true,
want: DownloadOptions{
TagName: "v1.2.3",
FilePatterns: []string(nil),
Destination: ".",
Concurrency: 5,
},
},
{
name: "version and file pattern",
args: "v1.2.3 -p *.tgz",
isTTY: true,
want: DownloadOptions{
TagName: "v1.2.3",
FilePatterns: []string{"*.tgz"},
Destination: ".",
Concurrency: 5,
},
},
{
name: "multiple file patterns",
args: "v1.2.3 -p 1 -p 2,3",
isTTY: true,
want: DownloadOptions{
TagName: "v1.2.3",
FilePatterns: []string{"1", "2,3"},
Destination: ".",
Concurrency: 5,
},
},
{
name: "version and destination",
args: "v1.2.3 -D tmp/assets",
isTTY: true,
want: DownloadOptions{
TagName: "v1.2.3",
FilePatterns: []string(nil),
Destination: "tmp/assets",
Concurrency: 5,
},
},
{
name: "download latest",
args: "-p *",
isTTY: true,
want: DownloadOptions{
TagName: "",
FilePatterns: []string{"*"},
Destination: ".",
Concurrency: 5,
},
},
{
name: "download archive with valid option",
args: "v1.2.3 -A zip",
isTTY: true,
want: DownloadOptions{
TagName: "v1.2.3",
FilePatterns: []string(nil),
Destination: ".",
ArchiveType: "zip",
Concurrency: 5,
},
},
{
name: "no arguments",
args: "",
isTTY: true,
wantErr: "`--pattern` or `--archive` is required when downloading the latest release",
},
{
name: "simultaneous pattern and archive arguments",
args: "-p * -A zip",
isTTY: true,
wantErr: "specify only one of '--pattern' or '--archive'",
},
{
name: "invalid archive argument",
args: "v1.2.3 -A abc",
isTTY: true,
wantErr: "the value for `--archive` must be one of \"zip\" or \"tar.gz\"",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
io, _, _, _ := iostreams.Test()
io.SetStdoutTTY(tt.isTTY)
io.SetStdinTTY(tt.isTTY)
io.SetStderrTTY(tt.isTTY)
f := &cmdutil.Factory{
IOStreams: io,
}
var opts *DownloadOptions
cmd := NewCmdDownload(f, func(o *DownloadOptions) error {
opts = o
return nil
})
cmd.PersistentFlags().StringP("repo", "R", "", "")
argv, err := shlex.Split(tt.args)
require.NoError(t, err)
cmd.SetArgs(argv)
cmd.SetIn(&bytes.Buffer{})
cmd.SetOut(ioutil.Discard)
cmd.SetErr(ioutil.Discard)
_, err = cmd.ExecuteC()
if tt.wantErr != "" {
require.EqualError(t, err, tt.wantErr)
return
} else {
require.NoError(t, err)
}
assert.Equal(t, tt.want.TagName, opts.TagName)
assert.Equal(t, tt.want.FilePatterns, opts.FilePatterns)
assert.Equal(t, tt.want.Destination, opts.Destination)
assert.Equal(t, tt.want.Concurrency, opts.Concurrency)
})
}
}
func Test_downloadRun(t *testing.T) {
tests := []struct {
name string
isTTY bool
opts DownloadOptions
wantErr string
wantStdout string
wantStderr string
wantFiles []string
}{
{
name: "download all assets",
isTTY: true,
opts: DownloadOptions{
TagName: "v1.2.3",
Destination: ".",
Concurrency: 2,
},
wantStdout: ``,
wantStderr: ``,
wantFiles: []string{
"linux.tgz",
"windows-32bit.zip",
"windows-64bit.zip",
},
},
{
name: "download assets matching pattern into destination directory",
isTTY: true,
opts: DownloadOptions{
TagName: "v1.2.3",
FilePatterns: []string{"windows-*.zip"},
Destination: "tmp/assets",
Concurrency: 2,
},
wantStdout: ``,
wantStderr: ``,
wantFiles: []string{
"tmp/assets/windows-32bit.zip",
"tmp/assets/windows-64bit.zip",
},
},
{
name: "no match for pattern",
isTTY: true,
opts: DownloadOptions{
TagName: "v1.2.3",
FilePatterns: []string{"linux*.zip"},
Destination: ".",
Concurrency: 2,
},
wantStdout: ``,
wantStderr: ``,
wantErr: "no assets match the file pattern",
},
{
name: "download archive in zip format into destination directory",
isTTY: true,
opts: DownloadOptions{
TagName: "v1.2.3",
ArchiveType: "zip",
Destination: "tmp/packages",
Concurrency: 2,
},
wantStdout: ``,
wantStderr: ``,
wantFiles: []string{
"tmp/packages/zipball.zip",
},
},
{
name: "download archive in `tar.gz` format into destination directory",
isTTY: true,
opts: DownloadOptions{
TagName: "v1.2.3",
ArchiveType: "tar.gz",
Destination: "tmp/packages",
Concurrency: 2,
},
wantStdout: ``,
wantStderr: ``,
wantFiles: []string{
"tmp/packages/tarball.tgz",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tempDir := t.TempDir()
tt.opts.Destination = filepath.Join(tempDir, tt.opts.Destination)
io, _, stdout, stderr := iostreams.Test()
io.SetStdoutTTY(tt.isTTY)
io.SetStdinTTY(tt.isTTY)
io.SetStderrTTY(tt.isTTY)
fakeHTTP := &httpmock.Registry{}
fakeHTTP.Register(httpmock.REST("GET", "repos/OWNER/REPO/releases/tags/v1.2.3"), httpmock.StringResponse(`{
"assets": [
{ "name": "windows-32bit.zip", "size": 12,
"url": "https://api.github.com/assets/1234" },
{ "name": "windows-64bit.zip", "size": 34,
"url": "https://api.github.com/assets/3456" },
{ "name": "linux.tgz", "size": 56,
"url": "https://api.github.com/assets/5678" }
],
"tarball_url": "https://api.github.com/repos/OWNER/REPO/tarball/v1.2.3",
"zipball_url": "https://api.github.com/repos/OWNER/REPO/zipball/v1.2.3"
}`))
fakeHTTP.Register(httpmock.REST("GET", "assets/1234"), httpmock.StringResponse(`1234`))
fakeHTTP.Register(httpmock.REST("GET", "assets/3456"), httpmock.StringResponse(`3456`))
fakeHTTP.Register(httpmock.REST("GET", "assets/5678"), httpmock.StringResponse(`5678`))
fakeHTTP.Register(
httpmock.REST(
"GET",
"repos/OWNER/REPO/tarball/v1.2.3",
),
httpmock.WithHeader(
httpmock.StringResponse("somedata"), "content-disposition", "attachment; filename=tarball.tgz",
),
)
fakeHTTP.Register(
httpmock.REST(
"GET",
"repos/OWNER/REPO/zipball/v1.2.3",
),
httpmock.WithHeader(
httpmock.StringResponse("somedata"), "content-disposition", "attachment; filename=zipball.zip",
),
)
tt.opts.IO = io
tt.opts.HttpClient = func() (*http.Client, error) {
return &http.Client{Transport: fakeHTTP}, nil
}
tt.opts.BaseRepo = func() (ghrepo.Interface, error) {
return ghrepo.FromFullName("OWNER/REPO")
}
err := downloadRun(&tt.opts)
if tt.wantErr != "" {
require.EqualError(t, err, tt.wantErr)
return
} else {
require.NoError(t, err)
}
var expectedAcceptHeader = "application/octet-stream"
if len(tt.opts.ArchiveType) > 0 {
expectedAcceptHeader = "application/octet-stream, application/json"
}
assert.Equal(t, expectedAcceptHeader, fakeHTTP.Requests[1].Header.Get("Accept"))
assert.Equal(t, tt.wantStdout, stdout.String())
assert.Equal(t, tt.wantStderr, stderr.String())
downloadedFiles, err := listFiles(tempDir)
require.NoError(t, err)
assert.Equal(t, tt.wantFiles, downloadedFiles)
})
}
}
func listFiles(dir string) ([]string, error) {
var files []string
err := filepath.Walk(dir, func(p string, f os.FileInfo, err error) error {
if !f.IsDir() {
rp, err := filepath.Rel(dir, p)
if err != nil {
return err
}
files = append(files, filepath.ToSlash(rp))
}
return err
})
return files, err
}