agiley-faraday_middleware 0.8.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/.gemtest +0 -0
  2. data/.gitignore +31 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +7 -0
  5. data/CHANGELOG.md +10 -0
  6. data/Gemfile +7 -0
  7. data/LICENSE.md +20 -0
  8. data/README.md +54 -0
  9. data/Rakefile +17 -0
  10. data/faraday_middleware.gemspec +24 -0
  11. data/lib/faraday_middleware.rb +42 -0
  12. data/lib/faraday_middleware/addressable_patch.rb +20 -0
  13. data/lib/faraday_middleware/backwards_compatibility.rb +15 -0
  14. data/lib/faraday_middleware/instrumentation.rb +30 -0
  15. data/lib/faraday_middleware/rack_compatible.rb +76 -0
  16. data/lib/faraday_middleware/request/encode_json.rb +50 -0
  17. data/lib/faraday_middleware/request/oauth.rb +64 -0
  18. data/lib/faraday_middleware/request/oauth2.rb +62 -0
  19. data/lib/faraday_middleware/response/caching.rb +76 -0
  20. data/lib/faraday_middleware/response/follow_redirects.rb +53 -0
  21. data/lib/faraday_middleware/response/mashify.rb +28 -0
  22. data/lib/faraday_middleware/response/parse_json.rb +38 -0
  23. data/lib/faraday_middleware/response/parse_marshal.rb +13 -0
  24. data/lib/faraday_middleware/response/parse_nokogiri_xml.rb +14 -0
  25. data/lib/faraday_middleware/response/parse_xml.rb +14 -0
  26. data/lib/faraday_middleware/response/parse_yaml.rb +13 -0
  27. data/lib/faraday_middleware/response/rashify.rb +13 -0
  28. data/lib/faraday_middleware/response_middleware.rb +78 -0
  29. data/lib/faraday_middleware/version.rb +3 -0
  30. data/spec/caching_test.rb +122 -0
  31. data/spec/encode_json_spec.rb +95 -0
  32. data/spec/follow_redirects_spec.rb +33 -0
  33. data/spec/helper.rb +33 -0
  34. data/spec/mashify_spec.rb +79 -0
  35. data/spec/oauth2_spec.rb +118 -0
  36. data/spec/oauth_spec.rb +101 -0
  37. data/spec/parse_json_spec.rb +94 -0
  38. data/spec/parse_marshal_spec.rb +16 -0
  39. data/spec/parse_xml_spec.rb +71 -0
  40. data/spec/parse_yaml_spec.rb +53 -0
  41. data/spec/rashify_spec.rb +69 -0
  42. metadata +202 -0
@@ -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
data/spec/helper.rb ADDED
@@ -0,0 +1,33 @@
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
8
+
9
+ require 'rspec'
10
+
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
+ }
19
+ end
20
+
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)
28
+ end
29
+ end
30
+
31
+ RSpec.configure do |config|
32
+ config.include ResponseMiddlewareExampleGroup, :type => :response
33
+ end
@@ -0,0 +1,79 @@
1
+ require 'helper'
2
+ require 'faraday_middleware/response/mashify'
3
+
4
+ describe FaradayMiddleware::Mashify do
5
+ context 'during configuration' do
6
+ it 'should allow for a custom Mash class to be set' do
7
+ described_class.should respond_to(:mash_class)
8
+ described_class.should respond_to(:mash_class=)
9
+ end
10
+ end
11
+
12
+ context 'when used' do
13
+ before(:each) { described_class.mash_class = ::Hashie::Mash }
14
+ let(:mashify) { described_class.new }
15
+
16
+ it 'should create a Hashie::Mash from the body' do
17
+ env = { :body => { "name" => "Erik Michaels-Ober", "username" => "sferik" } }
18
+ me = mashify.on_complete(env)
19
+ me.class.should == Hashie::Mash
20
+ end
21
+
22
+ it 'should handle strings' do
23
+ env = { :body => "Most amazing string EVER" }
24
+ me = mashify.on_complete(env)
25
+ me.should == "Most amazing string EVER"
26
+ end
27
+
28
+ it 'should handle arrays' do
29
+ env = { :body => [123, 456] }
30
+ values = mashify.on_complete(env)
31
+ values.first.should == 123
32
+ values.last.should == 456
33
+ end
34
+
35
+ it 'should handle arrays of hashes' do
36
+ env = { :body => [{ "username" => "sferik" }, { "username" => "pengwynn" }] }
37
+ us = mashify.on_complete(env)
38
+ us.first.username.should == 'sferik'
39
+ us.last.username.should == 'pengwynn'
40
+ end
41
+
42
+ it 'should handle mixed arrays' do
43
+ env = { :body => [123, { "username" => "sferik" }, 456] }
44
+ values = mashify.on_complete(env)
45
+ values.first.should == 123
46
+ values.last.should == 456
47
+ values[1].username.should == 'sferik'
48
+ end
49
+
50
+ it 'should allow for use of custom Mash subclasses' do
51
+ class MyMash < ::Hashie::Mash; end
52
+ described_class.mash_class = MyMash
53
+
54
+ env = { :body => { "name" => "Erik Michaels-Ober", "username" => "sferik" } }
55
+ me = mashify.on_complete(env)
56
+
57
+ me.class.should == MyMash
58
+ end
59
+ end
60
+
61
+ context 'integration test' do
62
+ let(:stubs) { Faraday::Adapter::Test::Stubs.new }
63
+ let(:connection) do
64
+ Faraday::Connection.new do |builder|
65
+ builder.adapter :test, stubs
66
+ builder.use described_class
67
+ end
68
+ end
69
+
70
+ # although it is not good practice to pass a hash as the body, if we add ParseJson
71
+ # to the middleware stack we end up testing two middlewares instead of one
72
+ it 'should create a Hash from the body' do
73
+ stubs.get('/hash') {[200, {'content-type' => 'application/json; charset=utf-8'}, { "name" => "Erik Michaels-Ober", "username" => "sferik" }]}
74
+ me = connection.get('/hash').body
75
+ me.name.should == 'Erik Michaels-Ober'
76
+ me.username.should == 'sferik'
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,118 @@
1
+ require 'helper'
2
+ require 'uri'
3
+ require 'faraday_middleware/request/oauth2'
4
+ require 'faraday/utils'
5
+
6
+ describe FaradayMiddleware::OAuth2 do
7
+
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
24
+
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
36
+
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"))
45
+ end
46
+ end
47
+
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
58
+
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
64
+
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
69
+ end
70
+ end
71
+
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')
78
+ end
79
+
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")
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,101 @@
1
+ require 'helper'
2
+ require 'faraday_middleware/request/oauth'
3
+ require 'uri'
4
+
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
16
+
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 => {}
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)
28
+ end
29
+
30
+ def make_app
31
+ described_class.new(lambda{|env| env}, *Array(options))
32
+ end
33
+
34
+ context "invalid options" do
35
+ let(:options) { nil }
36
+
37
+ it "should error out" do
38
+ expect { make_app }.to raise_error(ArgumentError)
39
+ end
40
+ end
41
+
42
+ context "empty options" do
43
+ let(:options) { [{}] }
44
+
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
52
+ end
53
+ end
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
61
+
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
89
+ end
90
+ end
91
+
92
+ context "configured without token" do
93
+ let(:options) { [{ :consumer_key => 'CKEY', :consumer_secret => 'CSECRET' }] }
94
+
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')
99
+ end
100
+ end
101
+ end