faraday 0.6.0 → 0.8.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 (58) hide show
  1. data/Gemfile +22 -15
  2. data/{LICENSE → LICENSE.md} +1 -1
  3. data/README.md +216 -146
  4. data/Rakefile +14 -58
  5. data/config.ru +6 -0
  6. data/faraday.gemspec +31 -38
  7. data/lib/faraday/adapter/em_http.rb +204 -0
  8. data/lib/faraday/adapter/em_synchrony/parallel_manager.rb +66 -0
  9. data/lib/faraday/adapter/em_synchrony.rb +67 -16
  10. data/lib/faraday/adapter/excon.rb +1 -2
  11. data/lib/faraday/adapter/net_http.rb +82 -39
  12. data/lib/faraday/adapter/net_http_persistent.rb +37 -0
  13. data/lib/faraday/adapter/patron.rb +42 -8
  14. data/lib/faraday/adapter/rack.rb +59 -0
  15. data/lib/faraday/adapter/test.rb +49 -9
  16. data/lib/faraday/adapter/typhoeus.rb +70 -15
  17. data/lib/faraday/adapter.rb +41 -18
  18. data/lib/faraday/builder.rb +34 -16
  19. data/lib/faraday/connection.rb +177 -115
  20. data/lib/faraday/error.rb +4 -2
  21. data/lib/faraday/middleware.rb +18 -10
  22. data/lib/faraday/request/authorization.rb +40 -0
  23. data/lib/faraday/request/basic_authentication.rb +13 -0
  24. data/lib/faraday/request/multipart.rb +5 -6
  25. data/lib/faraday/request/retry.rb +21 -0
  26. data/lib/faraday/request/token_authentication.rb +15 -0
  27. data/lib/faraday/request/url_encoded.rb +1 -3
  28. data/lib/faraday/request.rb +48 -38
  29. data/lib/faraday/response/raise_error.rb +1 -1
  30. data/lib/faraday/response.rb +6 -5
  31. data/lib/faraday/upload_io.rb +1 -1
  32. data/lib/faraday/utils.rb +196 -29
  33. data/lib/faraday.rb +69 -13
  34. data/test/adapters/default_test.rb +14 -0
  35. data/test/adapters/em_http_test.rb +14 -0
  36. data/test/adapters/em_synchrony_test.rb +14 -0
  37. data/test/adapters/excon_test.rb +23 -0
  38. data/test/adapters/integration.rb +190 -0
  39. data/test/adapters/logger_test.rb +2 -2
  40. data/test/adapters/net_http_persistent_test.rb +11 -0
  41. data/test/adapters/net_http_test.rb +37 -21
  42. data/test/adapters/patron_test.rb +17 -0
  43. data/test/adapters/rack_test.rb +26 -0
  44. data/test/adapters/test_middleware_test.rb +28 -1
  45. data/test/adapters/typhoeus_test.rb +14 -0
  46. data/test/authentication_middleware_test.rb +65 -0
  47. data/test/connection_test.rb +211 -81
  48. data/test/env_test.rb +80 -31
  49. data/test/helper.rb +23 -13
  50. data/test/live_server.rb +38 -29
  51. data/test/middleware/retry_test.rb +25 -0
  52. data/test/middleware_stack_test.rb +85 -4
  53. data/test/request_middleware_test.rb +42 -19
  54. data/test/response_middleware_test.rb +30 -3
  55. metadata +104 -110
  56. data/lib/faraday/adapter/action_dispatch.rb +0 -30
  57. data/lib/faraday/request/json.rb +0 -31
  58. data/test/adapters/live_test.rb +0 -186
