1 package api
2
3 import (
4 "bytes"
5 "context"
6 "io"
7 "net/http"
8 "testing"
9 "time"
10
11 "github.com/stretchr/testify/assert"
12 "gopkg.in/h2non/gock.v1"
13 )
14
15 func TestRESTClient(t *testing.T) {
16 stubConfig(t, testConfig())
17 t.Cleanup(gock.Off)
18
19 gock.New("https://api.github.com").
20 Get("/some/test/path").
21 MatchHeader("Authorization", "token abc123").
22 Reply(200).
23 JSON(`{"message": "success"}`)
24
25 client, err := DefaultRESTClient()
26 assert.NoError(t, err)
27
28 res := struct{ Message string }{}
29 err = client.Do("GET", "some/test/path", nil, &res)
30 assert.NoError(t, err)
31 assert.True(t, gock.IsDone(), printPendingMocks(gock.Pending()))
32 assert.Equal(t, "success", res.Message)
33 }
34
35 func TestRESTClientRequest(t *testing.T) {
36 tests := []struct {
37 name string
38 host string
39 path string
40 httpMocks func()
41 wantErr bool
42 wantErrMsg string
43 wantBody string
44 }{
45 {
46 name: "success request empty response",
47 path: "some/test/path",
48 httpMocks: func() {
49 gock.New("https://api.github.com").
50 Get("/some/test/path").
51 Reply(204)
52 },
53 wantBody: ``,
54 },
55 {
56 name: "success request non-empty response",
57 path: "some/test/path",
58 httpMocks: func() {
59 gock.New("https://api.github.com").
60 Get("/some/test/path").
61 Reply(200).
62 JSON(`{"message": "success"}`)
63 },
64 wantBody: `{"message": "success"}`,
65 },
66 {
67 name: "fail request empty response",
68 path: "some/test/path",
69 httpMocks: func() {
70 gock.New("https://api.github.com").
71 Get("/some/test/path").
72 Reply(404).
73 JSON(`{}`)
74 },
75 wantErr: true,
76 wantErrMsg: "HTTP 404 (https://api.github.com/some/test/path)",
77 wantBody: `{}`,
78 },
79 {
80 name: "fail request non-empty response",
81 path: "some/test/path",
82 httpMocks: func() {
83 gock.New("https://api.github.com").
84 Get("/some/test/path").
85 Reply(422).
86 JSON(`{"message": "OH NO"}`)
87 },
88 wantErr: true,
89 wantErrMsg: "HTTP 422: OH NO (https://api.github.com/some/test/path)",
90 wantBody: `{"message": "OH NO"}`,
91 },
92 {
93 name: "support full urls",
94 path: "https://example.com/someother/test/path",
95 httpMocks: func() {
96 gock.New("https://example.com").
97 Get("/someother/test/path").
98 Reply(200).
99 JSON(`{}`)
100 },
101 wantBody: `{}`,
102 },
103 {
104 name: "support enterprise hosts",
105 host: "enterprise.com",
106 path: "some/test/path",
107 httpMocks: func() {
108 gock.New("https://enterprise.com").
109 Get("/some/test/path").
110 Reply(200).
111 JSON(`{}`)
112 },
113 wantBody: `{}`,
114 },
115 }
116
117 for _, tt := range tests {
118 t.Run(tt.name, func(t *testing.T) {
119 t.Cleanup(gock.Off)
120 if tt.host == "" {
121 tt.host = "github.com"
122 }
123 if tt.httpMocks != nil {
124 tt.httpMocks()
125 }
126 client, _ := NewRESTClient(ClientOptions{
127 Host: tt.host,
128 AuthToken: "token",
129 Transport: http.DefaultTransport,
130 })
131
132 resp, err := client.Request("GET", tt.path, nil)
133 if tt.wantErr {
134 assert.EqualError(t, err, tt.wantErrMsg)
135 } else {
136 assert.NoError(t, err)
137 }
138
139 if err == nil {
140 defer resp.Body.Close()
141 body, err := io.ReadAll(resp.Body)
142 assert.NoError(t, err)
143 assert.Equal(t, tt.wantBody, string(body))
144 }
145
146 assert.True(t, gock.IsDone(), printPendingMocks(gock.Pending()))
147 })
148 }
149 }
150
151 func TestRESTClientDo(t *testing.T) {
152 tests := []struct {
153 name string
154 host string
155 path string
156 httpMocks func()
157 wantErr bool
158 wantErrMsg string
159 wantMsg string
160 }{
161 {
162 name: "success request empty response",
163 path: "some/test/path",
164 httpMocks: func() {
165 gock.New("https://api.github.com").
166 Get("/some/test/path").
167 Reply(204).
168 JSON(`{}`)
169 },
170 },
171 {
172 name: "success request non-empty response",
173 path: "some/test/path",
174 httpMocks: func() {
175 gock.New("https://api.github.com").
176 Get("/some/test/path").
177 Reply(200).
178 JSON(`{"message": "success"}`)
179 },
180 wantMsg: "success",
181 },
182 {
183 name: "fail request empty response",
184 path: "some/test/path",
185 httpMocks: func() {
186 gock.New("https://api.github.com").
187 Get("/some/test/path").
188 Reply(404).
189 JSON(`{}`)
190 },
191 wantErr: true,
192 wantErrMsg: "HTTP 404 (https://api.github.com/some/test/path)",
193 },
194 {
195 name: "fail request non-empty response",
196 path: "some/test/path",
197 httpMocks: func() {
198 gock.New("https://api.github.com").
199 Get("/some/test/path").
200 Reply(422).
201 JSON(`{"message": "OH NO"}`)
202 },
203 wantErr: true,
204 wantErrMsg: "HTTP 422: OH NO (https://api.github.com/some/test/path)",
205 },
206 {
207 name: "support full urls",
208 path: "https://example.com/someother/test/path",
209 httpMocks: func() {
210 gock.New("https://example.com").
211 Get("/someother/test/path").
212 Reply(204).
213 JSON(`{}`)
214 },
215 },
216 {
217 name: "support enterprise hosts",
218 host: "enterprise.com",
219 path: "some/test/path",
220 httpMocks: func() {
221 gock.New("https://enterprise.com").
222 Get("/some/test/path").
223 Reply(204).
224 JSON(`{}`)
225 },
226 },
227 }
228
229 for _, tt := range tests {
230 t.Run(tt.name, func(t *testing.T) {
231 t.Cleanup(gock.Off)
232 if tt.host == "" {
233 tt.host = "github.com"
234 }
235 if tt.httpMocks != nil {
236 tt.httpMocks()
237 }
238 client, _ := NewRESTClient(ClientOptions{
239 Host: tt.host,
240 AuthToken: "token",
241 Transport: http.DefaultTransport,
242 })
243 res := struct{ Message string }{}
244 err := client.Do("GET", tt.path, nil, &res)
245 if tt.wantErr {
246 assert.EqualError(t, err, tt.wantErrMsg)
247 } else {
248 assert.NoError(t, err)
249 }
250 assert.True(t, gock.IsDone(), printPendingMocks(gock.Pending()))
251 assert.Equal(t, tt.wantMsg, res.Message)
252 })
253 }
254 }
255
256 func TestRESTClientDelete(t *testing.T) {
257 t.Cleanup(gock.Off)
258 gock.New("https://api.github.com").
259 Delete("/some/path/here").
260 Reply(204).
261 JSON(`{}`)
262 client, _ := NewRESTClient(ClientOptions{
263 Host: "github.com",
264 AuthToken: "token",
265 Transport: http.DefaultTransport,
266 })
267 err := client.Delete("some/path/here", nil)
268 assert.NoError(t, err)
269 assert.True(t, gock.IsDone(), printPendingMocks(gock.Pending()))
270 }
271
272 func TestRESTClientGet(t *testing.T) {
273 t.Cleanup(gock.Off)
274 gock.New("https://api.github.com").
275 Get("/some/path/here").
276 Reply(204).
277 JSON(`{}`)
278 client, _ := NewRESTClient(ClientOptions{
279 Host: "github.com",
280 AuthToken: "token",
281 Transport: http.DefaultTransport,
282 })
283 err := client.Get("some/path/here", nil)
284 assert.NoError(t, err)
285 assert.True(t, gock.IsDone(), printPendingMocks(gock.Pending()))
286 }
287
288 func TestRESTClientPatch(t *testing.T) {
289 t.Cleanup(gock.Off)
290 gock.New("https://api.github.com").
291 Patch("/some/path/here").
292 BodyString(`{}`).
293 Reply(204).
294 JSON(`{}`)
295 client, _ := NewRESTClient(ClientOptions{
296 Host: "github.com",
297 AuthToken: "token",
298 Transport: http.DefaultTransport,
299 })
300 r := bytes.NewReader([]byte(`{}`))
301 err := client.Patch("some/path/here", r, nil)
302 assert.NoError(t, err)
303 assert.True(t, gock.IsDone(), printPendingMocks(gock.Pending()))
304 }
305
306 func TestRESTClientPost(t *testing.T) {
307 t.Cleanup(gock.Off)
308 gock.New("https://api.github.com").
309 Post("/some/path/here").
310 BodyString(`{}`).
311 Reply(204).
312 JSON(`{}`)
313 client, _ := NewRESTClient(ClientOptions{
314 Host: "github.com",
315 AuthToken: "token",
316 Transport: http.DefaultTransport,
317 })
318 r := bytes.NewReader([]byte(`{}`))
319 err := client.Post("some/path/here", r, nil)
320 assert.NoError(t, err)
321 assert.True(t, gock.IsDone(), printPendingMocks(gock.Pending()))
322 }
323
324 func TestRESTClientPut(t *testing.T) {
325 t.Cleanup(gock.Off)
326 gock.New("https://api.github.com").
327 Put("/some/path/here").
328 BodyString(`{}`).
329 Reply(204).
330 JSON(`{}`)
331 client, _ := NewRESTClient(ClientOptions{
332 Host: "github.com",
333 AuthToken: "token",
334 Transport: http.DefaultTransport,
335 })
336 r := bytes.NewReader([]byte(`{}`))
337 err := client.Put("some/path/here", r, nil)
338 assert.NoError(t, err)
339 assert.True(t, gock.IsDone(), printPendingMocks(gock.Pending()))
340 }
341
342 func TestRESTClientDoWithContext(t *testing.T) {
343 tests := []struct {
344 name string
345 wantErrMsg string
346 getCtx func() context.Context
347 }{
348 {
349 name: "http fail request canceled",
350 getCtx: func() context.Context {
351 ctx, cancel := context.WithCancel(context.Background())
352
353 cancel()
354 return ctx
355 },
356 wantErrMsg: `Get "https://api.github.com/some/path": context canceled`,
357 },
358 {
359 name: "http fail request timed out",
360 getCtx: func() context.Context {
361
362 ctx, cancel := context.WithDeadline(context.Background(), time.Now())
363 cancel()
364 return ctx
365 },
366 wantErrMsg: `Get "https://api.github.com/some/path": context deadline exceeded`,
367 },
368 }
369
370 for _, tt := range tests {
371 t.Run(tt.name, func(t *testing.T) {
372
373 t.Cleanup(gock.Off)
374 gock.New("https://api.github.com").
375 Get("/some/path").
376 Reply(204).
377 JSON(`{}`)
378
379 client, _ := NewRESTClient(ClientOptions{
380 Host: "github.com",
381 AuthToken: "token",
382 Transport: http.DefaultTransport,
383 })
384 res := struct{ Message string }{}
385
386
387 ctx := tt.getCtx()
388 gotErr := client.DoWithContext(ctx, http.MethodGet, "some/path", nil, &res)
389
390
391 assert.EqualError(t, gotErr, tt.wantErrMsg)
392 assert.True(t, gock.IsDone(), printPendingMocks(gock.Pending()))
393 })
394 }
395 }
396
397 func TestRESTClientRequestWithContext(t *testing.T) {
398 tests := []struct {
399 name string
400 wantErrMsg string
401 getCtx func() context.Context
402 }{
403 {
404 name: "http fail request canceled",
405 getCtx: func() context.Context {
406 ctx, cancel := context.WithCancel(context.Background())
407
408 cancel()
409 return ctx
410 },
411 wantErrMsg: `Get "https://api.github.com/some/path": context canceled`,
412 },
413 {
414 name: "http fail request timed out",
415 getCtx: func() context.Context {
416
417 ctx, cancel := context.WithDeadline(context.Background(), time.Now())
418 cancel()
419 return ctx
420 },
421 wantErrMsg: `Get "https://api.github.com/some/path": context deadline exceeded`,
422 },
423 }
424
425 for _, tt := range tests {
426 t.Run(tt.name, func(t *testing.T) {
427
428 t.Cleanup(gock.Off)
429 gock.New("https://api.github.com").
430 Get("/some/path").
431 Reply(204).
432 JSON(`{}`)
433
434 client, _ := NewRESTClient(ClientOptions{
435 Host: "github.com",
436 AuthToken: "token",
437 Transport: http.DefaultTransport,
438 })
439
440
441 ctx := tt.getCtx()
442 _, gotErr := client.RequestWithContext(ctx, http.MethodGet, "some/path", nil)
443
444
445 assert.EqualError(t, gotErr, tt.wantErrMsg)
446 assert.True(t, gock.IsDone(), printPendingMocks(gock.Pending()))
447 })
448 }
449 }
450
451 func TestRestPrefix(t *testing.T) {
452 tests := []struct {
453 name string
454 host string
455 wantEndpoint string
456 }{
457 {
458 name: "github",
459 host: "github.com",
460 wantEndpoint: "https://api.github.com/",
461 },
462 {
463 name: "localhost",
464 host: "github.localhost",
465 wantEndpoint: "http://api.github.localhost/",
466 },
467 {
468 name: "garage",
469 host: "garage.github.com",
470 wantEndpoint: "https://garage.github.com/api/v3/",
471 },
472 {
473 name: "enterprise",
474 host: "enterprise.com",
475 wantEndpoint: "https://enterprise.com/api/v3/",
476 },
477 {
478 name: "tenant",
479 host: "tenant.ghe.com",
480 wantEndpoint: "https://api.tenant.ghe.com/",
481 },
482 }
483
484 for _, tt := range tests {
485 t.Run(tt.name, func(t *testing.T) {
486 endpoint := restPrefix(tt.host)
487 assert.Equal(t, tt.wantEndpoint, endpoint)
488 })
489 }
490 }
491
View as plain text