rest-core 0.0.1

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 (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