koala 0.4 → 3.7.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 (71) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/test.yml +32 -0
  3. data/.gitignore +9 -0
  4. data/.rspec +1 -0
  5. data/.yardopts +3 -0
  6. data/Gemfile +25 -0
  7. data/ISSUE_TEMPLATE +25 -0
  8. data/LICENSE +22 -0
  9. data/Manifest +32 -5
  10. data/PULL_REQUEST_TEMPLATE +11 -0
  11. data/Rakefile +12 -12
  12. data/changelog.md +781 -0
  13. data/code_of_conduct.md +74 -0
  14. data/koala.gemspec +28 -24
  15. data/lib/koala/api/batch_operation.rb +86 -0
  16. data/lib/koala/api/graph_api_methods.rb +504 -0
  17. data/lib/koala/api/graph_batch_api.rb +167 -0
  18. data/lib/koala/api/graph_collection.rb +129 -0
  19. data/lib/koala/api/graph_error_checker.rb +72 -0
  20. data/lib/koala/api.rb +159 -0
  21. data/lib/koala/configuration.rb +56 -0
  22. data/lib/koala/errors.rb +126 -0
  23. data/lib/koala/http_service/request.rb +133 -0
  24. data/lib/koala/http_service/response.rb +20 -0
  25. data/lib/koala/http_service/uploadable_io.rb +183 -0
  26. data/lib/koala/http_service.rb +108 -0
  27. data/lib/koala/oauth.rb +342 -0
  28. data/lib/koala/realtime_updates.rb +151 -0
  29. data/lib/koala/test_users.rb +189 -0
  30. data/lib/koala/utils.rb +41 -0
  31. data/lib/koala/version.rb +3 -0
  32. data/lib/koala.rb +51 -291
  33. data/readme.md +269 -21
  34. data/spec/cases/api_spec.rb +362 -0
  35. data/spec/cases/configuration_spec.rb +11 -0
  36. data/spec/cases/error_spec.rb +143 -0
  37. data/spec/cases/graph_api_batch_spec.rb +788 -0
  38. data/spec/cases/graph_api_spec.rb +76 -0
  39. data/spec/cases/graph_collection_spec.rb +192 -0
  40. data/spec/cases/graph_error_checker_spec.rb +147 -0
  41. data/spec/cases/http_service/request_spec.rb +250 -0
  42. data/spec/cases/http_service/response_spec.rb +24 -0
  43. data/spec/cases/http_service_spec.rb +280 -0
  44. data/spec/cases/koala_spec.rb +57 -0
  45. data/spec/cases/koala_test_spec.rb +5 -0
  46. data/spec/cases/oauth_spec.rb +647 -0
  47. data/spec/cases/realtime_updates_spec.rb +327 -0
  48. data/spec/cases/test_users_spec.rb +383 -0
  49. data/spec/cases/uploadable_io_spec.rb +266 -0
  50. data/spec/cases/utils_spec.rb +55 -0
  51. data/spec/fixtures/beach.jpg +0 -0
  52. data/spec/fixtures/cat.m4v +0 -0
  53. data/spec/fixtures/facebook_data.yml +63 -0
  54. data/spec/fixtures/mock_facebook_responses.yml +483 -0
  55. data/spec/fixtures/vcr_cassettes/app_test_accounts.yml +97 -0
  56. data/spec/fixtures/vcr_cassettes/friend_list_next_page.yml +121 -0
  57. data/spec/integration/graph_collection_spec.rb +24 -0
  58. data/spec/spec_helper.rb +25 -0
  59. data/spec/support/custom_matchers.rb +28 -0
  60. data/spec/support/graph_api_shared_examples.rb +534 -0
  61. data/spec/support/koala_test.rb +251 -0
  62. data/spec/support/mock_http_service.rb +140 -0
  63. data/spec/support/uploadable_io_shared_examples.rb +70 -0
  64. metadata +206 -62
  65. data/CHANGELOG +0 -24
  66. data/init.rb +0 -2
  67. data/lib/http_services.rb +0 -60
  68. data/test/facebook_data.yml +0 -5
  69. data/test/koala/facebook_no_access_token_tests.rb +0 -119
  70. data/test/koala/facebook_with_access_token_tests.rb +0 -106
  71. data/test/koala_tests.rb +0 -30
