faraday_middleware 0.7.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 (45) hide show
  1. data/.rspec +0 -1
  2. data/.travis.yml +1 -2
  3. data/CHANGELOG.md +9 -9
  4. data/Gemfile +3 -3
  5. data/README.md +2 -2
  6. data/Rakefile +9 -2
  7. data/faraday_middleware.gemspec +16 -24
  8. data/lib/faraday_middleware.rb +51 -11
  9. data/lib/faraday_middleware/addressable_patch.rb +20 -0
  10. data/lib/faraday_middleware/backwards_compatibility.rb +30 -0
  11. data/lib/faraday_middleware/instrumentation.rb +30 -0
  12. data/lib/faraday_middleware/rack_compatible.rb +76 -0
  13. data/lib/faraday_middleware/request/encode_json.rb +50 -0
  14. data/lib/faraday_middleware/request/oauth.rb +61 -0
  15. data/lib/faraday_middleware/request/oauth2.rb +60 -0
  16. data/lib/faraday_middleware/response/caching.rb +76 -0
  17. data/lib/faraday_middleware/response/follow_redirects.rb +53 -0
  18. data/lib/{faraday → faraday_middleware}/response/mashify.rb +2 -2
  19. data/lib/faraday_middleware/response/parse_json.rb +35 -0
  20. data/lib/faraday_middleware/response/parse_marshal.rb +10 -0
  21. data/lib/faraday_middleware/response/parse_xml.rb +11 -0
  22. data/lib/faraday_middleware/response/parse_yaml.rb +10 -0
  23. data/lib/faraday_middleware/response/rashify.rb +9 -0
  24. data/lib/faraday_middleware/response_middleware.rb +78 -0
  25. data/lib/faraday_middleware/version.rb +1 -1
  26. data/spec/caching_test.rb +122 -0
  27. data/spec/encode_json_spec.rb +95 -0
  28. data/spec/follow_redirects_spec.rb +33 -0
  29. data/spec/helper.rb +27 -12
  30. data/spec/mashify_spec.rb +8 -7
  31. data/spec/oauth2_spec.rb +100 -32
  32. data/spec/oauth_spec.rb +83 -28
  33. data/spec/parse_json_spec.rb +71 -46
  34. data/spec/parse_marshal_spec.rb +9 -26
  35. data/spec/parse_xml_spec.rb +56 -24
  36. data/spec/parse_yaml_spec.rb +40 -20
  37. data/spec/rashify_spec.rb +4 -3
  38. metadata +59 -57
  39. data/lib/faraday/request/oauth.rb +0 -23
  40. data/lib/faraday/request/oauth2.rb +0 -24
  41. data/lib/faraday/response/parse_json.rb +0 -20
  42. data/lib/faraday/response/parse_marshal.rb +0 -10
  43. data/lib/faraday/response/parse_xml.rb +0 -11
  44. data/lib/faraday/response/parse_yaml.rb +0 -11
  45. data/lib/faraday/response/rashify.rb +0 -19
