koala 1.0.0.beta → 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 (69) hide show
  1. data/.autotest +12 -0
  2. data/.gitignore +5 -0
  3. data/.travis.yml +8 -0
  4. data/CHANGELOG +42 -4
  5. data/Gemfile +7 -0
  6. data/LICENSE +1 -1
  7. data/Manifest +5 -2
  8. data/Rakefile +13 -14
  9. data/autotest/discover.rb +1 -0
  10. data/koala.gemspec +35 -20
  11. data/lib/koala/batch_operation.rb +74 -0
  12. data/lib/koala/graph_api.rb +196 -143
  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 +15 -124
  18. data/lib/koala/oauth.rb +181 -0
  19. data/lib/koala/realtime_updates.rb +5 -14
  20. data/lib/koala/rest_api.rb +13 -8
  21. data/lib/koala/test_users.rb +21 -8
  22. data/lib/koala/uploadable_io.rb +175 -0
  23. data/lib/koala.rb +48 -240
  24. data/readme.md +60 -28
  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/{koala/test_users/test_users_tests.rb → cases/test_users_spec.rb} +47 -34
  37. data/spec/cases/uploadable_io_spec.rb +193 -0
  38. data/spec/fixtures/cat.m4v +0 -0
  39. data/spec/{facebook_data.yml → fixtures/facebook_data.yml} +12 -14
  40. data/spec/{mock_facebook_responses.yml → fixtures/mock_facebook_responses.yml} +408 -306
  41. data/spec/spec_helper.rb +19 -0
  42. data/spec/support/graph_api_shared_examples.rb +495 -0
  43. data/spec/support/json_testing_fix.rb +18 -0
  44. data/spec/{koala → support}/live_testing_data_helper.rb +39 -42
  45. data/spec/support/mock_http_service.rb +96 -0
  46. data/spec/support/rest_api_shared_examples.rb +285 -0
  47. data/spec/support/setup_mocks_or_live.rb +51 -0
  48. data/spec/support/uploadable_io_shared_examples.rb +76 -0
  49. metadata +110 -64
  50. data/init.rb +0 -2
  51. data/spec/koala/api_base_tests.rb +0 -102
  52. data/spec/koala/graph_and_rest_api/graph_and_rest_api_no_token_tests.rb +0 -14
  53. data/spec/koala/graph_and_rest_api/graph_and_rest_api_with_token_tests.rb +0 -16
  54. data/spec/koala/graph_api/graph_api_no_access_token_tests.rb +0 -63
  55. data/spec/koala/graph_api/graph_api_tests.rb +0 -86
  56. data/spec/koala/graph_api/graph_api_with_access_token_tests.rb +0 -154
  57. data/spec/koala/graph_api/graph_collection_tests.rb +0 -104
  58. data/spec/koala/net_http_service_tests.rb +0 -430
  59. data/spec/koala/oauth/oauth_tests.rb +0 -409
  60. data/spec/koala/realtime_updates/realtime_updates_tests.rb +0 -187
  61. data/spec/koala/rest_api/rest_api_no_access_token_tests.rb +0 -25
  62. data/spec/koala/rest_api/rest_api_tests.rb +0 -118
  63. data/spec/koala/rest_api/rest_api_with_access_token_tests.rb +0 -38
  64. data/spec/koala/typhoeus_service_tests.rb +0 -156
  65. data/spec/koala_spec.rb +0 -18
  66. data/spec/koala_spec_helper.rb +0 -70
  67. data/spec/koala_spec_without_mocks.rb +0 -19
  68. data/spec/mock_http_service.rb +0 -96
  69. /data/spec/{koala/assets → fixtures}/beach.jpg +0 -0
data/lib/koala.rb CHANGED
@@ -1,55 +1,38 @@
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
6
  # OpenSSL and Base64 are required to support signed_request
9
7
  require 'openssl'
10
8
  require 'base64'
11
9
 
12
- # include default http services
10
+ # include koala modules
13
11
  require 'koala/http_services'
14
-
15
- # add Graph API methods
12
+ require 'koala/http_services/net_http_service'
13
+ require 'koala/oauth'
16
14
  require 'koala/graph_api'
