faraday 0.11.0 → 1.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/CHANGELOG.md +380 -0
- data/LICENSE.md +1 -1
- data/README.md +25 -229
- data/Rakefile +7 -0
- data/examples/client_spec.rb +65 -0
- data/examples/client_test.rb +79 -0
- data/lib/faraday/adapter/httpclient.rb +83 -59
- data/lib/faraday/adapter/patron.rb +92 -36
- data/lib/faraday/adapter/rack.rb +30 -13
- data/lib/faraday/adapter/test.rb +103 -62
- data/lib/faraday/adapter/typhoeus.rb +7 -115
- data/lib/faraday/adapter.rb +77 -22
- data/lib/faraday/adapter_registry.rb +30 -0
- data/lib/faraday/autoload.rb +42 -36
- data/lib/faraday/connection.rb +351 -167
- data/lib/faraday/dependency_loader.rb +37 -0
- data/lib/faraday/encoders/flat_params_encoder.rb +105 -0
- data/lib/faraday/encoders/nested_params_encoder.rb +176 -0
- data/lib/faraday/error.rb +127 -38
- data/lib/faraday/file_part.rb +128 -0
- data/lib/faraday/logging/formatter.rb +105 -0
- data/lib/faraday/methods.rb +6 -0
- data/lib/faraday/middleware.rb +19 -25
- data/lib/faraday/middleware_registry.rb +129 -0
- data/lib/faraday/options/connection_options.rb +22 -0
- data/lib/faraday/options/env.rb +181 -0
- data/lib/faraday/options/proxy_options.rb +32 -0
- data/lib/faraday/options/request_options.rb +22 -0
- data/lib/faraday/options/ssl_options.rb +59 -0
- data/lib/faraday/options.rb +58 -207
- data/lib/faraday/param_part.rb +53 -0
- data/lib/faraday/parameters.rb +4 -196
- data/lib/faraday/rack_builder.rb +84 -48
- data/lib/faraday/request/authorization.rb +44 -30
- data/lib/faraday/request/basic_authentication.rb +14 -7
- data/lib/faraday/request/instrumentation.rb +45 -27
- data/lib/faraday/request/multipart.rb +88 -45
- data/lib/faraday/request/retry.rb +211 -126
- data/lib/faraday/request/token_authentication.rb +15 -10
- data/lib/faraday/request/url_encoded.rb +43 -23
- data/lib/faraday/request.rb +94 -32
- data/lib/faraday/response/logger.rb +22 -69
- data/lib/faraday/response/raise_error.rb +49 -14
- data/lib/faraday/response.rb +27 -23
- data/lib/faraday/utils/headers.rb +139 -0
- data/lib/faraday/utils/params_hash.rb +61 -0
- data/lib/faraday/utils.rb +38 -238
- data/lib/faraday/version.rb +5 -0
- data/lib/faraday.rb +124 -187
- data/spec/external_adapters/faraday_specs_setup.rb +14 -0
- data/spec/faraday/adapter/em_http_spec.rb +47 -0
- data/spec/faraday/adapter/em_synchrony_spec.rb +16 -0
- data/spec/faraday/adapter/excon_spec.rb +49 -0
- data/spec/faraday/adapter/httpclient_spec.rb +73 -0
- data/spec/faraday/adapter/net_http_spec.rb +64 -0
- data/spec/faraday/adapter/patron_spec.rb +18 -0
- data/spec/faraday/adapter/rack_spec.rb +8 -0
- data/spec/faraday/adapter/test_spec.rb +260 -0
- data/spec/faraday/adapter/typhoeus_spec.rb +7 -0
- data/spec/faraday/adapter_registry_spec.rb +28 -0
- data/spec/faraday/adapter_spec.rb +55 -0
- data/spec/faraday/composite_read_io_spec.rb +80 -0
- data/spec/faraday/connection_spec.rb +736 -0
- data/spec/faraday/error_spec.rb +60 -0
- data/spec/faraday/middleware_spec.rb +52 -0
- data/spec/faraday/options/env_spec.rb +70 -0
- data/spec/faraday/options/options_spec.rb +297 -0
- data/spec/faraday/options/proxy_options_spec.rb +44 -0
- data/spec/faraday/options/request_options_spec.rb +19 -0
- data/spec/faraday/params_encoders/flat_spec.rb +42 -0
- data/spec/faraday/params_encoders/nested_spec.rb +142 -0
- data/spec/faraday/rack_builder_spec.rb +345 -0
- data/spec/faraday/request/authorization_spec.rb +88 -0
- data/spec/faraday/request/instrumentation_spec.rb +76 -0
- data/spec/faraday/request/multipart_spec.rb +302 -0
- data/spec/faraday/request/retry_spec.rb +242 -0
- data/spec/faraday/request/url_encoded_spec.rb +83 -0
- data/spec/faraday/request_spec.rb +120 -0
- data/spec/faraday/response/logger_spec.rb +220 -0
- data/spec/faraday/response/middleware_spec.rb +68 -0
- data/spec/faraday/response/raise_error_spec.rb +169 -0
- data/spec/faraday/response_spec.rb +75 -0
- data/spec/faraday/utils/headers_spec.rb +82 -0
- data/spec/faraday/utils_spec.rb +56 -0
- data/spec/faraday_spec.rb +37 -0
- data/spec/spec_helper.rb +132 -0
- data/spec/support/disabling_stub.rb +14 -0
- data/spec/support/fake_safe_buffer.rb +15 -0
- data/spec/support/helper_methods.rb +133 -0
- data/spec/support/shared_examples/adapter.rb +105 -0
- data/spec/support/shared_examples/params_encoder.rb +18 -0
- data/spec/support/shared_examples/request_method.rb +262 -0
- data/spec/support/streaming_response_checker.rb +35 -0
- data/spec/support/webmock_rack_app.rb +68 -0
- metadata +164 -16
- data/lib/faraday/adapter/em_http.rb +0 -243
- data/lib/faraday/adapter/em_http_ssl_patch.rb +0 -56
- data/lib/faraday/adapter/em_synchrony/parallel_manager.rb +0 -66
- data/lib/faraday/adapter/em_synchrony.rb +0 -106
- data/lib/faraday/adapter/excon.rb +0 -80
- data/lib/faraday/adapter/net_http.rb +0 -135
- data/lib/faraday/adapter/net_http_persistent.rb +0 -50
- data/lib/faraday/upload_io.rb +0 -67
@@ -0,0 +1,120 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe Faraday::Request do
|
4
|
+
let(:conn) do
|
5
|
+
Faraday.new(url: 'http://sushi.com/api',
|
6
|
+
headers: { 'Mime-Version' => '1.0' },
|
7
|
+
request: { oauth: { consumer_key: 'anonymous' } })
|
8
|
+
end
|
9
|
+
let(:http_method) { :get }
|
10
|
+
let(:block) { nil }
|
11
|
+
|
12
|
+
subject { conn.build_request(http_method, &block) }
|
13
|
+
|
14
|
+
context 'when nothing particular is configured' do
|
15
|
+
it { expect(subject.http_method).to eq(:get) }
|
16
|
+
it { expect(subject.to_env(conn).ssl.verify).to be_falsey }
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'when HTTP method is post' do
|
20
|
+
let(:http_method) { :post }
|
21
|
+
|
22
|
+
it { expect(subject.http_method).to eq(:post) }
|
23
|
+
end
|
24
|
+
|
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
|
+
context 'when setting the url on setup with a URI' do
|
37
|
+
let(:block) { proc { |req| req.url URI.parse('foo.json?a=1') } }
|
38
|
+
|
39
|
+
it { expect(subject.path).to eq(URI.parse('foo.json')) }
|
40
|
+
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') }
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'when setting the url on setup with a string path and params' do
|
45
|
+
let(:block) { proc { |req| req.url 'foo.json', 'a' => 1 } }
|
46
|
+
|
47
|
+
it { expect(subject.path).to eq('foo.json') }
|
48
|
+
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') }
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'when setting the url on setup with a path including params' do
|
53
|
+
let(:block) { proc { |req| req.url 'foo.json?b=2&a=1#qqq' } }
|
54
|
+
|
55
|
+
it { expect(subject.path).to eq('foo.json') }
|
56
|
+
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') }
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'when setting a header on setup with []= syntax' do
|
61
|
+
let(:block) { proc { |req| req['Server'] = 'Faraday' } }
|
62
|
+
let(:headers) { subject.to_env(conn).request_headers }
|
63
|
+
|
64
|
+
it { expect(subject.headers['Server']).to eq('Faraday') }
|
65
|
+
it { expect(headers['mime-version']).to eq('1.0') }
|
66
|
+
it { expect(headers['server']).to eq('Faraday') }
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'when setting the body on setup' do
|
70
|
+
let(:block) { proc { |req| req.body = 'hi' } }
|
71
|
+
|
72
|
+
it { expect(subject.body).to eq('hi') }
|
73
|
+
it { expect(subject.to_env(conn).body).to eq('hi') }
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'with global request options set' do
|
77
|
+
let(:env_request) { subject.to_env(conn).request }
|
78
|
+
|
79
|
+
before do
|
80
|
+
conn.options.timeout = 3
|
81
|
+
conn.options.open_timeout = 5
|
82
|
+
conn.ssl.verify = false
|
83
|
+
conn.proxy = 'http://proxy.com'
|
84
|
+
end
|
85
|
+
|
86
|
+
it { expect(subject.options.timeout).to eq(3) }
|
87
|
+
it { expect(subject.options.open_timeout).to eq(5) }
|
88
|
+
it { expect(env_request.timeout).to eq(3) }
|
89
|
+
it { expect(env_request.open_timeout).to eq(5) }
|
90
|
+
|
91
|
+
context 'and per-request options set' do
|
92
|
+
let(:block) do
|
93
|
+
proc do |req|
|
94
|
+
req.options.timeout = 10
|
95
|
+
req.options.boundary = 'boo'
|
96
|
+
req.options.oauth[:consumer_secret] = 'xyz'
|
97
|
+
req.options.context = {
|
98
|
+
foo: 'foo',
|
99
|
+
bar: 'bar'
|
100
|
+
}
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
it { expect(subject.options.timeout).to eq(10) }
|
105
|
+
it { expect(subject.options.open_timeout).to eq(5) }
|
106
|
+
it { expect(env_request.timeout).to eq(10) }
|
107
|
+
it { expect(env_request.open_timeout).to eq(5) }
|
108
|
+
it { expect(env_request.boundary).to eq('boo') }
|
109
|
+
it { expect(env_request.context).to eq(foo: 'foo', bar: 'bar') }
|
110
|
+
it do
|
111
|
+
oauth_expected = { consumer_secret: 'xyz', consumer_key: 'anonymous' }
|
112
|
+
expect(env_request.oauth).to eq(oauth_expected)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'supports marshal serialization' do
|
118
|
+
expect(Marshal.load(Marshal.dump(subject))).to eq(subject)
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,220 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stringio'
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
RSpec.describe Faraday::Response::Logger do
|
7
|
+
let(:string_io) { StringIO.new }
|
8
|
+
let(:logger) { Logger.new(string_io) }
|
9
|
+
let(:logger_options) { {} }
|
10
|
+
let(:conn) do
|
11
|
+
rubbles = ['Barney', 'Betty', 'Bam Bam']
|
12
|
+
|
13
|
+
Faraday.new do |b|
|
14
|
+
b.response :logger, logger, logger_options do |logger|
|
15
|
+
logger.filter(/(soylent green is) (.+)/, '\1 tasty')
|
16
|
+
logger.filter(/(api_key:).*"(.+)."/, '\1[API_KEY]')
|
17
|
+
logger.filter(/(password)=(.+)/, '\1=[HIDDEN]')
|
18
|
+
end
|
19
|
+
b.adapter :test do |stubs|
|
20
|
+
stubs.get('/hello') { [200, { 'Content-Type' => 'text/html' }, 'hello'] }
|
21
|
+
stubs.post('/ohai') { [200, { 'Content-Type' => 'text/html' }, 'fred'] }
|
22
|
+
stubs.post('/ohyes') { [200, { 'Content-Type' => 'text/html' }, 'pebbles'] }
|
23
|
+
stubs.get('/rubbles') { [200, { 'Content-Type' => 'application/json' }, rubbles] }
|
24
|
+
stubs.get('/filtered_body') { [200, { 'Content-Type' => 'text/html' }, 'soylent green is people'] }
|
25
|
+
stubs.get('/filtered_headers') { [200, { 'Content-Type' => 'text/html' }, 'headers response'] }
|
26
|
+
stubs.get('/filtered_params') { [200, { 'Content-Type' => 'text/html' }, 'params response'] }
|
27
|
+
stubs.get('/filtered_url') { [200, { 'Content-Type' => 'text/html' }, 'url response'] }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
before do
|
33
|
+
logger.level = Logger::DEBUG
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'still returns output' do
|
37
|
+
resp = conn.get '/hello', nil, accept: 'text/html'
|
38
|
+
expect(resp.body).to eq('hello')
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'without configuration' do
|
42
|
+
let(:conn) do
|
43
|
+
Faraday.new do |b|
|
44
|
+
b.response :logger
|
45
|
+
b.adapter :test do |stubs|
|
46
|
+
stubs.get('/hello') { [200, { 'Content-Type' => 'text/html' }, 'hello'] }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'defaults to stdout' do
|
52
|
+
expect(Logger).to receive(:new).with($stdout).and_return(Logger.new(nil))
|
53
|
+
conn.get('/hello')
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'with default formatter' do
|
58
|
+
let(:formatter) { instance_double(Faraday::Logging::Formatter, request: true, response: true, filter: []) }
|
59
|
+
|
60
|
+
before { allow(Faraday::Logging::Formatter).to receive(:new).and_return(formatter) }
|
61
|
+
|
62
|
+
it 'delegates logging to the formatter' do
|
63
|
+
expect(formatter).to receive(:request).with(an_instance_of(Faraday::Env))
|
64
|
+
expect(formatter).to receive(:response).with(an_instance_of(Faraday::Env))
|
65
|
+
conn.get '/hello'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'with custom formatter' do
|
70
|
+
let(:formatter_class) do
|
71
|
+
Class.new(Faraday::Logging::Formatter) do
|
72
|
+
def request(_env)
|
73
|
+
info 'Custom log formatter request'
|
74
|
+
end
|
75
|
+
|
76
|
+
def response(_env)
|
77
|
+
info 'Custom log formatter response'
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
let(:logger_options) { { formatter: formatter_class } }
|
83
|
+
|
84
|
+
it 'logs with custom formatter' do
|
85
|
+
conn.get '/hello'
|
86
|
+
|
87
|
+
expect(string_io.string).to match('Custom log formatter request')
|
88
|
+
expect(string_io.string).to match('Custom log formatter response')
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'logs method and url' do
|
93
|
+
conn.get '/hello', nil, accept: 'text/html'
|
94
|
+
expect(string_io.string).to match('GET http:/hello')
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'logs request headers by default' do
|
98
|
+
conn.get '/hello', nil, accept: 'text/html'
|
99
|
+
expect(string_io.string).to match(%(Accept: "text/html))
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'logs response headers by default' do
|
103
|
+
conn.get '/hello', nil, accept: 'text/html'
|
104
|
+
expect(string_io.string).to match(%(Content-Type: "text/html))
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'does not log request body by default' do
|
108
|
+
conn.post '/ohai', 'name=Unagi', accept: 'text/html'
|
109
|
+
expect(string_io.string).not_to match(%(name=Unagi))
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'does not log response body by default' do
|
113
|
+
conn.post '/ohai', 'name=Toro', accept: 'text/html'
|
114
|
+
expect(string_io.string).not_to match(%(fred))
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'logs filter headers' do
|
118
|
+
conn.headers = { 'api_key' => 'ABC123' }
|
119
|
+
conn.get '/filtered_headers', nil, accept: 'text/html'
|
120
|
+
expect(string_io.string).to match(%(api_key:))
|
121
|
+
expect(string_io.string).to match(%([API_KEY]))
|
122
|
+
expect(string_io.string).not_to match(%(ABC123))
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'logs filter url' do
|
126
|
+
conn.get '/filtered_url?password=hunter2', nil, accept: 'text/html'
|
127
|
+
expect(string_io.string).to match(%([HIDDEN]))
|
128
|
+
expect(string_io.string).not_to match(%(hunter2))
|
129
|
+
end
|
130
|
+
|
131
|
+
context 'when not logging request headers' do
|
132
|
+
let(:logger_options) { { headers: { request: false } } }
|
133
|
+
|
134
|
+
it 'does not log request headers if option is false' do
|
135
|
+
conn.get '/hello', nil, accept: 'text/html'
|
136
|
+
expect(string_io.string).not_to match(%(Accept: "text/html))
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
context 'when not logging response headers' do
|
141
|
+
let(:logger_options) { { headers: { response: false } } }
|
142
|
+
|
143
|
+
it 'does not log response headers if option is false' do
|
144
|
+
conn.get '/hello', nil, accept: 'text/html'
|
145
|
+
expect(string_io.string).not_to match(%(Content-Type: "text/html))
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
context 'when logging request body' do
|
150
|
+
let(:logger_options) { { bodies: { request: true } } }
|
151
|
+
|
152
|
+
it 'log only request body' do
|
153
|
+
conn.post '/ohyes', 'name=Tamago', accept: 'text/html'
|
154
|
+
expect(string_io.string).to match(%(name=Tamago))
|
155
|
+
expect(string_io.string).not_to match(%(pebbles))
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
context 'when logging response body' do
|
160
|
+
let(:logger_options) { { bodies: { response: true } } }
|
161
|
+
|
162
|
+
it 'log only response body' do
|
163
|
+
conn.post '/ohyes', 'name=Hamachi', accept: 'text/html'
|
164
|
+
expect(string_io.string).to match(%(pebbles))
|
165
|
+
expect(string_io.string).not_to match(%(name=Hamachi))
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
context 'when logging request and response bodies' do
|
170
|
+
let(:logger_options) { { bodies: true } }
|
171
|
+
|
172
|
+
it 'log request and response body' do
|
173
|
+
conn.post '/ohyes', 'name=Ebi', accept: 'text/html'
|
174
|
+
expect(string_io.string).to match(%(name=Ebi))
|
175
|
+
expect(string_io.string).to match(%(pebbles))
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'log response body object' do
|
179
|
+
conn.get '/rubbles', nil, accept: 'text/html'
|
180
|
+
expect(string_io.string).to match(%([\"Barney\", \"Betty\", \"Bam Bam\"]\n))
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'logs filter body' do
|
184
|
+
conn.get '/filtered_body', nil, accept: 'text/html'
|
185
|
+
expect(string_io.string).to match(%(soylent green is))
|
186
|
+
expect(string_io.string).to match(%(tasty))
|
187
|
+
expect(string_io.string).not_to match(%(people))
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
context 'when using log_level' do
|
192
|
+
let(:logger_options) { { bodies: true, log_level: :debug } }
|
193
|
+
|
194
|
+
it 'logs request/request body on the specified level (debug)' do
|
195
|
+
logger.level = Logger::DEBUG
|
196
|
+
conn.post '/ohyes', 'name=Ebi', accept: 'text/html'
|
197
|
+
expect(string_io.string).to match(%(name=Ebi))
|
198
|
+
expect(string_io.string).to match(%(pebbles))
|
199
|
+
end
|
200
|
+
|
201
|
+
it 'logs headers on the debug level' do
|
202
|
+
logger.level = Logger::DEBUG
|
203
|
+
conn.get '/hello', nil, accept: 'text/html'
|
204
|
+
expect(string_io.string).to match(%(Content-Type: "text/html))
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'does not log request/response body on the info level' do
|
208
|
+
logger.level = Logger::INFO
|
209
|
+
conn.post '/ohyes', 'name=Ebi', accept: 'text/html'
|
210
|
+
expect(string_io.string).not_to match(%(name=Ebi))
|
211
|
+
expect(string_io.string).not_to match(%(pebbles))
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'does not log headers on the info level' do
|
215
|
+
logger.level = Logger::INFO
|
216
|
+
conn.get '/hello', nil, accept: 'text/html'
|
217
|
+
expect(string_io.string).not_to match(%(Content-Type: "text/html))
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
@@ -0,0 +1,68 @@
|
|
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
|
@@ -0,0 +1,169 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe Faraday::Response::RaiseError do
|
4
|
+
let(:conn) do
|
5
|
+
Faraday.new do |b|
|
6
|
+
b.response :raise_error
|
7
|
+
b.adapter :test do |stub|
|
8
|
+
stub.get('ok') { [200, { 'Content-Type' => 'text/html' }, '<body></body>'] }
|
9
|
+
stub.get('bad-request') { [400, { 'X-Reason' => 'because' }, 'keep looking'] }
|
10
|
+
stub.get('unauthorized') { [401, { 'X-Reason' => 'because' }, 'keep looking'] }
|
11
|
+
stub.get('forbidden') { [403, { 'X-Reason' => 'because' }, 'keep looking'] }
|
12
|
+
stub.get('not-found') { [404, { 'X-Reason' => 'because' }, 'keep looking'] }
|
13
|
+
stub.get('proxy-error') { [407, { 'X-Reason' => 'because' }, 'keep looking'] }
|
14
|
+
stub.get('conflict') { [409, { 'X-Reason' => 'because' }, 'keep looking'] }
|
15
|
+
stub.get('unprocessable-entity') { [422, { 'X-Reason' => 'because' }, 'keep looking'] }
|
16
|
+
stub.get('4xx') { [499, { 'X-Reason' => 'because' }, 'keep looking'] }
|
17
|
+
stub.get('nil-status') { [nil, { 'X-Reason' => 'nil' }, 'fail'] }
|
18
|
+
stub.get('server-error') { [500, { 'X-Error' => 'bailout' }, 'fail'] }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'raises no exception for 200 responses' do
|
24
|
+
expect { conn.get('ok') }.not_to raise_error
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'raises Faraday::BadRequestError for 400 responses' do
|
28
|
+
expect { conn.get('bad-request') }.to raise_error(Faraday::BadRequestError) do |ex|
|
29
|
+
expect(ex.message).to eq('the server responded with status 400')
|
30
|
+
expect(ex.response[:headers]['X-Reason']).to eq('because')
|
31
|
+
expect(ex.response[:status]).to eq(400)
|
32
|
+
expect(ex.response_status).to eq(400)
|
33
|
+
expect(ex.response_body).to eq('keep looking')
|
34
|
+
expect(ex.response_headers['X-Reason']).to eq('because')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'raises Faraday::UnauthorizedError for 401 responses' do
|
39
|
+
expect { conn.get('unauthorized') }.to raise_error(Faraday::UnauthorizedError) do |ex|
|
40
|
+
expect(ex.message).to eq('the server responded with status 401')
|
41
|
+
expect(ex.response[:headers]['X-Reason']).to eq('because')
|
42
|
+
expect(ex.response[:status]).to eq(401)
|
43
|
+
expect(ex.response_status).to eq(401)
|
44
|
+
expect(ex.response_body).to eq('keep looking')
|
45
|
+
expect(ex.response_headers['X-Reason']).to eq('because')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'raises Faraday::ForbiddenError for 403 responses' do
|
50
|
+
expect { conn.get('forbidden') }.to raise_error(Faraday::ForbiddenError) do |ex|
|
51
|
+
expect(ex.message).to eq('the server responded with status 403')
|
52
|
+
expect(ex.response[:headers]['X-Reason']).to eq('because')
|
53
|
+
expect(ex.response[:status]).to eq(403)
|
54
|
+
expect(ex.response_status).to eq(403)
|
55
|
+
expect(ex.response_body).to eq('keep looking')
|
56
|
+
expect(ex.response_headers['X-Reason']).to eq('because')
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'raises Faraday::ResourceNotFound for 404 responses' do
|
61
|
+
expect { conn.get('not-found') }.to raise_error(Faraday::ResourceNotFound) do |ex|
|
62
|
+
expect(ex.message).to eq('the server responded with status 404')
|
63
|
+
expect(ex.response[:headers]['X-Reason']).to eq('because')
|
64
|
+
expect(ex.response[:status]).to eq(404)
|
65
|
+
expect(ex.response_status).to eq(404)
|
66
|
+
expect(ex.response_body).to eq('keep looking')
|
67
|
+
expect(ex.response_headers['X-Reason']).to eq('because')
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'raises Faraday::ProxyAuthError for 407 responses' do
|
72
|
+
expect { conn.get('proxy-error') }.to raise_error(Faraday::ProxyAuthError) do |ex|
|
73
|
+
expect(ex.message).to eq('407 "Proxy Authentication Required"')
|
74
|
+
expect(ex.response[:headers]['X-Reason']).to eq('because')
|
75
|
+
expect(ex.response[:status]).to eq(407)
|
76
|
+
expect(ex.response_status).to eq(407)
|
77
|
+
expect(ex.response_body).to eq('keep looking')
|
78
|
+
expect(ex.response_headers['X-Reason']).to eq('because')
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'raises Faraday::ConflictError for 409 responses' do
|
83
|
+
expect { conn.get('conflict') }.to raise_error(Faraday::ConflictError) do |ex|
|
84
|
+
expect(ex.message).to eq('the server responded with status 409')
|
85
|
+
expect(ex.response[:headers]['X-Reason']).to eq('because')
|
86
|
+
expect(ex.response[:status]).to eq(409)
|
87
|
+
expect(ex.response_status).to eq(409)
|
88
|
+
expect(ex.response_body).to eq('keep looking')
|
89
|
+
expect(ex.response_headers['X-Reason']).to eq('because')
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
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')
|
96
|
+
expect(ex.response[:headers]['X-Reason']).to eq('because')
|
97
|
+
expect(ex.response[:status]).to eq(422)
|
98
|
+
expect(ex.response_status).to eq(422)
|
99
|
+
expect(ex.response_body).to eq('keep looking')
|
100
|
+
expect(ex.response_headers['X-Reason']).to eq('because')
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'raises Faraday::NilStatusError for nil status in response' do
|
105
|
+
expect { conn.get('nil-status') }.to raise_error(Faraday::NilStatusError) do |ex|
|
106
|
+
expect(ex.message).to eq('http status could not be derived from the server response')
|
107
|
+
expect(ex.response[:headers]['X-Reason']).to eq('nil')
|
108
|
+
expect(ex.response[:status]).to be_nil
|
109
|
+
expect(ex.response_status).to be_nil
|
110
|
+
expect(ex.response_body).to eq('fail')
|
111
|
+
expect(ex.response_headers['X-Reason']).to eq('nil')
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'raises Faraday::ClientError for other 4xx responses' do
|
116
|
+
expect { conn.get('4xx') }.to raise_error(Faraday::ClientError) do |ex|
|
117
|
+
expect(ex.message).to eq('the server responded with status 499')
|
118
|
+
expect(ex.response[:headers]['X-Reason']).to eq('because')
|
119
|
+
expect(ex.response[:status]).to eq(499)
|
120
|
+
expect(ex.response_status).to eq(499)
|
121
|
+
expect(ex.response_body).to eq('keep looking')
|
122
|
+
expect(ex.response_headers['X-Reason']).to eq('because')
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'raises Faraday::ServerError for 500 responses' do
|
127
|
+
expect { conn.get('server-error') }.to raise_error(Faraday::ServerError) do |ex|
|
128
|
+
expect(ex.message).to eq('the server responded with status 500')
|
129
|
+
expect(ex.response[:headers]['X-Error']).to eq('bailout')
|
130
|
+
expect(ex.response[:status]).to eq(500)
|
131
|
+
expect(ex.response_status).to eq(500)
|
132
|
+
expect(ex.response_body).to eq('fail')
|
133
|
+
expect(ex.response_headers['X-Error']).to eq('bailout')
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
describe 'request info' do
|
138
|
+
let(:conn) do
|
139
|
+
Faraday.new do |b|
|
140
|
+
b.response :raise_error
|
141
|
+
b.adapter :test do |stub|
|
142
|
+
stub.post('request?full=true', request_body, request_headers) do
|
143
|
+
[400, { 'X-Reason' => 'because' }, 'keep looking']
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
let(:request_body) { JSON.generate({ 'item' => 'sth' }) }
|
149
|
+
let(:request_headers) { { 'Authorization' => 'Basic 123' } }
|
150
|
+
|
151
|
+
subject(:perform_request) do
|
152
|
+
conn.post 'request' do |req|
|
153
|
+
req.headers['Authorization'] = 'Basic 123'
|
154
|
+
req.params[:full] = true
|
155
|
+
req.body = request_body
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'returns the request info in the exception' do
|
160
|
+
expect { perform_request }.to raise_error(Faraday::BadRequestError) do |ex|
|
161
|
+
expect(ex.response[:request][:method]).to eq(:post)
|
162
|
+
expect(ex.response[:request][:url_path]).to eq('/request')
|
163
|
+
expect(ex.response[:request][:params]).to eq({ 'full' => 'true' })
|
164
|
+
expect(ex.response[:request][:headers]).to match(a_hash_including(request_headers))
|
165
|
+
expect(ex.response[:request][:body]).to eq(request_body)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe Faraday::Response do
|
4
|
+
subject { Faraday::Response.new(env) }
|
5
|
+
|
6
|
+
let(:env) do
|
7
|
+
Faraday::Env.from(status: 404, body: 'yikes',
|
8
|
+
response_headers: { 'Content-Type' => 'text/plain' })
|
9
|
+
end
|
10
|
+
|
11
|
+
it { expect(subject.finished?).to be_truthy }
|
12
|
+
it { expect { subject.finish({}) }.to raise_error(RuntimeError) }
|
13
|
+
it { expect(subject.success?).to be_falsey }
|
14
|
+
it { expect(subject.status).to eq(404) }
|
15
|
+
it { expect(subject.body).to eq('yikes') }
|
16
|
+
it { expect(subject.headers['Content-Type']).to eq('text/plain') }
|
17
|
+
it { expect(subject['content-type']).to eq('text/plain') }
|
18
|
+
|
19
|
+
describe '#apply_request' do
|
20
|
+
before { subject.apply_request(body: 'a=b', method: :post) }
|
21
|
+
|
22
|
+
it { expect(subject.body).to eq('yikes') }
|
23
|
+
it { expect(subject.env[:method]).to eq(:post) }
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#to_hash' do
|
27
|
+
let(:hash) { subject.to_hash }
|
28
|
+
|
29
|
+
it { expect(hash).to be_a(Hash) }
|
30
|
+
it { expect(hash[:status]).to eq(subject.status) }
|
31
|
+
it { expect(hash[:response_headers]).to eq(subject.headers) }
|
32
|
+
it { expect(hash[:body]).to eq(subject.body) }
|
33
|
+
end
|
34
|
+
|
35
|
+
describe 'marshal serialization support' do
|
36
|
+
subject { Faraday::Response.new }
|
37
|
+
let(:loaded) { Marshal.load(Marshal.dump(subject)) }
|
38
|
+
|
39
|
+
before do
|
40
|
+
subject.on_complete {}
|
41
|
+
subject.finish(env.merge(params: 'moo'))
|
42
|
+
end
|
43
|
+
|
44
|
+
it { expect(loaded.env[:params]).to be_nil }
|
45
|
+
it { expect(loaded.env[:body]).to eq(env[:body]) }
|
46
|
+
it { expect(loaded.env[:response_headers]).to eq(env[:response_headers]) }
|
47
|
+
it { expect(loaded.env[:status]).to eq(env[:status]) }
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '#on_complete' do
|
51
|
+
subject { Faraday::Response.new }
|
52
|
+
|
53
|
+
it 'parse body on finish' do
|
54
|
+
subject.on_complete { |env| env[:body] = env[:body].upcase }
|
55
|
+
subject.finish(env)
|
56
|
+
|
57
|
+
expect(subject.body).to eq('YIKES')
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'can access response body in on_complete callback' do
|
61
|
+
subject.on_complete { |env| env[:body] = subject.body.upcase }
|
62
|
+
subject.finish(env)
|
63
|
+
|
64
|
+
expect(subject.body).to eq('YIKES')
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'can access response body in on_complete callback' do
|
68
|
+
callback_env = nil
|
69
|
+
subject.on_complete { |env| callback_env = env }
|
70
|
+
subject.finish({})
|
71
|
+
|
72
|
+
expect(subject.env).to eq(callback_env)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|