rest-core 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/.gitignore +6 -0
  2. data/.gitmodules +3 -0
  3. data/.travis.yml +9 -0
  4. data/CONTRIBUTORS +11 -0
  5. data/Gemfile +8 -0
  6. data/LICENSE +201 -0
  7. data/NOTE.md +48 -0
  8. data/README +83 -0
  9. data/README.md +83 -0
  10. data/Rakefile +26 -0
  11. data/TODO.md +17 -0
  12. data/example/facebook.rb +145 -0
  13. data/example/github.rb +21 -0
  14. data/lib/rest-core.rb +48 -0
  15. data/lib/rest-core/app/ask.rb +11 -0
  16. data/lib/rest-core/app/rest-client.rb +24 -0
  17. data/lib/rest-core/builder.rb +24 -0
  18. data/lib/rest-core/client.rb +278 -0
  19. data/lib/rest-core/client/github.rb +19 -0
  20. data/lib/rest-core/client/linkedin.rb +57 -0
  21. data/lib/rest-core/client/rest-graph.rb +262 -0
  22. data/lib/rest-core/client/twitter.rb +59 -0
  23. data/lib/rest-core/client_oauth1.rb +25 -0
  24. data/lib/rest-core/event.rb +17 -0
  25. data/lib/rest-core/middleware.rb +53 -0
  26. data/lib/rest-core/middleware/cache.rb +80 -0
  27. data/lib/rest-core/middleware/common_logger.rb +27 -0
  28. data/lib/rest-core/middleware/default_headers.rb +11 -0
  29. data/lib/rest-core/middleware/default_query.rb +11 -0
  30. data/lib/rest-core/middleware/default_site.rb +15 -0
  31. data/lib/rest-core/middleware/defaults.rb +44 -0
  32. data/lib/rest-core/middleware/error_detector.rb +16 -0
  33. data/lib/rest-core/middleware/error_detector_http.rb +11 -0
  34. data/lib/rest-core/middleware/error_handler.rb +19 -0
  35. data/lib/rest-core/middleware/json_decode.rb +83 -0
  36. data/lib/rest-core/middleware/oauth1_header.rb +81 -0
  37. data/lib/rest-core/middleware/oauth2_query.rb +19 -0
  38. data/lib/rest-core/middleware/timeout.rb +13 -0
  39. data/lib/rest-core/util/hmac.rb +22 -0
  40. data/lib/rest-core/version.rb +4 -0
  41. data/lib/rest-core/wrapper.rb +55 -0
  42. data/lib/rest-graph/config_util.rb +43 -0
  43. data/rest-core.gemspec +162 -0
  44. data/task/.gitignore +1 -0
  45. data/task/gemgem.rb +184 -0
  46. data/test/common.rb +29 -0
  47. data/test/config/rest-graph.yaml +7 -0
  48. data/test/pending/test_load_config.rb +42 -0
  49. data/test/pending/test_multi.rb +123 -0
  50. data/test/pending/test_test_util.rb +86 -0
  51. data/test/test_api.rb +98 -0
  52. data/test/test_cache.rb +62 -0
  53. data/test/test_default.rb +27 -0
  54. data/test/test_error.rb +66 -0
  55. data/test/test_handler.rb +87 -0
  56. data/test/test_misc.rb +75 -0
  57. data/test/test_oauth.rb +42 -0
  58. data/test/test_oauth1_header.rb +46 -0
  59. data/test/test_old.rb +116 -0
  60. data/test/test_page.rb +110 -0
  61. data/test/test_parse.rb +131 -0
  62. data/test/test_rest-graph.rb +10 -0
  63. data/test/test_serialize.rb +44 -0
  64. data/test/test_timeout.rb +25 -0
  65. metadata +267 -0
