faraday 2.0.0.alpha.pre.4 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,254 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe Faraday::Request::Retry do
4
- let(:calls) { [] }
5
- let(:times_called) { calls.size }
6
- let(:options) { [] }
7
- let(:conn) do
8
- Faraday.new do |b|
9
- b.request :retry, *options
10
-
11
- b.adapter :test do |stub|
12
- %w[get post].each do |method|
13
- stub.send(method, '/unstable') do |env|
14
- calls << env.dup
15
- env[:body] = nil # simulate blanking out response body
16
- callback.call
17
- end
18
- end
19
- end
20
- end
21
- end
22
-
23
- context 'when an unexpected error happens' do
24
- let(:callback) { -> { raise 'boom!' } }
25
-
26
- before { expect { conn.get('/unstable') }.to raise_error(RuntimeError) }
27
-
28
- it { expect(times_called).to eq(1) }
29
-
30
- context 'and this is passed as a custom exception' do
31
- let(:options) { [{ exceptions: StandardError }] }
32
-
33
- it { expect(times_called).to eq(3) }
34
- end
35
-
36
- context 'and this is passed as a string custom exception' do
37
- let(:options) { [{ exceptions: 'StandardError' }] }
38
-
39
- it { expect(times_called).to eq(3) }
40
- end
41
-
42
- context 'and a non-existent string custom exception is passed' do
43
- let(:options) { [{ exceptions: 'WrongStandardErrorNotExisting' }] }
44
-
45
- it { expect(times_called).to eq(1) }
46
- end
47
- end
48
-
49
- context 'when an expected error happens' do
50
- let(:callback) { -> { raise Errno::ETIMEDOUT } }
51
-
52
- before do
53
- @started = Time.now
54
- expect { conn.get('/unstable') }.to raise_error(Errno::ETIMEDOUT)
55
- end
56
-
57
- it { expect(times_called).to eq(3) }
58
-
59
- context 'and legacy max_retry set to 1' do
60
- let(:options) { [1] }
61
-
62
- it { expect(times_called).to eq(2) }
63
- end
64
-
65
- context 'and legacy max_retry set to -9' do
66
- let(:options) { [-9] }
67
-
68
- it { expect(times_called).to eq(1) }
69
- end
70
-
71
- context 'and new max_retry set to 3' do
72
- let(:options) { [{ max: 3 }] }
73
-
74
- it { expect(times_called).to eq(4) }
75
- end
76
-
77
- context 'and new max_retry set to -9' do
78
- let(:options) { [{ max: -9 }] }
79
-
80
- it { expect(times_called).to eq(1) }
81
- end
82
-
83
- context 'and both max_retry and interval are set' do
84
- let(:options) { [{ max: 2, interval: 0.1 }] }
85
-
86
- it { expect(Time.now - @started).to be_within(0.04).of(0.2) }
87
- end
88
- end
89
-
90
- context 'when no exception raised' do
91
- let(:options) { [{ max: 1, retry_statuses: 429 }] }
92
-
93
- before { conn.get('/unstable') }
94
-
95
- context 'and response code is in retry_statuses' do
96
- let(:callback) { -> { [429, {}, ''] } }
97
-
98
- it { expect(times_called).to eq(2) }
99
- end
100
-
101
- context 'and response code is not in retry_statuses' do
102
- let(:callback) { -> { [503, {}, ''] } }
103
-
104
- it { expect(times_called).to eq(1) }
105
- end
106
- end
107
-
108
- describe '#calculate_retry_interval' do
109
- context 'with exponential backoff' do
110
- let(:options) { { max: 5, interval: 0.1, backoff_factor: 2 } }
111
- let(:middleware) { Faraday::Request::Retry.new(nil, options) }
112
-
113
- it { expect(middleware.send(:calculate_retry_interval, 5)).to eq(0.1) }
114
- it { expect(middleware.send(:calculate_retry_interval, 4)).to eq(0.2) }
115
- it { expect(middleware.send(:calculate_retry_interval, 3)).to eq(0.4) }
116
- end
117
-
118
- context 'with exponential backoff and max_interval' do
119
- let(:options) { { max: 5, interval: 0.1, backoff_factor: 2, max_interval: 0.3 } }
120
- let(:middleware) { Faraday::Request::Retry.new(nil, options) }
121
-
122
- it { expect(middleware.send(:calculate_retry_interval, 5)).to eq(0.1) }
123
- it { expect(middleware.send(:calculate_retry_interval, 4)).to eq(0.2) }
124
- it { expect(middleware.send(:calculate_retry_interval, 3)).to eq(0.3) }
125
- it { expect(middleware.send(:calculate_retry_interval, 2)).to eq(0.3) }
126
- end
127
-
128
- context 'with exponential backoff and interval_randomness' do
129
- let(:options) { { max: 2, interval: 0.1, interval_randomness: 0.05 } }
130
- let(:middleware) { Faraday::Request::Retry.new(nil, options) }
131
-
132
- it { expect(middleware.send(:calculate_retry_interval, 2)).to be_between(0.1, 0.105) }
133
- end
134
- end
135
-
136
- context 'when method is not idempotent' do
137
- let(:callback) { -> { raise Errno::ETIMEDOUT } }
138
-
139
- before { expect { conn.post('/unstable') }.to raise_error(Errno::ETIMEDOUT) }
140
-
141
- it { expect(times_called).to eq(1) }
142
- end
143
-
144
- describe 'retry_if option' do
145
- let(:callback) { -> { raise Errno::ETIMEDOUT } }
146
- let(:options) { [{ retry_if: @check }] }
147
-
148
- it 'retries if retry_if block always returns true' do
149
- body = { foo: :bar }
150
- @check = ->(_, _) { true }
151
- expect { conn.post('/unstable', body) }.to raise_error(Errno::ETIMEDOUT)
152
- expect(times_called).to eq(3)
153
- expect(calls.all? { |env| env[:body] == body }).to be_truthy
154
- end
155
-
156
- it 'does not retry if retry_if block returns false checking env' do
157
- @check = ->(env, _) { env[:method] != :post }
158
- expect { conn.post('/unstable') }.to raise_error(Errno::ETIMEDOUT)
159
- expect(times_called).to eq(1)
160
- end
161
-
162
- it 'does not retry if retry_if block returns false checking exception' do
163
- @check = ->(_, exception) { !exception.is_a?(Errno::ETIMEDOUT) }
164
- expect { conn.post('/unstable') }.to raise_error(Errno::ETIMEDOUT)
165
- expect(times_called).to eq(1)
166
- end
167
-
168
- it 'FilePart: should rewind files on retry' do
169
- io = StringIO.new('Test data')
170
- filepart = Faraday::FilePart.new(io, 'application/octet/stream')
171
-
172
- rewound = 0
173
- rewind = -> { rewound += 1 }
174
-
175
- @check = ->(_, _) { true }
176
- allow(filepart).to receive(:rewind, &rewind)
177
- expect { conn.post('/unstable', file: filepart) }.to raise_error(Errno::ETIMEDOUT)
178
- expect(times_called).to eq(3)
179
- expect(rewound).to eq(2)
180
- end
181
-
182
- it 'UploadIO: should rewind files on retry' do
183
- io = StringIO.new('Test data')
184
- upload_io = Faraday::FilePart.new(io, 'application/octet/stream')
185
-
186
- rewound = 0
187
- rewind = -> { rewound += 1 }
188
-
189
- @check = ->(_, _) { true }
190
- allow(upload_io).to receive(:rewind, &rewind)
191
- expect { conn.post('/unstable', file: upload_io) }.to raise_error(Errno::ETIMEDOUT)
192
- expect(times_called).to eq(3)
193
- expect(rewound).to eq(2)
194
- end
195
-
196
- context 'when explicitly specifying methods to retry' do
197
- let(:options) { [{ retry_if: @check, methods: [:post] }] }
198
-
199
- it 'does not call retry_if for specified methods' do
200
- @check = ->(_, _) { raise 'this should have never been called' }
201
- expect { conn.post('/unstable') }.to raise_error(Errno::ETIMEDOUT)
202
- expect(times_called).to eq(3)
203
- end
204
- end
205
-
206
- context 'with empty list of methods to retry' do
207
- let(:options) { [{ retry_if: @check, methods: [] }] }
208
-
209
- it 'calls retry_if for all methods' do
210
- @check = ->(_, _) { calls.size < 2 }
211
- expect { conn.get('/unstable') }.to raise_error(Errno::ETIMEDOUT)
212
- expect(times_called).to eq(2)
213
- end
214
- end
215
- end
216
-
217
- describe 'retry_after header support' do
218
- let(:callback) { -> { [504, headers, ''] } }
219
- let(:elapsed) { Time.now - @started }
220
-
221
- before do
222
- @started = Time.now
223
- conn.get('/unstable')
224
- end
225
-
226
- context 'when retry_after bigger than interval' do
227
- let(:headers) { { 'Retry-After' => '0.5' } }
228
- let(:options) { [{ max: 1, interval: 0.1, retry_statuses: 504 }] }
229
-
230
- it { expect(elapsed).to be > 0.5 }
231
- end
232
-
233
- context 'when retry_after smaller than interval' do
234
- let(:headers) { { 'Retry-After' => '0.1' } }
235
- let(:options) { [{ max: 1, interval: 0.2, retry_statuses: 504 }] }
236
-
237
- it { expect(elapsed).to be > 0.2 }
238
- end
239
-
240
- context 'when retry_after is a timestamp' do
241
- let(:headers) { { 'Retry-After' => (Time.now.utc + 2).strftime('%a, %d %b %Y %H:%M:%S GMT') } }
242
- let(:options) { [{ max: 1, interval: 0.1, retry_statuses: 504 }] }
243
-
244
- it { expect(elapsed).to be > 1 }
245
- end
246
-
247
- context 'when retry_after is bigger than max_interval' do
248
- let(:headers) { { 'Retry-After' => (Time.now.utc + 20).strftime('%a, %d %b %Y %H:%M:%S GMT') } }
249
- let(:options) { [{ max: 2, interval: 0.1, max_interval: 5, retry_statuses: 504 }] }
250
-
251
- it { expect(times_called).to eq(1) }
252
- end
253
- end
254
- end