faraday 1.10.1 → 2.12.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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +198 -4
  3. data/LICENSE.md +1 -1
  4. data/README.md +34 -20
  5. data/Rakefile +6 -1
  6. data/examples/client_spec.rb +41 -19
  7. data/examples/client_test.rb +48 -22
  8. data/lib/faraday/adapter/test.rb +62 -13
  9. data/lib/faraday/adapter.rb +6 -10
  10. data/lib/faraday/connection.rb +72 -150
  11. data/lib/faraday/encoders/nested_params_encoder.rb +14 -7
  12. data/lib/faraday/error.rb +24 -5
  13. data/lib/faraday/logging/formatter.rb +28 -15
  14. data/lib/faraday/middleware.rb +43 -2
  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 +59 -45
  21. data/lib/faraday/options.rb +7 -6
  22. data/lib/faraday/rack_builder.rb +23 -21
  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 +18 -3
  26. data/lib/faraday/request/url_encoded.rb +5 -1
  27. data/lib/faraday/request.rb +15 -31
  28. data/lib/faraday/response/json.rb +25 -5
  29. data/lib/faraday/response/logger.rb +6 -0
  30. data/lib/faraday/response/raise_error.rb +45 -18
  31. data/lib/faraday/response.rb +9 -21
  32. data/lib/faraday/utils/headers.rb +15 -4
  33. data/lib/faraday/utils.rb +11 -7
  34. data/lib/faraday/version.rb +1 -1
  35. data/lib/faraday.rb +8 -44
  36. data/spec/faraday/adapter/test_spec.rb +65 -0
  37. data/spec/faraday/connection_spec.rb +165 -93
  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 +161 -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 +10 -1
  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 +88 -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 +93 -6
  52. data/spec/faraday/response/logger_spec.rb +38 -0
  53. data/spec/faraday/response/raise_error_spec.rb +111 -5
  54. data/spec/faraday/response_spec.rb +3 -1
  55. data/spec/faraday/utils/headers_spec.rb +31 -4
  56. data/spec/faraday/utils_spec.rb +64 -1
  57. data/spec/faraday_spec.rb +10 -4
  58. data/spec/spec_helper.rb +5 -6
  59. data/spec/support/fake_safe_buffer.rb +1 -1
  60. data/spec/support/faraday_middleware_subclasses.rb +18 -0
  61. data/spec/support/helper_methods.rb +0 -37
  62. data/spec/support/shared_examples/adapter.rb +2 -2
  63. data/spec/support/shared_examples/request_method.rb +22 -21
  64. metadata +24 -145
  65. data/lib/faraday/adapter/typhoeus.rb +0 -15
  66. data/lib/faraday/autoload.rb +0 -87
  67. data/lib/faraday/dependency_loader.rb +0 -37
  68. data/lib/faraday/deprecate.rb +0 -109
  69. data/lib/faraday/request/basic_authentication.rb +0 -20
  70. data/lib/faraday/request/token_authentication.rb +0 -20
  71. data/spec/faraday/adapter/em_http_spec.rb +0 -49
  72. data/spec/faraday/adapter/em_synchrony_spec.rb +0 -18
  73. data/spec/faraday/adapter/excon_spec.rb +0 -49
  74. data/spec/faraday/adapter/httpclient_spec.rb +0 -73
  75. data/spec/faraday/adapter/net_http_spec.rb +0 -64
  76. data/spec/faraday/adapter/patron_spec.rb +0 -18
  77. data/spec/faraday/adapter/rack_spec.rb +0 -8
  78. data/spec/faraday/adapter/typhoeus_spec.rb +0 -7
  79. data/spec/faraday/composite_read_io_spec.rb +0 -80
  80. data/spec/faraday/deprecate_spec.rb +0 -147
  81. data/spec/faraday/response/middleware_spec.rb +0 -68
  82. data/spec/support/webmock_rack_app.rb +0 -68
@@ -1,14 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'stringio'
4
+
3
5
  RSpec.describe Faraday::Request::UrlEncoded do
4
6
  let(:conn) do
5
7
  Faraday.new do |b|
6
- b.request :multipart
7
8
  b.request :url_encoded
8
9
  b.adapter :test do |stub|
9
10
  stub.post('/echo') do |env|
10
11
  posted_as = env[:request_headers]['Content-Type']
11
- [200, { 'Content-Type' => posted_as }, env[:body]]
12
+ body = env[:body]
13
+ if body.respond_to?(:read)
14
+ body = body.read
15
+ end
16
+ [200, { 'Content-Type' => posted_as }, body]
12
17
  end
13
18
  end
14
19
  end
