faraday 2.7.0 → 2.13.4

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1 -1
  3. data/LICENSE.md +1 -1
  4. data/README.md +29 -17
  5. data/Rakefile +6 -1
  6. data/lib/faraday/adapter/test.rb +20 -7
  7. data/lib/faraday/adapter.rb +2 -3
  8. data/lib/faraday/connection.rb +35 -32
  9. data/lib/faraday/encoders/nested_params_encoder.rb +1 -1
  10. data/lib/faraday/error.rb +59 -7
  11. data/lib/faraday/logging/formatter.rb +18 -20
  12. data/lib/faraday/middleware.rb +40 -1
  13. data/lib/faraday/options/connection_options.rb +7 -6
  14. data/lib/faraday/options/env.rb +68 -63
  15. data/lib/faraday/options/proxy_options.rb +11 -5
  16. data/lib/faraday/options/request_options.rb +7 -6
  17. data/lib/faraday/options/ssl_options.rb +57 -50
  18. data/lib/faraday/options.rb +4 -3
  19. data/lib/faraday/rack_builder.rb +21 -25
  20. data/lib/faraday/request/instrumentation.rb +3 -1
  21. data/lib/faraday/request/json.rb +18 -3
  22. data/lib/faraday/request.rb +10 -7
  23. data/lib/faraday/response/json.rb +21 -1
  24. data/lib/faraday/response/logger.rb +7 -5
  25. data/lib/faraday/response/raise_error.rb +36 -17
  26. data/lib/faraday/response.rb +2 -1
  27. data/lib/faraday/utils/headers.rb +8 -2
  28. data/lib/faraday/utils.rb +3 -4
  29. data/lib/faraday/version.rb +1 -1
  30. data/lib/faraday.rb +2 -1
  31. data/spec/faraday/adapter/test_spec.rb +29 -0
  32. data/spec/faraday/connection_spec.rb +17 -2
  33. data/spec/faraday/error_spec.rb +122 -7
  34. data/spec/faraday/middleware_spec.rb +143 -0
  35. data/spec/faraday/options/options_spec.rb +1 -1
  36. data/spec/faraday/options/proxy_options_spec.rb +35 -0
  37. data/spec/faraday/params_encoders/nested_spec.rb +2 -1
  38. data/spec/faraday/request/json_spec.rb +88 -0
  39. data/spec/faraday/response/json_spec.rb +89 -0
  40. data/spec/faraday/response/logger_spec.rb +50 -5
  41. data/spec/faraday/response/raise_error_spec.rb +112 -9
  42. data/spec/faraday/response_spec.rb +3 -1
  43. data/spec/faraday/utils/headers_spec.rb +9 -0
  44. data/spec/faraday/utils_spec.rb +3 -1
  45. data/spec/faraday_spec.rb +10 -4
  46. data/spec/spec_helper.rb +6 -5
  47. data/spec/support/faraday_middleware_subclasses.rb +18 -0
  48. metadata +26 -14
@@ -73,6 +73,30 @@ RSpec.describe Faraday::Request::Json do
73
73
  end
74
74
  end
75
75
 
76
+ context 'true body' do
77
+ let(:result) { process(true) }
78
+
79
+ it 'encodes body' do
80
+ expect(result_body).to eq('true')
81
+ end
82
+
83
+ it 'adds content type' do
84
+ expect(result_type).to eq('application/json')
85
+ end
86
+ end
87
+
88
+ context 'false body' do
89
+ let(:result) { process(false) }
90
+
91
+ it 'encodes body' do
92
+ expect(result_body).to eq('false')
93
+ end
94
+
95
+ it 'adds content type' do
96
+ expect(result_type).to eq('application/json')
97
+ end
98
+ end
99
+
76
100
  context 'object body with json type' do
77
101
  let(:result) { process({ a: 1 }, 'application/json; charset=utf-8') }
78
102
 
@@ -108,4 +132,68 @@ RSpec.describe Faraday::Request::Json do
108
132
  expect(result_type).to eq('application/xml; charset=utf-8')
