faraday 2.7.0 → 2.14.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.
Files changed (49) 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/flat_params_encoder.rb +2 -2
  10. data/lib/faraday/encoders/nested_params_encoder.rb +1 -1
  11. data/lib/faraday/error.rb +63 -8
  12. data/lib/faraday/logging/formatter.rb +19 -21
  13. data/lib/faraday/middleware.rb +40 -1
  14. data/lib/faraday/options/connection_options.rb +7 -6
  15. data/lib/faraday/options/env.rb +68 -63
  16. data/lib/faraday/options/proxy_options.rb +11 -5
  17. data/lib/faraday/options/request_options.rb +7 -6
  18. data/lib/faraday/options/ssl_options.rb +57 -50
  19. data/lib/faraday/options.rb +4 -3
  20. data/lib/faraday/rack_builder.rb +21 -25
  21. data/lib/faraday/request/instrumentation.rb +3 -1
  22. data/lib/faraday/request/json.rb +18 -3
  23. data/lib/faraday/request.rb +10 -7
  24. data/lib/faraday/response/json.rb +21 -1
  25. data/lib/faraday/response/logger.rb +7 -5
  26. data/lib/faraday/response/raise_error.rb +36 -17
  27. data/lib/faraday/response.rb +7 -2
  28. data/lib/faraday/utils/headers.rb +8 -2
  29. data/lib/faraday/utils.rb +3 -4
  30. data/lib/faraday/version.rb +1 -1
  31. data/lib/faraday.rb +2 -1
  32. data/spec/faraday/adapter/test_spec.rb +29 -0
  33. data/spec/faraday/connection_spec.rb +17 -2
  34. data/spec/faraday/error_spec.rb +122 -7
  35. data/spec/faraday/middleware_spec.rb +143 -0
  36. data/spec/faraday/options/options_spec.rb +1 -1
  37. data/spec/faraday/options/proxy_options_spec.rb +35 -0
  38. data/spec/faraday/params_encoders/nested_spec.rb +2 -1
  39. data/spec/faraday/request/json_spec.rb +88 -0
  40. data/spec/faraday/response/json_spec.rb +89 -0
  41. data/spec/faraday/response/logger_spec.rb +56 -5
  42. data/spec/faraday/response/raise_error_spec.rb +126 -12
  43. data/spec/faraday/response_spec.rb +10 -1
  44. data/spec/faraday/utils/headers_spec.rb +9 -0
  45. data/spec/faraday/utils_spec.rb +3 -1
  46. data/spec/faraday_spec.rb +10 -4
  47. data/spec/spec_helper.rb +6 -5
  48. data/spec/support/faraday_middleware_subclasses.rb +18 -0
  49. 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
@@ -21,10 +21,12 @@ RSpec.describe Faraday::Response::Logger do
21
21
  stubs.post('/ohai') { [200, { 'Content-Type' => 'text/html' }, 'fred'] }
22
22
  stubs.post('/ohyes') { [200, { 'Content-Type' => 'text/html' }, 'pebbles'] }
23
23
  stubs.get('/rubbles') { [200, { 'Content-Type' => 'application/json' }, rubbles] }
24
+ stubs.get('/8bit') { [200, { 'Content-Type' => 'text/html' }, (+'café!').force_encoding(Encoding::ASCII_8BIT)] }
24
25
  stubs.get('/filtered_body') { [200, { 'Content-Type' => 'text/html' }, 'soylent green is people'] }
25
26
  stubs.get('/filtered_headers') { [200, { 'Content-Type' => 'text/html' }, 'headers response'] }
26
27
  stubs.get('/filtered_params') { [200, { 'Content-Type' => 'text/html' }, 'params response'] }
27
28
  stubs.get('/filtered_url') { [200, { 'Content-Type' => 'text/html' }, 'url response'] }
29
+ stubs.get('/connection_failed') { raise Faraday::ConnectionFailed, 'Failed to open TCP connection' }
28
30
  end
29
31
  end
30
32
  end
@@ -54,6 +56,26 @@ RSpec.describe Faraday::Response::Logger do
54
56
  end
55
57
  end
56
58
 
59
+ context 'when logger with program name' do
60
+ let(:logger) { Logger.new(string_io, progname: 'my_best_program') }
61
+
62
+ it 'logs with program name' do
63
+ conn.get '/hello'
64
+
65
+ expect(string_io.string).to match('-- my_best_program: request:')
66
+ expect(string_io.string).to match('-- my_best_program: response:')
67
+ end
68
+ end
69
+
70
+ context 'when logger without program name' do
71
+ it 'logs without program name' do
72
+ conn.get '/hello'
73
+
74
+ expect(string_io.string).to match('-- : request:')
75
+ expect(string_io.string).to match('-- : response:')
76
+ end
77
+ end
78
+
57
79
  context 'with default formatter' do
