diff --git a/acceptance/testdata/workflow/cache-list-empty.txtar b/acceptance/testdata/workflow/cache-list-empty.txtar new file mode 100644 index 00000000000..0e6d32cb70a --- /dev/null +++ b/acceptance/testdata/workflow/cache-list-empty.txtar @@ -0,0 +1,36 @@ +# It's unclear what we want to do with these acceptance tests beyond our GHEC discovery, so skip new ones by default +skip + +# Set up env vars +env REPO=${ORG}/${SCRIPT_NAME}-${RANDOM_STRING} + +# Create a repository with a file so it has a default branch +exec gh repo create ${REPO} --add-readme --private + +# Defer repo cleanup +defer gh repo delete --yes ${REPO} + +# Set the repo to be targeted by all following commands +env GH_REPO=${REPO} + +# Listing the cache non-interactively shows nothing +exec gh cache list +! stdout '.' + +# Listing the cache non-interactively with --json shows an empty array +exec gh cache list --json id +stdout '\[\]' + +# Now set an env var so the commands run interactively and without colour for stdout matching +# Unfortunately testscript provides no way to turn them off again, and since this +# script is for discovery, we're not adding a new command. +env GH_FORCE_TTY=true +env CLICOLOR=0 + +# Listing the cache interactively shows an informative message on stderr +exec gh cache list +stderr 'No caches found in' + +# Listing the cache interactively with --json shows an empty array +exec gh cache list --json id +stdout '\[\]' diff --git a/pkg/cmd/cache/list/list.go b/pkg/cmd/cache/list/list.go index f5aa8fd5aac..902285df6b4 100644 --- a/pkg/cmd/cache/list/list.go +++ b/pkg/cmd/cache/list/list.go @@ -106,7 +106,7 @@ func listRun(opts *ListOptions) error { return fmt.Errorf("%s Failed to get caches: %w", opts.IO.ColorScheme().FailureIcon(), err) } - if len(result.ActionsCaches) == 0 { + if len(result.ActionsCaches) == 0 && opts.Exporter == nil { return cmdutil.NewNoResultsError(fmt.Sprintf("No caches found in %s", ghrepo.FullName(repo))) } diff --git a/pkg/cmd/cache/list/list_test.go b/pkg/cmd/cache/list/list_test.go index 24d835bcae8..d4810cbcce5 100644 --- a/pkg/cmd/cache/list/list_test.go +++ b/pkg/cmd/cache/list/list_test.go @@ -2,6 +2,7 @@ package list import ( "bytes" + "fmt" "net/http" "testing" "time" @@ -243,7 +244,8 @@ ID KEY SIZE CREATED ACCESSED wantErrMsg: "No caches found in OWNER/REPO", }, { - name: "displays no results", + 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"), @@ -267,6 +269,48 @@ ID KEY SIZE CREATED ACCESSED 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 { @@ -305,6 +349,22 @@ ID KEY SIZE CREATED ACCESSED } } +// 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