koala 1.0.0 → 1.2.0beta1

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 (52) hide show
  1. data/.autotest +12 -0
  2. data/.gitignore +3 -1
  3. data/.travis.yml +9 -0
  4. data/CHANGELOG +52 -2
  5. data/Gemfile +8 -0
  6. data/Rakefile +0 -1
  7. data/autotest/discover.rb +1 -0
  8. data/koala.gemspec +14 -14
  9. data/lib/koala/batch_operation.rb +74 -0
  10. data/lib/koala/graph_api.rb +157 -133
  11. data/lib/koala/graph_batch_api.rb +87 -0
  12. data/lib/koala/graph_collection.rb +54 -0
  13. data/lib/koala/http_service.rb +177 -0
  14. data/lib/koala/oauth.rb +181 -0
  15. data/lib/koala/realtime_updates.rb +23 -29
  16. data/lib/koala/rest_api.rb +13 -8
  17. data/lib/koala/test_users.rb +33 -16
  18. data/lib/koala/uploadable_io.rb +152 -87
  19. data/lib/koala/utils.rb +11 -0
  20. data/lib/koala.rb +54 -217
  21. data/readme.md +71 -52
  22. data/spec/cases/api_base_spec.rb +6 -6
  23. data/spec/cases/error_spec.rb +32 -0
  24. data/spec/cases/graph_and_rest_api_spec.rb +20 -3
  25. data/spec/cases/graph_api_batch_spec.rb +600 -0
  26. data/spec/cases/graph_api_spec.rb +21 -4
  27. data/spec/cases/http_service_spec.rb +446 -0
  28. data/spec/cases/koala_spec.rb +50 -0
  29. data/spec/cases/oauth_spec.rb +220 -201
  30. data/spec/cases/realtime_updates_spec.rb +45 -31
  31. data/spec/cases/rest_api_spec.rb +23 -7
  32. data/spec/cases/test_users_spec.rb +112 -52
  33. data/spec/cases/uploadable_io_spec.rb +92 -37
  34. data/spec/cases/utils_spec.rb +10 -0
  35. data/spec/fixtures/cat.m4v +0 -0
  36. data/spec/fixtures/facebook_data.yml +23 -22
  37. data/spec/fixtures/mock_facebook_responses.yml +201 -76
  38. data/spec/spec_helper.rb +30 -5
  39. data/spec/support/graph_api_shared_examples.rb +134 -56
  40. data/spec/support/json_testing_fix.rb +42 -0
  41. data/spec/support/koala_test.rb +163 -0
  42. data/spec/support/mock_http_service.rb +60 -57
  43. data/spec/support/ordered_hash.rb +205 -0
  44. data/spec/support/rest_api_shared_examples.rb +139 -15
  45. data/spec/support/uploadable_io_shared_examples.rb +2 -8
  46. metadata +98 -112
  47. data/lib/koala/http_services.rb +0 -146
  48. data/spec/cases/http_services/http_service_spec.rb +0 -54
  49. data/spec/cases/http_services/net_http_service_spec.rb +0 -350
  50. data/spec/cases/http_services/typhoeus_service_spec.rb +0 -144
  51. data/spec/support/live_testing_data_helper.rb +0 -40
  52. data/spec/support/setup_mocks_or_live.rb +0 -52
data/lib/koala.rb CHANGED
@@ -1,18 +1,23 @@
1
1
  require 'cgi'
2
2
  require 'digest/md5'
3
3
 
4
- require 'json'
4
+ require 'multi_json'
5
5
 
6
6
  # OpenSSL and Base64 are required to support signed_request
7
7
  require 'openssl'
8
8
  require 'base64'
9
9
 
10
10
  # include koala modules
11
- require 'koala/http_services'
11
+ require 'koala/http_service'
12
+ require 'koala/oauth'
12
13
  require 'koala/graph_api'
14
+ require 'koala/graph_batch_api'
15
+ require 'koala/batch_operation'
16
+ require 'koala/graph_collection'
13
17
  require 'koala/rest_api'
14
18
  require 'koala/realtime_updates'
15
19
  require 'koala/test_users'
20
+ require 'koala/utils'
16
21
 
17
22
  # add KoalaIO class
18
23
  require 'koala/uploadable_io'