@@ -0,0 +1,95 @@
1
+ require 'helper'
2
+ require 'faraday_middleware/request/encode_json'
3
+
4
+ describe FaradayMiddleware::EncodeJson do
5
+ let(:middleware) { described_class.new(lambda{|env| env}) }
6
+
7
+ def process(body, content_type = nil)
8
+ env = {:body => body, :request_headers => Faraday::Utils::Headers.new}
9
+ env[:request_headers]['content-type'] = content_type if content_type
10
+ middleware.call(env)
11
+ end
12
+
13
+ def result_body() result[:body] end
14
+ def result_type() result[:request_headers]['content-type'] end
15
+
16
+ context "no body" do
17
+ let(:result) { process(nil) }
18
+
19
+ it "doesn't change body" do
20
+ result_body.should be_nil
21
+ end
22
+
23
+ it "doesn't add content type" do
24
+ result_type.should be_nil
25
+ end
26
+ end
27
+
28
+ context "empty body" do
29
+ let(:result) { process('') }
30
+
31
+ it "doesn't change body" do
32
+ result_body.should be_empty
33
+ end
34
+
35
+ it "doesn't add content type" do
36
+ result_type.should be_nil
37
+ end
38
+ end
39
+
40
+ context "string body" do
41
+ let(:result) { process('{"a":1}') }
42
+
43
+ it "doesn't change body" do
44
+ result_body.should eql('{"a":1}')
45
+ end
46
+
47
+ it "adds content type" do
48
+ result_type.should eql('application/json')
49
+ end
50
+ end
51
+
52
+ context "object body" do
53
+ let(:result) { process({:a => 1}) }
54
+
55
+ it "encodes body" do
56
+ result_body.should eql('{"a":1}')
57
+ end
58
+
59
+ it "adds content type" do
60
+ result_type.should eql('application/json')
61
+ end
62
+ end
63
+
64
+ context "empty object body" do
65
+ let(:result) { process({}) }
66
+
67
+ it "encodes body" do
68
+ result_body.should eql('{}')
69
+ end
70
+ end
71
+
72
+ context "object body with json type" do
73
+ let(:result) { process({:a => 1}, 'application/json; charset=utf-8') }
74
+
75
+ it "encodes body" do
76
+ result_body.should eql('{"a":1}')
77
+ end
78
+
79
+ it "doesn't change content type" do
80
+ result_type.should eql('application/json; charset=utf-8')
81
+ end
82
+ end
83
+
84
+ context "object body with incompatible type" do
85
+ let(:result) { process({:a => 1}, 'application/xml; charset=utf-8') }
86
+
87
+ it "doesn't change body" do
88
+ result_body.should eql({:a => 1})
89
+ end
90
+
91
+ it "doesn't change content type" do
92
+ result_type.should eql('application/xml; charset=utf-8')
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,33 @@
1
+ require 'helper'
2
+ require 'faraday_middleware/response/follow_redirects'
3
+ require 'faraday'
4
+ require 'forwardable'
5
+
6
+ describe FaradayMiddleware::FollowRedirects do
7
+ let(:connection) {
8
+ Faraday.new do |c|
9
+ c.use described_class
10
+ c.adapter :test do |stub|
11
+ stub.get('/') { [301, {'Location' => '/found'}, ''] }
12
+ stub.post('/create') { [302, {'Location' => '/'}, ''] }
13
+ stub.get('/found') { [200, {'Content-Type' => 'text/plain'}, 'fin'] }
14
+ stub.get('/loop') { [302, {'Location' => '/loop'}, ''] }
15
+ end
16
+ end
17
+ }
18
+
19
+ extend Forwardable
20
+ def_delegators :connection, :get, :post
21
+
22
+ it "follows redirect" do
23
+ get('/').body.should eql('fin')
24
+ end
25
+
26
+ it "follows redirect twice" do
27
+ post('/create').body.should eql('fin')
28
+ end
29
+
30
+ it "raises exception on loop" do
31
+ expect { get('/loop') }.to raise_error(FaradayMiddleware::RedirectLimitReached)
32
+ end
33
+ end
@@ -1,18 +1,33 @@
1
- $:.unshift File.expand_path('..', __FILE__)
2
- $:.unshift File.expand_path('../../lib', __FILE__)
3
- require 'simplecov'
4
- SimpleCov.start
5
- require 'faraday_middleware'
6
- require 'rspec'
1
+ if ENV['COVERAGE']
2
+ require 'simplecov'
3
+ SimpleCov.start do
4
+ # add_filter 'faraday_middleware.rb'
5
+ add_filter 'backwards_compatibility.rb'
6
+ end
7
+ end
7
8
 
8
- class DummyApp
9
- attr_accessor :env
9
+ require 'rspec'
10
10
 
11
- def call(env)
12
- @env = env
11
+ module ResponseMiddlewareExampleGroup
12
+ def self.included(base)
13
+ base.let(:options) { Hash.new }
14
+ base.let(:middleware) {
15
+ described_class.new(lambda {|env|
16
+ Faraday::Response.new(env)
17
+ }, options)
18
+ }
13
19
  end
14
20
 
15
- def reset
16
- @env = nil
21
+ def process(body, content_type = nil, options = {})
22
+ env = {
23
+ :body => body, :request => options,
24
+ :response_headers => Faraday::Utils::Headers.new
25
+ }
26
+ env[:response_headers]['content-type'] = content_type if content_type
27
+ middleware.call(env)
17
28
  end
18
29
  end
30
+
31
+ RSpec.configure do |config|
32
+ config.include ResponseMiddlewareExampleGroup, :type => :response
33
+ end
@@ -1,16 +1,17 @@
1
1
  require 'helper'
2
+ require 'faraday_middleware/response/mashify'
2
3
 