109
133
  end
110
134
  end
135
+
136
+ context 'with encoder' do
137
+ let(:encoder) do
138
+ double('Encoder').tap do |e|
139
+ allow(e).to receive(:dump) { |s, opts| JSON.generate(s, opts) }
140
+ end
141
+ end
142
+
143
+ let(:result) { process(a: 1) }
144
+
145
+ context 'when encoder is passed as object' do
146
+ let(:middleware) { described_class.new(->(env) { Faraday::Response.new(env) }, { encoder: encoder }) }
147
+
148
+ it 'calls specified JSON encoder\'s dump method' do
149
+ expect(encoder).to receive(:dump).with({ a: 1 })
150
+
151
+ result
152
+ end
153
+
154
+ it 'encodes body' do
155
+ expect(result_body).to eq('{"a":1}')
156
+ end
157
+
158
+ it 'adds content type' do
159
+ expect(result_type).to eq('application/json')
160
+ end
161
+ end
162
+
163
+ context 'when encoder is passed as an object-method pair' do
164
+ let(:middleware) { described_class.new(->(env) { Faraday::Response.new(env) }, { encoder: [encoder, :dump] }) }
165
+
166
+ it 'calls specified JSON encoder' do
167
+ expect(encoder).to receive(:dump).with({ a: 1 })
168
+
169
+ result
170
+ end
171
+
172
+ it 'encodes body' do
173
+ expect(result_body).to eq('{"a":1}')
174
+ end
175
+
176
+ it 'adds content type' do
177
+ expect(result_type).to eq('application/json')
178
+ end
179
+ end
180
+
181
+ context 'when encoder is not passed' do
182
+ let(:middleware) { described_class.new(->(env) { Faraday::Response.new(env) }) }
183
+
184
+ it 'calls JSON.generate' do
185
+ expect(JSON).to receive(:generate).with({ a: 1 })
186
+
187
+ result
188
+ end
189
+
190
+ it 'encodes body' do
191
+ expect(result_body).to eq('{"a":1}')
192
+ end
193
+
194
+ it 'adds content type' do
195
+ expect(result_type).to eq('application/json')
196
+ end
197
+ end
198
+ end
111
199
  end
@@ -114,4 +114,93 @@ RSpec.describe Faraday::Response::Json, type: :response do
114
114
  expect(response.body).to eq(result)
115
115
  end
116
116
  end
117
+
118
+ context 'with decoder' do
119
+ let(:decoder) do
120
+ double('Decoder').tap do |e|
121
+ allow(e).to receive(:load) { |s, opts| JSON.parse(s, opts) }
122
+ end
123
+ end
124
+
125
+ let(:body) { '{"a": 1}' }
126
+ let(:result) { { a: 1 } }
127
+
128
+ context 'when decoder is passed as object' do
129
+ let(:options) do
130
+ {
131
+ parser_options: {
132
+ decoder: decoder,
133
+ option: :option_value,
134
+ symbolize_names: true
135
+ }
136
+ }
137
+ end
138
+
139
+ it 'passes relevant options to specified decoder\'s load method' do
140
+ expect(decoder).to receive(:load)
141
+ .with(body, { option: :option_value, symbolize_names: true })
142
+ .and_return(result)
143
+
144
+ response = process(body)
145
+ expect(response.body).to eq(result)
146
+ end
147
+ end
148
+
149
+ context 'when decoder is passed as an object-method pair' do
150
+ let(:options) do
151
+ {
152
+ parser_options: {
153
+ decoder: [decoder, :load],
154
+ option: :option_value,
155
+ symbolize_names: true
156
+ }
157
+ }
158
+ end
159
+
160
+ it 'passes relevant options to specified decoder\'s method' do
161
+ expect(decoder).to receive(:load)
162
+ .with(body, { option: :option_value, symbolize_names: true })
163
+ .and_return(result)
164
+
165
+ response = process(body)
166
+ expect(response.body).to eq(result)
167
+ end
168
+ end
169
+
170
+ context 'when decoder is not passed' do
171
+ let(:options) do
172
+ {
173
+ parser_options: {
174
+ symbolize_names: true
175
+ }
176
+ }
177
+ end
178
+
179
+ it 'passes relevant options to JSON parse' do
180
+ expect(JSON).to receive(:parse)
181
+ .with(body, { symbolize_names: true })
182
+ .and_return(result)
183
+
184
+ response = process(body)
185
+ expect(response.body).to eq(result)
186
+ end
187
+
188
+ it 'passes relevant options to JSON parse even when nil responds to :load' do
189
+ original_allow_message_expectations_on_nil = RSpec::Mocks.configuration.allow_message_expectations_on_nil
190
+ RSpec::Mocks.configuration.allow_message_expectations_on_nil = true
191
+ allow(nil).to receive(:respond_to?)
192
+ .with(:load)
193
+ .and_return(true)
194
+
195
+ expect(JSON).to receive(:parse)
196
+ .with(body, { symbolize_names: true })
197
+ .and_return(result)
198
+
199
+ response = process(body)
200
+ expect(response.body).to eq(result)
201
+ ensure
202
+ RSpec::Mocks.configuration.allow_message_expectations_on_nil = original_allow_message_expectations_on_nil
203
+ end
204
+ end
205
+ end
117
206
  end
