Skip to content

Commit f58e0bf

Browse files
committed
Add api tests
1 parent d8146cd commit f58e0bf

File tree

4 files changed

+444
-11
lines changed

4 files changed

+444
-11
lines changed

pkg/cmd/api/api.go

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@ import (
1010
"strings"
1111

1212
"github.com/cli/cli/context"
13+
"github.com/cli/cli/pkg/cmdutil"
14+
"github.com/cli/cli/pkg/iostreams"
1315
"github.com/spf13/cobra"
1416
)
1517

1618
type ApiOptions struct {
19+
IO *iostreams.IOStreams
20+
1721
RequestMethod string
1822
RequestMethodPassed bool
1923
RequestPath string
@@ -54,9 +58,12 @@ on the format of the value:
5458
opts.RequestPath = args[0]
5559
opts.RequestMethodPassed = c.Flags().Changed("method")
5660

61+
// TODO: pass in via caller
62+
opts.IO = iostreams.System()
63+
5764
opts.HttpClient = func() (*http.Client, error) {
5865
ctx := context.New()
59-
token, err := ctx.AuthLogin()
66+
token, err := ctx.AuthToken()
6067
if err != nil {
6168
return nil, err
6269
}
@@ -108,12 +115,16 @@ func apiRun(opts *ApiOptions) error {
108115
}
109116
defer resp.Body.Close()
110117

111-
// TODO: make stdout configurable for tests
112-
_, err = io.Copy(os.Stdout, resp.Body)
118+
_, err = io.Copy(opts.IO.Out, resp.Body)
113119
if err != nil {
114120
return err
115121
}
116122

123+
// TODO: detect GraphQL errors
124+
if resp.StatusCode > 299 {
125+
return cmdutil.SilentError
126+
}
127+
117128
return nil
118129
}
119130

@@ -131,7 +142,7 @@ func parseFields(opts *ApiOptions) (map[string]interface{}, error) {
131142
if err != nil {
132143
return params, err
133144
}
134-
value, err := magicFieldValue(strValue)
145+
value, err := magicFieldValue(strValue, opts.IO.In)
135146
if err != nil {
136147
return params, fmt.Errorf("error parsing %q value: %w", key, err)
137148
}
@@ -148,12 +159,12 @@ func parseField(f string) (string, string, error) {
148159
return f[0:idx], f[idx+1:], nil
149160
}
150161

151-
func magicFieldValue(v string) (interface{}, error) {
162+
func magicFieldValue(v string, stdin io.ReadCloser) (interface{}, error) {
152163
if strings.HasPrefix(v, "@") {
153-
return readUserFile(v[1:])
164+
return readUserFile(v[1:], stdin)
154165
}
155166

156-
if n, err := strconv.Atoi(v); err != nil {
167+
if n, err := strconv.Atoi(v); err == nil {
157168
return n, nil
158169
}
159170

@@ -169,18 +180,17 @@ func magicFieldValue(v string) (interface{}, error) {
169180
}
170181
}
171182

172-
func readUserFile(fn string) ([]byte, error) {
183+
func readUserFile(fn string, stdin io.ReadCloser) ([]byte, error) {
173184
var r io.ReadCloser
174185
if fn == "-" {
175-
// TODO: make stdin configurable for tests
176-
r = os.Stdin
186+
r = stdin
177187
} else {
178188
var err error
179189
r, err = os.Open(fn)
180190
if err != nil {
181191
return nil, err
182192
}
183-
defer r.Close()
184193
}
194+
defer r.Close()
185195
return ioutil.ReadAll(r)
186196
}

pkg/cmd/api/api_test.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package api
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"io/ioutil"
7+
"net/http"
8+
"reflect"
9+
"testing"
10+
11+
"github.com/cli/cli/pkg/cmdutil"
12+
"github.com/cli/cli/pkg/iostreams"
13+
)
14+
15+
func Test_apiRun(t *testing.T) {
16+
tests := []struct {
17+
name string
18+
httpResponse *http.Response
19+
err error
20+
stdout string
21+
stderr string
22+
}{
23+
{
24+
name: "success",
25+
httpResponse: &http.Response{
26+
StatusCode: 200,
27+
Body: ioutil.NopCloser(bytes.NewBufferString(`bam!`)),
28+
},
29+
err: nil,
30+
stdout: `bam!`,
31+
stderr: ``,
32+
},
33+
{
34+
name: "failure",
35+
httpResponse: &http.Response{
36+
StatusCode: 502,
37+
Body: ioutil.NopCloser(bytes.NewBufferString(`gateway timeout`)),
38+
},
39+
err: cmdutil.SilentError,
40+
stdout: `gateway timeout`,
41+
stderr: ``,
42+
},
43+
}
44+
45+
for _, tt := range tests {
46+
t.Run(tt.name, func(t *testing.T) {
47+
io, _, stdout, stderr := iostreams.Test()
48+
49+
opts := ApiOptions{
50+
IO: io,
51+
HttpClient: func() (*http.Client, error) {
52+
var tr roundTripper = func(req *http.Request) (*http.Response, error) {
53+
resp := tt.httpResponse
54+
resp.Request = req
55+
return resp, nil
56+
}
57+
return &http.Client{Transport: tr}, nil
58+
},
59+
60+
RawFields: []string{},
61+
MagicFields: []string{},
62+
}
63+
64+
err := apiRun(&opts)
65+
if err != tt.err {
66+
t.Errorf("expected error %v, got %v", tt.err, err)
67+
}
68+
69+
if stdout.String() != tt.stdout {
70+
t.Errorf("expected output %q, got %q", tt.stdout, stdout.String())
71+
}
72+
if stderr.String() != tt.stderr {
73+
t.Errorf("expected error output %q, got %q", tt.stderr, stderr.String())
74+
}
75+
})
76+
}
77+
}
78+
79+
func Test_parseFields(t *testing.T) {
80+
io, stdin, _, _ := iostreams.Test()
81+
fmt.Fprint(stdin, "pasted contents")
82+
83+
opts := ApiOptions{
84+
IO: io,
85+
RawFields: []string{
86+
"robot=Hubot",
87+
"destroyer=false",
88+
"helper=true",
89+
"location=@work",
90+
},
91+
MagicFields: []string{
92+
"input=@-",
93+
"enabled=true",
94+
"victories=123",
95+
},
96+
}
97+
98+
params, err := parseFields(&opts)
99+
if err != nil {
100+
t.Fatalf("parseFields error: %v", err)
101+
}
102+
103+
expect := map[string]interface{}{
104+
"robot": "Hubot",
105+
"destroyer": "false",
106+
"helper": "true",
107+
"location": "@work",
108+
"input": []byte("pasted contents"),
109+
"enabled": true,
110+
"victories": 123,
111+
}
112+
if !reflect.DeepEqual(params, expect) {
113+
t.Errorf("expected %v, got %v", expect, params)
114+
}
115+
}

pkg/cmd/api/http.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ func httpRequest(client *http.Client, method string, p string, params interface{
3434
}
3535
case io.Reader:
3636
body = pp
37+
case nil:
38+
body = nil
3739
default:
3840
return nil, fmt.Errorf("unrecognized parameters type: %v", params)
3941
}

0 commit comments

Comments
 (0)