58
80
  let(:formatter) { instance_double(Faraday::Logging::Formatter, request: true, response: true, filter: []) }
59
81
 
@@ -68,7 +90,7 @@ RSpec.describe Faraday::Response::Logger do
68
90
  context 'when no route' do
69
91
  it 'delegates logging to the formatter' do
70
92
  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))
93
+ expect(formatter).to receive(:exception).with(an_instance_of(Faraday::Adapter::Test::Stubs::NotFound))
72
94
 
73
95
  expect { conn.get '/noroute' }.to raise_error(Faraday::Adapter::Test::Stubs::NotFound)
74
96
  end
@@ -168,7 +190,7 @@ RSpec.describe Faraday::Response::Logger do
168
190
  context 'when logging request body' do
169
191
  let(:logger_options) { { bodies: { request: true } } }
170
192
 
171
- it 'log only request body' do
193
+ it 'logs only request body' do
172
194
  conn.post '/ohyes', 'name=Tamago', accept: 'text/html'
173
195
  expect(string_io.string).to match(%(name=Tamago))
174
196
  expect(string_io.string).not_to match(%(pebbles))
@@ -178,7 +200,7 @@ RSpec.describe Faraday::Response::Logger do
178
200
  context 'when logging response body' do
179
201
  let(:logger_options) { { bodies: { response: true } } }
180
202
 
181
- it 'log only response body' do
203
+ it 'logs only response body' do
182
204
  conn.post '/ohyes', 'name=Hamachi', accept: 'text/html'
183
205
  expect(string_io.string).to match(%(pebbles))
184
206
  expect(string_io.string).not_to match(%(name=Hamachi))
@@ -188,13 +210,13 @@ RSpec.describe Faraday::Response::Logger do
188
210
  context 'when logging request and response bodies' do
189
211
  let(:logger_options) { { bodies: true } }
190
212
 
191
- it 'log request and response body' do
213
+ it 'logs request and response body' do
192
214
  conn.post '/ohyes', 'name=Ebi', accept: 'text/html'
193
215
  expect(string_io.string).to match(%(name=Ebi))
194
216
  expect(string_io.string).to match(%(pebbles))
195
217
  end
196
218
 
197
- it 'log response body object' do
219
+ it 'logs response body object' do
198
220
  conn.get '/rubbles', nil, accept: 'text/html'