@@ -0,0 +1,190 @@
1
+ require 'forwardable'
2
+ require File.expand_path("../../helper", __FILE__)
3
+
4
+ module Adapters
5
+ # Adapter integration tests. To use, implement two methods:
6
+ #
7
+ # `#adapter` required. returns a symbol for the adapter middleware name
8
+ # `#adapter_options` optional. extra arguments for building an adapter
9
+ module Integration
10
+ def self.apply(base, *extras)
11
+ if Faraday::TestCase::LIVE_SERVER
12
+ ([:Common] + extras).each {|name| base.send(:include, self.const_get(name)) }
13
+ yield if block_given?
14
+ elsif !defined? @warned
15
+ warn "Warning: Not running integration tests against a live server."
16
+ warn "Start the server `ruby test/live_server.rb` and set the LIVE=1 env variable."
17
+ @warned = true
18
+ end
19
+ end
20
+
21
+ module Parallel
22
+ def test_in_parallel
23
+ resp1, resp2 = nil, nil
24
+
25
+ connection = create_connection
26
+ connection.in_parallel do
27
+ resp1 = connection.get('echo?a=1')
28
+ resp2 = connection.get('echo?b=2')
29
+ assert connection.in_parallel?
30
+ assert_nil resp1.body
31
+ assert_nil resp2.body
32
+ end
33
+ assert !connection.in_parallel?
34
+ assert_equal 'get ?{"a"=>"1"}', resp1.body
35
+ assert_equal 'get ?{"b"=>"2"}', resp2.body
36
+ end
37
+ end
38
+
39
+ module NonParallel
40
+ def test_no_parallel_support
41
+ connection = create_connection
42
+ response = nil
43
+
44
+ err = capture_warnings do
45
+ connection.in_parallel do
46
+ response = connection.get('echo').body
47
+ end
48
+ end
49
+ assert response
50
+ assert_match "no parallel-capable adapter on Faraday stack", err
51
+ assert_match __FILE__, err
52
+ end
53
+ end
54
+
55
+ module Compression
56
+ def test_GET_handles_compression
57
+ res = get('echo_header', :name => 'accept-encoding')
58
+ assert_match /gzip;.+\bdeflate\b/, res.body
59
+ end
60
+ end
61
+
62
+ module Common
63
+ extend Forwardable
64
+ def_delegators :create_connection, :get, :head, :put, :post, :patch, :delete, :run_request
65
+
66
+ def test_GET_retrieves_the_response_body
67
+ assert_equal 'get', get('echo').body
68
+ end
69
+
70
+ def test_GET_send_url_encoded_params
71
+ assert_equal %(get ?{"name"=>"zack"}), get('echo', :name => 'zack').body
72
+ end
73
+
74
+ def test_GET_retrieves_the_response_headers
75
+ response = get('echo')
76
+ assert_match(/text\/plain/, response.headers['Content-Type'], 'original case fail')
77
+ assert_match(/text\/plain/, response.headers['content-type'], 'lowercase fail')
78
+ end
79
+
80
+ def test_GET_handles_headers_with_multiple_values
81
+ assert_equal 'one, two', get('multi').headers['set-cookie']
82
+ end
83
+
84
+ def test_GET_with_body
85
+ response = get('echo') do |req|
86
+ req.body = {'bodyrock' => true}
87
+ end
88
+ assert_equal %(get {"bodyrock"=>"true"}), response.body
89
+ end
90
+
91
+ def test_GET_sends_user_agent
92
+ response = get('echo_header', {:name => 'user-agent'}, :user_agent => 'Agent Faraday')
93
+ assert_equal 'Agent Faraday', response.body
94
+ end
95
+
96
+ def test_POST_send_url_encoded_params
97
+ assert_equal %(post {"name"=>"zack"}), post('echo', :name => 'zack').body
98
+ end
99
+
100
+ def test_POST_send_url_encoded_nested_params
101
+ resp = post('echo', 'name' => {'first' => 'zack'})
102
+ assert_equal %(post {"name"=>{"first"=>"zack"}}), resp.body
103
+ end
104
+
105
+ def test_POST_retrieves_the_response_headers
106
+ assert_match(/text\/plain/, post('echo').headers['content-type'])
107
+ end
108
+
109
+ def test_POST_sends_files
110
+ resp = post('file') do |req|
111
+ req.body = {'uploaded_file' => Faraday::UploadIO.new(__FILE__, 'text/x-ruby')}
112
+ end
113
+ assert_equal "file integration.rb text/x-ruby", resp.body
114
+ end
115
+
116
+ def test_PUT_send_url_encoded_params
117
+ assert_equal %(put {"name"=>"zack"}), put('echo', :name => 'zack').body
118
+ end
119
+
120
+ def test_PUT_send_url_encoded_nested_params
121
+ resp = put('echo', 'name' => {'first' => 'zack'})
122
+ assert_equal %(put {"name"=>{"first"=>"zack"}}), resp.body
123
+ end
124
+
125
+ def test_PUT_retrieves_the_response_headers
126
+ assert_match(/text\/plain/, put('echo').headers['content-type'])
127
+ end
128
+
129
+ def test_PATCH_send_url_encoded_params
130
+ assert_equal %(patch {"name"=>"zack"}), patch('echo', :name => 'zack').body
131
+ end
132
+
133
+ def test_OPTIONS
134
+ resp = run_request(:options, 'echo', nil, {})
135
+ assert_equal 'options', resp.body
136
+ end
137
+
138
+ def test_HEAD_retrieves_no_response_body
139
+ # FIXME: some adapters return empty string, some nil
140
+ assert_equal '', head('echo').body.to_s
141
+ end
142
+
143
+ def test_HEAD_retrieves_the_response_headers
144
+ assert_match(/text\/plain/, head('echo').headers['content-type'])
145
+ end
146
+
147
+ def test_DELETE_retrieves_the_response_headers
148
+ assert_match(/text\/plain/, delete('echo').headers['content-type'])
149
+ end
150
+
151
+ def test_DELETE_retrieves_the_body
152
+ assert_equal %(delete), delete('echo').body
153
+ end
154
+
155
+ def test_timeout
156
+ conn = create_connection(:request => {:timeout => 1, :open_timeout => 1})
157
+ assert_raise Faraday::Error::TimeoutError do
158
+ conn.get '/slow'
159
+ end
160
+ end
161
+
162
+ def adapter
163
+ raise NotImplementedError.new("Need to override #adapter")
164
+ end
165
+
166
+ # extra options to pass when building the adapter
167
+ def adapter_options
168
+ []
169
+ end
170
+
171
+ def create_connection(options = {})
172
+ if adapter == :default
173
+ builder_block = nil
174
+ else
175
+ builder_block = Proc.new do |b|
176
+ b.request :multipart
177
+ b.request :url_encoded
178
+ b.adapter adapter, *adapter_options
179
+ end
180
+ end
181
+
182
+ Faraday::Connection.new(Faraday::TestCase::LIVE_SERVER, options, &builder_block).tap do |conn|
183
+ conn.headers['X-Faraday-Adapter'] = adapter.to_s
184
+ adapter_handler = conn.builder.handlers.last
185
+ conn.builder.insert_before adapter_handler, Faraday::Response::RaiseError
186
+ end
187
+ end
188
+ end
189
+ end
190
+ end
@@ -15,7 +15,7 @@ module Adapters
15
15
  stubs.get('/hello') { [200, {'Content-Type' => 'text/html'}, 'hello'] }
