koala 0.9.0 → 1.1.0

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 (82) hide show
  1. data/.autotest +12 -0
  2. data/.gitignore +5 -0
  3. data/.travis.yml +8 -0
  4. data/CHANGELOG +71 -7
  5. data/Gemfile +7 -0
  6. data/LICENSE +1 -1
  7. data/Manifest +10 -15
  8. data/Rakefile +13 -13
  9. data/autotest/discover.rb +1 -0
  10. data/koala.gemspec +36 -16
  11. data/lib/koala/batch_operation.rb +74 -0
  12. data/lib/koala/graph_api.rb +200 -134
  13. data/lib/koala/graph_batch_api.rb +87 -0
  14. data/lib/koala/graph_collection.rb +54 -0
  15. data/lib/koala/http_services/net_http_service.rb +92 -0
  16. data/lib/koala/http_services/typhoeus_service.rb +37 -0
  17. data/lib/koala/http_services.rb +22 -47
  18. data/lib/koala/oauth.rb +181 -0
  19. data/lib/koala/realtime_updates.rb +5 -14
  20. data/lib/koala/rest_api.rb +82 -10
  21. data/lib/koala/test_users.rb +85 -0
  22. data/lib/koala/uploadable_io.rb +175 -0
  23. data/lib/koala.rb +67 -229
  24. data/readme.md +71 -38
  25. data/spec/cases/api_base_spec.rb +101 -0
  26. data/spec/cases/graph_and_rest_api_spec.rb +31 -0
  27. data/spec/cases/graph_api_batch_spec.rb +609 -0
  28. data/spec/cases/graph_api_spec.rb +25 -0
  29. data/spec/cases/http_services/http_service_spec.rb +129 -0
  30. data/spec/cases/http_services/net_http_service_spec.rb +532 -0
  31. data/spec/cases/http_services/typhoeus_service_spec.rb +152 -0
  32. data/spec/cases/koala_spec.rb +55 -0
  33. data/spec/cases/oauth_spec.rb +409 -0
  34. data/spec/cases/realtime_updates_spec.rb +184 -0
  35. data/spec/cases/rest_api_spec.rb +25 -0
  36. data/spec/cases/test_users_spec.rb +221 -0
  37. data/spec/cases/uploadable_io_spec.rb +193 -0
  38. data/spec/fixtures/beach.jpg +0 -0
  39. data/spec/fixtures/cat.m4v +0 -0
  40. data/spec/{facebook_data.yml → fixtures/facebook_data.yml} +18 -14
  41. data/spec/fixtures/mock_facebook_responses.yml +409 -0
  42. data/spec/spec_helper.rb +19 -0
  43. data/spec/support/graph_api_shared_examples.rb +495 -0
  44. data/spec/support/json_testing_fix.rb +18 -0
  45. data/spec/support/live_testing_data_helper.rb +40 -0
  46. data/spec/support/mock_http_service.rb +96 -0
  47. data/spec/support/rest_api_shared_examples.rb +285 -0
  48. data/spec/support/setup_mocks_or_live.rb +51 -0
  49. data/spec/support/uploadable_io_shared_examples.rb +76 -0
  50. metadata +131 -59
  51. data/examples/oauth_playground/Capfile +0 -2
  52. data/examples/oauth_playground/LICENSE +0 -22
  53. data/examples/oauth_playground/Rakefile +0 -4
  54. data/examples/oauth_playground/config/deploy.rb +0 -39
  55. data/examples/oauth_playground/config/facebook.yml +0 -13
  56. data/examples/oauth_playground/config.ru +0 -27
  57. data/examples/oauth_playground/lib/load_facebook.rb +0 -3
  58. data/examples/oauth_playground/lib/oauth_playground.rb +0 -187
  59. data/examples/oauth_playground/readme.md +0 -8
  60. data/examples/oauth_playground/spec/oauth_playground_spec.rb +0 -35
  61. data/examples/oauth_playground/spec/spec_helper.rb +0 -36
  62. data/examples/oauth_playground/tmp/restart.txt +0 -0
  63. data/examples/oauth_playground/views/index.erb +0 -206
  64. data/examples/oauth_playground/views/layout.erb +0 -39
  65. data/init.rb +0 -2
  66. data/spec/koala/api_base_tests.rb +0 -95
  67. data/spec/koala/graph_and_rest_api/graph_and_rest_api_no_token_tests.rb +0 -10
  68. data/spec/koala/graph_and_rest_api/graph_and_rest_api_with_token_tests.rb +0 -11
  69. data/spec/koala/graph_api/graph_api_no_access_token_tests.rb +0 -114
  70. data/spec/koala/graph_api/graph_api_with_access_token_tests.rb +0 -150
  71. data/spec/koala/graph_api/graph_collection_tests.rb +0 -104
  72. data/spec/koala/live_testing_data_helper.rb +0 -15
  73. data/spec/koala/net_http_service_tests.rb +0 -181
  74. data/spec/koala/oauth/oauth_tests.rb +0 -440
  75. data/spec/koala/realtime_updates/realtime_updates_tests.rb +0 -187
  76. data/spec/koala/rest_api/rest_api_no_access_token_tests.rb +0 -94
  77. data/spec/koala/rest_api/rest_api_with_access_token_tests.rb +0 -36
  78. data/spec/koala_spec.rb +0 -18
  79. data/spec/koala_spec_helper.rb +0 -31
  80. data/spec/koala_spec_without_mocks.rb +0 -19
  81. data/spec/mock_facebook_responses.yml +0 -241
  82. data/spec/mock_http_service.rb +0 -81
