koala 1.3.0rc1 → 1.3.0rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/.gitignore +3 -1
  2. data/.travis.yml +4 -0
  3. data/.yardopts +3 -0
  4. data/CHANGELOG +7 -0
  5. data/Gemfile +14 -0
  6. data/Guardfile +6 -0
  7. data/lib/koala.rb +16 -97
  8. data/lib/koala/api.rb +93 -0
  9. data/lib/koala/api/batch_operation.rb +83 -0
  10. data/lib/koala/api/graph_api.rb +476 -0
  11. data/lib/koala/{graph_batch_api.rb → api/graph_batch_api.rb} +20 -16
  12. data/lib/koala/api/graph_collection.rb +107 -0
  13. data/lib/koala/api/legacy.rb +26 -0
  14. data/lib/koala/{rest_api.rb → api/rest_api.rb} +33 -3
  15. data/lib/koala/http_service.rb +69 -19
  16. data/lib/koala/http_service/multipart_request.rb +41 -0
  17. data/lib/koala/http_service/response.rb +18 -0
  18. data/lib/koala/http_service/uploadable_io.rb +187 -0
  19. data/lib/koala/oauth.rb +117 -14
  20. data/lib/koala/realtime_updates.rb +89 -51
  21. data/lib/koala/test_users.rb +109 -33
  22. data/lib/koala/utils.rb +4 -0
  23. data/lib/koala/version.rb +1 -1
  24. data/spec/cases/api_spec.rb +19 -12
  25. data/spec/cases/graph_api_batch_spec.rb +41 -41
  26. data/spec/cases/http_service_spec.rb +1 -22
  27. data/spec/cases/legacy_spec.rb +107 -0
  28. data/spec/cases/multipart_request_spec.rb +5 -5
  29. data/spec/cases/oauth_spec.rb +9 -9
  30. data/spec/cases/realtime_updates_spec.rb +154 -47
  31. data/spec/cases/test_users_spec.rb +268 -219
  32. data/spec/fixtures/mock_facebook_responses.yml +10 -6
  33. data/spec/support/graph_api_shared_examples.rb +17 -12
  34. data/spec/support/koala_test.rb +1 -1
  35. data/spec/support/mock_http_service.rb +2 -2
  36. data/spec/support/rest_api_shared_examples.rb +1 -1
  37. metadata +82 -104
  38. data/lib/koala/batch_operation.rb +0 -74
  39. data/lib/koala/graph_api.rb +0 -289
  40. data/lib/koala/graph_collection.rb +0 -63
  41. data/lib/koala/multipart_request.rb +0 -35
  42. data/lib/koala/uploadable_io.rb +0 -181
  43. data/spec/cases/graph_and_rest_api_spec.rb +0 -22
  44. data/spec/cases/graph_api_spec.rb +0 -22
  45. data/spec/cases/rest_api_spec.rb +0 -22
@@ -1,36 +1,27 @@
1
1
  module Koala
2
2
  module Facebook
3
- module RealtimeUpdateMethods
4
- # note: to subscribe to real-time updates, you must have an application access token
3
+ class RealtimeUpdates
4
+ # Manage realtime callbacks for changes to users' information.
5
+ # See http://developers.facebook.com/docs/reference/api/realtime.
6
+ #
7
+ # @note: to subscribe to real-time updates, you must have an application access token
8
+ # or provide the app secret when initializing your RealtimeUpdates object.
5
9
 
6
- def self.included(base)
7
- # make the attributes readable
8
- base.class_eval do
9
- attr_reader :api, :app_id, :app_access_token, :secret
10
-
11
- # parses the challenge params and makes sure the call is legitimate
12
- # returns the challenge string to be sent back to facebook if true
13
- # returns false otherwise
14
- # this is a class method, since you don't need to know anything about the app
15
- # saves a potential trip fetching the app access token
16
- def self.meet_challenge(params, verify_token = nil, &verification_block)
17
- if params["hub.mode"] == "subscribe" &&
18
- # you can make sure this is legitimate through two ways
19
- # if your store the token across the calls, you can pass in the token value
20
- # and we'll make sure it matches
21
- (verify_token && params["hub.verify_token"] == verify_token) ||
22
- # alternately, if you sent a specially-constructed value (such as a hash of various secret values)
23
- # you can pass in a block, which we'll call with the verify_token sent by Facebook
24
- # if it's legit, return anything that evaluates to true; otherwise, return nil or false
25
- (verification_block && yield(params["hub.verify_token"]))
26
- params["hub.challenge"]
27
- else
28
- false
29
- end
30
- end
31
- end
32
- end
10
+ # The application API interface used to communicate with Facebook.
11
+ # @return [Koala::Facebook::API]
12
+ attr_reader :api
13
+ attr_reader :app_id, :app_access_token, :secret
33
14
 