16
16
  end
17
17
  end
18
- @resp = @conn.get '/hello', :accept => 'text/html'
18
+ @resp = @conn.get '/hello', nil, :accept => 'text/html'
19
19
  end
20
20
 
21
21
  def test_still_returns_output
@@ -23,7 +23,7 @@ module Adapters
23
23
  end
24
24
 
25
25
  def test_logs_method_and_url
26
- assert_match "get /hello", @io.string
26
+ assert_match "get http:/hello", @io.string
27
27
  end
28
28
 
29
29
  def test_logs_request_headers
@@ -0,0 +1,11 @@
1
+ require File.expand_path('../integration', __FILE__)
2
+
3
+ module Adapters
4
+ class NetHttpPersistentTest < Faraday::TestCase
5
+
6
+ def adapter() :net_http_persistent end
7
+
8
+ Integration.apply(self, :NonParallel)
9
+
10
+ end
11
+ end
@@ -1,33 +1,49 @@
1
- require File.expand_path(File.join(File.dirname(__FILE__), '..', 'helper'))
1
+ require File.expand_path('../integration', __FILE__)
2
2
 
3
3
  module Adapters
4
4
  class NetHttpTest < Faraday::TestCase
5
- def setup
6
- @connection = Faraday.new('http://disney.com') do |b|
5
+
6
+ def adapter() :net_http end
7
+
8
+ behaviors = [:NonParallel]
9
+ behaviors << :Compression if RUBY_VERSION >= '1.9'
10
+
11
+ Integration.apply(self, *behaviors)
12
+
13
+ def test_connection_errors_get_wrapped
14
+ connection = Faraday.new('http://disney.com') do |b|
7
15
  b.adapter :net_http
8
16
  end
9
- end
10
17
 
