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