@@ -0,0 +1,29 @@
1
+
2
+ require 'rubygems' if RUBY_VERSION < '1.9.2'
3
+ require 'rack' if RUBY_VERSION < '1.9.2' # autoload broken in 1.8?
4
+ require 'rest-core/client/rest-graph'
5
+
6
+ # need to require this before webmock in order to enable mocking in em-http
7
+
8
+ require 'rr'
9
+ require 'webmock'
10
+ require 'bacon'
11
+
12
+ # for testing lighten (serialization)
13
+ require 'yaml'
14
+
15
+ include RR::Adapters::RRMethods
16
+ include WebMock::API
17
+ WebMock.disable_net_connect!
18
+ Bacon.summary_on_exit
19
+
20
+ module TestHelper
21
+ module_function
22
+ def normalize_query query
23
+ '?' + query[1..-1].split('&').sort.join('&')
24
+ end
25
+
26
+ def normalize_url url
27
+ url.sub(/\?.+/){ |query| TestHelper.normalize_query(query) }
28
+ end
29
+ end
@@ -0,0 +1,7 @@
1
+
2
+ test:
3
+ app_id: 41829
4
+ secret: <%= 'r41829'.reverse %>
5
+ auto_decode: false
6
+ lang: zh-tw
7
+ auto_authorize_scope: 'publish_stream'
@@ -0,0 +1,42 @@
1
+
2
+ if respond_to?(:require_relative, true)
3
+ require_relative 'common'
4
+ else
5
+ require File.dirname(__FILE__) + '/common'
6
+ end
7
+
8
+ require 'rest-graph/config_util'
9
+
10
+ describe RestGraph::ConfigUtil do
11
+
12
+ after do
13
+ RR.verify
14
+ end
15
+
16
+ should 'honor rails config' do
17
+ ::Rails = Object.new
18
+ mock(Rails).env { 'test' }.times(2)
19
+ mock(Rails).root{ File.dirname(__FILE__) }.times(2)
20
+
21
+ check = lambda{
22
+ RestGraph.default_app_id.should == 41829
23
+ RestGraph.default_secret.should == 'r41829'.reverse
24
+ RestGraph.default_auto_decode.should == false
25
+ RestGraph.default_lang.should == 'zh-tw'
26
+ }
27
+
28
+ [RestGraph::ConfigUtil, RestGraph].each{ |const|
29
+ TestHelper.ensure_rollback{
30
+ const.load_config_for_rails
31
+ check.call
32
+ }
33
+
34
+ TestHelper.ensure_rollback{
35
+ const.load_config(
36
+ "#{File.dirname(__FILE__)}/config/rest-graph.yaml",
37
+ 'test')
38
+ check.call
39
+ }
40
+ }
41
+ end
42
+ end
@@ -0,0 +1,123 @@
1
+
2
+ if respond_to?(:require_relative, true)
3
+ require_relative 'common'
4
+ else
5
+ require File.dirname(__FILE__) + '/common'
6
+ end
7
+
8
+ describe 'RestGraph#multi' do
9
+ after do
10
+ WebMock.reset!
11
+ RR.verify
12
+ end
13
+
14
+ should 'do multi query with em-http-request' do
15
+ url = 'https://graph.facebook.com/me'
16
+ stub_request(:get, url).to_return(:body => '{"data":"get"}')
17
+ stub_request(:put, url).to_return(:body => '{"data":"put"}')
18
+ rg = RestGraph.new
19
+ mock.proxy(rg).request_em(anything, anything)
20
+ EM.run{
21
+ rg.multi([[:get, 'me'], [:put, 'me']]){ |results|
22
+ results.should == [{'data' => 'get'}, {'data' => 'put'}]
23
+ EM.stop
24
+ }
25
+ }
26
+ end
27
+
28
+ should 'call aget, aput family with multi' do
29
+ url = 'https://graph.facebook.com/me'
30
+ %w[aget adelete apost aput].each{ |meth|
31
+ stub_request("#{meth[1..-1]}".to_sym, url).
32
+ to_return(:body => "{\"data\":\"#{meth}\"}")
33
+ rg = RestGraph.new
34
+ mock.proxy(rg).request_em(anything, anything)
35
+ EM.run{
36
+ rg.send(meth, 'me', {}){ |result|
37
+ result.should == {'data' => meth.to_s}
38
+ EM.stop
39
+ }
40
+ }
41
+ }
42
+ end
43
+
44
+ should 'for_pages' do
45
+ rg = RestGraph.new
46
+
47
+ args = [is_a(Hash), is_a(Array)]
48
+ mock.proxy(rg).request_em(*args) # at least one time
49
+ stub.proxy(rg).request_em(*args)
50
+
51
+ %w[next previous].each{ |type|
52
+ kind = "#{type}_page"
53
+ data = {'paging' => {type => 'http://z'}, 'data' => ['z']}
54
+
55
+ # invalid pages or just the page itself
56
+ # not really need network
57
+ nils = 0
58
+ ranges = -1..1
59
+ ranges.each{ |page|
60
+ rg.for_pages(data, page, {:async => true}, kind){ |r|
61
+ if r
62
+ r.should == data
63
+ else
64
+ nils += 1
65
+ end
66
+ }.should == data
67
+ }
68
+ nils.should == ranges.to_a.size
69
+
70
+ (2..4).each{ |pages|
71
+ # merge data
72
+ stub_request(:get, 'z').to_return(:body => '{"data":["y"]}')
73
+ expects = [{'data' => %w[y]}, nil]
74
+
75
+ EM.run{
76
+ rg.for_pages(data, pages, {:async => true}, kind){ |r|
77
+ r.should == expects.shift
78
+ EM.stop if expects.empty?
79
+ }
80
+ }
81
+
82
+ # this data cannot be merged
83
+ stub_request(:get, 'z').to_return(:body => '{"data":"y"}')
84
+ expects = [{'data' => 'y'}, nil]
85
+
86
+ EM.run{
87
+ rg.for_pages(data, pages, {:async => true}, kind){ |r|
88
+ r.should == expects.shift
89
+ EM.stop if expects.empty?
90
+ }
91
+ }
92
+ }
93
+
94
+ stub_request(:get, 'z').to_return(:body =>
95
+ '{"paging":{"'+type+'":"http://yyy"},"data":["y"]}')
96
+ stub_request(:get, 'yyy').to_return(:body => '{"data":["x"]}')
97
+ expects = [{'data' => %w[y]}, {'data' => %w[x]}, nil]
98
+
99
+ EM.run{
100
+ rg.for_pages(data, 3, {:async => true}, kind){ |rr|
101
+ rr.frozen?.should == true unless rr.nil? && RUBY_VERSION < '1.9.2'
102
+ if rr
103
+ r = rr.dup
104
+ r.delete('paging')
105
+ else
106
+ r = rr
107
+ end
108
+ r.should == expects.shift
109
+ EM.stop if expects.empty?
110
+ }
111
+ }
112
+ }
113
+ end
114
+
115
+ # should 'cache in multi' do
116
+ # end
117
+ #
118
+ # should 'logging' do
119
+ # end
120
+ #
121
+ # should 'error handler?' do
122
+ # end
123
+ end
@@ -0,0 +1,86 @@
1
+
2
+ if respond_to?(:require_relative, true)
3
+ require_relative 'common'
4
+ else
5
+ require File.dirname(__FILE__) + '/common'
6
+ end
7
+
8
+ require 'rest-graph/test_util'
9
+
10
+ describe RestGraph::TestUtil do
11
+ before do
12
+ RestGraph::TestUtil.setup
13
+ end
14
+
15
+ after do
16
+ RestGraph::TestUtil.teardown
17
+ end
18
+
19
+ should 'stub requests and store result and teardown do cleanup' do
20
+ RestGraph.new.get('me') .should == {'data' => []}
21
+ RestGraph::TestUtil.history .should ==
22
+ [[:get, "https://graph.facebook.com/me", nil]]
23
+
24
+ RestGraph::TestUtil.teardown
25
+
26
+ RestGraph::TestUtil.history.should == []
27
+ begin
28
+ RestGraph.new.get('me')
29
+ rescue => e
30
+ e.should.kind_of?(WebMock::NetConnectNotAllowedError)
31
+ end
32
+ end
33
+
34
+ should 'have default response' do
35
+ default = {'meta' => []}
36
+ RestGraph::TestUtil.default_response = default
37
+ RestGraph.new.get('me') .should == default
38
+ end
39
+
40
+ should 'have default data' do
41
+ rg = RestGraph.new
42
+ rg.data['uid'] .should == '1234'
43
+ RestGraph::TestUtil.default_data = {'uid' => '4321'}
44
+ rg.data['uid'] .should == '4321'
45
+ RestGraph.new.data['uid'].should == '4321'
46
+ end
47
+
48
+ should 'be easy to stub data' do
49
+ response = {'data' => 'me'}
50
+ RestGraph::TestUtil.get('me'){ response }
51
+ RestGraph.new.get('me').should == response
52
+ RestGraph.new.get('he').should == RestGraph::TestUtil.default_response
53
+ end
54
+
55
+ should 'emulate login' do
56
+ RestGraph::TestUtil.login(1829)
57
+ rg = RestGraph.new
58
+ rg.data['uid'].should == '1829'
59
+ rg.authorized?.should == true
60
+ rg.get('me').should == RestGraph::TestUtil.user('1829')
61
+ end
62
+
63
+ should 'reset before login' do
64
+ RestGraph::TestUtil.login(1234).login(1829)
65
+ rg = RestGraph.new
66
+ rg.data['uid'].should == '1829'
67
+ rg.authorized?.should == true
68
+ rg.get('me').should == RestGraph::TestUtil.user('1829')
69
+ RestGraph::TestUtil.login(1234)
70
+ rg.data['uid'].should == '1234'
71
+ rg.authorized?.should == true
72
+ rg.get('me').should == RestGraph::TestUtil.user('1234')
73
+ end
74
+
75
+ should 'return correct response in fake get' do
76
+ RestGraph.new.fql('', {}, :post => true).
77
+ should == [RestGraph::TestUtil.default_response]
78
+
79
+ RestGraph.new.fql_multi({:a => '', :b => ''}, {}, :post => true).
80
+ sort_by{ |h| h['name'] }.
81
+ should == [{'name' => 'a',
82
+ 'fql_result_set' => [RestGraph::TestUtil.default_response]},
83
+ {'name' => 'b',
84
+ 'fql_result_set' => [RestGraph::TestUtil.default_response]}]
85
+ end
86
+ end
@@ -0,0 +1,98 @@
1
+
2
+ if respond_to?(:require_relative, true)
3
+ require_relative 'common'
4
+ else
5
+ require File.dirname(__FILE__) + '/common'
6
+ end
7
+
8
+ describe RestGraph do
9
+ after do
10
+ WebMock.reset!
11
+ RR.verify
12
+ end
13
+
14
+ should 'generate correct url' do
15
+ TestHelper.normalize_url(
16
+ RestGraph.new(:access_token => 'awesome').url('path', :query => 'str')).
17
+ should ==
18
+ 'https://graph.facebook.com/path?access_token=awesome&query=str'
19
+ end
20
+
21
+ should 'request to correct server' do
22
+ stub_request(:get, 'http://nothing.godfat.org/me').with(
23
+ :headers => {'Accept' => 'text/plain',
24
+ 'Accept-Language' => 'zh-tw',
25
+ 'Accept-Encoding' => 'gzip, deflate', # this is by ruby
26
+ }.merge(RUBY_VERSION < '1.9.2' ?
27
+ {} :
28
+ {'User-Agent' => 'Ruby'})). # this is by ruby
29
+ to_return(:body => '{"data": []}')
30
+
31
+ RestGraph.new(:site => 'http://nothing.godfat.org/',
32
+ :lang => 'zh-tw',
33
+ :accept => 'text/plain').get('me').should == {'data' => []}
34
+ end
35
+
36
+ should 'pass custom headers' do
37
+ stub_request(:get, 'http://example.com/').with(
38
+ :headers => {'Accept' => 'application/json',
39
+ 'Accept-Language' => 'en-us',
40
+ 'Accept-Encoding' => 'gzip, deflate', # this is by ruby
41
+ 'X-Forwarded-For' => '127.0.0.1',
42
+ }.merge(RUBY_VERSION < '1.9.2' ?
43
+ {} :
44
+ {'User-Agent' => 'Ruby'})). # this is by ruby
45
+ to_return(:body => '{"data": []}')
46
+
47
+ RestGraph.new.request({:headers => {'X-Forwarded-For' => '127.0.0.1'}},
48
+ [:get, 'http://example.com']).
49
+ should == {'data' => []}
50
+ end
51
+
52
+ should 'post right' do
53
+ stub_request(:post, 'https://graph.facebook.com/feed/me').
54
+ with(:body => 'message=hi%20there').to_return(:body => 'ok')
55
+
56
+ RestGraph.new(:auto_decode => false).
57
+ post('feed/me', :message => 'hi there').should == 'ok'
58
+ end
59
+
60
+ should 'use secret_access_token' do
61
+ stub_request(:get,
62
+ 'https://graph.facebook.com/me?access_token=1|2').
63
+ to_return(:body => 'ok')
64
+
65
+ rg = RestGraph.new(:auto_decode => false, :access_token => 'wrong',
66
+ :app_id => '1', :secret => '2')
67
+ rg.get('me', {}, :secret => true).should == 'ok'
68
+ rg.url('me', {}, :secret => true).should ==
69
+ 'https://graph.facebook.com/me?access_token=1%7C2'
70
+ rg.url('me', {}, :secret => true, :site => '/').should ==
71
+ '/me?access_token=1%7C2'
72
+ end
73
+
74
+ should 'suppress auto-decode in an api call' do
75
+ stub_request(:get, 'https://graph.facebook.com/woot').
76
+ to_return(:body => 'bad json')
77
+
78
+ rg = RestGraph.new(:auto_decode => true)
79
+ rg.get('woot', {}, :auto_decode => false).should == 'bad json'
80
+ rg.auto_decode.should == true
81
+ end
82
+
83
+ should 'not raise exception when encountering error' do
84
+ [500, 401, 402, 403].each{ |status|
85
+ stub_request(:delete, 'https://graph.facebook.com/123').to_return(
86
+ :body => '[]', :status => status)
87
+
88
+ RestGraph.new.delete('123').should == []
89
+ }
90
+ end
91
+
92
+ should 'convert query to string' do
93
+ stub(o = Object.new).to_s{ 'i am mock' }
94
+ stub_request(:get, "https://graph.facebook.com/search?q=i%20am%20mock").
95
+ to_return(:body => 'ok')
96
+ RestGraph.new(:auto_decode => false).get('search', :q => o).should == 'ok'
97
+ end
98
+ end
@@ -0,0 +1,62 @@
1
+
2
+ if respond_to?(:require_relative, true)
3
+ require_relative 'common'
4
+ else
5
+ require File.dirname(__FILE__) + '/common'
6
+ end
7
+
8
+ describe RestGraph do
9
+ after do
10
+ WebMock.reset!
11
+ RR.verify
12
+ end
13
+
14
+ describe 'cache' do
15
+ before do
16
+ @url, @body = "https://graph.facebook.com/cache", '{"message":"ok"}'
17
+ @cache_key = Digest::MD5.hexdigest(@url)
18
+ @cache = {}
19
+ @rg = RestGraph.new(:cache => @cache, :auto_decode => false)
20
+ stub_request(:get, @url).to_return(:body => @body).times(1)
21
+ end
22
+
23
+ should 'enable cache if passing cache' do
24
+ 3.times{ @rg.get('cache').should == @body }
25
+ @cache.should == {@cache_key => @body}
26
+ end
27
+
28
+ should 'respect expires_in' do
29
+ mock(@cache).method(:store){ mock!.arity{ -3 } }
30
+ mock(@cache).store(@cache_key, @body, :expires_in => 3)
31
+ @rg.get('cache', {}, :expires_in => 3).should == @body
32
+ end
33
+
34
+ should 'update cache if there is cache option set to false' do
35
+ @rg.get('cache') .should == @body
36
+ stub_request(:get, @url).to_return(:body => @body.reverse).times(2)
37
+ @rg.get('cache') .should == @body
38
+ @rg.get('cache', {}, :cache => false).should == @body.reverse
39
+ @rg.get('cache') .should == @body.reverse
40
+ @rg.cache = nil
41
+ @rg.get('cache', {}, :cache => false).should == @body.reverse
42
+ end
43
+ end
44
+
45
+ should 'not cache post/put/delete' do
46
+ [:put, :post, :delete].each{ |meth|
47
+ url, body = "https://graph.facebook.com/cache", '{"message":"ok"}'
48
+ stub_request(meth, url).to_return(:body => body).times(3)
49
+
50
+ cache = {}
51
+ rg = RestGraph.new(:cache => cache)
52
+ 3.times{
53
+ if meth == :delete
54
+ rg.send(meth, 'cache').should == {'message' => 'ok'}
55
+ else
56
+ rg.send(meth, 'cache', 'payload').should == {'message' => 'ok'}
57
+ end
58
+ }
59
+ cache.should == {}
60
+ }
61
+ end
62
+ end