github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/resty/retry_test.go (about)

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