koala 0.9.1 → 1.0.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 (52) hide show
  1. data/.gitignore +3 -0
  2. data/CHANGELOG +42 -7
  3. data/Gemfile +3 -0
  4. data/LICENSE +1 -1
  5. data/Manifest +10 -1
  6. data/Rakefile +13 -14
  7. data/koala.gemspec +36 -19
  8. data/lib/koala/graph_api.rb +188 -123
  9. data/lib/koala/http_services.rb +93 -18
  10. data/lib/koala/rest_api.rb +73 -6
  11. data/lib/koala/test_users.rb +85 -0
  12. data/lib/koala/uploadable_io.rb +115 -0
  13. data/lib/koala.rb +114 -116
  14. data/readme.md +32 -18
  15. data/spec/cases/api_base_spec.rb +101 -0
  16. data/spec/cases/graph_and_rest_api_spec.rb +31 -0
  17. data/spec/cases/graph_api_spec.rb +25 -0
  18. data/spec/cases/http_services/http_service_spec.rb +54 -0
  19. data/spec/cases/http_services/net_http_service_spec.rb +350 -0
  20. data/spec/cases/http_services/typhoeus_service_spec.rb +144 -0
  21. data/spec/cases/oauth_spec.rb +409 -0
  22. data/spec/cases/realtime_updates_spec.rb +184 -0
  23. data/spec/cases/rest_api_spec.rb +25 -0
  24. data/spec/cases/test_users_spec.rb +221 -0
  25. data/spec/cases/uploadable_io_spec.rb +151 -0
  26. data/spec/fixtures/beach.jpg +0 -0
  27. data/spec/{facebook_data.yml → fixtures/facebook_data.yml} +18 -14
  28. data/spec/{mock_facebook_responses.yml → fixtures/mock_facebook_responses.yml} +314 -241
  29. data/spec/spec_helper.rb +18 -0
  30. data/spec/support/graph_api_shared_examples.rb +424 -0
  31. data/spec/support/live_testing_data_helper.rb +40 -0
  32. data/spec/{mock_http_service.rb → support/mock_http_service.rb} +94 -80
  33. data/spec/support/rest_api_shared_examples.rb +161 -0
  34. data/spec/support/setup_mocks_or_live.rb +52 -0
  35. data/spec/support/uploadable_io_shared_examples.rb +76 -0
  36. metadata +131 -43
  37. data/init.rb +0 -2
  38. data/spec/koala/api_base_tests.rb +0 -96
  39. data/spec/koala/graph_and_rest_api/graph_and_rest_api_no_token_tests.rb +0 -10
  40. data/spec/koala/graph_and_rest_api/graph_and_rest_api_with_token_tests.rb +0 -11
  41. data/spec/koala/graph_api/graph_api_no_access_token_tests.rb +0 -114
  42. data/spec/koala/graph_api/graph_api_with_access_token_tests.rb +0 -150
  43. data/spec/koala/graph_api/graph_collection_tests.rb +0 -104
  44. data/spec/koala/live_testing_data_helper.rb +0 -23
  45. data/spec/koala/net_http_service_tests.rb +0 -186
  46. data/spec/koala/oauth/oauth_tests.rb +0 -433
  47. data/spec/koala/realtime_updates/realtime_updates_tests.rb +0 -187
  48. data/spec/koala/rest_api/rest_api_no_access_token_tests.rb +0 -94
  49. data/spec/koala/rest_api/rest_api_with_access_token_tests.rb +0 -36
  50. data/spec/koala_spec.rb +0 -18
  51. data/spec/koala_spec_helper.rb +0 -45
  52. data/spec/koala_spec_without_mocks.rb +0 -19
data/lib/koala.rb CHANGED
@@ -1,77 +1,60 @@
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
4
  require '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
15
12
  require 'koala/graph_api'
16
-
17
- # add REST API methods
18
13
  require 'koala/rest_api'
19
-
20
14
  require 'koala/realtime_updates'