11
- def test_handles_compression_transparently_on_get
12
- stub_request(:get, 'disney.com/hello').with { |request|
13
- accept_encoding = request.headers['Accept-Encoding']
14
- if RUBY_VERSION.index('1.8') == 0
15
- # ruby 1.8 doesn't do any gzip/deflate automatically
16
- accept_encoding == nil
17
- else
18
- # test for a value such as "gzip;q=1.0,deflate;q=0.6,identity;q=0.3"
19
- accept_encoding =~ /gzip;.+\bdeflate\b/
20
- end
21
- }
22
- @connection.get('/hello')
23
- end
18
+ exceptions = [
19
+ EOFError,
20
+ Errno::ECONNABORTED,
21
+ Errno::ECONNREFUSED,
22
+ Errno::ECONNRESET,
23
+ Errno::EINVAL,
24
+ Net::HTTPBadResponse,
25
+ Net::HTTPHeaderSyntaxError,
26
+ Net::ProtocolError,
27
+ SocketError
28
+ ]
29
+
30
+ exceptions << OpenSSL::SSL::SSLError if defined?(OpenSSL)
24
31
 
25
- def test_connect_error_gets_wrapped
26
- stub_request(:get, 'disney.com/hello').to_raise(Errno::ECONNREFUSED)
32
+ exceptions.each do |exception_class|
33
+ stub_request(:get, 'disney.com/hello').to_raise(exception_class)
27
34
 
28
- assert_raise Faraday::Error::ConnectionFailed do
29
- @connection.get('/hello')
35
+ assert_raise(Faraday::Error::ConnectionFailed,
36
+ "Failed to wrap #{exception_class} exceptions") do
37
+ connection.get('/hello')
38
+ end
30
39
  end
31
40
  end
41
+
42
+ def test_configure_ssl
43
+ http = Net::HTTP.new 'disney.com', 443
44
+ # this should not raise an error
45
+ Faraday::Adapter::NetHttp.new.configure_ssl(http, :ssl => {:verify => true})
46
+ end
47
+
32
48
  end
33
49
  end
@@ -0,0 +1,17 @@
1
+ require File.expand_path('../integration', __FILE__)
2
+
3
+ module Adapters
4
+ class Patron < Faraday::TestCase
5
+
6
+ def adapter() :patron end
7
+
8
+ Integration.apply(self, :NonParallel) do
9
+ # https://github.com/toland/patron/issues/34
10
+ undef :test_PATCH_send_url_encoded_params
11
+
12
+ # https://github.com/toland/patron/issues/52
13
+ undef :test_GET_with_body
14
+ end
15
+
16
+ end unless defined? RUBY_ENGINE and 'jruby' == RUBY_ENGINE
17
+ end
@@ -0,0 +1,26 @@
1
+ require File.expand_path("../integration", __FILE__)
2
+ require File.expand_path('../../live_server', __FILE__)
3
+
4
+ module Adapters
5
+ class RackTest < Faraday::TestCase
6
+
7
+ def adapter() :rack end
8
+
9
+ def adapter_options
10
+ [FaradayTestServer]
11
+ end
12
+
13
+ # no Integration.apply because this doesn't require a server as a separate process
14
+ include Integration::Common
15
+ include Integration::NonParallel
16
+
17
+ # not using shared test because error is swallowed by Sinatra
18
+ def test_timeout
19
+ conn = create_connection(:request => {:timeout => 1, :open_timeout => 1})
20
+ begin
21
+ conn.get '/slow'
22
+ rescue Faraday::Error::ClientError
23
+ end
24
+ end
25
+ end
26
+ end
@@ -27,6 +27,20 @@ module Adapters
27
27
  assert_equal 'hello', @conn.get("/hello").body
28
28
  end
29
29
 
30
+ def test_middleware_with_get_params
31
+ @stubs.get('/param?a=1') { [200, {}, 'a'] }
32
+ assert_equal 'a', @conn.get('/param?a=1').body
33
+ end
34
+
35
+ def test_middleware_ignores_unspecified_get_params
36
+ @stubs.get('/optional?a=1') { [200, {}, 'a'] }
37
+ assert_equal 'a', @conn.get('/optional?a=1&b=1').body
38
+ assert_equal 'a', @conn.get('/optional?a=1').body
39
+ assert_raise Faraday::Adapter::Test::Stubs::NotFound do
40
+ @conn.get('/optional')
41
+ end
42
+ end
43
+
30
44
  def test_middleware_allow_different_outcomes_for_the_same_request
31
45
  @stubs.get('/hello') { [200, {'Content-Type' => 'text/html'}, 'hello'] }
32
46
  @stubs.get('/hello') { [200, {'Content-Type' => 'text/html'}, 'world'] }