@@ -25,6 +30,7 @@ module Koala
25
30
  # Contributors: Alex Koppel, Chris Baclig, Rafi Jacoby, and the team at Context Optional
26
31
  # http://github.com/arsduo/koala
27
32
 
33
+ # APIs
28
34
  class API
29
35
  # initialize with an access token
30
36
  def initialize(access_token = nil)
@@ -32,10 +38,13 @@ module Koala
32
38
  end
33
39
  attr_reader :access_token
34
40
 
41
+ include GraphAPIMethods
42
+ include RestAPIMethods
43
+
35
44
  def api(path, args = {}, verb = "get", options = {}, &error_checking_block)
36
45
  # Fetches the given path in the Graph API.
37
46
  args["access_token"] = @access_token || @app_access_token if @access_token || @app_access_token
38
-
47
+
39
48
  # add a leading /
40
49
  path = "/#{path}" unless path =~ /^\//
41
50
 
@@ -47,45 +56,42 @@ module Koala
47
56
  # in the case of a server error
48
57
  raise APIError.new({"type" => "HTTP #{result.status.to_s}", "message" => "Response body: #{result.body}"}) if result.status >= 500
49
58
 
50
- # Parse the body as JSON and check for errors if provided a mechanism to do so
59
+ # parse the body as JSON and run it through the error checker (if provided)
51
60
  # Note: Facebook sometimes sends results like "true" and "false", which aren't strictly objects
52
- # and cause JSON.parse to fail -- so we account for that by wrapping the result in []
53
- body = response = JSON.parse("[#{result.body.to_s}]")[0]
54
- if error_checking_block
55
- yield(body)
56
- end
61
+ # and cause MultiJson.decode to fail -- so we account for that by wrapping the result in []
62
+ body = MultiJson.decode("[#{result.body.to_s}]")[0]
63
+ yield body if error_checking_block
57
64
 
58
- # now return the desired information
59
- if options[:http_component]
60
- result.send(options[:http_component])
61
- else
62
- body
63
- end
65
+ # if we want a component other than the body (e.g. redirect header for images), return that
66
+ options[:http_component] ? result.send(options[:http_component]) : body
64
67
  end
65
68
  end
66
69
 
67
- class GraphAPI < API
68
- include GraphAPIMethods
70
+ # special enhanced APIs
71
+ class GraphBatchAPI < API
72
+ include GraphBatchAPIMethods
69
73
  end
70
74
 
71
- class RestAPI < API
72
- include RestAPIMethods
75
+ class RealtimeUpdates
76
+ include RealtimeUpdateMethods
73
77
  end
74
78
 
75
- class GraphAndRestAPI < API
76
- include GraphAPIMethods
77
- include RestAPIMethods
79
+ class TestUsers
80
+ include TestUserMethods
78
81
  end
79
82
 
80
- class RealtimeUpdates < API
81
- include RealtimeUpdateMethods
83
+ # legacy support for old APIs
84
+ class OldAPI < API;
85
+ def initialize(*args)
86
+ Koala::Utils.deprecate("#{self.class.name} is deprecated and will be removed in a future version; please use the API class instead.")
87
+ super
88
+ end
82
89
  end
90
+ class GraphAPI < OldAPI; end
91
+ class RestAPI < OldAPI; end
92
+ class GraphAndRestAPI < OldAPI; end
83
93
 
84
- class TestUsers < API
85
- include TestUserMethods
86
- # make the Graph API accessible in case someone wants to make other calls to interact with their users
87
- attr_reader :graph_api
88
- end
94
+ # Errors
89
95
 
90
96
  class APIError < StandardError
91
97
  attr_accessor :fb_error_type
@@ -94,201 +100,32 @@ module Koala
94
100
  super("#{fb_error_type}: #{details["message"]}")
95
101
  end
96
102
  end
103
+ end
97
104
 
105
+ class KoalaError < StandardError; end
98
106
 