15
+ require 'koala/test_users'
16
+
17
+ # add KoalaIO class
18
+ require 'koala/uploadable_io'
21
19
 
22
20
  module Koala
23
-
21
+
24
22
  module Facebook
25
23
  # 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/.
24
+ # Copyright 2010-2011 Alex Koppel
25
+ # Contributors: Alex Koppel, Chris Baclig, Rafi Jacoby, and the team at Context Optional
26
+ # http://github.com/arsduo/koala
45
27
 
46
28
  class API
47
- # initialize with an access token
29
+ # initialize with an access token
48
30
  def initialize(access_token = nil)
49
31
  @access_token = access_token
50
32
  end
51
-
33
+ attr_reader :access_token
34
+
52
35
  def api(path, args = {}, verb = "get", options = {}, &error_checking_block)
53
36
  # Fetches the given path in the Graph API.
54
37
  args["access_token"] = @access_token || @app_access_token if @access_token || @app_access_token
55
-
38
+
56
39
  # add a leading /
57
40
  path = "/#{path}" unless path =~ /^\//
58
41
 
59
42
  # make the request via the provided service
60
43
  result = Koala.make_request(path, args, verb, options)
61
-
44
+
62
45
  # Check for any 500 errors before parsing the body
63
46
  # since we're not guaranteed that the body is valid JSON
64
47
  # in the case of a server error
65
48
  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
49
+
50
+ # Parse the body as JSON and check for errors if provided a mechanism to do so
68
51
  # Note: Facebook sometimes sends results like "true" and "false", which aren't strictly objects
69
52
  # and cause JSON.parse to fail -- so we account for that by wrapping the result in []
70
53
  body = response = JSON.parse("[#{result.body.to_s}]")[0]
71
54
  if error_checking_block
72
55
  yield(body)
73
56
  end
74
-
57
+
75
58
  # now return the desired information
76
59
  if options[:http_component]
77
60
  result.send(options[:http_component])
@@ -80,51 +63,57 @@ module Koala
80
63
  end
81
64
  end
82
65
  end
83
-
66
+
84
67
  class GraphAPI < API
85
68
  include GraphAPIMethods
86
69
  end
87
-
70
+
88
71
  class RestAPI < API
89
72
  include RestAPIMethods
90
73
  end
91
-
74
+
92
75
  class GraphAndRestAPI < API
93
76
  include GraphAPIMethods
94
77
  include RestAPIMethods
95
78
  end
96
-
79
+
97
80
  class RealtimeUpdates < API
98
81
  include RealtimeUpdateMethods
99
82
  end
100
-
101
- class APIError < Exception
83
+
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
89
+
90
+ class APIError < StandardError
102
91
  attr_accessor :fb_error_type
103
92
  def initialize(details = {})
104
- self.fb_error_type = details["type"]
93
+ self.fb_error_type = details["type"]
105
94
  super("#{fb_error_type}: #{details["message"]}")
106
95
  end
107
96
  end
108
-
109
-
97
+
98
+
110
99
  class OAuth
111
100
  attr_reader :app_id, :app_secret, :oauth_callback_url
112
101
  def initialize(app_id, app_secret, oauth_callback_url = nil)
113
102
  @app_id = app_id
114
103
  @app_secret = app_secret
115
- @oauth_callback_url = oauth_callback_url
104
+ @oauth_callback_url = oauth_callback_url
116
105
  end
117
106
 
118
107
  def get_user_info_from_cookie(cookie_hash)
119
108
  # Parses the cookie set by the official Facebook JavaScript SDK.
120
- #
109
+ #
121
110
  # cookies should be a Hash, like the one Rails provides
122
- #
111
+ #
123
112
  # If the user is logged in via Facebook, we return a dictionary with the
124
113
  # keys "uid" and "access_token". The former is the user's Facebook ID,
125
114
  # and the latter can be used to make authenticated requests to the Graph API.
