koala 0.9.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. data/.autotest +12 -0
  2. data/.gitignore +5 -0
  3. data/.travis.yml +8 -0
  4. data/CHANGELOG +71 -7
  5. data/Gemfile +7 -0
  6. data/LICENSE +1 -1
  7. data/Manifest +10 -15
  8. data/Rakefile +13 -13
  9. data/autotest/discover.rb +1 -0
  10. data/koala.gemspec +36 -16
  11. data/lib/koala/batch_operation.rb +74 -0
  12. data/lib/koala/graph_api.rb +200 -134
  13. data/lib/koala/graph_batch_api.rb +87 -0
  14. data/lib/koala/graph_collection.rb +54 -0
  15. data/lib/koala/http_services/net_http_service.rb +92 -0
  16. data/lib/koala/http_services/typhoeus_service.rb +37 -0
  17. data/lib/koala/http_services.rb +22 -47
  18. data/lib/koala/oauth.rb +181 -0
  19. data/lib/koala/realtime_updates.rb +5 -14
  20. data/lib/koala/rest_api.rb +82 -10
  21. data/lib/koala/test_users.rb +85 -0
  22. data/lib/koala/uploadable_io.rb +175 -0
  23. data/lib/koala.rb +67 -229
  24. data/readme.md +71 -38
  25. data/spec/cases/api_base_spec.rb +101 -0
  26. data/spec/cases/graph_and_rest_api_spec.rb +31 -0
  27. data/spec/cases/graph_api_batch_spec.rb +609 -0
  28. data/spec/cases/graph_api_spec.rb +25 -0
  29. data/spec/cases/http_services/http_service_spec.rb +129 -0
  30. data/spec/cases/http_services/net_http_service_spec.rb +532 -0
  31. data/spec/cases/http_services/typhoeus_service_spec.rb +152 -0
  32. data/spec/cases/koala_spec.rb +55 -0
  33. data/spec/cases/oauth_spec.rb +409 -0
  34. data/spec/cases/realtime_updates_spec.rb +184 -0
  35. data/spec/cases/rest_api_spec.rb +25 -0
  36. data/spec/cases/test_users_spec.rb +221 -0
  37. data/spec/cases/uploadable_io_spec.rb +193 -0
  38. data/spec/fixtures/beach.jpg +0 -0
  39. data/spec/fixtures/cat.m4v +0 -0
  40. data/spec/{facebook_data.yml → fixtures/facebook_data.yml} +18 -14
  41. data/spec/fixtures/mock_facebook_responses.yml +409 -0
  42. data/spec/spec_helper.rb +19 -0
  43. data/spec/support/graph_api_shared_examples.rb +495 -0
  44. data/spec/support/json_testing_fix.rb +18 -0
  45. data/spec/support/live_testing_data_helper.rb +40 -0
  46. data/spec/support/mock_http_service.rb +96 -0
  47. data/spec/support/rest_api_shared_examples.rb +285 -0
  48. data/spec/support/setup_mocks_or_live.rb +51 -0
  49. data/spec/support/uploadable_io_shared_examples.rb +76 -0
  50. metadata +131 -59
  51. data/examples/oauth_playground/Capfile +0 -2
  52. data/examples/oauth_playground/LICENSE +0 -22
  53. data/examples/oauth_playground/Rakefile +0 -4
  54. data/examples/oauth_playground/config/deploy.rb +0 -39
  55. data/examples/oauth_playground/config/facebook.yml +0 -13
  56. data/examples/oauth_playground/config.ru +0 -27
  57. data/examples/oauth_playground/lib/load_facebook.rb +0 -3
  58. data/examples/oauth_playground/lib/oauth_playground.rb +0 -187
  59. data/examples/oauth_playground/readme.md +0 -8
  60. data/examples/oauth_playground/spec/oauth_playground_spec.rb +0 -35
  61. data/examples/oauth_playground/spec/spec_helper.rb +0 -36
  62. data/examples/oauth_playground/tmp/restart.txt +0 -0
  63. data/examples/oauth_playground/views/index.erb +0 -206
  64. data/examples/oauth_playground/views/layout.erb +0 -39
  65. data/init.rb +0 -2
  66. data/spec/koala/api_base_tests.rb +0 -95
  67. data/spec/koala/graph_and_rest_api/graph_and_rest_api_no_token_tests.rb +0 -10
  68. data/spec/koala/graph_and_rest_api/graph_and_rest_api_with_token_tests.rb +0 -11
  69. data/spec/koala/graph_api/graph_api_no_access_token_tests.rb +0 -114
  70. data/spec/koala/graph_api/graph_api_with_access_token_tests.rb +0 -150
  71. data/spec/koala/graph_api/graph_collection_tests.rb +0 -104
  72. data/spec/koala/live_testing_data_helper.rb +0 -15
  73. data/spec/koala/net_http_service_tests.rb +0 -181
  74. data/spec/koala/oauth/oauth_tests.rb +0 -440
  75. data/spec/koala/realtime_updates/realtime_updates_tests.rb +0 -187
  76. data/spec/koala/rest_api/rest_api_no_access_token_tests.rb +0 -94
  77. data/spec/koala/rest_api/rest_api_with_access_token_tests.rb +0 -36
  78. data/spec/koala_spec.rb +0 -18
  79. data/spec/koala_spec_helper.rb +0 -31
  80. data/spec/koala_spec_without_mocks.rb +0 -19
  81. data/spec/mock_facebook_responses.yml +0 -241
  82. data/spec/mock_http_service.rb +0 -81
