mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
Refactoring the timeout retries into a more general 'retry policy' with support for retrying on HTTP 429 (Too Many Requests) and other responses with a `Retry-After` header The delay between requests is controlled by a combination of a 'backoff' (currently only a simple linear backoff), and honouring the `Retry-After` value (longest delay wins) This makes the 'rate limiter' an optional argument as well, removing the use of `nil` when one isn't supplied Signed-off-by: David Ackroyd <daveo.ackroyd@gmail.com>
125 lines
3.1 KiB
Go
125 lines
3.1 KiB
Go
package request_test
|
|
|
|
import (
|
|
"net"
|
|
"net/http"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
|
)
|
|
|
|
func TestDefaultRetryPolicy(t *testing.T) {
|
|
type args struct {
|
|
Error error
|
|
Response *http.Response
|
|
}
|
|
type want struct {
|
|
Error error
|
|
Retry bool
|
|
}
|
|
testTable := map[string]struct {
|
|
Args args
|
|
Want want
|
|
}{
|
|
"DNS Error": {
|
|
Args: args{Error: &net.DNSError{Err: "fake"}},
|
|
Want: want{Error: &net.DNSError{Err: "fake"}},
|
|
},
|
|
"DNS Timeout": {
|
|
Args: args{Error: &net.DNSError{Err: "fake", IsTimeout: true}},
|
|
Want: want{Retry: true},
|
|
},
|
|
"Too Many Requests": {
|
|
Args: args{Response: &http.Response{StatusCode: http.StatusTooManyRequests}},
|
|
Want: want{Retry: true},
|
|
},
|
|
"Not Found": {
|
|
Args: args{Response: &http.Response{StatusCode: http.StatusNotFound}},
|
|
},
|
|
"Retry After": {
|
|
Args: args{Response: &http.Response{StatusCode: http.StatusTeapot, Header: http.Header{"Retry-After": []string{"0.5"}}}},
|
|
Want: want{Retry: true},
|
|
},
|
|
}
|
|
|
|
for name, tt := range testTable {
|
|
tt := tt
|
|
t.Run(name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
retry, err := request.DefaultRetryPolicy(tt.Args.Response, tt.Args.Error)
|
|
|
|
if exp := tt.Want.Error; exp != nil {
|
|
if !reflect.DeepEqual(err, exp) {
|
|
t.Fatalf("unexpected error\nexp: %#v, got: %#v", exp, err)
|
|
}
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
t.Fatalf("unexpected error\nexp: <nil>, got: %#v", err)
|
|
}
|
|
|
|
if tt.Want.Retry != retry {
|
|
t.Fatalf("incorrect retry flag\nexp: %v, got: %v", tt.Want.Retry, retry)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRetryAfter(t *testing.T) {
|
|
now := time.Date(2020, time.April, 20, 13, 31, 13, 0, time.UTC)
|
|
|
|
type args struct {
|
|
Now time.Time
|
|
Response *http.Response
|
|
}
|
|
type want struct {
|
|
Delay time.Duration
|
|
}
|
|
testTable := map[string]struct {
|
|
Args args
|
|
Want want
|
|
}{
|
|
"No Response": {},
|
|
"Empty Header": {
|
|
Args: args{Response: &http.Response{StatusCode: http.StatusTooManyRequests, Header: http.Header{"Retry-After": []string{""}}}},
|
|
},
|
|
"Partial Seconds": {
|
|
Args: args{Response: &http.Response{StatusCode: http.StatusTooManyRequests, Header: http.Header{"Retry-After": []string{"0.5"}}}},
|
|
},
|
|
"Delay Seconds": {
|
|
Args: args{Response: &http.Response{StatusCode: http.StatusTooManyRequests, Header: http.Header{"Retry-After": []string{"3"}}}},
|
|
Want: want{Delay: 3 * time.Second},
|
|
},
|
|
"Invalid HTTP Date RFC3339": {
|
|
Args: args{
|
|
Now: now,
|
|
Response: &http.Response{StatusCode: http.StatusTeapot, Header: http.Header{"Retry-After": []string{"2020-04-02T13:31:18Z"}}},
|
|
},
|
|
},
|
|
"Valid HTTP Date": {
|
|
Args: args{
|
|
Now: now,
|
|
Response: &http.Response{StatusCode: http.StatusTeapot, Header: http.Header{"Retry-After": []string{"Mon, 20 Apr 2020 13:31:18 GMT"}}},
|
|
},
|
|
Want: want{Delay: 5 * time.Second},
|
|
},
|
|
}
|
|
|
|
for name, tt := range testTable {
|
|
tt := tt
|
|
t.Run(name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
delay := request.RetryAfter(tt.Args.Response, tt.Args.Now)
|
|
|
|
if exp := tt.Want.Delay; delay != exp {
|
|
t.Fatalf("unexpected delay\nexp: %v, got: %v", exp, delay)
|
|
}
|
|
})
|
|
}
|
|
}
|