@@ -0,0 +1,647 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Koala::Facebook::OAuth" do
4
+ before :all do
5
+ # make the relevant test data easily accessible
6
+ @app_id = KoalaTest.app_id
7
+ @secret = KoalaTest.secret
8
+ @code = KoalaTest.code
9
+ @callback_url = KoalaTest.oauth_test_data["callback_url"]
10
+ @access_token = KoalaTest.oauth_test_data["access_token"]
11
+ @raw_token_string = KoalaTest.oauth_test_data["raw_token_string"]
12
+ @raw_offline_access_token_string = KoalaTest.oauth_test_data["raw_offline_access_token_string"]
13
+
14
+ # for signed requests (http://developers.facebook.com/docs/authentication/canvas/encryption_proposal)
15
+ @signed_params = KoalaTest.oauth_test_data["signed_params"]
16
+ @signed_params_result = KoalaTest.oauth_test_data["signed_params_result"]
17
+
18
+ # this should expanded to cover all variables
19
+ raise Exception, "Must supply app data to run FacebookOAuthTests!" unless @app_id && @secret && @callback_url &&
20
+ @raw_token_string &&
21
+ @raw_offline_access_token_string
22
+
23
+ # we can just test against the same key twice
24
+ @multiple_session_keys = [KoalaTest.session_key, KoalaTest.session_key] if KoalaTest.session_key
25
+
26
+ @oauth = Koala::Facebook::OAuth.new(@app_id, @secret, @callback_url)
27
+ end
28
+
29
+ before :each do
30
+ @time = Time.now
31
+ allow(Time).to receive(:now).and_return(@time)
32
+ allow(@time).to receive(:to_i).and_return(1273363199)
33
+ end
34
+
35
+ describe ".new" do
36
+ it "properly initializes" do
37
+ expect(@oauth).to be_truthy
38
+ end
39
+
40
+ it "properly sets attributes" do
41
+ expect(@oauth.app_id == @app_id &&
42
+ @oauth.app_secret == @secret &&
43
+ @oauth.oauth_callback_url == @callback_url).to be_truthy
44
+ end
45
+
46
+ it "properly initializes without a callback_url" do
47
+ @oauth = Koala::Facebook::OAuth.new(@app_id, @secret)
48
+ end
49
+
50
+ it "properly sets attributes without a callback URL" do
51
+ @oauth = Koala::Facebook::OAuth.new(@app_id, @secret)
52
+ expect(@oauth.app_id == @app_id &&
53
+ @oauth.app_secret == @secret &&
54
+ @oauth.oauth_callback_url == nil).to be_truthy
55
+ end
56
+
57
+ context "with global defaults" do
58
+ let(:app_id) { :app_id }
59
+ let(:app_secret) { :app_secret }
60
+ let(:oauth_callback_url) { :oauth_callback_url }
61
+
62
+ before :each do
63
+ Koala.configure do |config|
64
+ config.app_id = app_id
65
+ config.app_secret = app_secret
66
+ config.oauth_callback_url = oauth_callback_url
67
+ end
68
+ end
69
+
70
+ it "defaults to the configured data if not otherwise provided" do
71
+ oauth = Koala::Facebook::OAuth.new
72
+ expect(oauth.app_id).to eq(app_id)
73
+ expect(oauth.app_secret).to eq(app_secret)
74
+ expect(oauth.oauth_callback_url).to eq(oauth_callback_url)
75
+ end
76
+
77
+ it "lets you override app_id" do
78
+ other_value = :another_id
79
+ oauth = Koala::Facebook::OAuth.new(other_value)
80
+ expect(oauth.app_id).to eq(other_value)
81
+ end
82
+
83
+ it "lets you override secret" do
84
+ other_value = :another_secret
85
+ oauth = Koala::Facebook::OAuth.new(nil, other_value)
86
+ expect(oauth.app_secret).to eq(other_value)
87
+ end
88
+
89
+ it "lets you override app_id" do
90
+ other_value = :another_token
91
+ oauth = Koala::Facebook::OAuth.new(nil, nil, other_value)
92
+ expect(oauth.oauth_callback_url).to eq(other_value)
93
+ end
94
+ end
95
+ end
96
+
97
+ describe "for cookie parsing" do
98
+ describe "get_user_info_from_cookies" do
99
+ context "for signed cookies" do
100
+ before :each do
101
+ # we don't actually want to make requests to Facebook to redeem the code
102
+ @cookie = KoalaTest.oauth_test_data["valid_signed_cookies"]
103
+ @token = "my token"
104
+ allow(@oauth).to receive(:get_access_token_info).and_return("access_token" => @token)
105
+ end
106
+
107
+ it "parses valid cookies" do
108
+ result = @oauth.get_user_info_from_cookies(@cookie)
109
+ expect(result).to be_a(Hash)
110
+ end
111
+
112
+ it "returns all the components in the signed request" do
113
+ result = @oauth.get_user_info_from_cookies(@cookie)
114
+ @oauth.parse_signed_request(@cookie.values.first).each_pair do |k, v|
115
+ expect(result[k]).to eq(v)
116
+ end
117
+ end
118
+
119
+ it "makes a request to Facebook to redeem the code if present" do
120
+ code = "foo"
121
+ allow(@oauth).to receive(:parse_signed_request).and_return({"code" => code})
122
+ expect(@oauth).to receive(:get_access_token_info).with(code, anything)
123
+ @oauth.get_user_info_from_cookies(@cookie)
124
+ end
125
+
126
+ it "sets the code redemption redirect_uri to ''" do
127
+ expect(@oauth).to receive(:get_access_token_info).with(anything, :redirect_uri => '')
128
+ @oauth.get_user_info_from_cookies(@cookie)
129
+ end
130
+
131
+ context "if the code is missing" do
132
+ it "doesn't make a request to Facebook" do
133
+ allow(@oauth).to receive(:parse_signed_request).and_return({})
134
+ expect(@oauth).to receive(:get_access_token_info).never
135
+ @oauth.get_user_info_from_cookies(@cookie)
136
+ end
137
+
138
+ it "returns nil" do
139
+ allow(@oauth).to receive(:parse_signed_request).and_return({})
140
+ expect(@oauth.get_user_info_from_cookies(@cookie)).to be_nil
141
+ end
142
+
143
+ it "logs a warning" do
144
+ allow(@oauth).to receive(:parse_signed_request).and_return({})
145
+ expect(Koala::Utils.logger).to receive(:warn)
146
+ @oauth.get_user_info_from_cookies(@cookie)
147
+ end
148
+ end
149
+
150
+ context "if the code is present" do
151
+ it "adds the access_token into the hash" do
152
+ expect(@oauth.get_user_info_from_cookies(@cookie)["access_token"]).to eq(@token)
153
+ end
154
+
155
+ it "returns nil if the call to FB returns no data" do
156
+ allow(@oauth).to receive(:get_access_token_info).and_return(nil)
157
+ expect(@oauth.get_user_info_from_cookies(@cookie)).to be_nil
158
+ end
159
+
160
+ it "returns nil if the call to FB returns an expired code error" do
161
+ allow(@oauth).to receive(:get_access_token_info).and_raise(Koala::Facebook::OAuthTokenRequestError.new(400,
162
+ '{ "error": { "type": "OAuthException", "message": "Code was invalid or expired. Session has expired at unix time 1324044000. The current unix time is 1324300957." } }'
163
+ ))
164
+ expect(@oauth.get_user_info_from_cookies(@cookie)).to be_nil
165
+ end
166
+
167
+ it "raises the error if the call to FB returns a different error" do
168
+ allow(@oauth).to receive(:get_access_token_info).and_raise(Koala::Facebook::OAuthTokenRequestError.new(400,
169
+ '{ "error": { "type": "OtherError", "message": "A Facebook Error" } }'))
170
+ expect { @oauth.get_user_info_from_cookies(@cookie) }.to raise_exception(Koala::Facebook::OAuthTokenRequestError)
171
+ end
172
+ end
173
+
174
+ it "doesn't parse invalid cookies" do
175
+ # make an invalid string by replacing some values
176
+ bad_cookie_hash = @cookie.inject({}) { |hash, value| hash[value[0]] = value[1].gsub(/[0-9]/, "3") }
177
+ result = @oauth.get_user_info_from_cookies(bad_cookie_hash)
178
+ expect(result).to be_nil
179
+ end
180
+ end
181
+
182
+ context "for unsigned cookies" do
183
+ it "properly parses valid cookies" do
184
+ result = @oauth.get_user_info_from_cookies(KoalaTest.oauth_test_data["valid_cookies"])
185
+ expect(result).to be_a(Hash)
186
+ end
187
+
188
+ it "returns all the cookie components from valid cookie string" do
189
+ cookie_data = KoalaTest.oauth_test_data["valid_cookies"]
190
+ parsing_results = @oauth.get_user_info_from_cookies(cookie_data)
191
+ number_of_components = cookie_data["fbs_#{@app_id.to_s}"].scan(/\=/).length
192
+ expect(parsing_results.length).to eq(number_of_components)
193
+ end
194
+
195
+ it "properly parses valid offline access cookies (e.g. no expiration)" do
196
+ result = @oauth.get_user_info_from_cookies(KoalaTest.oauth_test_data["offline_access_cookies"])
197
+ expect(result["uid"]).to be_truthy
198
+ end
199
+
200
+ it "returns all the cookie components from offline access cookies" do
201
+ cookie_data = KoalaTest.oauth_test_data["offline_access_cookies"]
202
+ parsing_results = @oauth.get_user_info_from_cookies(cookie_data)
203
+ number_of_components = cookie_data["fbs_#{@app_id.to_s}"].scan(/\=/).length
204
+ expect(parsing_results.length).to eq(number_of_components)
205
+ end
206
+
207
+ it "doesn't parse expired cookies" do
208
+ new_time = @time.to_i * 2
209
+ allow(@time).to receive(:to_i).and_return(new_time)
210
+ expect(@oauth.get_user_info_from_cookies(KoalaTest.oauth_test_data["valid_cookies"])).to be_nil
211
+ end
212
+
213
+ it "doesn't parse invalid cookies" do
214
+ # make an invalid string by replacing some values
215
+ bad_cookie_hash = KoalaTest.oauth_test_data["valid_cookies"].inject({}) { |hash, value| hash[value[0]] = value[1].gsub(/[0-9]/, "3") }
216
+ result = @oauth.get_user_info_from_cookies(bad_cookie_hash)
217
+ expect(result).to be_nil
218
+ end
219
+ end
220
+ end
221
+
222
+ describe "for URL generation" do
223
+ describe "#url_for_oauth_code" do
224
+ it "generates a properly formatted OAuth code URL with the default values" do
225
+ url = @oauth.url_for_oauth_code
226
+ expect(url).to match_url("https://#{Koala.config.dialog_host}/dialog/oauth?client_id=#{@app_id}&redirect_uri=#{CGI.escape @callback_url}")
227
+ end
228
+
229
+ it "includes the api version if specified" do
230
+ version = Koala.config.api_version = "v.2.2.2.2"
231
+ url = @oauth.url_for_oauth_code
232
+ expect(url).to match_url("https://#{Koala.config.dialog_host}/#{version}/dialog/oauth?client_id=#{@app_id}&redirect_uri=#{CGI.escape @callback_url}")
233
+ end
234
+
235
+ it "generates a properly formatted OAuth code URL when a callback is given" do
236
+ callback = "foo.com"
237
+ url = @oauth.url_for_oauth_code(:callback => callback)
238
+ expect(url).to match_url("https://#{Koala.config.dialog_host}/dialog/oauth?client_id=#{@app_id}&redirect_uri=#{callback}")
239
+ end
240
+
241
+ it "generates a properly formatted OAuth code URL when permissions are requested as a string" do
242
+ permissions = "publish_stream,read_stream"
243
+ url = @oauth.url_for_oauth_code(:permissions => permissions)
244
+ expect(url).to match_url("https://#{Koala.config.dialog_host}/dialog/oauth?client_id=#{@app_id}&scope=#{CGI.escape permissions}&redirect_uri=#{CGI.escape @callback_url}")
245
+ end
246
+
247
+ it "generates a properly formatted OAuth code URL when permissions are requested as a string" do
248
+ permissions = ["publish_stream", "read_stream"]
249
+ url = @oauth.url_for_oauth_code(:permissions => permissions)
250
+ expect(url).to match_url("https://#{Koala.config.dialog_host}/dialog/oauth?client_id=#{@app_id}&scope=#{CGI.escape permissions.join(",")}&redirect_uri=#{CGI.escape @callback_url}")
251
+ end
252
+
253
+ it "generates a properly formatted OAuth code URL when both permissions and callback are provided" do
254
+ permissions = "publish_stream,read_stream"
255
+ callback = "foo.com"
256
+ url = @oauth.url_for_oauth_code(:callback => callback, :permissions => permissions)
257
+ expect(url).to match_url("https://#{Koala.config.dialog_host}/dialog/oauth?client_id=#{@app_id}&scope=#{CGI.escape permissions}&redirect_uri=#{CGI.escape callback}")
258
+ end
259
+
260
+ it "generates a properly formatted OAuth code URL when a display is given as a string" do
261
+ url = @oauth.url_for_oauth_code(:display => "page")
262
+ expect(url).to match_url("https://#{Koala.config.dialog_host}/dialog/oauth?client_id=#{@app_id}&display=page&redirect_uri=#{CGI.escape @callback_url}")
263
+ end
264
+
265
+ it "raises an exception if no callback is given in initialization or the call" do
266
+ oauth2 = Koala::Facebook::OAuth.new(@app_id, @secret)
267
+ expect { oauth2.url_for_oauth_code }.to raise_error(ArgumentError)
268
+ end
269
+
270
+ it "includes any additional options as URL parameters, appropriately escaped" do
271
+ params = {
272
+ :url => "http://foo.bar?c=2",
273
+ :email => "cdc@b.com"
274
+ }
275
+ url = @oauth.url_for_oauth_code(params)
276
+ params.each_pair do |key, value|
277
+ expect(url).to match(/[\&\?]#{key}=#{CGI.escape value}/)
278
+ end
279
+ end
280
+ end
281
+
282
+ describe "#url_for_access_token" do
283
+ before :each do
284
+ # since we're just composing a URL here, we don't need to have a real code
285
+ @code ||= "test_code"
286
+ end
287
+
288
+ it "generates a properly formatted OAuth token URL when provided a code" do
289
+ url = @oauth.url_for_access_token(@code)
290
+ expect(url).to match_url("https://#{Koala.config.graph_server}/oauth/access_token?client_id=#{@app_id}&code=#{@code}&client_secret=#{@secret}&redirect_uri=#{CGI.escape @callback_url}")
291
+ end
292
+
293
+ it "includes the api version if specified" do
294
+ version = Koala.config.api_version = "v.2.2.2.2"
295
+ url = @oauth.url_for_access_token(@code)
296
+ expect(url).to match_url("https://#{Koala.config.graph_server}/#{version}/oauth/access_token?client_id=#{@app_id}&code=#{@code}&client_secret=#{@secret}&redirect_uri=#{CGI.escape @callback_url}")
297
+ end
298
+
299
+ it "generates a properly formatted OAuth token URL when provided a callback" do
300
+ callback = "foo.com"
301
+ url = @oauth.url_for_access_token(@code, :callback => callback)
302
+ expect(url).to match_url("https://#{Koala.config.graph_server}/oauth/access_token?client_id=#{@app_id}&code=#{@code}&client_secret=#{@secret}&redirect_uri=#{CGI.escape callback}")
303
+ end
304
+
305
+ it "includes any additional options as URL parameters, appropriately escaped" do
306
+ params = {
307
+ :url => "http://foo.bar?c=2",
308
+ :email => "cdc@b.com"
309
+ }
310
+ url = @oauth.url_for_access_token(@code, params)
311
+ params.each_pair do |key, value|
312
+ expect(url).to match(/[\&\?]#{key}=#{CGI.escape value}/)
313
+ end
314
+ end
315
+ end
316
+
317
+ describe "#url_for_dialog" do
318
+ it "builds the base properly" do
319
+ dialog_type = "my_dialog_type"
320
+ expect(@oauth.url_for_dialog(dialog_type)).to match(/^https:\/\/#{Koala.config.dialog_host}\/dialog\/#{dialog_type}/)
321
+ end
322
+
323
+ it "includes the api version if specified" do
324
+ version = Koala.config.api_version = "v.2.2.2.2"
325
+ dialog_type = "my_dialog_type"
326
+ expect(@oauth.url_for_dialog(dialog_type)).to match("https:\/\/#{Koala.config.dialog_host}\/#{version}\/dialog\/#{dialog_type}")
327
+ end
328
+
329
+ it "adds the app_id/client_id to the url" do
330
+ automatic_params = {:app_id => @app_id, :client_id => @client_id}
331
+ url = @oauth.url_for_dialog("foo", automatic_params)
332
+ automatic_params.each_pair do |key, value|
333
+ # we're slightly simplifying how encode_params works, but for strings/ints, it's okay
334
+ expect(url).to match(/[\&\?]#{key}=#{CGI.escape value.to_s}/)
335
+ end
336
+ end
337
+
338
+ it "includes any additional options as URL parameters, appropriately escaped" do
339
+ params = {
340
+ :url => "http://foo.bar?c=2",
341
+ :email => "cdc@b.com"
342
+ }
343
+ url = @oauth.url_for_dialog("friends", params)
344
+ params.each_pair do |key, value|
345
+ # we're slightly simplifying how encode_params works, but strings/ints, it's okay
346
+ expect(url).to match(/[\&\?]#{key}=#{CGI.escape value.to_s}/)
347
+ end
348
+ end
349
+
350
+ describe "real examples from FB documentation" do
351
+ # see http://developers.facebook.com/docs/reference/dialogs/
352
+ # slightly brittle (e.g. if parameter order changes), but still useful
353
+ it "can generate a send dialog" do
354
+ url = @oauth.url_for_dialog("send", :name => "People Argue Just to Win", :link => "http://www.nytimes.com/2011/06/15/arts/people-argue-just-to-win-scholars-assert.html")
355
+ expect(url).to match_url("https://www.facebook.com/dialog/send?app_id=#{@app_id}&client_id=#{@app_id}&link=http%3A%2F%2Fwww.nytimes.com%2F2011%2F06%2F15%2Farts%2Fpeople-argue-just-to-win-scholars-assert.html&name=People+Argue+Just+to+Win&redirect_uri=#{CGI.escape @callback_url}")
356
+ end
357
+
358
+ it "can generate a feed dialog" do
359
+ url = @oauth.url_for_dialog("feed", :name => "People Argue Just to Win", :link => "http://www.nytimes.com/2011/06/15/arts/people-argue-just-to-win-scholars-assert.html")
360
+ expect(url).to match_url("https://www.facebook.com/dialog/feed?app_id=#{@app_id}&client_id=#{@app_id}&link=http%3A%2F%2Fwww.nytimes.com%2F2011%2F06%2F15%2Farts%2Fpeople-argue-just-to-win-scholars-assert.html&name=People+Argue+Just+to+Win&redirect_uri=#{CGI.escape @callback_url}")
361
+ end
362
+
363
+ it "can generate a oauth dialog" do
364
+ url = @oauth.url_for_dialog("oauth", :scope => "email", :response_type => "token")
365
+ expect(url).to match_url("https://www.facebook.com/dialog/oauth?app_id=#{@app_id}&client_id=#{@app_id}&redirect_uri=#{CGI.escape @callback_url}&response_type=token&scope=email")
366
+ end
367
+
368
+ it "can generate a pay dialog" do
369
+ url = @oauth.url_for_dialog("pay", :order_id => "foo", :credits_purchase => false)
370
+ expect(url).to match_url("https://www.facebook.com/dialog/pay?app_id=#{@app_id}&client_id=#{@app_id}&order_id=foo&credits_purchase=false&redirect_uri=#{CGI.escape @callback_url}")
371
+ end
372
+ end
373
+ end
374
+ end
375
+
376
+ describe 'for generating a client code' do
377
+ describe '#generate_client_code' do
378
+ if KoalaTest.mock_interface? || KoalaTest.oauth_token
379
+ it 'makes a request using the correct endpoint' do
380
+ expect(Koala).to receive(:make_request).with('/oauth/client_code', anything, 'get', anything).and_return(Koala::HTTPService::Response.new(200, '{"code": "fake_client_code"}', {}))
381
+ @oauth.generate_client_code(KoalaTest.oauth_token)
382
+ end
383
+
384
+ it 'gets a valid client code returned' do
385
+ expect(Koala).to receive(:make_request).with('/oauth/client_code', anything, 'get', anything).and_return(Koala::HTTPService::Response.new(200, '{"code": "fake_client_code"}', {}))
386
+ result = @oauth.generate_client_code(KoalaTest.oauth_token)
387
+ expect(result).to be_a(String)
388
+ expect(result).to eq('fake_client_code')
389
+ end
390
+
391
+ it 'raises a BadFacebookResponse error when empty response body is returned' do
392
+ expect(Koala).to receive(:make_request).with('/oauth/client_code', anything, 'get', anything).and_return(Koala::HTTPService::Response.new(200, '', {}))
393
+ expect { @oauth.generate_client_code(KoalaTest.oauth_token) }.to raise_error(Koala::Facebook::BadFacebookResponse)
394
+ end
395
+
396
+ it 'raises an OAuthTokenRequestError when empty response body is returned' do
397
+ expect(Koala).to receive(:make_request).with('/oauth/client_code', anything, 'get', anything).and_return(Koala::HTTPService::Response.new(400, '', {}))
398
+ expect { @oauth.generate_client_code(KoalaTest.oauth_token) }.to raise_error(Koala::Facebook::OAuthTokenRequestError)
399
+ end
400
+
401
+ it 'raises a ServerError when empty response body is returned' do
402
+ expect(Koala).to receive(:make_request).with('/oauth/client_code', anything, 'get', anything).and_return(Koala::HTTPService::Response.new(500, '', {}))
403
+ expect { @oauth.generate_client_code(KoalaTest.oauth_token) }.to raise_error(Koala::Facebook::ServerError)
404
+ end
405
+
406
+ it 'raises a KoalaError when empty response body is returned' do
407
+ expect(Koala).to receive(:make_request).with('/oauth/client_code', anything, 'get', anything).and_return(Koala::HTTPService::Response.new(200, '{"client_code":"should_not_be_returned"}', {}))
408
+ expect { @oauth.generate_client_code(KoalaTest.oauth_token) }.to raise_error(Koala::KoalaError)
409
+ end
410
+ else
411
+ pending "Some OAuth token exchange tests will not be run since the access token field in facebook_data.yml is blank."
412
+ end
413
+ end
414
+ end
415
+
416
+ describe "for fetching access tokens" do
417
+ describe "#get_access_token_info" do
418
+ it "uses options[:redirect_uri] if provided" do
419
+ uri = "foo"
420
+ expect(Koala).to receive(:make_request).with(anything, hash_including(:redirect_uri => uri), anything, anything).and_return(Koala::HTTPService::Response.new(200, "", {}))
421
+ @oauth.get_access_token_info(@code, :redirect_uri => uri)
422
+ end
423
+
424
+ it "uses the redirect_uri used to create the @oauth if no :redirect_uri option is provided" do
425
+ expect(Koala).to receive(:make_request).with(anything, hash_including(:redirect_uri => @callback_url), anything, anything).and_return(Koala::HTTPService::Response.new(200, "", {}))
426
+ @oauth.get_access_token_info(@code)
427
+ end
428
+
429
+ it "makes a GET request" do
430
+ expect(Koala).to receive(:make_request).with(anything, anything, "get", anything).and_return(Koala::HTTPService::Response.new(200, "", {}))
431
+ @oauth.get_access_token_info(@code)
432
+ end
433
+
434
+ it "properly decodes JSON results" do
435
+ result = {
436
+ "access_token" => "foo",
437
+ "expires_in" => "baz",
438
+ "machine_id" => "bar"
439
+ }
440
+ allow(Koala).to receive(:make_request).and_return(
441
+ Koala::HTTPService::Response.new(
442
+ 200,
443
+ JSON.dump(result),
444
+ {}
445
+ )
446
+ )
447
+ expect(@oauth.get_access_token_info(@code)).to eq(result)
448
+ end
449
+
450
+ it "falls back to URL-style parsing " do
451
+ result = "access_token=foo&expires_in=baz&machine_id=bar"
452
+ allow(Koala).to receive(:make_request).and_return(
453
+ Koala::HTTPService::Response.new(200, result, {})
454
+ )
455
+ expect(@oauth.get_access_token_info(@code)).to eq({
456
+ "access_token" => "foo",
457
+ "expires_in" => "baz",
458
+ "machine_id" => "bar"
459
+ })
460
+ end
461
+
462
+ if KoalaTest.code
463
+ it "properly gets and parses an access token token results into a hash" do
464
+ result = @oauth.get_access_token_info(@code)
465
+ expect(result).to be_a(Hash)
466
+ end
467
+
468
+ it "properly includes the access token results" do
469
+ result = @oauth.get_access_token_info(@code)
470
+ expect(result["access_token"]).to be_truthy
471
+ end
472
+
473
+ it "raises an error when get_access_token is called with a bad code" do
474
+ expect { @oauth.get_access_token_info("foo") }.to raise_error(Koala::Facebook::OAuthTokenRequestError)
475
+ end
476
+ end
477
+ end
478
+
479
+ describe "#get_access_token" do
480
+ # TODO refactor these to be proper tests with stubs and tests against real data
481
+ it "passes on any options provided to make_request" do
482
+ options = {:a => 2}
483
+ expect(Koala).to receive(:make_request).with(anything, anything, anything, hash_including(options)).and_return(Koala::HTTPService::Response.new(200, "", {}))
484
+ @oauth.get_access_token(@code, options)
485
+ end
486
+
487
+ if KoalaTest.code
488
+ it "uses get_access_token_info to get and parse an access token token results" do
489
+ result = @oauth.get_access_token(@code)
490
+ expect(result).to be_a(String)
491
+ end
492
+
493
+ it "returns the access token as a string" do
494
+ result = @oauth.get_access_token(@code)
495
+ original = @oauth.get_access_token_info(@code)
496
+ expect(result).to eq(original["access_token"])
497
+ end
498
+
499
+ it "raises an error when get_access_token is called with a bad code" do
500
+ expect { @oauth.get_access_token("foo") }.to raise_error(Koala::Facebook::OAuthTokenRequestError)
501
+ end
502
+ end
503
+ end
504
+
505
+ unless KoalaTest.code
506
+ it "Some OAuth code tests will not be run since the code field in facebook_data.yml is blank."
507
+ end
508
+
509
+ describe "get_app_access_token_info" do
510
+ it "properly gets and parses an app's access token as a hash" do
511
+ result = @oauth.get_app_access_token_info
512
+ expect(result).to be_a(Hash)
513
+ end
514
+
515
+ it "includes the access token" do
516
+ result = @oauth.get_app_access_token_info
517
+ expect(result["access_token"]).to be_truthy
518
+ end
519
+
520
+ it "passes on any options provided to make_request" do
521
+ options = {:a => 2}
522
+ expect(Koala).to receive(:make_request).with(anything, anything, anything, hash_including(options)).and_return(Koala::HTTPService::Response.new(200, "", {}))
523
+ @oauth.get_app_access_token_info(options)
524
+ end
525
+ end
526
+
527
+ describe "get_app_access_token" do
528
+ it "uses get_access_token_info to get and parse an access token token results" do
529
+ result = @oauth.get_app_access_token
530
+ expect(result).to be_a(String)
531
+ end
532
+
533
+ it "returns the access token as a string" do
534
+ result = @oauth.get_app_access_token
535
+ original = @oauth.get_app_access_token_info
536
+ expect(result).to eq(original["access_token"])
537
+ end
538
+
539
+ it "passes on any options provided to make_request" do
540
+ options = {:a => 2}
541
+ expect(Koala).to receive(:make_request).with(anything, anything, anything, hash_including(options)).and_return(Koala::HTTPService::Response.new(200, "", {}))
542
+ @oauth.get_app_access_token(options)
543
+ end
544
+ end
545
+
546
+ describe "exchange_access_token_info" do
547
+ if KoalaTest.mock_interface? || KoalaTest.oauth_token
548
+ it "properly gets and parses an app's access token as a hash" do
549
+ result = @oauth.exchange_access_token_info(KoalaTest.oauth_token)
550
+ expect(result).to be_a(Hash)
551
+ end
552
+
553
+ it "includes the access token" do
554
+ result = @oauth.exchange_access_token_info(KoalaTest.oauth_token)
555
+ expect(result["access_token"]).not_to be_nil
556
+ end
557
+ else
558
+ pending "Some OAuth token exchange tests will not be run since the access token field in facebook_data.yml is blank."
559
+ end
560
+
561
+ it "passes on any options provided to make_request" do
562
+ options = {:a => 2}
563
+ expect(Koala).to receive(:make_request).with(anything, anything, anything, hash_including(options)).and_return(Koala::HTTPService::Response.new(200, "", {}))
564
+ @oauth.exchange_access_token_info(KoalaTest.oauth_token, options)
565
+ end
566
+
567
+ it "raises an error when exchange_access_token_info is called with a bad code" do
568
+ expect { @oauth.exchange_access_token_info("foo") }.to raise_error(Koala::Facebook::OAuthTokenRequestError)
569
+ end
570
+ end
571
+
572
+ describe "exchange_access_token" do
573
+ it "uses get_access_token_info to get and parse an access token token results" do
574
+ hash = {"access_token" => Time.now.to_i * rand}
575
+ allow(@oauth).to receive(:exchange_access_token_info).and_return(hash)
576
+ expect(@oauth.exchange_access_token(KoalaTest.oauth_token)).to eq(hash["access_token"])
577
+ end
578
+
579
+ it "passes on any options provided to make_request" do
580
+ options = {:a => 2}
581
+ expect(Koala).to receive(:make_request).with(anything, anything, anything, hash_including(options)).and_return(Koala::HTTPService::Response.new(200, "", {}))
582
+ @oauth.exchange_access_token(KoalaTest.oauth_token, options)
583
+ end
584
+ end
585
+
586
+ describe "protected methods" do
587
+ # protected methods
588
+ # since these are pretty fundamental and pretty testable, we want to test them
589
+
590
+ # parse_access_token
591
+ it "properly parses access token results" do
592
+ result = @oauth.send(:parse_access_token, @raw_token_string)
593
+ has_both_parts = result["access_token"] && result["expires"]
594
+ expect(has_both_parts).to be_truthy
595
+ end
596
+
597
+ it "properly parses offline access token results" do
598
+ result = @oauth.send(:parse_access_token, @raw_offline_access_token_string)
599
+ has_both_parts = result["access_token"] && !result["expires"]
600
+ expect(has_both_parts).to be true
601
+ end
602
+
603
+ # fetch_token_string
604
+ # somewhat duplicative with the tests for get_access_token and get_app_access_token
605
+ # but no harm in thoroughness
606
+ if KoalaTest.code
607
+ it "fetches a proper token string from Facebook when given a code" do
608
+ result = @oauth.send(:fetch_token_string, :code => @code, :redirect_uri => @callback_url)
609
+ expect(result).to match(/^access_token/)
610
+ end
611
+ else
612
+ it "fetch_token_string code test will not be run since the code field in facebook_data.yml is blank."
613
+ end
614
+
615
+ it "fetches a proper token string from Facebook when asked for the app token" do
616
+ result = @oauth.send(:fetch_token_string, {:grant_type => 'client_credentials'}, true)
617
+ expect(result).to match(/^access_token/)
618
+ end
619
+ end
620
+ end
621
+
622
+ describe "for parsing signed requests" do
623
+ # the signed request code is ported directly from Facebook
624
+ # so we only need to test at a high level that it works
625
+ it "throws an error if the algorithm is unsupported" do
626
+ allow(JSON).to receive(:parse).and_return("algorithm" => "my fun algorithm")
627
+ expect { @oauth.parse_signed_request(@signed_params) }.to raise_error(Koala::Facebook::OAuthSignatureError)
628
+ end
629
+
630
+ it "throws an error if the signature is invalid" do
631
+ allow(OpenSSL::HMAC).to receive(:hexdigest).and_return("i'm an invalid signature")
632
+ expect { @oauth.parse_signed_request(@signed_params) }.to raise_error(Koala::Facebook::OAuthSignatureError)
633
+ end
634
+
635
+ it "throws an error if the signature string is empty" do
636
+ # this occasionally happens due to Facebook error
637
+ expect { @oauth.parse_signed_request("") }.to raise_error(Koala::Facebook::OAuthSignatureError)
638
+ expect { @oauth.parse_signed_request("abc-def") }.to raise_error(Koala::Facebook::OAuthSignatureError)
639
+ end
640
+
641
+ it "properly parses requests" do
642
+ @oauth = Koala::Facebook::OAuth.new(@app_id, @secret || @app_secret)
643
+ expect(@oauth.parse_signed_request(@signed_params)).to eq(@signed_params_result)
644
+ end
645
+ end
646
+ end
647
+ end # describe