agiley-faraday_middleware 0.8.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.
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