126
115
  # If the user is not logged in, we return None.
127
- #
116
+ #
128
117
  # Download the official Facebook JavaScript SDK at
129
118
  # http://github.com/facebook/connect-js/. Read more about Facebook
130
119
  # authentication at http://developers.facebook.com/docs/authentication/.
@@ -139,147 +128,155 @@ module Koala
139
128
 
140
129
  # generate the signature and make sure it matches what we expect
141
130
  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)
131
+ sig = Digest::MD5.hexdigest(auth_string + @app_secret)
143
132
  sig == components["sig"] && (components["expires"] == "0" || Time.now.to_i < components["expires"].to_i) ? components : nil
144
133
  end
145
134
  end
146
135
  alias_method :get_user_info_from_cookies, :get_user_info_from_cookie
147
-
136
+
148
137
  def get_user_from_cookie(cookies)
149
138
  if info = get_user_info_from_cookies(cookies)
150
139
  string = info["uid"]
151
140
  end
152
141
  end
153
142
  alias_method :get_user_from_cookies, :get_user_from_cookie
154
-
143
+
155
144
  # URLs
156
-
145
+
157
146
  def url_for_oauth_code(options = {})
158
147
  # for permissions, see http://developers.facebook.com/docs/authentication/permissions
159
148
  permissions = options[:permissions]
160
149
  scope = permissions ? "&scope=#{permissions.is_a?(Array) ? permissions.join(",") : permissions}" : ""
161
-
150
+ display = options.has_key?(:display) ? "&display=#{options[:display]}" : ""
151
+
162
152
  callback = options[:callback] || @oauth_callback_url
163
153
  raise ArgumentError, "url_for_oauth_code must get a callback either from the OAuth object or in the options!" unless callback
164
154
 
165
155
  # 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}"
156
+ "https://#{GRAPH_SERVER}/oauth/authorize?client_id=#{@app_id}&redirect_uri=#{callback}#{scope}#{display}"
167
157
  end
168
-
158
+
169
159
  def url_for_access_token(code, options = {})
170
160
  # Creates the URL for the token corresponding to a given code generated by Facebook
171
161
  callback = options[:callback] || @oauth_callback_url
172
162
  raise ArgumentError, "url_for_access_token must get a callback either from the OAuth object or in the parameters!" unless callback
173
163
  "https://#{GRAPH_SERVER}/oauth/access_token?client_id=#{@app_id}&redirect_uri=#{callback}&client_secret=#{@app_secret}&code=#{code}"
174
164
  end
175
-
176
- def get_access_token_info(code)
165
+
166
+ def get_access_token_info(code, options = {})
177
167
  # convenience method to get a parsed token from Facebook for a given code
178
168
  # should this require an OAuth callback URL?
179
- get_token_from_server(:code => code, :redirect_uri => @oauth_callback_url)
169
+ get_token_from_server({:code => code, :redirect_uri => @oauth_callback_url}, false, options)
180
170
  end
181
-
182
- def get_access_token(code)
171
+
172
+ def get_access_token(code, options = {})
183
173
  # upstream methods will throw errors if needed
184
- if info = get_access_token_info(code)
185
- string = info["access_token"]
174
+ if info = get_access_token_info(code, options)
175
+ string = info["access_token"]
186
176
  end
187
177
  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)
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)
192
182
  end
193
-
194
- def get_app_access_token
195
- if info = get_app_access_token_info
196
- string = info["access_token"]
183
+
184
+ def get_app_access_token(options = {})
185
+ if info = get_app_access_token_info(options)
186
+ string = info["access_token"]
197
187
  end
198
188
  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
189
 
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
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
214
207
  end
215
208
 
216
209
  # from session keys
217
- def get_token_info_from_session_keys(sessions)
210
+ def get_token_info_from_session_keys(sessions, options = {})
218
211
  # fetch the OAuth tokens from Facebook