15
+ # Create a new RealtimeUpdates instance.
16
+ # If you don't have your app's access token, provide the app's secret and
17
+ # Koala will make a request to Facebook for the appropriate token.
18
+ #
19
+ # @param options initialization options.
20
+ # @option options :app_id the application's ID.
21
+ # @option options :app_access_token an application access token, if known.
22
+ # @option options :secret the application's secret.
23
+ #
24
+ # @raise ArgumentError if the application ID and one of the app access token or the secret are not provided.
34
25
  def initialize(options = {})
35
26
  @app_id = options[:app_id]
36
27
  @app_access_token = options[:app_access_token]
@@ -45,45 +36,92 @@ module Koala
45
36
  @app_access_token = oauth.get_app_access_token
46
37
  end
47
38
 
48
- @graph_api = API.new(@app_access_token)
39
+ @api = API.new(@app_access_token)
49
40
  end
50
41
 
51
- # subscribes for realtime updates
52
- # your callback_url must be set up to handle the verification request or the subscription will not be set up
53
- # http://developers.facebook.com/docs/api/realtime
54
- def subscribe(object, fields, callback_url, verify_token)
42
+ # Subscribe to realtime updates for certain fields on a given object (user, page, etc.).
43
+ # See {http://developers.facebook.com/docs/reference/api/realtime the realtime updates documentation}
44
+ # for more information on what objects and fields you can register for.
45
+ #
46
+ # @note Your callback_url must be set up to handle the verification request or the subscription will not be set up.
47
+ #
48
+ # @param object a Facebook ID (name or number)
49
+ # @param fields the fields you want your app to be updated about
50
+ # @param callback_url the URL Facebook should ping when an update is available
51
+ # @param verify_token a token included in the verification request, allowing you to ensure the call is genuine
52
+ # (see the docs for more information)
53
+ # @param options (see Koala::HTTPService.make_request)
54
+ #
55
+ # @return true if successful, false (or an APIError) otherwise.
56
+ def subscribe(object, fields, callback_url, verify_token, options = {})
55
57
  args = {
56
58
  :object => object,
57
59
  :fields => fields,
58
60
  :callback_url => callback_url,
59
- :verify_token => verify_token
60
- }
61
+ }.merge(verify_token ? {:verify_token => verify_token} : {})
61
62
  # a subscription is a success if Facebook returns a 200 (after hitting your server for verification)
62
- @graph_api.graph_call(subscription_path, args, 'post', :http_component => :status) == 200
63
+ @api.graph_call(subscription_path, args, 'post', options.merge(:http_component => :status)) == 200
63
64
  end
64
65
 
65
- # removes subscription for object
66
- # if object is nil, it will remove all subscriptions
67
- def unsubscribe(object = nil)
68
- args = {}
69
- args[:object] = object if object
70
- @graph_api.graph_call(subscription_path, args, 'delete', :http_component => :status) == 200
66
+ # Unsubscribe from updates for a particular object or from updates.
67
+ #
68
+ # @param object the object whose subscriptions to delete.
69
+ # If no object is provided, all subscriptions will be removed.
70
+ # @param options (see Koala::HTTPService.make_request)
71
+ #
72
+ # @return true if the unsubscription is successful, false (or an APIError) otherwise.
73
+ def unsubscribe(object = nil, options = {})
74
+ @api.graph_call(subscription_path, object ? {:object => object} : {}, "delete", options.merge(:http_component => :status)) == 200
71
75
  end
72
76
 