@@ -0,0 +1,495 @@
1
+ shared_examples_for "Koala GraphAPI" do
2
+ # all Graph API instances should pass these tests, regardless of configuration
3
+
4
+ # API
5
+ it "should never use the rest api server" do
6
+ Koala.should_receive(:make_request).with(
7
+ anything,
8
+ anything,
9
+ anything,
10
+ hash_not_including(:rest_api => true)
11
+ ).and_return(Koala::Response.new(200, "", {}))
12
+
13
+ @api.api("anything")
14
+ end
15
+
16
+ # GRAPH CALL
17
+ describe "graph_call" do
18
+ it "should pass all arguments to the api method" do
19
+ args = ["koppel", {}, "get", {:a => :b}]
20
+
21
+ @api.should_receive(:api).with(*args)
22
+
23
+ @api.graph_call(*args)
24
+ end
25
+
26
+ it "should throw an APIError if the result hash has an error key" do
27
+ Koala.stub(:make_request).and_return(Koala::Response.new(500, {"error" => "An error occurred!"}, {}))
28
+ lambda { @api.graph_call("koppel", {}) }.should raise_exception(Koala::Facebook::APIError)
29
+ end
30
+ end
31
+
32
+ # SEARCH
33
+ it "should be able to search" do
34
+ result = @api.search("facebook")
35
+ result.length.should be_an(Integer)
36
+ end
37
+
38
+ # DATA
39
+ # access public info
40
+
41
+ # get_object
42
+ it "should get public data about a user" do
43
+ result = @api.get_object("koppel")
44
+ # the results should have an ID and a name, among other things
45
+ (result["id"] && result["name"]).should_not be_nil
46
+ end
47
+
48
+ it "should get public data about a Page" do
49
+ result = @api.get_object("contextoptional")
50
+ # the results should have an ID and a name, among other things
51
+ (result["id"] && result["name"]).should
52
+ end
53
+
54
+ it "should return [] from get_objects if passed an empty array" do
55
+ results = @api.get_objects([])
56
+ results.should == []
57
+ end
58
+
59
+ it "should be able to get multiple objects" do
60
+ results = @api.get_objects(["contextoptional", "naitik"])
61
+ results.should have(2).items
62
+ end
63
+
64
+ it "should be able to get multiple objects if they're a string" do
65
+ results = @api.get_objects("contextoptional,naitik")
66
+ results.should have(2).items
67
+ end
68
+
69
+ it "should be able to access a user's picture" do
70
+ @api.get_picture("chris.baclig").should =~ /http[s]*\:\/\//
71
+ end
72
+
73
+ it "should be able to access a user's picture, given a picture type" do
74
+ @api.get_picture("lukeshepard", {:type => 'large'}).should =~ /^http[s]*\:\/\//
75
+ end
76
+
77
+ it "should be able to access connections from public Pages" do
78
+ result = @api.get_connections("contextoptional", "photos")
79
+ result.should be_a(Array)
80
+ end
81
+
82
+ it "should be able to access comments for a URL" do
83
+ result = @api.get_comments_for_urls(["http://developers.facebook.com/blog/post/472"])
84
+ (result["http://developers.facebook.com/blog/post/472"]).should
85
+ end
86
+
87
+ it "should be able to access comments for 2 URLs" do
88
+ result = @api.get_comments_for_urls(["http://developers.facebook.com/blog/post/490", "http://developers.facebook.com/blog/post/472"])
89
+ (result["http://developers.facebook.com/blog/post/490"] && result["http://developers.facebook.com/blog/post/472"]).should
90
+ end
91
+
92
+ # SEARCH
93
+ it "should be able to search" do
94
+ result = @api.search("facebook")
95
+ result.length.should be_an(Integer)
96
+ end
97
+
98
+ # PAGING THROUGH COLLECTIONS
99
+ # see also graph_collection_tests
100
+ it "should make a request for a page when provided a specific set of page params" do
101
+ query = [1, 2]
102
+ @api.should_receive(:graph_call).with(*query)
103
+ @api.get_page(query)
104
+ end
105
+ end
106
+
107
+
108
+ shared_examples_for "Koala GraphAPI with an access token" do
109
+ it "should get private data about a user" do
110
+ result = @api.get_object("koppel")
111
+ # updated_time should be a pretty fixed test case
112
+ result["updated_time"].should_not be_nil
113
+ end
114
+
115
+ it "should get data about 'me'" do
116
+ result = @api.get_object("me")
117
+ result["updated_time"].should
118
+ end
119
+
120
+ it "should be able to get multiple objects" do
121
+ result = @api.get_objects(["contextoptional", "naitik"])
122
+ result.length.should == 2
123
+ end
124
+ it "should be able to access connections from users" do
125
+ result = @api.get_connections("lukeshepard", "likes")
126
+ result.length.should > 0
127
+ end
128
+
129
+ # PUT
130
+ it "should be able to write an object to the graph" do
131
+ result = @api.put_wall_post("Hello, world, from the test suite!")
132
+ @temporary_object_id = result["id"]
133
+ @temporary_object_id.should_not be_nil
134
+ end
135
+
136
+ # DELETE
137
+ it "should be able to delete posts" do
138
+ result = @api.put_wall_post("Hello, world, from the test suite delete method!")
139
+ object_id_to_delete = result["id"]
140
+ delete_result = @api.delete_object(object_id_to_delete)
141
+ delete_result.should == true
142
+ end
143
+
144
+ it "should be able to delete likes" do
145
+ result = @api.put_wall_post("Hello, world, from the test suite delete method!")
146
+ @temporary_object_id = result["id"]
147
+ @api.put_like(@temporary_object_id)
148
+ delete_like_result = @api.delete_like(@temporary_object_id)
149
+ delete_like_result.should == true
150
+ end
151
+
152
+ # additional put tests
153
+ it "should be able to verify messages posted to a wall" do
154
+ message = "the cats are asleep"
155
+ put_result = @api.put_wall_post(message)
156
+ @temporary_object_id = put_result["id"]
157
+ get_result = @api.get_object(@temporary_object_id)
158
+
159
+ # make sure the message we sent is the message that got posted
160
+ get_result["message"].should == message
161
+ end
162
+
163
+ it "should be able to post a message with an attachment to a feed" do
164
+ result = @api.put_wall_post("Hello, world, from the test suite again!", {:name => "OAuth Playground", :link => "http://oauth.twoalex.com/"})
165
+ @temporary_object_id = result["id"]
166
+ @temporary_object_id.should_not be_nil
167
+ end
168
+
169
+ describe ".put_picture" do
170
+ it "should be able to post photos to the user's wall with an open file object" do
171
+ content_type = "image/jpg"
172
+ file = File.open(File.join(File.dirname(__FILE__), "..", "fixtures", "beach.jpg"))
173
+
174
+ result = @api.put_picture(file, content_type)
175
+ @temporary_object_id = result["id"]
176
+ @temporary_object_id.should_not be_nil
177
+ end
178
+
179
+ it "uses the base HTTP service if the upload is a StringIO or similar" do
180
+ source = stub("UploadIO")
181
+ Koala::UploadableIO.stub(:new).and_return(source)
182
+ source.stub(:requires_base_http_service).and_return(true)
183
+ Koala.should_receive(:make_request).with(anything, anything, anything, hash_including(:http_service => Koala.base_http_service)).and_return(Koala::Response.new(200, "[]", {}))
184
+ @api.put_picture(StringIO.new)
185
+ end
186
+
187
+ it "should be able to post photos to the user's wall without an open file object" do
188
+ content_type = "image/jpg",
189
+ file_path = File.join(File.dirname(__FILE__), "..", "fixtures", "beach.jpg")
190
+
191
+ result = @api.put_picture(file_path, content_type)
192
+ @temporary_object_id = result["id"]
193
+ @temporary_object_id.should_not be_nil
194
+ end
195
+
196
+ it "should be able to verify a photo posted to a user's wall" do
197
+ content_type = "image/jpg",
198
+ file_path = File.join(File.dirname(__FILE__), "..", "fixtures", "beach.jpg")
199
+
200
+ expected_message = "This is the test message"
201
+
202
+ result = @api.put_picture(file_path, content_type, :message => expected_message)
203
+ @temporary_object_id = result["id"]
204
+ @temporary_object_id.should_not be_nil
205
+
206
+ get_result = @api.get_object(@temporary_object_id)
207
+ get_result["name"].should == expected_message
208
+ end
209
+ end
210
+
211
+ describe ".put_video" do
212
+ before :each do
213
+ @cat_movie = File.join(File.dirname(__FILE__), "..", "fixtures", "cat.m4v")
214
+ @content_type = "video/mpeg4"
215
+ end
216
+
217
+ it "should set options[:video] to true" do
218
+ source = stub("UploadIO")
219
+ Koala::UploadableIO.stub(:new).and_return(source)
220
+ source.stub(:requires_base_http_service).and_return(false)
221
+ Koala.should_receive(:make_request).with(anything, anything, anything, hash_including(:video => true)).and_return(Koala::Response.new(200, "[]", {}))
222
+ @api.put_video("foo")
223
+ end
224
+
225
+ it "should be able to post videos to the user's wall with an open file object" do
226
+ file = File.open(@cat_movie)
227
+
228
+ result = @api.put_video(file, @content_type)
229
+ @temporary_object_id = result["id"]
230
+ @temporary_object_id.should_not be_nil
231
+ end
232
+
233
+ it "uses the base HTTP service if the upload is a StringIO or similar" do
234
+ source = stub("UploadIO")
235
+ Koala::UploadableIO.stub(:new).and_return(source)
236
+ source.stub(:requires_base_http_service).and_return(true)
237
+ Koala.should_receive(:make_request).with(anything, anything, anything, hash_including(:http_service => Koala.base_http_service)).and_return(Koala::Response.new(200, "[]", {}))
238
+ @api.put_video(StringIO.new)
239
+ end
240
+
241
+ it "should be able to post videos to the user's wall without an open file object" do
242
+ result = @api.put_video(@cat_movie, @content_type)
243
+ @temporary_object_id = result["id"]
244
+ @temporary_object_id.should_not be_nil
245
+ end
246
+
247
+ # note: Facebook doesn't post videos immediately to the wall, due to processing time
248
+ # during which get_object(video_id) will return false
249
+ # hence we can't do the same verify test we do for photos
250
+ end
251
+
252
+ it "should be able to verify a message with an attachment posted to a feed" do
253
+ attachment = {"name" => "OAuth Playground", "link" => "http://oauth.twoalex.com/"}
254
+ result = @api.put_wall_post("Hello, world, from the test suite again!", attachment)
255
+ @temporary_object_id = result["id"]
256
+ get_result = @api.get_object(@temporary_object_id)
257
+
258
+ # make sure the result we fetch includes all the parameters we sent
259
+ it_matches = attachment.inject(true) {|valid, param| valid && (get_result[param[0]] == attachment[param[0]])}
260
+ it_matches.should == true
261
+ end
262
+
263
+ it "should be able to comment on an object" do
264
+ result = @api.put_wall_post("Hello, world, from the test suite, testing comments!")
265
+ @temporary_object_id = result["id"]
266
+
267
+ # this will be deleted when the post gets deleted
268
+ comment_result = @api.put_comment(@temporary_object_id, "it's my comment!")
269
+ comment_result.should_not be_nil
270
+ end
271
+
272
+ it "should be able to verify a comment posted about an object" do
273
+ message_text = "Hello, world, from the test suite, testing comments!"
274
+ result = @api.put_wall_post(message_text)
275
+ @temporary_object_id = result["id"]
276
+
277
+ # this will be deleted when the post gets deleted
278
+ comment_text = "it's my comment!"
279
+ comment_result = @api.put_comment(@temporary_object_id, comment_text)
280
+ get_result = @api.get_object(comment_result["id"])
281
+
282
+ # make sure the text of the comment matches what we sent
283
+ get_result["message"].should == comment_text
284
+ end
285
+
286
+ it "should be able to like an object" do
287
+ result = @api.put_wall_post("Hello, world, from the test suite, testing comments!")
288
+ @temporary_object_id = result["id"]
289
+ like_result = @api.put_like(@temporary_object_id)
290
+ like_result.should be_true
291
+ end
292
+
293
+
294
+ # test all methods to make sure they pass data through to the API
295
+ # we run the tests here (rather than in the common shared example group)
296
+ # since some require access tokens
297
+ describe "HTTP options" do
298
+ # Each of the below methods should take an options hash as their last argument
299
+ # ideally we'd use introspection to determine how many arguments a method has
300
+ # but some methods require specially formatted arguments for processing
301
+ # (and anyway, Ruby 1.8's arity method fails (for this) for methods w/ 2+ optional arguments)
302
+ # (Ruby 1.9's parameters method is perfect, but only in 1.9)
303
+ # so we have to double-document
304
+ {
305
+ :get_object => 3, :put_object => 4, :delete_object => 2,
306
+ :get_connections => 4, :put_connections => 4, :delete_connections => 4,
307
+ :put_wall_post => 4,
308
+ :put_comment => 3,
309
+ :put_like => 2, :delete_like => 2,
310
+ :search => 3,
311
+ # methods that have special arguments
312
+ :put_picture => ["x.jpg", "image/jpg", {}, "me"],
313
+ :put_video => ["x.mp4", "video/mpeg4", {}, "me"],
314
+ :get_objects => [["x"], {}]
315
+ }.each_pair do |method_name, params|
316
+ it "should pass http options through for #{method_name}" do
317
+ options = {:a => 2}
318
+ # graph call should ultimately receive options as the fourth argument
319
+ @api.should_receive(:graph_call).with(anything, anything, anything, options)
320
+
321
+ # if we supply args, use them (since some methods process params)
322
+ # the method should receive as args n-1 anythings and then options
323
+ args = (params.is_a?(Integer) ? ([{}] * (params - 1)) : params) + [options]
324
+
325
+ @api.send(method_name, *args)
326
+ end
327
+ end
328
+
329
+ # also test get_picture, which merges a parameter into options
330
+ it "should pass http options through for get_picture" do
331
+ options = {:a => 2}
332
+ # graph call should ultimately receive options as the fourth argument
333
+ @api.should_receive(:graph_call).with(anything, anything, anything, hash_including(options)).and_return({})
334
+ @api.send(:get_picture, "x", {}, options)
335
+ end
336
+ end
337
+ end
338
+
339
+
340
+ # GraphCollection
341
+ shared_examples_for "Koala GraphAPI with GraphCollection" do
342
+
343
+ it "should create an array-like object" do
344
+ call = @api.graph_call("contextoptional/photos")
345
+ Koala::Facebook::GraphCollection.new(call, @api).should be_an(Array)
346
+ end
347
+
348
+ describe "when getting a collection" do
349
+ # GraphCollection methods
350
+ it "should get a GraphCollection when getting connections" do
351
+ @result = @api.get_connections("contextoptional", "photos")
352
+ @result.should be_a(Koala::Facebook::GraphCollection)
353
+ end
354
+
355
+ it "should return nil if the get_collections call fails with nil" do
356
+ # this happens sometimes
357
+ @api.should_receive(:graph_call).and_return(nil)
358
+ @api.get_connections("contextoptional", "photos").should be_nil
359
+ end
360
+
361
+ it "should get a GraphCollection when searching" do
362
+ result = @api.search("facebook")
363
+ result.should be_a(Koala::Facebook::GraphCollection)
364
+ end
365
+
366
+ it "should return nil if the search call fails with nil" do
367
+ # this happens sometimes
368
+ @api.should_receive(:graph_call).and_return(nil)
369
+ @api.search("facebook").should be_nil
370
+ end
371
+
372
+ it "should get a GraphCollection when paging through results" do
373
+ @results = @api.get_page(["search", {"q"=>"facebook", "limit"=>"25", "until"=>"2010-09-23T21:17:33+0000"}])
374
+ @results.should be_a(Koala::Facebook::GraphCollection)
375
+ end
376
+
377
+ it "should return nil if the page call fails with nil" do
378
+ # this happens sometimes
379
+ @api.should_receive(:graph_call).and_return(nil)
380
+ @api.get_page(["search", {"q"=>"facebook", "limit"=>"25", "until"=>"2010-09-23T21:17:33+0000"}]).should be_nil
381
+ end
382
+
383
+ # GraphCollection attributes
384
+ describe "the GraphCollection" do
385
+ before(:each) do
386
+ @result = @api.get_connections("contextoptional", "photos")
387
+ end
388
+
389
+ it "should have a read-only paging attribute" do
390
+ @result.methods.map(&:to_sym).should include(:paging)
391
+ @result.methods.map(&:to_sym).should_not include(:paging=)
392
+ end
393
+
394
+ describe "when getting a whole page" do
395
+ before(:each) do
396
+ @second_page = stub("page of Fb graph results")
397
+ @base = stub("base")
398
+ @args = stub("args")
399
+ @page_of_results = stub("page of results")
400
+ end
401
+
402
+ it "should return the previous page of results" do
403
+ @result.should_receive(:previous_page_params).and_return([@base, @args])
404
+ @api.should_receive(:graph_call).with(@base, @args).and_yield(@second_page)
405
+ Koala::Facebook::GraphCollection.should_receive(:new).with(@second_page, @api).and_return(@page_of_results)
406
+
407
+ @result.previous_page#.should == @page_of_results
408
+ end
409
+
410
+ it "should return the next page of results" do
411
+ @result.should_receive(:next_page_params).and_return([@base, @args])
412
+ @api.should_receive(:graph_call).with(@base, @args).and_yield(@second_page)
413
+ Koala::Facebook::GraphCollection.should_receive(:new).with(@second_page, @api).and_return(@page_of_results)
414
+
415
+ @result.next_page#.should == @page_of_results
416
+ end
417
+
418
+ it "should return nil it there are no other pages" do
419
+ %w{next previous}.each do |this|
420
+ @result.should_receive("#{this}_page_params".to_sym).and_return(nil)
421
+ @result.send("#{this}_page").should == nil
422
+ end
423
+ end
424
+ end
425
+
426
+ describe "when parsing page paramters" do
427
+ before(:each) do
428
+ @graph_collection = Koala::Facebook::GraphCollection.new({"data" => []}, Koala::Facebook::GraphAPI.new)
429
+ end
430
+
431
+ it "should return the base as the first array entry" do
432
+ base = "url_path"
433
+ @graph_collection.parse_page_url("anything.com/#{base}?anything").first.should == base
434
+ end
435
+
436
+ it "should return the arguments as a hash as the last array entry" do
437
+ args_hash = {"one" => "val_one", "two" => "val_two"}
438
+ @graph_collection.parse_page_url("anything.com/anything?#{args_hash.map {|k,v| "#{k}=#{v}" }.join("&")}").last.should == args_hash
439
+ end
440
+ end
441
+ end
442
+ end
443
+ end
444
+
445
+
446
+ shared_examples_for "Koala GraphAPI without an access token" do
447
+
448
+ it "should not get private data about a user" do
449
+ result = @api.get_object("koppel")
450
+ # updated_time should be a pretty fixed test case
451
+ result["updated_time"].should be_nil
452
+ end
453
+
454
+ it "should not be able to get data about 'me'" do
455
+ lambda { @api.get_object("me") }.should raise_error(Koala::Facebook::APIError)
456
+ end
457
+
458
+ it "shouldn't be able to access connections from users" do
459
+ lambda { @api.get_connections("lukeshepard", "likes") }.should raise_error(Koala::Facebook::APIError)
460
+ end
461
+
462
+ it "should not be able to put an object" do
463
+ lambda { @result = @api.put_object("lukeshepard", "feed", :message => "Hello, world") }.should raise_error(Koala::Facebook::APIError)
464
+ puts "Error! Object #{@result.inspect} somehow put onto Luke Shepard's wall!" if @result
465
+ end
466
+
467
+ # these are not strictly necessary as the other put methods resolve to put_object, but are here for completeness
468
+ it "should not be able to post to a feed" do
469
+ (lambda do
470
+ attachment = {:name => "OAuth Playground", :link => "http://oauth.twoalex.com/"}
471
+ @result = @api.put_wall_post("Hello, world", attachment, "contextoptional")
472
+ end).should raise_error(Koala::Facebook::APIError)
473
+ puts "Error! Object #{@result.inspect} somehow put onto Context Optional's wall!" if @result
474
+ end
475
+
476
+ it "should not be able to comment on an object" do
477
+ # random public post on the ContextOptional wall
478
+ lambda { @result = @api.put_comment("7204941866_119776748033392", "The hackathon was great!") }.should raise_error(Koala::Facebook::APIError)
479
+ puts "Error! Object #{@result.inspect} somehow commented on post 7204941866_119776748033392!" if @result
480
+ end
481
+
482
+ it "should not be able to like an object" do
483
+ lambda { @api.put_like("7204941866_119776748033392") }.should raise_error(Koala::Facebook::APIError)
484
+ end
485
+
486
+ # DELETE
487
+ it "should not be able to delete posts" do
488
+ # test post on the Ruby SDK Test application
489
+ lambda { @result = @api.delete_object("115349521819193_113815981982767") }.should raise_error(Koala::Facebook::APIError)
490
+ end
491
+
492
+ it "should not be able to delete a like" do
493
+ lambda { @api.delete_like("7204941866_119776748033392") }.should raise_error(Koala::Facebook::APIError)
494
+ end
495
+ end
@@ -0,0 +1,18 @@
1
+ # when testing across Ruby versions, we found that JSON string creation inconsistently ordered keys
2
+ # which is a problem because our mock testing service ultimately matches strings to see if requests are mocked
3
+ # this fix solves that problem by ensuring all hashes are created with a consistent key order every time
4
+
5
+ module MultiJson
6
+ self.engine = :ok_json
7
+
8
+ def encode_with_ordering(object)
9
+ # if it's a hash, recreate it with k/v pairs inserted in sorted-by-key order
10
+ # (for some reason, REE 1.8.7 fails if we don't assign the ternary result as a local variable
11
+ # separate from calling encode_original)
12
+ new_object = object.is_a?(Hash) ? object.keys.sort.inject({}) {|hash, k| hash[k] = object[k]; hash} : object
13
+ encode_original(new_object)
14
+ end
15
+
16
+ alias_method :encode_original, :encode
17
+ alias_method :encode, :encode_with_ordering
18
+ end
@@ -0,0 +1,40 @@
1
+ module LiveTestingDataHelper
2
+ # in RSpec 2, included example groups no longer share any hooks or state with outside examples
3
+ # even if in the same block
4
+ # so we have to use a module to provide setup and teardown hooks for live testing
5
+
6
+ def self.included(base)
7
+ base.class_eval do
8
+ before :each do
9
+ @token = $testing_data["oauth_token"]
10
+ raise Exception, "Must supply access token to run FacebookWithAccessTokenTests!" unless @token
11
+ # track temporary objects created
12
+ @temporary_object_ids = []
13
+ end
14
+
15
+ after :each do
16
+ # clean up any temporary objects
17
+ @temporary_object_ids << @temporary_object_id if @temporary_object_id
18
+ count = @temporary_object_ids.length
19
+ errors = []
20
+
21
+ if count > 0
22
+ @temporary_object_ids.each do |id|
23
+ # get our API
24
+ api = @api || (@test_users ? @test_users.graph_api : nil)
25
+ raise "Unable to locate API when passed temporary object to delete!" unless api
26
+
27
+ # delete the object
28
+ result = (api.delete_object(id) rescue false)
29
+ # if we errored out or Facebook returned false, track that
30
+ errors << id unless result
31
+ end
32
+
33
+ unless errors.length == 0
34
+ puts "cleaned up #{count - errors.length} objects, but errored out on the following:\n #{errors.join(", ")}"
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,96 @@
1
+ require 'erb'
2
+ require 'yaml'
3
+
4
+ module Koala
5
+ module MockHTTPService
6
+ include Koala::HTTPService
7
+
8
+ # fix our specs to use ok_json, so we always get the same results from to_json
9
+ MultiJson.engine = :ok_json
10
+
11
+ # Mocks all HTTP requests for with koala_spec_with_mocks.rb
12
+ # Mocked values to be included in TEST_DATA used in specs
13
+ ACCESS_TOKEN = '*'
14
+ APP_ACCESS_TOKEN = "**"
15
+ OAUTH_CODE = 'OAUTHCODE'
16
+
17
+ # Loads testing data
18
+ TEST_DATA = YAML.load_file(File.join(File.dirname(__FILE__), '..', 'fixtures', 'facebook_data.yml'))
19
+ TEST_DATA.merge!('oauth_token' => Koala::MockHTTPService::ACCESS_TOKEN)
20
+ TEST_DATA['oauth_test_data'].merge!('code' => Koala::MockHTTPService::OAUTH_CODE)
21
+
22
+ # Useful in mock_facebook_responses.yml
23
+ OAUTH_DATA = TEST_DATA['oauth_test_data']
24
+ OAUTH_DATA.merge!({
25
+ 'app_access_token' => APP_ACCESS_TOKEN,
26
+ 'session_key' => "session_key",
27
+ 'multiple_session_keys' => ["session_key", "session_key_2"]
28
+ })
29
+ APP_ID = OAUTH_DATA['app_id']
30
+ SECRET = OAUTH_DATA['secret']
31
+ SUBSCRIPTION_DATA = TEST_DATA["subscription_test_data"]
32
+
33
+ # Loads the mock response data via ERB to substitue values for TEST_DATA (see oauth/access_token)
34
+ mock_response_file_path = File.join(File.dirname(__FILE__), '..', 'fixtures', 'mock_facebook_responses.yml')
35
+ RESPONSES = YAML.load(ERB.new(IO.read(mock_response_file_path)).result(binding))
36
+
37
+ def self.make_request(path, args, verb, options = {})
38
+ path = 'root' if path == '' || path == '/'
39
+ verb ||= 'get'
40
+ server = options[:rest_api] ? 'rest_api' : 'graph_api'
41
+ token = args.delete('access_token')
42
+ with_token = (token == ACCESS_TOKEN || token == APP_ACCESS_TOKEN) ? 'with_token' : 'no_token'
43
+
44
+ # Assume format is always JSON
45
+ args.delete('format')
46
+
47
+ # Create a hash key for the arguments
48
+ args = create_params_key(args)
49
+
50
+ begin
51
+ response = RESPONSES[server][path][args][verb][with_token]
52
+
53
+ # Raises an error of with_token/no_token key is missing
54
+ raise NoMethodError unless response
55
+
56
+ # create response class object
57
+ response_object = if response.is_a? String
58
+ Koala::Response.new(200, response, {})
59
+ else
60
+ Koala::Response.new(response["code"] || 200, response["body"] || "", response["headers"] || {})
61
+ end
62
+
63
+ rescue NoMethodError
64
+ # Raises an error message with the place in the data YML
65
+ # to place a mock as well as a URL to request from
66
+ # Facebook's servers for the actual data
67
+ # (Don't forget to replace ACCESS_TOKEN with a real access token)
68
+ data_trace = [server, path, args, verb, with_token] * ': '
69
+
70
+ args = args == 'no_args' ? '' : "#{args}&"
71
+ args += 'format=json'
72
+ args += "&access_token=#{ACCESS_TOKEN}" if with_token
73
+
74
+ raise "Missing a mock response for #{data_trace}\nAPI PATH: #{[path, args].join('?')}"
75
+ end
76
+
77
+ response_object
78
+ end
79
+
80
+ def self.mock?
81
+ true
82
+ end
83
+
84
+ protected
85
+ def self.create_params_key(params_hash)
86
+ if params_hash.empty?
87
+ 'no_args'
88
+ else
89
+ params_hash.sort{ |a,b| a[0].to_s <=> b[0].to_s}.map do |arr|
90
+ arr[1] = '[FILE]' if arr[1].kind_of?(Koala::UploadableIO)
91
+ arr.join('=')
92
+ end.join('&')
93
+ end
94
+ end
95
+ end
96
+ end