17
-
18
- # add REST API methods
15
+ require 'koala/graph_batch_api'
16
+ require 'koala/batch_operation'
17
+ require 'koala/graph_collection'
19
18
  require 'koala/rest_api'
20
-
21
- # add realtime update methods
22
19
  require 'koala/realtime_updates'
23
-
24
- # add test user methods
25
20
  require 'koala/test_users'
21
+ require 'koala/http_services'
22
+
23
+ # add KoalaIO class
24
+ require 'koala/uploadable_io'
26
25
 
27
26
  module Koala
28
27
 
29
28
  module Facebook
30
29
  # Ruby client library for the Facebook Platform.
31
- # Copyright 2010 Facebook
32
- # Adapted from the Python library by Alex Koppel, Rafi Jacoby, and the team at Context Optional
33
- #
34
- # Licensed under the Apache License, Version 2.0 (the "License"); you may
35
- # not use this file except in compliance with the License. You may obtain
36
- # a copy of the License at
37
- # http://www.apache.org/licenses/LICENSE-2.0
38
- #
39
- # Unless required by applicable law or agreed to in writing, software
40
- # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
41
- # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
42
- # License for the specific language governing permissions and limitations
43
- # under the License.
44
- #
45
- # This client library is designed to support the Graph API and the official
46
- # Facebook JavaScript SDK, which is the canonical way to implement
47
- # Facebook authentication. Read more about the Graph API at
48
- # http://developers.facebook.com/docs/api. You can download the Facebook
49
- # 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
50
33
 
51
34
  class API
52
- # initialize with an access token
35
+ # initialize with an access token
53
36
  def initialize(access_token = nil)
54
37
  @access_token = access_token
55
38
  end
@@ -58,7 +41,7 @@ module Koala
58
41
  def api(path, args = {}, verb = "get", options = {}, &error_checking_block)
59
42
  # Fetches the given path in the Graph API.
60
43
  args["access_token"] = @access_token || @app_access_token if @access_token || @app_access_token
61
-
44
+
62
45
  # add a leading /
63
46
  path = "/#{path}" unless path =~ /^\//
64
47
 
@@ -70,27 +53,27 @@ module Koala
70
53
  # in the case of a server error
71
54
  raise APIError.new({"type" => "HTTP #{result.status.to_s}", "message" => "Response body: #{result.body}"}) if result.status >= 500
72
55
 
73
- # Parse the body as JSON and check for errors if provided a mechanism to do so
56
+ # parse the body as JSON and run it through the error checker (if provided)
74
57
  # Note: Facebook sometimes sends results like "true" and "false", which aren't strictly objects
75
- # and cause JSON.parse to fail -- so we account for that by wrapping the result in []
76
- body = response = JSON.parse("[#{result.body.to_s}]")[0]
77
- if error_checking_block
78
- yield(body)
79
- 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
80
61
 
81
- # now return the desired information
82
- if options[:http_component]
83
- result.send(options[:http_component])
84
- else
85
- body
86
- end
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
87
64
  end
88
65
  end
89
66
 
67
+ # APIs
68
+
90
69
  class GraphAPI < API
91
70
  include GraphAPIMethods
92
71
  end
93
-
72
+
73
+ class GraphBatchAPI < GraphAPI
74
+ include GraphBatchAPIMethods
75
+ end
76
+
94
77
  class RestAPI < API
95
78
  include RestAPIMethods
96
79
  end
@@ -110,217 +93,42 @@ module Koala
110
93
  attr_reader :graph_api
111
94
  end
112
95
 
113
- class APIError < Exception
96
+ # Errors
97
+
98
+ class APIError < StandardError
114
99
  attr_accessor :fb_error_type
115
100
  def initialize(details = {})
116
101
  self.fb_error_type = details["type"]
117
102
  super("#{fb_error_type}: #{details["message"]}")
118
103
  end
119
104
  end
105
+ end
120
106
 
107
+ class KoalaError < StandardError; end
121
108
 