73
- def list_subscriptions
74
- @graph_api.graph_call(subscription_path)
77
+ # List all active subscriptions for this application.
78
+ #
79
+ # @param options (see Koala::HTTPService.make_request)
80
+ #
81
+ # @return [Array] a list of active subscriptions
82
+ def list_subscriptions(options = {})
83
+ @api.graph_call(subscription_path, {}, "get", options)
75
84
  end
76
85
 
77
- def graph_api
78
- Koala::Utils.deprecate("the TestUsers.graph_api accessor is deprecated and will be removed in a future version; please use .api instead.")
79
- @api
86
+ # As a security measure (to prevent DDoS attacks), Facebook sends a verification request to your server
87
+ # after you request a subscription.
88
+ # This method parses the challenge params and makes sure the call is legitimate.
89
+ #
90
+ # @param params the request parameters sent by Facebook. (You can pass in a Rails params hash.)
91
+ # @param verify_token the verify token sent in the {#subscribe subscription request}, if you provided one
92
+ #
93
+ # @yield verify_token if you need to compute the verification token
94
+ # (for instance, if your callback URL includes a record ID, which you look up
95
+ # and use to calculate a hash), you can pass meet_challenge a block, which
96
+ # will receive the verify_token received back from Facebook.
97
+ #
98
+ # @return the challenge string to be sent back to Facebook, or false if the request is invalid.
99
+ def self.meet_challenge(params, verify_token = nil, &verification_block)
100
+ if params["hub.mode"] == "subscribe" &&
101
+ # you can make sure this is legitimate through two ways
102
+ # if your store the token across the calls, you can pass in the token value
103
+ # and we'll make sure it matches
104
+ (verify_token && params["hub.verify_token"] == verify_token) ||
105
+ # alternately, if you sent a specially-constructed value (such as a hash of various secret values)
106
+ # you can pass in a block, which we'll call with the verify_token sent by Facebook
107
+ # if it's legit, return anything that evaluates to true; otherwise, return nil or false
108
+ (verification_block && yield(params["hub.verify_token"]))
109
+ params["hub.challenge"]
110
+ else
111
+ false
112
+ end
80
113
  end
81
-
82
- protected
83
-
114
+
115
+ # The Facebook subscription management URL for your application.
84
116
  def subscription_path
85
117
  @subscription_path ||= "#{@app_id}/subscriptions"
86
118
  end
119
+
120
+ # @private
121
+ def graph_api
122
+ Koala::Utils.deprecate("the TestUsers.graph_api accessor is deprecated and will be removed in a future version; please use .api instead.")
123
+ @api
124
+ end
87
125
  end
88
126
  end
89
127
  end
@@ -1,16 +1,34 @@
1
1
  require 'koala'
2
-
3
2
  module Koala
4
3
  module Facebook
5
- module TestUserMethods
6
-
7
- def self.included(base)
8
- base.class_eval do
9
- # make the Graph API accessible in case someone wants to make other calls to interact with their users
10
- attr_reader :api, :app_id, :app_access_token, :secret
11
- end
12
- end
13
-
4
+
5
+ # Create and manage test users for your application.
6
+ # A test user is a user account associated with an app created for the purpose
7
+ # of testing the functionality of that app.
8
+ # You can use test users for manual or automated testing --
9
+ # Koala's live test suite uses test users to verify the library works with Facebook.
10
+ #
11
+ # @note the test user API is fairly slow compared to other interfaces
12
+ # (which makes sense -- it's creating whole new user accounts!).
13
+ #
14
+ # See http://developers.facebook.com/docs/test_users/.
15
+ class TestUsers
16
+
17
+ # The application API interface used to communicate with Facebook.
18
+ # @return [Koala::Facebook::API]
19
+ attr_reader :api
20
+ attr_reader :app_id, :app_access_token, :secret
21
+
22
+ # Create a new TestUsers instance.
23
+ # If you don't have your app's access token, provide the app's secret and
24
+ # Koala will make a request to Facebook for the appropriate token.
25
+ #
26
+ # @param options initialization options.
27
+ # @option options :app_id the application's ID.
28
+ # @option options :app_access_token an application access token, if known.
29
+ # @option options :secret the application's secret.
30
+ #
31
+ # @raise ArgumentError if the application ID and one of the app access token or the secret are not provided.
14
32
  def initialize(options = {})
