1
2
3
4
5 package resty
6
7 import (
8 "context"
9 "encoding/json"
10 "errors"
11 "net/http"
12 "reflect"
13 "strconv"
14 "strings"
15 "testing"
16 "time"
17 )
18
19 func TestBackoffSuccess(t *testing.T) {
20 attempts := 3
21 externalCounter := 0
22 retryErr := Backoff(func() (*Response, error) {
23 externalCounter++
24 if externalCounter < attempts {
25 return nil, errors.New("not yet got the number we're after")
26 }
27
28 return nil, nil
29 })
30
31 assertError(t, retryErr)
32 assertEqual(t, externalCounter, attempts)
33 }
34
35 func TestBackoffNoWaitForLastRetry(t *testing.T) {
36 attempts := 1
37 externalCounter := 0
38 numRetries := 1
39
40 canceledCtx, cancel := context.WithCancel(context.Background())
41 defer cancel()
42
43 resp := &Response{
44 Request: &Request{
45 ctx: canceledCtx,
46 client: &Client{
47 RetryAfter: func(*Client, *Response) (time.Duration, error) {
48 return 6, nil
49 },
50 },
51 },
52 }
53
54 retryErr := Backoff(func() (*Response, error) {
55 externalCounter++
56 return resp, nil
57 }, RetryConditions([]RetryConditionFunc{func(response *Response, err error) bool {
58 if externalCounter == attempts + numRetries {
59
60 cancel()
61 }
62 return true
63 }}), Retries(numRetries))
64
65 assertNil(t, retryErr)
66 }
67
68 func TestBackoffTenAttemptsSuccess(t *testing.T) {
69 attempts := 10
70 externalCounter := 0
71 retryErr := Backoff(func() (*Response, error) {
72 externalCounter++
73 if externalCounter < attempts {
74 return nil, errors.New("not yet got the number we're after")
75 }
76 return nil, nil
77 }, Retries(attempts), WaitTime(5), MaxWaitTime(500))
78
79 assertError(t, retryErr)
80 assertEqual(t, externalCounter, attempts)
81 }
82
83
84 func TestConditionalBackoffCondition(t *testing.T) {
85 attempts := 3
86 counter := 0
87 check := RetryConditionFunc(func(*Response, error) bool {
88 return attempts != counter
89 })
90 retryErr := Backoff(func() (*Response, error) {
91 counter++
92 return nil, nil
93 }, RetryConditions([]RetryConditionFunc{check}))
94
95 assertError(t, retryErr)
96 assertEqual(t, counter, attempts)
97 }
98
99
100 func TestConditionalBackoffConditionNonExecution(t *testing.T) {
101 attempts := 3
102 counter := 0
103
104 retryErr := Backoff(func() (*Response, error) {
105 counter++
106 return nil, nil
107 }, RetryConditions([]RetryConditionFunc{filler}))
108
109 assertError(t, retryErr)
110 assertNotEqual(t, counter, attempts)
111 }
112
113
114 func TestOnRetryBackoff(t *testing.T) {
115 attempts := 3
116 counter := 0
117
118 hook := func(r *Response, err error) {
119 counter++
120 }
121
122 retryErr := Backoff(func() (*Response, error) {
123 return nil, nil
124 }, RetryHooks([]OnRetryFunc{hook}))
125
126 assertError(t, retryErr)
127 assertNotEqual(t, counter, attempts)
128 }
129
130
131 func TestConditionalGet(t *testing.T) {
132 ts := createGetServer(t)
133 defer ts.Close()
134 attemptCount := 1
135 externalCounter := 0
136
137
138 check := RetryConditionFunc(func(*Response, error) bool {
139 externalCounter++
140 return attemptCount != externalCounter
141 })
142
143 client := dc().AddRetryCondition(check).SetRetryCount(1)
144 resp, err := client.R().
145 SetQueryParam("request_no", strconv.FormatInt(time.Now().Unix(), 10)).
146 Get(ts.URL + "/")
147
148 assertError(t, err)
149 assertEqual(t, http.StatusOK, resp.StatusCode())
150 assertEqual(t, "200 OK", resp.Status())
151 assertNotNil(t, resp.Body())
152 assertEqual(t, "TestGet: text response", resp.String())
153 assertEqual(t, externalCounter, attemptCount)
154
155 logResponse(t, resp)
156 }
157
158
159 func TestConditionalGetDefaultClient(t *testing.T) {
160 ts := createGetServer(t)
161 defer ts.Close()
162 attemptCount := 1
163 externalCounter := 0
164
165
166 check := RetryConditionFunc(func(*Response, error) bool {
167 externalCounter++
168 return attemptCount != externalCounter
169 })
170
171
172 client := dc()
173
174 client.AddRetryCondition(check).SetRetryCount(1)
175 resp, err := client.R().
176 SetQueryParam("request_no", strconv.FormatInt(time.Now().Unix(), 10)).
177 Get(ts.URL + "/")
178
179 assertError(t, err)
180 assertEqual(t, http.StatusOK, resp.StatusCode())
181 assertEqual(t, "200 OK", resp.Status())
182 assertNotNil(t, resp.Body())
183 assertEqual(t, "TestGet: text response", resp.String())
184 assertEqual(t, externalCounter, attemptCount)
185
186 logResponse(t, resp)
187 }
188
189 func TestClientRetryGet(t *testing.T) {
190 ts := createGetServer(t)
191 defer ts.Close()
192
193 c := dc().
194 SetTimeout(time.Second * 3).
195 SetRetryCount(3)
196
197 resp, err := c.R().Get(ts.URL + "/set-retrycount-test")
198 assertEqual(t, "", resp.Status())
199 assertEqual(t, "", resp.Proto())
200 assertEqual(t, 0, resp.StatusCode())
201 assertEqual(t, 0, len(resp.Cookies()))
202 assertNotNil(t, resp.Body())
203 assertEqual(t, 0, len(resp.Header()))
204
205 assertEqual(t, true, strings.HasPrefix(err.Error(), "Get "+ts.URL+"/set-retrycount-test") ||
206 strings.HasPrefix(err.Error(), "Get \""+ts.URL+"/set-retrycount-test\""))
207 }
208
209 func TestClientRetryWait(t *testing.T) {
210 ts := createGetServer(t)
211 defer ts.Close()
212
213 attempt := 0
214
215 retryCount := 5
216 retryIntervals := make([]uint64, retryCount+1)
217
218
219 retryWaitTime := time.Duration(3) * time.Second
220 retryMaxWaitTime := time.Duration(9) * time.Second
221
222 c := dc().
223 SetRetryCount(retryCount).
224 SetRetryWaitTime(retryWaitTime).
225 SetRetryMaxWaitTime(retryMaxWaitTime).
226 AddRetryCondition(
227 func(r *Response, _ error) bool {
228 timeSlept, _ := strconv.ParseUint(string(r.Body()), 10, 64)
229 retryIntervals[attempt] = timeSlept
230 attempt++
231 return true
232 },
233 )
234 _, _ = c.R().Get(ts.URL + "/set-retrywaittime-test")
235
236
237 assertEqual(t, attempt, 6)
238
239
240 assertEqual(t, retryIntervals[0], uint64(0))
241
242 for i := 1; i < len(retryIntervals); i++ {
243 slept := time.Duration(retryIntervals[i])
244
245
246 if slept < retryWaitTime || slept > retryMaxWaitTime {
247 t.Errorf("Client has slept %f seconds before retry %d", slept.Seconds(), i)
248 }
249 }
250 }
251
252 func TestClientRetryWaitMaxInfinite(t *testing.T) {
253 ts := createGetServer(t)
254 defer ts.Close()
255
256 attempt := 0
257
258 retryCount := 5
259 retryIntervals := make([]uint64, retryCount+1)
260
261
262 retryWaitTime := time.Duration(3) * time.Second
263 retryMaxWaitTime := time.Duration(-1.0)
264
265 c := dc().
266 SetRetryCount(retryCount).
267 SetRetryWaitTime(retryWaitTime).
268 SetRetryMaxWaitTime(retryMaxWaitTime).
269 AddRetryCondition(
270 func(r *Response, _ error) bool {
271 timeSlept, _ := strconv.ParseUint(string(r.Body()), 10, 64)
272 retryIntervals[attempt] = timeSlept
273 attempt++
274 return true
275 },
276 )
277 _, _ = c.R().Get(ts.URL + "/set-retrywaittime-test")
278
279
280 assertEqual(t, attempt, 6)
281
282
283 assertEqual(t, retryIntervals[0], uint64(0))
284
285 for i := 1; i < len(retryIntervals); i++ {
286 slept := time.Duration(retryIntervals[i])
287
288
289 if slept < retryWaitTime {
290 t.Errorf("Client has slept %f seconds before retry %d", slept.Seconds(), i)
291 }
292 }
293 }
294
295 func TestClientRetryWaitCallbackError(t *testing.T) {
296 ts := createGetServer(t)
297 defer ts.Close()
298
299 attempt := 0
300
301 retryCount := 5
302 retryIntervals := make([]uint64, retryCount+1)
303
304
305 retryWaitTime := 3 * time.Second
306 retryMaxWaitTime := 9 * time.Second
307
308 retryAfter := func(client *Client, resp *Response) (time.Duration, error) {
309 return 0, errors.New("quota exceeded")
310 }
311
312 c := dc().
313 SetRetryCount(retryCount).
314 SetRetryWaitTime(retryWaitTime).
315 SetRetryMaxWaitTime(retryMaxWaitTime).
316 SetRetryAfter(retryAfter).
317 AddRetryCondition(
318 func(r *Response, _ error) bool {
319 timeSlept, _ := strconv.ParseUint(string(r.Body()), 10, 64)
320 retryIntervals[attempt] = timeSlept
321 attempt++
322 return true
323 },
324 )
325
326 _, err := c.R().Get(ts.URL + "/set-retrywaittime-test")
327
328
329 assertEqual(t, attempt, 1)
330
331
332 assertNotEqual(t, nil, err)
333 }
334
335 func TestClientRetryWaitCallback(t *testing.T) {
336 ts := createGetServer(t)
337 defer ts.Close()
338
339 attempt := 0
340
341 retryCount := 5
342 retryIntervals := make([]uint64, retryCount+1)
343
344
345 retryWaitTime := 3 * time.Second
346 retryMaxWaitTime := 9 * time.Second
347
348 retryAfter := func(client *Client, resp *Response) (time.Duration, error) {
349 return 5 * time.Second, nil
350 }
351
352 c := dc().
353 SetRetryCount(retryCount).
354 SetRetryWaitTime(retryWaitTime).
355 SetRetryMaxWaitTime(retryMaxWaitTime).
356 SetRetryAfter(retryAfter).
357 AddRetryCondition(
358 func(r *Response, _ error) bool {
359 timeSlept, _ := strconv.ParseUint(string(r.Body()), 10, 64)
360 retryIntervals[attempt] = timeSlept
361 attempt++
362 return true
363 },
364 )
365 _, _ = c.R().Get(ts.URL + "/set-retrywaittime-test")
366
367
368 assertEqual(t, attempt, 6)
369
370
371 assertEqual(t, retryIntervals[0], uint64(0))
372
373 for i := 1; i < len(retryIntervals); i++ {
374 slept := time.Duration(retryIntervals[i])
375
376
377 if slept < 5*time.Second-5*time.Millisecond || 5*time.Second+5*time.Millisecond < slept {
378 t.Logf("Client has slept %f seconds before retry %d", slept.Seconds(), i)
379 }
380 }
381 }
382
383 func TestClientRetryWaitCallbackTooShort(t *testing.T) {
384 ts := createGetServer(t)
385 defer ts.Close()
386
387 attempt := 0
388
389 retryCount := 5
390 retryIntervals := make([]uint64, retryCount+1)
391
392
393 retryWaitTime := 3 * time.Second
394 retryMaxWaitTime := 9 * time.Second
395
396 retryAfter := func(client *Client, resp *Response) (time.Duration, error) {
397 return 2 * time.Second, nil
398 }
399
400 c := dc().
401 SetRetryCount(retryCount).
402 SetRetryWaitTime(retryWaitTime).
403 SetRetryMaxWaitTime(retryMaxWaitTime).
404 SetRetryAfter(retryAfter).
405 AddRetryCondition(
406 func(r *Response, _ error) bool {
407 timeSlept, _ := strconv.ParseUint(string(r.Body()), 10, 64)
408 retryIntervals[attempt] = timeSlept
409 attempt++
410 return true
411 },
412 )
413 _, _ = c.R().Get(ts.URL + "/set-retrywaittime-test")
414
415
416 assertEqual(t, attempt, 6)
417
418
419 assertEqual(t, retryIntervals[0], uint64(0))
420
421 for i := 1; i < len(retryIntervals); i++ {
422 slept := time.Duration(retryIntervals[i])
423
424
425 if slept < retryWaitTime-5*time.Millisecond || retryWaitTime+5*time.Millisecond < slept {
426 t.Logf("Client has slept %f seconds before retry %d", slept.Seconds(), i)
427 }
428 }
429 }
430
431 func TestClientRetryWaitCallbackTooLong(t *testing.T) {
432 ts := createGetServer(t)
433 defer ts.Close()
434
435 attempt := 0
436
437 retryCount := 5
438 retryIntervals := make([]uint64, retryCount+1)
439
440
441 retryWaitTime := 1 * time.Second
442 retryMaxWaitTime := 3 * time.Second
443
444 retryAfter := func(client *Client, resp *Response) (time.Duration, error) {
445 return 4 * time.Second, nil
446 }
447
448 c := dc().
449 SetRetryCount(retryCount).
450 SetRetryWaitTime(retryWaitTime).
451 SetRetryMaxWaitTime(retryMaxWaitTime).
452 SetRetryAfter(retryAfter).
453 AddRetryCondition(
454 func(r *Response, _ error) bool {
455 timeSlept, _ := strconv.ParseUint(string(r.Body()), 10, 64)
456 retryIntervals[attempt] = timeSlept
457 attempt++
458 return true
459 },
460 )
461 _, _ = c.R().Get(ts.URL + "/set-retrywaittime-test")
462
463
464 assertEqual(t, attempt, 6)
465
466
467 assertEqual(t, retryIntervals[0], uint64(0))
468
469 for i := 1; i < len(retryIntervals); i++ {
470 slept := time.Duration(retryIntervals[i])
471
472
473 if slept < retryMaxWaitTime-5*time.Millisecond || retryMaxWaitTime+5*time.Millisecond < slept {
474 t.Logf("Client has slept %f seconds before retry %d", slept.Seconds(), i)
475 }
476 }
477 }
478
479 func TestClientRetryWaitCallbackSwitchToDefault(t *testing.T) {
480 ts := createGetServer(t)
481 defer ts.Close()
482
483 attempt := 0
484
485 retryCount := 5
486 retryIntervals := make([]uint64, retryCount+1)
487
488
489 retryWaitTime := 1 * time.Second
490 retryMaxWaitTime := 3 * time.Second
491
492 retryAfter := func(client *Client, resp *Response) (time.Duration, error) {
493 return 0, nil
494 }
495
496 c := dc().
497 EnableTrace().
498 SetRetryCount(retryCount).
499 SetRetryWaitTime(retryWaitTime).
500 SetRetryMaxWaitTime(retryMaxWaitTime).
501 SetRetryAfter(retryAfter).
502 AddRetryCondition(
503 func(r *Response, _ error) bool {
504 timeSlept, _ := strconv.ParseUint(string(r.Body()), 10, 64)
505 retryIntervals[attempt] = timeSlept
506 attempt++
507 return true
508 },
509 )
510 resp, _ := c.R().Get(ts.URL + "/set-retrywaittime-test")
511
512
513 assertEqual(t, attempt, 6)
514 assertEqual(t, resp.Request.Attempt, 6)
515 assertEqual(t, resp.Request.TraceInfo().RequestAttempt, 6)
516
517
518 assertEqual(t, retryIntervals[0], uint64(0))
519
520 for i := 1; i < len(retryIntervals); i++ {
521 slept := time.Duration(retryIntervals[i])
522 expected := (1 << (uint(i - 1))) * time.Second
523 if expected > retryMaxWaitTime {
524 expected = retryMaxWaitTime
525 }
526
527
528
529 if slept < expected/2-5*time.Millisecond || expected+5*time.Millisecond < slept {
530 t.Errorf("Client has slept %f seconds before retry %d", slept.Seconds(), i)
531 }
532 }
533 }
534
535 func TestClientRetryCancel(t *testing.T) {
536 ts := createGetServer(t)
537 defer ts.Close()
538
539 attempt := 0
540
541 retryCount := 5
542 retryIntervals := make([]uint64, retryCount+1)
543
544
545 retryWaitTime := time.Duration(10) * time.Second
546 retryMaxWaitTime := time.Duration(20) * time.Second
547
548 c := dc().
549 SetRetryCount(retryCount).
550 SetRetryWaitTime(retryWaitTime).
551 SetRetryMaxWaitTime(retryMaxWaitTime).
552 AddRetryCondition(
553 func(r *Response, _ error) bool {
554 timeSlept, _ := strconv.ParseUint(string(r.Body()), 10, 64)
555 retryIntervals[attempt] = timeSlept
556 attempt++
557 return true
558 },
559 )
560
561 timeout := 2 * time.Second
562
563 ctx, cancelFunc := context.WithTimeout(context.Background(), timeout)
564 _, _ = c.R().SetContext(ctx).Get(ts.URL + "/set-retrywaittime-test")
565
566
567 assertEqual(t, attempt, 1)
568
569
570 assertEqual(t, retryIntervals[0], uint64(0))
571
572
573 if time.Duration(retryIntervals[1]) > timeout {
574 t.Errorf("Client didn't awake on context cancel")
575 }
576 cancelFunc()
577 }
578
579 func TestClientRetryPost(t *testing.T) {
580 ts := createPostServer(t)
581 defer ts.Close()
582
583 usersmap := map[string]interface{}{
584 "user1": map[string]interface{}{"FirstName": "firstname1", "LastName": "lastname1", "ZipCode": "10001"},
585 }
586
587 var users []map[string]interface{}
588 users = append(users, usersmap)
589
590 c := dc()
591 c.SetRetryCount(3)
592 c.AddRetryCondition(RetryConditionFunc(func(r *Response, _ error) bool {
593 return r.StatusCode() >= http.StatusInternalServerError
594 }))
595
596 resp, _ := c.R().
597 SetBody(&users).
598 Post(ts.URL + "/usersmap?status=500")
599
600 if resp != nil {
601 if resp.StatusCode() == http.StatusInternalServerError {
602 t.Logf("Got response body: %s", string(resp.body))
603 var usersResponse []map[string]interface{}
604 err := json.Unmarshal(resp.body, &usersResponse)
605 assertError(t, err)
606
607 if !reflect.DeepEqual(users, usersResponse) {
608 t.Errorf("Expected request body to be echoed back as response body. Instead got: %s", string(resp.body))
609 }
610
611 return
612 }
613 t.Errorf("Got unexpected response code: %d with body: %s", resp.StatusCode(), string(resp.body))
614 }
615 }
616
617 func TestClientRetryErrorRecover(t *testing.T) {
618 ts := createGetServer(t)
619 defer ts.Close()
620
621 c := dc().
622 SetRetryCount(2).
623 SetError(AuthError{}).
624 AddRetryCondition(
625 func(r *Response, _ error) bool {
626 err, ok := r.Error().(*AuthError)
627 retry := ok && r.StatusCode() == 429 && err.Message == "too many"
628 return retry
629 },
630 )
631
632 resp, err := c.R().
633 SetHeader(hdrContentTypeKey, "application/json; charset=utf-8").
634 SetJSONEscapeHTML(false).
635 SetResult(AuthSuccess{}).
636 Get(ts.URL + "/set-retry-error-recover")
637
638 assertError(t, err)
639
640 authSuccess := resp.Result().(*AuthSuccess)
641
642 assertEqual(t, http.StatusOK, resp.StatusCode())
643 assertEqual(t, "hello", authSuccess.Message)
644
645 assertNil(t, resp.Error())
646 }
647
648 func TestClientRetryCount(t *testing.T) {
649 ts := createGetServer(t)
650 defer ts.Close()
651
652 attempt := 0
653
654 c := dc().
655 SetTimeout(time.Second * 3).
656 SetRetryCount(1).
657 AddRetryCondition(
658 func(r *Response, _ error) bool {
659 attempt++
660 return true
661 },
662 )
663
664 resp, err := c.R().Get(ts.URL + "/set-retrycount-test")
665 assertEqual(t, "", resp.Status())
666 assertEqual(t, "", resp.Proto())
667 assertEqual(t, 0, resp.StatusCode())
668 assertEqual(t, 0, len(resp.Cookies()))
669 assertNotNil(t, resp.Body())
670 assertEqual(t, 0, len(resp.Header()))
671
672
673 assertEqual(t, attempt, 2)
674
675 assertEqual(t, true, strings.HasPrefix(err.Error(), "Get "+ts.URL+"/set-retrycount-test") ||
676 strings.HasPrefix(err.Error(), "Get \""+ts.URL+"/set-retrycount-test\""))
677 }
678
679 func TestClientErrorRetry(t *testing.T) {
680 ts := createGetServer(t)
681 defer ts.Close()
682
683 c := dc().
684 SetTimeout(time.Second * 3).
685 SetRetryCount(1).
686 AddRetryAfterErrorCondition()
687
688 resp, err := c.R().
689 SetHeader(hdrContentTypeKey, "application/json; charset=utf-8").
690 SetJSONEscapeHTML(false).
691 SetResult(AuthSuccess{}).
692 Get(ts.URL + "/set-retry-error-recover")
693
694 assertError(t, err)
695
696 authSuccess := resp.Result().(*AuthSuccess)
697
698 assertEqual(t, http.StatusOK, resp.StatusCode())
699 assertEqual(t, "hello", authSuccess.Message)
700
701 assertNil(t, resp.Error())
702 }
703
704 func TestClientRetryHook(t *testing.T) {
705 ts := createGetServer(t)
706 defer ts.Close()
707
708 attempt := 0
709
710 c := dc().
711 SetRetryCount(2).
712 SetTimeout(time.Second * 3).
713 AddRetryHook(
714 func(r *Response, _ error) {
715 attempt++
716 },
717 )
718
719 resp, err := c.R().Get(ts.URL + "/set-retrycount-test")
720 assertEqual(t, "", resp.Status())
721 assertEqual(t, "", resp.Proto())
722 assertEqual(t, 0, resp.StatusCode())
723 assertEqual(t, 0, len(resp.Cookies()))
724 assertNotNil(t, resp.Body())
725 assertEqual(t, 0, len(resp.Header()))
726
727 assertEqual(t, 3, attempt)
728
729 assertEqual(t, true, strings.HasPrefix(err.Error(), "Get "+ts.URL+"/set-retrycount-test") ||
730 strings.HasPrefix(err.Error(), "Get \""+ts.URL+"/set-retrycount-test\""))
731 }
732
733 func filler(*Response, error) bool {
734 return false
735 }
736
View as plain text