X Tutup
Skip to content

Commit 50c49df

Browse files
authored
Merge pull request cli#3010 from cli/api-cache
Add `api --cache` flag
2 parents 953855c + 69b9aa3 commit 50c49df

File tree

3 files changed

+95
-12
lines changed

3 files changed

+95
-12
lines changed

api/cache.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,13 @@ func (fs *fileStorage) store(key string, res *http.Response) error {
167167
defer f.Close()
168168

169169
var origBody io.ReadCloser
170-
origBody, res.Body = copyStream(res.Body)
171-
defer res.Body.Close()
170+
if res.Body != nil {
171+
origBody, res.Body = copyStream(res.Body)
172+
defer res.Body.Close()
173+
}
172174
err = res.Write(f)
173-
res.Body = origBody
175+
if origBody != nil {
176+
res.Body = origBody
177+
}
174178
return err
175179
}

pkg/cmd/api/api.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ import (
1414
"strconv"
1515
"strings"
1616
"syscall"
17+
"time"
1718

1819
"github.com/MakeNowJust/heredoc"
20+
"github.com/cli/cli/api"
1921
"github.com/cli/cli/internal/ghinstance"
2022
"github.com/cli/cli/internal/ghrepo"
2123
"github.com/cli/cli/pkg/cmdutil"
@@ -38,6 +40,7 @@ type ApiOptions struct {
3840
ShowResponseHeaders bool
3941
Paginate bool
4042
Silent bool
43+
CacheTTL time.Duration
4144

4245
HttpClient func() (*http.Client, error)
4346
BaseRepo func() (ghrepo.Interface, error)
@@ -176,6 +179,7 @@ func NewCmdApi(f *cmdutil.Factory, runF func(*ApiOptions) error) *cobra.Command
176179
cmd.Flags().BoolVar(&opts.Paginate, "paginate", false, "Make additional HTTP requests to fetch all pages of results")
177180
cmd.Flags().StringVar(&opts.RequestInputFile, "input", "", "The `file` to use as body for the HTTP request")
178181
cmd.Flags().BoolVar(&opts.Silent, "silent", false, "Do not print the response body")
182+
cmd.Flags().DurationVar(&opts.CacheTTL, "cache", 0, "Cache the response, e.g. \"3600s\", \"60m\", \"1h\"")
179183
return cmd
180184
}
181185

@@ -219,6 +223,9 @@ func apiRun(opts *ApiOptions) error {
219223
if err != nil {
220224
return err
221225
}
226+
if opts.CacheTTL > 0 {
227+
httpClient = api.NewCachedClient(httpClient, opts.CacheTTL)
228+
}
222229

223230
headersOutputStream := opts.IO.Out
224231
if opts.Silent {

pkg/cmd/api/api_test.go

Lines changed: 81 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import (
77
"io/ioutil"
88
"net/http"
99
"os"
10+
"path/filepath"
1011
"testing"
12+
"time"
1113

1214
"github.com/cli/cli/git"
1315
"github.com/cli/cli/internal/ghrepo"
@@ -42,6 +44,7 @@ func Test_NewCmdApi(t *testing.T) {
4244
ShowResponseHeaders: false,
4345
Paginate: false,
4446
Silent: false,
47+
CacheTTL: 0,
4548
},
4649
wantsErr: false,
4750
},
@@ -60,6 +63,7 @@ func Test_NewCmdApi(t *testing.T) {
6063
ShowResponseHeaders: false,
6164
Paginate: false,
6265
Silent: false,
66+
CacheTTL: 0,
6367
},
6468
wantsErr: false,
6569
},
@@ -78,6 +82,7 @@ func Test_NewCmdApi(t *testing.T) {
7882
ShowResponseHeaders: false,
7983
Paginate: false,
8084
Silent: false,
85+
CacheTTL: 0,
8186
},
8287
wantsErr: false,
8388
},
@@ -96,6 +101,7 @@ func Test_NewCmdApi(t *testing.T) {
96101
ShowResponseHeaders: true,
97102
Paginate: false,
98103
Silent: false,
104+
CacheTTL: 0,
99105
},
100106
wantsErr: false,
101107
},
@@ -114,6 +120,7 @@ func Test_NewCmdApi(t *testing.T) {
114120
ShowResponseHeaders: false,
115121
Paginate: true,
116122
Silent: false,
123+
CacheTTL: 0,
117124
},
118125
wantsErr: false,
119126
},
@@ -132,6 +139,7 @@ func Test_NewCmdApi(t *testing.T) {
132139
ShowResponseHeaders: false,
133140
Paginate: false,
134141
Silent: true,
142+
CacheTTL: 0,
135143
},
136144
wantsErr: false,
137145
},
@@ -155,6 +163,7 @@ func Test_NewCmdApi(t *testing.T) {
155163
ShowResponseHeaders: false,
156164
Paginate: true,
157165
Silent: false,
166+
CacheTTL: 0,
158167
},
159168
wantsErr: false,
160169
},
@@ -178,6 +187,7 @@ func Test_NewCmdApi(t *testing.T) {
178187
ShowResponseHeaders: false,
179188
Paginate: false,
180189
Silent: false,
190+
CacheTTL: 0,
181191
},
182192
wantsErr: false,
183193
},
@@ -201,22 +211,35 @@ func Test_NewCmdApi(t *testing.T) {
201211
ShowResponseHeaders: false,
202212
Paginate: false,
203213
Silent: false,
214+
CacheTTL: 0,
215+
},
216+
wantsErr: false,
217+
},
218+
{
219+
name: "with cache",
220+
cli: "user --cache 5m",
221+
wants: ApiOptions{
222+
Hostname: "",
223+
RequestMethod: "GET",
224+
RequestMethodPassed: false,
225+
RequestPath: "user",
226+
RequestInputFile: "",
227+
RawFields: []string(nil),
228+
MagicFields: []string(nil),
229+
RequestHeaders: []string(nil),
230+
ShowResponseHeaders: false,
231+
Paginate: false,
232+
Silent: false,
233+
CacheTTL: time.Minute * 5,
204234
},
205235
wantsErr: false,
206236
},
207237
}
208238
for _, tt := range tests {
209239
t.Run(tt.name, func(t *testing.T) {
240+
var opts *ApiOptions
210241
cmd := NewCmdApi(f, func(o *ApiOptions) error {
211-
assert.Equal(t, tt.wants.Hostname, o.Hostname)
212-
assert.Equal(t, tt.wants.RequestMethod, o.RequestMethod)
213-
assert.Equal(t, tt.wants.RequestMethodPassed, o.RequestMethodPassed)
214-
assert.Equal(t, tt.wants.RequestPath, o.RequestPath)
215-
assert.Equal(t, tt.wants.RequestInputFile, o.RequestInputFile)
216-
assert.Equal(t, tt.wants.RawFields, o.RawFields)
217-
assert.Equal(t, tt.wants.MagicFields, o.MagicFields)
218-
assert.Equal(t, tt.wants.RequestHeaders, o.RequestHeaders)
219-
assert.Equal(t, tt.wants.ShowResponseHeaders, o.ShowResponseHeaders)
242+
opts = o
220243
return nil
221244
})
222245

@@ -232,6 +255,19 @@ func Test_NewCmdApi(t *testing.T) {
232255
return
233256
}
234257
assert.NoError(t, err)
258+
259+
assert.Equal(t, tt.wants.Hostname, opts.Hostname)
260+
assert.Equal(t, tt.wants.RequestMethod, opts.RequestMethod)
261+
assert.Equal(t, tt.wants.RequestMethodPassed, opts.RequestMethodPassed)
262+
assert.Equal(t, tt.wants.RequestPath, opts.RequestPath)
263+
assert.Equal(t, tt.wants.RequestInputFile, opts.RequestInputFile)
264+
assert.Equal(t, tt.wants.RawFields, opts.RawFields)
265+
assert.Equal(t, tt.wants.MagicFields, opts.MagicFields)
266+
assert.Equal(t, tt.wants.RequestHeaders, opts.RequestHeaders)
267+
assert.Equal(t, tt.wants.ShowResponseHeaders, opts.ShowResponseHeaders)
268+
assert.Equal(t, tt.wants.Paginate, opts.Paginate)
269+
assert.Equal(t, tt.wants.Silent, opts.Silent)
270+
assert.Equal(t, tt.wants.CacheTTL, opts.CacheTTL)
235271
})
236272
}
237273
}
@@ -593,6 +629,42 @@ func Test_apiRun_inputFile(t *testing.T) {
593629
}
594630
}
595631