@@ -34,8 +48,21 @@ module Adapters
34
48
  assert_equal 'world', @conn.get("/hello").body
35
49
  end
36
50
 
51
+ def test_yields_env_to_stubs
52
+ @stubs.get '/hello' do |env|
53
+ assert_equal '/hello', env[:url].path
54
+ assert_equal 'foo.com', env[:url].host
55
+ assert_equal '1', env[:params]['a']
56
+ assert_equal 'text/plain', env[:request_headers]['Accept']
57
+ [200, {}, 'a']
58
+ end
59
+
60
+ @conn.headers['Accept'] = 'text/plain'
61
+ assert_equal 'a', @conn.get('http://foo.com/hello?a=1').body
62
+ end
63
+
37
64
  def test_raises_an_error_if_no_stub_is_found_for_request
38
- assert_raise RuntimeError do
65
+ assert_raise Faraday::Adapter::Test::Stubs::NotFound do
39
66
  @conn.get('/invalid'){ [200, {}, []] }
40
67
  end
41
68
  end
@@ -0,0 +1,14 @@
1
+ require File.expand_path('../integration', __FILE__)
2
+
3
+ module Adapters
4
+ class TyphoeusTest < Faraday::TestCase
5
+
6
+ def adapter() :typhoeus end
7
+
8
+ Integration.apply(self, :Parallel) do
9
+ # https://github.com/dbalatero/typhoeus/issues/75
10
+ undef :test_GET_with_body
11
+ end
12
+
13
+ end unless defined? RUBY_ENGINE and 'jruby' == RUBY_ENGINE
14
+ end
@@ -0,0 +1,65 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'helper'))
2
+
3
+ class AuthenticationMiddlewareTest < Faraday::TestCase
4
+ def conn
5
+ Faraday::Connection.new('http://example.net/') do |builder|
6
+ yield builder
7
+ builder.adapter :test do |stub|
8
+ stub.get('/auth-echo') do |env|
9
+ [200, {}, env[:request_headers]['Authorization']]
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ def test_basic_middleware_adds_basic_header
16
+ response = conn { |b| b.request :basic_auth, 'aladdin', 'opensesame' }.get('/auth-echo')
17
+ assert_equal 'Basic YWxhZGRpbjpvcGVuc2VzYW1l', response.body
18
+ end
19
+
20
+ def test_basic_middleware_adds_basic_header_correctly_with_long_values
21
+ response = conn { |b| b.request :basic_auth, 'A' * 255, '' }.get('/auth-echo')
22
+ assert_equal "Basic #{'QUFB' * 85}Og==", response.body
23
+ end
24
+
25
+ def test_basic_middleware_does_not_interfere_with_existing_authorization
26
+ response = conn { |b| b.request :basic_auth, 'aladdin', 'opensesame' }.
27
+ get('/auth-echo', nil, :authorization => 'Token token="bar"')
28
+ assert_equal 'Token token="bar"', response.body
29
+ end
30
+
31
+ def test_token_middleware_adds_token_header
32
+ response = conn { |b| b.request :token_auth, 'quux' }.get('/auth-echo')
33
+ assert_equal 'Token token="quux"', response.body
34
+ end
35
+
36
+ def test_token_middleware_includes_other_values_if_provided
37
+ response = conn { |b|
38
+ b.request :token_auth, 'baz', :foo => 42
39
+ }.get('/auth-echo')
40
+ assert_match /^Token /, response.body
41
+ assert_match /token="baz"/, response.body
42
+ assert_match /foo="42"/, response.body
43
+ end
44
+
45
+ def test_token_middleware_does_not_interfere_with_existing_authorization
46
+ response = conn { |b| b.request :token_auth, 'quux' }.
47
+ get('/auth-echo', nil, :authorization => 'Token token="bar"')
48
+ assert_equal 'Token token="bar"', response.body
49
+ end
50
+
51
+ def test_authorization_middleware_with_string
52
+ response = conn { |b|
53
+ b.request :authorization, 'custom', 'abc def'
54
+ }.get('/auth-echo')
55
+ assert_match /^custom abc def$/, response.body
56
+ end
57
+
58
+ def test_authorization_middleware_with_hash
59
+ response = conn { |b|
60
+ b.request :authorization, 'baz', :foo => 42
61
+ }.get('/auth-echo')
62
+ assert_match /^baz /, response.body
63
+ assert_match /foo="42"/, response.body
64
+ end
65
+ end