1 package api
2
3 import (
4 "context"
5 "errors"
6 "net/http"
7 "testing"
8 "time"
9
10 "github.com/stretchr/testify/assert"
11 "gopkg.in/h2non/gock.v1"
12 )
13
14 func TestGraphQLClient(t *testing.T) {
15 stubConfig(t, testConfig())
16 t.Cleanup(gock.Off)
17
18 gock.New("https://api.github.com").
19 Post("/graphql").
20 MatchHeader("Authorization", "token abc123").
21 BodyString(`{"query":"QUERY","variables":{"var":"test"}}`).
22 Reply(200).
23 JSON(`{"data":{"viewer":{"login":"hubot"}}}`)
24
25 client, err := DefaultGraphQLClient()
26 assert.NoError(t, err)
27
28 vars := map[string]interface{}{"var": "test"}
29 res := struct{ Viewer struct{ Login string } }{}
30 err = client.Do("QUERY", vars, &res)
31 assert.NoError(t, err)
32 assert.True(t, gock.IsDone(), printPendingMocks(gock.Pending()))
33 assert.Equal(t, "hubot", res.Viewer.Login)
34 }
35
36 func TestGraphQLClientDoError(t *testing.T) {
37 stubConfig(t, testConfig())
38 t.Cleanup(gock.Off)
39
40 gock.New("https://api.github.com").
41 Post("/graphql").
42 MatchHeader("Authorization", "token abc123").
43 BodyString(`{"query":"QUERY","variables":null}`).
44 Reply(200).
45 JSON(`{"errors":[{"type":"NOT_FOUND","path":["organization"],"message":"Could not resolve to an Organization with the login of 'cli'."}]}`)
46
47 client, err := DefaultGraphQLClient()
48 assert.NoError(t, err)
49
50 res := struct{ Organization struct{ Name string } }{}
51 err = client.Do("QUERY", nil, &res)
52 var graphQLErr *GraphQLError
53 assert.True(t, errors.As(err, &graphQLErr))
54 assert.EqualError(t, graphQLErr, "GraphQL: Could not resolve to an Organization with the login of 'cli'. (organization)")
55 assert.True(t, gock.IsDone(), printPendingMocks(gock.Pending()))
56 }
57
58 func TestGraphQLClientQueryError(t *testing.T) {
59 stubConfig(t, testConfig())
60 t.Cleanup(gock.Off)
61
62 gock.New("https://api.github.com").
63 Post("/graphql").
64 MatchHeader("Authorization", "token abc123").
65 BodyString(`{"query":"query QUERY{organization{name}}"}`).
66 Reply(200).
67 JSON(`{"errors":[{"type":"NOT_FOUND","path":["organization"],"message":"Could not resolve to an Organization with the login of 'cli'."}]}`)
68
69 client, err := DefaultGraphQLClient()
70 assert.NoError(t, err)
71
72 var res struct{ Organization struct{ Name string } }
73 err = client.Query("QUERY", &res, nil)
74 var graphQLErr *GraphQLError
75 assert.True(t, errors.As(err, &graphQLErr))
76 assert.EqualError(t, graphQLErr, "GraphQL: Could not resolve to an Organization with the login of 'cli'. (organization)")
77 assert.True(t, gock.IsDone(), printPendingMocks(gock.Pending()))
78 }
79
80 func TestGraphQLClientMutateError(t *testing.T) {
81 stubConfig(t, testConfig())
82 t.Cleanup(gock.Off)
83
84 gock.New("https://api.github.com").
85 Post("/graphql").
86 MatchHeader("Authorization", "token abc123").
87 BodyString(`{"query":"mutation MUTATE($input:ID!){updateRepository{repository{name}}}","variables":{"input":"variables"}}`).
88 Reply(200).
89 JSON(`{"errors":[{"type":"NOT_FOUND","path":["organization"],"message":"Could not resolve to an Organization with the login of 'cli'."}]}`)
90
91 client, err := DefaultGraphQLClient()
92 assert.NoError(t, err)
93
94 var mutation struct {
95 UpdateRepository struct{ Repository struct{ Name string } }
96 }
97 variables := map[string]interface{}{"input": "variables"}
98 err = client.Mutate("MUTATE", &mutation, variables)
99 var graphQLErr *GraphQLError
100 assert.True(t, errors.As(err, &graphQLErr))
101 assert.EqualError(t, graphQLErr, "GraphQL: Could not resolve to an Organization with the login of 'cli'. (organization)")
102 assert.True(t, gock.IsDone(), printPendingMocks(gock.Pending()))
103 }
104
105 func TestGraphQLClientDo(t *testing.T) {
106 tests := []struct {
107 name string
108 host string
109 httpMocks func()
110 wantErr bool
111 wantErrMsg string
112 wantLogin string
113 }{
114 {
115 name: "success request",
116 httpMocks: func() {
117 gock.New("https://api.github.com").
118 Post("/graphql").
119 BodyString(`{"query":"QUERY","variables":{"var":"test"}}`).
120 Reply(200).
121 JSON(`{"data":{"viewer":{"login":"hubot"}}}`)
122 },
123 wantLogin: "hubot",
124 },
125 {
126 name: "fail request",
127 httpMocks: func() {
128 gock.New("https://api.github.com").
129 Post("/graphql").
130 BodyString(`{"query":"QUERY","variables":{"var":"test"}}`).
131 Reply(200).
132 JSON(`{"errors":[{"message":"OH NO"},{"message":"this is fine"}]}`)
133 },
134 wantErr: true,
135 wantErrMsg: "GraphQL: OH NO, this is fine",
136 },
137 {
138 name: "http fail request empty response",
139 httpMocks: func() {
140 gock.New("https://api.github.com").
141 Post("/graphql").
142 BodyString(`{"query":"QUERY","variables":{"var":"test"}}`).
143 Reply(404).
144 JSON(`{}`)
145 },
146 wantErr: true,
147 wantErrMsg: "HTTP 404 (https://api.github.com/graphql)",
148 },
149 {
150 name: "http fail request message response",
151 httpMocks: func() {
152 gock.New("https://api.github.com").
153 Post("/graphql").
154 BodyString(`{"query":"QUERY","variables":{"var":"test"}}`).
155 Reply(422).
156 JSON(`{"message": "OH NO"}`)
157 },
158 wantErr: true,
159 wantErrMsg: "HTTP 422: OH NO (https://api.github.com/graphql)",
160 },
161 {
162 name: "http fail request errors response",
163 httpMocks: func() {
164 gock.New("https://api.github.com").
165 Post("/graphql").
166 BodyString(`{"query":"QUERY","variables":{"var":"test"}}`).
167 Reply(502).
168 JSON(`{"errors":[{"message":"Something went wrong"}]}`)
169 },
170 wantErr: true,
171 wantErrMsg: "HTTP 502: Something went wrong (https://api.github.com/graphql)",
172 },
173 {
174 name: "support enterprise hosts",
175 host: "enterprise.com",
176 httpMocks: func() {
177 gock.New("https://enterprise.com").
178 Post("/api/graphql").
179 BodyString(`{"query":"QUERY","variables":{"var":"test"}}`).
180 Reply(200).
181 JSON(`{"data":{"viewer":{"login":"hubot"}}}`)
182 },
183 wantLogin: "hubot",
184 },
185 }
186
187 for _, tt := range tests {
188 t.Run(tt.name, func(t *testing.T) {
189 t.Cleanup(gock.Off)
190 if tt.host == "" {
191 tt.host = "github.com"
192 }
193 if tt.httpMocks != nil {
194 tt.httpMocks()
195 }
196 client, _ := NewGraphQLClient(ClientOptions{
197 Host: tt.host,
198 AuthToken: "token",
199 Transport: http.DefaultTransport,
200 })
201 vars := map[string]interface{}{"var": "test"}
202 res := struct{ Viewer struct{ Login string } }{}
203 err := client.Do("QUERY", vars, &res)
204 if tt.wantErr {
205 assert.EqualError(t, err, tt.wantErrMsg)
206 } else {
207 assert.NoError(t, err)
208 }
209 assert.True(t, gock.IsDone(), printPendingMocks(gock.Pending()))
210 assert.Equal(t, tt.wantLogin, res.Viewer.Login)
211 })
212 }
213 }
214
215 func TestGraphQLClientDoWithContext(t *testing.T) {
216 tests := []struct {
217 name string
218 wantErrMsg string
219 getCtx func() context.Context
220 }{
221 {
222 name: "http fail request canceled",
223 getCtx: func() context.Context {
224 ctx, cancel := context.WithCancel(context.Background())
225
226 cancel()
227 return ctx
228 },
229 wantErrMsg: `Post "https://api.github.com/graphql": context canceled`,
230 },
231 {
232 name: "http fail request timed out",
233 getCtx: func() context.Context {
234
235 ctx, cancel := context.WithDeadline(context.Background(), time.Now())
236 cancel()
237 return ctx
238 },
239 wantErrMsg: `Post "https://api.github.com/graphql": context deadline exceeded`,
240 },
241 }
242
243 for _, tt := range tests {
244 t.Run(tt.name, func(t *testing.T) {
245 t.Cleanup(gock.Off)
246 gock.New("https://api.github.com").
247 Post("/graphql").
248 BodyString(`{"query":"QUERY","variables":{"var":"test"}}`).
249 Reply(200).
250 JSON(`{}`)
251
252 client, _ := NewGraphQLClient(ClientOptions{
253 Host: "github.com",
254 AuthToken: "token",
255 Transport: http.DefaultTransport,
256 })
257
258 vars := map[string]interface{}{"var": "test"}
259 res := struct{ Viewer struct{ Login string } }{}
260
261 ctx := tt.getCtx()
262 gotErr := client.DoWithContext(ctx, "QUERY", vars, &res)
263
264 assert.True(t, gock.IsDone(), printPendingMocks(gock.Pending()))
265 assert.EqualError(t, gotErr, tt.wantErrMsg)
266 })
267 }
268 }
269
270 func TestGraphQLEndpoint(t *testing.T) {
271 tests := []struct {
272 name string
273 host string
274 wantEndpoint string
275 }{
276 {
277 name: "github",
278 host: "github.com",
279 wantEndpoint: "https://api.github.com/graphql",
280 },
281 {
282 name: "localhost",
283 host: "github.localhost",
284 wantEndpoint: "http://api.github.localhost/graphql",
285 },
286 {
287 name: "garage",
288 host: "garage.github.com",
289 wantEndpoint: "https://garage.github.com/api/graphql",
290 },
291 {
292 name: "enterprise",
293 host: "enterprise.com",
294 wantEndpoint: "https://enterprise.com/api/graphql",
295 },
296 {
297 name: "tenant",
298 host: "tenant.ghe.com",
299 wantEndpoint: "https://api.tenant.ghe.com/graphql",
300 },
301 }
302
303 for _, tt := range tests {
304 t.Run(tt.name, func(t *testing.T) {
305 endpoint := graphQLEndpoint(tt.host)
306 assert.Equal(t, tt.wantEndpoint, endpoint)
307 })
308 }
309 }
310
View as plain text