632+
func Test_apiRun_cache(t *testing.T) {
633+
io, _, stdout, stderr := iostreams.Test()
634+
635+
requestCount := 0
636+
options := ApiOptions{
637+
IO: io,
638+
HttpClient: func() (*http.Client, error) {
639+
var tr roundTripper = func(req *http.Request) (*http.Response, error) {
640+
requestCount++
641+
return &http.Response{
642+
Request: req,
643+
StatusCode: 204,
644+
}, nil
645+
}
646+
return &http.Client{Transport: tr}, nil
647+
},
648+
649+
RequestPath: "issues",
650+
CacheTTL: time.Minute,
651+
}
652+
653+
t.Cleanup(func() {
654+
cacheDir := filepath.Join(os.TempDir(), "gh-cli-cache")
655+
os.RemoveAll(cacheDir)
656+
})
657+
658+
err := apiRun(&options)
659+
assert.NoError(t, err)
660+
err = apiRun(&options)
661+
assert.NoError(t, err)
662+
663+
assert.Equal(t, 1, requestCount)
664+
assert.Equal(t, "", stdout.String(), "stdout")
665+
assert.Equal(t, "", stderr.String(), "stderr")
666+
}
667+
596668
func Test_parseFields(t *testing.T) {
597669
io, stdin, _, _ := iostreams.Test()
598670
fmt.Fprint(stdin, "pasted contents")

0 commit comments

Comments
 (0)
X Tutup