99
- class OAuth
100
- attr_reader :app_id, :app_secret, :oauth_callback_url
101
- def initialize(app_id, app_secret, oauth_callback_url = nil)
102
- @app_id = app_id
103
- @app_secret = app_secret
104
- @oauth_callback_url = oauth_callback_url
105
- end
106
-
107
- def get_user_info_from_cookie(cookie_hash)
108
- # Parses the cookie set by the official Facebook JavaScript SDK.
109
- #
110
- # cookies should be a Hash, like the one Rails provides
111
- #
112
- # If the user is logged in via Facebook, we return a dictionary with the
113
- # keys "uid" and "access_token". The former is the user's Facebook ID,
114
- # and the latter can be used to make authenticated requests to the Graph API.
115
- # If the user is not logged in, we return None.
116
- #
117
- # Download the official Facebook JavaScript SDK at
118
- # http://github.com/facebook/connect-js/. Read more about Facebook
119
- # authentication at http://developers.facebook.com/docs/authentication/.
120
-
121
- if fb_cookie = cookie_hash["fbs_" + @app_id.to_s]
122
- # remove the opening/closing quote
123
- fb_cookie = fb_cookie.gsub(/\"/, "")
124
-
125
- # since we no longer get individual cookies, we have to separate out the components ourselves
126
- components = {}
127
- fb_cookie.split("&").map {|param| param = param.split("="); components[param[0]] = param[1]}
128
-
129
- # generate the signature and make sure it matches what we expect
130
- auth_string = components.keys.sort.collect {|a| a == "sig" ? nil : "#{a}=#{components[a]}"}.reject {|a| a.nil?}.join("")
131
- sig = Digest::MD5.hexdigest(auth_string + @app_secret)
132
- sig == components["sig"] && (components["expires"] == "0" || Time.now.to_i < components["expires"].to_i) ? components : nil
133
- end
134
- end
135
- alias_method :get_user_info_from_cookies, :get_user_info_from_cookie
136
-
137
- def get_user_from_cookie(cookies)
138
- if info = get_user_info_from_cookies(cookies)
139
- string = info["uid"]
140
- end
141
- end
142
- alias_method :get_user_from_cookies, :get_user_from_cookie
143
-
144
- # URLs
145
-
146
- def url_for_oauth_code(options = {})
147
- # for permissions, see http://developers.facebook.com/docs/authentication/permissions
148
- permissions = options[:permissions]
149
- scope = permissions ? "&scope=#{permissions.is_a?(Array) ? permissions.join(",") : permissions}" : ""
150
- display = options.has_key?(:display) ? "&display=#{options[:display]}" : ""
151
-
152
- callback = options[:callback] || @oauth_callback_url
153
- raise ArgumentError, "url_for_oauth_code must get a callback either from the OAuth object or in the options!" unless callback
154
-
155
- # Creates the URL for oauth authorization for a given callback and optional set of permissions
156
- "https://#{GRAPH_SERVER}/oauth/authorize?client_id=#{@app_id}&redirect_uri=#{callback}#{scope}#{display}"
157
- end
158
-
159
- def url_for_access_token(code, options = {})
160
- # Creates the URL for the token corresponding to a given code generated by Facebook
161
- callback = options[:callback] || @oauth_callback_url
162
- raise ArgumentError, "url_for_access_token must get a callback either from the OAuth object or in the parameters!" unless callback
163
- "https://#{GRAPH_SERVER}/oauth/access_token?client_id=#{@app_id}&redirect_uri=#{callback}&client_secret=#{@app_secret}&code=#{code}"
164
- end
165
-
166
- def get_access_token_info(code, options = {})
167
- # convenience method to get a parsed token from Facebook for a given code
168
- # should this require an OAuth callback URL?
169
- get_token_from_server({:code => code, :redirect_uri => @oauth_callback_url}, false, options)
170
- end
171
-
172
- def get_access_token(code, options = {})
173
- # upstream methods will throw errors if needed
174
- if info = get_access_token_info(code, options)
175
- string = info["access_token"]
176
- end
177
- end
178
-
179
- def get_app_access_token_info(options = {})
180
- # convenience method to get a the application's sessionless access token
181
- get_token_from_server({:type => 'client_cred'}, true, options)
182
- end
183
-
184
- def get_app_access_token(options = {})
185
- if info = get_app_access_token_info(options)
186
- string = info["access_token"]
187
- end
188
- end
189
-
190
- # Originally provided directly by Facebook, however this has changed
191
- # as their concept of crypto changed. For historic purposes, this is their proposal:
192
- # https://developers.facebook.com/docs/authentication/canvas/encryption_proposal/
193
- # Currently see https://github.com/facebook/php-sdk/blob/master/src/facebook.php#L758
194
- # for a more accurate reference implementation strategy.
195
- def parse_signed_request(input)
196
- encoded_sig, encoded_envelope = input.split('.', 2)
197
- signature = base64_url_decode(encoded_sig).unpack("H*").first
198
- envelope = JSON.parse(base64_url_decode(encoded_envelope))
199
-
200
- raise "SignedRequest: Unsupported algorithm #{envelope['algorithm']}" if envelope['algorithm'] != 'HMAC-SHA256'
201
-
202
- # now see if the signature is valid (digest, key, data)
203
- hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, @app_secret, encoded_envelope.tr("-_", "+/"))
204
- raise 'SignedRequest: Invalid signature' if (signature != hmac)
205
-
206
- return envelope
207
- end
208
-
209
- # from session keys
210
- def get_token_info_from_session_keys(sessions, options = {})
211
- # fetch the OAuth tokens from Facebook
212
- response = fetch_token_string({
213
- :type => 'client_cred',
214
- :sessions => sessions.join(",")
215
- }, true, "exchange_sessions", options)
216
-
217
- # Facebook returns an empty body in certain error conditions
218
- if response == ""
219
- raise APIError.new({
220
- "type" => "ArgumentError",
221
- "message" => "get_token_from_session_key received an error (empty response body) for sessions #{sessions.inspect}!"
222
- })
223
- end
224
-
225
- JSON.parse(response)
226
- end
227
-
228
- def get_tokens_from_session_keys(sessions, options = {})
229
- # get the original hash results
230
- results = get_token_info_from_session_keys(sessions, options)
231
- # now recollect them as just the access tokens
232
- results.collect { |r| r ? r["access_token"] : nil }
233
- end
234
-
235
- def get_token_from_session_key(session, options = {})
236
- # convenience method for a single key
237
- # gets the overlaoded strings automatically
238
- get_tokens_from_session_keys([session], options)[0]
239
- end
240
-
241
- protected
242
-
243
- def get_token_from_server(args, post = false, options = {})
244
- # fetch the result from Facebook's servers
245
- result = fetch_token_string(args, post, "access_token", options)
246
-
247
- # if we have an error, parse the error JSON and raise an error
248
- raise APIError.new((JSON.parse(result)["error"] rescue nil) || {}) if result =~ /error/
249
-
250
- # otherwise, parse the access token
251
- parse_access_token(result)
252
- end
253
-
254
- def parse_access_token(response_text)
255
- components = response_text.split("&").inject({}) do |hash, bit|
256
- key, value = bit.split("=")
257
- hash.merge!(key => value)
258
- end
259
- components
260
- end
261
-
262
- def fetch_token_string(args, post = false, endpoint = "access_token", options = {})
263
- Koala.make_request("/oauth/#{endpoint}", {
264
- :client_id => @app_id,
265
- :client_secret => @app_secret
266
- }.merge!(args), post ? "post" : "get", {:use_ssl => true}.merge!(options)).body
267
- end
268
107
 