15
33
  @app_id = options[:app_id]
16
34
  @app_access_token = options[:app_access_token]
@@ -24,35 +42,81 @@ module Koala
24
42
  oauth = Koala::Facebook::OAuth.new(@app_id, @secret)
25
43
  @app_access_token = oauth.get_app_access_token
26
44
  end
45
+
27
46
  @api = API.new(@app_access_token)
28
47
  end
29
48
 
49
+ # Create a new test user.
50
+ #
51
+ # @param installed whether the user has installed your app
52
+ # @param permissions a comma-separated string or array of permissions the user has granted (if installed)
53
+ # @param args any additional arguments for the create call (name, etc.)
54
+ # @param options (see Koala::Facebook::API#api)
55
+ #
56
+ # @return a hash of information for the new user (id, access token, login URL, etc.)
30
57
  def create(installed, permissions = nil, args = {}, options = {})
31
58
  # Creates and returns a test user
32
59
  args['installed'] = installed
33
60
  args['permissions'] = (permissions.is_a?(Array) ? permissions.join(",") : permissions) if installed
34
- @api.graph_call(accounts_path, args, "post", options)
61
+ @api.graph_call(test_user_accounts_path, args, "post", options)
35
62
  end
36
63
 
37
- def list
38
- @api.graph_call(accounts_path)
64
+ # List all test users for the app.
65
+ #
66
+ # @param options (see Koala::Facebook::API#api)
67
+ #
68
+ # @return an array of hashes of user information (id, access token, etc.)
69
+ def list(options = {})
70
+ @api.graph_call(test_user_accounts_path, {}, "get", options)
39
71
  end
40
72
 
41
- def delete(test_user)
73
+ # Delete a test user.
74
+ #
75
+ # @param test_user the user to delete; can be either a Facebook ID or the hash returned by {#create}
76
+ # @param options (see Koala::Facebook::API#api)
77
+ #
78
+ # @return true if successful, false (or an {Koala::Facebook::APIError APIError}) if not
79
+ def delete(test_user, options = {})
42
80
  test_user = test_user["id"] if test_user.is_a?(Hash)
43
- @api.delete_object(test_user)
81
+ @api.delete_object(test_user, options)
44
82
  end
45
83
 
46
- def delete_all
47
- list.each {|u| delete u}
84
+ # Deletes all test users.
85
+ #
86
+ # @note if you have a lot of test users (> 20), this operation can take a long time.
87
+ #
88
+ # @param options (see Koala::Facebook::API#api)
89
+ #
90
+ # @return a list of the test users that have been deleted
91
+ def delete_all(options = {})
92
+ list(options).each {|u| delete(u, options)}
48
93
  end
49
94
 
50
- def update(test_user, args = {}, http_options = {})
95
+ # Updates a test user's attributes.
96
+ #
97
+ # @note currently, only name and password can be changed;
98
+ # see {http://developers.facebook.com/docs/test_users/ the Facebook documentation}.
99
+ #
100
+ # @param test_user the user to update; can be either a Facebook ID or the hash returned by {#create}
101
+ # @param args the updates to make
102
+ # @param options (see Koala::Facebook::API#api)
103
+ #
104
+ # @return true if successful, false (or an {Koala::Facebook::APIError APIError}) if not
105
+ def update(test_user, args = {}, options = {})
51
106
  test_user = test_user["id"] if test_user.is_a?(Hash)
52
- @api.graph_call(test_user, args, "post", http_options)
107
+ @api.graph_call(test_user, args, "post", options)
53
108
  end
54
109
 
55
- def befriend(user1_hash, user2_hash)
110
+ # Make two test users friends.
111
+ #
112
+ # @note there's no way to unfriend test users; you can always just create a new one.
113
+ #
114
+ # @param user1_hash one of the users to friend; the hash must contain both ID and access token (as returned by {#create})
115
+ # @param user2_hash the other user to friend
116
+ # @param options (see Koala::Facebook::API#api)
117
+ #
118
+ # @return true if successful, false (or an {Koala::Facebook::APIError APIError}) if not
119
+ def befriend(user1_hash, user2_hash, options = {})
56
120
  user1_id = user1_hash["id"] || user1_hash[:id]