219
212
  response = fetch_token_string({
220
213
  :type => 'client_cred',
221
214
  :sessions => sessions.join(",")
222
- }, true, "exchange_sessions")
223
-
215
+ }, true, "exchange_sessions", options)
216
+
224
217
  # 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}!")
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
+ })
227
223
  end
228
-
224
+
229
225
  JSON.parse(response)
230
226
  end
231
-
232
- def get_tokens_from_session_keys(sessions)
227
+
228
+ def get_tokens_from_session_keys(sessions, options = {})
233
229
  # get the original hash results
234
- results = get_token_info_from_session_keys(sessions)
230
+ results = get_token_info_from_session_keys(sessions, options)
235
231
  # now recollect them as just the access tokens
236
232
  results.collect { |r| r ? r["access_token"] : nil }
237
233
  end
238
-
239
- def get_token_from_session_key(session)
234
+
235
+ def get_token_from_session_key(session, options = {})
240
236
  # convenience method for a single key
241
237
  # gets the overlaoded strings automatically
242
- get_tokens_from_session_keys([session])[0]
238
+ get_tokens_from_session_keys([session], options)[0]
243
239
  end
244
-
240
+
245
241
  protected
246
-
247
- def get_token_from_server(args, post = false)
242
+
243
+ def get_token_from_server(args, post = false, options = {})
248
244
  # fetch the result from Facebook's servers
249
- result = fetch_token_string(args, post)
250
-
245
+ result = fetch_token_string(args, post, "access_token", options)
246
+
251
247
  # if we have an error, parse the error JSON and raise an error
252
248
  raise APIError.new((JSON.parse(result)["error"] rescue nil) || {}) if result =~ /error/
253
249
 
254
250
  # otherwise, parse the access token
255
- parse_access_token(result)
251
+ parse_access_token(result)
256
252
  end
257
-
253
+
258
254
  def parse_access_token(response_text)
259
255
  components = response_text.split("&").inject({}) do |hash, bit|
260
256
  key, value = bit.split("=")
261
257
  hash.merge!(key => value)
262
258
  end
263
- components
259
+ components
264
260
  end
265
261
 
266
- def fetch_token_string(args, post = false, endpoint = "access_token")
262
+ def fetch_token_string(args, post = false, endpoint = "access_token", options = {})
267
263
  Koala.make_request("/oauth/#{endpoint}", {
268
- :client_id => @app_id,
264
+ :client_id => @app_id,
269
265
  :client_secret => @app_secret
270
- }.merge!(args), post ? "post" : "get").body
266
+ }.merge!(args), post ? "post" : "get", {:use_ssl => true}.merge!(options)).body
271
267
  end
272
-
268
+
273
269
  # 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]
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('-_', '+/'))
279
274
  end
280
275
  end
281
276
  end
282
-
277
+
278
+ class KoalaError< StandardError; end
279
+
283
280
  # finally, set up the http service Koala methods used to make requests
284
281
  # you can use your own (for HTTParty, etc.) by calling Koala.http_service = YourModule
285
282
  def self.http_service=(service)
@@ -287,8 +284,9 @@ module Koala
287
284
  end
288
285
 
289
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)
290
289
  begin
291
- require 'typhoeus'
292
290
  Koala.http_service = TyphoeusService
293
291
  rescue LoadError
294
292
  Koala.http_service = NetHTTPService
data/readme.md CHANGED
@@ -1,28 +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 (<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:
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.)
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
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
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
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
+ 1.0
11
+ ---
12
+ Version 1.0 is due out on May 1st, 2011 with a ton of great features.
13
+
14
+ sudo gem install koala
15
+
16
+ Until then, you can install the release candidate like so:
17
+
18
+ sudo gem install koala --pre
19
+
10
20
  Graph API
11
21
  ----
12
- The Graph API is the simple, slick new interface to Facebook's data. Using it with Koala is quite straightforward:
22
+ The Graph API is the simple, slick new interface to Facebook's data. Using it with Koala is quite straightforward:
13
23
 
