1package autorest
2
3// Copyright 2017 Microsoft Corporation
4//
5//  Licensed under the Apache License, Version 2.0 (the "License");
6//  you may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at
8//
9//      http://www.apache.org/licenses/LICENSE-2.0
10//
11//  Unless required by applicable law or agreed to in writing, software
12//  distributed under the License is distributed on an "AS IS" BASIS,
13//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14//  See the License for the specific language governing permissions and
15//  limitations under the License.
16
17import (
18	"bytes"
19	"context"
20	"fmt"
21	"log"
22	"net/http"
23	"net/http/httptest"
24	"os"
25	"reflect"
26	"sync"
27	"testing"
28	"time"
29
30	"github.com/Azure/go-autorest/autorest/mocks"
31)
32
33func ExampleSendWithSender() {
34	r := mocks.NewResponseWithStatus("202 Accepted", http.StatusAccepted)
35	mocks.SetAcceptedHeaders(r)
36
37	client := mocks.NewSender()
38	client.AppendAndRepeatResponse(r, 10)
39
40	logger := log.New(os.Stdout, "autorest: ", 0)
41	na := NullAuthorizer{}
42
43	req, _ := Prepare(&http.Request{},
44		AsGet(),
45		WithBaseURL("https://microsoft.com/a/b/c/"),
46		na.WithAuthorization())
47
48	r, _ = SendWithSender(client, req,
49		WithLogging(logger),
50		DoErrorIfStatusCode(http.StatusAccepted),
51		DoCloseIfError(),
52		DoRetryForAttempts(5, time.Duration(0)))
53
54	Respond(r,
55		ByDiscardingBody(),
56		ByClosing())
57
58	// Output:
59	// autorest: Sending GET https://microsoft.com/a/b/c/
60	// autorest: GET https://microsoft.com/a/b/c/ received 202 Accepted
61	// autorest: Sending GET https://microsoft.com/a/b/c/
62	// autorest: GET https://microsoft.com/a/b/c/ received 202 Accepted
63	// autorest: Sending GET https://microsoft.com/a/b/c/
64	// autorest: GET https://microsoft.com/a/b/c/ received 202 Accepted
65	// autorest: Sending GET https://microsoft.com/a/b/c/
66	// autorest: GET https://microsoft.com/a/b/c/ received 202 Accepted
67	// autorest: Sending GET https://microsoft.com/a/b/c/
68	// autorest: GET https://microsoft.com/a/b/c/ received 202 Accepted
69}
70
71func ExampleDoRetryForAttempts() {
72	client := mocks.NewSender()
73	client.SetAndRepeatError(fmt.Errorf("Faux Error"), 10)
74
75	// Retry with backoff -- ensure returned Bodies are closed
76	r, _ := SendWithSender(client, mocks.NewRequest(),
77		DoCloseIfError(),
78		DoRetryForAttempts(5, time.Duration(0)))
79
80	Respond(r,
81		ByDiscardingBody(),
82		ByClosing())
83
84	fmt.Printf("Retry stopped after %d attempts", client.Attempts())
85	// Output: Retry stopped after 5 attempts
86}
87
88func ExampleDoErrorIfStatusCode() {
89	client := mocks.NewSender()
90	client.AppendAndRepeatResponse(mocks.NewResponseWithStatus("204 NoContent", http.StatusNoContent), 10)
91
92	// Chain decorators to retry the request, up to five times, if the status code is 204
93	r, _ := SendWithSender(client, mocks.NewRequest(),
94		DoErrorIfStatusCode(http.StatusNoContent),
95		DoCloseIfError(),
96		DoRetryForAttempts(5, time.Duration(0)))
97
98	Respond(r,
99		ByDiscardingBody(),
100		ByClosing())
101
102	fmt.Printf("Retry stopped after %d attempts with code %s", client.Attempts(), r.Status)
103	// Output: Retry stopped after 5 attempts with code 204 NoContent
104}
105
106func TestSendWithSenderRunsDecoratorsInOrder(t *testing.T) {
107	client := mocks.NewSender()
108	s := ""
109
110	r, err := SendWithSender(client, mocks.NewRequest(),
111		withMessage(&s, "a"),
112		withMessage(&s, "b"),
113		withMessage(&s, "c"))
114	if err != nil {
115		t.Fatalf("autorest: SendWithSender returned an error (%v)", err)
116	}
117
118	Respond(r,
119		ByDiscardingBody(),
120		ByClosing())
121
122	if s != "abc" {
123		t.Fatalf("autorest: SendWithSender invoke decorators out of order; expected 'abc', received '%s'", s)
124	}
125}
126
127func TestCreateSender(t *testing.T) {
128	f := false
129
130	s := CreateSender(
131		(func() SendDecorator {
132			return func(s Sender) Sender {
133				return SenderFunc(func(r *http.Request) (*http.Response, error) {
134					f = true
135					return nil, nil
136				})
137			}
138		})())
139	s.Do(&http.Request{})
140
141	if !f {
142		t.Fatal("autorest: CreateSender failed to apply supplied decorator")
143	}
144}
145
146func TestSend(t *testing.T) {
147	f := false
148
149	Send(&http.Request{},
150		(func() SendDecorator {
151			return func(s Sender) Sender {
152				return SenderFunc(func(r *http.Request) (*http.Response, error) {
153					f = true
154					return nil, nil
155				})
156			}
157		})())
158
159	if !f {
160		t.Fatal("autorest: Send failed to apply supplied decorator")
161	}
162}
163
164func TestAfterDelayWaits(t *testing.T) {
165	client := mocks.NewSender()
166
167	d := 2 * time.Second
168
169	tt := time.Now()
170	r, _ := SendWithSender(client, mocks.NewRequest(),
171		AfterDelay(d))
172	s := time.Since(tt)
173	if s < d {
174		t.Fatal("autorest: AfterDelay failed to wait for at least the specified duration")
175	}
176
177	Respond(r,
178		ByDiscardingBody(),
179		ByClosing())
180}
181
182func TestAfterDelay_Cancels(t *testing.T) {
183	client := mocks.NewSender()
184	ctx, cancel := context.WithCancel(context.Background())
185	delay := 5 * time.Second
186
187	var wg sync.WaitGroup
188	wg.Add(1)
189	start := time.Now()
190	end := time.Now()
191	var err error
192	go func() {
193		req := mocks.NewRequest()
194		req = req.WithContext(ctx)
195		_, err = SendWithSender(client, req,
196			AfterDelay(delay))
197		end = time.Now()
198		wg.Done()
199	}()
200	cancel()
201	wg.Wait()
202	time.Sleep(5 * time.Millisecond)
203	if end.Sub(start) >= delay {
204		t.Fatal("autorest: AfterDelay elapsed")
205	}
206	if err == nil {
207		t.Fatal("autorest: AfterDelay didn't cancel")
208	}
209}
210
211func TestAfterDelayDoesNotWaitTooLong(t *testing.T) {
212	client := mocks.NewSender()
213
214	d := 5 * time.Millisecond
215	start := time.Now()
216	r, _ := SendWithSender(client, mocks.NewRequest(),
217		AfterDelay(d))
218
219	if time.Since(start) > (5 * d) {
220		t.Fatal("autorest: AfterDelay waited too long (exceeded 5 times specified duration)")
221	}
222
223	Respond(r,
224		ByDiscardingBody(),
225		ByClosing())
226}
227
228func TestAsIs(t *testing.T) {
229	client := mocks.NewSender()
230
231	r1 := mocks.NewResponse()
232	client.AppendResponse(r1)
233
234	r2, err := SendWithSender(client, mocks.NewRequest(),
235		AsIs())
236	if err != nil {
237		t.Fatalf("autorest: AsIs returned an unexpected error (%v)", err)
238	} else if !reflect.DeepEqual(r1, r2) {
239		t.Fatalf("autorest: AsIs modified the response -- received %v, expected %v", r2, r1)
240	}
241
242	Respond(r1,
243		ByDiscardingBody(),
244		ByClosing())
245	Respond(r2,
246		ByDiscardingBody(),
247		ByClosing())
248}
249
250func TestDoCloseIfError(t *testing.T) {
251	client := mocks.NewSender()
252	client.AppendResponse(mocks.NewResponseWithStatus("400 BadRequest", http.StatusBadRequest))
253
254	r, _ := SendWithSender(client, mocks.NewRequest(),
255		DoErrorIfStatusCode(http.StatusBadRequest),
256		DoCloseIfError())
257
258	if r.Body.(*mocks.Body).IsOpen() {
259		t.Fatal("autorest: Expected DoCloseIfError to close response body -- it was left open")
260	}
261
262	Respond(r,
263		ByDiscardingBody(),
264		ByClosing())
265}
266
267func TestDoCloseIfErrorAcceptsNilResponse(t *testing.T) {
268	client := mocks.NewSender()
269
270	SendWithSender(client, mocks.NewRequest(),
271		(func() SendDecorator {
272			return func(s Sender) Sender {
273				return SenderFunc(func(r *http.Request) (*http.Response, error) {
274					resp, err := s.Do(r)
275					if err != nil {
276						resp.Body.Close()
277					}
278					return nil, fmt.Errorf("Faux Error")
279				})
280			}
281		})(),
282		DoCloseIfError())
283}
284
285func TestDoCloseIfErrorAcceptsNilBody(t *testing.T) {
286	client := mocks.NewSender()
287
288	SendWithSender(client, mocks.NewRequest(),
289		(func() SendDecorator {
290			return func(s Sender) Sender {
291				return SenderFunc(func(r *http.Request) (*http.Response, error) {
292					resp, err := s.Do(r)
293					if err != nil {
294						resp.Body.Close()
295					}
296					resp.Body = nil
297					return resp, fmt.Errorf("Faux Error")
298				})
299			}
300		})(),
301		DoCloseIfError())
302}
303
304func TestDoErrorIfStatusCode(t *testing.T) {
305	client := mocks.NewSender()
306	client.AppendResponse(mocks.NewResponseWithStatus("400 BadRequest", http.StatusBadRequest))
307
308	r, err := SendWithSender(client, mocks.NewRequest(),
309		DoErrorIfStatusCode(http.StatusBadRequest),
310		DoCloseIfError())
311	if err == nil {
312		t.Fatal("autorest: DoErrorIfStatusCode failed to emit an error for passed code")
313	}
314
315	Respond(r,
316		ByDiscardingBody(),
317		ByClosing())
318}
319
320func TestDoErrorIfStatusCodeIgnoresStatusCodes(t *testing.T) {
321	client := mocks.NewSender()
322	client.AppendResponse(newAcceptedResponse())
323
324	r, err := SendWithSender(client, mocks.NewRequest(),
325		DoErrorIfStatusCode(http.StatusBadRequest),
326		DoCloseIfError())
327	if err != nil {
328		t.Fatal("autorest: DoErrorIfStatusCode failed to ignore a status code")
329	}
330
331	Respond(r,
332		ByDiscardingBody(),
333		ByClosing())
334}
335
336func TestDoErrorUnlessStatusCode(t *testing.T) {
337	client := mocks.NewSender()
338	client.AppendResponse(mocks.NewResponseWithStatus("400 BadRequest", http.StatusBadRequest))
339
340	r, err := SendWithSender(client, mocks.NewRequest(),
341		DoErrorUnlessStatusCode(http.StatusAccepted),
342		DoCloseIfError())
343	if err == nil {
344		t.Fatal("autorest: DoErrorUnlessStatusCode failed to emit an error for an unknown status code")
345	}
346
347	Respond(r,
348		ByDiscardingBody(),
349		ByClosing())
350}
351
352func TestDoErrorUnlessStatusCodeIgnoresStatusCodes(t *testing.T) {
353	client := mocks.NewSender()
354	client.AppendResponse(newAcceptedResponse())
355
356	r, err := SendWithSender(client, mocks.NewRequest(),
357		DoErrorUnlessStatusCode(http.StatusAccepted),
358		DoCloseIfError())
359	if err != nil {
360		t.Fatal("autorest: DoErrorUnlessStatusCode emitted an error for a knonwn status code")
361	}
362
363	Respond(r,
364		ByDiscardingBody(),
365		ByClosing())
366}
367
368func TestDoRetryForAttemptsStopsAfterSuccess(t *testing.T) {
369	client := mocks.NewSender()
370
371	r, err := SendWithSender(client, mocks.NewRequest(),
372		DoRetryForAttempts(5, time.Duration(0)))
373	if client.Attempts() != 1 {
374		t.Fatalf("autorest: DoRetryForAttempts failed to stop after success -- expected attempts %v, actual %v",
375			1, client.Attempts())
376	}
377	if err != nil {
378		t.Fatalf("autorest: DoRetryForAttempts returned an unexpected error (%v)", err)
379	}
380
381	Respond(r,
382		ByDiscardingBody(),
383		ByClosing())
384}
385
386func TestDoRetryForAttemptsStopsAfterAttempts(t *testing.T) {
387	client := mocks.NewSender()
388	client.SetAndRepeatError(fmt.Errorf("Faux Error"), 10)
389
390	r, err := SendWithSender(client, mocks.NewRequest(),
391		DoRetryForAttempts(5, time.Duration(0)),
392		DoCloseIfError())
393	if err == nil {
394		t.Fatal("autorest: Mock client failed to emit errors")
395	}
396
397	Respond(r,
398		ByDiscardingBody(),
399		ByClosing())
400
401	if client.Attempts() != 5 {
402		t.Fatal("autorest: DoRetryForAttempts failed to stop after specified number of attempts")
403	}
404}
405
406func TestDoRetryForAttemptsReturnsResponse(t *testing.T) {
407	client := mocks.NewSender()
408	client.SetError(fmt.Errorf("Faux Error"))
409
410	r, err := SendWithSender(client, mocks.NewRequest(),
411		DoRetryForAttempts(1, time.Duration(0)))
412	if err == nil {
413		t.Fatal("autorest: Mock client failed to emit errors")
414	}
415
416	if r == nil {
417		t.Fatal("autorest: DoRetryForAttempts failed to return the underlying response")
418	}
419
420	Respond(r,
421		ByDiscardingBody(),
422		ByClosing())
423}
424
425func TestDoRetryForDurationStopsAfterSuccess(t *testing.T) {
426	client := mocks.NewSender()
427
428	r, err := SendWithSender(client, mocks.NewRequest(),
429		DoRetryForDuration(10*time.Millisecond, time.Duration(0)))
430	if client.Attempts() != 1 {
431		t.Fatalf("autorest: DoRetryForDuration failed to stop after success -- expected attempts %v, actual %v",
432			1, client.Attempts())
433	}
434	if err != nil {
435		t.Fatalf("autorest: DoRetryForDuration returned an unexpected error (%v)", err)
436	}
437
438	Respond(r,
439		ByDiscardingBody(),
440		ByClosing())
441}
442
443func TestDoRetryForDurationStopsAfterDuration(t *testing.T) {
444	client := mocks.NewSender()
445	client.SetAndRepeatError(fmt.Errorf("Faux Error"), -1)
446
447	d := 5 * time.Millisecond
448	start := time.Now()
449	r, err := SendWithSender(client, mocks.NewRequest(),
450		DoRetryForDuration(d, time.Duration(0)),
451		DoCloseIfError())
452	if err == nil {
453		t.Fatal("autorest: Mock client failed to emit errors")
454	}
455
456	if time.Since(start) < d {
457		t.Fatal("autorest: DoRetryForDuration failed stopped too soon")
458	}
459
460	Respond(r,
461		ByDiscardingBody(),
462		ByClosing())
463}
464
465func TestDoRetryForDurationStopsWithinReason(t *testing.T) {
466	client := mocks.NewSender()
467	client.SetAndRepeatError(fmt.Errorf("Faux Error"), -1)
468
469	d := 5 * time.Second
470	start := time.Now()
471	r, err := SendWithSender(client, mocks.NewRequest(),
472		DoRetryForDuration(d, time.Duration(0)),
473		DoCloseIfError())
474	if err == nil {
475		t.Fatal("autorest: Mock client failed to emit errors")
476	}
477
478	if time.Since(start) > (5 * d) {
479		t.Fatal("autorest: DoRetryForDuration failed stopped soon enough (exceeded 5 times specified duration)")
480	}
481
482	Respond(r,
483		ByDiscardingBody(),
484		ByClosing())
485}
486
487func TestDoRetryForDurationReturnsResponse(t *testing.T) {
488	client := mocks.NewSender()
489	client.SetAndRepeatError(fmt.Errorf("Faux Error"), -1)
490
491	r, err := SendWithSender(client, mocks.NewRequest(),
492		DoRetryForDuration(10*time.Millisecond, time.Duration(0)),
493		DoCloseIfError())
494	if err == nil {
495		t.Fatal("autorest: Mock client failed to emit errors")
496	}
497
498	if r == nil {
499		t.Fatal("autorest: DoRetryForDuration failed to return the underlying response")
500	}
501
502	Respond(r,
503		ByDiscardingBody(),
504		ByClosing())
505}
506
507func TestDelayForBackoff(t *testing.T) {
508	d := 2 * time.Second
509	start := time.Now()
510	DelayForBackoff(d, 0, nil)
511	if time.Since(start) < d {
512		t.Fatal("autorest: DelayForBackoff did not delay as long as expected")
513	}
514}
515
516func TestDelayForBackoffWithCap(t *testing.T) {
517	d := 2 * time.Second
518	start := time.Now()
519	DelayForBackoffWithCap(d, 1*time.Second, 0, nil)
520	if time.Since(start) >= d {
521		t.Fatal("autorest: DelayForBackoffWithCap delayed for too long")
522	}
523}
524
525func TestDelayForBackoff_Cancels(t *testing.T) {
526	cancel := make(chan struct{})
527	delay := 5 * time.Second
528
529	var wg sync.WaitGroup
530	wg.Add(1)
531	start := time.Now()
532	go func() {
533		wg.Done()
534		DelayForBackoff(delay, 0, cancel)
535	}()
536	wg.Wait()
537	close(cancel)
538	time.Sleep(5 * time.Millisecond)
539	if time.Since(start) >= delay {
540		t.Fatal("autorest: DelayForBackoff failed to cancel")
541	}
542}
543
544func TestDelayForBackoffWithinReason(t *testing.T) {
545	d := 5 * time.Second
546	maxCoefficient := 2
547	start := time.Now()
548	DelayForBackoff(d, 0, nil)
549	if time.Since(start) > (time.Duration(maxCoefficient) * d) {
550
551		t.Fatalf("autorest: DelayForBackoff delayed too long (exceeded %d times the specified duration)", maxCoefficient)
552	}
553}
554
555func TestDoPollForStatusCodes_IgnoresUnspecifiedStatusCodes(t *testing.T) {
556	client := mocks.NewSender()
557
558	r, _ := SendWithSender(client, mocks.NewRequest(),
559		DoPollForStatusCodes(time.Duration(0), time.Duration(0)))
560
561	if client.Attempts() != 1 {
562		t.Fatalf("autorest: Sender#DoPollForStatusCodes polled for unspecified status code")
563	}
564
565	Respond(r,
566		ByDiscardingBody(),
567		ByClosing())
568}
569
570func TestDoPollForStatusCodes_PollsForSpecifiedStatusCodes(t *testing.T) {
571	client := mocks.NewSender()
572	client.AppendResponse(newAcceptedResponse())
573
574	r, _ := SendWithSender(client, mocks.NewRequest(),
575		DoPollForStatusCodes(time.Millisecond, time.Millisecond, http.StatusAccepted))
576
577	if client.Attempts() != 2 {
578		t.Fatalf("autorest: Sender#DoPollForStatusCodes failed to poll for specified status code")
579	}
580
581	Respond(r,
582		ByDiscardingBody(),
583		ByClosing())
584}
585
586func TestDoPollForStatusCodes_CanBeCanceled(t *testing.T) {
587	cancel := make(chan struct{})
588	delay := 5 * time.Second
589
590	r := mocks.NewResponse()
591	mocks.SetAcceptedHeaders(r)
592	client := mocks.NewSender()
593	client.AppendAndRepeatResponse(r, 100)
594
595	var wg sync.WaitGroup
596	wg.Add(1)
597	start := time.Now()
598	go func() {
599		wg.Done()
600		r, _ := SendWithSender(client, mocks.NewRequest(),
601			DoPollForStatusCodes(time.Millisecond, time.Millisecond, http.StatusAccepted))
602		Respond(r,
603			ByDiscardingBody(),
604			ByClosing())
605	}()
606	wg.Wait()
607	close(cancel)
608	time.Sleep(5 * time.Millisecond)
609	if time.Since(start) >= delay {
610		t.Fatalf("autorest: Sender#DoPollForStatusCodes failed to cancel")
611	}
612}
613
614func TestDoPollForStatusCodes_ClosesAllNonreturnedResponseBodiesWhenPolling(t *testing.T) {
615	resp := newAcceptedResponse()
616
617	client := mocks.NewSender()
618	client.AppendAndRepeatResponse(resp, 2)
619
620	r, _ := SendWithSender(client, mocks.NewRequest(),
621		DoPollForStatusCodes(time.Millisecond, time.Millisecond, http.StatusAccepted))
622
623	if resp.Body.(*mocks.Body).IsOpen() || resp.Body.(*mocks.Body).CloseAttempts() < 2 {
624		t.Fatalf("autorest: Sender#DoPollForStatusCodes did not close unreturned response bodies")
625	}
626
627	Respond(r,
628		ByDiscardingBody(),
629		ByClosing())
630}
631
632func TestDoPollForStatusCodes_LeavesLastResponseBodyOpen(t *testing.T) {
633	client := mocks.NewSender()
634	client.AppendResponse(newAcceptedResponse())
635
636	r, _ := SendWithSender(client, mocks.NewRequest(),
637		DoPollForStatusCodes(time.Millisecond, time.Millisecond, http.StatusAccepted))
638
639	if !r.Body.(*mocks.Body).IsOpen() {
640		t.Fatalf("autorest: Sender#DoPollForStatusCodes did not leave open the body of the last response")
641	}
642
643	Respond(r,
644		ByDiscardingBody(),
645		ByClosing())
646}
647
648func TestDoPollForStatusCodes_StopsPollingAfterAnError(t *testing.T) {
649	client := mocks.NewSender()
650	client.AppendAndRepeatResponse(newAcceptedResponse(), 5)
651	client.SetError(fmt.Errorf("Faux Error"))
652	client.SetEmitErrorAfter(1)
653
654	r, _ := SendWithSender(client, mocks.NewRequest(),
655		DoPollForStatusCodes(time.Millisecond, time.Millisecond, http.StatusAccepted))
656
657	if client.Attempts() > 2 {
658		t.Fatalf("autorest: Sender#DoPollForStatusCodes failed to stop polling after receiving an error")
659	}
660
661	Respond(r,
662		ByDiscardingBody(),
663		ByClosing())
664}
665
666func TestDoPollForStatusCodes_ReturnsPollingError(t *testing.T) {
667	client := mocks.NewSender()
668	client.AppendAndRepeatResponse(newAcceptedResponse(), 5)
669	client.SetError(fmt.Errorf("Faux Error"))
670	client.SetEmitErrorAfter(1)
671
672	r, err := SendWithSender(client, mocks.NewRequest(),
673		DoPollForStatusCodes(time.Millisecond, time.Millisecond, http.StatusAccepted))
674
675	if err == nil {
676		t.Fatalf("autorest: Sender#DoPollForStatusCodes failed to return error from polling")
677	}
678
679	Respond(r,
680		ByDiscardingBody(),
681		ByClosing())
682}
683
684func TestWithLogging_Logs(t *testing.T) {
685	buf := &bytes.Buffer{}
686	logger := log.New(buf, "autorest: ", 0)
687	client := mocks.NewSender()
688
689	r, _ := SendWithSender(client, &http.Request{},
690		WithLogging(logger))
691
692	if buf.String() == "" {
693		t.Fatal("autorest: Sender#WithLogging failed to log the request")
694	}
695
696	Respond(r,
697		ByDiscardingBody(),
698		ByClosing())
699}
700
701func TestWithLogging_HandlesMissingResponse(t *testing.T) {
702	buf := &bytes.Buffer{}
703	logger := log.New(buf, "autorest: ", 0)
704	client := mocks.NewSender()
705	client.AppendResponse(nil)
706	client.SetError(fmt.Errorf("Faux Error"))
707
708	r, err := SendWithSender(client, &http.Request{},
709		WithLogging(logger))
710
711	if r != nil || err == nil {
712		t.Fatal("autorest: Sender#WithLogging returned a valid response -- expecting nil")
713	}
714	if buf.String() == "" {
715		t.Fatal("autorest: Sender#WithLogging failed to log the request for a nil response")
716	}
717
718	Respond(r,
719		ByDiscardingBody(),
720		ByClosing())
721}
722
723func TestDoRetryForStatusCodesWithSuccess(t *testing.T) {
724	client := mocks.NewSender()
725	client.AppendAndRepeatResponse(mocks.NewResponseWithStatus("408 Request Timeout", http.StatusRequestTimeout), 2)
726	client.AppendResponse(mocks.NewResponseWithStatus("200 OK", http.StatusOK))
727
728	r, _ := SendWithSender(client, mocks.NewRequest(),
729		DoRetryForStatusCodes(5, time.Duration(2*time.Second), http.StatusRequestTimeout),
730	)
731
732	Respond(r,
733		ByDiscardingBody(),
734		ByClosing())
735
736	if client.Attempts() != 3 {
737		t.Fatalf("autorest: Sender#DoRetryForStatusCodes -- Got: StatusCode %v in %v attempts; Want: StatusCode 200 OK in 2 attempts -- ",
738			r.Status, client.Attempts()-1)
739	}
740}
741
742func TestDoRetryForStatusCodesWithNoSuccess(t *testing.T) {
743	client := mocks.NewSender()
744	client.AppendAndRepeatResponse(mocks.NewResponseWithStatus("504 Gateway Timeout", http.StatusGatewayTimeout), 5)
745
746	r, _ := SendWithSender(client, mocks.NewRequest(),
747		DoRetryForStatusCodes(2, time.Duration(2*time.Second), http.StatusGatewayTimeout),
748	)
749	Respond(r,
750		ByDiscardingBody(),
751		ByClosing())
752
753	if client.Attempts() != 3 {
754		t.Fatalf("autorest: Sender#DoRetryForStatusCodes -- Got: failed stop after %v retry attempts; Want: Stop after 2 retry attempts",
755			client.Attempts()-1)
756	}
757}
758
759func TestDoRetryForStatusCodes_CodeNotInRetryList(t *testing.T) {
760	client := mocks.NewSender()
761	client.AppendAndRepeatResponse(mocks.NewResponseWithStatus("204 No Content", http.StatusNoContent), 1)
762
763	r, _ := SendWithSender(client, mocks.NewRequest(),
764		DoRetryForStatusCodes(6, time.Duration(2*time.Second), http.StatusGatewayTimeout),
765	)
766
767	Respond(r,
768		ByDiscardingBody(),
769		ByClosing())
770
771	if client.Attempts() != 1 || r.Status != "204 No Content" {
772		t.Fatalf("autorest: Sender#DoRetryForStatusCodes -- Got: Retry attempts %v for StatusCode %v; Want: 0 attempts for StatusCode 204",
773			client.Attempts(), r.Status)
774	}
775}
776
777func TestDoRetryForStatusCodes_RequestBodyReadError(t *testing.T) {
778	client := mocks.NewSender()
779	client.AppendAndRepeatResponse(mocks.NewResponseWithStatus("204 No Content", http.StatusNoContent), 2)
780
781	r, err := SendWithSender(client, mocks.NewRequestWithCloseBody(),
782		DoRetryForStatusCodes(6, time.Duration(2*time.Second), http.StatusGatewayTimeout),
783	)
784
785	Respond(r,
786		ByDiscardingBody(),
787		ByClosing())
788
789	if err == nil || client.Attempts() != 0 {
790		t.Fatalf("autorest: Sender#DoRetryForStatusCodes -- Got: Not failed for request body read error; Want: Failed for body read error - %v", err)
791	}
792}
793
794func newAcceptedResponse() *http.Response {
795	resp := mocks.NewResponseWithStatus("202 Accepted", http.StatusAccepted)
796	mocks.SetAcceptedHeaders(resp)
797	return resp
798}
799
800func TestDelayWithRetryAfterWithSuccess(t *testing.T) {
801	Count429AsRetry = false
802	defer func() { Count429AsRetry = true }()
803	after, retries := 2, 2
804	totalSecs := after * retries
805
806	client := mocks.NewSender()
807	resp := mocks.NewResponseWithStatus("429 Too many requests", http.StatusTooManyRequests)
808	mocks.SetResponseHeader(resp, "Retry-After", fmt.Sprintf("%v", after))
809	client.AppendAndRepeatResponse(resp, retries)
810	client.AppendResponse(mocks.NewResponseWithStatus("200 OK", http.StatusOK))
811
812	d := time.Second * time.Duration(totalSecs)
813	start := time.Now()
814	r, _ := SendWithSender(client, mocks.NewRequest(),
815		DoRetryForStatusCodes(1, time.Duration(time.Second), http.StatusTooManyRequests),
816	)
817
818	if time.Since(start) < d {
819		t.Fatal("autorest: DelayWithRetryAfter failed stopped too soon")
820	}
821
822	Respond(r,
823		ByDiscardingBody(),
824		ByClosing())
825
826	if r.StatusCode != http.StatusOK {
827		t.Fatalf("autorest: Sender#DelayWithRetryAfterWithSuccess -- got status code %d, wanted 200", r.StatusCode)
828	}
829	if client.Attempts() != 3 {
830		t.Fatalf("autorest: Sender#DelayWithRetryAfterWithSuccess -- Got: StatusCode %v in %v attempts; Want: StatusCode 200 OK in 2 attempts -- ",
831			r.Status, client.Attempts()-1)
832	}
833}
834
835func TestDelayWithRetryAfterWithFail(t *testing.T) {
836	after, retries := 2, 2
837	totalSecs := after * retries
838
839	client := mocks.NewSender()
840	resp := mocks.NewResponseWithStatus("429 Too many requests", http.StatusTooManyRequests)
841	mocks.SetResponseHeader(resp, "Retry-After", fmt.Sprintf("%v", after))
842	client.AppendAndRepeatResponse(resp, retries)
843	client.AppendResponse(mocks.NewResponseWithStatus("200 OK", http.StatusOK))
844
845	d := time.Second * time.Duration(totalSecs)
846	start := time.Now()
847	r, _ := SendWithSender(client, mocks.NewRequest(),
848		DoRetryForStatusCodes(1, time.Duration(time.Second), http.StatusTooManyRequests),
849	)
850
851	if time.Since(start) < d {
852		t.Fatal("autorest: DelayWithRetryAfter failed stopped too soon")
853	}
854
855	Respond(r,
856		ByDiscardingBody(),
857		ByClosing())
858
859	if r.StatusCode != http.StatusTooManyRequests {
860		t.Fatalf("autorest: Sender#DelayWithRetryAfterWithFail -- got status code %d, wanted 429", r.StatusCode)
861	}
862	if client.Attempts() != 2 {
863		t.Fatalf("autorest: Sender#DelayWithRetryAfterWithFail -- Got: StatusCode %v in %v attempts; Want: StatusCode 429 OK in 1 attempt -- ",
864			r.Status, client.Attempts()-1)
865	}
866}
867
868func TestDelayWithRetryAfterWithSuccessDateTime(t *testing.T) {
869	resumeAt := time.Now().Add(2 * time.Second).Round(time.Second)
870
871	client := mocks.NewSender()
872	resp := mocks.NewResponseWithStatus("503 Service temporarily unavailable", http.StatusServiceUnavailable)
873	mocks.SetResponseHeader(resp, "Retry-After", resumeAt.Format(time.RFC1123))
874	client.AppendResponse(resp)
875	client.AppendResponse(mocks.NewResponseWithStatus("200 OK", http.StatusOK))
876
877	r, _ := SendWithSender(client, mocks.NewRequest(),
878		DoRetryForStatusCodes(1, time.Duration(time.Second), http.StatusServiceUnavailable),
879	)
880
881	if time.Now().Before(resumeAt) {
882		t.Fatal("autorest: DelayWithRetryAfter failed stopped too soon")
883	}
884
885	Respond(r,
886		ByDiscardingBody(),
887		ByClosing())
888
889	if client.Attempts() != 2 {
890		t.Fatalf("autorest: Sender#DelayWithRetryAfter -- Got: StatusCode %v in %v attempts; Want: StatusCode 200 OK in 2 attempts -- ",
891			r.Status, client.Attempts()-1)
892	}
893}
894
895type temporaryError struct {
896	message string
897}
898
899func (te temporaryError) Error() string {
900	return te.message
901}
902
903func (te temporaryError) Timeout() bool {
904	return true
905}
906
907func (te temporaryError) Temporary() bool {
908	return true
909}
910
911func TestDoRetryForStatusCodes_NilResponseTemporaryError(t *testing.T) {
912	client := mocks.NewSender()
913	client.AppendResponse(nil)
914	client.SetError(temporaryError{message: "faux error"})
915
916	r, err := SendWithSender(client, mocks.NewRequest(),
917		DoRetryForStatusCodes(3, time.Duration(1*time.Second), StatusCodesForRetry...),
918	)
919
920	Respond(r,
921		ByDiscardingBody(),
922		ByClosing())
923
924	if err != nil || client.Attempts() != 2 {
925		t.Fatalf("autorest: Sender#TestDoRetryForStatusCodes_NilResponseTemporaryError -- Got: non-nil error or wrong number of attempts - %v", err)
926	}
927}
928
929func TestDoRetryForStatusCodes_NilResponseTemporaryError2(t *testing.T) {
930	client := mocks.NewSender()
931	client.AppendResponse(nil)
932	client.SetError(fmt.Errorf("faux error"))
933
934	r, err := SendWithSender(client, mocks.NewRequest(),
935		DoRetryForStatusCodes(3, time.Duration(1*time.Second), StatusCodesForRetry...),
936	)
937
938	Respond(r,
939		ByDiscardingBody(),
940		ByClosing())
941
942	if err != nil || client.Attempts() != 2 {
943		t.Fatalf("autorest: Sender#TestDoRetryForStatusCodes_NilResponseTemporaryError2 -- Got: nil error or wrong number of attempts - %v", err)
944	}
945}
946
947type fatalError struct {
948	message string
949}
950
951func (fe fatalError) Error() string {
952	return fe.message
953}
954
955func (fe fatalError) Timeout() bool {
956	return false
957}
958
959func (fe fatalError) Temporary() bool {
960	return false
961}
962
963func TestDoRetryForStatusCodes_NilResponseFatalError(t *testing.T) {
964	const retryAttempts = 3
965	client := mocks.NewSender()
966	client.AppendAndRepeatResponse(nil, retryAttempts+1)
967	client.SetAndRepeatError(fatalError{"fatal error"}, retryAttempts+1)
968
969	r, err := SendWithSender(client, mocks.NewRequest(),
970		DoRetryForStatusCodes(retryAttempts, time.Duration(1*time.Second), StatusCodesForRetry...),
971	)
972
973	Respond(r,
974		ByDiscardingBody(),
975		ByClosing())
976
977	if err == nil || client.Attempts() < retryAttempts+1 {
978		t.Fatalf("autorest: Sender#TestDoRetryForStatusCodes_NilResponseFatalError -- Got: nil error or wrong number of attempts - %v", err)
979	}
980}
981
982func TestDoRetryForStatusCodes_Cancel429(t *testing.T) {
983	Count429AsRetry = false
984	defer func() { Count429AsRetry = true }()
985	retries := 6
986	client := mocks.NewSender()
987	resp := mocks.NewResponseWithStatus("429 Too many requests", http.StatusTooManyRequests)
988	client.AppendAndRepeatResponse(resp, retries)
989
990	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(retries/2)*time.Second)
991	defer cancel()
992	req := mocks.NewRequest().WithContext(ctx)
993	r, err := SendWithSender(client, req,
994		DoRetryForStatusCodes(1, time.Duration(time.Second), http.StatusTooManyRequests),
995	)
996
997	if err == nil {
998		t.Fatal("unexpected nil-error")
999	}
1000	if r == nil {
1001		t.Fatal("unexpected nil response")
1002	}
1003	if r.StatusCode != http.StatusTooManyRequests {
1004		t.Fatalf("expected status code 429, got: %d", r.StatusCode)
1005	}
1006	if client.Attempts() >= retries {
1007		t.Fatalf("too many attempts: %d", client.Attempts())
1008	}
1009}
1010
1011func TestDoRetryForStatusCodes_Race(t *testing.T) {
1012	// cannot use the mock sender as it's not safe for concurrent use
1013	s := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}))
1014	defer s.Close()
1015
1016	sender := DecorateSender(s.Client(),
1017		DoRetryForStatusCodes(0, 0, http.StatusRequestTimeout))
1018
1019	runs := 2
1020	errCh := make(chan error, runs)
1021
1022	for i := 0; i < runs; i++ {
1023		go func() {
1024			req, _ := http.NewRequest(http.MethodGet, s.URL, nil)
1025			// cannot use testing.T.Fatal inside a goroutine, send error down channel
1026			_, err := sender.Do(req)
1027			errCh <- err
1028		}()
1029	}
1030	for i := 0; i < runs; i++ {
1031		err := <-errCh
1032		if err != nil {
1033			t.Fatal(err)
1034		}
1035	}
1036	close(errCh)
1037}
1038
1039func TestGetSendDecorators(t *testing.T) {
1040	sd := GetSendDecorators(context.Background())
1041	if l := len(sd); l != 0 {
1042		t.Fatalf("expected zero length but got %d", l)
1043	}
1044	sd = GetSendDecorators(context.Background(), DoCloseIfError(), DoErrorIfStatusCode())
1045	if l := len(sd); l != 2 {
1046		t.Fatalf("expected length of two but got %d", l)
1047	}
1048}
1049
1050func TestWithSendDecorators(t *testing.T) {
1051	ctx := WithSendDecorators(context.Background(), []SendDecorator{DoRetryForAttempts(5, 5*time.Second)})
1052	sd := GetSendDecorators(ctx)
1053	if l := len(sd); l != 1 {
1054		t.Fatalf("expected length of one but got %d", l)
1055	}
1056	sd = GetSendDecorators(ctx, DoCloseIfError(), DoErrorIfStatusCode())
1057	if l := len(sd); l != 1 {
1058		t.Fatalf("expected length of one but got %d", l)
1059	}
1060}
1061