@@ -3,9 +3,11 @@ package httpclient
33import (
44 "bytes"
55 "context"
6+ "errors"
67 "io"
78 "net/http"
89 "net/http/httptest"
10+ "net/url"
911 "strings"
1012 "sync/atomic"
1113 "testing"
@@ -510,6 +512,36 @@ func TestCustomHTTPClientHeaderSuccess(t *testing.T) {
510512 assert .Equal (t , "{ \" response\" : \" ok\" }" , string (body ))
511513}
512514
515+ func TestHTTPClientContextTimeout (t * testing.T ) {
516+ client := NewClient (WithHTTPTimeout (1000 * time .Millisecond ))
517+
518+ dummyHandler := func (w http.ResponseWriter , r * http.Request ) {
519+ assert .Equal (t , http .MethodGet , r .Method )
520+ assert .Equal (t , "application/json" , r .Header .Get ("Content-Type" ))
521+ assert .Equal (t , "en" , r .Header .Get ("Accept-Language" ))
522+
523+ time .Sleep (100 * time .Millisecond )
524+
525+ w .WriteHeader (http .StatusOK )
526+ w .Write ([]byte (`{ "response": "ok" }` ))
527+ }
528+
529+ server := httptest .NewServer (http .HandlerFunc (dummyHandler ))
530+ defer server .Close ()
531+
532+ ctxt , cancel := context .WithTimeout (context .Background (), 1 * time .Millisecond )
533+ defer cancel ()
534+
535+ req , err := http .NewRequestWithContext (ctxt , http .MethodGet , server .URL , nil )
536+ require .NoError (t , err )
537+ req .Header .Set ("Content-Type" , "application/json" )
538+ req .Header .Set ("Accept-Language" , "en" )
539+ response , err := client .Do (req )
540+ require .ErrorIs (t , err , context .DeadlineExceeded )
541+ assert .Equal (t , & url.Error {Op : "Get" , URL : server .URL , Err : context .DeadlineExceeded }, err )
542+ require .Nil (t , response )
543+ }
544+
513545func respBody (t * testing.T , response * http.Response ) string {
514546 if response .Body != nil {
515547 defer response .Body .Close ()
@@ -555,7 +587,7 @@ func TestHTTPClientDoContextCancelledDuringRetry(t *testing.T) {
555587
556588 _ , err = client .Do (req )
557589 require .Error (t , err )
558- assert .Contains (t , err . Error (), context .Canceled . Error () )
590+ assert .Equal (t , context .Canceled , err )
559591 assert .Less (t , count .Load (), int32 (noOfRetries + 1 ), "should not have completed all retries due to context cancellation" )
560592}
561593
@@ -624,3 +656,41 @@ func TestHTTPClientDoContextTimeoutDuringRetry(t *testing.T) {
624656 assert .Contains (t , err .Error (), context .DeadlineExceeded .Error ())
625657 assert .Less (t , count .Load (), int32 (noOfRetries + 1 ), "should not have completed all retries due to context timeout" )
626658}
659+
660+ func TestHTTPClientMultiRetryOnTimeout (t * testing.T ) {
661+ noOfRetries := 3
662+ backoffInterval := 4 * time .Millisecond
663+ maximumJitterInterval := 2 * time .Millisecond
664+
665+ client := NewClient (
666+ WithHTTPTimeout (5 * time .Millisecond ),
667+ WithRetryCount (noOfRetries ),
668+ WithRetrier (heimdall .NewRetrier (heimdall .NewConstantBackoff (backoffInterval , maximumJitterInterval ))),
669+ )
670+
671+ dummyHandler := func (w http.ResponseWriter , r * http.Request ) {
672+ time .Sleep (10 * time .Millisecond )
673+ w .WriteHeader (http .StatusOK )
674+ }
675+
676+ server := httptest .NewServer (http .HandlerFunc (dummyHandler ))
677+ defer server .Close ()
678+
679+ req , err := http .NewRequest (http .MethodGet , server .URL , nil )
680+ require .NoError (t , err )
681+
682+ _ , err = client .Do (req )
683+ require .Error (t , err )
684+ assert .ErrorIs (t , err , context .DeadlineExceeded )
685+ mutliErr , ok := err .(interface { Unwrap () []error })
686+ require .True (t , ok )
687+ errs := mutliErr .Unwrap ()
688+ assert .Len (t , errs , noOfRetries + 1 )
689+ for _ , e := range errs {
690+ var urlErr * url.Error
691+ require .True (t , errors .As (e , & urlErr ))
692+ assert .Equal (t , "Get" , urlErr .Op )
693+ assert .Equal (t , server .URL , urlErr .URL )
694+ assert .ErrorIs (t , urlErr .Err , context .DeadlineExceeded )
695+ }
696+ }
0 commit comments