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,27 @@
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
+ should 'honor default attributes' do
10
+ RestGraph.members.reject{ |name|
11
+ name.to_s =~ /method$|handler$|detector$/ }.each{ |name|
12
+ RestGraph.new.send(name).should ==
13
+ RestGraph.new.send("default_#{name}")
14
+ }
15
+ end
16
+
17
+ should 'use module to override default attributes' do
18
+ klass = RestGraph.dup
19
+ klass.send(:include, Module.new do
20
+ def default_app_id
21
+ '1829'
22
+ end
23
+ end)
24
+
25
+ klass.new.app_id.should == '1829'
26
+ end
27
+ end
@@ -0,0 +1,66 @@
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::Error do
9
+ after do
10
+ WebMock.reset!
11
+ RR.verify
12
+ end
13
+
14
+ should 'have the right ancestors' do
15
+ RestGraph::Error::AccessToken .should < RestGraph::Error
16
+
17
+ RestGraph::Error::InvalidAccessToken.should <
18
+ RestGraph::Error::AccessToken
19
+
20
+ RestGraph::Error::MissingAccessToken.should <
21
+ RestGraph::Error::AccessToken
22
+ end
23
+
24
+ def error2env hash
25
+ {RestCore::RESPONSE_BODY => hash,
26
+ RestCore::REQUEST_PATH => '/' ,
27
+ RestCore::REQUEST_QUERY => {}}
28
+ end
29
+
30
+ should 'parse right' do
31
+ %w[OAuthInvalidTokenException OAuthException].each{ |type|
32
+ RestGraph::Error.call(error2env('error' => {'type' => type})).
33
+ should.kind_of?(RestGraph::Error::InvalidAccessToken)
34
+ }
35
+
36
+ RestGraph::Error.call(error2env('error'=>{'type'=>'QueryParseException',
37
+ 'message'=>'An active access token..'})).
38
+ should.kind_of?(RestGraph::Error::MissingAccessToken)
39
+
40
+ RestGraph::Error.call(error2env('error'=>{'type'=>'QueryParseException',
41
+ 'message'=>'Oh active access token..'})).
42
+ should.not.kind_of?(RestGraph::Error::MissingAccessToken)
43
+
44
+ RestGraph::Error.call(error2env('error_code' => 190)).
45
+ should.kind_of?(RestGraph::Error::InvalidAccessToken)
46
+
47
+ RestGraph::Error.call(error2env('error_code' => 104)).
48
+ should.kind_of?(RestGraph::Error::MissingAccessToken)
49
+
50
+ RestGraph::Error.call(error2env('error_code' => 999)).
51
+ should.not.kind_of?(RestGraph::Error::AccessToken)
52
+
53
+ error = RestGraph::Error.call(error2env(['not a hash']))
54
+ error.should.not.kind_of?(RestGraph::Error::AccessToken)
55
+ error.should .kind_of?(RestGraph::Error)
56
+ end
57
+
58
+ should 'nuke cache upon errors' do
59
+ stub_request(:get, 'https://graph.facebook.com/me').
60
+ to_return(:body => '{"error":"wrong"}').times(2)
61
+
62
+ rg = RestGraph.new(:cache => {}, :error_handler => lambda{|env|env})
63
+ rg.get('me'); rg.get('me')
64
+ rg.cache.values.should == []
65
+ end
66
+ end
@@ -0,0 +1,87 @@
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 'log method' do
15
+ should 'log whenever doing network request' do
16
+ stub_request(:get, 'https://graph.facebook.com/me').
17
+ to_return(:body => '{}')
18
+
19
+ logger = []
20
+ rg = RestGraph.new(:log_method => lambda{ |s| logger << [s] })
21
+ rg.get('me')
22
+
23
+ logger.size.should == 1
24
+ end
25
+ end
26
+
27
+ describe 'with Graph API' do
28
+ before do
29
+ @id = lambda{ |env| env }
30
+ @error = '{"error":{"type":"Exception","message":"(#2500)"}}'
31
+ @error_hash = RestCore::JsonDecode.json_decode(@error)
32
+
33
+ stub_request(:get, 'https://graph.facebook.com/me').
34
+ to_return(:body => @error)
35
+ end
36
+
37
+ should 'call error_handler if error occurred' do
38
+ RestGraph.new(:error_handler => @id).get('me').should == @error_hash
39
+ end
40
+
41
+ should 'raise ::RestGraph::Error in default error_handler' do
42
+ begin
43
+ RestGraph.new.get('me')
44
+ rescue ::RestGraph::Error => e
45
+ e.error .should == @error_hash
46
+ e.message.should ==
47
+ "#{@error_hash.inspect} from https://graph.facebook.com/me"
48
+ end
49
+ end
50
+ end
51
+
52
+ describe 'with FQL API' do
53
+ # Example of an actual response (without newline)
54
+ # {"error_code":603,"error_msg":"Unknown table: bad_table",
55
+ # "request_args":[{"key":"method","value":"fql.query"},
56
+ # {"key":"format","value":"json"},
57
+ # {"key":"query","value":
58
+ # "SELECT name FROM bad_table WHERE uid=12345"}]}
59
+ before do
60
+ @id = lambda{ |env| env }
61
+ @fql_error = '{"error_code":603,"error_msg":"Unknown table: bad"}'
62
+ @fql_error_hash = RestCore::JsonDecode.json_decode(@fql_error)
63
+
64
+ @bad_fql_query = 'SELECT name FROM bad_table WHERE uid="12345"'
65
+ bad_fql_request = "https://api.facebook.com/method/fql.query?" \
66
+ "format=json&query=#{CGI.escape(@bad_fql_query)}"
67
+
68
+ stub_request(:get, bad_fql_request).to_return(:body => @fql_error)
69
+ end
70
+
71
+ should 'call error_handler if error occurred' do
72
+ RestGraph.new(:error_handler => @id).fql(@bad_fql_query).
73
+ should == @fql_error_hash
74
+ end
75
+
76
+ should 'raise ::RestGraph::Error in default error_handler' do
77
+ begin
78
+ RestGraph.new.fql(@bad_fql_query)
79
+ rescue ::RestGraph::Error => e
80
+ e.error .should == @fql_error_hash
81
+ e.message.should.start_with?(
82
+ "#{@fql_error_hash.inspect} from " \
83
+ "https://api.facebook.com/method/fql.query?")
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,75 @@
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 'return true in authorized? if there is an access_token' do
15
+ RestGraph.new(:access_token => '1').authorized?.should == true
16
+ RestGraph.new(:access_token => nil).authorized?.should == false
17
+ end
18
+
19
+ should 'treat oauth_token as access_token as well' do
20
+ rg = RestGraph.new
21
+ hate_facebook = 'why the hell two different name?'
22
+ rg.data['oauth_token'] = hate_facebook
23
+ rg.authorized?.should == true
24
+ rg.access_token == hate_facebook
25
+ end
26
+
27
+ should 'build correct headers' do
28
+ rg = RestGraph.new(:accept => 'text/html',
29
+ :lang => 'zh-tw')
30
+
31
+ headers = rg.ask.call(rg.send(:build_env))[RestCore::REQUEST_HEADERS]
32
+ headers['Accept' ].should == 'text/html'
33
+ headers['Accept-Language'].should == 'zh-tw'
34
+ end
35
+
36
+ should 'build empty query string' do
37
+ rg = RestGraph.new
38
+ (rg.ask.call(rg.send(:build_env))[RestCore::REQUEST_QUERY] || {}).
39
+ should == {}
40
+ end
41
+
42
+ should 'create access_token in query string' do
43
+ rg = RestGraph.new(:access_token => 'token')
44
+ (rg.ask.call(rg.send(:build_env))[RestCore::REQUEST_QUERY] || {}).
45
+ should == {'access_token' => 'token'}
46
+ end
47
+
48
+ should 'build correct query string' do
49
+ rg = RestGraph.new(:access_token => 'token')
50
+ TestHelper.normalize_url(rg.url('', :message => 'hi!!')).
51
+ should == "#{rg.site}?access_token=token&message=hi%21%21"
52
+
53
+ rg.access_token = nil
54
+ TestHelper.normalize_url(rg.url('', :message => 'hi!!',
55
+ :subject => '(&oh&)')).
56
+ should == "#{rg.site}?message=hi%21%21&subject=%28%26oh%26%29"
57
+ end
58
+
59
+ should 'auto decode json' do
60
+ rg = RestGraph.new(:auto_decode => true)
61
+ stub_request(:get, rg.site).to_return(:body => '[]')
62
+ rg.get('').should == []
63
+ end
64
+
65
+ should 'not auto decode json' do
66
+ rg = RestGraph.new(:auto_decode => false)
67
+ stub_request(:get, rg.site).to_return(:body => '[]')
68
+ rg.get('').should == '[]'
69
+ end
70
+
71
+ should 'give attributes' do
72
+ RestGraph.new(:auto_decode => false).attributes.keys.map(&:to_s).sort.
73
+ should == RestGraph.members.map(&:to_s).sort
74
+ end
75
+ end
@@ -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
+ describe RestGraph do
9
+ before do
10
+ @rg = RestGraph.new(:app_id => '29', :secret => '18')
11
+ @uri = 'http://zzz.tw'
12
+ end
13
+
14
+ after do
15
+ WebMock.reset!
16
+ end
17
+
18
+ should 'return correct oauth url' do
19
+ TestHelper.normalize_url(@rg.authorize_url(:redirect_uri => @uri)).
20
+ should == 'https://graph.facebook.com/oauth/authorize?' \
21
+ 'client_id=29&redirect_uri=http%3A%2F%2Fzzz.tw'
22
+ end
23
+
24
+ should 'do authorizing and parse result and save it in data' do
25
+ stub_request(:get, 'https://graph.facebook.com/oauth/access_token?' \
26
+ 'client_id=29&client_secret=18&code=zzz&' \
27
+ 'redirect_uri=http%3A%2F%2Fzzz.tw').
28
+ to_return(:body => 'access_token=baken&expires=2918')
29
+
30
+ result = {'access_token' => 'baken', 'expires' => '2918'}
31
+
32
+ @rg.authorize!(:redirect_uri => @uri, :code => 'zzz').
33
+ should == result
34
+ @rg.data.should == result
35
+ end
36
+
37
+ should 'not append access_token in authorize_url even presented' do
38
+ RestGraph.new(:access_token => 'do not use me').authorize_url.
39
+ should == 'https://graph.facebook.com/oauth/authorize'
40
+ end
41
+
42
+ end
@@ -0,0 +1,46 @@
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 RestCore::Oauth1Header do
9
+ before do
10
+ @env = {RestCore::REQUEST_METHOD => :post,
11
+ RestCore::REQUEST_PATH =>
12
+ 'https://api.twitter.com/oauth/request_token'}
13
+
14
+ callback =
15
+ 'http://localhost:3005/the_dance/process_callback?service_provider_id=11'
16
+
17
+ @oauth_params =
18
+ {'oauth_callback' => callback ,
19
+ 'oauth_consumer_key' => 'GDdmIQH6jhtmLUypg82g' ,
20
+ 'oauth_nonce' => 'QP70eNmVz8jvdPevU3oJD2AfF7R7odC2XJcn4XlZJqk',
21
+ 'oauth_timestamp' => '1272323042' ,
22
+ 'oauth_version' => '1.0' ,
23
+ 'oauth_signature_method' => 'HMAC-SHA1'}
24
+
25
+ @auth = RestCore::Oauth1Header.new(RestCore::Ask.new,
26
+ nil, nil, nil,
27
+ 'GDdmIQH6jhtmLUypg82g',
28
+ 'MCD8BKwGdgPHvAuvgvz4EQpqDAtx89grbuNMRd7Eh98')
29
+ end
30
+
31
+ should 'have correct base_string' do
32
+ @auth.base_string(@env, @oauth_params).should.equal(
33
+ 'POST&https%3A%2F%2Fapi.twitter.com%2Foauth%2Frequest_token&' \
34
+ 'oauth_callback%3Dhttp%253A%252F%252Flocalhost%253A3005%252F' \
35
+ 'the_dance%252Fprocess_callback%253Fservice_provider_id%253D' \
36
+ '11%26oauth_consumer_key%3DGDdmIQH6jhtmLUypg82g%26oauth_nonc' \
37
+ 'e%3DQP70eNmVz8jvdPevU3oJD2AfF7R7odC2XJcn4XlZJqk%26oauth_sig' \
38
+ 'nature_method%3DHMAC-SHA1%26oauth_timestamp%3D1272323042%26' \
39
+ 'oauth_version%3D1.0')
40
+ end
41
+
42
+ should 'have correct signature' do
43
+ @auth.signature(@env, @oauth_params).should.equal(
44
+ '8wUi7m5HFQy76nowoCThusfgB+Q=')
45
+ end
46
+ end
@@ -0,0 +1,116 @@
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 'do fql query with/without access_token' do
15
+ fql = 'SELECT name FROM likes where id="123"'
16
+ query = "format=json&query=#{CGI.escape(fql)}"
17
+ stub_request(:get, "https://api.facebook.com/method/fql.query?#{query}").
18
+ to_return(:body => '[]')
19
+
20
+ RestGraph.new.fql(fql).should == []
21
+
22
+ token = 'token'.reverse
23
+ stub_request(:get, "https://api.facebook.com/method/fql.query?#{query}" \
24
+ "&access_token=#{token}").
25
+ to_return(:body => '[]')
26
+
27
+ RestGraph.new(:access_token => token).fql(fql).should == []
28
+ end
29
+
30
+ should 'do fql.mutilquery correctly' do
31
+ f0 = 'SELECT display_name FROM application WHERE app_id="233082465238"'
32
+ f1 = 'SELECT display_name FROM application WHERE app_id="110225210740"'
33
+ f0q, f1q = "\"#{f0.gsub('"', '\\"')}\"", "\"#{f1.gsub('"', '\\"')}\""
34
+ q = "format=json&queries=#{CGI.escape("{\"f0\":#{f0q},\"f1\":#{f1q}}")}"
35
+ p = "format=json&queries=#{CGI.escape("{\"f1\":#{f1q},\"f0\":#{f0q}}")}"
36
+
37
+ stub_multi = lambda{
38
+ stub_request(:get,
39
+ "https://api.facebook.com/method/fql.multiquery?#{q}").
40
+ to_return(:body => '[]')
41
+
42
+ stub_request(:get,
43
+ "https://api.facebook.com/method/fql.multiquery?#{p}").
44
+ to_return(:body => '[]')
45
+ }
46
+
47
+ stub_multi.call
48
+ RestGraph.new.fql_multi(:f0 => f0, :f1 => f1).should == []
49
+ end
50
+
51
+ should 'cache fake post in fql' do
52
+ query = 'select name from user where uid = 4'
53
+ body = '[{"name":"Mark Zuckerberg"}]'
54
+ stub_request(:post,
55
+ 'https://api.facebook.com/method/fql.query?format=json').
56
+ with(:body => {:query => query}).
57
+ to_return(:body => body)
58
+
59
+ RestGraph.new(:cache => (cache = {})).fql(query, {}, :post => true).
60
+ first['name'] .should == 'Mark Zuckerberg'
61
+ cache.size .should == 1
62
+ cache.values.first.should == body
63
+
64
+ WebMock.reset! # should hit the cache
65
+
66
+ RestGraph.new(:cache => cache).fql(query, {}, :post => true).
67
+ first['name'] .should == 'Mark Zuckerberg'
68
+ cache.size .should == 1
69
+ cache.values.first.should == body
70
+
71
+ # query changed
72
+ should.raise(WebMock::NetConnectNotAllowedError) do
73
+ RestGraph.new(:cache => cache).fql(query.upcase, {}, :post => true)
74
+ end
75
+
76
+ # cache should work for normal get
77
+ RestGraph.new(:cache => cache).fql(query).
78
+ first['name'] .should == 'Mark Zuckerberg'
79
+ cache.size .should == 1
80
+ cache.values.first.should == body
81
+ end
82
+
83
+ should 'do facebook old rest api' do
84
+ body = 'hate facebook inconsistent'
85
+ stub_request(:get,
86
+ 'https://api.facebook.com/method/notes.create?format=json').
87
+ to_return(:body => body)
88
+
89
+ RestGraph.new.old_rest('notes.create', {}, :auto_decode => false).
90
+ should == body
91
+ end
92
+
93
+ should 'exchange sessions for access token' do
94
+ stub_request(:post,
95
+ 'https://graph.facebook.com/oauth/exchange_sessions?' \
96
+ 'type=client_cred&client_id=id&client_secret=di&' \
97
+ 'sessions=bad%20bed').
98
+ to_return(:body => '[{"access_token":"bogus"}]')
99
+
100
+ RestGraph.new(:app_id => 'id',
101
+ :secret => 'di').
102
+ exchange_sessions(:sessions => 'bad bed').
103
+ first['access_token'].should == 'bogus'
104
+ end
105
+
106
+ should 'use an secret access_token' do
107
+ stub_request(:get,
108
+ 'https://api.facebook.com/method/admin.getAppProperties?' \
109
+ 'access_token=123%7Cs&format=json&properties=app_id'
110
+ ).to_return(:body => '{"app_id":"123"}')
111
+
112
+ RestGraph.new(:app_id => '123', :secret => 's').
113
+ secret_old_rest('admin.getAppProperties', :properties => 'app_id').
114
+ should == {'app_id' => '123'}
115
+ end
116
+ end