@@ -68,6 +73,11 @@ RSpec.describe Faraday::Request::UrlEncoded do
68
73
  expect(response.body).to eq('a%5Bb%5D%5Bc%5D%5B%5D=d')
69
74
  end
70
75
 
76
+ it 'works with files' do
77
+ response = conn.post('/echo', StringIO.new('str=apple'))
78
+ expect(response.body).to eq('str=apple')
79
+ end
80
+
71
81
  context 'customising default_space_encoding' do
72
82
  around do |example|
73
83
  Faraday::Utils.default_space_encoding = '%20'
@@ -2,7 +2,7 @@
2
2
 
3
3
  RSpec.describe Faraday::Request do
4
4
  let(:conn) do
5
- Faraday.new(url: 'http://sushi.com/api',
5
+ Faraday.new(url: 'http://httpbingo.org/api',
6
6
  headers: { 'Mime-Version' => '1.0' },
7
7
  request: { oauth: { consumer_key: 'anonymous' } })
8
8
  end
@@ -14,6 +14,7 @@ RSpec.describe Faraday::Request do
14
14
  context 'when nothing particular is configured' do
15
15
  it { expect(subject.http_method).to eq(:get) }
16
16
  it { expect(subject.to_env(conn).ssl.verify).to be_falsey }
17
+ it { expect(subject.to_env(conn).ssl.verify_hostname).to be_falsey }
17
18
  end
18
19
 
19
20
  context 'when HTTP method is post' do
@@ -22,23 +23,12 @@ RSpec.describe Faraday::Request do
22
23
  it { expect(subject.http_method).to eq(:post) }
23
24
  end
24
25
 
25
- describe 'deprecate method for HTTP method' do
26
- let(:http_method) { :post }
27
- let(:expected_warning) do
28
- %r{WARNING: `Faraday::Request#method` is deprecated; use `#http_method` instead. It will be removed in or after version 2.0.\n`Faraday::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
26
  context 'when setting the url on setup with a URI' do
37
27
  let(:block) { proc { |req| req.url URI.parse('foo.json?a=1') } }
38
28
 
39
29
  it { expect(subject.path).to eq(URI.parse('foo.json')) }
40
30
  it { expect(subject.params).to eq('a' => '1') }
41
- it { expect(subject.to_env(conn).url.to_s).to eq('http://sushi.com/api/foo.json?a=1') }
31
+ it { expect(subject.to_env(conn).url.to_s).to eq('http://httpbingo.org/api/foo.json?a=1') }
42
32
  end
43
33
 
44
34
  context 'when setting the url on setup with a string path and params' do
@@ -46,7 +36,7 @@ RSpec.describe Faraday::Request do
46
36
 
47
37
  it { expect(subject.path).to eq('foo.json') }
48
38
  it { expect(subject.params).to eq('a' => 1) }
49
- it { expect(subject.to_env(conn).url.to_s).to eq('http://sushi.com/api/foo.json?a=1') }
39
+ it { expect(subject.to_env(conn).url.to_s).to eq('http://httpbingo.org/api/foo.json?a=1') }
50
40
  end
51
41
 
52
42
  context 'when setting the url on setup with a path including params' do
@@ -54,7 +44,7 @@ RSpec.describe Faraday::Request do
54
44
 
55
45
  it { expect(subject.path).to eq('foo.json') }
56
46
  it { expect(subject.params).to eq('a' => '1', 'b' => '2') }
57
- it { expect(subject.to_env(conn).url.to_s).to eq('http://sushi.com/api/foo.json?a=1&b=2') }
47
+ it { expect(subject.to_env(conn).url.to_s).to eq('http://httpbingo.org/api/foo.json?a=1&b=2') }
58
48
  end
59
49
 
60
50
  context 'when setting a header on setup with []= syntax' do
@@ -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
- begin
80
- process('{') { |env| env[:response] = Faraday::Response.new }
81
- raise 'Parsing should have failed.'
82
- rescue Faraday::ParsingError => e
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
@@ -116,4 +114,93 @@ RSpec.describe Faraday::Response::Json, type: :response do
116
114
  expect(response.body).to eq(result)
117
115
  end
118
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
119
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
@@ -64,6 +65,15 @@ RSpec.describe Faraday::Response::Logger do
64
65
  expect(formatter).to receive(:response).with(an_instance_of(Faraday::Env))
65
66
  conn.get '/hello'
66
67
  end
68
+
69
+ context 'when no route' do
70
+ it 'delegates logging to the formatter' do
71
+ expect(formatter).to receive(:request).with(an_instance_of(Faraday::Env))
72
+ expect(formatter).to receive(:exception).with(an_instance_of(Faraday::Adapter::Test::Stubs::NotFound))
73
+
74
+ expect { conn.get '/noroute' }.to raise_error(Faraday::Adapter::Test::Stubs::NotFound)
75
+ end
76
+ end
67
77
  end
68
78
 
69
79
  context 'with custom formatter' do
@@ -94,6 +104,16 @@ RSpec.describe Faraday::Response::Logger do
94
104
  expect(string_io.string).to match('GET http:/hello')
95
105
  end
96
106
 
107
+ it 'logs status' do
108
+ conn.get '/hello', nil, accept: 'text/html'
109
+ expect(string_io.string).to match('Status 200')
110
+ end
111
+
112
+ it 'does not log error message by default' do
113
+ expect { conn.get '/noroute' }.to raise_error(Faraday::Adapter::Test::Stubs::NotFound)
114
+ expect(string_io.string).not_to match(%(no stubbed request for get http:/noroute))
115
+ end
116
+
97
117
  it 'logs request headers by default' do
98
118
  conn.get '/hello', nil, accept: 'text/html'
99
119
  expect(string_io.string).to match(%(Accept: "text/html))
@@ -188,6 +208,24 @@ RSpec.describe Faraday::Response::Logger do
188
208
  end
189
209
  end
190
210
 
211
+ context 'when logging errors' do
212
+ let(:logger_options) { { errors: true } }
213
+
214
+ it 'logs error message' do
215
+ expect { conn.get '/noroute' }.to raise_error(Faraday::Adapter::Test::Stubs::NotFound)
216
+ expect(string_io.string).to match(%(no stubbed request for get http:/noroute))
217
+ end
218
+ end
219
+
220
+ context 'when logging headers and errors' do
221
+ let(:logger_options) { { headers: true, errors: true } }
222
+
223
+ it 'logs error message' do
224
+ expect { conn.get '/connection_failed' }.to raise_error(Faraday::ConnectionFailed)
225
+ expect(string_io.string).to match(%(Failed to open TCP connection))
226
+ end
227
+ end
228
+
191
229
  context 'when using log_level' do
192
230
  let(:logger_options) { { bodies: true, log_level: :debug } }
193
231
 
@@ -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'] }
@@ -79,6 +81,17 @@ 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')
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
97
  expect(ex.message).to eq('the server responded with status 409')
@@ -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')
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')
@@ -137,21 +161,24 @@ 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
- stub.post('request?full=true', request_body, request_headers) do
166
+ stub.post(url, request_body, request_headers) do
143
167
  [400, { 'X-Reason' => 'because' }, 'keep looking']
144
168
  end
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' } }
175
+ let(:url_path) { 'request' }
176
+ let(:query_params) { 'full=true' }
177
+ let(:url) { "#{url_path}?#{query_params}" }
150
178
 
151
179
  subject(:perform_request) do
152
- conn.post 'request' do |req|
180
+ conn.post url do |req|
153
181
  req.headers['Authorization'] = 'Basic 123'
154
- req.params[:full] = true
155
182
  req.body = request_body
156
183
  end
157
184
  end
@@ -159,11 +186,90 @@ RSpec.describe Faraday::Response::RaiseError do
159
186
  it 'returns the request info in the exception' do
160
187
  expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
161
188
  expect(ex.response[:request][:method]).to eq(:post)
162
- expect(ex.response[:request][:url_path]).to eq('/request')
189
+ expect(ex.response[:request][:url]).to eq(URI("http:/#{url}"))
190
+ expect(ex.response[:request][:url_path]).to eq("/#{url_path}")
163
191
  expect(ex.response[:request][:params]).to eq({ 'full' => 'true' })
164
192
  expect(ex.response[:request][:headers]).to match(a_hash_including(request_headers))
165
193
  expect(ex.response[:request][:body]).to eq(request_body)
166
194
  end
167
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
168
274
  end
169
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,27 +56,54 @@ RSpec.describe Faraday::Utils::Headers do
56
56
  it { expect(subject.delete('content-type')).to be_nil }
57
57
  end
58
58
 
59
- describe '#parse' do
60
- before { subject.parse(headers) }
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
61
67
 
68
+ describe '#parse' do
62
69
  context 'when response headers leave http status line out' do
63
70
  let(:headers) { "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n" }
64
71
 
72
+ before { subject.parse(headers) }
73
+
65
74
  it { expect(subject.keys).to eq(%w[Content-Type]) }
66
75
  it { expect(subject['Content-Type']).to eq('text/html') }
67
76
  it { expect(subject['content-type']).to eq('text/html') }
68
77
  end
69
78
 
70
79
  context 'when response headers values include a colon' do
71
- let(:headers) { "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nLocation: http://sushi.com/\r\n\r\n" }
80
+ let(:headers) { "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nLocation: http://httpbingo.org/\r\n\r\n" }
72
81
 
73
- it { expect(subject['location']).to eq('http://sushi.com/') }
82
+ before { subject.parse(headers) }
83
+
84
+ it { expect(subject['location']).to eq('http://httpbingo.org/') }
74
85
  end
75
86
 
76
87
  context 'when response headers include a blank line' do
77
88
  let(:headers) { "HTTP/1.1 200 OK\r\n\r\nContent-Type: text/html\r\n\r\n" }
78
89
 
90
+ before { subject.parse(headers) }
91
+
79
92
  it { expect(subject['content-type']).to eq('text/html') }
80
93
  end
94
+
95
+ context 'when response headers include already stored keys' do
96
+ let(:headers) { "HTTP/1.1 200 OK\r\nX-Numbers: 123\r\n\r\n" }
97
+
98
+ before do
99
+ h = subject
100
+ h[:x_numbers] = 8
101
+ h.parse(headers)
102
+ end
103
+
104
+ it do
105
+ expect(subject[:x_numbers]).to eq('8, 123')
106
+ end
107
+ end
81
108
  end
82
109
  end
@@ -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
- "HTTP/1.x 200 OK\r\nContent-Type: application/json; charset=UTF-8\r\n\r\n"
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
@@ -53,4 +53,67 @@ RSpec.describe Faraday::Utils do
53
53
  expect(headers).not_to have_key('authorization')
54
54
  end
55
55
  end
56
+
57
+ describe '.deep_merge!' do
58
+ let(:connection_options) { Faraday::ConnectionOptions.new }
59
+ let(:url) do
60
+ {
61
+ url: 'http://example.com/abc',
62
+ headers: { 'Mime-Version' => '1.0' },
63
+ request: { oauth: { consumer_key: 'anonymous' } },
64
+ ssl: { version: '2' }
65
+ }
66
+ end
67
+
68
+ it 'recursively merges the headers' do
69
+ connection_options.headers = { user_agent: 'My Agent 1.0' }
70
+ deep_merge = Faraday::Utils.deep_merge!(connection_options, url)
71
+
72
+ expect(deep_merge.headers).to eq('Mime-Version' => '1.0', user_agent: 'My Agent 1.0')
73
+ end
74
+
75
+ context 'when a target hash has an Options Struct value' do
76
+ let(:request) do
77
+ {
78
+ params_encoder: nil,
79
+ proxy: nil,
80
+ bind: nil,
81
+ timeout: nil,
82
+ open_timeout: nil,
83
+ read_timeout: nil,
84
+ write_timeout: nil,
85
+ boundary: nil,
86
+ oauth: { consumer_key: 'anonymous' },
87
+ context: nil,
88
+ on_data: nil
89
+ }
90
+ end
91
+ let(:ssl) do
92
+ {
93
+ verify: nil,
94
+ ca_file: nil,
95
+ ca_path: nil,
96
+ verify_mode: nil,
97
+ cert_store: nil,
98
+ client_cert: nil,
99
+ client_key: nil,
100
+ certificate: nil,
101
+ private_key: nil,
102
+ verify_depth: nil,
103
+ version: '2',
104
+ min_version: nil,
105
+ max_version: nil,
106
+ verify_hostname: nil,
107
+ ciphers: nil
108
+ }
109
+ end
110
+
111
+ it 'does not overwrite an Options Struct value' do
112
+ deep_merge = Faraday::Utils.deep_merge!(connection_options, url)
113
+
114
+ expect(deep_merge.request.to_h).to eq(request)
115
+ expect(deep_merge.ssl.to_h).to eq(ssl)
116
+ end
117
+ end
118
+ end
56
119
  end
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,16 +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 }
40
38
 
41
- Faraday::Deprecate.skip = false
39
+ # Load all Rspec support files
40
+ Dir['./spec/support/**/*.rb'].each { |file| require file }
42
41
 
43
42
  RSpec.configure do |config|
44
43
  # rspec-expectations config goes here. You can use an alternate
@@ -8,7 +8,7 @@ FakeSafeBuffer = Struct.new(:string) do
8
8
 
9
9
  def gsub(regex)
10
10
  string.gsub(regex) do
11
- match, = $&, '' =~ /a/
11
+ match, = Regexp.last_match(0), '' =~ /a/ # rubocop:disable Performance/StringInclude
12
12
  yield(match)
13
13
  end
14
14
  end
@@ -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)