57
121
  user2_id = user2_hash["id"] || user2_hash[:id]
58
122
  user1_token = user1_hash["access_token"] || user1_hash[:access_token]
@@ -67,35 +131,47 @@ module Koala
67
131
  u1_graph_api = API.new(user1_token)
68
132
  u2_graph_api = API.new(user2_token)
69
133
 
70
- u1_graph_api.graph_call("#{user1_id}/friends/#{user2_id}", {}, "post") &&
71
- u2_graph_api.graph_call("#{user2_id}/friends/#{user1_id}", {}, "post")
134
+ u1_graph_api.graph_call("#{user1_id}/friends/#{user2_id}", {}, "post", options) &&
135
+ u2_graph_api.graph_call("#{user2_id}/friends/#{user1_id}", {}, "post", options)
72
136
  end
73
137
 
74
- def create_network(network_size, installed = true, permissions = '')
75
- users = (0...network_size).collect { create(installed, permissions) }
138
+ # Create a network of test users, all of whom are friends and have the same permissions.
139
+ #
140
+ # @note this call slows down dramatically the more users you create
141
+ # (test user calls are slow, and more users => more 1-on-1 connections to be made).
142
+ # Use carefully.
143
+ #
144
+ # @param network_size how many users to create
145
+ # @param installed whether the users have installed your app (see {#create})
146
+ # @param permissions what permissions the users have granted (see {#create})
147
+ # @param options (see Koala::Facebook::API#api)
148
+ #
149
+ # @return the list of users created
150
+ def create_network(network_size, installed = true, permissions = '', options = {})
151
+ users = (0...network_size).collect { create(installed, permissions, options) }
76
152
  friends = users.clone
77
153
  users.each do |user|
78
154
  # Remove this user from list of friends
79
155
  friends.delete_at(0)
80
156
  # befriend all the others
81
157
  friends.each do |friend|
82
- befriend(user, friend)
158
+ befriend(user, friend, options)
83
159
  end
84
160
  end
85
161
  return users
86
162
  end
87
-
163
+
164
+ # The Facebook test users management URL for your application.
165
+ def test_user_accounts_path
166
+ @test_user_accounts_path ||= "/#{@app_id}/accounts/test-users"
167
+ end
168
+
169
+ # @private
170
+ # Legacy accessor for before GraphAPI was unified into API
88
171
  def graph_api
89
172
  Koala::Utils.deprecate("the TestUsers.graph_api accessor is deprecated and will be removed in a future version; please use .api instead.")
90
173
  @api
91
174
  end
92
-
93
- protected
94
-
95
- def accounts_path
96
- @accounts_path ||= "/#{@app_id}/accounts/test-users"
97
- end
98
-
99
175
  end # TestUserMethods
100
176
  end # Facebook
101
177
  end # Koala
@@ -1,7 +1,11 @@
1
1
  module Koala
2
2
  module Utils
3
3
 
4
+ # @private
4
5
  DEPRECATION_PREFIX = "KOALA: Deprecation warning: "
6
+
7
+ # Prints a deprecation message.
8
+ # Each individual message will only be printed once to avoid spamming.
5
9
  def self.deprecate(message)
6
10
  @posted_deprecations ||= []
7
11
  unless @posted_deprecations.include?(message)
@@ -1,3 +1,3 @@
1
1
  module Koala
2
- VERSION = "1.3.0rc1"
2
+ VERSION = "1.3.0rc2"
3
3
  end
@@ -11,7 +11,7 @@ describe "Koala::Facebook::API" do
11
11
  hash_not_including('access_token' => 1),
12
12
  anything,
13
13
  anything
14
- ).and_return(Koala::Response.new(200, "", ""))
14
+ ).and_return(Koala::HTTPService::Response.new(200, "", ""))
15
15
 
16
16
  @service.api('anything')
17
17
  end
@@ -25,7 +25,7 @@ describe "Koala::Facebook::API" do
25
25
  hash_including('access_token' => token),
