faraday_middleware 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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