122
- class OAuth
123
- attr_reader :app_id, :app_secret, :oauth_callback_url
124
- def initialize(app_id, app_secret, oauth_callback_url = nil)
125
- @app_id = app_id
126
- @app_secret = app_secret
127
- @oauth_callback_url = oauth_callback_url
128
- end
129
-
130
- def get_user_info_from_cookie(cookie_hash)
131
- # Parses the cookie set by the official Facebook JavaScript SDK.
132
- #
133
- # cookies should be a Hash, like the one Rails provides
134
- #
135
- # If the user is logged in via Facebook, we return a dictionary with the
136
- # keys "uid" and "access_token". The former is the user's Facebook ID,
137
- # and the latter can be used to make authenticated requests to the Graph API.
138
- # If the user is not logged in, we return None.
139
- #
140
- # Download the official Facebook JavaScript SDK at
141
- # http://github.com/facebook/connect-js/. Read more about Facebook
142
- # authentication at http://developers.facebook.com/docs/authentication/.
143
-
144
- if fb_cookie = cookie_hash["fbs_" + @app_id.to_s]
145
- # remove the opening/closing quote
146
- fb_cookie = fb_cookie.gsub(/\"/, "")
147
-
148
- # since we no longer get individual cookies, we have to separate out the components ourselves
149
- components = {}
150
- fb_cookie.split("&").map {|param| param = param.split("="); components[param[0]] = param[1]}
151
-
152
- # generate the signature and make sure it matches what we expect
153
- auth_string = components.keys.sort.collect {|a| a == "sig" ? nil : "#{a}=#{components[a]}"}.reject {|a| a.nil?}.join("")
154
- sig = Digest::MD5.hexdigest(auth_string + @app_secret)
155
- sig == components["sig"] && (components["expires"] == "0" || Time.now.to_i < components["expires"].to_i) ? components : nil
156
- end
157
- end
158
- alias_method :get_user_info_from_cookies, :get_user_info_from_cookie
159
-
160
- def get_user_from_cookie(cookies)
161
- if info = get_user_info_from_cookies(cookies)
162
- string = info["uid"]
163
- end
164
- end
165
- alias_method :get_user_from_cookies, :get_user_from_cookie
166
-
167
- # URLs
168
-
169
- def url_for_oauth_code(options = {})
170
- # for permissions, see http://developers.facebook.com/docs/authentication/permissions
171
- permissions = options[:permissions]
172
- scope = permissions ? "&scope=#{permissions.is_a?(Array) ? permissions.join(",") : permissions}" : ""
173
-
174
- callback = options[:callback] || @oauth_callback_url
175
- raise ArgumentError, "url_for_oauth_code must get a callback either from the OAuth object or in the options!" unless callback
176
-
177
- # Creates the URL for oauth authorization for a given callback and optional set of permissions
178
- "https://#{GRAPH_SERVER}/oauth/authorize?client_id=#{@app_id}&redirect_uri=#{callback}#{scope}"
179
- end
180
-
181
- def url_for_access_token(code, options = {})
182
- # Creates the URL for the token corresponding to a given code generated by Facebook
183
- callback = options[:callback] || @oauth_callback_url
184
- raise ArgumentError, "url_for_access_token must get a callback either from the OAuth object or in the parameters!" unless callback
185
- "https://#{GRAPH_SERVER}/oauth/access_token?client_id=#{@app_id}&redirect_uri=#{callback}&client_secret=#{@app_secret}&code=#{code}"
186
- end
187
-
188
- def get_access_token_info(code)
189
- # convenience method to get a parsed token from Facebook for a given code
190
- # should this require an OAuth callback URL?
191
- get_token_from_server(:code => code, :redirect_uri => @oauth_callback_url)
192
- end
193
-
194
- def get_access_token(code)
195
- # upstream methods will throw errors if needed
196
- if info = get_access_token_info(code)
197
- string = info["access_token"]
198
- end
199
- end
200
-
201
- def get_app_access_token_info
202
- # convenience method to get a the application's sessionless access token
203
- get_token_from_server({:type => 'client_cred'}, true)
204
- end
205
-
206
- def get_app_access_token
207
- if info = get_app_access_token_info
208
- string = info["access_token"]
209
- end
210
- end
211
-
212
- # provided directly by Facebook
213
- # see https://github.com/facebook/crypto-request-examples/blob/master/sample.rb
214
- # and http://developers.facebook.com/docs/authentication/canvas/encryption_proposal
215
- def parse_signed_request(input, max_age = 3600)
216
- encoded_sig, encoded_envelope = input.split('.', 2)
217
- envelope = JSON.parse(base64_url_decode(encoded_envelope))
218
- algorithm = envelope['algorithm']
219
-
220
- raise 'Invalid request. (Unsupported algorithm.)' \
221
- if algorithm != 'AES-256-CBC HMAC-SHA256' && algorithm != 'HMAC-SHA256'
222
-
223
- raise 'Invalid request. (Too old.)' \
224
- if algorithm == "AES-256-CBC HMAC-SHA256" && envelope['issued_at'].to_i < Time.now.to_i - max_age
225
-
226
- raise 'Invalid request. (Invalid signature.)' \
227
- if base64_url_decode(encoded_sig) !=
228
- OpenSSL::HMAC.hexdigest(
229
- 'sha256', @app_secret, encoded_envelope).split.pack('H*')
230
-
231
- # for requests that are signed, but not encrypted, we're done
232
- return envelope if algorithm == 'HMAC-SHA256'
233
-
234
- # otherwise, decrypt the payload
235
- cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
236
- cipher.decrypt
237
- cipher.key = @app_secret
238
- cipher.iv = base64_url_decode(envelope['iv'])
239
- cipher.padding = 0
240
- decrypted_data = cipher.update(base64_url_decode(envelope['payload']))
241
- decrypted_data << cipher.final
242
- return JSON.parse(decrypted_data.strip)
243
- end
244
-
245
- # from session keys
246
- def get_token_info_from_session_keys(sessions)
247
- # fetch the OAuth tokens from Facebook
248
- response = fetch_token_string({
249
- :type => 'client_cred',
250
- :sessions => sessions.join(",")
251
- }, true, "exchange_sessions")
252
-
253
- # Facebook returns an empty body in certain error conditions
254
- if response == ""
255
- raise APIError.new({
256
- "type" => "ArgumentError",
257
- "message" => "get_token_from_session_key received an error (empty response body) for sessions #{sessions.inspect}!"
258
- })
259
- end
260
-
261
- JSON.parse(response)
262
- end
263
-
264
- def get_tokens_from_session_keys(sessions)
265
- # get the original hash results
266
- results = get_token_info_from_session_keys(sessions)
267
- # now recollect them as just the access tokens
268
- results.collect { |r| r ? r["access_token"] : nil }
269
- end
270
-
271
- def get_token_from_session_key(session)
272
- # convenience method for a single key
273
- # gets the overlaoded strings automatically
274
- get_tokens_from_session_keys([session])[0]
275
- end
276
-
277
- protected
278
-
279
- def get_token_from_server(args, post = false)
280
- # fetch the result from Facebook's servers
281
- result = fetch_token_string(args, post)
282
-
283
- # if we have an error, parse the error JSON and raise an error
284
- raise APIError.new((JSON.parse(result)["error"] rescue nil) || {}) if result =~ /error/
285
-
286
- # otherwise, parse the access token
287
- parse_access_token(result)
288
- end
289
-
290
- def parse_access_token(response_text)
291
- components = response_text.split("&").inject({}) do |hash, bit|
292
- key, value = bit.split("=")
293
- hash.merge!(key => value)
294
- end
295
- components
296
- end
297
-
298
- def fetch_token_string(args, post = false, endpoint = "access_token")
299
- Koala.make_request("/oauth/#{endpoint}", {
300
- :client_id => @app_id,
301
- :client_secret => @app_secret
302
- }.merge!(args), post ? "post" : "get", :use_ssl => true).body
303
- end
304
-
305
- # base 64
306
- # directly from https://github.com/facebook/crypto-request-examples/raw/master/sample.rb
307
- def base64_url_decode(str)
308
- str += '=' * (4 - str.length.modulo(4))
309
- Base64.decode64(str.gsub('-', '+').gsub('_', '/'))
310
- end
311
- 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)
312
114
  end