199
221
  expect(string_io.string).to match(%([\"Barney\", \"Betty\", \"Bam Bam\"]\n))
200
222
  end
@@ -207,6 +229,26 @@ RSpec.describe Faraday::Response::Logger do
207
229
  end
208
230
  end
209
231
 
232
+ context 'when bodies are logged by default' do
233
+ before do
234
+ described_class.default_options = { bodies: true }
235
+ end
236
+
237
+ it 'logs response body' do
238
+ conn.post '/ohai'
239
+ expect(string_io.string).to match(%(fred))
240
+ end
241
+
242
+ it 'converts to UTF-8' do
243
+ conn.get '/8bit'
244
+ expect(string_io.string).to match(%(caf��!))
245
+ end
246
+
247
+ after do
248
+ described_class.default_options = { bodies: false }
249
+ end
250
+ end
251
+
210
252
  context 'when logging errors' do
211
253
  let(:logger_options) { { errors: true } }
212
254
 
@@ -216,6 +258,15 @@ RSpec.describe Faraday::Response::Logger do
216
258
  end
217
259
  end
218
260
 
261
+ context 'when logging headers and errors' do
262
+ let(:logger_options) { { headers: true, errors: true } }
263
+
264
+ it 'logs error message' do
265
+ expect { conn.get '/connection_failed' }.to raise_error(Faraday::ConnectionFailed)
266
+ expect(string_io.string).to match(%(Failed to open TCP connection))
267
+ end
268
+ end
269
+
219
270
  context 'when using log_level' do
220
271
  let(:logger_options) { { bodies: true, log_level: :debug } }
221
272
 
@@ -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
- stub.get('unprocessable-entity') { [422, { 'X-Reason' => 'because' }, 'keep looking'] }
16
+ stub.get('unprocessable-content') { [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)
@@ -90,9 +103,20 @@ RSpec.describe Faraday::Response::RaiseError do
90
103
  end
91
104
  end
92
105
 
93
- it 'raises Faraday::UnprocessableEntityError for 422 responses' do
94
- expect { conn.get('unprocessable-entity') }.to raise_error(Faraday::UnprocessableEntityError) do |ex|
95
- expect(ex.message).to eq('the server responded with status 422')
106
+ it 'raises legacy Faraday::UnprocessableEntityError for 422 responses' do
107
+ expect { conn.get('unprocessable-content') }.to raise_error(Faraday::UnprocessableEntityError) do |ex|
108
+ expect(ex.message).to eq('the server responded with status 422 for GET http:/unprocessable-content')
109
+ expect(ex.response[:headers]['X-Reason']).to eq('because')
110
+ expect(ex.response[:status]).to eq(422)
111
+ expect(ex.response_status).to eq(422)
112
+ expect(ex.response_body).to eq('keep looking')
113
+ expect(ex.response_headers['X-Reason']).to eq('because')
114
+ end
115
+ end
116
+
117
+ it 'raises Faraday::UnprocessableContentError for 422 responses' do
118
+ expect { conn.get('unprocessable-content') }.to raise_error(Faraday::UnprocessableContentError) do |ex|
119
+ expect(ex.message).to eq('the server responded with status 422 for GET http:/unprocessable-content')
96
120
  expect(ex.response[:headers]['X-Reason']).to eq('because')
97
121
  expect(ex.response[:status]).to eq(422)
98
122
  expect(ex.response_status).to eq(422)
@@ -101,6 +125,17 @@ RSpec.describe Faraday::Response::RaiseError do
101
125
  end
102
126
  end
103
127
 
128
+ it 'raises Faraday::TooManyRequestsError for 429 responses' do
129
+ expect { conn.get('too-many-requests') }.to raise_error(Faraday::TooManyRequestsError) do |ex|
130
+ expect(ex.message).to eq('the server responded with status 429 for GET http:/too-many-requests')
131
+ expect(ex.response[:headers]['X-Reason']).to eq('because')
132
+ expect(ex.response[:status]).to eq(429)
133
+ expect(ex.response_status).to eq(429)
134
+ expect(ex.response_body).to eq('keep looking')
135
+ expect(ex.response_headers['X-Reason']).to eq('because')
136
+ end
137
+ end
138
+
104
139
  it 'raises Faraday::NilStatusError for nil status in response' do
105
140
  expect { conn.get('nil-status') }.to raise_error(Faraday::NilStatusError) do |ex|
106
141
  expect(ex.message).to eq('http status could not be derived from the server response')
@@ -114,7 +149,7 @@ RSpec.describe Faraday::Response::RaiseError do
114
149
 
115
150
  it 'raises Faraday::ClientError for other 4xx responses' do
116
151
  expect { conn.get('4xx') }.to raise_error(Faraday::ClientError) do |ex|
117
- expect(ex.message).to eq('the server responded with status 499')
152
+ expect(ex.message).to eq('the server responded with status 499 for GET http:/4xx')
118
153
  expect(ex.response[:headers]['X-Reason']).to eq('because')
119
154
  expect(ex.response[:status]).to eq(499)
120
155
  expect(ex.response_status).to eq(499)
@@ -125,7 +160,7 @@ RSpec.describe Faraday::Response::RaiseError do
125
160
 
126
161
  it 'raises Faraday::ServerError for 500 responses' do
127
162
  expect { conn.get('server-error') }.to raise_error(Faraday::ServerError) do |ex|
128
- expect(ex.message).to eq('the server responded with status 500')
163
+ expect(ex.message).to eq('the server responded with status 500 for GET http:/server-error')
129
164
  expect(ex.response[:headers]['X-Error']).to eq('bailout')
130
165
  expect(ex.response[:status]).to eq(500)
131
166
  expect(ex.response_status).to eq(500)
@@ -137,7 +172,7 @@ RSpec.describe Faraday::Response::RaiseError do
137
172
  describe 'request info' do
138
173
  let(:conn) do
139
174
  Faraday.new do |b|
140
- b.response :raise_error
175
+ b.response :raise_error, **middleware_options
141
176
  b.adapter :test do |stub|
142
177
  stub.post(url, request_body, request_headers) do
143
178
  [400, { 'X-Reason' => 'because' }, 'keep looking']
@@ -145,6 +180,7 @@ RSpec.describe Faraday::Response::RaiseError do
145
180
  end
146
181
  end
147
182
  end
183
+ let(:middleware_options) { {} }
148
184
  let(:request_body) { JSON.generate({ 'item' => 'sth' }) }
149
185
  let(:request_headers) { { 'Authorization' => 'Basic 123' } }
150
186
  let(:url_path) { 'request' }
@@ -168,5 +204,83 @@ RSpec.describe Faraday::Response::RaiseError do
168
204
  expect(ex.response[:request][:body]).to eq(request_body)
169
205
  end
170
206
  end
207
+
208
+ describe 'DEFAULT_OPTION: include_request' do
209
+ before(:each) do
210
+ Faraday::Response::RaiseError.instance_variable_set(:@default_options, nil)
211
+ Faraday::Middleware.instance_variable_set(:@default_options, nil)
212
+ end
213
+
214
+ after(:all) do
215
+ Faraday::Response::RaiseError.instance_variable_set(:@default_options, nil)
216
+ Faraday::Middleware.instance_variable_set(:@default_options, nil)
217
+ end
218
+
219
+ context 'when RaiseError DEFAULT_OPTION (include_request: true) is used' do
220
+ it 'includes request info in the exception' do
221
+ expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
222
+ expect(ex.response.keys).to contain_exactly(
223
+ :status,
224
+ :headers,
225
+ :body,
226
+ :request
227
+ )
228
+ end
229
+ end
230
+ end
231
+
232
+ context 'when application sets default_options `include_request: false`' do
233
+ before(:each) do
234
+ Faraday::Response::RaiseError.default_options = { include_request: false }
235
+ end
236
+
237
+ context 'and when include_request option is omitted' do
238
+ it 'does not include request info in the exception' do
239
+ expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
240
+ expect(ex.response.keys).to contain_exactly(
241
+ :status,
242
+ :headers,
243
+ :body
244
+ )
245
+ end
246
+ end
247
+ end
248
+
249
+ context 'and when include_request option is explicitly set for instance' do
250
+ let(:middleware_options) { { include_request: true } }
251
+
252
+ it 'includes request info in the exception' do
253
+ expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
254
+ expect(ex.response.keys).to contain_exactly(
255
+ :status,
256
+ :headers,
257
+ :body,
258
+ :request
259
+ )
260
+ end
261
+ end
262
+ end
263
+ end
264
+ end
265
+ end
266
+
267
+ describe 'allowing certain status codes' do
268
+ let(:conn) do
269
+ Faraday.new do |b|
270
+ b.response :raise_error, allowed_statuses: [404]
271
+ b.adapter :test do |stub|
272
+ stub.get('bad-request') { [400, { 'X-Reason' => 'because' }, 'keep looking'] }
273
+ stub.get('not-found') { [404, { 'X-Reason' => 'because' }, 'keep looking'] }
274
+ end
275
+ end
276
+ end
277
+
278
+ it 'raises an error for status codes that are not explicitly allowed' do
279
+ expect { conn.get('bad-request') }.to raise_error(Faraday::BadRequestError)
280
+ end
281
+
282
+ it 'does not raise an error for allowed status codes' do
283
+ expect { conn.get('not-found') }.not_to raise_error
284
+ end
171
285
  end
172
286
  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
 
@@ -13,6 +13,7 @@ RSpec.describe Faraday::Response do
13
13
  it { expect(subject.success?).to be_falsey }
14
14
  it { expect(subject.status).to eq(404) }
15
15
  it { expect(subject.body).to eq('yikes') }
16
+ it { expect(subject.url).to eq(URI('https://lostisland.github.io/faraday')) }
16
17
  it { expect(subject.headers['Content-Type']).to eq('text/plain') }
17
18
  it { expect(subject['content-type']).to eq('text/plain') }
18
19
 
@@ -30,6 +31,13 @@ RSpec.describe Faraday::Response do
30
31
  it { expect(hash[:status]).to eq(subject.status) }
31
32
  it { expect(hash[:response_headers]).to eq(subject.headers) }
32
33
  it { expect(hash[:body]).to eq(subject.body) }
34
+ it { expect(hash[:url]).to eq(subject.env.url) }
35
+
36
+ context 'when response is not finished' do
37
+ subject { Faraday::Response.new.to_hash }
38
+
39
+ it { is_expected.to eq({ status: nil, body: nil, response_headers: {}, url: nil }) }
40
+ end
33
41
  end
34
42
 
35
43
  describe 'marshal serialization support' do
@@ -45,6 +53,7 @@ RSpec.describe Faraday::Response do
45
53
  it { expect(loaded.env[:body]).to eq(env[:body]) }
46
54
  it { expect(loaded.env[:response_headers]).to eq(env[:response_headers]) }
47
55
  it { expect(loaded.env[:status]).to eq(env[:status]) }
56
+ it { expect(loaded.env[:url]).to eq(env[:url]) }
48
57
  end
49
58
 
50
59
  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)