269
- # base 64
270
- # directly from https://github.com/facebook/crypto-request-examples/raw/master/sample.rb
271
- def base64_url_decode(str)
272
- str += '=' * (4 - str.length.modulo(4))
273
- Base64.decode64(str.tr('-_', '+/'))
274
- end
275
- end
108
+ # finally, the few things defined on the Koala module itself
109
+ class << self
110
+ attr_accessor :http_service
276
111
  end
277
112
 
278
- class KoalaError< StandardError; end
279
-
280
- # finally, set up the http service Koala methods used to make requests
281
- # you can use your own (for HTTParty, etc.) by calling Koala.http_service = YourModule
282
113
  def self.http_service=(service)
283
- self.send(:include, service)
114
+ if service.respond_to?(:deprecated_interface)
115
+ # if this is a deprecated module, support the old interface
116
+ # by changing the default adapter so the right library is used
117
+ # we continue to use the single HTTPService module for everything
118
+ service.deprecated_interface
119
+ else
120
+ # if it's a real http_service, use it
121
+ @http_service = service
122
+ end
284
123
  end
285
124
 
286
- # by default, try requiring Typhoeus -- if that works, use it
287
- # if you have Typheous and don't want to use it (or want another service),
288
- # you can run Koala.http_service = NetHTTPService (or MyHTTPService)
289
- begin
290
- Koala.http_service = TyphoeusService
291
- rescue LoadError
292
- Koala.http_service = NetHTTPService
125
+ def self.make_request(path, args, verb, options = {})
126
+ http_service.make_request(path, args, verb, options)
293
127
  end
