joelind-koala 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +88 -0
- data/LICENSE +22 -0
- data/Manifest +29 -0
- data/Rakefile +17 -0
- data/init.rb +2 -0
- data/joelind-koala.gemspec +30 -0
- data/koala.gemspec +30 -0
- data/lib/koala/graph_api.rb +131 -0
- data/lib/koala/http_services.rb +70 -0
- data/lib/koala/realtime_updates.rb +95 -0
- data/lib/koala/rest_api.rb +23 -0
- data/lib/koala.rb +292 -0
- data/readme.md +104 -0
- data/spec/facebook_data.yml +44 -0
- data/spec/koala/api_base_tests.rb +80 -0
- data/spec/koala/graph_and_rest_api/graph_and_rest_api_no_token_tests.rb +10 -0
- data/spec/koala/graph_and_rest_api/graph_and_rest_api_with_token_tests.rb +11 -0
- data/spec/koala/graph_api/graph_api_no_access_token_tests.rb +105 -0
- data/spec/koala/graph_api/graph_api_with_access_token_tests.rb +139 -0
- data/spec/koala/live_testing_data_helper.rb +15 -0
- data/spec/koala/net_http_service_tests.rb +181 -0
- data/spec/koala/oauth/oauth_tests.rb +308 -0
- data/spec/koala/realtime_updates/realtime_updates_tests.rb +187 -0
- data/spec/koala/rest_api/rest_api_no_access_token_tests.rb +94 -0
- data/spec/koala/rest_api/rest_api_with_access_token_tests.rb +36 -0
- data/spec/koala_spec.rb +18 -0
- data/spec/koala_spec_helper.rb +30 -0
- data/spec/koala_spec_without_mocks.rb +19 -0
- data/spec/mock_facebook_responses.yml +228 -0
- data/spec/mock_http_service.rb +81 -0
- metadata +103 -0
data/lib/koala.rb
ADDED
@@ -0,0 +1,292 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'digest/md5'
|
3
|
+
|
4
|
+
# rubygems is required to support json, how facebook returns data
|
5
|
+
require 'rubygems'
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
# openssl is required to support signed_request
|
9
|
+
require 'openssl'
|
10
|
+
|
11
|
+
# include default http services
|
12
|
+
require 'koala/http_services'
|
13
|
+
|
14
|
+
# add Graph API methods
|
15
|
+
require 'koala/graph_api'
|
16
|
+
|
17
|
+
# add REST API methods
|
18
|
+
require 'koala/rest_api'
|
19
|
+
|
20
|
+
require 'koala/realtime_updates'
|
21
|
+
|
22
|
+
module Koala
|
23
|
+
|
24
|
+
module Facebook
|
25
|
+
# 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/.
|
45
|
+
|
46
|
+
class API
|
47
|
+
# initialize with an access token
|
48
|
+
def initialize(access_token = nil)
|
49
|
+
@access_token = access_token
|
50
|
+
end
|
51
|
+
|
52
|
+
def api(path, args = {}, verb = "get", options = {}, &error_checking_block)
|
53
|
+
# Fetches the given path in the Graph API.
|
54
|
+
args["access_token"] = @access_token || @app_access_token if @access_token || @app_access_token
|
55
|
+
# make the request via the provided service
|
56
|
+
result = Koala.make_request(path, args, verb, options)
|
57
|
+
|
58
|
+
# Check for any 500 errors before parsing the body
|
59
|
+
# since we're not guaranteed that the body is valid JSON
|
60
|
+
# in the case of a server error
|
61
|
+
raise APIError.new({"type" => "HTTP #{result.status.to_s}", "message" => "Response body: #{result.body}"}) if result.status >= 500
|
62
|
+
|
63
|
+
# Parse the body as JSON and check for errors if provided a mechanism to do so
|
64
|
+
# Note: Facebook sometimes sends results like "true" and "false", which aren't strictly objects
|
65
|
+
# and cause JSON.parse to fail -- so we account for that by wrapping the result in []
|
66
|
+
body = response = JSON.parse("[#{result.body.to_s}]")[0]
|
67
|
+
if error_checking_block
|
68
|
+
yield(body)
|
69
|
+
end
|
70
|
+
|
71
|
+
# now return the desired information
|
72
|
+
if options[:http_component]
|
73
|
+
result.send(options[:http_component])
|
74
|
+
else
|
75
|
+
body
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class GraphAPI < API
|
81
|
+
include GraphAPIMethods
|
82
|
+
end
|
83
|
+
|
84
|
+
class RestAPI < API
|
85
|
+
include RestAPIMethods
|
86
|
+
end
|
87
|
+
|
88
|
+
class GraphAndRestAPI < API
|
89
|
+
include GraphAPIMethods
|
90
|
+
include RestAPIMethods
|
91
|
+
end
|
92
|
+
|
93
|
+
class RealtimeUpdates < API
|
94
|
+
include RealtimeUpdateMethods
|
95
|
+
end
|
96
|
+
|
97
|
+
class APIError < Exception
|
98
|
+
attr_accessor :fb_error_type
|
99
|
+
def initialize(details = {})
|
100
|
+
self.fb_error_type = details["type"]
|
101
|
+
super("#{fb_error_type}: #{details["message"]}")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
class OAuth
|
107
|
+
attr_reader :app_id, :app_secret, :oauth_callback_url
|
108
|
+
def initialize(app_id, app_secret, oauth_callback_url = nil)
|
109
|
+
@app_id = app_id
|
110
|
+
@app_secret = app_secret
|
111
|
+
@oauth_callback_url = oauth_callback_url
|
112
|
+
end
|
113
|
+
|
114
|
+
def get_user_info_from_cookie(cookie_hash)
|
115
|
+
# Parses the cookie set by the official Facebook JavaScript SDK.
|
116
|
+
#
|
117
|
+
# cookies should be a Hash, like the one Rails provides
|
118
|
+
#
|
119
|
+
# If the user is logged in via Facebook, we return a dictionary with the
|
120
|
+
# keys "uid" and "access_token". The former is the user's Facebook ID,
|
121
|
+
# and the latter can be used to make authenticated requests to the Graph API.
|
122
|
+
# If the user is not logged in, we return None.
|
123
|
+
#
|
124
|
+
# Download the official Facebook JavaScript SDK at
|
125
|
+
# http://github.com/facebook/connect-js/. Read more about Facebook
|
126
|
+
# authentication at http://developers.facebook.com/docs/authentication/.
|
127
|
+
|
128
|
+
if fb_cookie = cookie_hash["fbs_" + @app_id.to_s]
|
129
|
+
# remove the opening/closing quote
|
130
|
+
fb_cookie = fb_cookie.gsub(/\"/, "")
|
131
|
+
|
132
|
+
# since we no longer get individual cookies, we have to separate out the components ourselves
|
133
|
+
components = {}
|
134
|
+
fb_cookie.split("&").map {|param| param = param.split("="); components[param[0]] = param[1]}
|
135
|
+
|
136
|
+
# generate the signature and make sure it matches what we expect
|
137
|
+
auth_string = components.keys.sort.collect {|a| a == "sig" ? nil : "#{a}=#{components[a]}"}.reject {|a| a.nil?}.join("")
|
138
|
+
sig = Digest::MD5.hexdigest(auth_string + @app_secret)
|
139
|
+
sig == components["sig"] && (components["expires"] == "0" || Time.now.to_i < components["expires"].to_i) ? components : nil
|
140
|
+
end
|
141
|
+
end
|
142
|
+
alias_method :get_user_info_from_cookies, :get_user_info_from_cookie
|
143
|
+
|
144
|
+
def get_user_from_cookie(cookies)
|
145
|
+
if info = get_user_info_from_cookies(cookies)
|
146
|
+
string = info["uid"]
|
147
|
+
end
|
148
|
+
end
|
149
|
+
alias_method :get_user_from_cookies, :get_user_from_cookie
|
150
|
+
|
151
|
+
# URLs
|
152
|
+
|
153
|
+
def url_for_oauth_code(options = {})
|
154
|
+
# for permissions, see http://developers.facebook.com/docs/authentication/permissions
|
155
|
+
permissions = options[:permissions]
|
156
|
+
scope = permissions ? "&scope=#{permissions.is_a?(Array) ? permissions.join(",") : permissions}" : ""
|
157
|
+
|
158
|
+
callback = options[:callback] || @oauth_callback_url
|
159
|
+
raise ArgumentError, "url_for_oauth_code must get a callback either from the OAuth object or in the options!" unless callback
|
160
|
+
|
161
|
+
# Creates the URL for oauth authorization for a given callback and optional set of permissions
|
162
|
+
"https://#{GRAPH_SERVER}/oauth/authorize?client_id=#{@app_id}&redirect_uri=#{callback}#{scope}"
|
163
|
+
end
|
164
|
+
|
165
|
+
def url_for_access_token(code, options = {})
|
166
|
+
# Creates the URL for the token corresponding to a given code generated by Facebook
|
167
|
+
callback = options[:callback] || @oauth_callback_url
|
168
|
+
raise ArgumentError, "url_for_access_token must get a callback either from the OAuth object or in the parameters!" unless callback
|
169
|
+
"https://#{GRAPH_SERVER}/oauth/access_token?client_id=#{@app_id}&redirect_uri=#{callback}&client_secret=#{@app_secret}&code=#{code}"
|
170
|
+
end
|
171
|
+
|
172
|
+
def get_access_token_info(code)
|
173
|
+
# convenience method to get a parsed token from Facebook for a given code
|
174
|
+
# should this require an OAuth callback URL?
|
175
|
+
get_token_from_server(:code => code, :redirect_uri => @oauth_callback_url)
|
176
|
+
end
|
177
|
+
|
178
|
+
def get_access_token(code)
|
179
|
+
# upstream methods will throw errors if needed
|
180
|
+
if info = get_access_token_info(code)
|
181
|
+
string = info["access_token"]
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def get_app_access_token_info
|
186
|
+
# convenience method to get a the application's sessionless access token
|
187
|
+
get_token_from_server({:type => 'client_cred'}, true)
|
188
|
+
end
|
189
|
+
|
190
|
+
def get_app_access_token
|
191
|
+
if info = get_app_access_token_info
|
192
|
+
string = info["access_token"]
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# signed_request
|
197
|
+
def parse_signed_request(request)
|
198
|
+
# Facebook's signed requests come in two parts -- the signature and the data payload
|
199
|
+
encoded_sig, payload = request.split(".")
|
200
|
+
|
201
|
+
sig = base64_url_decode(encoded_sig)
|
202
|
+
|
203
|
+
# if the signature matches, return the data, decoded and parsed as JSON
|
204
|
+
if OpenSSL::HMAC.digest("sha256", @app_secret, payload) == sig
|
205
|
+
JSON.parse(base64_url_decode(payload))
|
206
|
+
else
|
207
|
+
nil
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
# from session keys
|
212
|
+
def get_token_info_from_session_keys(sessions)
|
213
|
+
# fetch the OAuth tokens from Facebook
|
214
|
+
response = fetch_token_string({
|
215
|
+
:type => 'client_cred',
|
216
|
+
:sessions => sessions.join(",")
|
217
|
+
}, true, "exchange_sessions")
|
218
|
+
|
219
|
+
# get_token_from_session_key should return an empty body if an empty string or nil is provided
|
220
|
+
# if invalid tokens are provided, it returns an array of nulls, which is a valid result
|
221
|
+
if response == ""
|
222
|
+
raise APIError.new("ArgumentError", "get_token_from_session_key received an error (empty response body) for sessions #{sessions.inspect}!")
|
223
|
+
end
|
224
|
+
|
225
|
+
JSON.parse(response)
|
226
|
+
end
|
227
|
+
|
228
|
+
def get_tokens_from_session_keys(sessions)
|
229
|
+
# get the original hash results
|
230
|
+
results = get_token_info_from_session_keys(sessions)
|
231
|
+
# now recollect them as just the access tokens
|
232
|
+
results.collect { |r| string = r["access_token"] }
|
233
|
+
end
|
234
|
+
|
235
|
+
def get_token_from_session_key(session)
|
236
|
+
# convenience method for a single key
|
237
|
+
# gets the overlaoded strings automatically
|
238
|
+
get_tokens_from_session_keys([session])[0]
|
239
|
+
end
|
240
|
+
|
241
|
+
protected
|
242
|
+
|
243
|
+
def get_token_from_server(args, post = false)
|
244
|
+
# fetch the result from Facebook's servers
|
245
|
+
result = fetch_token_string(args, post)
|
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")
|
263
|
+
Koala.make_request("oauth/#{endpoint}", {
|
264
|
+
:client_id => @app_id,
|
265
|
+
:client_secret => @app_secret
|
266
|
+
}.merge!(args), post ? "post" : "get").body
|
267
|
+
end
|
268
|
+
|
269
|
+
# base 64
|
270
|
+
def base64_url_decode(string)
|
271
|
+
# to properly decode what Facebook provides, we need to add == to the end
|
272
|
+
# and translate certain characters to others before running the actual decoding
|
273
|
+
# see http://developers.facebook.com/docs/authentication/canvas
|
274
|
+
"#{string}==".tr("-_", "+/").unpack("m")[0]
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
# finally, set up the http service Koala methods used to make requests
|
280
|
+
# you can use your own (for HTTParty, etc.) by calling Koala.http_service = YourModule
|
281
|
+
def self.http_service=(service)
|
282
|
+
self.send(:include, service)
|
283
|
+
end
|
284
|
+
|
285
|
+
# by default, try requiring Typhoeus -- if that works, use it
|
286
|
+
begin
|
287
|
+
require 'typhoeus'
|
288
|
+
Koala.http_service = TyphoeusService
|
289
|
+
rescue LoadError
|
290
|
+
Koala.http_service = NetHTTPService
|
291
|
+
end
|
292
|
+
end
|
data/readme.md
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
Koala
|
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:
|
4
|
+
|
5
|
+
* Lightweight: Koala should be as light and simple as Facebook’s own new libraries, providing API accessors and returning simple JSON. (We clock in, with comments, just over 500 lines of code.)
|
6
|
+
* Fast: Koala should, out of the box, be quick. In addition to supporting the vanilla Ruby networking libraries, it natively supports Typhoeus, our preferred gem for making fast HTTP requests. Of course, That brings us to our next topic:
|
7
|
+
* Flexible: Koala should be useful to everyone, regardless of their current configuration. (We have no dependencies beyond the JSON gem. Koala also has a built-in mechanism for using whichever HTTP library you prefer to make requests against the graph.)
|
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
|
+
|
10
|
+
Graph API
|
11
|
+
----
|
12
|
+
The Graph API is the simple, slick new interface to Facebook's data. Using it with Koala is quite straightforward:
|
13
|
+
graph = Koala::Facebook::GraphAPI.new(oauth_access_token)
|
14
|
+
profile = graph.get_object("me")
|
15
|
+
friends = graph.get_connection("me", "friends")
|
16
|
+
graph.put_object("me", "feed", :message => "I am writing on my wall!")
|
17
|
+
|
18
|
+
Check out the wiki for more examples.
|
19
|
+
|
20
|
+
The old-school REST API
|
21
|
+
-----
|
22
|
+
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 -- including fql.query -- that can't yet be done via the Graph.
|
23
|
+
|
24
|
+
Koala now supports the old-school REST API using OAuth access tokens; to use this, instantiate your class using the RestAPI class:
|
25
|
+
|
26
|
+
@rest = Koala::Facebook::RestAPI.new(oauth_access_token)
|
27
|
+
@rest.fql_query(my_fql_query) # convenience method
|
28
|
+
@rest.rest_call("stream.publish", arguments_hash) # generic version
|
29
|
+
|
30
|
+
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.
|
31
|
+
|
32
|
+
(If you want the power of both APIs in the palm of your hand, try out the GraphAndRestAPI class.)
|
33
|
+
|
34
|
+
OAuth
|
35
|
+
-----
|
36
|
+
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:
|
37
|
+
@oauth = Koala::Facebook::OAuth.new(app_id, code, callback_url)
|
38
|
+
|
39
|
+
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:
|
40
|
+
@oauth.get_user_from_cookie(cookies)
|
41
|
+
|
42
|
+
And if you have to use the more complicated [redirect-based OAuth process](http://developers.facebook.com/docs/authentication/), Koala helps out there, too:
|
43
|
+
# generate authenticating URL
|
44
|
+
@oauth.url_for_oauth_code
|
45
|
+
# fetch the access token once you have the code
|
46
|
+
@oauth.get_access_token(code)
|
47
|
+
|
48
|
+
You can also get your application's own access token, which can be used without a user session for subscriptions and certain other requests:
|
49
|
+
@oauth.get_app_access_token
|
50
|
+
|
51
|
+
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).
|
52
|
+
|
53
|
+
*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:
|
54
|
+
@oauth.get_token_from_session_key(session_key)
|
55
|
+
@oauth.get_tokens_from_session_keys(array_of_session_keys)
|
56
|
+
|
57
|
+
Real-time Updates
|
58
|
+
-----
|
59
|
+
The Graph API now allows your application to subscribe to real-time updates for certain objects in the graph.
|
60
|
+
|
61
|
+
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.
|
62
|
+
|
63
|
+
Koala makes it easy to interact with your applications using the RealTimeUpdates class:
|
64
|
+
|
65
|
+
@updates = Koala::Facebook::RealTimeUpdates.new(:app_id => app_id, :secret => secret)
|
66
|
+
|
67
|
+
You can do just about anything with your real-time update subscriptions using the RealTimeUpdates class:
|
68
|
+
|
69
|
+
# Add/modify a subscription to updates for when the first_name or last_name fields of any of your users is changed
|
70
|
+
@updates.subscribe("user", "first_name, last_name", callback_token, verify_token)
|
71
|
+
|
72
|
+
# Get an array of your current subscriptions (one hash for each object you've subscribed to)
|
73
|
+
@updates.list_subscriptions
|
74
|
+
|
75
|
+
# Unsubscribe from updates for an object
|
76
|
+
@updates.unsubscribe("user")
|
77
|
+
|
78
|
+
And to top it all off, RealTimeUpdates provides a static method to respond to Facebook servers' verification of your callback URLs:
|
79
|
+
|
80
|
+
# Returns the hub.challenge parameter in params if the verify token in params matches verify_token
|
81
|
+
Koala::Facebook::RealTimeUpdates.meet_challenge(params, your_verify_token)
|
82
|
+
|
83
|
+
For more information about meet_challenge and the RealTimeUpdates class, check out the Real-Time Updates page on the wiki.
|
84
|
+
|
85
|
+
See examples, ask questions
|
86
|
+
-----
|
87
|
+
Some resources to help you as you play with Koala and the Graph API:
|
88
|
+
|
89
|
+
* Complete Koala documentation <a href="http://wiki.github.com/arsduo/koala/">on the wiki</a>
|
90
|
+
* The <a href="http://groups.google.com/group/koala-users">Koala users group</a> on Google Groups, the place for your Koala and API questions
|
91
|
+
* The Koala-powered <a href="http://oauth.twoalex.com" target="_blank">OAuth Playground</a>, where you can easily generate OAuth access tokens and any other data needed to test out the APIs or OAuth
|
92
|
+
|
93
|
+
Testing
|
94
|
+
-----
|
95
|
+
|
96
|
+
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:
|
97
|
+
# From the spec directory
|
98
|
+
spec koala_spec.rb
|
99
|
+
|
100
|
+
You can also run live tests against Facebook's servers:
|
101
|
+
# Again from the spec directory
|
102
|
+
spec koala_spec_without_mocks.rb
|
103
|
+
|
104
|
+
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.)
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# Check out http://oauth.twoalex.com/ to easily generate tokens, cookies, etc.
|
2
|
+
# Those values will work with the default settings in this yaml.
|
3
|
+
# Of course, you can change this to work with your own app.
|
4
|
+
# Just remember to update all fields!
|
5
|
+
|
6
|
+
# You must supply this value yourself to test the GraphAPI class.
|
7
|
+
# Your OAuth token should have publish_stream and read_stream permissions.
|
8
|
+
oauth_token:
|
9
|
+
|
10
|
+
# for testing the OAuth class
|
11
|
+
# baseline app
|
12
|
+
oauth_test_data:
|
13
|
+
# You must supply this value yourself, since they will expire.
|
14
|
+
code:
|
15
|
+
# easiest way to get session keys: use multiple test accounts with the Javascript login at http://oauth.twoalex.com
|
16
|
+
session_key:
|
17
|
+
multiple_session_keys:
|
18
|
+
-
|
19
|
+
-
|
20
|
+
|
21
|
+
# These values will work out of the box
|
22
|
+
app_id: 119908831367602
|
23
|
+
secret: e45e55a333eec232d4206d2703de1307
|
24
|
+
callback_url: http://oauth.twoalex.com/
|
25
|
+
app_access_token: 119908831367602|o3wswWQ88LYjEC9-ukR_gjRIOMw.
|
26
|
+
raw_token_string: "access_token=119908831367602|2.6GneoQbnEqtSiPppZzDU4Q__.3600.1273366800-2905623|3OLa3w0x1K4C1S5cOgbs07TytAk.&expires=6621"
|
27
|
+
raw_offline_access_token_string: access_token=119908831367602|2.6GneoQbnEqtSiPppZzDU4Q__.3600.1273366800-2905623|3OLa3w0x1K4C1S5cOgbs07TytAk.
|
28
|
+
valid_cookies:
|
29
|
+
# note: the tests stub the time class so these default cookies are always valid (if you're using the default app)
|
30
|
+
# if not you may want to remove the stubbing to test expiration
|
31
|
+
fbs_119908831367602: '"access_token=119908831367602|2.LKE7ksSPOx0V_8mHPr2NHQ__.3600.1273363200-2905623|CMpi0AYbn03Oukzv94AUha2qbO4.&expires=1273363200&secret=lT_9zm5r5IbJ6Aa5O54nFw__&session_key=2.LKE7ksSPOx0V_8mHPr2NHQ__.3600.1273363200-2905623&sig=9515e93113921f9476a4efbdd4a3c746&uid=2905623"'
|
32
|
+
expired_cookies:
|
33
|
+
fbs_119908831367602: '"access_token=119908831367602|2.xv9mi6QSOpr474s4n2X_pw__.3600.1273287600-2905623|yVt5WH_S6J5p3gFa5_5lBzckhws.&expires=1273287600&secret=V_E79ovQnXqxGctFuC_n5A__&session_key=2.xv9mi6QSOpr474s4n2X_pw__.3600.1273287600-2905623&sig=eeef60838c0c800258d89b7e6ddddddb&uid=2905623"'
|
34
|
+
offline_access_cookies:
|
35
|
+
# note: I've revoked the offline access for security reasons, so you can't make calls against this :)
|
36
|
+
fbs_119908831367602: '"access_token=119908831367602|08170230801eb225068e7a70-2905623|Q3LDCYYF8CX9cstxnZLsxiR0nwg.&expires=0&secret=78abaee300b392e275072a9f2727d436&session_key=08170230801eb225068e7a70-2905623&sig=423b8aa4b6fa1f9a571955f8e929d567&uid=2905623"'
|
37
|
+
|
38
|
+
subscription_test_data:
|
39
|
+
subscription_path: http://oauth.twoalex.com/subscriptions
|
40
|
+
verify_token: "myverificationtoken|1f54545d5f722733e17faae15377928f"
|
41
|
+
challenge_data:
|
42
|
+
"hub.challenge": "1290024882"
|
43
|
+
"hub.verify_token": "myverificationtoken|1f54545d5f722733e17faae15377928f"
|
44
|
+
"hub.mode": "subscribe"
|
@@ -0,0 +1,80 @@
|
|
1
|
+
class ApiBaseTests < Test::Unit::TestCase
|
2
|
+
describe "Koala API base class" do
|
3
|
+
before(:each) do
|
4
|
+
@service = Koala::Facebook::API.new
|
5
|
+
end
|
6
|
+
|
7
|
+
it "should not include an access token if none was given" do
|
8
|
+
Koala.should_receive(:make_request).with(
|
9
|
+
anything,
|
10
|
+
hash_not_including('access_token' => 1),
|
11
|
+
anything,
|
12
|
+
anything
|
13
|
+
).and_return(Koala::Response.new(200, "", ""))
|
14
|
+
|
15
|
+
@service.api('anything')
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should include an access token if given" do
|
19
|
+
token = 'adfadf'
|
20
|
+
service = Koala::Facebook::API.new token
|
21
|
+
|
22
|
+
Koala.should_receive(:make_request).with(
|
23
|
+
anything,
|
24
|
+
hash_including('access_token' => token),
|
25
|
+
anything,
|
26
|
+
anything
|
27
|
+
).and_return(Koala::Response.new(200, "", ""))
|
28
|
+
|
29
|
+
service.api('anything')
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should get the attribute of a Koala::Response given by the http_component parameter" do
|
33
|
+
http_component = :method_name
|
34
|
+
|
35
|
+
response = mock('Mock KoalaResponse', :body => '', :status => 200)
|
36
|
+
response.should_receive(http_component).and_return('')
|
37
|
+
|
38
|
+
Koala.stub(:make_request).and_return(response)
|
39
|
+
|
40
|
+
@service.api('anything', 'get', {}, :http_component => http_component)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should return the body of the request as JSON if no http_component is given" do
|
44
|
+
response = stub('response', :body => 'body', :status => 200)
|
45
|
+
Koala.stub(:make_request).and_return(response)
|
46
|
+
|
47
|
+
json_body = mock('JSON body')
|
48
|
+
JSON.stub(:parse).and_return([json_body])
|
49
|
+
|
50
|
+
@service.api('anything').should == json_body
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should execute a block with the response body if passed one" do
|
54
|
+
body = '{}'
|
55
|
+
Koala.stub(:make_request).and_return(Koala::Response.new(200, body, {}))
|
56
|
+
|
57
|
+
yield_test = mock('Yield Tester')
|
58
|
+
yield_test.should_receive(:pass)
|
59
|
+
|
60
|
+
@service.api('anything') do |arg|
|
61
|
+
yield_test.pass
|
62
|
+
arg.should == JSON.parse(body)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should raise an API error if the HTTP response code is greater than or equal to 500" do
|
67
|
+
Koala.stub(:make_request).and_return(Koala::Response.new(500, 'response body', {}))
|
68
|
+
|
69
|
+
lambda { @service.api('anything') }.should raise_exception(Koala::Facebook::APIError)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should handle rogue true/false as responses" do
|
73
|
+
Koala.should_receive(:make_request).and_return(Koala::Response.new(200, 'true', {}))
|
74
|
+
@service.api('anything').should be_true
|
75
|
+
|
76
|
+
Koala.should_receive(:make_request).and_return(Koala::Response.new(200, 'false', {}))
|
77
|
+
@service.api('anything').should be_false
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class GraphAndRestAPINoTokenTests < Test::Unit::TestCase
|
2
|
+
describe "Koala GraphAndRestAPI without an access token" do
|
3
|
+
before(:each) do
|
4
|
+
@api = Koala::Facebook::GraphAndRestAPI.new
|
5
|
+
end
|
6
|
+
|
7
|
+
it_should_behave_like "Koala RestAPI without an access token"
|
8
|
+
it_should_behave_like "Koala GraphAPI without an access token"
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class GraphAndRestAPIWithTokenTests < Test::Unit::TestCase
|
2
|
+
describe "Koala GraphAndRestAPI without an access token" do
|
3
|
+
it_should_behave_like "live testing examples"
|
4
|
+
it_should_behave_like "Koala RestAPI with an access token"
|
5
|
+
it_should_behave_like "Koala GraphAPI with an access token"
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
@api = Koala::Facebook::GraphAndRestAPI.new(@token)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
shared_examples_for "Koala GraphAPI without an access token" do
|
2
|
+
it "should get public data about a user" do
|
3
|
+
result = @api.get_object("koppel")
|
4
|
+
# the results should have an ID and a name, among other things
|
5
|
+
(result["id"] && result["name"]).should
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should not get private data about a user" do
|
9
|
+
result = @api.get_object("koppel")
|
10
|
+
# updated_time should be a pretty fixed test case
|
11
|
+
result["updated_time"].should be_nil
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should get public data about a Page" do
|
15
|
+
result = @api.get_object("contextoptional")
|
16
|
+
# the results should have an ID and a name, among other things
|
17
|
+
(result["id"] && result["name"]).should
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should not be able to get data about 'me'" do
|
21
|
+
lambda { @api.get_object("me") }.should raise_error(Koala::Facebook::APIError)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should be able to get multiple objects" do
|
25
|
+
results = @api.get_objects(["contextoptional", "naitik"])
|
26
|
+
results.length.should == 2
|
27
|
+
end
|
28
|
+
|
29
|
+
it "shouldn't be able to access connections from users" do
|
30
|
+
lambda { @api.get_connections("lukeshepard", "likes") }.should raise_error(Koala::Facebook::APIError)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should be able to access a user's picture" do
|
34
|
+
@api.get_picture("chris.baclig").should =~ /http\:\/\//
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should be able to access a user's picture, given a picture type" do
|
38
|
+
@api.get_picture("chris.baclig", {:type => 'large'}).should =~ /^http\:\/\//
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should be able to access connections from public Pages" do
|
42
|
+
result = @api.get_connections("contextoptional", "likes")
|
43
|
+
result.should be_a(Array)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should not be able to put an object" do
|
47
|
+
lambda { @result = @api.put_object("lukeshepard", "feed", :message => "Hello, world") }.should raise_error(Koala::Facebook::APIError)
|
48
|
+
puts "Error! Object #{@result.inspect} somehow put onto Luke Shepard's wall!" if @result
|
49
|
+
end
|
50
|
+
|
51
|
+
# these are not strictly necessary as the other put methods resolve to put_object, but are here for completeness
|
52
|
+
it "should not be able to post to a feed" do
|
53
|
+
(lambda do
|
54
|
+
attachment = {:name => "Context Optional", :link => "http://www.contextoptional.com/"}
|
55
|
+
@result = @api.put_wall_post("Hello, world", attachment, "contextoptional")
|
56
|
+
end).should raise_error(Koala::Facebook::APIError)
|
57
|
+
puts "Error! Object #{@result.inspect} somehow put onto Context Optional's wall!" if @result
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should not be able to comment on an object" do
|
61
|
+
# random public post on the ContextOptional wall
|
62
|
+
lambda { @result = @api.put_comment("7204941866_119776748033392", "The hackathon was great!") }.should raise_error(Koala::Facebook::APIError)
|
63
|
+
puts "Error! Object #{@result.inspect} somehow commented on post 7204941866_119776748033392!" if @result
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should not be able to like an object" do
|
67
|
+
lambda { @api.put_like("7204941866_119776748033392") }.should raise_error(Koala::Facebook::APIError)
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
# DELETE
|
72
|
+
it "should not be able to delete posts" do
|
73
|
+
# test post on the Ruby SDK Test application
|
74
|
+
lambda { @result = @api.delete_object("115349521819193_113815981982767") }.should raise_error(Koala::Facebook::APIError)
|
75
|
+
end
|
76
|
+
|
77
|
+
# SEARCH
|
78
|
+
it "should be able to search" do
|
79
|
+
result = @api.search("facebook")
|
80
|
+
result["data"].should be_an(Array)
|
81
|
+
end
|
82
|
+
|
83
|
+
# API
|
84
|
+
it "should never use the rest api server" do
|
85
|
+
Koala.should_receive(:make_request).with(
|
86
|
+
anything,
|
87
|
+
anything,
|
88
|
+
anything,
|
89
|
+
hash_not_including(:rest_api => true)
|
90
|
+
).and_return(Koala::Response.new(200, "", {}))
|
91
|
+
|
92
|
+
@api.api("anything")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
class FacebookNoAccessTokenTests < Test::Unit::TestCase
|
97
|
+
describe "Koala GraphAPI without an access token" do
|
98
|
+
before :each do
|
99
|
+
@api = Koala::Facebook::GraphAPI.new
|
100
|
+
end
|
101
|
+
|
102
|
+
it_should_behave_like "Koala GraphAPI without an access token"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|