14
24
  graph = Koala::Facebook::GraphAPI.new(oauth_access_token)
15
25
  profile = graph.get_object("me")
16
26
  friends = graph.get_connections("me", "friends")
17
27
  graph.put_object("me", "feed", :message => "I am writing on my wall!")
18
28
 
19
- The response of most requests is the JSON data returned from the Facebook servers as a Hash.
29
+ The response of most requests is the JSON data returned from the Facebook servers as a Hash.
20
30
 
21
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:
22
-
32
+
23
33
  # Returns the feed items for the currently logged-in user as a GraphCollection
24
34
  feed = graph.get_connections("me", "feed")
25
-
35
+
26
36
  # GraphCollection is a sub-class of Array, so you can use it as a usual Array
27
37
  first_entry = feed[0]
28
38
  last_entry = feed.last
@@ -33,7 +43,7 @@ When retrieving data that returns an array of results (for example, when calling
33
43
  # Returns an array describing the URL for the next page: [path, arguments]
34
44
  # This is useful for paging across multiple requests
35
45
  next_path, next_args = feed.next_page_params
36
-
46
+
37
47
  # You can use those params to easily get the next (or prevous) page
38
48
  page = graph.get_page(feed.next_page_params)
39
49
 
@@ -41,25 +51,26 @@ Check out the wiki for more examples.
41
51
 
42
52
  The old-school REST API
43
53
  -----
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.
54
+ 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
55
 
46
56
  Koala now supports the old-school REST API using OAuth access tokens; to use this, instantiate your class using the RestAPI class:
47
57
 
48
58
  @rest = Koala::Facebook::RestAPI.new(oauth_access_token)
49
59
  @rest.fql_query(my_fql_query) # convenience method
50
60
  @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.
61
+
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.
53
63
 
54
64
  (If you want the power of both APIs in the palm of your hand, try out the GraphAndRestAPI class.)
55
65
 
56
66
  OAuth
57
67
  -----
58
68
  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)
69
+ @oauth = Koala::Facebook::OAuth.new(app_id, app_secret, callback_url)
60
70
 
61
71
  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)
72
+ @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
63
74
 
64
75
  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
76
  # generate authenticating URL
@@ -118,12 +129,15 @@ Some resources to help you as you play with Koala and the Graph API:
118
129
  Testing
119
130
  -----
120
131
 
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
132
+ 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
+
134
+ # From anywhere in the project directory:
135
+ rake spec
136
+
124
137
 
125
138
  You can also run live tests against Facebook's servers:
126
- # Again from the spec directory
127
- spec koala_spec_without_mocks.rb
139
+
140
+ # Again from anywhere in the project directory:
141
+ LIVE=true rake spec
128
142
 
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.)
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.)
@@ -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
+ JSON.stub(:parse).and_return([json_body])
56
+
57
+ @service.api('anything').should == json_body
58
+ end
59
+
60
+ it "should execute a block with the response body if passed one" 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') do |arg|
68
+ yield_test.pass
69
+ arg.should == JSON.parse(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
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Koala::Facebook::GraphAPI" do
4
+ include LiveTestingDataHelper
5
+
6
+ context "with an access token" do
7
+ before :each do
8
+ @api = Koala::Facebook::GraphAPI.new(@token)
9
+ end
10
+
11
+ it_should_behave_like "Koala GraphAPI"
12
+ it_should_behave_like "Koala GraphAPI with an access token"
13
+ it_should_behave_like "Koala GraphAPI with GraphCollection"
14
+ end
15
+
16
+ context "without an access token" do
17
+ before :each do
18
+ @api = Koala::Facebook::GraphAPI.new
19
+ end
20
+
21
+ it_should_behave_like "Koala GraphAPI"
22
+ it_should_behave_like "Koala GraphAPI without an access token"
23
+ it_should_behave_like "Koala GraphAPI with GraphCollection"
24
+ end
25
+ end