1
2
3
4
5 package resty
6
7 import (
8 "compress/gzip"
9 "encoding/base64"
10 "encoding/json"
11 "encoding/xml"
12 "fmt"
13 "io"
14 "io/ioutil"
15 "net/http"
16 "net/http/httptest"
17 "os"
18 "path/filepath"
19 "reflect"
20 "strconv"
21 "strings"
22 "sync/atomic"
23 "testing"
24 "time"
25 )
26
27
28
29
30
31 func getTestDataPath() string {
32 pwd, _ := os.Getwd()
33 return filepath.Join(pwd, ".testdata")
34 }
35
36 func createGetServer(t *testing.T) *httptest.Server {
37 var attempt int32
38 var sequence int32
39 var lastRequest time.Time
40 ts := createTestServer(func(w http.ResponseWriter, r *http.Request) {
41 t.Logf("Method: %v", r.Method)
42 t.Logf("Path: %v", r.URL.Path)
43
44 if r.Method == MethodGet {
45 switch r.URL.Path {
46 case "/":
47 _, _ = w.Write([]byte("TestGet: text response"))
48 case "/no-content":
49 _, _ = w.Write([]byte(""))
50 case "/json":
51 w.Header().Set("Content-Type", "application/json")
52 _, _ = w.Write([]byte(`{"TestGet": "JSON response"}`))
53 case "/json-invalid":
54 w.Header().Set("Content-Type", "application/json")
55 _, _ = w.Write([]byte("TestGet: Invalid JSON"))
56 case "/long-text":
57 _, _ = w.Write([]byte("TestGet: text response with size > 30"))
58 case "/long-json":
59 w.Header().Set("Content-Type", "application/json")
60 _, _ = w.Write([]byte(`{"TestGet": "JSON response with size > 30"}`))
61 case "/mypage":
62 w.WriteHeader(http.StatusBadRequest)
63 case "/mypage2":
64 _, _ = w.Write([]byte("TestGet: text response from mypage2"))
65 case "/set-retrycount-test":
66 attp := atomic.AddInt32(&attempt, 1)
67 if attp <= 4 {
68 time.Sleep(time.Second * 6)
69 }
70 _, _ = w.Write([]byte("TestClientRetry page"))
71 case "/set-retrywaittime-test":
72
73
74 if atomic.LoadInt32(&attempt) == 0 {
75 lastRequest = time.Now()
76 _, _ = fmt.Fprint(w, "0")
77 } else {
78 now := time.Now()
79 sinceLastRequest := now.Sub(lastRequest)
80 lastRequest = now
81 _, _ = fmt.Fprintf(w, "%d", uint64(sinceLastRequest))
82 }
83 atomic.AddInt32(&attempt, 1)
84
85 case "/set-retry-error-recover":
86 w.Header().Set(hdrContentTypeKey, "application/json; charset=utf-8")
87 if atomic.LoadInt32(&attempt) == 0 {
88 w.WriteHeader(http.StatusTooManyRequests)
89 _, _ = w.Write([]byte(`{ "message": "too many" }`))
90 } else {
91 _, _ = w.Write([]byte(`{ "message": "hello" }`))
92 }
93 atomic.AddInt32(&attempt, 1)
94 case "/set-timeout-test-with-sequence":
95 seq := atomic.AddInt32(&sequence, 1)
96 time.Sleep(time.Second * 2)
97 _, _ = fmt.Fprintf(w, "%d", seq)
98 case "/set-timeout-test":
99 time.Sleep(time.Second * 6)
100 _, _ = w.Write([]byte("TestClientTimeout page"))
101 case "/my-image.png":
102 fileBytes, _ := ioutil.ReadFile(filepath.Join(getTestDataPath(), "test-img.png"))
103 w.Header().Set("Content-Type", "image/png")
104 w.Header().Set("Content-Length", strconv.Itoa(len(fileBytes)))
105 _, _ = w.Write(fileBytes)
106 case "/get-method-payload-test":
107 body, err := ioutil.ReadAll(r.Body)
108 if err != nil {
109 t.Errorf("Error: could not read get body: %s", err.Error())
110 }
111 _, _ = w.Write(body)
112 case "/host-header":
113 _, _ = w.Write([]byte(r.Host))
114 }
115
116 switch {
117 case strings.HasPrefix(r.URL.Path, "/v1/users/sample@sample.com/100002"):
118 if strings.HasSuffix(r.URL.Path, "details") {
119 _, _ = w.Write([]byte("TestGetPathParams: text response: " + r.URL.String()))
120 } else {
121 _, _ = w.Write([]byte("TestPathParamURLInput: text response: " + r.URL.String()))
122 }
123 }
124
125 }
126 })
127
128 return ts
129 }
130
131 func handleLoginEndpoint(t *testing.T, w http.ResponseWriter, r *http.Request) {
132 if r.URL.Path == "/login" {
133 user := &User{}
134
135
136 if IsJSONType(r.Header.Get(hdrContentTypeKey)) {
137 jd := json.NewDecoder(r.Body)
138 err := jd.Decode(user)
139 if r.URL.Query().Get("ct") == "problem" {
140 w.Header().Set(hdrContentTypeKey, "application/problem+json; charset=utf-8")
141 } else if r.URL.Query().Get("ct") == "rpc" {
142 w.Header().Set(hdrContentTypeKey, "application/json-rpc")
143 } else {
144 w.Header().Set(hdrContentTypeKey, "application/json")
145 }
146
147 if err != nil {
148 t.Logf("Error: %#v", err)
149 w.WriteHeader(http.StatusBadRequest)
150 _, _ = w.Write([]byte(`{ "id": "bad_request", "message": "Unable to read user info" }`))
151 return
152 }
153
154 if user.Username == "testuser" && user.Password == "testpass" {
155 _, _ = w.Write([]byte(`{ "id": "success", "message": "login successful" }`))
156 } else if user.Username == "testuser" && user.Password == "invalidjson" {
157 _, _ = w.Write([]byte(`{ "id": "success", "message": "login successful", }`))
158 } else {
159 w.WriteHeader(http.StatusUnauthorized)
160 _, _ = w.Write([]byte(`{ "id": "unauthorized", "message": "Invalid credentials" }`))
161 }
162
163 return
164 }
165
166
167 if IsXMLType(r.Header.Get(hdrContentTypeKey)) {
168 xd := xml.NewDecoder(r.Body)
169 err := xd.Decode(user)
170
171 w.Header().Set(hdrContentTypeKey, "application/xml")
172 if err != nil {
173 t.Logf("Error: %v", err)
174 w.WriteHeader(http.StatusBadRequest)
175 _, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>`))
176 _, _ = w.Write([]byte(`<AuthError><Id>bad_request</Id><Message>Unable to read user info</Message></AuthError>`))
177 return
178 }
179
180 if user.Username == "testuser" && user.Password == "testpass" {
181 _, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>`))
182 _, _ = w.Write([]byte(`<AuthSuccess><Id>success</Id><Message>login successful</Message></AuthSuccess>`))
183 } else if user.Username == "testuser" && user.Password == "invalidxml" {
184 _, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>`))
185 _, _ = w.Write([]byte(`<AuthSuccess><Id>success</Id><Message>login successful</AuthSuccess>`))
186 } else {
187 w.Header().Set("Www-Authenticate", "Protected Realm")
188 w.WriteHeader(http.StatusUnauthorized)
189 _, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>`))
190 _, _ = w.Write([]byte(`<AuthError><Id>unauthorized</Id><Message>Invalid credentials</Message></AuthError>`))
191 }
192
193 return
194 }
195 }
196 }
197
198 func handleUsersEndpoint(t *testing.T, w http.ResponseWriter, r *http.Request) {
199 if r.URL.Path == "/users" {
200
201 if IsJSONType(r.Header.Get(hdrContentTypeKey)) {
202 var users []ExampleUser
203 jd := json.NewDecoder(r.Body)
204 err := jd.Decode(&users)
205 w.Header().Set(hdrContentTypeKey, "application/json")
206 if err != nil {
207 t.Logf("Error: %v", err)
208 w.WriteHeader(http.StatusBadRequest)
209 _, _ = w.Write([]byte(`{ "id": "bad_request", "message": "Unable to read user info" }`))
210 return
211 }
212
213
214 if len(users) != 3 {
215 t.Log("Error: Excepted count of 3 records")
216 w.WriteHeader(http.StatusBadRequest)
217 _, _ = w.Write([]byte(`{ "id": "bad_request", "message": "Expected record count doesn't match" }`))
218 return
219 }
220
221 eu := users[2]
222 if eu.FirstName == "firstname3" && eu.ZipCode == "10003" {
223 w.WriteHeader(http.StatusAccepted)
224 _, _ = w.Write([]byte(`{ "message": "Accepted" }`))
225 }
226
227 return
228 }
229 }
230 }
231
232 func createPostServer(t *testing.T) *httptest.Server {
233 ts := createTestServer(func(w http.ResponseWriter, r *http.Request) {
234 t.Logf("Method: %v", r.Method)
235 t.Logf("Path: %v", r.URL.Path)
236 t.Logf("RawQuery: %v", r.URL.RawQuery)
237 t.Logf("Content-Type: %v", r.Header.Get(hdrContentTypeKey))
238
239 if r.Method == MethodPost {
240 handleLoginEndpoint(t, w, r)
241
242 handleUsersEndpoint(t, w, r)
243
244 if r.URL.Path == "/login-json-html" {
245 w.Header().Set(hdrContentTypeKey, "text/html")
246 w.WriteHeader(http.StatusOK)
247 _, _ = w.Write([]byte(`<htm><body>Test JSON request with HTML response</body></html>`))
248 return
249 }
250
251 if r.URL.Path == "/usersmap" {
252
253 if IsJSONType(r.Header.Get(hdrContentTypeKey)) {
254 if r.URL.Query().Get("status") == "500" {
255 body, err := ioutil.ReadAll(r.Body)
256 if err != nil {
257 t.Errorf("Error: could not read post body: %s", err.Error())
258 }
259 t.Logf("Got query param: status=500 so we're returning the post body as response and a 500 status code. body: %s", string(body))
260 w.Header().Set(hdrContentTypeKey, "application/json; charset=utf-8")
261 w.WriteHeader(http.StatusInternalServerError)
262 _, _ = w.Write(body)
263 return
264 }
265
266 var users []map[string]interface{}
267 jd := json.NewDecoder(r.Body)
268 err := jd.Decode(&users)
269 w.Header().Set(hdrContentTypeKey, "application/json; charset=utf-8")
270 if err != nil {
271 t.Logf("Error: %v", err)
272 w.WriteHeader(http.StatusBadRequest)
273 _, _ = w.Write([]byte(`{ "id": "bad_request", "message": "Unable to read user info" }`))
274 return
275 }
276
277
278 if len(users) != 1 {
279 t.Log("Error: Excepted count of 1 map records")
280 w.WriteHeader(http.StatusBadRequest)
281 _, _ = w.Write([]byte(`{ "id": "bad_request", "message": "Expected record count doesn't match" }`))
282 return
283 }
284
285 w.WriteHeader(http.StatusAccepted)
286 _, _ = w.Write([]byte(`{ "message": "Accepted" }`))
287
288 return
289 }
290 } else if r.URL.Path == "/redirect" {
291 w.Header().Set(hdrLocationKey, "/login")
292 w.WriteHeader(http.StatusTemporaryRedirect)
293 }
294 }
295 })
296
297 return ts
298 }
299
300 func createFormPostServer(t *testing.T) *httptest.Server {
301 ts := createTestServer(func(w http.ResponseWriter, r *http.Request) {
302 t.Logf("Method: %v", r.Method)
303 t.Logf("Path: %v", r.URL.Path)
304 t.Logf("Content-Type: %v", r.Header.Get(hdrContentTypeKey))
305
306 if r.Method == MethodPost {
307 _ = r.ParseMultipartForm(10e6)
308
309 if r.URL.Path == "/profile" {
310 t.Logf("FirstName: %v", r.FormValue("first_name"))
311 t.Logf("LastName: %v", r.FormValue("last_name"))
312 t.Logf("City: %v", r.FormValue("city"))
313 t.Logf("Zip Code: %v", r.FormValue("zip_code"))
314
315 _, _ = w.Write([]byte("Success"))
316 return
317 } else if r.URL.Path == "/search" {
318 formEncodedData := r.Form.Encode()
319 t.Logf("Received Form Encoded values: %v", formEncodedData)
320
321 assertEqual(t, true, strings.Contains(formEncodedData, "search_criteria=pencil"))
322 assertEqual(t, true, strings.Contains(formEncodedData, "search_criteria=glass"))
323
324 _, _ = w.Write([]byte("Success"))
325 return
326 } else if r.URL.Path == "/upload" {
327 t.Logf("FirstName: %v", r.FormValue("first_name"))
328 t.Logf("LastName: %v", r.FormValue("last_name"))
329
330 targetPath := filepath.Join(getTestDataPath(), "upload")
331 _ = os.MkdirAll(targetPath, 0700)
332
333 for _, fhdrs := range r.MultipartForm.File {
334 for _, hdr := range fhdrs {
335 t.Logf("Name: %v", hdr.Filename)
336 t.Logf("Header: %v", hdr.Header)
337 dotPos := strings.LastIndex(hdr.Filename, ".")
338
339 fname := fmt.Sprintf("%s-%v%s", hdr.Filename[:dotPos], time.Now().Unix(), hdr.Filename[dotPos:])
340 t.Logf("Write name: %v", fname)
341
342 infile, _ := hdr.Open()
343 f, err := os.OpenFile(filepath.Join(targetPath, fname), os.O_WRONLY|os.O_CREATE, 0666)
344 if err != nil {
345 t.Logf("Error: %v", err)
346 return
347 }
348 defer func() {
349 _ = f.Close()
350 }()
351 _, _ = io.Copy(f, infile)
352
353 _, _ = w.Write([]byte(fmt.Sprintf("File: %v, uploaded as: %v\n", hdr.Filename, fname)))
354 }
355 }
356
357 return
358 }
359 }
360 })
361
362 return ts
363 }
364
365 func createFilePostServer(t *testing.T) *httptest.Server {
366 ts := createTestServer(func(w http.ResponseWriter, r *http.Request) {
367 t.Logf("Method: %v", r.Method)
368 t.Logf("Path: %v", r.URL.Path)
369 t.Logf("Content-Type: %v", r.Header.Get(hdrContentTypeKey))
370
371 if r.Method != MethodPost {
372 t.Log("createPostServer:: Not a Post request")
373 w.WriteHeader(http.StatusBadRequest)
374 fmt.Fprint(w, http.StatusText(http.StatusBadRequest))
375 return
376 }
377
378 targetPath := filepath.Join(getTestDataPath(), "upload-large")
379 _ = os.MkdirAll(targetPath, 0700)
380 defer cleanupFiles(targetPath)
381
382 switch r.URL.Path {
383 case "/upload":
384 f, err := os.OpenFile(filepath.Join(targetPath, "large-file.png"),
385 os.O_WRONLY|os.O_CREATE, 0666)
386 if err != nil {
387 t.Logf("Error: %v", err)
388 return
389 }
390 defer func() {
391 _ = f.Close()
392 }()
393 size, _ := io.Copy(f, r.Body)
394
395 fmt.Fprintf(w, "File Uploaded successfully, file size: %v", size)
396 }
397 })
398
399 return ts
400 }
401
402 func createAuthServer(t *testing.T) *httptest.Server {
403 ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
404 t.Logf("Method: %v", r.Method)
405 t.Logf("Path: %v", r.URL.Path)
406 t.Logf("Content-Type: %v", r.Header.Get(hdrContentTypeKey))
407
408 if r.Method == MethodGet {
409 if r.URL.Path == "/profile" {
410
411 auth := r.Header.Get("Authorization")
412 t.Logf("Bearer Auth: %v", auth)
413
414 w.Header().Set(hdrContentTypeKey, "application/json; charset=utf-8")
415
416 if !strings.HasPrefix(auth, "Bearer ") {
417 w.Header().Set("Www-Authenticate", "Protected Realm")
418 w.WriteHeader(http.StatusUnauthorized)
419 _, _ = w.Write([]byte(`{ "id": "unauthorized", "message": "Invalid credentials" }`))
420
421 return
422 }
423
424 if auth[7:] == "004DDB79-6801-4587-B976-F093E6AC44FF" || auth[7:] == "004DDB79-6801-4587-B976-F093E6AC44FF-Request" {
425 _, _ = w.Write([]byte(`{ "id": "success", "message": "login successful" }`))
426 }
427 }
428
429 return
430 }
431
432 if r.Method == MethodPost {
433 if r.URL.Path == "/login" {
434 auth := r.Header.Get("Authorization")
435 t.Logf("Basic Auth: %v", auth)
436
437 w.Header().Set(hdrContentTypeKey, "application/json; charset=utf-8")
438
439 password, err := base64.StdEncoding.DecodeString(auth[6:])
440 if err != nil || string(password) != "myuser:basicauth" {
441 w.Header().Set("Www-Authenticate", "Protected Realm")
442 w.WriteHeader(http.StatusUnauthorized)
443 _, _ = w.Write([]byte(`{ "id": "unauthorized", "message": "Invalid credentials" }`))
444
445 return
446 }
447
448 _, _ = w.Write([]byte(`{ "id": "success", "message": "login successful" }`))
449 }
450
451 return
452 }
453 }))
454
455 return ts
456 }
457
458 func createGenServer(t *testing.T) *httptest.Server {
459 ts := createTestServer(func(w http.ResponseWriter, r *http.Request) {
460 t.Logf("Method: %v", r.Method)
461 t.Logf("Path: %v", r.URL.Path)
462
463 if r.Method == MethodGet {
464 if r.URL.Path == "/json-no-set" {
465
466
467 w.Header().Set(hdrContentTypeKey, "")
468 _, _ = w.Write([]byte(`{"response":"json response no content type set"}`))
469 } else if r.URL.Path == "/gzip-test" {
470 w.Header().Set(hdrContentTypeKey, plainTextType)
471 w.Header().Set(hdrContentEncodingKey, "gzip")
472 zw := gzip.NewWriter(w)
473 _, _ = zw.Write([]byte("This is Gzip response testing"))
474 zw.Close()
475 } else if r.URL.Path == "/gzip-test-gziped-empty-body" {
476 w.Header().Set(hdrContentTypeKey, plainTextType)
477 w.Header().Set(hdrContentEncodingKey, "gzip")
478 zw := gzip.NewWriter(w)
479
480 _, _ = zw.Write([]byte(""))
481 zw.Close()
482 } else if r.URL.Path == "/gzip-test-no-gziped-body" {
483 w.Header().Set(hdrContentTypeKey, plainTextType)
484 w.Header().Set(hdrContentEncodingKey, "gzip")
485
486 }
487
488 return
489 }
490
491 if r.Method == MethodPut {
492 if r.URL.Path == "/plaintext" {
493 _, _ = w.Write([]byte("TestPut: plain text response"))
494 } else if r.URL.Path == "/json" {
495 w.Header().Set(hdrContentTypeKey, "application/json; charset=utf-8")
496 _, _ = w.Write([]byte(`{"response":"json response"}`))
497 } else if r.URL.Path == "/xml" {
498 w.Header().Set(hdrContentTypeKey, "application/xml")
499 _, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?><Response>XML response</Response>`))
500 }
501 return
502 }
503
504 if r.Method == MethodOptions && r.URL.Path == "/options" {
505 w.Header().Set("Access-Control-Allow-Origin", "localhost")
506 w.Header().Set("Access-Control-Allow-Methods", "PUT, PATCH")
507 w.Header().Set("Access-Control-Expose-Headers", "x-go-resty-id")
508 w.WriteHeader(http.StatusOK)
509 return
510 }
511
512 if r.Method == MethodPatch && r.URL.Path == "/patch" {
513 w.WriteHeader(http.StatusOK)
514 return
515 }
516
517 if r.Method == "REPORT" && r.URL.Path == "/report" {
518 body, _ := ioutil.ReadAll(r.Body)
519 if len(body) == 0 {
520 w.WriteHeader(http.StatusOK)
521 }
522 return
523 }
524 })
525
526 return ts
527 }
528
529 func createRedirectServer(t *testing.T) *httptest.Server {
530 ts := createTestServer(func(w http.ResponseWriter, r *http.Request) {
531 t.Logf("Method: %v", r.Method)
532 t.Logf("Path: %v", r.URL.Path)
533
534 if r.Method == MethodGet {
535 if strings.HasPrefix(r.URL.Path, "/redirect-host-check-") {
536 cntStr := strings.SplitAfter(r.URL.Path, "-")[3]
537 cnt, _ := strconv.Atoi(cntStr)
538
539 if cnt != 7 {
540 if cnt >= 5 {
541 http.Redirect(w, r, "http://httpbin.org/get", http.StatusTemporaryRedirect)
542 } else {
543 http.Redirect(w, r, fmt.Sprintf("/redirect-host-check-%d", cnt+1), http.StatusTemporaryRedirect)
544 }
545 }
546 } else if strings.HasPrefix(r.URL.Path, "/redirect-") {
547 cntStr := strings.SplitAfter(r.URL.Path, "-")[1]
548 cnt, _ := strconv.Atoi(cntStr)
549
550 http.Redirect(w, r, fmt.Sprintf("/redirect-%d", cnt+1), http.StatusTemporaryRedirect)
551 }
552 }
553 })
554
555 return ts
556 }
557
558 func createTestServer(fn func(w http.ResponseWriter, r *http.Request)) *httptest.Server {
559 return httptest.NewServer(http.HandlerFunc(fn))
560 }
561
562 func dc() *Client {
563 c := New().
564 outputLogTo(ioutil.Discard)
565 return c
566 }
567
568 func dcl() *Client {
569 c := New().
570 SetDebug(true).
571 outputLogTo(ioutil.Discard)
572 return c
573 }
574
575 func dcr() *Request {
576 return dc().R()
577 }
578
579 func dclr() *Request {
580 c := dc().
581 SetDebug(true).
582 outputLogTo(ioutil.Discard)
583 return c.R()
584 }
585
586 func assertNil(t *testing.T, v interface{}) {
587 if !isNil(v) {
588 t.Errorf("[%v] was expected to be nil", v)
589 }
590 }
591
592 func assertNotNil(t *testing.T, v interface{}) {
593 if isNil(v) {
594 t.Errorf("[%v] was expected to be non-nil", v)
595 }
596 }
597
598 func assertType(t *testing.T, typ, v interface{}) {
599 if reflect.DeepEqual(reflect.TypeOf(typ), reflect.TypeOf(v)) {
600 t.Errorf("Expected type %t, got %t", typ, v)
601 }
602 }
603
604 func assertError(t *testing.T, err error) {
605 if err != nil {
606 t.Errorf("Error occurred [%v]", err)
607 }
608 }
609
610 func assertEqual(t *testing.T, e, g interface{}) (r bool) {
611 if !equal(e, g) {
612 t.Errorf("Expected [%v], got [%v]", e, g)
613 }
614
615 return
616 }
617
618 func assertNotEqual(t *testing.T, e, g interface{}) (r bool) {
619 if equal(e, g) {
620 t.Errorf("Expected [%v], got [%v]", e, g)
621 } else {
622 r = true
623 }
624
625 return
626 }
627
628 func equal(expected, got interface{}) bool {
629 return reflect.DeepEqual(expected, got)
630 }
631
632 func isNil(v interface{}) bool {
633 if v == nil {
634 return true
635 }
636
637 rv := reflect.ValueOf(v)
638 kind := rv.Kind()
639 if kind >= reflect.Chan && kind <= reflect.Slice && rv.IsNil() {
640 return true
641 }
642
643 return false
644 }
645
646 func logResponse(t *testing.T, resp *Response) {
647 t.Logf("Response Status: %v", resp.Status())
648 t.Logf("Response Time: %v", resp.Time())
649 t.Logf("Response Headers: %v", resp.Header())
650 t.Logf("Response Cookies: %v", resp.Cookies())
651 t.Logf("Response Body: %v", resp)
652 }
653
654 func cleanupFiles(files ...string) {
655 pwd, _ := os.Getwd()
656
657 for _, f := range files {
658 if filepath.IsAbs(f) {
659 _ = os.RemoveAll(f)
660 } else {
661 _ = os.RemoveAll(filepath.Join(pwd, f))
662 }
663 }
664 }
665
View as plain text