313
115
 
314
116
  # finally, set up the http service Koala methods used to make requests
315
117
  # you can use your own (for HTTParty, etc.) by calling Koala.http_service = YourModule
316
- def self.http_service=(service)
317
- self.send(:include, service)
118
+ class << self
119
+ attr_accessor :http_service
120
+ attr_accessor :always_use_ssl
121
+ attr_accessor :base_http_service
318
122
  end
123
+ Koala.base_http_service = NetHTTPService
319
124
 
320
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)
321
128
  begin
129
+ require 'koala/http_services/typhoeus_service'
322
130
  Koala.http_service = TyphoeusService
323
131
  rescue LoadError
324
- Koala.http_service = NetHTTPService
132
+ Koala.http_service = Koala.base_http_service
325
133
  end
326
134
  end
data/readme.md CHANGED
@@ -1,27 +1,38 @@
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 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. (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
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
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")
35
+ feed = @graph.get_connections("me", "feed")
25
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]
@@ -34,20 +45,29 @@ When retrieving data that returns an array of results (for example, when calling
34
45
  # This is useful for paging across multiple requests
35
46
  next_path, next_args = feed.next_page_params
36
47
 
37
- # You can use those params to easily get the next (or prevous) page
38
- page = graph.get_page(feed.next_page_params)
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
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
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
51
71
 
52
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
 
@@ -60,7 +80,7 @@ You can use the Graph and REST APIs without an OAuth access token, but the real
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
82
  @oauth.get_user_from_cookies(cookies) # gets the user's ID
63
- @oauth.get_user_info_from_cookies(cookies) # parses and returns the entire hash
83
+ @oauth.get_user_info_from_cookies(cookies) # parses and returns the entire hash
64
84
 
65
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:
66
86
  # generate authenticating URL
@@ -71,20 +91,18 @@ And if you have to use the more complicated [redirect-based OAuth process](http:
71
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:
72
92
  @oauth.get_app_access_token
73
93
 
74
- 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).
75
-
76
- *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:
77
95
  @oauth.parse_signed_request(request)
78
96
 
79
- *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:
80
98
  @oauth.get_token_from_session_key(session_key)
81
99
  @oauth.get_tokens_from_session_keys(array_of_session_keys)
82
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
+
83
103
  Real-time Updates
84
104
  -----
85
- The Graph API now allows your application to subscribe to real-time updates for certain objects in the graph.
86
-
87
- 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.
88
106
 
89
107
  Koala makes it easy to interact with your applications using the RealtimeUpdates class:
90
108
 
@@ -108,6 +126,17 @@ And to top it all off, RealtimeUpdates provides a static method to respond to Fa
108
126
 
109
127
  For more information about meet_challenge and the RealtimeUpdates class, check out the Real-Time Updates page on the wiki.
110
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
+
111
140
  See examples, ask questions
112
141
  -----
113
142
  Some resources to help you as you play with Koala and the Graph API:
@@ -120,11 +149,14 @@ Testing
120
149
  -----
121
150
 
122
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:
123
- # From the spec directory
124
- spec koala_spec.rb
152
+
153
+ # From anywhere in the project directory:
154
+ bundle exec rake spec
155
+
125
156
 
126
157
  You can also run live tests against Facebook's servers:
127
- # Again from the spec directory
128
- spec koala_spec_without_mocks.rb
158
+
159
+ # Again from anywhere in the project directory:
160
+ LIVE=true bundle exec rake spec
129
161
 
130
- 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
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Koala::Facebook::GraphAndRestAPI" do
4
+ include LiveTestingDataHelper
5
+
6
+ describe "with an access token" do
7
+ before(:each) do
8
+ @api = Koala::Facebook::GraphAndRestAPI.new(@token)
9
+ end
10
+
11
+ it_should_behave_like "Koala RestAPI"
12
+ it_should_behave_like "Koala RestAPI with an access token"
13
+
14
+ it_should_behave_like "Koala GraphAPI"
15
+ it_should_behave_like "Koala GraphAPI with an access token"
16
+ it_should_behave_like "Koala GraphAPI with GraphCollection"
17
+ end
18
+
19
+ describe "without an access token" do
20
+ before(:each) do
21
+ @api = Koala::Facebook::GraphAndRestAPI.new
22
+ end
23
+
24
+ it_should_behave_like "Koala RestAPI"
25
+ it_should_behave_like "Koala RestAPI without an access token"
26
+
27
+ it_should_behave_like "Koala GraphAPI"
28
+ it_should_behave_like "Koala GraphAPI without an access token"
29
+ it_should_behave_like "Koala GraphAPI with GraphCollection"
30
+ end
31
+ end