26
26
  anything,
27
27
  anything
28
- ).and_return(Koala::Response.new(200, "", ""))
28
+ ).and_return(Koala::HTTPService::Response.new(200, "", ""))
29
29
 
30
30
  service.api('anything')
31
31
  end
@@ -36,15 +36,22 @@ describe "Koala::Facebook::API" do
36
36
  service.access_token.should == token
37
37
  end
38
38
 
39
- it "gets the attribute of a Koala::Response given by the http_component parameter" do
39
+ it "gets the attribute of a Koala::HTTPService::Response given by the http_component parameter" do
40
40
  http_component = :method_name
41
41
 
42
42
  response = mock('Mock KoalaResponse', :body => '', :status => 200)
43
- response.should_receive(http_component).and_return('')
44
-
43
+ result = stub("result")
44
+ response.stub(http_component).and_return(result)
45
45
  Koala.stub(:make_request).and_return(response)
46
46
 
47
- @service.api('anything', {}, 'get', :http_component => http_component)
47
+ @service.api('anything', {}, 'get', :http_component => http_component).should == result
48
+ end
49
+
50
+ it "returns the entire response if http_component => :response" do
51
+ http_component = :response
52
+ response = mock('Mock KoalaResponse', :body => '', :status => 200)
53
+ Koala.stub(:make_request).and_return(response)
54
+ @service.api('anything', {}, 'get', :http_component => http_component).should == response
48
55
  end
49
56
 
50
57
  it "returns the body of the request as JSON if no http_component is given" do
@@ -59,7 +66,7 @@ describe "Koala::Facebook::API" do
59
66
 
60
67
  it "executes an error checking block if provided" do
61
68
  body = '{}'
62
- Koala.stub(:make_request).and_return(Koala::Response.new(200, body, {}))
69
+ Koala.stub(:make_request).and_return(Koala::HTTPService::Response.new(200, body, {}))
63
70
 
64
71
  yield_test = mock('Yield Tester')
65
72
  yield_test.should_receive(:pass)
@@ -71,29 +78,29 @@ describe "Koala::Facebook::API" do
71
78
  end
72
79
 
73
80
  it "raises an API error if the HTTP response code is greater than or equal to 500" do
74
- Koala.stub(:make_request).and_return(Koala::Response.new(500, 'response body', {}))
81
+ Koala.stub(:make_request).and_return(Koala::HTTPService::Response.new(500, 'response body', {}))
75
82
 
76
83
  lambda { @service.api('anything') }.should raise_exception(Koala::Facebook::APIError)
77
84
  end
78
85
 
79
86
  it "handles rogue true/false as responses" do
80
- Koala.should_receive(:make_request).and_return(Koala::Response.new(200, 'true', {}))
87
+ Koala.should_receive(:make_request).and_return(Koala::HTTPService::Response.new(200, 'true', {}))
81
88
  @service.api('anything').should be_true
82
89
 
83
- Koala.should_receive(:make_request).and_return(Koala::Response.new(200, 'false', {}))
90
+ Koala.should_receive(:make_request).and_return(Koala::HTTPService::Response.new(200, 'false', {}))
84
91
  @service.api('anything').should be_false
85
92
  end
86
93
 
87
94
  describe "with regard to leading slashes" do
88
95
  it "adds a leading / to the path if not present" do
89
96
  path = "anything"
90
- Koala.should_receive(:make_request).with("/#{path}", anything, anything, anything).and_return(Koala::Response.new(200, 'true', {}))
97
+ Koala.should_receive(:make_request).with("/#{path}", anything, anything, anything).and_return(Koala::HTTPService::Response.new(200, 'true', {}))
91
98
  @service.api(path)
92
99
  end
93
100
 
94
101
  it "doesn't change the path if a leading / is present" do
95
102
  path = "/anything"
96
- Koala.should_receive(:make_request).with(path, anything, anything, anything).and_return(Koala::Response.new(200, 'true', {}))
103
+ Koala.should_receive(:make_request).with(path, anything, anything, anything).and_return(Koala::HTTPService::Response.new(200, 'true', {}))
97
104
  @service.api(path)
98
105
  end
99
106
  end