data/lib/koala.rb CHANGED
@@ -1,296 +1,134 @@
1
1
  require 'cgi'
2
2
  require 'digest/md5'
3
3
 
4
- # rubygems is required to support json, how facebook returns data
5
- require 'rubygems'
6
- require 'json'
4
+ require 'multi_json'
7
5
 
8
- # openssl is required to support signed_request
6
+ # OpenSSL and Base64 are required to support signed_request
9
7
  require 'openssl'
8
+ require 'base64'
10
9
 
11
- # include default http services
10
+ # include koala modules
12
11
  require 'koala/http_services'
13
-
14
- # add Graph API methods
12
+ require 'koala/http_services/net_http_service'
13
+ require 'koala/oauth'
15
14
  require 'koala/graph_api'
16
-
17
- # add REST API methods
15
+ require 'koala/graph_batch_api'
16
+ require 'koala/batch_operation'
17
+ require 'koala/graph_collection'
18
18
  require 'koala/rest_api'
19
-
20
19
  require 'koala/realtime_updates'
20
+ require 'koala/test_users'
21
+ require 'koala/http_services'
22
+
23
+ # add KoalaIO class
24
+ require 'koala/uploadable_io'
21
25
 
22
26
  module Koala
23
-
27
+
24
28
  module Facebook
25
29
  # Ruby client library for the Facebook Platform.
26
- # Copyright 2010 Facebook
27
- # Adapted from the Python library by Alex Koppel, Rafi Jacoby, and the team at Context Optional
28
- #
29
- # Licensed under the Apache License, Version 2.0 (the "License"); you may
30
- # not use this file except in compliance with the License. You may obtain
31
- # a copy of the License at
32
- # http://www.apache.org/licenses/LICENSE-2.0
33
- #
34
- # Unless required by applicable law or agreed to in writing, software
35
- # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
36
- # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
37
- # License for the specific language governing permissions and limitations
38
- # under the License.
39
- #
40
- # This client library is designed to support the Graph API and the official
41
- # Facebook JavaScript SDK, which is the canonical way to implement
42
- # Facebook authentication. Read more about the Graph API at
43
- # http://developers.facebook.com/docs/api. You can download the Facebook
44
- # JavaScript SDK at http://github.com/facebook/connect-js/.
30
+ # Copyright 2010-2011 Alex Koppel
31
+ # Contributors: Alex Koppel, Chris Baclig, Rafi Jacoby, and the team at Context Optional
32
+ # http://github.com/arsduo/koala
45
33
 
46
34
  class API
47
- # initialize with an access token
35
+ # initialize with an access token
48
36
  def initialize(access_token = nil)
49
37
  @access_token = access_token
50
38
  end
51
-
39
+ attr_reader :access_token
40
+
52
41
  def api(path, args = {}, verb = "get", options = {}, &error_checking_block)
53
42
  # Fetches the given path in the Graph API.
54
43
  args["access_token"] = @access_token || @app_access_token if @access_token || @app_access_token
55
-
44
+
56
45
  # add a leading /
57
46
  path = "/#{path}" unless path =~ /^\//
58
47
 
59
48
  # make the request via the provided service
60
49
  result = Koala.make_request(path, args, verb, options)
61
-
50
+
62
51
  # Check for any 500 errors before parsing the body
63
52
  # since we're not guaranteed that the body is valid JSON
64
53
  # in the case of a server error