@@ -25,6 +25,7 @@ RSpec.describe Faraday::Response::Logger do
25
25
  stubs.get('/filtered_headers') { [200, { 'Content-Type' => 'text/html' }, 'headers response'] }
26
26
  stubs.get('/filtered_params') { [200, { 'Content-Type' => 'text/html' }, 'params response'] }
27
27
  stubs.get('/filtered_url') { [200, { 'Content-Type' => 'text/html' }, 'url response'] }
28
+ stubs.get('/connection_failed') { raise Faraday::ConnectionFailed, 'Failed to open TCP connection' }
28
29
  end
29
30
  end
30
31
  end
@@ -54,6 +55,26 @@ RSpec.describe Faraday::Response::Logger do
54
55
  end
55
56
  end
56
57
 
58
+ context 'when logger with program name' do
59
+ let(:logger) { Logger.new(string_io, progname: 'my_best_program') }
60
+
61
+ it 'logs with program name' do
62
+ conn.get '/hello'
63
+
64
+ expect(string_io.string).to match('-- my_best_program: request:')
65
+ expect(string_io.string).to match('-- my_best_program: response:')
66
+ end
67
+ end
68
+
69
+ context 'when logger without program name' do
70
+ it 'logs without program name' do
71
+ conn.get '/hello'
72
+
73
+ expect(string_io.string).to match('-- : request:')
74
+ expect(string_io.string).to match('-- : response:')
75
+ end
76
+ end
77
+
57
78
  context 'with default formatter' do
58
79
  let(:formatter) { instance_double(Faraday::Logging::Formatter, request: true, response: true, filter: []) }
59
80
 
@@ -68,7 +89,7 @@ RSpec.describe Faraday::Response::Logger do
68
89
  context 'when no route' do
69
90
  it 'delegates logging to the formatter' do
70
91
  expect(formatter).to receive(:request).with(an_instance_of(Faraday::Env))
71
- expect(formatter).to receive(:error).with(an_instance_of(Faraday::Adapter::Test::Stubs::NotFound))
92
+ expect(formatter).to receive(:exception).with(an_instance_of(Faraday::Adapter::Test::Stubs::NotFound))
72
93
 
73
94
  expect { conn.get '/noroute' }.to raise_error(Faraday::Adapter::Test::Stubs::NotFound)
74
95
  end
@@ -168,7 +189,7 @@ RSpec.describe Faraday::Response::Logger do
168
189
  context 'when logging request body' do
169
190
  let(:logger_options) { { bodies: { request: true } } }
170
191
 
171
- it 'log only request body' do
192
+ it 'logs only request body' do
172
193
  conn.post '/ohyes', 'name=Tamago', accept: 'text/html'
173
194
  expect(string_io.string).to match(%(name=Tamago))
