koala 1.3.0rc1 → 1.3.0rc2

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