65
54
  raise APIError.new({"type" => "HTTP #{result.status.to_s}", "message" => "Response body: #{result.body}"}) if result.status >= 500
66
-
67
- # Parse the body as JSON and check for errors if provided a mechanism to do so
55
+
56
+ # parse the body as JSON and run it through the error checker (if provided)
68
57
  # Note: Facebook sometimes sends results like "true" and "false", which aren't strictly objects
69
- # and cause JSON.parse to fail -- so we account for that by wrapping the result in []
70
- body = response = JSON.parse("[#{result.body.to_s}]")[0]
71
- if error_checking_block
72
- yield(body)
73
- end
74
-
75
- # now return the desired information
76
- if options[:http_component]
77
- result.send(options[:http_component])
78
- else
79
- body
80
- end
58
+ # and cause MultiJson.decode to fail -- so we account for that by wrapping the result in []
59
+ body = MultiJson.decode("[#{result.body.to_s}]")[0]
60
+ yield body if error_checking_block
61
+
62
+ # if we want a component other than the body (e.g. redirect header for images), return that
63
+ options[:http_component] ? result.send(options[:http_component]) : body
81
64
  end
82
65
  end
66
+
67
+ # APIs
83
68
 
84
69
  class GraphAPI < API
85
70
  include GraphAPIMethods
86
71
  end
87
72
 
73
+ class GraphBatchAPI < GraphAPI
74
+ include GraphBatchAPIMethods
75
+ end
76
+
88
77
  class RestAPI < API
89
78
  include RestAPIMethods
90
79
  end
91
-
80
+
92
81
  class GraphAndRestAPI < API
93
82
  include GraphAPIMethods
94
83
  include RestAPIMethods
95
84
  end
96
-
85
+
97
86
  class RealtimeUpdates < API
98
87
  include RealtimeUpdateMethods
99
88
  end
100
-
101
- class APIError < Exception
89
+
90
+ class TestUsers < API
91
+ include TestUserMethods
92
+ # make the Graph API accessible in case someone wants to make other calls to interact with their users
93
+ attr_reader :graph_api
94
+ end
95
+
96
+ # Errors
97
+
98
+ class APIError < StandardError
102
99
  attr_accessor :fb_error_type
103
100
  def initialize(details = {})
104
- self.fb_error_type = details["type"]
101
+ self.fb_error_type = details["type"]
105
102
  super("#{fb_error_type}: #{details["message"]}")
106
103
  end
107
104
  end