3
- describe Faraday::Response::Mashify do
4
+ describe FaradayMiddleware::Mashify do
4
5
  context 'during configuration' do
5
6
  it 'should allow for a custom Mash class to be set' do
6
- Faraday::Response::Mashify.should respond_to(:mash_class)
7
- Faraday::Response::Mashify.should respond_to(:mash_class=)
7
+ described_class.should respond_to(:mash_class)
8
+ described_class.should respond_to(:mash_class=)
8
9
  end
9
10
  end
10
11
 
11
12
  context 'when used' do
12
- before(:each) { Faraday::Response::Mashify.mash_class = ::Hashie::Mash }
13
- let(:mashify) { Faraday::Response::Mashify.new }
13
+ before(:each) { described_class.mash_class = ::Hashie::Mash }
14
+ let(:mashify) { described_class.new }
14
15
 
15
16
  it 'should create a Hashie::Mash from the body' do
16
17
  env = { :body => { "name" => "Erik Michaels-Ober", "username" => "sferik" } }
@@ -48,7 +49,7 @@ describe Faraday::Response::Mashify do
48
49
 
49
50
  it 'should allow for use of custom Mash subclasses' do
50
51
  class MyMash < ::Hashie::Mash; end
51
- Faraday::Response::Mashify.mash_class = MyMash
52
+ described_class.mash_class = MyMash
52
53
 
53
54
  env = { :body => { "name" => "Erik Michaels-Ober", "username" => "sferik" } }
54
55
  me = mashify.on_complete(env)
@@ -62,7 +63,7 @@ describe Faraday::Response::Mashify do
62
63
  let(:connection) do
63
64
  Faraday::Connection.new do |builder|
64
65
  builder.adapter :test, stubs
65
- builder.use Faraday::Response::Mashify
66
+ builder.use described_class
66
67
  end
67
68
  end
68
69
 
@@ -1,50 +1,118 @@
1
1
  require 'helper'
2
+ require 'uri'
3
+ require 'faraday_middleware/request/oauth2'
4
+ require 'faraday/utils'
2
5
 
3
- describe Faraday::Request::OAuth2 do
6
+ describe FaradayMiddleware::OAuth2 do
4
7
 
5
- context 'when used with a access token in the initializer' do
6
- let(:oauth2) { Faraday::Request::OAuth2.new(DummyApp.new, '1234') }
8
+ def query_params(env)
9
+ Faraday::Utils.parse_query env[:url].query
10
+ end
11
+
12
+ def auth_header(env)
13
+ env[:request_headers]['Authorization']
14
+ end
15
+
16
+ def perform(params = {}, headers = {})
17
+ env = {
18
+ :url => URI('http://example.com/?' + Faraday::Utils.build_query(params)),
19
+ :request_headers => Faraday::Utils::Headers.new.update(headers)
20
+ }
21
+ app = make_app
22
+ app.call(env)
23
+ end
7
24
 
8
- it 'should add the access token to the request' do
9
- env = {
10
- :request_headers => {},
11
- :url => Addressable::URI.parse('http://www.github.com')
12
- }
25
+ def make_app
26
+ described_class.new(lambda{|env| env}, *Array(options))
27
+ end
28
+
29
+ context "no token configured" do
30
+ let(:options) { nil }
31
+
32
+ it "doesn't add params" do
33
+ request = perform(:q => 'hello')
34
+ query_params(request).should eq('q' => 'hello')
35
+ end
13
36
 
14
- request = oauth2.call(env)
15
- request[:request_headers]["Authorization"].should == "Token token=\"1234\""
16
- request[:url].query_values["access_token"].should == "1234"
37
+ it "doesn't add headers" do
38
+ auth_header(perform).should be_nil
39
+ end
40
+
41
+ it "creates header for explicit token" do
42
+ request = perform(:q => 'hello', :access_token => 'abc123')
43
+ query_params(request).should eq('q' => 'hello', 'access_token' => 'abc123')
44
+ auth_header(request).should eq(%(Token token="abc123"))
17
45
  end
18
46
  end
19
47
 
20
- context 'when used with a access token in the query_values' do
21
- let(:oauth2) { Faraday::Request::OAuth2.new(DummyApp.new) }
48
+ context "default token configured" do
49
+ let(:options) { 'XYZ' }
50
+
51
+ it "adds token param" do
52
+ query_params(perform(:q => 'hello')).should eq('q' => 'hello', 'access_token' => 'XYZ')
53
+ end
54
+
55
+ it "adds token header" do
56
+ auth_header(perform).should eq(%(Token token="XYZ"))
57
+ end
22
58
 