128
+
129
+ # we use Faraday as our main service, with mock as the other main one
130
+ self.http_service = HTTPService
294
131
  end
data/readme.md CHANGED
@@ -1,67 +1,77 @@
1
+ [![Build Status](https://secure.travis-ci.org/arsduo/koala.png)](http://travis-ci.org/arsduo/koala)
2
+
1
3
  Koala
2
4
  ====
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 (including photo uploads), the old REST API, realtime updates, and OAuth validation. We wrote Koala with four goals:
5
+ [Koala](http://github.com/arsduo/koala) is a 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
6
 
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.)
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.)
7
+ * Lightweight: Koala should be as light and simple as Facebook’s own libraries, providing API accessors and returning simple JSON.
8
+ * Fast: Koala should, out of the box, be quick. Out of the box, we use Facebook's faster read-only servers when possible and if available, the Typhoeus gem to make snappy Facebook requests. Of course, that brings us to our next topic:
9
+ * Flexible: Koala should be useful to everyone, regardless of their current configuration. (We support JRuby, Rubinius, and REE as well as vanilla Ruby, and use the Faraday library to provide complete flexibility over how HTTP requests are made.)
10
+ * Tested: Koala should have complete test coverage, so you can rely on it. (Our test coverage is complete and can be run against either mocked responses or the live Facebook servers.)
9
11
 
10
- 1.0
12
+ Installation
11
13
  ---
12
- Version 1.0 is due out on May 1st, 2011 with a ton of great features.
13
-
14
- sudo gem install koala
15
14
 
16
- Until then, you can install the release candidate like so:
17
-
18
- sudo gem install koala --pre
19
-
15
+ Easy:
16
+
17
+ [sudo|rvm] gem install koala --pre # for 1.2 beta
18
+ [sudo|rvm] gem install koala # for 1.1
19
+
20
+ Or in Bundler:
21
+
22
+ gem "koala", "~> 1.2.0beta"
23
+ gem "koala" # for 1.1
24
+
20
25
  Graph API
21
26
  ----
22
27
  The Graph API is the simple, slick new interface to Facebook's data. Using it with Koala is quite straightforward:
23
28
 
24
- graph = Koala::Facebook::GraphAPI.new(oauth_access_token)
25
- profile = graph.get_object("me")
26
- friends = graph.get_connections("me", "friends")
27
- graph.put_object("me", "feed", :message => "I am writing on my wall!")
29
+ @graph = Koala::Facebook::API.new(oauth_access_token)
30
+ profile = @graph.get_object("me")
31
+ friends = @graph.get_connections("me", "friends")
32
+ @graph.put_object("me", "feed", :message => "I am writing on my wall!")
28
33
 
29
34
  The response of most requests is the JSON data returned from the Facebook servers as a Hash.
30
35
 
31
- 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:
36
+ When retrieving data that returns an array of results (for example, when calling API#get_connections or API#search) a GraphCollection object will be returned, which makes it easy to page through the results:
32
37
 
33
38
  # Returns the feed items for the currently logged-in user as a GraphCollection
34
- feed = graph.get_connections("me", "feed")
35
-
36
- # GraphCollection is a sub-class of Array, so you can use it as a usual Array
37
- first_entry = feed[0]
38
- last_entry = feed.last
39
-
40
- # Returns the next page of results (also as a GraphCollection)
39
+ feed = @graph.get_connections("me", "feed")
40
+ feed.each {|f| do_something_with_item(f) } # it's a subclass of Array
41
41
  next_feed = feed.next_page
42
42
 
43
- # Returns an array describing the URL for the next page: [path, arguments]
44
- # This is useful for paging across multiple requests
45
- next_path, next_args = feed.next_page_params
43
+ # You can also get an array describing the URL for the next page: [path, arguments]
44
+ # This is useful for storing page state across multiple browser requests
45
+ next_page_params = feed.next_page_params
46
+ page = @graph.get_page(next_page_params)
47
+
48
+ You can also make multiple calls at once using Facebook's batch API:
46
49
 
47
- # You can use those params to easily get the next (or prevous) page
48
- page = graph.get_page(feed.next_page_params)
50
+ # Returns an array of results as if they were called non-batch
51
+ @graph.batch do |batch_api|
52
+ batch_api.get_object('me')
53
+ batch_api.put_wall_post('Making a post in a batch.')
54
+ end
49
55
 
50
- Check out the wiki for more examples.
56
+ Check out the wiki for more details and examples.
51
57
 
52
- The old-school REST API
58
+ The REST API
53
59
  -----
54
60
  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.
55
61
 
56
- Koala now supports the old-school REST API using OAuth access tokens; to use this, instantiate your class using the RestAPI class:
62
+ Fortunately, Koala supports the REST API using the very same interface; to use this, instantiate an API:
57
63
 
58
- @rest = Koala::Facebook::RestAPI.new(oauth_access_token)
59
- @rest.fql_query(my_fql_query) # convenience method
60
- @rest.rest_call("stream.publish", arguments_hash) # generic version
64
+ @rest = Koala::Facebook::API.new(oauth_access_token)
65
+ @rest.fql_query(my_fql_query) # convenience method
66
+ @rest.fql_multiquery(fql_query_hash) # convenience method
67
+ @rest.rest_call("stream.publish", arguments_hash) # generic version
61
68
 
62
- 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.
69
+ Of course, you can use the Graph API methods on the same object -- the power of two APIs right in the palm of your hand.
63
70
 
64
- (If you want the power of both APIs in the palm of your hand, try out the GraphAndRestAPI class.)
71
+ @api = Koala::Facebook::API.new(oauth_access_token)
72
+ fql = @api.fql_query(my_fql_query)
73
+ @api.put_wall_post(process_result(fql))
74
+
65
75
 
66
76
  OAuth
67
77
  -----
@@ -70,7 +80,7 @@ You can use the Graph and REST APIs without an OAuth access token, but the real
70
80
 
71
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:
72
82
  @oauth.get_user_from_cookies(cookies) # gets the user's ID
73
- @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
74
84
 
75
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:
76
86
  # generate authenticating URL
@@ -81,20 +91,18 @@ And if you have to use the more complicated [redirect-based OAuth process](http:
81
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:
82
92
  @oauth.get_app_access_token
83
93
 
84
- 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).
85
-
86
- *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:
87
95
  @oauth.parse_signed_request(request)
88
96
 
89
- *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:
90
98
  @oauth.get_token_from_session_key(session_key)
91
99
  @oauth.get_tokens_from_session_keys(array_of_session_keys)
92
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
+
93
103
  Real-time Updates
94
104
  -----
95
- The Graph API now allows your application to subscribe to real-time updates for certain objects in the graph.
96
-
97
- 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.
98
106
 
99
107
  Koala makes it easy to interact with your applications using the RealtimeUpdates class:
100
108
 
@@ -118,6 +126,17 @@ And to top it all off, RealtimeUpdates provides a static method to respond to Fa
118
126
 
119
127
  For more information about meet_challenge and the RealtimeUpdates class, check out the Real-Time Updates page on the wiki.
120
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::API.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
+
121
140
  See examples, ask questions
122
141
  -----
123
142
  Some resources to help you as you play with Koala and the Graph API:
@@ -130,14 +149,14 @@ Testing
130
149
  -----
131
150
 
132
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:
133
-
152
+
134
153
  # From anywhere in the project directory:
135
- rake spec
136
-
154
+ bundle exec rake spec
155
+
137
156
 
138
157
  You can also run live tests against Facebook's servers:
139
-
158
+
140
159
  # Again from anywhere in the project directory:
141
- LIVE=true rake spec
160
+ LIVE=true bundle exec rake spec
142
161
 
143
- 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 thisdata 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
+ By default, the live tests are run against test users, so you can run them as frequently as you want. If you want to run them against a real user, however, you can fill in the OAuth token, code, and access\_token values in spec/fixtures/facebook_data.yml. See the wiki for more details.
@@ -44,7 +44,7 @@ describe "Koala::Facebook::API" do
44
44
 
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)
48
48
  end
49
49
 
50
50
  it "should return the body of the request as JSON if no http_component is given" do
@@ -52,21 +52,21 @@ describe "Koala::Facebook::API" do
52
52
  Koala.stub(:make_request).and_return(response)
53
53
 
54
54
  json_body = mock('JSON body')
55
- JSON.stub(:parse).and_return([json_body])
55
+ MultiJson.stub(:decode).and_return([json_body])
56
56
 
57
57
  @service.api('anything').should == json_body
58
58
  end
59
59
 
60
- it "should execute a block with the response body if passed one" do
60
+ it "should execute an error checking block if provided" do
61
61
  body = '{}'
62
62
  Koala.stub(:make_request).and_return(Koala::Response.new(200, body, {}))
63
63
 
64
64
  yield_test = mock('Yield Tester')
65
65
  yield_test.should_receive(:pass)
66
66
 
67
- @service.api('anything') do |arg|
67
+ @service.api('anything', {}, "get") do |arg|
68
68
  yield_test.pass
69
- arg.should == JSON.parse(body)
69
+ arg.should == MultiJson.decode(body)
70
70
  end
71
71
  end
72
72
 
@@ -98,4 +98,4 @@ describe "Koala::Facebook::API" do
98
98
  end
99
99
  end
100
100
 
101
- end
101
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe Koala::Facebook::APIError do
4
+ it "is a StandardError" do
5
+ Koala::Facebook::APIError.new.should be_a(StandardError)
6
+ end
7
+
8
+ it "has an accessor for fb_error_type" do
9
+ Koala::Facebook::APIError.instance_methods.map(&:to_sym).should include(:fb_error_type)
10
+ Koala::Facebook::APIError.instance_methods.map(&:to_sym).should include(:fb_error_type=)
11
+ end
12
+
13
+ it "sets fb_error_type to details['type']" do
14
+ type = "foo"
15
+ Koala::Facebook::APIError.new("type" => type).fb_error_type.should == type
16
+ end
17
+
18
+ it "sets the error message details['type']: details['message']" do
19
+ type = "foo"
20
+ message = "bar"
21
+ error = Koala::Facebook::APIError.new("type" => type, "message" => message)
22
+ error.message.should =~ /#{type}/
23
+ error.message.should =~ /#{message}/
24
+ end
25
+ end
26
+
27
+ describe Koala::KoalaError do
28
+ it "is a StandardError" do
29
+ Koala::KoalaError.new.should be_a(StandardError)
30
+ end
31
+ end
32
+
@@ -1,11 +1,28 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe "Koala::Facebook::GraphAndRestAPI" do
4
- include LiveTestingDataHelper
4
+ describe "class consolidation" do
5
+ before :each do
6
+ Koala::Utils.stub(:deprecate) # avoid actual messages to stderr
7
+ end
8
+
9
+ it "still allows you to instantiate a GraphAndRestAPI object" do
10
+ api = Koala::Facebook::GraphAndRestAPI.new("token").should be_a(Koala::Facebook::GraphAndRestAPI)
11
+ end
12
+
13
+ it "ultimately creates an API object" do
14
+ api = Koala::Facebook::GraphAndRestAPI.new("token").should be_a(Koala::Facebook::API)
15
+ end
16
+
17
+ it "fires a depreciation warning" do
18
+ Koala::Utils.should_receive(:deprecate)
19
+ api = Koala::Facebook::GraphAndRestAPI.new("token")
20
+ end
21
+ end
5
22
 
6
23
  describe "with an access token" do
7
24
  before(:each) do
8
- @api = Koala::Facebook::GraphAndRestAPI.new(@token)
25
+ @api = Koala::Facebook::API.new(@token)
9
26
  end
10
27
 
11
28
  it_should_behave_like "Koala RestAPI"
@@ -18,7 +35,7 @@ describe "Koala::Facebook::GraphAndRestAPI" do
18
35
 
19
36
  describe "without an access token" do
20
37
  before(:each) do
21
- @api = Koala::Facebook::GraphAndRestAPI.new
38
+ @api = Koala::Facebook::API.new
22
39
  end
23
40
 
24
41
  it_should_behave_like "Koala RestAPI"