koala 1.1.0rc2 → 1.1.0rc3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.travis.yml +8 -0
- data/CHANGELOG +11 -6
- data/koala.gemspec +5 -5
- data/lib/koala.rb +10 -5
- data/lib/koala/batch_operation.rb +74 -0
- data/lib/koala/graph_api.rb +78 -117
- data/lib/koala/graph_batch_api.rb +87 -0
- data/lib/koala/graph_collection.rb +54 -0
- data/lib/koala/http_services.rb +5 -3
- data/lib/koala/http_services/net_http_service.rb +31 -26
- data/lib/koala/http_services/typhoeus_service.rb +4 -4
- data/lib/koala/oauth.rb +3 -3
- data/lib/koala/rest_api.rb +1 -1
- data/lib/koala/uploadable_io.rb +122 -90
- data/readme.md +6 -6
- data/spec/cases/api_base_spec.rb +2 -2
- data/spec/cases/graph_api_batch_spec.rb +171 -162
- data/spec/cases/http_services/http_service_spec.rb +27 -27
- data/spec/cases/http_services/net_http_service_spec.rb +169 -103
- data/spec/cases/oauth_spec.rb +1 -1
- data/spec/cases/realtime_updates_spec.rb +3 -3
- data/spec/fixtures/cat.m4v +0 -0
- data/spec/fixtures/mock_facebook_responses.yml +33 -22
- data/spec/spec_helper.rb +1 -1
- data/spec/support/graph_api_shared_examples.rb +79 -35
- data/spec/support/mock_http_service.rb +3 -0
- data/spec/support/rest_api_shared_examples.rb +2 -2
- data/spec/support/setup_mocks_or_live.rb +1 -2
- metadata +10 -5
- data/lib/koala/graph_api_batch.rb +0 -151
data/spec/cases/oauth_spec.rb
CHANGED
@@ -391,7 +391,7 @@ describe "Koala::Facebook::OAuth" do
|
|
391
391
|
# the signed request code is ported directly from Facebook
|
392
392
|
# so we only need to test at a high level that it works
|
393
393
|
it "should throw an error if the algorithm is unsupported" do
|
394
|
-
|
394
|
+
MultiJson.stub(:decode).and_return("algorithm" => "my fun algorithm")
|
395
395
|
lambda { @oauth.parse_signed_request(@signed_request) }.should raise_error
|
396
396
|
end
|
397
397
|
|
@@ -43,19 +43,19 @@ describe "Koala::Facebook::RealtimeUpdates" do
|
|
43
43
|
it "should not allow write access to app_id" do
|
44
44
|
updates = Koala::Facebook::RealtimeUpdates.new(:app_id => @app_id, :app_access_token => @app_access_token)
|
45
45
|
# this should not throw errors
|
46
|
-
|
46
|
+
updates.methods.map(&:to_sym).should_not include(:app_id=)
|
47
47
|
end
|
48
48
|
|
49
49
|
it "should not allow write access to app_access_token" do
|
50
50
|
updates = Koala::Facebook::RealtimeUpdates.new(:app_id => @app_id, :app_access_token => @app_access_token)
|
51
51
|
# this should not throw errors
|
52
|
-
|
52
|
+
updates.methods.map(&:to_sym).should_not include(:app_access_token=)
|
53
53
|
end
|
54
54
|
|
55
55
|
it "should not allow write access to secret" do
|
56
56
|
updates = Koala::Facebook::RealtimeUpdates.new(:app_id => @app_id, :app_access_token => @app_access_token)
|
57
57
|
# this should not throw errors
|
58
|
-
|
58
|
+
updates.methods.map(&:to_sym).should_not include(:secret=)
|
59
59
|
end
|
60
60
|
|
61
61
|
# init with secret / fetching the token
|
Binary file
|
@@ -25,11 +25,11 @@ rest_api:
|
|
25
25
|
no_token: '{"error_code":104,"error_msg":"Requires valid signature","request_args":[{"key":"method","value":"fql.query"},{"key":"format","value":"json"},{"key":"query","value":"select read_stream from permissions where uid = 216743"}]}'
|
26
26
|
|
27
27
|
/method/fql.multiquery:
|
28
|
-
'queries
|
28
|
+
'queries=<%= MultiJson.encode({"query1" => "select post_id from stream where source_id = me()", "query2" => "select fromid from comment where post_id in (select post_id from #query1)", "query3" => "select uid, name from user where uid in (select fromid from #query2)"}) %>':
|
29
29
|
get:
|
30
30
|
with_token: '[{"name":"query1", "fql_result_set":[]},{"name":"query2", "fql_result_set":[]},{"name":"query3", "fql_result_set":[]}]'
|
31
31
|
no_token: '{"error_code":104,"error_msg":"Requires valid signature","request_args":[{"key":"method","value":"fql.query"},{"key":"format","value":"json"},{"key":"query","value":"select read_stream from permissions where uid = 216743"}]}'
|
32
|
-
'queries
|
32
|
+
'queries=<%= MultiJson.encode({"query1" => "select first_name from user where uid = 216743", "query2" => "select first_name from user where uid = 2905623"}) %>':
|
33
33
|
get:
|
34
34
|
with_token: '[{"name":"query1", "fql_result_set":[{"first_name":"Chris"}]},{"name":"query2", "fql_result_set":[{"first_name":"Alex"}]}]'
|
35
35
|
no_token: '[{"name":"query1", "fql_result_set":[{"first_name":"Chris"}]},{"name":"query2", "fql_result_set":[{"first_name":"Alex"}]}]'
|
@@ -74,52 +74,53 @@ graph_api:
|
|
74
74
|
with_token: '{"contextoptional":"{}","naitik":"{}"}'
|
75
75
|
no_token: '{"contextoptional":"{}","naitik":"{}"}'
|
76
76
|
# Ruby 1.8.7 and 1.9.2 generate JSON with different key ordering, hence we have to dynamically generate it here
|
77
|
-
batch=<%= [{"method" => "get", "relative_url" => "me"},{"method" => "get", "relative_url" => "koppel"}]
|
77
|
+
batch=<%= MultiJson.encode([{"method" => "get", "relative_url" => "me"},{"method" => "get", "relative_url" => "koppel"}]) %>:
|
78
78
|
post:
|
79
79
|
with_token: '[{"body":"{\"id\":\"123\"}"}, {"body":"{\"id\":\"456\"}"}]'
|
80
|
-
batch=<%= [{"method" => "get", "relative_url" => "me/picture"}]
|
80
|
+
batch=<%= MultiJson.encode([{"method" => "get", "relative_url" => "me/picture"}]) %>:
|
81
81
|
post:
|
82
82
|
with_token: '[{"headers":[{"name":"Location","value":"http://google.com"}]}]'
|
83
|
-
batch=<%= [{"method" => "get", "relative_url" => "me"},{"method" => "get", "relative_url" => "me/friends"}]
|
83
|
+
batch=<%= MultiJson.encode([{"method" => "get", "relative_url" => "me"},{"method" => "get", "relative_url" => "me/friends"}]) %>:
|
84
84
|
post:
|
85
85
|
with_token: '[{"body":"{\"id\":\"123\"}"}, {"body":"{\"data\":[]}"}]'
|
86
|
-
batch=<%= [{"method"=>"get", "relative_url"=>"me"}, {"method"=>"get", "relative_url"=>"#{OAUTH_DATA["app_id"]}/insights?access_token=#{CGI.escape APP_ACCESS_TOKEN}"}]
|
86
|
+
batch=<%= MultiJson.encode([{"method"=>"get", "relative_url"=>"me"}, {"method"=>"get", "relative_url"=>"#{OAUTH_DATA["app_id"]}/insights?access_token=#{CGI.escape APP_ACCESS_TOKEN}"}]) %>:
|
87
87
|
post:
|
88
88
|
with_token: '[{"body":"{\"id\":\"123\"}"}, {"body":"{\"data\":[]}"}]'
|
89
|
-
batch=<%= [{"method"=>"get", "relative_url"=>"#{OAUTH_DATA["app_id"]}/insights"}, {"method"=>"get", "relative_url"=>"koppel?access_token=#{CGI.escape APP_ACCESS_TOKEN}"}]
|
89
|
+
batch=<%= MultiJson.encode([{"method"=>"get", "relative_url"=>"#{OAUTH_DATA["app_id"]}/insights"}, {"method"=>"get", "relative_url"=>"koppel?access_token=#{CGI.escape APP_ACCESS_TOKEN}"}]) %>:
|
90
90
|
post:
|
91
91
|
with_token: '[{"body": "{\"error\":{\"type\":\"AnError\", \"message\":\"An error occurred!.\"}}"},{"body":"{\"id\":\"123\"}"}]'
|
92
|
-
batch=<%= [{"method"=>"post", "relative_url"=>"FEED_ITEM_BATCH/likes"}, {"method"=>"delete", "relative_url"=> "FEED_ITEM_BATCH"}]
|
92
|
+
batch=<%= MultiJson.encode([{"method"=>"post", "relative_url"=>"FEED_ITEM_BATCH/likes"}, {"method"=>"delete", "relative_url"=> "FEED_ITEM_BATCH"}]) %>:
|
93
93
|
post:
|
94
94
|
with_token: '[{"body": "{\"id\": \"MOCK_LIKE\"}"},{"body":true}]'
|
95
|
-
batch=<%= [{"method" => "get", "relative_url" => "me/friends?limit=5", "name" => "get-friends"}, {"method" => "get", "relative_url" => "?ids=#{CGI.escape "{result=get-friends:$.data.*.id}"}"}]
|
95
|
+
batch=<%= MultiJson.encode([{"method" => "get", "relative_url" => "me/friends?limit=5", "name" => "get-friends"}, {"method" => "get", "relative_url" => "?ids=#{CGI.escape "{result=get-friends:$.data.*.id}"}"}]) %>:
|
96
96
|
post:
|
97
97
|
with_token: '[null,{"body":"{}"}]'
|
98
|
-
batch=<%= [{"method" => "get", "relative_url" => "me/friends?limit=5", "name" => "get-friends", "omit_response_on_success" => false}, {"method" => "get", "relative_url" => "?ids=#{CGI.escape "{result=get-friends:$.data.*.id}"}"}]
|
98
|
+
batch=<%= MultiJson.encode([{"method" => "get", "relative_url" => "me/friends?limit=5", "name" => "get-friends", "omit_response_on_success" => false}, {"method" => "get", "relative_url" => "?ids=#{CGI.escape "{result=get-friends:$.data.*.id}"}"}]) %>:
|
99
99
|
post:
|
100
100
|
with_token: '[{"body":"{\"data\":[]}"},{"body":"{}"}]'
|
101
|
-
batch=<%= [{"method" => "get", "relative_url" => "me/friends?limit=5"}, {"method" => "get", "relative_url" => "?ids=#{CGI.escape "{result=i-dont-exist:$.data.*.id}"}"}]
|
101
|
+
batch=<%= MultiJson.encode([{"method" => "get", "relative_url" => "me/friends?limit=5"}, {"method" => "get", "relative_url" => "?ids=#{CGI.escape "{result=i-dont-exist:$.data.*.id}"}"}]) %>:
|
102
102
|
post:
|
103
103
|
with_token: '{"error":190,"error_description":"Error validating access token."}'
|
104
|
-
batch=<%= [{"method" => "post", "relative_url" => "method/fql.query", "body" => "query=select+name+from+user+where+uid%3D4"}]
|
104
|
+
batch=<%= MultiJson.encode([{"method" => "post", "relative_url" => "method/fql.query", "body" => "query=select+name+from+user+where+uid%3D4"}]) %>:
|
105
105
|
post:
|
106
106
|
with_token: '[{"body":"[{\"name\":\"Mark Zuckerberg\"}]"}]'
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
batch=<%= [{"method"=>"get", "relative_url"=>"#{OAUTH_DATA["app_id"]}/insights", "name" => "getdata"}, {"method"=>"get", "relative_url"=>"koppel", "depends_on" => "getdata"}].to_json %>:
|
107
|
+
|
108
|
+
# dependencies
|
109
|
+
batch=<%= MultiJson.encode([{"method"=>"get", "relative_url"=>"me", "name" => "getme"}, {"method"=>"get", "relative_url"=>"koppel", "depends_on" => "getme"}]) %>:
|
110
|
+
post: &batch_dependent
|
111
|
+
with_token: '[null,{"body":"{\"id\":\"123\"}"}]'
|
112
|
+
batch=<%= MultiJson.encode([{"method"=>"get", "relative_url"=>"#{OAUTH_DATA["app_id"]}/insights", "name" => "getdata"}, {"method"=>"get", "relative_url"=>"koppel", "depends_on" => "getdata"}]) %>:
|
114
113
|
post:
|
115
114
|
with_token: '[{"body": "{\"error\":{\"type\":\"AnError\", \"message\":\"An error occurred!.\"}}"},null]'
|
116
|
-
|
115
|
+
|
116
|
+
# attached files tests
|
117
|
+
batch=<%= MultiJson.encode([{"method"=>"post", "relative_url"=>"me/photos", "attached_files" => "op1_file0"}]) %>&op1_file0=[FILE]:
|
117
118
|
post:
|
118
119
|
with_token: '[{"body": "{\"error\":{\"type\":\"AnError\", \"message\":\"An error occurred!.\"}}"},null]'
|
119
|
-
batch=<%= [{"method"=>"post", "relative_url"=>"me/photos", "attached_files" => "
|
120
|
+
batch=<%= MultiJson.encode([{"method"=>"post", "relative_url"=>"me/photos", "attached_files" => "op1_file0"}]) %>&op1_file0=[FILE]:
|
120
121
|
post:
|
121
122
|
with_token: '[{"body":"{\"id\": \"MOCK_PHOTO\"}"}]'
|
122
|
-
batch=<%= [{"method"=>"post", "relative_url"=>"me/photos", "attached_files" => "
|
123
|
+
batch=<%= MultiJson.encode([{"method"=>"post", "relative_url"=>"me/photos", "attached_files" => "op1_file0"}, {"method"=>"post", "relative_url"=>"koppel/photos", "attached_files" => "op2_file0"}]) %>&op1_file0=[FILE]&op2_file0=[FILE]:
|
123
124
|
post:
|
124
125
|
with_token: '[{"body":"{\"id\": \"MOCK_PHOTO\"}"}, {"body":"{\"id\": \"MOCK_PHOTO\"}"}]'
|
125
126
|
|
@@ -161,6 +162,16 @@ graph_api:
|
|
161
162
|
post:
|
162
163
|
<<: *token_required
|
163
164
|
with_token: '{"id": "MOCK_PHOTO"}'
|
165
|
+
/me/videos:
|
166
|
+
source=[FILE]:
|
167
|
+
post:
|
168
|
+
<<: *token_required
|
169
|
+
with_token: '{"id": "MOCK_PHOTO"}'
|
170
|
+
message=This is the test message&source=[FILE]:
|
171
|
+
post:
|
172
|
+
<<: *token_required
|
173
|
+
with_token: '{"id": "MOCK_PHOTO"}'
|
174
|
+
|
164
175
|
/koppel:
|
165
176
|
no_args:
|
166
177
|
get:
|
data/spec/spec_helper.rb
CHANGED
@@ -166,44 +166,87 @@ shared_examples_for "Koala GraphAPI with an access token" do
|
|
166
166
|
@temporary_object_id.should_not be_nil
|
167
167
|
end
|
168
168
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
result = @api.put_picture(file, content_type)
|
174
|
-
@temporary_object_id = result["id"]
|
175
|
-
@temporary_object_id.should_not be_nil
|
176
|
-
end
|
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"))
|
177
173
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
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")
|
185
190
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
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
|
193
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
|
194
216
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
@temporary_object_id = result["id"]
|
203
|
-
@temporary_object_id.should_not be_nil
|
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
|
204
224
|
|
205
|
-
|
206
|
-
|
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
|
207
250
|
end
|
208
251
|
|
209
252
|
it "should be able to verify a message with an attachment posted to a feed" do
|
@@ -267,6 +310,7 @@ shared_examples_for "Koala GraphAPI with an access token" do
|
|
267
310
|
:search => 3,
|
268
311
|
# methods that have special arguments
|
269
312
|
:put_picture => ["x.jpg", "image/jpg", {}, "me"],
|
313
|
+
:put_video => ["x.mp4", "video/mpeg4", {}, "me"],
|
270
314
|
:get_objects => [["x"], {}]
|
271
315
|
}.each_pair do |method_name, params|
|
272
316
|
it "should pass http options through for #{method_name}" do
|
@@ -343,8 +387,8 @@ shared_examples_for "Koala GraphAPI with GraphCollection" do
|
|
343
387
|
end
|
344
388
|
|
345
389
|
it "should have a read-only paging attribute" do
|
346
|
-
|
347
|
-
|
390
|
+
@result.methods.map(&:to_sym).should include(:paging)
|
391
|
+
@result.methods.map(&:to_sym).should_not include(:paging=)
|
348
392
|
end
|
349
393
|
|
350
394
|
describe "when getting a whole page" do
|
@@ -5,6 +5,9 @@ module Koala
|
|
5
5
|
module MockHTTPService
|
6
6
|
include Koala::HTTPService
|
7
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
|
+
|
8
11
|
# Mocks all HTTP requests for with koala_spec_with_mocks.rb
|
9
12
|
# Mocked values to be included in TEST_DATA used in specs
|
10
13
|
ACCESS_TOKEN = '*'
|
@@ -154,13 +154,13 @@ shared_examples_for "Koala RestAPI" do
|
|
154
154
|
"fql.multiquery", anything, anything
|
155
155
|
).and_return({})
|
156
156
|
|
157
|
-
@api.fql_multiquery
|
157
|
+
@api.fql_multiquery 'query string'
|
158
158
|
end
|
159
159
|
|
160
160
|
it "should pass a queries argument" do
|
161
161
|
queries = stub('query string')
|
162
162
|
queries_json = "some JSON"
|
163
|
-
|
163
|
+
MultiJson.stub(:encode).with(queries).and_return(queries_json)
|
164
164
|
|
165
165
|
@api.should_receive(:rest_call).with(
|
166
166
|
anything,
|
@@ -4,8 +4,7 @@ module KoalaTest
|
|
4
4
|
print "Validating permissions for live testing..."
|
5
5
|
# make sure we have the necessary permissions
|
6
6
|
api = Koala::Facebook::GraphAndRestAPI.new(token)
|
7
|
-
|
8
|
-
perms = api.fql_query("select read_stream, publish_stream, user_photos, read_insights from permissions where uid = #{uid}")[0]
|
7
|
+
perms = api.fql_query("select read_stream, publish_stream, user_photos, user_videos, read_insights from permissions where uid = me()")[0]
|
9
8
|
perms.each_pair do |perm, value|
|
10
9
|
if value == (perm == "read_insights" ? 1 : 0) # live testing depends on insights calls failing
|
11
10
|
puts "failed!\n" # put a new line after the print above
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: koala
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease: 5
|
5
|
-
version: 1.1.
|
5
|
+
version: 1.1.0rc3
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Alex Koppel, Chris Baclig, Rafi Jacoby, Context Optional
|
@@ -10,11 +10,11 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2011-06-
|
13
|
+
date: 2011-06-30 00:00:00 +02:00
|
14
14
|
default_executable:
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
|
-
name:
|
17
|
+
name: multi_json
|
18
18
|
requirement: &id001 !ruby/object:Gem::Requirement
|
19
19
|
none: false
|
20
20
|
requirements:
|
@@ -80,6 +80,7 @@ extra_rdoc_files:
|
|
80
80
|
files:
|
81
81
|
- .autotest
|
82
82
|
- .gitignore
|
83
|
+
- .travis.yml
|
83
84
|
- CHANGELOG
|
84
85
|
- Gemfile
|
85
86
|
- LICENSE
|
@@ -88,8 +89,10 @@ files:
|
|
88
89
|
- autotest/discover.rb
|
89
90
|
- koala.gemspec
|
90
91
|
- lib/koala.rb
|
92
|
+
- lib/koala/batch_operation.rb
|
91
93
|
- lib/koala/graph_api.rb
|
92
|
-
- lib/koala/
|
94
|
+
- lib/koala/graph_batch_api.rb
|
95
|
+
- lib/koala/graph_collection.rb
|
93
96
|
- lib/koala/http_services.rb
|
94
97
|
- lib/koala/http_services/net_http_service.rb
|
95
98
|
- lib/koala/http_services/typhoeus_service.rb
|
@@ -113,6 +116,7 @@ files:
|
|
113
116
|
- spec/cases/test_users_spec.rb
|
114
117
|
- spec/cases/uploadable_io_spec.rb
|
115
118
|
- spec/fixtures/beach.jpg
|
119
|
+
- spec/fixtures/cat.m4v
|
116
120
|
- spec/fixtures/facebook_data.yml
|
117
121
|
- spec/fixtures/mock_facebook_responses.yml
|
118
122
|
- spec/spec_helper.rb
|
@@ -139,7 +143,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
139
143
|
requirements:
|
140
144
|
- - ">="
|
141
145
|
- !ruby/object:Gem::Version
|
142
|
-
hash:
|
146
|
+
hash: 1796660115517042142
|
143
147
|
segments:
|
144
148
|
- 0
|
145
149
|
version: "0"
|
@@ -171,6 +175,7 @@ test_files:
|
|
171
175
|
- spec/cases/test_users_spec.rb
|
172
176
|
- spec/cases/uploadable_io_spec.rb
|
173
177
|
- spec/fixtures/beach.jpg
|
178
|
+
- spec/fixtures/cat.m4v
|
174
179
|
- spec/fixtures/facebook_data.yml
|
175
180
|
- spec/fixtures/mock_facebook_responses.yml
|
176
181
|
- spec/spec_helper.rb
|
@@ -1,151 +0,0 @@
|
|
1
|
-
module Koala
|
2
|
-
module Facebook
|
3
|
-
class BatchOperation
|
4
|
-
attr_reader :access_token, :http_options, :post_processing, :files
|
5
|
-
|
6
|
-
def initialize(options = {})
|
7
|
-
@args = (options[:args] || {}).dup # because we modify it below
|
8
|
-
@access_token = options[:access_token]
|
9
|
-
@http_options = (options[:http_options] || {}).dup # dup because we modify it below
|
10
|
-
@batch_args = @http_options.delete(:batch_args) || {}
|
11
|
-
@url = options[:url]
|
12
|
-
@method = options[:method].to_sym
|
13
|
-
@post_processing = options[:post_processing]
|
14
|
-
|
15
|
-
process_binary_args
|
16
|
-
|
17
|
-
raise Koala::KoalaError, "Batch operations require an access token, none provided." unless @access_token
|
18
|
-
end
|
19
|
-
|
20
|
-
def to_batch_params(main_access_token)
|
21
|
-
# set up the arguments
|
22
|
-
args_string = Koala.http_service.encode_params(@access_token == main_access_token ? @args : @args.merge(:access_token => @access_token))
|
23
|
-
|
24
|
-
response = {
|
25
|
-
:method => @method,
|
26
|
-
:relative_url => @url,
|
27
|
-
}
|
28
|
-
|
29
|
-
# handle batch-level arguments, such as name, depends_on, and attached_files
|
30
|
-
@batch_args[:attached_files] = @files.keys.join(",") if @files
|
31
|
-
response.merge!(@batch_args) if @batch_args
|
32
|
-
|
33
|
-
# for get and delete, we append args to the URL string
|
34
|
-
# otherwise, they go in the body
|
35
|
-
if args_string.length > 0
|
36
|
-
if args_in_url?
|
37
|
-
response[:relative_url] += (@url =~ /\?/ ? "&" : "?") + args_string if args_string.length > 0
|
38
|
-
else
|
39
|
-
response[:body] = args_string if args_string.length > 0
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
response
|
44
|
-
end
|
45
|
-
|
46
|
-
protected
|
47
|
-
|
48
|
-
def process_binary_args
|
49
|
-
# collect binary files
|
50
|
-
@args.each_pair do |key, value|
|
51
|
-
if UploadableIO.binary_content?(value)
|
52
|
-
@files ||= {}
|
53
|
-
# we use object_id to ensure unique file identifiers across multiple batch operations
|
54
|
-
# remove it from the original hash and add it to the file store
|
55
|
-
id = "file#{GraphAPI.batch_calls.length}_#{@files.keys.length}"
|
56
|
-
@files[id] = @args.delete(key).is_a?(UploadableIO) ? value : UploadableIO.new(value)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def args_in_url?
|
62
|
-
@method == :get || @method == :delete
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
module GraphAPIBatchMethods
|
67
|
-
def self.included(base)
|
68
|
-
base.class_eval do
|
69
|
-
# batch mode flags
|
70
|
-
def self.batch_mode?
|
71
|
-
!!@batch_mode
|
72
|
-
end
|
73
|
-
|
74
|
-
def self.batch_calls
|
75
|
-
raise KoalaError, "GraphAPI.batch_calls accessed when not in batch block!" unless batch_mode?
|
76
|
-
@batch_calls
|
77
|
-
end
|
78
|
-
|
79
|
-
def self.batch(http_options = {}, &block)
|
80
|
-
@batch_mode = true
|
81
|
-
@batch_http_options = http_options
|
82
|
-
@batch_calls = []
|
83
|
-
yield
|
84
|
-
begin
|
85
|
-
results = batch_api(@batch_calls)
|
86
|
-
ensure
|
87
|
-
@batch_mode = false
|
88
|
-
end
|
89
|
-
results
|
90
|
-
end
|
91
|
-
|
92
|
-
def self.batch_api(batch_calls)
|
93
|
-
return [] unless batch_calls.length > 0
|
94
|
-
# Facebook requires a top-level access token
|
95
|
-
|
96
|
-
# Get the access token for the user and start building a hash to store params
|
97
|
-
# Turn the call args collected into what facebook expects
|
98
|
-
args = {}
|
99
|
-
access_token = args["access_token"] = batch_calls.first.access_token
|
100
|
-
args['batch'] = batch_calls.map { |batch_op|
|
101
|
-
args.merge!(batch_op.files) if batch_op.files
|
102
|
-
batch_op.to_batch_params(access_token)
|
103
|
-
}.to_json
|
104
|
-
|
105
|
-
# Make the POST request for the batch call
|
106
|
-
# batch operations have to go over SSL, but since there's an access token, that secures that
|
107
|
-
result = Koala.make_request('/', args, 'post', @batch_http_options)
|
108
|
-
# Raise an error if we get a 500
|
109
|
-
raise APIError.new("type" => "HTTP #{result.status.to_s}", "message" => "Response body: #{result.body}") if result.status >= 500
|
110
|
-
|
111
|
-
response = JSON.parse(result.body.to_s)
|
112
|
-
# raise an error if we get a Batch API error message
|
113
|
-
raise APIError.new("type" => "Error #{response["error"]}", "message" => response["error_description"]) if response.is_a?(Hash) && response["error"]
|
114
|
-
|
115
|
-
# otherwise, map the results with post-processing included
|
116
|
-
index = 0 # keep compat with ruby 1.8 - no with_index for map
|
117
|
-
response.map do |call_result|
|
118
|
-
# Get the options hash
|
119
|
-
batch_op = batch_calls[index]
|
120
|
-
index += 1
|
121
|
-
|
122
|
-
if call_result
|
123
|
-
# (see note in regular api method about JSON parsing)
|
124
|
-
body = JSON.parse("[#{call_result['body'].to_s}]")[0]
|
125
|
-
unless call_result["code"].to_i >= 500 || error = GraphAPI.check_response(body)
|
126
|
-
# Get the HTTP component they want
|
127
|
-
data = case batch_op.http_options[:http_component]
|
128
|
-
when :status
|
129
|
-
call_result["code"].to_i
|
130
|
-
when :headers
|
131
|
-
# facebook returns the headers as an array of k/v pairs, but we want a regular hash
|
132
|
-
call_result['headers'].inject({}) { |headers, h| headers[h['name']] = h['value']; headers}
|
133
|
-
else
|
134
|
-
body
|
135
|
-
end
|
136
|
-
|
137
|
-
# process it if we are given a block to process with
|
138
|
-
batch_op.post_processing ? batch_op.post_processing.call(data) : data
|
139
|
-
else
|
140
|
-
error || APIError.new({"type" => "HTTP #{call_result["code"].to_s}", "message" => "Response body: #{body}"})
|
141
|
-
end
|
142
|
-
else
|
143
|
-
nil
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
148
|
-
end
|
149
|
-
end
|
150
|
-
end
|
151
|
-
end
|