23
- it 'should add the access token to the request' do
24
- env = {
25
- :request_headers => {},
26
- :url => Addressable::URI.parse('http://www.github.com/?access_token=1234')
27
- }
59
+ it "overrides default with explicit token" do
60
+ request = perform(:q => 'hello', :access_token => 'abc123')
61
+ query_params(request).should eq('q' => 'hello', 'access_token' => 'abc123')
62
+ auth_header(request).should eq(%(Token token="abc123"))
63
+ end
28
64
 
29
- request = oauth2.call(env)
30
- request[:request_headers]["Authorization"].should == "Token token=\"1234\""
31
- request[:url].query_values["access_token"].should == "1234"
65
+ it "clears default with empty explicit token" do
66
+ request = perform(:q => 'hello', :access_token => nil)
67
+ query_params(request).should eq('q' => 'hello', 'access_token' => nil)
68
+ auth_header(request).should be_nil
32
69
  end
33
70
  end
34
71
 
35
- context 'integration test' do
36
- let(:stubs) { Faraday::Adapter::Test::Stubs.new }
37
- let(:connection) do
38
- Faraday::Connection.new do |builder|
39
- builder.use Faraday::Request::OAuth2, '1234'
40
- builder.adapter :test, stubs
41
- end
72
+ context "existing Authorization header" do
73
+ let(:options) { 'XYZ' }
74
+ subject { perform({:q => 'hello'}, 'Authorization' => 'custom') }
75
+
76
+ it "adds token param" do
77
+ query_params(subject).should eq('q' => 'hello', 'access_token' => 'XYZ')
42
78
  end
43
79
 
44
- it 'should add the access token to the query string' do
45
- stubs.get('/me?access_token=1234') {[200, {}, 'sferik']}
46
- me = connection.get('/me')
47
- me.body.should == 'sferik'
80
+ it "doesn't override existing header" do
81
+ auth_header(subject).should eq('custom')
82
+ end
83
+ end
84
+
85
+ context "custom param name configured" do
86
+ let(:options) { ['XYZ', {:param_name => :oauth}] }
87
+
88
+ it "adds token param" do
89
+ query_params(perform).should eq('oauth' => 'XYZ')
90
+ end
91
+
92
+ it "overrides default with explicit token" do
93
+ request = perform(:oauth => 'abc123')
94
+ query_params(request).should eq('oauth' => 'abc123')
95
+ auth_header(request).should eq(%(Token token="abc123"))
96
+ end
97
+ end
98
+
99
+ context "options without token configuration" do
100
+ let(:options) { [{:param_name => :oauth}] }
101
+
102
+ it "doesn't add param" do
103
+ query_params(perform).should be_empty
104
+ end
105
+
106
+ it "overrides default with explicit token" do
107
+ query_params(perform(:oauth => 'abc123')).should eq('oauth' => 'abc123')
108
+ end
109
+ end
110
+
111
+ context "invalid param name configured" do
112
+ let(:options) { ['XYZ', {:param_name => nil}] }
113
+
114
+ it "raises error" do
115
+ expect { make_app }.to raise_error(ArgumentError, ":param_name can't be blank")
48
116
  end
49
117
  end
50
118
  end
@@ -1,46 +1,101 @@
1
1
  require 'helper'
2
+ require 'faraday_middleware/request/oauth'
3
+ require 'uri'
2
4
 
3
- describe Faraday::Request::OAuth do
4
- OAUTH_HEADER_REGEX = /^OAuth oauth_consumer_key=\"\d{4}\", oauth_nonce=\".+\", oauth_signature=\".+\", oauth_signature_method=\"HMAC-SHA1\", oauth_timestamp=\"\d{10}\", oauth_token=\"\d{4}\", oauth_version=\"1\.0\"/
5
+ describe FaradayMiddleware::OAuth do
6
+ def auth_header(env)
7
+ env[:request_headers]['Authorization']
8
+ end
9
+
10
+ def auth_values(env)
11
+ if auth = auth_header(env)
12
+ raise "invalid header: #{auth.inspect}" unless auth.sub!('OAuth ', '')
13
+ Hash[*auth.split(/, |=/)]
14
+ end
15
+ end
5
16
 
