faraday 1.8.0 → 2.7.11

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.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +197 -3
  3. data/LICENSE.md +1 -1
  4. data/README.md +34 -20
  5. data/Rakefile +3 -1
  6. data/examples/client_spec.rb +41 -19
  7. data/examples/client_test.rb +48 -22
  8. data/lib/faraday/adapter/test.rb +61 -12
  9. data/lib/faraday/adapter.rb +5 -9
  10. data/lib/faraday/connection.rb +58 -145
  11. data/lib/faraday/encoders/nested_params_encoder.rb +13 -6
  12. data/lib/faraday/error.rb +16 -11
  13. data/lib/faraday/logging/formatter.rb +27 -14
  14. data/lib/faraday/middleware.rb +3 -1
  15. data/lib/faraday/middleware_registry.rb +17 -63
  16. data/lib/faraday/options/connection_options.rb +7 -6
  17. data/lib/faraday/options/env.rb +85 -62
  18. data/lib/faraday/options/proxy_options.rb +7 -3
  19. data/lib/faraday/options/request_options.rb +7 -6
  20. data/lib/faraday/options/ssl_options.rb +56 -45
  21. data/lib/faraday/options.rb +4 -3
  22. data/lib/faraday/rack_builder.rb +23 -20
  23. data/lib/faraday/request/authorization.rb +33 -41
  24. data/lib/faraday/request/instrumentation.rb +5 -1
  25. data/lib/faraday/request/json.rb +64 -0
  26. data/lib/faraday/request/url_encoded.rb +5 -1
  27. data/lib/faraday/request.rb +20 -37
  28. data/lib/faraday/response/json.rb +54 -0
  29. data/lib/faraday/response/logger.rb +8 -4
  30. data/lib/faraday/response/raise_error.rb +29 -4
  31. data/lib/faraday/response.rb +10 -20
  32. data/lib/faraday/utils/headers.rb +7 -2
  33. data/lib/faraday/utils.rb +10 -5
  34. data/lib/faraday/version.rb +1 -1
  35. data/lib/faraday.rb +10 -38
  36. data/spec/faraday/adapter/test_spec.rb +65 -0
  37. data/spec/faraday/connection_spec.rb +163 -91
  38. data/spec/faraday/error_spec.rb +31 -6
  39. data/spec/faraday/middleware_registry_spec.rb +31 -0
  40. data/spec/faraday/middleware_spec.rb +18 -0
  41. data/spec/faraday/options/env_spec.rb +8 -2
  42. data/spec/faraday/options/options_spec.rb +1 -1
  43. data/spec/faraday/options/proxy_options_spec.rb +8 -0
  44. data/spec/faraday/params_encoders/nested_spec.rb +8 -0
  45. data/spec/faraday/rack_builder_spec.rb +26 -54
  46. data/spec/faraday/request/authorization_spec.rb +50 -28
  47. data/spec/faraday/request/instrumentation_spec.rb +5 -7
  48. data/spec/faraday/request/json_spec.rb +135 -0
  49. data/spec/faraday/request/url_encoded_spec.rb +12 -2
  50. data/spec/faraday/request_spec.rb +5 -15
  51. data/spec/faraday/response/json_spec.rb +117 -0
  52. data/spec/faraday/response/logger_spec.rb +38 -0
  53. data/spec/faraday/response/raise_error_spec.rb +35 -5
  54. data/spec/faraday/response_spec.rb +3 -1
  55. data/spec/faraday/utils/headers_spec.rb +22 -4
  56. data/spec/faraday/utils_spec.rb +63 -1
  57. data/spec/faraday_spec.rb +8 -4
  58. data/spec/support/fake_safe_buffer.rb +1 -1
  59. data/spec/support/helper_methods.rb +0 -37
  60. data/spec/support/shared_examples/adapter.rb +2 -2
  61. data/spec/support/shared_examples/request_method.rb +22 -21
  62. metadata +19 -134
  63. data/lib/faraday/adapter/typhoeus.rb +0 -15
  64. data/lib/faraday/autoload.rb +0 -87
  65. data/lib/faraday/dependency_loader.rb +0 -37
  66. data/lib/faraday/file_part.rb +0 -128
  67. data/lib/faraday/param_part.rb +0 -53
  68. data/lib/faraday/request/basic_authentication.rb +0 -20
  69. data/lib/faraday/request/multipart.rb +0 -106
  70. data/lib/faraday/request/retry.rb +0 -239
  71. data/lib/faraday/request/token_authentication.rb +0 -20
  72. data/spec/faraday/adapter/em_http_spec.rb +0 -49
  73. data/spec/faraday/adapter/em_synchrony_spec.rb +0 -18
  74. data/spec/faraday/adapter/excon_spec.rb +0 -49
  75. data/spec/faraday/adapter/httpclient_spec.rb +0 -73
  76. data/spec/faraday/adapter/net_http_spec.rb +0 -64
  77. data/spec/faraday/adapter/patron_spec.rb +0 -18
  78. data/spec/faraday/adapter/rack_spec.rb +0 -8
  79. data/spec/faraday/adapter/typhoeus_spec.rb +0 -7
  80. data/spec/faraday/composite_read_io_spec.rb +0 -80
  81. data/spec/faraday/request/multipart_spec.rb +0 -302
  82. data/spec/faraday/request/retry_spec.rb +0 -242
  83. data/spec/faraday/response/middleware_spec.rb +0 -68
  84. data/spec/support/webmock_rack_app.rb +0 -68