174
195
  expect(string_io.string).not_to match(%(pebbles))
@@ -178,7 +199,7 @@ RSpec.describe Faraday::Response::Logger do
178
199
  context 'when logging response body' do
179
200
  let(:logger_options) { { bodies: { response: true } } }
180
201
 
181
- it 'log only response body' do
202
+ it 'logs only response body' do
182
203
  conn.post '/ohyes', 'name=Hamachi', accept: 'text/html'
183
204
  expect(string_io.string).to match(%(pebbles))
184
205
  expect(string_io.string).not_to match(%(name=Hamachi))
@@ -188,13 +209,13 @@ RSpec.describe Faraday::Response::Logger do
188
209
  context 'when logging request and response bodies' do
189
210
  let(:logger_options) { { bodies: true } }
190
211
 
191
- it 'log request and response body' do
212
+ it 'logs request and response body' do
192
213
  conn.post '/ohyes', 'name=Ebi', accept: 'text/html'
193
214
  expect(string_io.string).to match(%(name=Ebi))
194
215
  expect(string_io.string).to match(%(pebbles))
195
216
  end
196
217
 
197
- it 'log response body object' do
218
+ it 'logs response body object' do
198
219
  conn.get '/rubbles', nil, accept: 'text/html'
199
220
  expect(string_io.string).to match(%([\"Barney\", \"Betty\", \"Bam Bam\"]\n))
200
221
  end
@@ -207,6 +228,21 @@ RSpec.describe Faraday::Response::Logger do
207
228
  end
208
229
  end
209
230
 
231
+ context 'when bodies are logged by default' do
232
+ before do
233
+ described_class.default_options = { bodies: true }
234
+ end
235
+
236
+ it 'logs response body' do
237
+ conn.post '/ohai'
238
+ expect(string_io.string).to match(%(fred))
239
+ end
240
+
241
+ after do
242
+ described_class.default_options = { bodies: false }
243
+ end
244
+ end
245
+
210
246
  context 'when logging errors' do
211
247
  let(:logger_options) { { errors: true } }
212
248
 
@@ -216,6 +252,15 @@ RSpec.describe Faraday::Response::Logger do
216
252
  end
217
253
  end
218
254
 
255
+ context 'when logging headers and errors' do
256
+ let(:logger_options) { { headers: true, errors: true } }
257
+
258
+ it 'logs error message' do
259
+ expect { conn.get '/connection_failed' }.to raise_error(Faraday::ConnectionFailed)
260
+ expect(string_io.string).to match(%(Failed to open TCP connection))
261
+ end
262
+ end
263
+
219
264
  context 'when using log_level' do
220
265
  let(:logger_options) { { bodies: true, log_level: :debug } }
221
266
 
@@ -11,8 +11,10 @@ RSpec.describe Faraday::Response::RaiseError do
11
11
  stub.get('forbidden') { [403, { 'X-Reason' => 'because' }, 'keep looking'] }
12
12
  stub.get('not-found') { [404, { 'X-Reason' => 'because' }, 'keep looking'] }
13
13
  stub.get('proxy-error') { [407, { 'X-Reason' => 'because' }, 'keep looking'] }
14
+ stub.get('request-timeout') { [408, { 'X-Reason' => 'because' }, 'keep looking'] }
14
15
  stub.get('conflict') { [409, { 'X-Reason' => 'because' }, 'keep looking'] }
15
16
  stub.get('unprocessable-entity') { [422, { 'X-Reason' => 'because' }, 'keep looking'] }
17
+ stub.get('too-many-requests') { [429, { 'X-Reason' => 'because' }, 'keep looking'] }
16
18
  stub.get('4xx') { [499, { 'X-Reason' => 'because' }, 'keep looking'] }
17
19
  stub.get('nil-status') { [nil, { 'X-Reason' => 'nil' }, 'fail'] }
18
20
  stub.get('server-error') { [500, { 'X-Error' => 'bailout' }, 'fail'] }
@@ -26,7 +28,7 @@ RSpec.describe Faraday::Response::RaiseError do
26
28
 
27
29
  it 'raises Faraday::BadRequestError for 400 responses' do
28
30
  expect { conn.get('bad-request') }.to raise_error(Faraday::BadRequestError) do |ex|
29
- expect(ex.message).to eq('the server responded with status 400')
31
+ expect(ex.message).to eq('the server responded with status 400 for GET http:/bad-request')
30
32
  expect(ex.response[:headers]['X-Reason']).to eq('because')
31
33
  expect(ex.response[:status]).to eq(400)
32
34
  expect(ex.response_status).to eq(400)
@@ -37,7 +39,7 @@ RSpec.describe Faraday::Response::RaiseError do
37
39
 
38
40
  it 'raises Faraday::UnauthorizedError for 401 responses' do
39
41
  expect { conn.get('unauthorized') }.to raise_error(Faraday::UnauthorizedError) do |ex|
40
- expect(ex.message).to eq('the server responded with status 401')
42
+ expect(ex.message).to eq('the server responded with status 401 for GET http:/unauthorized')
41
43
  expect(ex.response[:headers]['X-Reason']).to eq('because')
42
44
  expect(ex.response[:status]).to eq(401)
43
45
  expect(ex.response_status).to eq(401)
@@ -48,7 +50,7 @@ RSpec.describe Faraday::Response::RaiseError do
48
50
 
49
51
  it 'raises Faraday::ForbiddenError for 403 responses' do
50
52
  expect { conn.get('forbidden') }.to raise_error(Faraday::ForbiddenError) do |ex|
51
- expect(ex.message).to eq('the server responded with status 403')
53
+ expect(ex.message).to eq('the server responded with status 403 for GET http:/forbidden')
52
54
  expect(ex.response[:headers]['X-Reason']).to eq('because')
53
55
  expect(ex.response[:status]).to eq(403)
54
56
  expect(ex.response_status).to eq(403)
@@ -59,7 +61,7 @@ RSpec.describe Faraday::Response::RaiseError do
59
61
 
60
62
  it 'raises Faraday::ResourceNotFound for 404 responses' do
61
63
  expect { conn.get('not-found') }.to raise_error(Faraday::ResourceNotFound) do |ex|
62
- expect(ex.message).to eq('the server responded with status 404')
64
+ expect(ex.message).to eq('the server responded with status 404 for GET http:/not-found')
63
65
  expect(ex.response[:headers]['X-Reason']).to eq('because')
64
66
  expect(ex.response[:status]).to eq(404)
65
67
  expect(ex.response_status).to eq(404)
@@ -79,9 +81,20 @@ RSpec.describe Faraday::Response::RaiseError do
79
81
  end
80
82
  end
81
83
 
84
+ it 'raises Faraday::RequestTimeoutError for 408 responses' do
85
+ expect { conn.get('request-timeout') }.to raise_error(Faraday::RequestTimeoutError) do |ex|
86
+ expect(ex.message).to eq('the server responded with status 408 for GET http:/request-timeout')
87
+ expect(ex.response[:headers]['X-Reason']).to eq('because')
88
+ expect(ex.response[:status]).to eq(408)
89
+ expect(ex.response_status).to eq(408)
90
+ expect(ex.response_body).to eq('keep looking')
91
+ expect(ex.response_headers['X-Reason']).to eq('because')
92
+ end
93
+ end
94
+
82
95
  it 'raises Faraday::ConflictError for 409 responses' do
83
96
  expect { conn.get('conflict') }.to raise_error(Faraday::ConflictError) do |ex|
84
- expect(ex.message).to eq('the server responded with status 409')
97
+ expect(ex.message).to eq('the server responded with status 409 for GET http:/conflict')
85
98
  expect(ex.response[:headers]['X-Reason']).to eq('because')
86
99
  expect(ex.response[:status]).to eq(409)
87
100
  expect(ex.response_status).to eq(409)
@@ -92,7 +105,7 @@ RSpec.describe Faraday::Response::RaiseError do
92
105
 
93
106
  it 'raises Faraday::UnprocessableEntityError for 422 responses' do
94
107
  expect { conn.get('unprocessable-entity') }.to raise_error(Faraday::UnprocessableEntityError) do |ex|
95
- expect(ex.message).to eq('the server responded with status 422')
108
+ expect(ex.message).to eq('the server responded with status 422 for GET http:/unprocessable-entity')
96
109
  expect(ex.response[:headers]['X-Reason']).to eq('because')
97
110
  expect(ex.response[:status]).to eq(422)
98
111
  expect(ex.response_status).to eq(422)
@@ -101,6 +114,17 @@ RSpec.describe Faraday::Response::RaiseError do
101
114
  end
102
115
  end
103
116
 
117
+ it 'raises Faraday::TooManyRequestsError for 429 responses' do
118
+ expect { conn.get('too-many-requests') }.to raise_error(Faraday::TooManyRequestsError) do |ex|
119
+ expect(ex.message).to eq('the server responded with status 429 for GET http:/too-many-requests')
120
+ expect(ex.response[:headers]['X-Reason']).to eq('because')
121
+ expect(ex.response[:status]).to eq(429)
122
+ expect(ex.response_status).to eq(429)
123
+ expect(ex.response_body).to eq('keep looking')
124
+ expect(ex.response_headers['X-Reason']).to eq('because')
125
+ end
126
+ end
127
+
104
128
  it 'raises Faraday::NilStatusError for nil status in response' do
105
129
  expect { conn.get('nil-status') }.to raise_error(Faraday::NilStatusError) do |ex|
106
130
  expect(ex.message).to eq('http status could not be derived from the server response')
@@ -114,7 +138,7 @@ RSpec.describe Faraday::Response::RaiseError do
114
138
 
115
139
  it 'raises Faraday::ClientError for other 4xx responses' do
116
140
  expect { conn.get('4xx') }.to raise_error(Faraday::ClientError) do |ex|
117
- expect(ex.message).to eq('the server responded with status 499')
141
+ expect(ex.message).to eq('the server responded with status 499 for GET http:/4xx')
118
142
  expect(ex.response[:headers]['X-Reason']).to eq('because')
119
143
  expect(ex.response[:status]).to eq(499)
120
144
  expect(ex.response_status).to eq(499)
@@ -125,7 +149,7 @@ RSpec.describe Faraday::Response::RaiseError do
125
149
 
126
150
  it 'raises Faraday::ServerError for 500 responses' do
127
151
  expect { conn.get('server-error') }.to raise_error(Faraday::ServerError) do |ex|
128
- expect(ex.message).to eq('the server responded with status 500')
152
+ expect(ex.message).to eq('the server responded with status 500 for GET http:/server-error')
129
153
  expect(ex.response[:headers]['X-Error']).to eq('bailout')
130
154
  expect(ex.response[:status]).to eq(500)
131
155
  expect(ex.response_status).to eq(500)
@@ -137,7 +161,7 @@ RSpec.describe Faraday::Response::RaiseError do
137
161
  describe 'request info' do
138
162
  let(:conn) do
139
163
  Faraday.new do |b|
140
- b.response :raise_error
164
+ b.response :raise_error, **middleware_options
141
165
  b.adapter :test do |stub|
142
166
  stub.post(url, request_body, request_headers) do
143
167
  [400, { 'X-Reason' => 'because' }, 'keep looking']
@@ -145,6 +169,7 @@ RSpec.describe Faraday::Response::RaiseError do
145
169
  end
146
170
  end
147
171
  end
172
+ let(:middleware_options) { {} }
148
173
  let(:request_body) { JSON.generate({ 'item' => 'sth' }) }
149
174
  let(:request_headers) { { 'Authorization' => 'Basic 123' } }
150
175
  let(:url_path) { 'request' }
@@ -168,5 +193,83 @@ RSpec.describe Faraday::Response::RaiseError do
168
193
  expect(ex.response[:request][:body]).to eq(request_body)
169
194
  end
170
195
  end
196
+
197
+ describe 'DEFAULT_OPTION: include_request' do
198
+ before(:each) do
199
+ Faraday::Response::RaiseError.instance_variable_set(:@default_options, nil)
200
+ Faraday::Middleware.instance_variable_set(:@default_options, nil)
201
+ end
202
+
203
+ after(:all) do
204
+ Faraday::Response::RaiseError.instance_variable_set(:@default_options, nil)
205
+ Faraday::Middleware.instance_variable_set(:@default_options, nil)
206
+ end
207
+
208
+ context 'when RaiseError DEFAULT_OPTION (include_request: true) is used' do
209
+ it 'includes request info in the exception' do
210
+ expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
211
+ expect(ex.response.keys).to contain_exactly(
212
+ :status,
213
+ :headers,
214
+ :body,
215
+ :request
216
+ )
217
+ end
218
+ end
219
+ end
220
+
221
+ context 'when application sets default_options `include_request: false`' do
222
+ before(:each) do
223
+ Faraday::Response::RaiseError.default_options = { include_request: false }
224
+ end
225
+
226
+ context 'and when include_request option is omitted' do
227
+ it 'does not include request info in the exception' do
228
+ expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
229
+ expect(ex.response.keys).to contain_exactly(
230
+ :status,
231
+ :headers,
232
+ :body
233
+ )
234
+ end
235
+ end
236
+ end
237
+
238
+ context 'and when include_request option is explicitly set for instance' do
239
+ let(:middleware_options) { { include_request: true } }
240
+
241
+ it 'includes request info in the exception' do
242
+ expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
243
+ expect(ex.response.keys).to contain_exactly(
244
+ :status,
245
+ :headers,
246
+ :body,
247
+ :request
248
+ )
249
+ end
250
+ end
251
+ end
252
+ end
253
+ end
254
+ end
255
+
256
+ describe 'allowing certain status codes' do
257
+ let(:conn) do
258
+ Faraday.new do |b|
259
+ b.response :raise_error, allowed_statuses: [404]
260
+ b.adapter :test do |stub|
261
+ stub.get('bad-request') { [400, { 'X-Reason' => 'because' }, 'keep looking'] }
262
+ stub.get('not-found') { [404, { 'X-Reason' => 'because' }, 'keep looking'] }
263
+ end
264
+ end
265
+ end
266
+
267
+ it 'raises an error for status codes that are not explicitly allowed' do
268
+ expect { conn.get('bad-request') }.to raise_error(Faraday::BadRequestError)
269
+ end
270
+
271
+ it 'does not raise an error for allowed status codes' do
272
+ expect { conn.get('not-found') }.not_to raise_error
273
+ end
171
274
  end
172
275
  end
@@ -4,7 +4,7 @@ RSpec.describe Faraday::Response do
4
4
  subject { Faraday::Response.new(env) }
5
5
 
6
6
  let(:env) do
7
- Faraday::Env.from(status: 404, body: 'yikes',
7
+ Faraday::Env.from(status: 404, body: 'yikes', url: Faraday::Utils.URI('https://lostisland.github.io/faraday'),
8
8
  response_headers: { 'Content-Type' => 'text/plain' })
9
9
  end
10
10
 
@@ -30,6 +30,7 @@ RSpec.describe Faraday::Response do
30
30
  it { expect(hash[:status]).to eq(subject.status) }
31
31
  it { expect(hash[:response_headers]).to eq(subject.headers) }
32
32
  it { expect(hash[:body]).to eq(subject.body) }
33
+ it { expect(hash[:url]).to eq(subject.env.url) }
33
34
  end
34
35
 
35
36
  describe 'marshal serialization support' do
@@ -45,6 +46,7 @@ RSpec.describe Faraday::Response do
45
46
  it { expect(loaded.env[:body]).to eq(env[:body]) }
46
47
  it { expect(loaded.env[:response_headers]).to eq(env[:response_headers]) }
47
48
  it { expect(loaded.env[:status]).to eq(env[:status]) }
49
+ it { expect(loaded.env[:url]).to eq(env[:url]) }
48
50
  end
49
51
 
50
52
  describe '#on_complete' do
@@ -56,6 +56,15 @@ RSpec.describe Faraday::Utils::Headers do
56
56
  it { expect(subject.delete('content-type')).to be_nil }
57
57
  end
58
58
 
59
+ describe '#dig' do
60
+ before { subject['Content-Type'] = 'application/json' }
61
+
62
+ it { expect(subject&.dig('Content-Type')).to eq('application/json') }
63
+ it { expect(subject&.dig('CONTENT-TYPE')).to eq('application/json') }
64
+ it { expect(subject&.dig(:content_type)).to eq('application/json') }
65
+ it { expect(subject&.dig('invalid')).to be_nil }
66
+ end
67
+
59
68
  describe '#parse' do
60
69
  context 'when response headers leave http status line out' do
61
70
  let(:headers) { "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n" }
@@ -103,7 +103,9 @@ RSpec.describe Faraday::Utils do
103
103
  version: '2',
104
104
  min_version: nil,
105
105
  max_version: nil,
106
- verify_hostname: nil
106
+ verify_hostname: nil,
107
+ hostname: nil,
108
+ ciphers: nil
107
109
  }
108
110
  end
109
111
 
data/spec/faraday_spec.rb CHANGED
@@ -18,10 +18,16 @@ RSpec.describe Faraday do
18
18
  end
19
19
 
20
20
  it 'uses method_missing on Faraday if there is no proxyable method' do
21
- expect { Faraday.this_method_does_not_exist }.to raise_error(
22
- NoMethodError,
23
- "undefined method `this_method_does_not_exist' for Faraday:Module"
24
- )
21
+ expected_message =
22
+ if RUBY_VERSION >= '3.4'
23
+ "undefined method 'this_method_does_not_exist' for module Faraday"
24
+ elsif RUBY_VERSION >= '3.3'
25
+ "undefined method `this_method_does_not_exist' for module Faraday"
26
+ else
27
+ "undefined method `this_method_does_not_exist' for Faraday:Module"
28
+ end
29
+
30
+ expect { Faraday.this_method_does_not_exist }.to raise_error(NoMethodError, expected_message)
25
31
  end
26
32
 
27
33
  it 'proxied methods can be accessed' do
data/spec/spec_helper.rb CHANGED
@@ -29,14 +29,15 @@ SimpleCov.start do
29
29
  minimum_coverage_by_file 26
30
30
  end
31
31
 
32
- # Ensure all /lib files are loaded
33
- # so they will be included in the test coverage report.
34
- Dir['./lib/**/*.rb'].sort.each { |file| require file }
35
-
36
32
  require 'faraday'
37
33
  require 'pry'
38
34
 
39
- Dir['./spec/support/**/*.rb'].sort.each { |f| require f }
35
+ # Ensure all /lib files are loaded
36
+ # so they will be included in the test coverage report.
37
+ Dir['./lib/**/*.rb'].each { |file| require file }
38
+
39
+ # Load all Rspec support files
40
+ Dir['./spec/support/**/*.rb'].each { |file| require file }
40
41
 
41
42
  RSpec.configure do |config|
42
43
  # rspec-expectations config goes here. You can use an alternate
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FaradayMiddlewareSubclasses
4
+ class SubclassNoOptions < Faraday::Middleware
5
+ end
6
+
7
+ class SubclassOneOption < Faraday::Middleware
8
+ DEFAULT_OPTIONS = { some_other_option: false }.freeze
9
+ end
10
+
11
+ class SubclassTwoOptions < Faraday::Middleware
12
+ DEFAULT_OPTIONS = { some_option: true, some_other_option: false }.freeze
13
+ end
14
+ end
15
+
16
+ Faraday::Response.register_middleware(no_options: FaradayMiddlewareSubclasses::SubclassNoOptions)
17
+ Faraday::Response.register_middleware(one_option: FaradayMiddlewareSubclasses::SubclassOneOption)
18
+ Faraday::Response.register_middleware(two_options: FaradayMiddlewareSubclasses::SubclassTwoOptions)