faraday 1.10.3 → 2.0.0.alpha.pre.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +111 -1
- data/README.md +16 -9
- data/examples/client_test.rb +1 -1
- data/lib/faraday/adapter/test.rb +2 -0
- data/lib/faraday/adapter.rb +0 -5
- data/lib/faraday/connection.rb +3 -84
- data/lib/faraday/encoders/nested_params_encoder.rb +2 -2
- data/lib/faraday/error.rb +7 -0
- data/lib/faraday/file_part.rb +122 -0
- data/lib/faraday/logging/formatter.rb +1 -0
- data/lib/faraday/middleware.rb +0 -1
- data/lib/faraday/middleware_registry.rb +15 -79
- data/lib/faraday/options.rb +3 -3
- data/lib/faraday/param_part.rb +53 -0
- data/lib/faraday/rack_builder.rb +1 -1
- data/lib/faraday/request/authorization.rb +26 -40
- data/lib/faraday/request/instrumentation.rb +2 -0
- data/lib/faraday/request/multipart.rb +108 -0
- data/lib/faraday/request/retry.rb +241 -0
- data/lib/faraday/request/url_encoded.rb +2 -0
- data/lib/faraday/request.rb +8 -24
- data/lib/faraday/response/json.rb +4 -4
- data/lib/faraday/response/logger.rb +2 -0
- data/lib/faraday/response/raise_error.rb +9 -1
- data/lib/faraday/response.rb +7 -20
- data/lib/faraday/utils/headers.rb +1 -1
- data/lib/faraday/utils.rb +9 -4
- data/lib/faraday/version.rb +1 -1
- data/lib/faraday.rb +6 -45
- data/spec/faraday/connection_spec.rb +78 -51
- data/spec/faraday/options/env_spec.rb +2 -2
- data/spec/faraday/rack_builder_spec.rb +5 -43
- data/spec/faraday/request/authorization_spec.rb +14 -36
- data/spec/faraday/request/instrumentation_spec.rb +5 -7
- data/spec/faraday/request/multipart_spec.rb +302 -0
- data/spec/faraday/request/retry_spec.rb +254 -0
- data/spec/faraday/request_spec.rb +0 -11
- data/spec/faraday/response/json_spec.rb +4 -6
- data/spec/faraday/response/raise_error_spec.rb +7 -4
- data/spec/faraday/utils_spec.rb +1 -1
- data/spec/spec_helper.rb +0 -2
- data/spec/support/fake_safe_buffer.rb +1 -1
- data/spec/support/shared_examples/request_method.rb +5 -5
- metadata +21 -152
- data/lib/faraday/adapter/typhoeus.rb +0 -15
- data/lib/faraday/autoload.rb +0 -87
- data/lib/faraday/dependency_loader.rb +0 -39
- data/lib/faraday/deprecate.rb +0 -110
- data/lib/faraday/request/basic_authentication.rb +0 -20
- data/lib/faraday/request/token_authentication.rb +0 -20
- data/spec/faraday/adapter/em_http_spec.rb +0 -49
- data/spec/faraday/adapter/em_synchrony_spec.rb +0 -18
- data/spec/faraday/adapter/excon_spec.rb +0 -49
- data/spec/faraday/adapter/httpclient_spec.rb +0 -73
- data/spec/faraday/adapter/net_http_spec.rb +0 -64
- data/spec/faraday/adapter/patron_spec.rb +0 -18
- data/spec/faraday/adapter/rack_spec.rb +0 -8
- data/spec/faraday/adapter/typhoeus_spec.rb +0 -7
- data/spec/faraday/deprecate_spec.rb +0 -147
- data/spec/faraday/response/middleware_spec.rb +0 -68
- data/spec/support/webmock_rack_app.rb +0 -68
@@ -0,0 +1,302 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe Faraday::Request::Multipart do
|
4
|
+
let(:options) { {} }
|
5
|
+
let(:conn) do
|
6
|
+
Faraday.new do |b|
|
7
|
+
b.request :multipart, options
|
8
|
+
b.request :url_encoded
|
9
|
+
b.adapter :test do |stub|
|
10
|
+
stub.post('/echo') do |env|
|
11
|
+
posted_as = env[:request_headers]['Content-Type']
|
12
|
+
expect(env[:body]).to be_a_kind_of(Faraday::CompositeReadIO)
|
13
|
+
[200, { 'Content-Type' => posted_as }, env[:body].read]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
shared_examples 'a multipart request' do
|
20
|
+
it 'generates a unique boundary for each request' do
|
21
|
+
response1 = conn.post('/echo', payload)
|
22
|
+
response2 = conn.post('/echo', payload)
|
23
|
+
|
24
|
+
b1 = parse_multipart_boundary(response1.headers['Content-Type'])
|
25
|
+
b2 = parse_multipart_boundary(response2.headers['Content-Type'])
|
26
|
+
expect(b1).to_not eq(b2)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'FilePart: when multipart objects in param' do
|
31
|
+
let(:payload) do
|
32
|
+
{
|
33
|
+
a: 1,
|
34
|
+
b: {
|
35
|
+
c: Faraday::FilePart.new(__FILE__, 'text/x-ruby', nil,
|
36
|
+
'Content-Disposition' => 'form-data; foo=1'),
|
37
|
+
d: 2
|
38
|
+
}
|
39
|
+
}
|
40
|
+
end
|
41
|
+
it_behaves_like 'a multipart request'
|
42
|
+
|
43
|
+
it 'forms a multipart request' do
|
44
|
+
response = conn.post('/echo', payload)
|
45
|
+
|
46
|
+
boundary = parse_multipart_boundary(response.headers['Content-Type'])
|
47
|
+
result = parse_multipart(boundary, response.body)
|
48
|
+
expect(result[:errors]).to be_empty
|
49
|
+
|
50
|
+
part_a, body_a = result.part('a')
|
51
|
+
expect(part_a).to_not be_nil
|
52
|
+
expect(part_a.filename).to be_nil
|
53
|
+
expect(body_a).to eq('1')
|
54
|
+
|
55
|
+
part_bc, body_bc = result.part('b[c]')
|
56
|
+
expect(part_bc).to_not be_nil
|
57
|
+
expect(part_bc.filename).to eq('multipart_spec.rb')
|
58
|
+
expect(part_bc.headers['content-disposition'])
|
59
|
+
.to eq(
|
60
|
+
'form-data; foo=1; name="b[c]"; filename="multipart_spec.rb"'
|
61
|
+
)
|
62
|
+
expect(part_bc.headers['content-type']).to eq('text/x-ruby')
|
63
|
+
expect(part_bc.headers['content-transfer-encoding']).to eq('binary')
|
64
|
+
expect(body_bc).to eq(File.read(__FILE__))
|
65
|
+
|
66
|
+
part_bd, body_bd = result.part('b[d]')
|
67
|
+
expect(part_bd).to_not be_nil
|
68
|
+
expect(part_bd.filename).to be_nil
|
69
|
+
expect(body_bd).to eq('2')
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'FilePart: when providing json and IO content in the same payload' do
|
74
|
+
let(:io) { StringIO.new('io-content') }
|
75
|
+
let(:json) do
|
76
|
+
{
|
77
|
+
b: 1,
|
78
|
+
c: 2
|
79
|
+
}.to_json
|
80
|
+
end
|
81
|
+
|
82
|
+
let(:payload) do
|
83
|
+
{
|
84
|
+
json: Faraday::ParamPart.new(json, 'application/json'),
|
85
|
+
io: Faraday::FilePart.new(io, 'application/pdf')
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
it_behaves_like 'a multipart request'
|
90
|
+
|
91
|
+
it 'forms a multipart request' do
|
92
|
+
response = conn.post('/echo', payload)
|
93
|
+
|
94
|
+
boundary = parse_multipart_boundary(response.headers['Content-Type'])
|
95
|
+
result = parse_multipart(boundary, response.body)
|
96
|
+
expect(result[:errors]).to be_empty
|
97
|
+
|
98
|
+
part_json, body_json = result.part('json')
|
99
|
+
expect(part_json).to_not be_nil
|
100
|
+
expect(part_json.mime).to eq('application/json')
|
101
|
+
expect(part_json.filename).to be_nil
|
102
|
+
expect(body_json).to eq(json)
|
103
|
+
|
104
|
+
part_io, body_io = result.part('io')
|
105
|
+
expect(part_io).to_not be_nil
|
106
|
+
expect(part_io.mime).to eq('application/pdf')
|
107
|
+
expect(part_io.filename).to eq('local.path')
|
108
|
+
expect(body_io).to eq(io.string)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'FilePart: when multipart objects in array param' do
|
113
|
+
let(:payload) do
|
114
|
+
{
|
115
|
+
a: 1,
|
116
|
+
b: [{
|
117
|
+
c: Faraday::FilePart.new(__FILE__, 'text/x-ruby'),
|
118
|
+
d: 2
|
119
|
+
}]
|
120
|
+
}
|
121
|
+
end
|
122
|
+
|
123
|
+
it_behaves_like 'a multipart request'
|
124
|
+
|
125
|
+
it 'forms a multipart request' do
|
126
|
+
response = conn.post('/echo', payload)
|
127
|
+
|
128
|
+
boundary = parse_multipart_boundary(response.headers['Content-Type'])
|
129
|
+
result = parse_multipart(boundary, response.body)
|
130
|
+
expect(result[:errors]).to be_empty
|
131
|
+
|
132
|
+
part_a, body_a = result.part('a')
|
133
|
+
expect(part_a).to_not be_nil
|
134
|
+
expect(part_a.filename).to be_nil
|
135
|
+
expect(body_a).to eq('1')
|
136
|
+
|
137
|
+
part_bc, body_bc = result.part('b[][c]')
|
138
|
+
expect(part_bc).to_not be_nil
|
139
|
+
expect(part_bc.filename).to eq('multipart_spec.rb')
|
140
|
+
expect(part_bc.headers['content-disposition'])
|
141
|
+
.to eq(
|
142
|
+
'form-data; name="b[][c]"; filename="multipart_spec.rb"'
|
143
|
+
)
|
144
|
+
expect(part_bc.headers['content-type']).to eq('text/x-ruby')
|
145
|
+
expect(part_bc.headers['content-transfer-encoding']).to eq('binary')
|
146
|
+
expect(body_bc).to eq(File.read(__FILE__))
|
147
|
+
|
148
|
+
part_bd, body_bd = result.part('b[][d]')
|
149
|
+
expect(part_bd).to_not be_nil
|
150
|
+
expect(part_bd.filename).to be_nil
|
151
|
+
expect(body_bd).to eq('2')
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
context 'UploadIO: when multipart objects in param' do
|
156
|
+
let(:payload) do
|
157
|
+
{
|
158
|
+
a: 1,
|
159
|
+
b: {
|
160
|
+
c: Faraday::FilePart.new(__FILE__, 'text/x-ruby', nil,
|
161
|
+
'Content-Disposition' => 'form-data; foo=1'),
|
162
|
+
d: 2
|
163
|
+
}
|
164
|
+
}
|
165
|
+
end
|
166
|
+
it_behaves_like 'a multipart request'
|
167
|
+
|
168
|
+
it 'forms a multipart request' do
|
169
|
+
response = conn.post('/echo', payload)
|
170
|
+
|
171
|
+
boundary = parse_multipart_boundary(response.headers['Content-Type'])
|
172
|
+
result = parse_multipart(boundary, response.body)
|
173
|
+
expect(result[:errors]).to be_empty
|
174
|
+
|
175
|
+
part_a, body_a = result.part('a')
|
176
|
+
expect(part_a).to_not be_nil
|
177
|
+
expect(part_a.filename).to be_nil
|
178
|
+
expect(body_a).to eq('1')
|
179
|
+
|
180
|
+
part_bc, body_bc = result.part('b[c]')
|
181
|
+
expect(part_bc).to_not be_nil
|
182
|
+
expect(part_bc.filename).to eq('multipart_spec.rb')
|
183
|
+
expect(part_bc.headers['content-disposition'])
|
184
|
+
.to eq(
|
185
|
+
'form-data; foo=1; name="b[c]"; filename="multipart_spec.rb"'
|
186
|
+
)
|
187
|
+
expect(part_bc.headers['content-type']).to eq('text/x-ruby')
|
188
|
+
expect(part_bc.headers['content-transfer-encoding']).to eq('binary')
|
189
|
+
expect(body_bc).to eq(File.read(__FILE__))
|
190
|
+
|
191
|
+
part_bd, body_bd = result.part('b[d]')
|
192
|
+
expect(part_bd).to_not be_nil
|
193
|
+
expect(part_bd.filename).to be_nil
|
194
|
+
expect(body_bd).to eq('2')
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
context 'UploadIO: when providing json and IO content in the same payload' do
|
199
|
+
let(:io) { StringIO.new('io-content') }
|
200
|
+
let(:json) do
|
201
|
+
{
|
202
|
+
b: 1,
|
203
|
+
c: 2
|
204
|
+
}.to_json
|
205
|
+
end
|
206
|
+
|
207
|
+
let(:payload) do
|
208
|
+
{
|
209
|
+
json: Faraday::ParamPart.new(json, 'application/json'),
|
210
|
+
io: Faraday::FilePart.new(io, 'application/pdf')
|
211
|
+
}
|
212
|
+
end
|
213
|
+
|
214
|
+
it_behaves_like 'a multipart request'
|
215
|
+
|
216
|
+
it 'forms a multipart request' do
|
217
|
+
response = conn.post('/echo', payload)
|
218
|
+
|
219
|
+
boundary = parse_multipart_boundary(response.headers['Content-Type'])
|
220
|
+
result = parse_multipart(boundary, response.body)
|
221
|
+
expect(result[:errors]).to be_empty
|
222
|
+
|
223
|
+
part_json, body_json = result.part('json')
|
224
|
+
expect(part_json).to_not be_nil
|
225
|
+
expect(part_json.mime).to eq('application/json')
|
226
|
+
expect(part_json.filename).to be_nil
|
227
|
+
expect(body_json).to eq(json)
|
228
|
+
|
229
|
+
part_io, body_io = result.part('io')
|
230
|
+
expect(part_io).to_not be_nil
|
231
|
+
expect(part_io.mime).to eq('application/pdf')
|
232
|
+
expect(part_io.filename).to eq('local.path')
|
233
|
+
expect(body_io).to eq(io.string)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
context 'UploadIO: when multipart objects in array param' do
|
238
|
+
let(:payload) do
|
239
|
+
{
|
240
|
+
a: 1,
|
241
|
+
b: [{
|
242
|
+
c: Faraday::FilePart.new(__FILE__, 'text/x-ruby'),
|
243
|
+
d: 2
|
244
|
+
}]
|
245
|
+
}
|
246
|
+
end
|
247
|
+
|
248
|
+
it_behaves_like 'a multipart request'
|
249
|
+
|
250
|
+
it 'forms a multipart request' do
|
251
|
+
response = conn.post('/echo', payload)
|
252
|
+
|
253
|
+
boundary = parse_multipart_boundary(response.headers['Content-Type'])
|
254
|
+
result = parse_multipart(boundary, response.body)
|
255
|
+
expect(result[:errors]).to be_empty
|
256
|
+
|
257
|
+
part_a, body_a = result.part('a')
|
258
|
+
expect(part_a).to_not be_nil
|
259
|
+
expect(part_a.filename).to be_nil
|
260
|
+
expect(body_a).to eq('1')
|
261
|
+
|
262
|
+
part_bc, body_bc = result.part('b[][c]')
|
263
|
+
expect(part_bc).to_not be_nil
|
264
|
+
expect(part_bc.filename).to eq('multipart_spec.rb')
|
265
|
+
expect(part_bc.headers['content-disposition'])
|
266
|
+
.to eq(
|
267
|
+
'form-data; name="b[][c]"; filename="multipart_spec.rb"'
|
268
|
+
)
|
269
|
+
expect(part_bc.headers['content-type']).to eq('text/x-ruby')
|
270
|
+
expect(part_bc.headers['content-transfer-encoding']).to eq('binary')
|
271
|
+
expect(body_bc).to eq(File.read(__FILE__))
|
272
|
+
|
273
|
+
part_bd, body_bd = result.part('b[][d]')
|
274
|
+
expect(part_bd).to_not be_nil
|
275
|
+
expect(part_bd.filename).to be_nil
|
276
|
+
expect(body_bd).to eq('2')
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
context 'when passing flat_encode=true option' do
|
281
|
+
let(:options) { { flat_encode: true } }
|
282
|
+
let(:io) { StringIO.new('io-content') }
|
283
|
+
let(:payload) do
|
284
|
+
{
|
285
|
+
a: 1,
|
286
|
+
b: [
|
287
|
+
Faraday::FilePart.new(io, 'application/pdf'),
|
288
|
+
Faraday::FilePart.new(io, 'application/pdf')
|
289
|
+
]
|
290
|
+
}
|
291
|
+
end
|
292
|
+
|
293
|
+
it_behaves_like 'a multipart request'
|
294
|
+
|
295
|
+
it 'encode params using flat encoder' do
|
296
|
+
response = conn.post('/echo', payload)
|
297
|
+
|
298
|
+
expect(response.body).to include('name="b"')
|
299
|
+
expect(response.body).not_to include('name="b[]"')
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
@@ -0,0 +1,254 @@
|
|
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
|
@@ -22,17 +22,6 @@ RSpec.describe Faraday::Request do
|
|
22
22
|
it { expect(subject.http_method).to eq(:post) }
|
23
23
|
end
|
24
24
|
|
25
|
-
describe 'deprecate method for HTTP method' do
|
26
|
-
let(:http_method) { :post }
|
27
|
-
let(:expected_warning) do
|
28
|
-
%r{NOTE: Faraday::Request#method is deprecated; use http_method instead\. It will be removed in or after version 2.0 \nFaraday::Request#method called from .+/spec/faraday/request_spec.rb:\d+.}
|
29
|
-
end
|
30
|
-
|
31
|
-
it { expect(subject.method).to eq(:post) }
|
32
|
-
|
33
|
-
it { expect { subject.method }.to output(expected_warning).to_stderr }
|
34
|
-
end
|
35
|
-
|
36
25
|
context 'when setting the url on setup with a URI' do
|
37
26
|
let(:block) { proc { |req| req.url URI.parse('foo.json?a=1') } }
|
38
27
|
|
@@ -76,12 +76,10 @@ RSpec.describe Faraday::Response::Json, type: :response do
|
|
76
76
|
end
|
77
77
|
|
78
78
|
it 'includes the response on the ParsingError instance' do
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
expect(e.response).to be_a(Faraday::Response)
|
84
|
-
end
|
79
|
+
process('{') { |env| env[:response] = Faraday::Response.new }
|
80
|
+
raise 'Parsing should have failed.'
|
81
|
+
rescue Faraday::ParsingError => e
|
82
|
+
expect(e.response).to be_a(Faraday::Response)
|
85
83
|
end
|
86
84
|
|
87
85
|
context 'HEAD responses' do
|
@@ -139,7 +139,7 @@ RSpec.describe Faraday::Response::RaiseError do
|
|
139
139
|
Faraday.new do |b|
|
140
140
|
b.response :raise_error
|
141
141
|
b.adapter :test do |stub|
|
142
|
-
stub.post(
|
142
|
+
stub.post(url, request_body, request_headers) do
|
143
143
|
[400, { 'X-Reason' => 'because' }, 'keep looking']
|
144
144
|
end
|
145
145
|
end
|
@@ -147,11 +147,13 @@ RSpec.describe Faraday::Response::RaiseError do
|
|
147
147
|
end
|
148
148
|
let(:request_body) { JSON.generate({ 'item' => 'sth' }) }
|
149
149
|
let(:request_headers) { { 'Authorization' => 'Basic 123' } }
|
150
|
+
let(:url_path) { 'request' }
|
151
|
+
let(:query_params) { 'full=true' }
|
152
|
+
let(:url) { "#{url_path}?#{query_params}" }
|
150
153
|
|
151
154
|
subject(:perform_request) do
|
152
|
-
conn.post
|
155
|
+
conn.post url do |req|
|
153
156
|
req.headers['Authorization'] = 'Basic 123'
|
154
|
-
req.params[:full] = true
|
155
157
|
req.body = request_body
|
156
158
|
end
|
157
159
|
end
|
@@ -159,7 +161,8 @@ RSpec.describe Faraday::Response::RaiseError do
|
|
159
161
|
it 'returns the request info in the exception' do
|
160
162
|
expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
|
161
163
|
expect(ex.response[:request][:method]).to eq(:post)
|
162
|
-
expect(ex.response[:request][:
|
164
|
+
expect(ex.response[:request][:url]).to eq(URI("http:/#{url}"))
|
165
|
+
expect(ex.response[:request][:url_path]).to eq("/#{url_path}")
|
163
166
|
expect(ex.response[:request][:params]).to eq({ 'full' => 'true' })
|
164
167
|
expect(ex.response[:request][:headers]).to match(a_hash_including(request_headers))
|
165
168
|
expect(ex.response[:request][:body]).to eq(request_body)
|
data/spec/faraday/utils_spec.rb
CHANGED
@@ -4,7 +4,7 @@ RSpec.describe Faraday::Utils do
|
|
4
4
|
describe 'headers parsing' do
|
5
5
|
let(:multi_response_headers) do
|
6
6
|
"HTTP/1.x 500 OK\r\nContent-Type: text/html; charset=UTF-8\r\n" \
|
7
|
-
|
7
|
+
"HTTP/1.x 200 OK\r\nContent-Type: application/json; charset=UTF-8\r\n\r\n"
|
8
8
|
end
|
9
9
|
|
10
10
|
it 'parse headers for aggregated responses' do
|
data/spec/spec_helper.rb
CHANGED
@@ -38,8 +38,6 @@ require 'pry'
|
|
38
38
|
|
39
39
|
Dir['./spec/support/**/*.rb'].sort.each { |f| require f }
|
40
40
|
|
41
|
-
Faraday::Deprecate.skip = false
|
42
|
-
|
43
41
|
RSpec.configure do |config|
|
44
42
|
# rspec-expectations config goes here. You can use an alternate
|
45
43
|
# assertion/expectation library such as wrong or the stdlib/minitest
|
@@ -79,7 +79,7 @@ shared_examples 'a request method' do |http_method|
|
|
79
79
|
|
80
80
|
on_feature :request_body_on_query_methods do
|
81
81
|
it 'sends request body' do
|
82
|
-
request_stub.with(
|
82
|
+
request_stub.with({ body: 'test' })
|
83
83
|
res = if query_or_body == :body
|
84
84
|
conn.public_send(http_method, '/', 'test')
|
85
85
|
else
|
@@ -93,7 +93,7 @@ shared_examples 'a request method' do |http_method|
|
|
93
93
|
|
94
94
|
it 'sends url encoded parameters' do
|
95
95
|
payload = { name: 'zack' }
|
96
|
-
request_stub.with(
|
96
|
+
request_stub.with({ query_or_body => payload })
|
97
97
|
res = conn.public_send(http_method, '/', payload)
|
98
98
|
if query_or_body == :query
|
99
99
|
expect(res.env.request_body).to be_nil
|
@@ -104,7 +104,7 @@ shared_examples 'a request method' do |http_method|
|
|
104
104
|
|
105
105
|
it 'sends url encoded nested parameters' do
|
106
106
|
payload = { name: { first: 'zack' } }
|
107
|
-
request_stub.with(
|
107
|
+
request_stub.with({ query_or_body => payload })
|
108
108
|
conn.public_send(http_method, '/', payload)
|
109
109
|
end
|
110
110
|
|
@@ -199,11 +199,11 @@ shared_examples 'a request method' do |http_method|
|
|
199
199
|
@payload2 = { b: '2' }
|
200
200
|
|
201
201
|
request_stub
|
202
|
-
.with(
|
202
|
+
.with({ query_or_body => @payload1 })
|
203
203
|
.to_return(body: @payload1.to_json)
|
204
204
|
|
205
205
|
stub_request(http_method, remote)
|
206
|
-
.with(
|
206
|
+
.with({ query_or_body => @payload2 })
|
207
207
|
.to_return(body: @payload2.to_json)
|
208
208
|
|
209
209
|
conn.in_parallel do
|