@@ -1,80 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'stringio'
4
-
5
- RSpec.describe Faraday::CompositeReadIO do
6
- Part = Struct.new(:to_io) do
7
- def length
8
- to_io.string.length
9
- end
10
- end
11
-
12
- def part(str)
13
- Part.new StringIO.new(str)
14
- end
15
-
16
- def composite_io(*parts)
17
- Faraday::CompositeReadIO.new(*parts)
18
- end
19
-
20
- context 'with empty composite_io' do
21
- subject { composite_io }
22
-
23
- it { expect(subject.length).to eq(0) }
24
- it { expect(subject.read).to eq('') }
25
- it { expect(subject.read(1)).to be_nil }
26
- end
27
-
28
- context 'with empty parts' do
29
- subject { composite_io(part(''), part('')) }
30
-
31
- it { expect(subject.length).to eq(0) }
32
- it { expect(subject.read).to eq('') }
33
- it { expect(subject.read(1)).to be_nil }
34
- end
35
-
36
- context 'with 2 parts' do
37
- subject { composite_io(part('abcd'), part('1234')) }
38
-
39
- it { expect(subject.length).to eq(8) }
40
- it { expect(subject.read).to eq('abcd1234') }
41
- it 'allows to read in chunks' do
42
- expect(subject.read(3)).to eq('abc')
43
- expect(subject.read(3)).to eq('d12')
44
- expect(subject.read(3)).to eq('34')
45
- expect(subject.read(3)).to be_nil
46
- end
47
- it 'allows to rewind while reading in chunks' do
48
- expect(subject.read(3)).to eq('abc')
49
- expect(subject.read(3)).to eq('d12')
50
- subject.rewind
51
- expect(subject.read(3)).to eq('abc')
52
- expect(subject.read(5)).to eq('d1234')
53
- expect(subject.read(3)).to be_nil
54
- subject.rewind
55
- expect(subject.read(2)).to eq('ab')
56
- end
57
- end
58
-
59
- context 'with mix of empty and non-empty parts' do
60
- subject { composite_io(part(''), part('abcd'), part(''), part('1234'), part('')) }
61
-
62
- it 'allows to read in chunks' do
63
- expect(subject.read(6)).to eq('abcd12')
64
- expect(subject.read(6)).to eq('34')
65
- expect(subject.read(6)).to be_nil
66
- end
67
- end
68
-
69
- context 'with utf8 multibyte part' do
70
- subject { composite_io(part("\x86"), part('ファイル')) }
71
-
72
- it { expect(subject.read).to eq(String.new("\x86\xE3\x83\x95\xE3\x82\xA1\xE3\x82\xA4\xE3\x83\xAB", encoding: 'BINARY')) }
73
- it 'allows to read in chunks' do
74
- expect(subject.read(3)).to eq(String.new("\x86\xE3\x83", encoding: 'BINARY'))
75
- expect(subject.read(3)).to eq(String.new("\x95\xE3\x82", encoding: 'BINARY'))
76
- expect(subject.read(8)).to eq(String.new("\xA1\xE3\x82\xA4\xE3\x83\xAB", encoding: 'BINARY'))
77
- expect(subject.read(3)).to be_nil
78
- end
79
- end
80
- end
@@ -1,302 +0,0 @@
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::UploadIO.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::UploadIO.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::UploadIO.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::UploadIO.new(io, 'application/pdf'),
288
- Faraday::UploadIO.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
@@ -1,242 +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
- end
36
-
37
- context 'when an expected error happens' do
38
- let(:callback) { -> { raise Errno::ETIMEDOUT } }
39
-
40
- before do
41
- @started = Time.now
42
- expect { conn.get('/unstable') }.to raise_error(Errno::ETIMEDOUT)
43
- end
44
-
45
- it { expect(times_called).to eq(3) }
46
-
47
- context 'and legacy max_retry set to 1' do
48
- let(:options) { [1] }
49
-
50
- it { expect(times_called).to eq(2) }
51
- end
52
-
53
- context 'and legacy max_retry set to -9' do
54
- let(:options) { [-9] }
55
-
56
- it { expect(times_called).to eq(1) }
57
- end
58
-
59
- context 'and new max_retry set to 3' do
60
- let(:options) { [{ max: 3 }] }
61
-
62
- it { expect(times_called).to eq(4) }
63
- end
64
-
65
- context 'and new max_retry set to -9' do
66
- let(:options) { [{ max: -9 }] }
67
-
68
- it { expect(times_called).to eq(1) }
69
- end
70
-
71
- context 'and both max_retry and interval are set' do
72
- let(:options) { [{ max: 2, interval: 0.1 }] }
73
-
74
- it { expect(Time.now - @started).to be_within(0.04).of(0.2) }
75
- end
76
- end
77
-
78
- context 'when no exception raised' do
79
- let(:options) { [{ max: 1, retry_statuses: 429 }] }
80
-
81
- before { conn.get('/unstable') }
82
-
83
- context 'and response code is in retry_statuses' do
84
- let(:callback) { -> { [429, {}, ''] } }
85
-
86
- it { expect(times_called).to eq(2) }
87
- end
88
-
89
- context 'and response code is not in retry_statuses' do
90
- let(:callback) { -> { [503, {}, ''] } }
91
-
92
- it { expect(times_called).to eq(1) }
93
- end
94
- end
95
-
96
- describe '#calculate_retry_interval' do
97
- context 'with exponential backoff' do
98
- let(:options) { { max: 5, interval: 0.1, backoff_factor: 2 } }
99
- let(:middleware) { Faraday::Request::Retry.new(nil, options) }
100
-
101
- it { expect(middleware.send(:calculate_retry_interval, 5)).to eq(0.1) }
102
- it { expect(middleware.send(:calculate_retry_interval, 4)).to eq(0.2) }
103
- it { expect(middleware.send(:calculate_retry_interval, 3)).to eq(0.4) }
104
- end
105
-
106
- context 'with exponential backoff and max_interval' do
107
- let(:options) { { max: 5, interval: 0.1, backoff_factor: 2, max_interval: 0.3 } }
108
- let(:middleware) { Faraday::Request::Retry.new(nil, options) }
109
-
110
- it { expect(middleware.send(:calculate_retry_interval, 5)).to eq(0.1) }
111
- it { expect(middleware.send(:calculate_retry_interval, 4)).to eq(0.2) }
112
- it { expect(middleware.send(:calculate_retry_interval, 3)).to eq(0.3) }
113
- it { expect(middleware.send(:calculate_retry_interval, 2)).to eq(0.3) }
114
- end
115
-
116
- context 'with exponential backoff and interval_randomness' do
117
- let(:options) { { max: 2, interval: 0.1, interval_randomness: 0.05 } }
118
- let(:middleware) { Faraday::Request::Retry.new(nil, options) }
119
-
120
- it { expect(middleware.send(:calculate_retry_interval, 2)).to be_between(0.1, 0.105) }
121
- end
122
- end
123
-
124
- context 'when method is not idempotent' do
125
- let(:callback) { -> { raise Errno::ETIMEDOUT } }
126
-
127
- before { expect { conn.post('/unstable') }.to raise_error(Errno::ETIMEDOUT) }
128
-
129
- it { expect(times_called).to eq(1) }
130
- end
131
-
132
- describe 'retry_if option' do
133
- let(:callback) { -> { raise Errno::ETIMEDOUT } }
134
- let(:options) { [{ retry_if: @check }] }
135
-
136
- it 'retries if retry_if block always returns true' do
137
- body = { foo: :bar }
138
- @check = ->(_, _) { true }
139
- expect { conn.post('/unstable', body) }.to raise_error(Errno::ETIMEDOUT)
140
- expect(times_called).to eq(3)
141
- expect(calls.all? { |env| env[:body] == body }).to be_truthy
142
- end
143
-
144
- it 'does not retry if retry_if block returns false checking env' do
145
- @check = ->(env, _) { env[:method] != :post }
146
- expect { conn.post('/unstable') }.to raise_error(Errno::ETIMEDOUT)
147
- expect(times_called).to eq(1)
148
- end
149
-
150
- it 'does not retry if retry_if block returns false checking exception' do
151
- @check = ->(_, exception) { !exception.is_a?(Errno::ETIMEDOUT) }
152
- expect { conn.post('/unstable') }.to raise_error(Errno::ETIMEDOUT)
153
- expect(times_called).to eq(1)
154
- end
155
-
156
- it 'FilePart: should rewind files on retry' do
157
- io = StringIO.new('Test data')
158
- filepart = Faraday::FilePart.new(io, 'application/octet/stream')
159
-
160
- rewound = 0
161
- rewind = -> { rewound += 1 }
162
-
163
- @check = ->(_, _) { true }
164
- allow(filepart).to receive(:rewind, &rewind)
165
- expect { conn.post('/unstable', file: filepart) }.to raise_error(Errno::ETIMEDOUT)
166
- expect(times_called).to eq(3)
167
- expect(rewound).to eq(2)
168
- end
169
-
170
- it 'UploadIO: should rewind files on retry' do
171
- io = StringIO.new('Test data')
172
- upload_io = Faraday::UploadIO.new(io, 'application/octet/stream')
173
-
174
- rewound = 0
175
- rewind = -> { rewound += 1 }
176
-
177
- @check = ->(_, _) { true }
178
- allow(upload_io).to receive(:rewind, &rewind)
179
- expect { conn.post('/unstable', file: upload_io) }.to raise_error(Errno::ETIMEDOUT)
180
- expect(times_called).to eq(3)
181
- expect(rewound).to eq(2)
182
- end
183
-
184
- context 'when explicitly specifying methods to retry' do
185
- let(:options) { [{ retry_if: @check, methods: [:post] }] }
186
-
187
- it 'does not call retry_if for specified methods' do
188
- @check = ->(_, _) { raise 'this should have never been called' }
189
- expect { conn.post('/unstable') }.to raise_error(Errno::ETIMEDOUT)
190
- expect(times_called).to eq(3)
191
- end
192
- end
193
-
194
- context 'with empty list of methods to retry' do
195
- let(:options) { [{ retry_if: @check, methods: [] }] }
196
-
197
- it 'calls retry_if for all methods' do
198
- @check = ->(_, _) { calls.size < 2 }
199
- expect { conn.get('/unstable') }.to raise_error(Errno::ETIMEDOUT)
200
- expect(times_called).to eq(2)
201
- end
202
- end
203
- end
204
-
205
- describe 'retry_after header support' do
206
- let(:callback) { -> { [504, headers, ''] } }
207
- let(:elapsed) { Time.now - @started }
208
-
209
- before do
210
- @started = Time.now
211
- conn.get('/unstable')
212
- end
213
-
214
- context 'when retry_after bigger than interval' do
215
- let(:headers) { { 'Retry-After' => '0.5' } }
216
- let(:options) { [{ max: 1, interval: 0.1, retry_statuses: 504 }] }
217
-
218
- it { expect(elapsed).to be > 0.5 }
219
- end
220
-
221
- context 'when retry_after smaller than interval' do
222
- let(:headers) { { 'Retry-After' => '0.1' } }
223
- let(:options) { [{ max: 1, interval: 0.2, retry_statuses: 504 }] }
224
-
225
- it { expect(elapsed).to be > 0.2 }
226
- end
227
-
228
- context 'when retry_after is a timestamp' do
229
- let(:headers) { { 'Retry-After' => (Time.now.utc + 2).strftime('%a, %d %b %Y %H:%M:%S GMT') } }
230
- let(:options) { [{ max: 1, interval: 0.1, retry_statuses: 504 }] }
231
-
232
- it { expect(elapsed).to be > 1 }
233
- end
234
-
235
- context 'when retry_after is bigger than max_interval' do
236
- let(:headers) { { 'Retry-After' => (Time.now.utc + 20).strftime('%a, %d %b %Y %H:%M:%S GMT') } }
237
- let(:options) { [{ max: 2, interval: 0.1, max_interval: 5, retry_statuses: 504 }] }
238
-
239
- it { expect(times_called).to eq(1) }
240
- end
241
- end
242
- end
@@ -1,68 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.describe Faraday::Response::Middleware do
4
- let(:conn) do
5
- Faraday.new do |b|
6
- b.use custom_middleware
7
- b.adapter :test do |stub|
8
- stub.get('ok') { [200, { 'Content-Type' => 'text/html' }, '<body></body>'] }
9
- stub.get('not_modified') { [304, nil, nil] }
10
- stub.get('no_content') { [204, nil, nil] }
11
- end
12
- end
13
- end
14
-
15
- context 'with a custom ResponseMiddleware' do
16
- let(:custom_middleware) do
17
- Class.new(Faraday::Response::Middleware) do
18
- def parse(body)
19
- body.upcase
20
- end
21
- end
22
- end
23
-
24
- it 'parses the response' do
25
- expect(conn.get('ok').body).to eq('<BODY></BODY>')
26
- end
27
- end
28
-
29
- context 'with a custom ResponseMiddleware and private parse' do
30
- let(:custom_middleware) do
31
- Class.new(Faraday::Response::Middleware) do
32
- private
33
-
34
- def parse(body)
35
- body.upcase
36
- end
37
- end
38
- end
39
-
40
- it 'parses the response' do
41
- expect(conn.get('ok').body).to eq('<BODY></BODY>')
42
- end
43
- end
44
-
45
- context 'with a custom ResponseMiddleware but empty response' do
46
- let(:custom_middleware) do
47
- Class.new(Faraday::Response::Middleware) do
48
- def parse(_body)
49
- raise 'this should not be called'
50
- end
51
- end
52
- end
53
-
54
- it 'raises exception for 200 responses' do
55
- expect { conn.get('ok') }.to raise_error(StandardError)
56
- end
57
-
58
- it 'doesn\'t call the middleware for 204 responses' do
59
- expect_any_instance_of(custom_middleware).not_to receive(:parse)
60
- expect(conn.get('no_content').body).to be_nil
61
- end
62
-
63
- it 'doesn\'t call the middleware for 304 responses' do
64
- expect_any_instance_of(custom_middleware).not_to receive(:parse)
65
- expect(conn.get('not_modified').body).to be_nil
66
- end
67
- end
68
- end