427 lines
10 KiB
Go
427 lines
10 KiB
Go
package list
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/MakeNowJust/heredoc"
|
|
"github.com/cli/cli/v2/internal/ghrepo"
|
|
"github.com/cli/cli/v2/pkg/cmd/cache/shared"
|
|
"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"
|
|
)
|
|
|
|
func TestNewCmdList(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
wants ListOptions
|
|
wantsErr string
|
|
}{
|
|
{
|
|
name: "no arguments",
|
|
input: "",
|
|
wants: ListOptions{
|
|
Limit: 30,
|
|
Order: "desc",
|
|
Sort: "last_accessed_at",
|
|
Key: "",
|
|
Ref: "",
|
|
},
|
|
},
|
|
{
|
|
name: "with limit",
|
|
input: "--limit 100",
|
|
wants: ListOptions{
|
|
Limit: 100,
|
|
Order: "desc",
|
|
Sort: "last_accessed_at",
|
|
Key: "",
|
|
Ref: "",
|
|
},
|
|
},
|
|
{
|
|
name: "invalid limit",
|
|
input: "-L 0",
|
|
wantsErr: "invalid limit: 0",
|
|
},
|
|
{
|
|
name: "with sort",
|
|
input: "--sort created_at",
|
|
wants: ListOptions{
|
|
Limit: 30,
|
|
Order: "desc",
|
|
Sort: "created_at",
|
|
Key: "",
|
|
Ref: "",
|
|
},
|
|
},
|
|
{
|
|
name: "with order",
|
|
input: "--order asc",
|
|
wants: ListOptions{
|
|
Limit: 30,
|
|
Order: "asc",
|
|
Sort: "last_accessed_at",
|
|
Key: "",
|
|
Ref: "",
|
|
},
|
|
},
|
|
{
|
|
name: "with key",
|
|
input: "--key cache-key-prefix-",
|
|
wants: ListOptions{
|
|
Limit: 30,
|
|
Order: "desc",
|
|
Sort: "last_accessed_at",
|
|
Key: "cache-key-prefix-",
|
|
Ref: "",
|
|
},
|
|
},
|
|
{
|
|
name: "with ref",
|
|
input: "--ref refs/heads/main",
|
|
wants: ListOptions{
|
|
Limit: 30,
|
|
Order: "desc",
|
|
Sort: "last_accessed_at",
|
|
Key: "",
|
|
Ref: "refs/heads/main",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
f := &cmdutil.Factory{}
|
|
argv, err := shlex.Split(tt.input)
|
|
assert.NoError(t, err)
|
|
var gotOpts *ListOptions
|
|
cmd := NewCmdList(f, func(opts *ListOptions) error {
|
|
gotOpts = opts
|
|
return nil
|
|
})
|
|
cmd.SetArgs(argv)
|
|
cmd.SetIn(&bytes.Buffer{})
|
|
cmd.SetOut(&bytes.Buffer{})
|
|
cmd.SetErr(&bytes.Buffer{})
|
|
|
|
_, err = cmd.ExecuteC()
|
|
if tt.wantsErr != "" {
|
|
assert.EqualError(t, err, tt.wantsErr)
|
|
return
|
|
}
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tt.wants.Limit, gotOpts.Limit)
|
|
assert.Equal(t, tt.wants.Sort, gotOpts.Sort)
|
|
assert.Equal(t, tt.wants.Order, gotOpts.Order)
|
|
assert.Equal(t, tt.wants.Key, gotOpts.Key)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestListRun(t *testing.T) {
|
|
var now = time.Date(2023, 1, 1, 1, 1, 1, 1, time.UTC)
|
|
tests := []struct {
|
|
name string
|
|
opts ListOptions
|
|
stubs func(*httpmock.Registry)
|
|
tty bool
|
|
wantErr bool
|
|
wantErrMsg string
|
|
wantStderr string
|
|
wantStdout string
|
|
}{
|
|
{
|
|
name: "displays results tty",
|
|
tty: true,
|
|
stubs: func(reg *httpmock.Registry) {
|
|
reg.Register(
|
|
httpmock.REST("GET", "repos/OWNER/REPO/actions/caches"),
|
|
httpmock.JSONResponse(shared.CachePayload{
|
|
ActionsCaches: []shared.Cache{
|
|
{
|
|
Id: 1,
|
|
Key: "foo",
|
|
CreatedAt: time.Date(2021, 1, 1, 1, 1, 1, 1, time.UTC),
|
|
LastAccessedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC),
|
|
SizeInBytes: 100,
|
|
},
|
|
{
|
|
Id: 2,
|
|
Key: "bar",
|
|
CreatedAt: time.Date(2021, 1, 1, 1, 1, 1, 1, time.UTC),
|
|
LastAccessedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC),
|
|
SizeInBytes: 1024,
|
|
},
|
|
},
|
|
TotalCount: 2,
|
|
}),
|
|
)
|
|
},
|
|
wantStdout: heredoc.Doc(`
|
|
|
|
Showing 2 of 2 caches in OWNER/REPO
|
|
|
|
ID KEY SIZE CREATED ACCESSED
|
|
1 foo 100 B about 2 years ago about 1 year ago
|
|
2 bar 1.00 KiB about 2 years ago about 1 year ago
|
|
`),
|
|
},
|
|
{
|
|
name: "displays results non-tty",
|
|
tty: false,
|
|
stubs: func(reg *httpmock.Registry) {
|
|
reg.Register(
|
|
httpmock.REST("GET", "repos/OWNER/REPO/actions/caches"),
|
|
httpmock.JSONResponse(shared.CachePayload{
|
|
ActionsCaches: []shared.Cache{
|
|
{
|
|
Id: 1,
|
|
Key: "foo",
|
|
CreatedAt: time.Date(2021, 1, 1, 1, 1, 1, 1, time.UTC),
|
|
LastAccessedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC),
|
|
SizeInBytes: 100,
|
|
},
|
|
{
|
|
Id: 2,
|
|
Key: "bar",
|
|
CreatedAt: time.Date(2021, 1, 1, 1, 1, 1, 1, time.UTC),
|
|
LastAccessedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC),
|
|
SizeInBytes: 1024,
|
|
},
|
|
},
|
|
TotalCount: 2,
|
|
}),
|
|
)
|
|
},
|
|
wantStdout: "1\tfoo\t100 B\t2021-01-01T01:01:01Z\t2022-01-01T01:01:01Z\n2\tbar\t1.00 KiB\t2021-01-01T01:01:01Z\t2022-01-01T01:01:01Z\n",
|
|
},
|
|
{
|
|
name: "only requests caches with the provided key prefix",
|
|
opts: ListOptions{
|
|
Key: "test-key",
|
|
},
|
|
stubs: func(reg *httpmock.Registry) {
|
|
reg.Register(
|
|
func(req *http.Request) bool {
|
|
return req.URL.Query().Get("key") == "test-key"
|
|
},
|
|
httpmock.JSONResponse(shared.CachePayload{
|
|
ActionsCaches: []shared.Cache{},
|
|
TotalCount: 0,
|
|
}))
|
|
},
|
|
// We could put anything here, we're really asserting that the key is passed
|
|
// to the API.
|
|
wantErr: true,
|
|
wantErrMsg: "No caches found in OWNER/REPO",
|
|
},
|
|
{
|
|
name: "only requests caches with the provided ref",
|
|
opts: ListOptions{
|
|
Ref: "refs/heads/main",
|
|
},
|
|
stubs: func(reg *httpmock.Registry) {
|
|
reg.Register(
|
|
func(req *http.Request) bool {
|
|
return req.URL.Query().Get("ref") == "refs/heads/main"
|
|
},
|
|
httpmock.JSONResponse(shared.CachePayload{
|
|
ActionsCaches: []shared.Cache{},
|
|
TotalCount: 0,
|
|
}))
|
|
},
|
|
// We could put anything here, we're really asserting that the key is passed
|
|
// to the API.
|
|
wantErr: true,
|
|
wantErrMsg: "No caches found in OWNER/REPO",
|
|
},
|
|
{
|
|
name: "displays no results when there is a tty",
|
|
tty: true,
|
|
stubs: func(reg *httpmock.Registry) {
|
|
reg.Register(
|
|
httpmock.REST("GET", "repos/OWNER/REPO/actions/caches"),
|
|
httpmock.JSONResponse(shared.CachePayload{
|
|
ActionsCaches: []shared.Cache{},
|
|
TotalCount: 0,
|
|
}),
|
|
)
|
|
},
|
|
wantErr: true,
|
|
wantErrMsg: "No caches found in OWNER/REPO",
|
|
},
|
|
{
|
|
name: "displays list error",
|
|
stubs: func(reg *httpmock.Registry) {
|
|
reg.Register(
|
|
httpmock.REST("GET", "repos/OWNER/REPO/actions/caches"),
|
|
httpmock.StatusStringResponse(404, "Not Found"),
|
|
)
|
|
},
|
|
wantErr: true,
|
|
wantErrMsg: "X Failed to get caches: HTTP 404 (https://api.github.com/repos/OWNER/REPO/actions/caches?per_page=100)",
|
|
},
|
|
{
|
|
name: "calls the exporter when requested",
|
|
opts: ListOptions{
|
|
Exporter: &verboseExporter{},
|
|
},
|
|
stubs: func(reg *httpmock.Registry) {
|
|
reg.Register(
|
|
httpmock.REST("GET", "repos/OWNER/REPO/actions/caches"),
|
|
httpmock.JSONResponse(shared.CachePayload{
|
|
ActionsCaches: []shared.Cache{
|
|
{
|
|
Id: 1,
|
|
Key: "foo",
|
|
CreatedAt: time.Date(2021, 1, 1, 1, 1, 1, 1, time.UTC),
|
|
LastAccessedAt: time.Date(2022, 1, 1, 1, 1, 1, 1, time.UTC),
|
|
SizeInBytes: 100,
|
|
},
|
|
},
|
|
TotalCount: 1,
|
|
}),
|
|
)
|
|
},
|
|
wantErr: false,
|
|
wantStdout: "[{CreatedAt:2021-01-01 01:01:01.000000001 +0000 UTC Id:1 Key:foo LastAccessedAt:2022-01-01 01:01:01.000000001 +0000 UTC Ref: SizeInBytes:100 Version:}]",
|
|
},
|
|
{
|
|
name: "calls the exporter even when there are no results",
|
|
opts: ListOptions{
|
|
Exporter: &verboseExporter{},
|
|
},
|
|
stubs: func(reg *httpmock.Registry) {
|
|
reg.Register(
|
|
httpmock.REST("GET", "repos/OWNER/REPO/actions/caches"),
|
|
httpmock.JSONResponse(shared.CachePayload{
|
|
ActionsCaches: []shared.Cache{},
|
|
TotalCount: 0,
|
|
}),
|
|
)
|
|
},
|
|
wantErr: false,
|
|
wantStdout: "[]",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
reg := &httpmock.Registry{}
|
|
if tt.stubs != nil {
|
|
tt.stubs(reg)
|
|
}
|
|
tt.opts.HttpClient = func() (*http.Client, error) {
|
|
return &http.Client{Transport: reg}, nil
|
|
}
|
|
ios, _, stdout, stderr := iostreams.Test()
|
|
ios.SetStdoutTTY(tt.tty)
|
|
ios.SetStdinTTY(tt.tty)
|
|
ios.SetStderrTTY(tt.tty)
|
|
tt.opts.IO = ios
|
|
tt.opts.Now = now
|
|
tt.opts.BaseRepo = func() (ghrepo.Interface, error) {
|
|
return ghrepo.New("OWNER", "REPO"), nil
|
|
}
|
|
defer reg.Verify(t)
|
|
|
|
err := listRun(&tt.opts)
|
|
if tt.wantErr {
|
|
if tt.wantErrMsg != "" {
|
|
assert.EqualError(t, err, tt.wantErrMsg)
|
|
} else {
|
|
assert.Error(t, err)
|
|
}
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
assert.Equal(t, tt.wantStdout, stdout.String())
|
|
assert.Equal(t, tt.wantStderr, stderr.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
// The verboseExporter just writes data formatted as %+v to stdout.
|
|
// This allows for easy assertion on the data provided to the exporter.
|
|
type verboseExporter struct{}
|
|
|
|
func (e *verboseExporter) Fields() []string {
|
|
return nil
|
|
}
|
|
|
|
func (e *verboseExporter) Write(io *iostreams.IOStreams, data interface{}) error {
|
|
_, err := io.Out.Write([]byte(fmt.Sprintf("%+v", data)))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func Test_humanFileSize(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
size int64
|
|
want string
|
|
}{
|
|
{
|
|
name: "min bytes",
|
|
size: 1,
|
|
want: "1 B",
|
|
},
|
|
{
|
|
name: "max bytes",
|
|
size: 1023,
|
|
want: "1023 B",
|
|
},
|
|
{
|
|
name: "min kibibytes",
|
|
size: 1024,
|
|
want: "1.00 KiB",
|
|
},
|
|
{
|
|
name: "max kibibytes",
|
|
size: 1024*1024 - 1,
|
|
want: "1023.99 KiB",
|
|
},
|
|
{
|
|
name: "min mibibytes",
|
|
size: 1024 * 1024,
|
|
want: "1.00 MiB",
|
|
},
|
|
{
|
|
name: "fractional mibibytes",
|
|
size: 1024*1024*12 + 1024*350,
|
|
want: "12.34 MiB",
|
|
},
|
|
{
|
|
name: "max mibibytes",
|
|
size: 1024*1024*1024 - 1,
|
|
want: "1023.99 MiB",
|
|
},
|
|
{
|
|
name: "min gibibytes",
|
|
size: 1024 * 1024 * 1024,
|
|
want: "1.00 GiB",
|
|
},
|
|
{
|
|
name: "fractional gibibytes",
|
|
size: 1024 * 1024 * 1024 * 1.5,
|
|
want: "1.50 GiB",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := humanFileSize(tt.size); got != tt.want {
|
|
t.Errorf("humanFileSize() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|