6
- let(:config) do
7
- {
8
- :consumer_key => '1234',
9
- :consumer_secret => '1234',
10
- :token => '1234',
11
- :token_secret => '1234'
17
+ def perform(oauth_options = {}, headers = {})
18
+ env = {
19
+ :url => URI('http://example.com/'),
20
+ :request_headers => Faraday::Utils::Headers.new.update(headers),
21
+ :request => {}
12
22
  }
23
+ unless oauth_options.is_a? Hash and oauth_options.empty?
24
+ env[:request][:oauth] = oauth_options
25
+ end
26
+ app = make_app
27
+ app.call(env)
13
28
  end
14
29
 
15
- context 'when used' do
16
- let(:oauth) { Faraday::Request::OAuth.new(DummyApp.new, config) }
30
+ def make_app
31
+ described_class.new(lambda{|env| env}, *Array(options))
32
+ end
17
33
 
18
- let(:env) do
19
- { :request_headers => {}, :url => Addressable::URI.parse('http://www.github.com') }
34
+ context "invalid options" do
35
+ let(:options) { nil }
36
+
37
+ it "should error out" do
38
+ expect { make_app }.to raise_error(ArgumentError)
20
39
  end
40
+ end
41
+
42
+ context "empty options" do
43
+ let(:options) { [{}] }
21
44
 
22
- it 'should add the access token to the header' do
23
- request = oauth.call(env)
24
- request[:request_headers]["Authorization"].should match OAUTH_HEADER_REGEX
45
+ it "should sign request" do
46
+ auth = auth_values(perform)
47
+ expected_keys = %w[ oauth_nonce
48
+ oauth_signature oauth_signature_method
49
+ oauth_timestamp oauth_version ]
50
+
51
+ auth.keys.should =~ expected_keys
25
52
  end
26
53
  end
27
54
 
55
+ context "configured with consumer and token" do
56
+ let(:options) do
57
+ [{ :consumer_key => 'CKEY', :consumer_secret => 'CSECRET',
58
+ :token => 'TOKEN', :token_secret => 'TSECRET'
59
+ }]
60
+ end
28
61
 
29
- context 'integration test' do
30
- let(:stubs) { Faraday::Adapter::Test::Stubs.new }
31
- let(:connection) do
32
- Faraday::Connection.new do |builder|
33
- builder.use Faraday::Request::OAuth, config
34
- builder.adapter :test, stubs
35
- end
62
+ it "adds auth info to the header" do
63
+ auth = auth_values(perform)
64
+ expected_keys = %w[ oauth_consumer_key oauth_nonce
65
+ oauth_signature oauth_signature_method
66
+ oauth_timestamp oauth_token oauth_version ]
67
+
68
+ auth.keys.should =~ expected_keys
69
+ auth['oauth_version'].should eq(%("1.0"))
70
+ auth['oauth_signature_method'].should eq(%("HMAC-SHA1"))
71
+ auth['oauth_consumer_key'].should eq(%("CKEY"))
72
+ auth['oauth_token'].should eq(%("TOKEN"))
73
+ end
74
+
75
+ it "doesn't override existing header" do
76
+ request = perform({}, "Authorization" => "iz me!")
77
+ auth_header(request).should eq("iz me!")
78
+ end
79
+
80
+ it "can override oauth options per-request" do
81
+ auth = auth_values(perform(:consumer_key => 'CKEY2'))
82
+
83
+ auth['oauth_consumer_key'].should eq(%("CKEY2"))
84
+ auth['oauth_token'].should eq(%("TOKEN"))
85
+ end
86
+
87
+ it "can turn off oauth signing per-request" do
88
+ auth_header(perform(false)).should be_nil
36
89
  end
90
+ end
91
+
92
+ context "configured without token" do
93
+ let(:options) { [{ :consumer_key => 'CKEY', :consumer_secret => 'CSECRET' }] }
37
94
 
38
- # Sadly we can not check the headers in this integration test, but this will
39
- # confirm that the middleware doesn't break the stack
40
- it 'should add the access token to the query string' do
41
- stubs.get('/me') {[200, {}, 'sferik']}
42
- me = connection.get('http://www.github.com/me')
43
- me.body.should == 'sferik'
95
+ it "adds auth info to the header" do
96
+ auth = auth_values(perform)
97
+ auth.should include('oauth_consumer_key')
98
+ auth.should_not include('oauth_token')
44
99
  end
45
100
  end
46
101
  end