108
-
109
-
110
- class OAuth
111
- attr_reader :app_id, :app_secret, :oauth_callback_url
112
- def initialize(app_id, app_secret, oauth_callback_url = nil)
113
- @app_id = app_id
114
- @app_secret = app_secret
115
- @oauth_callback_url = oauth_callback_url
116
- end
117
-
118
- def get_user_info_from_cookie(cookie_hash)
119
- # Parses the cookie set by the official Facebook JavaScript SDK.
120
- #
121
- # cookies should be a Hash, like the one Rails provides
122
- #
123
- # If the user is logged in via Facebook, we return a dictionary with the
124
- # keys "uid" and "access_token". The former is the user's Facebook ID,
125
- # and the latter can be used to make authenticated requests to the Graph API.
126
- # If the user is not logged in, we return None.
127
- #
128
- # Download the official Facebook JavaScript SDK at
129
- # http://github.com/facebook/connect-js/. Read more about Facebook
130
- # authentication at http://developers.facebook.com/docs/authentication/.
131
-
132
- if fb_cookie = cookie_hash["fbs_" + @app_id.to_s]
133
- # remove the opening/closing quote
134
- fb_cookie = fb_cookie.gsub(/\"/, "")
135
-
136
- # since we no longer get individual cookies, we have to separate out the components ourselves
137
- components = {}
138
- fb_cookie.split("&").map {|param| param = param.split("="); components[param[0]] = param[1]}
139
-
140
- # generate the signature and make sure it matches what we expect
141
- auth_string = components.keys.sort.collect {|a| a == "sig" ? nil : "#{a}=#{components[a]}"}.reject {|a| a.nil?}.join("")
142
- sig = Digest::MD5.hexdigest(auth_string + @app_secret)
143
- sig == components["sig"] && (components["expires"] == "0" || Time.now.to_i < components["expires"].to_i) ? components : nil
144
- end
145
- end
146
- alias_method :get_user_info_from_cookies, :get_user_info_from_cookie
147
-
148
- def get_user_from_cookie(cookies)
149
- if info = get_user_info_from_cookies(cookies)
150
- string = info["uid"]
151
- end
152
- end
153
- alias_method :get_user_from_cookies, :get_user_from_cookie
154
-
155
- # URLs
156
-
157
- def url_for_oauth_code(options = {})
158
- # for permissions, see http://developers.facebook.com/docs/authentication/permissions
159
- permissions = options[:permissions]
160
- scope = permissions ? "&scope=#{permissions.is_a?(Array) ? permissions.join(",") : permissions}" : ""
161
-
162
- callback = options[:callback] || @oauth_callback_url
163
- raise ArgumentError, "url_for_oauth_code must get a callback either from the OAuth object or in the options!" unless callback
164
-
165
- # Creates the URL for oauth authorization for a given callback and optional set of permissions
166
- "https://#{GRAPH_SERVER}/oauth/authorize?client_id=#{@app_id}&redirect_uri=#{callback}#{scope}"
167
- end
168
-
169
- def url_for_access_token(code, options = {})
170
- # Creates the URL for the token corresponding to a given code generated by Facebook
171
- callback = options[:callback] || @oauth_callback_url
172
- raise ArgumentError, "url_for_access_token must get a callback either from the OAuth object or in the parameters!" unless callback
173
- "https://#{GRAPH_SERVER}/oauth/access_token?client_id=#{@app_id}&redirect_uri=#{callback}&client_secret=#{@app_secret}&code=#{code}"
174
- end
175
-
176
- def get_access_token_info(code)
177
- # convenience method to get a parsed token from Facebook for a given code
178
- # should this require an OAuth callback URL?
179
- get_token_from_server(:code => code, :redirect_uri => @oauth_callback_url)
180
- end
181
-
182
- def get_access_token(code)
183
- # upstream methods will throw errors if needed
184
- if info = get_access_token_info(code)
185
- string = info["access_token"]
186
- end
187
- end
188
-
189
- def get_app_access_token_info
190
- # convenience method to get a the application's sessionless access token
191
- get_token_from_server({:type => 'client_cred'}, true)
192
- end
193
-
194
- def get_app_access_token
195
- if info = get_app_access_token_info
196
- string = info["access_token"]
197
- end
198
- end
199
-
200
- # signed_request
201
- def parse_signed_request(request)
202
- # Facebook's signed requests come in two parts -- the signature and the data payload
203
- # see http://developers.facebook.com/docs/authentication/canvas
204
- encoded_sig, payload = request.split(".")
205
-
206
- sig = base64_url_decode(encoded_sig)
207
-
208
- # if the signature matches, return the data, decoded and parsed as JSON
209
- if OpenSSL::HMAC.digest("sha256", @app_secret, payload) == sig
210
- JSON.parse(base64_url_decode(payload))
211
- else
212
- nil
213
- end
214
- end
215
-
216
- # from session keys
217
- def get_token_info_from_session_keys(sessions)
218
- # fetch the OAuth tokens from Facebook
219
- response = fetch_token_string({
220
- :type => 'client_cred',
221
- :sessions => sessions.join(",")
222
- }, true, "exchange_sessions")
223
-
224
- # Facebook returns an empty body in certain error conditions
225
- if response == ""
226
- raise APIError.new("ArgumentError", "get_token_from_session_key received an error (empty response body) for sessions #{sessions.inspect}!")
227
- end
228
-
229
- JSON.parse(response)
230
- end
231
-
232
- def get_tokens_from_session_keys(sessions)
233
- # get the original hash results
234
- results = get_token_info_from_session_keys(sessions)
235
- # now recollect them as just the access tokens
236
- results.collect { |r| r ? r["access_token"] : nil }
237
- end
238
-
239
- def get_token_from_session_key(session)
240
- # convenience method for a single key
241
- # gets the overlaoded strings automatically
242
- get_tokens_from_session_keys([session])[0]
243
- end
244
-
245
- protected
246
-
247
- def get_token_from_server(args, post = false)
248
- # fetch the result from Facebook's servers
249
- result = fetch_token_string(args, post)
250
-
251
- # if we have an error, parse the error JSON and raise an error
252
- raise APIError.new((JSON.parse(result)["error"] rescue nil) || {}) if result =~ /error/
105
+ end
253
106
 
254
- # otherwise, parse the access token
255
- parse_access_token(result)
256
- end
257
-
258
- def parse_access_token(response_text)
259
- components = response_text.split("&").inject({}) do |hash, bit|
260
- key, value = bit.split("=")
261
- hash.merge!(key => value)
262
- end
263
- components
264
- end
107
+ class KoalaError < StandardError; end
265
108
 
266
- def fetch_token_string(args, post = false, endpoint = "access_token")
267
- Koala.make_request("/oauth/#{endpoint}", {
268
- :client_id => @app_id,
269
- :client_secret => @app_secret
270
- }.merge!(args), post ? "post" : "get").body
271
- end
272
-
273
- # base 64
274
- def base64_url_decode(string)
275
- # to properly decode what Facebook provides, we need to add == to the end
276
- # and translate certain characters to others before running the actual decoding
277
- # see http://developers.facebook.com/docs/authentication/canvas
278
- "#{string}==".tr("-_", "+/").unpack("m")[0]
279
- end
280
- end
109
+ # Make an api request using the provided api service or one passed by the caller
110
+ def self.make_request(path, args, verb, options = {})
111
+ http_service = options.delete(:http_service) || Koala.http_service
112
+ options = options.merge(:use_ssl => true) if @always_use_ssl
113
+ http_service.make_request(path, args, verb, options)
281
114
  end
282
-
115
+
283
116
  # finally, set up the http service Koala methods used to make requests
284
117
  # you can use your own (for HTTParty, etc.) by calling Koala.http_service = YourModule
285
- def self.http_service=(service)
286
- self.send(:include, service)
118
+ class << self
119
+ attr_accessor :http_service
120
+ attr_accessor :always_use_ssl
121
+ attr_accessor :base_http_service
287
122
  end
123
+ Koala.base_http_service = NetHTTPService
288
124
 
289
125
  # by default, try requiring Typhoeus -- if that works, use it
126
+ # if you have Typheous and don't want to use it (or want another service),
127
+ # you can run Koala.http_service = NetHTTPService (or MyHTTPService)
290
128
  begin
291
- require 'typhoeus'
129
+ require 'koala/http_services/typhoeus_service'
292
130
  Koala.http_service = TyphoeusService
293
131
  rescue LoadError
294
- Koala.http_service = NetHTTPService
132
+ Koala.http_service = Koala.base_http_service
295
133
  end
296
134
  end
data/readme.md CHANGED
@@ -1,28 +1,39 @@
1
1
  Koala
2
2
  ====
3
- Koala (<a href="http://github.com/arsduo/koala" target="_blank">http://github.com/arsduo/koala</a>) is a new Facebook library for Ruby, supporting the Graph API, the old REST API, realtime updates, and OAuth validation. We wrote Koala with four goals:
3
+ [Koala](http://github.com/arsduo/koala) is a new Facebook library for Ruby, supporting the Graph API (including the batch requests and photo uploads), the REST API, realtime updates, test users, and OAuth validation. We wrote Koala with four goals:
4
4
 
5
- * Lightweight: Koala should be as light and simple as Facebook’s own new libraries, providing API accessors and returning simple JSON. (We clock in, with comments, just over 500 lines of code.)
6
- * Fast: Koala should, out of the box, be quick. In addition to supporting the vanilla Ruby networking libraries, it natively supports Typhoeus, our preferred gem for making fast HTTP requests. Of course, That brings us to our next topic:
7
- * Flexible: Koala should be useful to everyone, regardless of their current configuration. (We have no dependencies beyond the JSON gem. Koala also has a built-in mechanism for using whichever HTTP library you prefer to make requests against the graph.)
5
+ * Lightweight: Koala should be as light and simple as Facebook’s own new libraries, providing API accessors and returning simple JSON. (We clock in, with comments, at just over 750 lines of code.)
6
+ * Fast: Koala should, out of the box, be quick. In addition to supporting the vanilla Ruby networking libraries, it natively supports Typhoeus, our preferred gem for making fast HTTP requests. Of course, that brings us to our next topic:
7
+ * Flexible: Koala should be useful to everyone, regardless of their current configuration. (In addition to vanilla Ruby, we support JRuby, Rubinius, and REE, and provide built-in mechanism for using whichever HTTP library you prefer.)
8
8
  * Tested: Koala should have complete test coverage, so you can rely on it. (Our complete test coverage can be run against either mocked responses or the live Facebook servers.)
9
9
 
10
+ Installation
11
+ ---
12
+
13
+ Easy:
14
+
15
+ [sudo|rvm] gem install koala
16
+
17
+ Or in Bundler:
18
+
19
+ gem "koala"
20
+
10
21
  Graph API
11
22
  ----
12
- The Graph API is the simple, slick new interface to Facebook's data. Using it with Koala is quite straightforward:
23
+ The Graph API is the simple, slick new interface to Facebook's data. Using it with Koala is quite straightforward:
13
24
 
14
- graph = Koala::Facebook::GraphAPI.new(oauth_access_token)
15
- profile = graph.get_object("me")
16
- friends = graph.get_connections("me", "friends")
17
- graph.put_object("me", "feed", :message => "I am writing on my wall!")
25
+ @graph = Koala::Facebook::GraphAPI.new(oauth_access_token)
26
+ profile = @graph.get_object("me")
27
+ friends = @graph.get_connections("me", "friends")
28
+ @graph.put_object("me", "feed", :message => "I am writing on my wall!")
18
29
 
19
- The response of most requests is the JSON data returned from the Facebook servers as a Hash.
30
+ The response of most requests is the JSON data returned from the Facebook servers as a Hash.
20
31
 
21
32
  When retrieving data that returns an array of results (for example, when calling GraphAPI#get_connections or GraphAPI#search) a GraphCollection object (a sub-class of Array) will be returned, which contains added methods for getting the next and previous page of results:
22
-
33
+
23
34
  # Returns the feed items for the currently logged-in user as a GraphCollection
24
- feed = graph.get_connections("me", "feed")
25
-
35
+ feed = @graph.get_connections("me", "feed")
36
+
26
37
  # GraphCollection is a sub-class of Array, so you can use it as a usual Array
27
38
  first_entry = feed[0]
28
39
  last_entry = feed.last
@@ -33,33 +44,43 @@ When retrieving data that returns an array of results (for example, when calling
33
44
  # Returns an array describing the URL for the next page: [path, arguments]
34
45
  # This is useful for paging across multiple requests
35
46
  next_path, next_args = feed.next_page_params
36
-
37
- # You can use those params to easily get the next (or prevous) page
38
- page = graph.get_page(feed.next_page_params)
47
+
48
+ # You can use those params to easily get the next (or previous) page
49
+ page = @graph.get_page(feed.next_page_params)
50
+
51
+ You can make multiple calls at once using Facebook's batch API:
52
+
53
+ # Returns an array of results as if they were called non-batch
54
+ @graph.batch do |batch_api|
55
+ batch_api.get_object('me')
56
+ batch_api.get_object('koppel')
57
+ end
39
58
 
40
59
  Check out the wiki for more examples.
41
60
 
42
- The old-school REST API
61
+ The REST API
43
62
  -----
44
- Where the Graph API and the old REST API overlap, you should choose the Graph API. Unfortunately, that overlap is far from complete, and there are many important API calls that can't yet be done via the Graph.
63
+ Where the Graph API and the old REST API overlap, you should choose the Graph API. Unfortunately, that overlap is far from complete, and there are many important API calls that can't yet be done via the Graph.
45
64
 
46
65
  Koala now supports the old-school REST API using OAuth access tokens; to use this, instantiate your class using the RestAPI class:
47
66
 
48
- @rest = Koala::Facebook::RestAPI.new(oauth_access_token)
49
- @rest.fql_query(my_fql_query) # convenience method
50
- @rest.rest_call("stream.publish", arguments_hash) # generic version
51
-
52
- We reserve the right to expand the built-in REST API coverage to additional convenience methods in the future, depending on how fast Facebook moves to fill in the gaps.
67
+ @rest = Koala::Facebook::RestAPI.new(oauth_access_token)
68
+ @rest.fql_query(my_fql_query) # convenience method
69
+ @rest.fql_multiquery(fql_query_hash) # convenience method
70
+ @rest.rest_call("stream.publish", arguments_hash) # generic version
71
+
72
+ We reserve the right to expand the built-in REST API coverage to additional convenience methods in the future, depending on how fast Facebook moves to fill in the gaps.
53
73
 
54
74
  (If you want the power of both APIs in the palm of your hand, try out the GraphAndRestAPI class.)
55
75
 
56
76
  OAuth
57
77
  -----
58
78
  You can use the Graph and REST APIs without an OAuth access token, but the real magic happens when you provide Facebook an OAuth token to prove you're authenticated. Koala provides an OAuth class to make that process easy:
59
- @oauth = Koala::Facebook::OAuth.new(app_id, code, callback_url)
79
+ @oauth = Koala::Facebook::OAuth.new(app_id, app_secret, callback_url)
60
80
 
61
81
  If your application uses Koala and the Facebook [JavaScript SDK](http://github.com/facebook/connect-js) (formerly Facebook Connect), you can use the OAuth class to parse the cookies:
62
- @oauth.get_user_from_cookie(cookies)
82
+ @oauth.get_user_from_cookies(cookies) # gets the user's ID
83
+ @oauth.get_user_info_from_cookies(cookies) # parses and returns the entire hash
63
84
 
64
85
  And if you have to use the more complicated [redirect-based OAuth process](http://developers.facebook.com/docs/authentication/), Koala helps out there, too:
65
86
  # generate authenticating URL
@@ -70,20 +91,18 @@ And if you have to use the more complicated [redirect-based OAuth process](http:
70
91
  You can also get your application's own access token, which can be used without a user session for subscriptions and certain other requests:
71
92
  @oauth.get_app_access_token
72
93
 
73
- That's it! It's pretty simple once you get the hang of it. If you're new to OAuth, though, check out the wiki and the OAuth Playground example site (see below).
74
-
75
- *Signed Requests:* Excited to try out the new signed request authentication scheme? Good news! Koala now supports parsing those parameters:
94
+ For those building apps on Facebook, parsing signed requests is simple:
76
95
  @oauth.parse_signed_request(request)
77
96
 
78
- *Exchanging session keys:* Stuck building tab applications on Facebook? Wishing you had an OAuth token so you could use the Graph API? You're in luck! Koala now allows you to exchange session keys for OAuth access tokens:
97
+ Or, if for some horrible reason, you're still using session keys, despair not! It's easy to turn them into shiny, modern OAuth tokens:
79
98
  @oauth.get_token_from_session_key(session_key)
80
99
  @oauth.get_tokens_from_session_keys(array_of_session_keys)
81
100
 
101
+ That's it! It's pretty simple once you get the hang of it. If you're new to OAuth, though, check out the wiki and the OAuth Playground example site (see below).
102
+
82
103
  Real-time Updates
83
104
  -----
84
- The Graph API now allows your application to subscribe to real-time updates for certain objects in the graph.
85
-
86
- Currently, Facebook only supports subscribing to users, permissions and errors. On top of that, there are limitations on what attributes and connections for each of these objects you can subscribe to updates for. Check the [official Facebook documentation](http://developers.facebook.com/docs/api/realtime) for more details.
105
+ Sometimes, reaching out to Facebook is a pain -- let it reach out to you instead. The Graph API allows your application to subscribe to real-time updates for certain objects in the graph; check the [official Facebook documentation](http://developers.facebook.com/docs/api/realtime) for more details on what objects you can subscribe to and what limitations may apply.
87
106
 
88
107
  Koala makes it easy to interact with your applications using the RealtimeUpdates class:
89
108
 
@@ -107,6 +126,17 @@ And to top it all off, RealtimeUpdates provides a static method to respond to Fa
107
126
 
108
127
  For more information about meet_challenge and the RealtimeUpdates class, check out the Real-Time Updates page on the wiki.
109
128
 
129
+ Test Users
130
+ -----
131
+
132
+ We also support the test users API, allowing you to conjure up fake users and command them to do your bidding using the Graph or REST API:
133
+
134
+ @test_users = Koala::Facebook::TestUsers.new(:app_id => id, :secret => secret)
135
+ user = @test_users.create(is_app_installed, desired_permissions)
136
+ user_graph_api = Koala::Facebook::GraphAPI.new(user["access_token"])
137
+ # or, if you want to make a whole community:
138
+ @test_users.create_network(network_size, is_app_installed, common_permissions)
139
+
110
140
  See examples, ask questions
111
141
  -----
112
142
  Some resources to help you as you play with Koala and the Graph API:
@@ -118,12 +148,15 @@ Some resources to help you as you play with Koala and the Graph API:
118
148
  Testing
119
149
  -----
120
150
 
121
- Unit tests are provided for all of Koala's methods. By default, these tests run against mock responses and hence are ready out of the box:
122
- # From the spec directory
123
- spec koala_spec.rb
151
+ Unit tests are provided for all of Koala's methods. By default, these tests run against mock responses and hence are ready out of the box:
152
+
153
+ # From anywhere in the project directory:
154
+ bundle exec rake spec
155
+
124
156
 
125
157
  You can also run live tests against Facebook's servers:
126
- # Again from the spec directory
127
- spec koala_spec_without_mocks.rb
158
+
159
+ # Again from anywhere in the project directory:
160
+ LIVE=true bundle exec rake spec
128
161
 
129
- Important Note: to run the live tests, you have to provide some of your own data: a valid OAuth access token with publish\_stream and read\_stream permissions and an OAuth code that can be used to generate an access token. You can get these data at the OAuth Playground; if you want to use your own app, remember to swap out the app ID, secret, and other values. (The file also provides valid values for other tests, which you're welcome to swap out for data specific to your own application.)
162
+ Important Note: to run the live tests, you have to provide some of your own data in spec/fixtures/facebook_data.yml: a valid OAuth access token with publish\_stream, read\_stream, and user\_photos permissions and an OAuth code that can be used to generate an access token. You can get this data at the OAuth Playground; if you want to use your own app, remember to swap out the app ID, secret, and other values. (The file also provides valid values for other tests, which you're welcome to swap out for data specific to your own application.)
@@ -0,0 +1,101 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Koala::Facebook::API" do
4
+ before(:each) do
5
+ @service = Koala::Facebook::API.new
6
+ end
7
+
8
+ it "should not include an access token if none was given" do
9
+ Koala.should_receive(:make_request).with(
10
+ anything,
11
+ hash_not_including('access_token' => 1),
12
+ anything,
13
+ anything
14
+ ).and_return(Koala::Response.new(200, "", ""))
15
+
16
+ @service.api('anything')
17
+ end
18
+
19
+ it "should include an access token if given" do
20
+ token = 'adfadf'
21
+ service = Koala::Facebook::API.new token
22
+
23
+ Koala.should_receive(:make_request).with(
24
+ anything,
25
+ hash_including('access_token' => token),
26
+ anything,
27
+ anything
28
+ ).and_return(Koala::Response.new(200, "", ""))
29
+
30
+ service.api('anything')
31
+ end
32
+
33
+ it "should have an attr_reader for access token" do
34
+ token = 'adfadf'
35
+ service = Koala::Facebook::API.new token
36
+ service.access_token.should == token
37
+ end
38
+
39
+ it "should get the attribute of a Koala::Response given by the http_component parameter" do
40
+ http_component = :method_name
41
+
42
+ response = mock('Mock KoalaResponse', :body => '', :status => 200)
43
+ response.should_receive(http_component).and_return('')
44
+
45
+ Koala.stub(:make_request).and_return(response)
46
+
47
+ @service.api('anything', 'get', {}, :http_component => http_component)
48
+ end
49
+
50
+ it "should return the body of the request as JSON if no http_component is given" do
51
+ response = stub('response', :body => 'body', :status => 200)
52
+ Koala.stub(:make_request).and_return(response)
53
+
54
+ json_body = mock('JSON body')
55
+ MultiJson.stub(:decode).and_return([json_body])
56
+
57
+ @service.api('anything').should == json_body
58
+ end
59
+
60
+ it "should execute an error checking block if provided" do
61
+ body = '{}'
62
+ Koala.stub(:make_request).and_return(Koala::Response.new(200, body, {}))
63
+
64
+ yield_test = mock('Yield Tester')
65
+ yield_test.should_receive(:pass)
66
+
67
+ @service.api('anything', {}, "get") do |arg|
68
+ yield_test.pass
69
+ arg.should == MultiJson.decode(body)
70
+ end
71
+ end
72
+
73
+ it "should raise 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', {}))
75
+
76
+ lambda { @service.api('anything') }.should raise_exception(Koala::Facebook::APIError)
77
+ end
78
+
79
+ it "should handle rogue true/false as responses" do
80
+ Koala.should_receive(:make_request).and_return(Koala::Response.new(200, 'true', {}))
81
+ @service.api('anything').should be_true
82
+
83
+ Koala.should_receive(:make_request).and_return(Koala::Response.new(200, 'false', {}))
84
+ @service.api('anything').should be_false
85
+ end
86
+
87
+ describe "with regard to leading slashes" do
88
+ it "should add a leading / to the path if not present" do
89
+ path = "anything"
90
+ Koala.should_receive(:make_request).with("/#{path}", anything, anything, anything).and_return(Koala::Response.new(200, 'true', {}))
91
+ @service.api(path)
92
+ end
93
+
94
+ it "shouldn't change the path if a leading / is present" do
95
+ path = "/anything"
96
+ Koala.should_receive(:make_request).with(path, anything, anything, anything).and_return(Koala::Response.new(200, 'true', {}))
97
+ @service.api(path)
98
+ end
99
+ end
100
+
101
+ end