Merge pull request #3010 from cli/api-cache

Add `api --cache` flag
This commit is contained in:
Mislav Marohnić 2021-03-02 12:47:03 +01:00 committed by GitHub
commit 50c49df41a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 95 additions and 12 deletions

View file

@ -14,8 +14,10 @@ import (
"strconv"
"strings"
"syscall"
"time"
"github.com/MakeNowJust/heredoc"
"github.com/cli/cli/api"
"github.com/cli/cli/internal/ghinstance"
"github.com/cli/cli/internal/ghrepo"
"github.com/cli/cli/pkg/cmdutil"
@ -38,6 +40,7 @@ type ApiOptions struct {
ShowResponseHeaders bool
Paginate bool
Silent bool
CacheTTL time.Duration
HttpClient func() (*http.Client, error)
BaseRepo func() (ghrepo.Interface, error)
@ -176,6 +179,7 @@ func NewCmdApi(f *cmdutil.Factory, runF func(*ApiOptions) error) *cobra.Command
cmd.Flags().BoolVar(&opts.Paginate, "paginate", false, "Make additional HTTP requests to fetch all pages of results")
cmd.Flags().StringVar(&opts.RequestInputFile, "input", "", "The `file` to use as body for the HTTP request")
cmd.Flags().BoolVar(&opts.Silent, "silent", false, "Do not print the response body")
cmd.Flags().DurationVar(&opts.CacheTTL, "cache", 0, "Cache the response, e.g. \"3600s\", \"60m\", \"1h\"")
return cmd
}
@ -219,6 +223,9 @@ func apiRun(opts *ApiOptions) error {
if err != nil {
return err
}
if opts.CacheTTL > 0 {
httpClient = api.NewCachedClient(httpClient, opts.CacheTTL)
}
headersOutputStream := opts.IO.Out
if opts.Silent {

View file

@ -7,7 +7,9 @@ import (
"io/ioutil"
"net/http"
"os"
"path/filepath"
"testing"
"time"
"github.com/cli/cli/git"
"github.com/cli/cli/internal/ghrepo"
@ -42,6 +44,7 @@ func Test_NewCmdApi(t *testing.T) {
ShowResponseHeaders: false,
Paginate: false,
Silent: false,
CacheTTL: 0,
},
wantsErr: false,
},
@ -60,6 +63,7 @@ func Test_NewCmdApi(t *testing.T) {
ShowResponseHeaders: false,
Paginate: false,
Silent: false,
CacheTTL: 0,
},
wantsErr: false,
},
@ -78,6 +82,7 @@ func Test_NewCmdApi(t *testing.T) {
ShowResponseHeaders: false,
Paginate: false,
Silent: false,
CacheTTL: 0,
},
wantsErr: false,
},
@ -96,6 +101,7 @@ func Test_NewCmdApi(t *testing.T) {
ShowResponseHeaders: true,
Paginate: false,
Silent: false,
CacheTTL: 0,
},
wantsErr: false,
},
@ -114,6 +120,7 @@ func Test_NewCmdApi(t *testing.T) {
ShowResponseHeaders: false,
Paginate: true,
Silent: false,
CacheTTL: 0,
},
wantsErr: false,
},
@ -132,6 +139,7 @@ func Test_NewCmdApi(t *testing.T) {
ShowResponseHeaders: false,
Paginate: false,
Silent: true,
CacheTTL: 0,
},
wantsErr: false,
},
@ -155,6 +163,7 @@ func Test_NewCmdApi(t *testing.T) {
ShowResponseHeaders: false,
Paginate: true,
Silent: false,
CacheTTL: 0,
},
wantsErr: false,
},
@ -178,6 +187,7 @@ func Test_NewCmdApi(t *testing.T) {
ShowResponseHeaders: false,
Paginate: false,
Silent: false,
CacheTTL: 0,
},
wantsErr: false,
},
@ -201,22 +211,35 @@ func Test_NewCmdApi(t *testing.T) {
ShowResponseHeaders: false,
Paginate: false,
Silent: false,
CacheTTL: 0,
},
wantsErr: false,
},
{
name: "with cache",
cli: "user --cache 5m",
wants: ApiOptions{
Hostname: "",
RequestMethod: "GET",
RequestMethodPassed: false,
RequestPath: "user",
RequestInputFile: "",
RawFields: []string(nil),
MagicFields: []string(nil),
RequestHeaders: []string(nil),
ShowResponseHeaders: false,
Paginate: false,
Silent: false,
CacheTTL: time.Minute * 5,
},
wantsErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var opts *ApiOptions
cmd := NewCmdApi(f, func(o *ApiOptions) error {
assert.Equal(t, tt.wants.Hostname, o.Hostname)
assert.Equal(t, tt.wants.RequestMethod, o.RequestMethod)
assert.Equal(t, tt.wants.RequestMethodPassed, o.RequestMethodPassed)
assert.Equal(t, tt.wants.RequestPath, o.RequestPath)
assert.Equal(t, tt.wants.RequestInputFile, o.RequestInputFile)
assert.Equal(t, tt.wants.RawFields, o.RawFields)
assert.Equal(t, tt.wants.MagicFields, o.MagicFields)
assert.Equal(t, tt.wants.RequestHeaders, o.RequestHeaders)
assert.Equal(t, tt.wants.ShowResponseHeaders, o.ShowResponseHeaders)
opts = o
return nil
})
@ -232,6 +255,19 @@ func Test_NewCmdApi(t *testing.T) {
return
}
assert.NoError(t, err)
assert.Equal(t, tt.wants.Hostname, opts.Hostname)
assert.Equal(t, tt.wants.RequestMethod, opts.RequestMethod)
assert.Equal(t, tt.wants.RequestMethodPassed, opts.RequestMethodPassed)
assert.Equal(t, tt.wants.RequestPath, opts.RequestPath)
assert.Equal(t, tt.wants.RequestInputFile, opts.RequestInputFile)
assert.Equal(t, tt.wants.RawFields, opts.RawFields)
assert.Equal(t, tt.wants.MagicFields, opts.MagicFields)
assert.Equal(t, tt.wants.RequestHeaders, opts.RequestHeaders)
assert.Equal(t, tt.wants.ShowResponseHeaders, opts.ShowResponseHeaders)
assert.Equal(t, tt.wants.Paginate, opts.Paginate)
assert.Equal(t, tt.wants.Silent, opts.Silent)
assert.Equal(t, tt.wants.CacheTTL, opts.CacheTTL)
})
}
}
@ -593,6 +629,42 @@ func Test_apiRun_inputFile(t *testing.T) {
}
}
func Test_apiRun_cache(t *testing.T) {
io, _, stdout, stderr := iostreams.Test()
requestCount := 0
options := ApiOptions{
IO: io,
HttpClient: func() (*http.Client, error) {
var tr roundTripper = func(req *http.Request) (*http.Response, error) {
requestCount++
return &http.Response{
Request: req,
StatusCode: 204,
}, nil
}
return &http.Client{Transport: tr}, nil
},
RequestPath: "issues",
CacheTTL: time.Minute,
}
t.Cleanup(func() {
cacheDir := filepath.Join(os.TempDir(), "gh-cli-cache")
os.RemoveAll(cacheDir)
})
err := apiRun(&options)
assert.NoError(t, err)
err = apiRun(&options)
assert.NoError(t, err)
assert.Equal(t, 1, requestCount)
assert.Equal(t, "", stdout.String(), "stdout")
assert.Equal(t, "", stderr.String(), "stderr")
}
func Test_parseFields(t *testing.T) {
io, stdin, _, _ := iostreams.Test()
fmt.Fprint(stdin, "pasted contents")