@@ -14,6 +14,7 @@ import (
1414 "sort"
1515 "strings"
1616 "sync"
17+ "time"
1718
1819 "github.com/swaggest/assertjson"
1920 "github.com/swaggest/assertjson/json5"
@@ -50,13 +51,42 @@ type Client struct {
5051 // reqConcurrency is a number of simultaneous requests to send.
5152 reqConcurrency int
5253
54+ retryBackOff RetryBackOff
5355 followRedirects bool
5456
5557 otherRespBody []byte
5658 otherResp * http.Response
5759 otherRespExpected bool
5860}
5961
62+ // RetryBackOff defines retry strategy.
63+ //
64+ // This interface matches github.com/cenkalti/retryBackOff/v4.BackOff.
65+ type RetryBackOff interface {
66+ // NextBackOff returns the duration to wait before retrying the operation,
67+ // or -1 to indicate that no more retries should be made.
68+ //
69+ // Example usage:
70+ //
71+ // duration := retryBackOff.NextBackOff();
72+ // if (duration == retryBackOff.Stop) {
73+ // // Do not retry operation.
74+ // } else {
75+ // // Sleep for duration and retry operation.
76+ // }
77+ //
78+ NextBackOff () time.Duration
79+ }
80+
81+ // RetryBackOffFunc implements RetryBackOff with a function.
82+ type RetryBackOffFunc func () time.Duration
83+
84+ // NextBackOff returns the duration to wait before retrying the operation,
85+ // or -1 to indicate that no more retries should be made.
86+ func (r RetryBackOffFunc ) NextBackOff () time.Duration {
87+ return r ()
88+ }
89+
6090var (
6191 errEmptyBody = errors .New ("received empty body" )
6292 errResponseCardinality = errors .New ("response status cardinality too high" )
@@ -111,6 +141,7 @@ func (c *Client) Reset() *Client {
111141
112142 c .reqConcurrency = 0
113143 c .followRedirects = false
144+ c .retryBackOff = nil
114145
115146 c .otherResp = nil
116147 c .otherRespBody = nil
@@ -151,6 +182,13 @@ func (c *Client) FollowRedirects() *Client {
151182 return c
152183}
153184
185+ // AllowRetries allows sending multiple requests until first response assertion passes.
186+ func (c * Client ) AllowRetries (b RetryBackOff ) * Client {
187+ c .retryBackOff = b
188+
189+ return c
190+ }
191+
154192// WithContext adds context to request.
155193func (c * Client ) WithContext (ctx context.Context ) * Client {
156194 c .ctx = ctx
@@ -286,6 +324,36 @@ func (c *Client) do() (err error) {
286324 return c .checkResponses (statusCodeCount , bodies , resps )
287325}
288326
327+ func (c * Client ) expectResp (check func () error ) (err error ) {
328+ if c .resp != nil {
329+ return check ()
330+ }
331+
332+ if c .retryBackOff != nil {
333+ for {
334+ if err = c .do (); err == nil {
335+ if err = check (); err == nil {
336+ return nil
337+ }
338+ }
339+
340+ dur := c .retryBackOff .NextBackOff ()
341+
342+ if dur == - 1 {
343+ return err
344+ }
345+
346+ time .Sleep (dur )
347+ }
348+ }
349+
350+ if err := c .do (); err != nil {
351+ return err
352+ }
353+
354+ return check ()
355+ }
356+
289357// CheckResponses checks if responses qualify idempotence criteria.
290358//
291359// Operation is considered idempotent in one of two cases:
@@ -453,26 +521,16 @@ func (c *Client) doOnce() (*http.Response, error) {
453521
454522// ExpectResponseStatus sets expected response status code.
455523func (c * Client ) ExpectResponseStatus (statusCode int ) error {
456- if c .resp == nil {
457- err := c .do ()
458- if err != nil {
459- return err
460- }
461- }
462-
463- return c .assertResponseCode (statusCode , c .resp )
524+ return c .expectResp (func () error {
525+ return c .assertResponseCode (statusCode , c .resp )
526+ })
464527}
465528
466529// ExpectResponseHeader asserts expected response header value.
467530func (c * Client ) ExpectResponseHeader (key , value string ) error {
468- if c .resp == nil {
469- err := c .do ()
470- if err != nil {
471- return err
472- }
473- }
474-
475- return c .assertResponseHeader (key , value , c .resp )
531+ return c .expectResp (func () error {
532+ return c .assertResponseHeader (key , value , c .resp )
533+ })
476534}
477535
478536// CheckUnexpectedOtherResponses fails if other responses were present, but not expected with
@@ -492,17 +550,13 @@ func (c *Client) CheckUnexpectedOtherResponses() error {
492550//
493551// Does not affect single (non-concurrent) calls.
494552func (c * Client ) ExpectNoOtherResponses () error {
495- if c .resp == nil {
496- if err := c . do (); err != nil {
497- return err
553+ return c .expectResp ( func () error {
554+ if c . otherResp != nil {
555+ return c . assertResponseCode ( c . resp . StatusCode , c . otherResp )
498556 }
499- }
500-
501- if c .otherResp != nil {
502- return c .assertResponseCode (c .resp .StatusCode , c .otherResp )
503- }
504557
505- return nil
558+ return nil
559+ })
506560}
507561
508562// ExpectOtherResponsesStatus sets expectation for response status to be received one or more times during concurrent
@@ -513,35 +567,27 @@ func (c *Client) ExpectNoOtherResponses() error {
513567func (c * Client ) ExpectOtherResponsesStatus (statusCode int ) error {
514568 c .otherRespExpected = true
515569
516- if c .resp == nil {
517- if err := c . do (); err ! = nil {
518- return err
570+ return c .expectResp ( func () error {
571+ if c . otherResp = = nil {
572+ return errNoOtherResponses
519573 }
520- }
521-
522- if c .otherResp == nil {
523- return errNoOtherResponses
524- }
525574
526- return c .assertResponseCode (statusCode , c .otherResp )
575+ return c .assertResponseCode (statusCode , c .otherResp )
576+ })
527577}
528578
529579// ExpectOtherResponsesHeader sets expectation for response header value to be received one or more times during
530580// concurrent calling.
531581func (c * Client ) ExpectOtherResponsesHeader (key , value string ) error {
532582 c .otherRespExpected = true
533583
534- if c .resp == nil {
535- if err := c . do (); err ! = nil {
536- return err
584+ return c .expectResp ( func () error {
585+ if c . otherResp = = nil {
586+ return errNoOtherResponses
537587 }
538- }
539-
540- if c .otherResp == nil {
541- return errNoOtherResponses
542- }
543588
544- return c .assertResponseHeader (key , value , c .otherResp )
589+ return c .assertResponseHeader (key , value , c .otherResp )
590+ })
545591}
546592
547593func (c * Client ) assertResponseCode (statusCode int , resp * http.Response ) error {
@@ -571,14 +617,9 @@ func (c *Client) assertResponseHeader(key, value string, resp *http.Response) er
571617//
572618// In concurrent mode such response must be met only once or for all calls.
573619func (c * Client ) ExpectResponseBodyCallback (cb func (received []byte ) error ) error {
574- if c .resp == nil {
575- err := c .do ()
576- if err != nil {
577- return err
578- }
579- }
580-
581- return c .checkBody (nil , c .respBody , cb )
620+ return c .expectResp (func () error {
621+ return c .checkBody (nil , c .respBody , cb )
622+ })
582623}
583624
584625// ExpectOtherResponsesBodyCallback sets expectation for response body to be received one or more times during concurrent
@@ -589,32 +630,22 @@ func (c *Client) ExpectResponseBodyCallback(cb func(received []byte) error) erro
589630func (c * Client ) ExpectOtherResponsesBodyCallback (cb func (received []byte ) error ) error {
590631 c .otherRespExpected = true
591632
592- if c .resp == nil {
593- err := c .do ()
594- if err != nil {
595- return err
633+ return c .expectResp (func () error {
634+ if c .otherResp == nil {
635+ return errNoOtherResponses
596636 }
597- }
598-
599- if c .otherResp == nil {
600- return errNoOtherResponses
601- }
602637
603- return c .checkBody (nil , c .otherRespBody , cb )
638+ return c .checkBody (nil , c .otherRespBody , cb )
639+ })
604640}
605641
606642// ExpectResponseBody sets expectation for response body to be received.
607643//
608644// In concurrent mode such response must be met only once or for all calls.
609645func (c * Client ) ExpectResponseBody (body []byte ) error {
610- if c .resp == nil {
611- err := c .do ()
612- if err != nil {
613- return err
614- }
615- }
616-
617- return c .checkBody (body , c .respBody , nil )
646+ return c .expectResp (func () error {
647+ return c .checkBody (body , c .respBody , nil )
648+ })
618649}
619650
620651// ExpectOtherResponsesBody sets expectation for response body to be received one or more times during concurrent
@@ -625,18 +656,13 @@ func (c *Client) ExpectResponseBody(body []byte) error {
625656func (c * Client ) ExpectOtherResponsesBody (body []byte ) error {
626657 c .otherRespExpected = true
627658
628- if c .resp == nil {
629- err := c .do ()
630- if err != nil {
631- return err
659+ return c .expectResp (func () error {
660+ if c .otherResp == nil {
661+ return errNoOtherResponses
632662 }
633- }
634-
635- if c .otherResp == nil {
636- return errNoOtherResponses
637- }
638663
639- return c .checkBody (body , c .otherRespBody , nil )
664+ return c .checkBody (body , c .otherRespBody , nil )
665+ })
640666}
641667
642668func (c * Client ) checkBody (expected , received []byte , cb func (received []byte ) error ) (err error ) {
0 commit comments