koala 1.1.0 → 1.2.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.
- data/.travis.yml +2 -1
- data/CHANGELOG +36 -0
- data/Gemfile +6 -2
- data/Rakefile +0 -1
- data/koala.gemspec +7 -8
- data/lib/koala/batch_operation.rb +15 -15
- data/lib/koala/graph_api.rb +85 -73
- data/lib/koala/graph_batch_api.rb +21 -11
- data/lib/koala/graph_collection.rb +13 -8
- data/lib/koala/http_service.rb +176 -0
- data/lib/koala/oauth.rb +34 -24
- data/lib/koala/realtime_updates.rb +21 -18
- data/lib/koala/rest_api.rb +1 -1
- data/lib/koala/test_users.rb +33 -17
- data/lib/koala/uploadable_io.rb +49 -43
- data/lib/koala/utils.rb +11 -0
- data/lib/koala/version.rb +3 -0
- data/lib/koala.rb +47 -45
- data/readme.md +58 -38
- data/spec/cases/{api_base_spec.rb → api_spec.rb} +27 -2
- data/spec/cases/error_spec.rb +32 -0
- data/spec/cases/graph_and_rest_api_spec.rb +12 -21
- data/spec/cases/graph_api_batch_spec.rb +92 -119
- data/spec/cases/graph_api_spec.rb +11 -14
- data/spec/cases/graph_collection_spec.rb +116 -0
- data/spec/cases/http_service_spec.rb +446 -0
- data/spec/cases/koala_spec.rb +36 -37
- data/spec/cases/oauth_spec.rb +318 -212
- data/spec/cases/realtime_updates_spec.rb +45 -31
- data/spec/cases/rest_api_spec.rb +23 -7
- data/spec/cases/test_users_spec.rb +123 -75
- data/spec/cases/uploadable_io_spec.rb +77 -36
- data/spec/cases/utils_spec.rb +10 -0
- data/spec/fixtures/facebook_data.yml +26 -24
- data/spec/fixtures/mock_facebook_responses.yml +131 -101
- data/spec/spec_helper.rb +29 -5
- data/spec/support/graph_api_shared_examples.rb +80 -120
- data/spec/support/json_testing_fix.rb +35 -11
- data/spec/support/koala_test.rb +187 -0
- data/spec/support/mock_http_service.rb +8 -5
- data/spec/support/ordered_hash.rb +205 -0
- data/spec/support/rest_api_shared_examples.rb +37 -37
- data/spec/support/uploadable_io_shared_examples.rb +2 -8
- metadata +72 -83
- data/lib/koala/http_services/net_http_service.rb +0 -92
- data/lib/koala/http_services/typhoeus_service.rb +0 -37
- data/lib/koala/http_services.rb +0 -46
- data/spec/cases/http_services/http_service_spec.rb +0 -129
- data/spec/cases/http_services/net_http_service_spec.rb +0 -532
- data/spec/cases/http_services/typhoeus_service_spec.rb +0 -152
- data/spec/support/live_testing_data_helper.rb +0 -40
- data/spec/support/setup_mocks_or_live.rb +0 -51
|
@@ -16,17 +16,36 @@ shared_examples_for "Koala GraphAPI" do
|
|
|
16
16
|
# GRAPH CALL
|
|
17
17
|
describe "graph_call" do
|
|
18
18
|
it "should pass all arguments to the api method" do
|
|
19
|
-
args = [
|
|
20
|
-
|
|
19
|
+
args = [KoalaTest.user1, {}, "get", {:a => :b}]
|
|
21
20
|
@api.should_receive(:api).with(*args)
|
|
22
|
-
|
|
23
21
|
@api.graph_call(*args)
|
|
24
22
|
end
|
|
25
23
|
|
|
26
24
|
it "should throw an APIError if the result hash has an error key" do
|
|
27
25
|
Koala.stub(:make_request).and_return(Koala::Response.new(500, {"error" => "An error occurred!"}, {}))
|
|
28
|
-
lambda { @api.graph_call(
|
|
26
|
+
lambda { @api.graph_call(KoalaTest.user1, {}) }.should raise_exception(Koala::Facebook::APIError)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "passes the results through GraphCollection.evaluate" do
|
|
30
|
+
result = {}
|
|
31
|
+
@api.stub(:api).and_return(result)
|
|
32
|
+
Koala::Facebook::GraphCollection.should_receive(:evaluate).with(result, @api)
|
|
33
|
+
@api.graph_call("/me")
|
|
29
34
|
end
|
|
35
|
+
|
|
36
|
+
it "returns the results of GraphCollection.evaluate" do
|
|
37
|
+
expected = {}
|
|
38
|
+
@api.stub(:api).and_return([])
|
|
39
|
+
Koala::Facebook::GraphCollection.should_receive(:evaluate).and_return(expected)
|
|
40
|
+
@api.graph_call("/me").should == expected
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "returns the post_processing block's results if one is supplied" do
|
|
44
|
+
other_result = [:a, 2, :three]
|
|
45
|
+
block = Proc.new {|r| other_result}
|
|
46
|
+
@api.stub(:api).and_return({})
|
|
47
|
+
@api.graph_call("/me", {}, "get", {}, &block).should == other_result
|
|
48
|
+
end
|
|
30
49
|
end
|
|
31
50
|
|
|
32
51
|
# SEARCH
|
|
@@ -40,13 +59,13 @@ shared_examples_for "Koala GraphAPI" do
|
|
|
40
59
|
|
|
41
60
|
# get_object
|
|
42
61
|
it "should get public data about a user" do
|
|
43
|
-
result = @api.get_object(
|
|
62
|
+
result = @api.get_object(KoalaTest.user1)
|
|
44
63
|
# the results should have an ID and a name, among other things
|
|
45
64
|
(result["id"] && result["name"]).should_not be_nil
|
|
46
65
|
end
|
|
47
66
|
|
|
48
67
|
it "should get public data about a Page" do
|
|
49
|
-
result = @api.get_object(
|
|
68
|
+
result = @api.get_object(KoalaTest.page)
|
|
50
69
|
# the results should have an ID and a name, among other things
|
|
51
70
|
(result["id"] && result["name"]).should
|
|
52
71
|
end
|
|
@@ -55,14 +74,14 @@ shared_examples_for "Koala GraphAPI" do
|
|
|
55
74
|
results = @api.get_objects([])
|
|
56
75
|
results.should == []
|
|
57
76
|
end
|
|
58
|
-
|
|
77
|
+
|
|
59
78
|
it "should be able to get multiple objects" do
|
|
60
|
-
results = @api.get_objects([
|
|
79
|
+
results = @api.get_objects([KoalaTest.page, KoalaTest.user1])
|
|
61
80
|
results.should have(2).items
|
|
62
81
|
end
|
|
63
82
|
|
|
64
83
|
it "should be able to get multiple objects if they're a string" do
|
|
65
|
-
results = @api.get_objects("contextoptional
|
|
84
|
+
results = @api.get_objects("contextoptional,#{KoalaTest.user1}")
|
|
66
85
|
results.should have(2).items
|
|
67
86
|
end
|
|
68
87
|
|
|
@@ -71,11 +90,11 @@ shared_examples_for "Koala GraphAPI" do
|
|
|
71
90
|
end
|
|
72
91
|
|
|
73
92
|
it "should be able to access a user's picture, given a picture type" do
|
|
74
|
-
@api.get_picture(
|
|
93
|
+
@api.get_picture(KoalaTest.user2, {:type => 'large'}).should =~ /^http[s]*\:\/\//
|
|
75
94
|
end
|
|
76
95
|
|
|
77
96
|
it "should be able to access connections from public Pages" do
|
|
78
|
-
result = @api.get_connections(
|
|
97
|
+
result = @api.get_connections(KoalaTest.page, "photos")
|
|
79
98
|
result.should be_a(Array)
|
|
80
99
|
end
|
|
81
100
|
|
|
@@ -107,7 +126,7 @@ end
|
|
|
107
126
|
|
|
108
127
|
shared_examples_for "Koala GraphAPI with an access token" do
|
|
109
128
|
it "should get private data about a user" do
|
|
110
|
-
result = @api.get_object(
|
|
129
|
+
result = @api.get_object(KoalaTest.user1)
|
|
111
130
|
# updated_time should be a pretty fixed test case
|
|
112
131
|
result["updated_time"].should_not be_nil
|
|
113
132
|
end
|
|
@@ -118,11 +137,11 @@ shared_examples_for "Koala GraphAPI with an access token" do
|
|
|
118
137
|
end
|
|
119
138
|
|
|
120
139
|
it "should be able to get multiple objects" do
|
|
121
|
-
result = @api.get_objects([
|
|
140
|
+
result = @api.get_objects([KoalaTest.page, KoalaTest.user1])
|
|
122
141
|
result.length.should == 2
|
|
123
142
|
end
|
|
124
143
|
it "should be able to access connections from users" do
|
|
125
|
-
result = @api.get_connections(
|
|
144
|
+
result = @api.get_connections(KoalaTest.user2, "friends")
|
|
126
145
|
result.length.should > 0
|
|
127
146
|
end
|
|
128
147
|
|
|
@@ -168,52 +187,63 @@ shared_examples_for "Koala GraphAPI with an access token" do
|
|
|
168
187
|
|
|
169
188
|
describe ".put_picture" do
|
|
170
189
|
it "should be able to post photos to the user's wall with an open file object" do
|
|
171
|
-
content_type = "image/jpg"
|
|
190
|
+
content_type = "image/jpg"
|
|
172
191
|
file = File.open(File.join(File.dirname(__FILE__), "..", "fixtures", "beach.jpg"))
|
|
173
|
-
|
|
192
|
+
|
|
174
193
|
result = @api.put_picture(file, content_type)
|
|
175
|
-
@temporary_object_id = result["id"]
|
|
194
|
+
@temporary_object_id = result["id"]
|
|
176
195
|
@temporary_object_id.should_not be_nil
|
|
177
196
|
end
|
|
178
197
|
|
|
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
198
|
it "should be able to post photos to the user's wall without an open file object" do
|
|
188
199
|
content_type = "image/jpg",
|
|
189
200
|
file_path = File.join(File.dirname(__FILE__), "..", "fixtures", "beach.jpg")
|
|
190
|
-
|
|
201
|
+
|
|
191
202
|
result = @api.put_picture(file_path, content_type)
|
|
192
|
-
@temporary_object_id = result["id"]
|
|
203
|
+
@temporary_object_id = result["id"]
|
|
193
204
|
@temporary_object_id.should_not be_nil
|
|
194
205
|
end
|
|
195
|
-
|
|
206
|
+
|
|
196
207
|
it "should be able to verify a photo posted to a user's wall" do
|
|
197
208
|
content_type = "image/jpg",
|
|
198
209
|
file_path = File.join(File.dirname(__FILE__), "..", "fixtures", "beach.jpg")
|
|
199
|
-
|
|
210
|
+
|
|
200
211
|
expected_message = "This is the test message"
|
|
201
|
-
|
|
212
|
+
|
|
202
213
|
result = @api.put_picture(file_path, content_type, :message => expected_message)
|
|
203
|
-
@temporary_object_id = result["id"]
|
|
214
|
+
@temporary_object_id = result["id"]
|
|
204
215
|
@temporary_object_id.should_not be_nil
|
|
205
|
-
|
|
216
|
+
|
|
206
217
|
get_result = @api.get_object(@temporary_object_id)
|
|
207
218
|
get_result["name"].should == expected_message
|
|
208
219
|
end
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
describe "using a URL instead of a file" do
|
|
223
|
+
before :each do
|
|
224
|
+
@url = "http://img.slate.com/images/redesign2008/slate_logo.gif"
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
it "should be able to post photo to the user's wall using a URL" do
|
|
228
|
+
result = @api.put_picture(@url)
|
|
229
|
+
@temporary_object_id = result["id"]
|
|
230
|
+
@temporary_object_id.should_not be_nil
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
it "should be able to post photo to the user's wall using a URL and an additional param" do
|
|
234
|
+
result = @api.put_picture(@url, :message => "my message")
|
|
235
|
+
@temporary_object_id = result["id"]
|
|
236
|
+
@temporary_object_id.should_not be_nil
|
|
237
|
+
end
|
|
238
|
+
end
|
|
209
239
|
end
|
|
210
|
-
|
|
240
|
+
|
|
211
241
|
describe ".put_video" do
|
|
212
242
|
before :each do
|
|
213
|
-
@cat_movie = File.join(File.dirname(__FILE__), "..", "fixtures", "cat.m4v")
|
|
214
|
-
@content_type = "video/mpeg4"
|
|
243
|
+
@cat_movie = File.join(File.dirname(__FILE__), "..", "fixtures", "cat.m4v")
|
|
244
|
+
@content_type = "video/mpeg4"
|
|
215
245
|
end
|
|
216
|
-
|
|
246
|
+
|
|
217
247
|
it "should set options[:video] to true" do
|
|
218
248
|
source = stub("UploadIO")
|
|
219
249
|
Koala::UploadableIO.stub(:new).and_return(source)
|
|
@@ -221,26 +251,19 @@ shared_examples_for "Koala GraphAPI with an access token" do
|
|
|
221
251
|
Koala.should_receive(:make_request).with(anything, anything, anything, hash_including(:video => true)).and_return(Koala::Response.new(200, "[]", {}))
|
|
222
252
|
@api.put_video("foo")
|
|
223
253
|
end
|
|
224
|
-
|
|
254
|
+
|
|
225
255
|
it "should be able to post videos to the user's wall with an open file object" do
|
|
226
256
|
file = File.open(@cat_movie)
|
|
227
257
|
|
|
228
258
|
result = @api.put_video(file, @content_type)
|
|
229
|
-
@temporary_object_id = result["id"]
|
|
259
|
+
@temporary_object_id = result["id"]
|
|
230
260
|
@temporary_object_id.should_not be_nil
|
|
231
261
|
end
|
|
232
262
|
|
|
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
263
|
|
|
241
264
|
it "should be able to post videos to the user's wall without an open file object" do
|
|
242
265
|
result = @api.put_video(@cat_movie, @content_type)
|
|
243
|
-
@temporary_object_id = result["id"]
|
|
266
|
+
@temporary_object_id = result["id"]
|
|
244
267
|
@temporary_object_id.should_not be_nil
|
|
245
268
|
end
|
|
246
269
|
|
|
@@ -290,6 +313,12 @@ shared_examples_for "Koala GraphAPI with an access token" do
|
|
|
290
313
|
like_result.should be_true
|
|
291
314
|
end
|
|
292
315
|
|
|
316
|
+
# Page Access Token Support
|
|
317
|
+
it "gets a page's access token" do
|
|
318
|
+
# we can't test this live since test users (or random real users) can't be guaranteed to have pages to manage
|
|
319
|
+
@api.should_receive(:api).with("my_page", {:fields => "access_token"}, "get", anything)
|
|
320
|
+
@api.get_page_access_token("my_page")
|
|
321
|
+
end
|
|
293
322
|
|
|
294
323
|
# test all methods to make sure they pass data through to the API
|
|
295
324
|
# we run the tests here (rather than in the common shared example group)
|
|
@@ -339,23 +368,17 @@ end
|
|
|
339
368
|
|
|
340
369
|
# GraphCollection
|
|
341
370
|
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
371
|
describe "when getting a collection" do
|
|
349
372
|
# GraphCollection methods
|
|
350
373
|
it "should get a GraphCollection when getting connections" do
|
|
351
|
-
@result = @api.get_connections(
|
|
374
|
+
@result = @api.get_connections(KoalaTest.page, "photos")
|
|
352
375
|
@result.should be_a(Koala::Facebook::GraphCollection)
|
|
353
376
|
end
|
|
354
377
|
|
|
355
378
|
it "should return nil if the get_collections call fails with nil" do
|
|
356
379
|
# this happens sometimes
|
|
357
380
|
@api.should_receive(:graph_call).and_return(nil)
|
|
358
|
-
@api.get_connections(
|
|
381
|
+
@api.get_connections(KoalaTest.page, "photos").should be_nil
|
|
359
382
|
end
|
|
360
383
|
|
|
361
384
|
it "should get a GraphCollection when searching" do
|
|
@@ -370,74 +393,14 @@ shared_examples_for "Koala GraphAPI with GraphCollection" do
|
|
|
370
393
|
end
|
|
371
394
|
|
|
372
395
|
it "should get a GraphCollection when paging through results" do
|
|
373
|
-
@results = @api.get_page(["search", {"q"=>"facebook", "limit"=>"25", "until"=>
|
|
396
|
+
@results = @api.get_page(["search", {"q"=>"facebook", "limit"=>"25", "until"=> KoalaTest.search_time}])
|
|
374
397
|
@results.should be_a(Koala::Facebook::GraphCollection)
|
|
375
398
|
end
|
|
376
399
|
|
|
377
400
|
it "should return nil if the page call fails with nil" do
|
|
378
401
|
# this happens sometimes
|
|
379
402
|
@api.should_receive(:graph_call).and_return(nil)
|
|
380
|
-
@api.get_page(["search", {"q"=>"facebook", "limit"=>"25", "until"=>
|
|
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
|
|
403
|
+
@api.get_page(["search", {"q"=>"facebook", "limit"=>"25", "until"=> KoalaTest.search_time}]).should be_nil
|
|
441
404
|
end
|
|
442
405
|
end
|
|
443
406
|
end
|
|
@@ -456,12 +419,11 @@ shared_examples_for "Koala GraphAPI without an access token" do
|
|
|
456
419
|
end
|
|
457
420
|
|
|
458
421
|
it "shouldn't be able to access connections from users" do
|
|
459
|
-
lambda { @api.get_connections("lukeshepard", "
|
|
422
|
+
lambda { @api.get_connections("lukeshepard", "friends") }.should raise_error(Koala::Facebook::APIError)
|
|
460
423
|
end
|
|
461
424
|
|
|
462
425
|
it "should not be able to put an object" do
|
|
463
426
|
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
427
|
end
|
|
466
428
|
|
|
467
429
|
# these are not strictly necessary as the other put methods resolve to put_object, but are here for completeness
|
|
@@ -470,13 +432,11 @@ shared_examples_for "Koala GraphAPI without an access token" do
|
|
|
470
432
|
attachment = {:name => "OAuth Playground", :link => "http://oauth.twoalex.com/"}
|
|
471
433
|
@result = @api.put_wall_post("Hello, world", attachment, "contextoptional")
|
|
472
434
|
end).should raise_error(Koala::Facebook::APIError)
|
|
473
|
-
puts "Error! Object #{@result.inspect} somehow put onto Context Optional's wall!" if @result
|
|
474
435
|
end
|
|
475
436
|
|
|
476
437
|
it "should not be able to comment on an object" do
|
|
477
438
|
# random public post on the ContextOptional wall
|
|
478
439
|
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
440
|
end
|
|
481
441
|
|
|
482
442
|
it "should not be able to like an object" do
|
|
@@ -1,18 +1,42 @@
|
|
|
1
1
|
# when testing across Ruby versions, we found that JSON string creation inconsistently ordered keys
|
|
2
2
|
# which is a problem because our mock testing service ultimately matches strings to see if requests are mocked
|
|
3
3
|
# this fix solves that problem by ensuring all hashes are created with a consistent key order every time
|
|
4
|
-
|
|
5
4
|
module MultiJson
|
|
6
5
|
self.engine = :ok_json
|
|
6
|
+
|
|
7
|
+
class << self
|
|
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 fails if we don't assign the ternary result as a local variable
|
|
11
|
+
# separately from calling encode_original)
|
|
12
|
+
encode_original(sort_object(object))
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
alias_method :encode_original, :encode
|
|
16
|
+
alias_method :encode, :encode_with_ordering
|
|
7
17
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
def decode_with_ordering(string)
|
|
19
|
+
sort_object(decode_original(string))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
alias_method :decode_original, :decode
|
|
23
|
+
alias_method :decode, :decode_with_ordering
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def sort_object(object)
|
|
28
|
+
if object.is_a?(Hash)
|
|
29
|
+
sort_hash(object)
|
|
30
|
+
elsif object.is_a?(Array)
|
|
31
|
+
object.collect {|item| item.is_a?(Hash) ? sort_hash(item) : item}
|
|
32
|
+
else
|
|
33
|
+
object
|
|
34
|
+
end
|
|
35
|
+
end
|
|
15
36
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
37
|
+
def sort_hash(unsorted_hash)
|
|
38
|
+
sorted_hash = KoalaTest::OrderedHash.new(sorted_hash)
|
|
39
|
+
unsorted_hash.keys.sort {|a, b| a.to_s <=> b.to_s}.inject(sorted_hash) {|hash, k| hash[k] = unsorted_hash[k]; hash}
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# small helper method for live testing
|
|
2
|
+
module KoalaTest
|
|
3
|
+
|
|
4
|
+
class << self
|
|
5
|
+
attr_accessor :oauth_token, :app_id, :secret, :app_access_token, :code, :session_key
|
|
6
|
+
attr_accessor :oauth_test_data, :subscription_test_data, :search_time
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Test setup
|
|
10
|
+
|
|
11
|
+
def self.setup_test_environment!
|
|
12
|
+
setup_rspec
|
|
13
|
+
|
|
14
|
+
unless ENV['LIVE']
|
|
15
|
+
# By default the Koala specs are run using stubs for HTTP requests,
|
|
16
|
+
# so they won't fail due to Facebook-imposed rate limits or server timeouts.
|
|
17
|
+
#
|
|
18
|
+
# However as a result they are more brittle since
|
|
19
|
+
# we are not testing the latest responses from the Facebook servers.
|
|
20
|
+
# To be certain all specs pass with the current Facebook services,
|
|
21
|
+
# run LIVE=true bundle exec rake spec.
|
|
22
|
+
Koala.http_service = Koala::MockHTTPService
|
|
23
|
+
KoalaTest.setup_test_data(Koala::MockHTTPService::TEST_DATA)
|
|
24
|
+
else
|
|
25
|
+
# Runs Koala specs through the Facebook servers
|
|
26
|
+
# using data for a real app
|
|
27
|
+
live_data = YAML.load_file(File.join(File.dirname(__FILE__), '../fixtures/facebook_data.yml'))
|
|
28
|
+
KoalaTest.setup_test_data(live_data)
|
|
29
|
+
|
|
30
|
+
# allow live tests with different adapters
|
|
31
|
+
adapter = ENV['ADAPTER'] || "typhoeus"# use Typhoeus by default if available
|
|
32
|
+
begin
|
|
33
|
+
require adapter
|
|
34
|
+
Faraday.default_adapter = adapter.to_sym
|
|
35
|
+
rescue LoadError
|
|
36
|
+
puts "Unable to load adapter #{adapter}, using Net::HTTP."
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
Koala.http_service.http_options[:beta] = true if ENV["beta"] || ENV["BETA"]
|
|
40
|
+
|
|
41
|
+
# use a test user unless the developer wants to test against a real profile
|
|
42
|
+
unless token = KoalaTest.oauth_token
|
|
43
|
+
KoalaTest.setup_test_users
|
|
44
|
+
else
|
|
45
|
+
KoalaTest.validate_user_info(token)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.setup_rspec
|
|
51
|
+
# set up a global before block to set the token for tests
|
|
52
|
+
# set the token up for
|
|
53
|
+
RSpec.configure do |config|
|
|
54
|
+
config.before :each do
|
|
55
|
+
@token = KoalaTest.oauth_token
|
|
56
|
+
Koala::Utils.stub(:deprecate) # never fire deprecation warnings
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
config.after :each do
|
|
60
|
+
# clean up any objects posted to Facebook
|
|
61
|
+
if @temporary_object_id && !KoalaTest.mock_interface?
|
|
62
|
+
api = @api || (@test_users ? @test_users.graph_api : nil)
|
|
63
|
+
raise "Unable to locate API when passed temporary object to delete!" unless api
|
|
64
|
+
|
|
65
|
+
# wait 10ms to allow Facebook to propagate data so we can delete it
|
|
66
|
+
sleep(0.01)
|
|
67
|
+
|
|
68
|
+
# clean up any objects we've posted
|
|
69
|
+
result = (api.delete_object(@temporary_object_id) rescue false)
|
|
70
|
+
# if we errored out or Facebook returned false, track that
|
|
71
|
+
puts "Unable to delete #{@temporary_object_id}: #{result} (probably a photo or video, which can't be deleted through the API)" unless result
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def self.setup_test_data(data)
|
|
78
|
+
# make data accessible to all our tests
|
|
79
|
+
self.oauth_test_data = data["oauth_test_data"]
|
|
80
|
+
self.subscription_test_data = data["subscription_test_data"]
|
|
81
|
+
self.oauth_token = data["oauth_token"]
|
|
82
|
+
self.app_id = data["oauth_test_data"]["app_id"]
|
|
83
|
+
self.app_access_token = data["oauth_test_data"]["app_access_token"]
|
|
84
|
+
self.secret = data["oauth_test_data"]["secret"]
|
|
85
|
+
self.code = data["oauth_test_data"]["code"]
|
|
86
|
+
self.session_key = data["oauth_test_data"]["session_key"]
|
|
87
|
+
|
|
88
|
+
# fix the search time so it can be used in the mock responses
|
|
89
|
+
self.search_time = data["search_time"] || (Time.now - 3600).to_s
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def self.testing_permissions
|
|
93
|
+
"read_stream, publish_stream, user_photos, user_videos, read_insights"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def self.setup_test_users
|
|
97
|
+
# note: we don't have to delete the two test users explicitly, since the test user specs do that for us
|
|
98
|
+
# technically, this is a point of brittleness and would break if the tests were run out of order
|
|
99
|
+
# however, for now we can live with it since it would slow tests way too much to constantly recreate our test users
|
|
100
|
+
print "Setting up test users..."
|
|
101
|
+
@test_user_api = Koala::Facebook::TestUsers.new(:app_id => self.app_id, :secret => self.secret)
|
|
102
|
+
|
|
103
|
+
RSpec.configure do |config|
|
|
104
|
+
config.before :all do
|
|
105
|
+
# before each test module, create two test users with specific names and befriend them
|
|
106
|
+
KoalaTest.create_test_users
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
config.after :all do
|
|
110
|
+
# after each test module, delete the test users to avoid cluttering up the application
|
|
111
|
+
KoalaTest.destroy_test_users
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
puts "done."
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def self.create_test_users
|
|
119
|
+
@live_testing_user = @test_user_api.create(true, KoalaTest.testing_permissions, :name => KoalaTest.user1_name)
|
|
120
|
+
@live_testing_friend = @test_user_api.create(true, KoalaTest.testing_permissions, :name => KoalaTest.user2_name)
|
|
121
|
+
@test_user_api.befriend(@live_testing_user, @live_testing_friend)
|
|
122
|
+
self.oauth_token = @live_testing_user["access_token"]
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def self.destroy_test_users
|
|
126
|
+
[@live_testing_user, @live_testing_friend].each do |u|
|
|
127
|
+
puts "Unable to delete test user #{u.inspect}" if u && !(@test_user_api.delete(u) rescue false)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def self.validate_user_info(token)
|
|
132
|
+
print "Validating permissions for live testing..."
|
|
133
|
+
# make sure we have the necessary permissions
|
|
134
|
+
api = Koala::Facebook::API.new(token)
|
|
135
|
+
perms = api.fql_query("select #{testing_permissions} from permissions where uid = me()")[0]
|
|
136
|
+
perms.each_pair do |perm, value|
|
|
137
|
+
if value == (perm == "read_insights" ? 1 : 0) # live testing depends on insights calls failing
|
|
138
|
+
puts "failed!\n" # put a new line after the print above
|
|
139
|
+
raise ArgumentError, "Your access token must have the read_stream, publish_stream, and user_photos permissions, and lack read_insights. You have: #{perms.inspect}"
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
puts "done!"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Info about the testing environment
|
|
146
|
+
def self.real_user?
|
|
147
|
+
!(mock_interface? || @test_user)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def self.test_user?
|
|
151
|
+
!!@test_user_api
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def self.mock_interface?
|
|
155
|
+
Koala.http_service == Koala::MockHTTPService
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Data for testing
|
|
159
|
+
def self.user1
|
|
160
|
+
test_user? ? @live_testing_user["id"] : "koppel"
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def self.user1_id
|
|
164
|
+
test_user? ? @live_testing_user["id"] : 2905623
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def self.user1_name
|
|
168
|
+
"Alex"
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def self.user2
|
|
172
|
+
test_user? ? @live_testing_friend["id"] : "lukeshepard"
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def self.user2_id
|
|
176
|
+
test_user? ? @live_testing_friend["id"] : 2901279
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def self.user2_name
|
|
180
|
+
"Luke"
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def self.page
|
|
184
|
+
"contextoptional"
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
end
|
|
@@ -18,7 +18,8 @@ module Koala
|
|
|
18
18
|
TEST_DATA = YAML.load_file(File.join(File.dirname(__FILE__), '..', 'fixtures', 'facebook_data.yml'))
|
|
19
19
|
TEST_DATA.merge!('oauth_token' => Koala::MockHTTPService::ACCESS_TOKEN)
|
|
20
20
|
TEST_DATA['oauth_test_data'].merge!('code' => Koala::MockHTTPService::OAUTH_CODE)
|
|
21
|
-
|
|
21
|
+
TEST_DATA['search_time'] = (Time.now - 3600).to_s
|
|
22
|
+
|
|
22
23
|
# Useful in mock_facebook_responses.yml
|
|
23
24
|
OAUTH_DATA = TEST_DATA['oauth_test_data']
|
|
24
25
|
OAUTH_DATA.merge!({
|
|
@@ -38,7 +39,7 @@ module Koala
|
|
|
38
39
|
path = 'root' if path == '' || path == '/'
|
|
39
40
|
verb ||= 'get'
|
|
40
41
|
server = options[:rest_api] ? 'rest_api' : 'graph_api'
|
|
41
|
-
token = args.delete('access_token')
|
|
42
|
+
token = args.delete('access_token')
|
|
42
43
|
with_token = (token == ACCESS_TOKEN || token == APP_ACCESS_TOKEN) ? 'with_token' : 'no_token'
|
|
43
44
|
|
|
44
45
|
# Assume format is always JSON
|
|
@@ -77,11 +78,13 @@ module Koala
|
|
|
77
78
|
response_object
|
|
78
79
|
end
|
|
79
80
|
|
|
80
|
-
def self.
|
|
81
|
-
|
|
81
|
+
def self.encode_params(*args)
|
|
82
|
+
# use HTTPService's encode_params
|
|
83
|
+
HTTPService.encode_params(*args)
|
|
82
84
|
end
|
|
83
85
|
|
|
84
86
|
protected
|
|
87
|
+
|
|
85
88
|
def self.create_params_key(params_hash)
|
|
86
89
|
if params_hash.empty?
|
|
87
90
|
'no_args'
|
|
@@ -93,4 +96,4 @@ module Koala
|
|
|
93
96
|
end
|
|
94
97
|
end
|
|
95
98
|
end
|
|
96
|
-
end
|
|
99
|
+
end
|