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,788 @@
1
+ require 'spec_helper'
2
+ require 'json' unless Hash.respond_to?(:to_json)
3
+
4
+ describe "Koala::Facebook::GraphAPI in batch mode" do
5
+
6
+ before :each do
7
+ @api = Koala::Facebook::API.new(@token)
8
+ # app API
9
+ @app_id = KoalaTest.app_id
10
+ @app_access_token = KoalaTest.app_access_token
11
+ @app_api = Koala::Facebook::API.new(@app_access_token)
12
+ end
13
+
14
+ describe Koala::Facebook::GraphBatchAPI::BatchOperation do
15
+ before :each do
16
+ @args = {
17
+ :url => "my url",
18
+ :args => {:a => 2, :b => 3},
19
+ :method => "get",
20
+ :access_token => "12345",
21
+ :http_options => {},
22
+ :post_processing => lambda { }
23
+ }
24
+ end
25
+
26
+ describe ".new" do
27
+ it "makes http_options accessible" do
28
+ expect(Koala::Facebook::GraphBatchAPI::BatchOperation.new(@args).http_options).to eq(@args[:http_options])
29
+ end
30
+
31
+ it "makes post_processing accessible" do
32
+ expect(Koala::Facebook::GraphBatchAPI::BatchOperation.new(@args).post_processing).to eq(@args[:post_processing])
33
+ end
34
+
35
+ it "makes access_token accessible" do
36
+ expect(Koala::Facebook::GraphBatchAPI::BatchOperation.new(@args).access_token).to eq(@args[:access_token])
37
+ end
38
+
39
+ it "doesn't change the original http_options" do
40
+ @args[:http_options][:name] = "baz2"
41
+ expected = @args[:http_options].dup
42
+ Koala::Facebook::GraphBatchAPI::BatchOperation.new(@args).to_batch_params(nil, nil)
43
+ expect(@args[:http_options]).to eq(expected)
44
+ end
45
+
46
+ it "leaves the file array nil by default" do
47
+ expect(Koala::Facebook::GraphBatchAPI::BatchOperation.new(@args).files).to be_nil
48
+ end
49
+
50
+ it "raises a KoalaError if no access token supplied" do
51
+ expect { Koala::Facebook::GraphBatchAPI::BatchOperation.new(@args.merge(:access_token => nil)) }.to raise_exception(Koala::KoalaError)
52
+ end
53
+
54
+ describe "when supplied binary files" do
55
+ before :each do
56
+ @binary = double("Binary file")
57
+ @uploadable_io = double("UploadableIO 1")
58
+
59
+ @batch_queue = []
60
+ allow(Koala::Facebook::API).to receive(:batch_calls).and_return(@batch_queue)
61
+
62
+ allow(Koala::HTTPService::UploadableIO).to receive(:new).with(@binary).and_return(@uploadable_io)
63
+ allow(Koala::HTTPService::UploadableIO).to receive(:binary_content?).and_return(false)
64
+ allow(Koala::HTTPService::UploadableIO).to receive(:binary_content?).with(@binary).and_return(true)
65
+ allow(Koala::HTTPService::UploadableIO).to receive(:binary_content?).with(@uploadable_io).and_return(true)
66
+ allow(@uploadable_io).to receive(:is_a?).with(Koala::HTTPService::UploadableIO).and_return(true)
67
+
68
+ @args[:method] = "post" # files are always post
69
+ end
70
+
71
+ it "adds binary files to the files attribute as UploadableIOs" do
72
+ @args[:args].merge!("source" => @binary)
73
+ batch_op = Koala::Facebook::GraphBatchAPI::BatchOperation.new(@args)
74
+ expect(batch_op.files).not_to be_nil
75
+ expect(batch_op.files.find {|k, v| v == @uploadable_io}).not_to be_nil
76
+ end
77
+
78
+ it "works if supplied an UploadableIO as an argument" do
79
+ # as happens with put_picture at the moment
80
+ @args[:args].merge!("source" => @uploadable_io)
81
+ batch_op = Koala::Facebook::GraphBatchAPI::BatchOperation.new(@args)
82
+ expect(batch_op.files).not_to be_nil
83
+ expect(batch_op.files.find {|k, v| v == @uploadable_io}).not_to be_nil
84
+ end
85
+
86
+ it "assigns each binary parameter unique name" do
87
+ @args[:args].merge!("source" => @binary, "source2" => @binary)
88
+ batch_op = Koala::Facebook::GraphBatchAPI::BatchOperation.new(@args)
89
+ # if the name wasn't unique, there'd just be one item
90
+ expect(batch_op.files.size).to eq(2)
91
+ end
92
+
93
+ it "assigns each binary parameter unique name across batch requests" do
94
+ @args[:args].merge!("source" => @binary, "source2" => @binary)
95
+ batch_op = Koala::Facebook::GraphBatchAPI::BatchOperation.new(@args)
96
+ # simulate the batch operation, since it's used in determination
97
+ @batch_queue << batch_op
98
+ batch_op2 = Koala::Facebook::GraphBatchAPI::BatchOperation.new(@args)
99
+ @batch_queue << batch_op2
100
+ # if the name wasn't unique, we should have < 4 items since keys would be the same
101
+ expect(batch_op.files.merge(batch_op2.files).size).to eq(4)
102
+ end
103
+
104
+ it "removes the value from the arguments" do
105
+ @args[:args].merge!("source" => @binary)
106
+ expect(Koala::Facebook::GraphBatchAPI::BatchOperation.new(@args).to_batch_params(nil, nil)[:body]).not_to match(/source=/)
107
+ end
108
+ end
109
+
110
+ end
111
+
112
+ describe "#to_batch_params" do
113
+ describe "handling arguments and URLs" do
114
+ shared_examples_for "request with no body" do
115
+ it "adds the args to the URL string, with ? if no args previously present" do
116
+ test_args = "foo"
117
+ @args[:url] = url = "/"
118
+ allow(Koala.http_service).to receive(:encode_params).and_return(test_args)
119
+
120
+ expect(Koala::Facebook::GraphBatchAPI::BatchOperation.new(@args).to_batch_params(nil, nil)[:relative_url]).to eq("#{url}?#{test_args}")
121
+ end
122
+
123
+ it "adds the args to the URL string, with & if args previously present" do
124
+ test_args = "foo"
125
+ @args[:url] = url = "/?a=2"
126
+ allow(Koala.http_service).to receive(:encode_params).and_return(test_args)
127
+
128
+ expect(Koala::Facebook::GraphBatchAPI::BatchOperation.new(@args).to_batch_params(nil, nil)[:relative_url]).to eq("#{url}&#{test_args}")
129
+ end
130
+
131
+ it "adds nothing to the URL string if there are no args to be added" do
132
+ @args[:args] = {}
133
+ expect(Koala::Facebook::GraphBatchAPI::BatchOperation.new(@args).to_batch_params(@args[:access_token], nil)[:relative_url]).to eq(@args[:url])
134
+ end
135
+
136
+ it "adds nothing to the body" do
137
+ expect(Koala::Facebook::GraphBatchAPI::BatchOperation.new(@args).to_batch_params(nil, nil)[:body]).to be_nil
138
+ end
139
+ end
140
+
141
+ shared_examples_for "requests with a body param" do
142
+ it "sets the body to the encoded args string, if there are args" do
143
+ test_args = "foo"
144
+ allow(Koala.http_service).to receive(:encode_params).and_return(test_args)
145
+
146
+ expect(Koala::Facebook::GraphBatchAPI::BatchOperation.new(@args).to_batch_params(nil, nil)[:body]).to eq(test_args)
147
+ end
148
+
149
+ it "does not set the body if there are no args" do
150
+ test_args = ""
151
+ allow(Koala.http_service).to receive(:encode_params).and_return(test_args)
152
+ expect(Koala::Facebook::GraphBatchAPI::BatchOperation.new(@args).to_batch_params(nil, nil)[:body]).to be_nil
153
+ end
154
+
155
+
156
+ it "doesn't change the url" do
157
+ test_args = "foo"
158
+ allow(Koala.http_service).to receive(:encode_params).and_return(test_args)
159
+
160
+ expect(Koala::Facebook::GraphBatchAPI::BatchOperation.new(@args).to_batch_params(nil, nil)[:relative_url]).to eq(@args[:url])
161
+ end
162
+ end
163
+
164
+ context "for get operations" do
165
+ before :each do
166
+ @args[:method] = :get
167
+ end
168
+
169
+ it_should_behave_like "request with no body"
170
+ end
171
+
172
+ context "for delete operations" do
173
+ before :each do
174
+ @args[:method] = :delete
175
+ end
176
+
177
+ it_should_behave_like "request with no body"
178
+ end
179
+
180
+ context "for get operations" do
181
+ before :each do
182
+ @args[:method] = :put
183
+ end
184
+
185
+ it_should_behave_like "requests with a body param"
186
+ end
187
+
188
+ context "for delete operations" do
189
+ before :each do
190
+ @args[:method] = :post
191
+ end
192
+
193
+ it_should_behave_like "requests with a body param"
194
+ end
195
+ end
196
+
197
+ it "includes the access token if the token is not the main one for the request" do
198
+ params = Koala::Facebook::GraphBatchAPI::BatchOperation.new(@args).to_batch_params(nil, nil)
199
+ expect(params[:relative_url]).to match(/access_token=#{@args[:access_token]}/)
200
+ end
201
+
202
+ it "re-signs the access token if the token is not the main one for the request" do
203
+ params = Koala::Facebook::GraphBatchAPI::BatchOperation.new(@args).to_batch_params(nil, '1234')
204
+ expect(params[:relative_url]).to match(/appsecret_proof=[^?&]+/)
205
+ end
206
+
207
+ it "includes the other arguments if the token is not the main one for the request" do
208
+ @args[:args] = {:a => 2}
209
+ params = Koala::Facebook::GraphBatchAPI::BatchOperation.new(@args).to_batch_params(nil, nil)
210
+ expect(params[:relative_url]).to match(/a=2/)
211
+ end
212
+
213
+ it "does not include the access token if the token is the main one for the request" do
214
+ params = Koala::Facebook::GraphBatchAPI::BatchOperation.new(@args).to_batch_params(@args[:access_token], nil)
215
+ expect(params[:relative_url]).not_to match(/access_token=#{@args[:access_token]}/)
216
+ end
217
+
218
+ it "includes the other arguments if the token is the main one for the request" do
219
+ @args[:args] = {:a => 2}
220
+ params = Koala::Facebook::GraphBatchAPI::BatchOperation.new(@args).to_batch_params(@args[:access_token], nil)
221
+ expect(params[:relative_url]).to match(/a=2/)
222
+ end
223
+
224
+ it "includes any arguments passed as http_options[:batch_args]" do
225
+ batch_args = {:name => "baz", :headers => {:some_param => true}}
226
+ @args[:http_options][:batch_args] = batch_args
227
+ params = Koala::Facebook::GraphBatchAPI::BatchOperation.new(@args).to_batch_params(nil, nil)
228
+ expect(params).to include(batch_args)
229
+ end
230
+
231
+ it "includes the method" do
232
+ params = Koala::Facebook::GraphBatchAPI::BatchOperation.new(@args).to_batch_params(@args[:access_token], nil)
233
+ expect(params[:method]).to eq(@args[:method].to_s)
234
+ end
235
+
236
+ it "works with nil http_options" do
237
+ expect { Koala::Facebook::GraphBatchAPI::BatchOperation.new(@args.merge(:http_options => nil)).to_batch_params(nil, nil) }.not_to raise_exception
238
+ end
239
+
240
+ it "works with nil args" do
241
+ expect { Koala::Facebook::GraphBatchAPI::BatchOperation.new(@args.merge(:args => nil)).to_batch_params(nil, nil) }.not_to raise_exception
242
+ end
243
+
244
+ describe "with binary files" do
245
+ before :each do
246
+ @binary = double("Binary file")
247
+ allow(Koala::HTTPService::UploadableIO).to receive(:binary_content?).and_return(false)
248
+ allow(Koala::HTTPService::UploadableIO).to receive(:binary_content?).with(@binary).and_return(true)
249
+ @uploadable_io = double("UploadableIO")
250
+ allow(Koala::HTTPService::UploadableIO).to receive(:new).with(@binary).and_return(@uploadable_io)
251
+ allow(@uploadable_io).to receive(:is_a?).with(Koala::HTTPService::UploadableIO).and_return(true)
252
+
253
+ @batch_queue = []
254
+ allow(Koala::Facebook::API).to receive(:batch_calls).and_return(@batch_queue)
255
+
256
+ @args[:method] = "post" # files are always post
257
+ end
258
+
259
+ it "adds file identifiers as attached_files in a comma-separated list" do
260
+ @args[:args].merge!("source" => @binary, "source2" => @binary)
261
+ batch_op = Koala::Facebook::GraphBatchAPI::BatchOperation.new(@args)
262
+ file_ids = batch_op.files.find_all {|k, v| v == @uploadable_io}.map {|k, v| k}
263
+ params = batch_op.to_batch_params(nil, nil)
264
+ expect(params[:attached_files]).to eq(file_ids.join(","))
265
+ end
266
+ end
267
+ end
268
+
269
+ end
270
+
271
+ describe "GraphAPI batch interface" do
272
+ it "returns nothing for a batch operation" do
273
+ allow(Koala).to receive(:make_request).and_return(Koala::HTTPService::Response.new(200, "[]", {}))
274
+ @api.batch do |batch_api|
275
+ expect(batch_api.get_object('me')).to be_nil
276
+ end
277
+ end
278
+
279
+ describe "#batch" do
280
+ before :each do
281
+ @fake_response = Koala::HTTPService::Response.new(200, "[]", {})
282
+ allow(Koala).to receive(:make_request).and_return(@fake_response)
283
+ end
284
+
285
+ describe "making the request" do
286
+ context "with no calls" do
287
+ it "does not make any requests if batch_calls is empty" do
288
+ expect(Koala).not_to receive(:make_request)
289
+ @api.batch {|batch_api|}
290
+ end
291
+
292
+ it "returns []" do
293
+ expect(@api.batch {|batch_api|}).to eq([])
294
+ end
295
+ end
296
+
297
+ it "includes the first operation's access token as the main one in the args" do
298
+ access_token = "foo"
299
+ expect(Koala).to receive(:make_request).with(anything, hash_including("access_token" => access_token), anything, anything).and_return(@fake_response)
300
+ Koala::Facebook::API.new(access_token).batch do |batch_api|
301
+ batch_api.get_object('me')
302
+ batch_api.get_object('me', {}, {'access_token' => 'bar'})
303
+ end
304
+ end
305
+
306
+ context 'with appsecret_proof and app_secret' do
307
+ it "includes the app_secret in the API call" do
308
+ access_token = "foo"
309
+ app_secret = "baz"
310
+ app_secret_digest = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha256"), app_secret, access_token)
311
+ expect(Koala).to receive(:make_request).with(anything, hash_including("access_token" => access_token, "appsecret_proof" => app_secret_digest), anything, anything).and_return(@fake_response)
312
+ Koala::Facebook::API.new(access_token, app_secret).batch do |batch_api|
313
+ batch_api.get_object('me')
314
+ batch_api.get_object('me', {}, {'access_token' => 'bar'})
315
+ end
316
+ end
317
+ end
318
+
319
+ it "sets args['batch'] to a json'd map of all the batch params" do
320
+ access_token = "bar"
321
+ op = Koala::Facebook::GraphBatchAPI::BatchOperation.new(:access_token => access_token, :method => :get, :url => "/")
322
+ allow(op).to receive(:to_batch_params).and_return({:a => 2})
323
+ allow(Koala::Facebook::GraphBatchAPI::BatchOperation).to receive(:new).and_return(op)
324
+
325
+ # two requests should generate two batch operations
326
+ expected = JSON.dump([op.to_batch_params(access_token, nil), op.to_batch_params(access_token, nil)])
327
+ expect(Koala).to receive(:make_request).with(anything, hash_including("batch" => expected), anything, anything).and_return(@fake_response)
328
+ Koala::Facebook::API.new(access_token).batch do |batch_api|
329
+ batch_api.get_object('me')
330
+ batch_api.get_object('me')
331
+ end
332
+ end
333
+
334
+ it "adds any files from the batch operations to the arguments" do
335
+ # stub the batch operation
336
+ # we test above to ensure that files are properly assimilated into the BatchOperation instance
337
+ # right now, we want to make sure that batch_api handles them properly
338
+ @key = "file0_0"
339
+ @uploadable_io = double("UploadableIO")
340
+ batch_op = double("Koala Batch Operation", :files => {@key => @uploadable_io}, :to_batch_params => {}, :access_token => "foo")
341
+ allow(Koala::Facebook::GraphBatchAPI::BatchOperation).to receive(:new).and_return(batch_op)
342
+
343
+ expect(Koala).to receive(:make_request).with(anything, hash_including(@key => @uploadable_io), anything, anything).and_return(@fake_response)
344
+ Koala::Facebook::API.new("bar").batch do |batch_api|
345
+ batch_api.put_picture("path/to/file", "image/jpeg")
346
+ end
347
+ end
348
+
349
+ it "preserves operation order" do
350
+ access_token = "bar"
351
+ # two requests should generate two batch operations
352
+ expect(Koala).to receive(:make_request) do |url, args, method, options|
353
+ # test the batch operations to make sure they appear in the right order
354
+ expect((args ||= {})["batch"]).to match(/.*me\/farglebarg.*otheruser\/bababa/)
355
+ @fake_response
356
+ end
357
+ Koala::Facebook::API.new(access_token).batch do |batch_api|
358
+ batch_api.get_connections('me', "farglebarg")
359
+ batch_api.get_connections('otheruser', "bababa")
360
+ end
361
+ end
362
+
363
+ it "makes a POST request" do
364
+ expect(Koala).to receive(:make_request).with(anything, anything, "post", anything).and_return(@fake_response)
365
+ Koala::Facebook::API.new("foo").batch do |batch_api|
366
+ batch_api.get_object('me')
367
+ end
368
+ end
369
+
370
+ it "makes a request to /" do
371
+ expect(Koala).to receive(:make_request).with("/", anything, anything, anything).and_return(@fake_response)
372
+ Koala::Facebook::API.new("foo").batch do |batch_api|
373
+ batch_api.get_object('me')
374
+ end
375
+ end
376
+
377
+ it "includes any http options specified at the top level" do
378
+ http_options = {"a" => "baz"}
379
+ expect(Koala).to receive(:make_request).with(anything, anything, anything, hash_including(http_options)).and_return(@fake_response)
380
+ Koala::Facebook::API.new("foo").batch(http_options) do |batch_api|
381
+ batch_api.get_object('me')
382
+ end
383
+ end
384
+ end
385
+
386
+ describe 'processing the request' do
387
+ let(:response_status) { 203 }
388
+ let(:response_body) { '{\"id\":\"1234\"}'.gsub('\\', '') }
389
+ let(:response_headers) { { 'Content-Type' => 'text/javascript; charset=UTF-8' } }
390
+
391
+ it 'returns the result headers as a hash if http_component is headers' do
392
+ allow(Koala).to receive(:make_request).and_return(Koala::HTTPService::Response.new(200, '[{"code":203,"headers":[{"name":"Content-Type","value":"text/javascript; charset=UTF-8"}],"body":"{\"id\":\"1234\"}"}]', {}))
393
+ result = @api.batch do |batch_api|
394
+ batch_api.get_object(KoalaTest.user1, {}, :http_component => :headers)
395
+ end
396
+ expect(response_headers).to eq(result[0])
397
+ end
398
+
399
+ it 'returns the complete response if http_component is response' do
400
+ allow(Koala).to receive(:make_request).and_return(Koala::HTTPService::Response.new(200, '[{"code":203,"headers":[{"name":"Content-Type","value":"text/javascript; charset=UTF-8"}],"body":"{\"id\":\"1234\"}"}]', {}))
401
+ result = @api.batch do |batch_api|
402
+ batch_api.get_object(KoalaTest.user1, {}, :http_component => :response)
403
+ end
404
+
405
+ expect(response_status).to eq(result[0].status)
406
+ expect(response_body).to eq(result[0].body)
407
+ expect(response_headers).to eq(result[0].headers)
408
+ end
409
+
410
+ describe 'if it errors' do
411
+ it 'raises an APIError if the response is not 200' do
412
+ allow(Koala).to receive(:make_request).and_return(Koala::HTTPService::Response.new(500, '[]', {}))
413
+ expect {
414
+ Koala::Facebook::API.new('foo').batch { |batch_api| batch_api.get_object('me') }
415
+ }.to raise_exception(Koala::Facebook::APIError)
416
+ end
417
+
418
+ it 'raises a BadFacebookResponse if the body is empty' do
419
+ allow(Koala).to receive(:make_request).and_return(Koala::HTTPService::Response.new(200, '', {}))
420
+ expect {
421
+ Koala::Facebook::API.new('foo').batch { |batch_api| batch_api.get_object('me') }
422
+ }.to raise_exception(Koala::Facebook::BadFacebookResponse, /Facebook returned an empty body \[HTTP 200\]/)
423
+ end
424
+
425
+ describe 'handle invalid body errors' do
426
+ describe 'with http_component set to :response' do
427
+ it 'raises a BadFacebookResponse if the body is non-empty, non-array' do
428
+ allow(Koala).to receive(:make_request).and_return(Koala::HTTPService::Response.new(200, '200', {}))
429
+ expect {
430
+ Koala::Facebook::API.new('foo').batch(http_component: :response) do |batch_api|
431
+ batch_api.get_object('me')
432
+ end
433
+ }.to raise_exception(Koala::Facebook::BadFacebookResponse, /Facebook returned an invalid body \[HTTP 200\]/)
434
+ end
435
+
436
+ it 'raises a BadFacebookResponse if the body is invalid JSON' do
437
+ allow(Koala).to receive(:make_request).and_return(Koala::HTTPService::Response.new(200, '{"\"id\":\1234\"}"}', {}))
438
+ expect {
439
+ Koala::Facebook::API.new('foo').batch(http_component: :response) do |batch_api|
440
+ batch_api.get_object('me')
441
+ end
442
+ }.to raise_exception(Koala::Facebook::BadFacebookResponse, /Facebook returned an invalid body \[HTTP 200\]/)
443
+ end
444
+ end
445
+
446
+ %i[headers status].each do |component|
447
+ describe "with http_component set to #{component}" do
448
+ it 'should not raise a BadFacebookResponse if the body is non-empty, non-array' do
449
+ allow(Koala).to receive(:make_request).and_return(Koala::HTTPService::Response.new(200, '200', {}))
450
+ expect {
451
+ Koala::Facebook::API.new('foo').batch(http_component: component) { |batch_api| batch_api.get_object('me') }
452
+ }.not_to raise_exception(Koala::Facebook::BadFacebookResponse, /Facebook returned an invalid body \[HTTP 200\]/)
453
+ end
454
+
455
+ it 'should not raise a BadFacebookResponse if the body is invalid JSON' do
456
+ allow(Koala).to receive(:make_request).and_return(Koala::HTTPService::Response.new(200, '{"\"id\":\1234\"}"}', {}))
457
+ expect {
458
+ Koala::Facebook::API.new('foo').batch(http_component: component) do |batch_api|
459
+ batch_api.get_object('me')
460
+ end
461
+ }.not_to raise_exception(Koala::Facebook::BadFacebookResponse, /Facebook returned an invalid body \[HTTP 200\]/)
462
+ end
463
+ end
464
+ end
465
+ end
466
+
467
+ context "with error info" do
468
+ before :each do
469
+ allow(Koala).to receive(:make_request).and_return(Koala::HTTPService::Response.new(400, '{"error":{"message":"Request 0 cannot depend on an unresolved request with name f. Requests can only depend on preceding requests","type":"GraphBatchException"}}', {}))
470
+ end
471
+
472
+ it "throws an error" do
473
+ expect {
474
+ Koala::Facebook::API.new("foo").batch {|batch_api| batch_api.get_object('me') }
475
+ }.to raise_exception(Koala::Facebook::APIError)
476
+ end
477
+
478
+ it "passes all the error details" do
479
+ begin
480
+ Koala::Facebook::API.new("foo").batch {|batch_api| batch_api.get_object('me') }
481
+ rescue Koala::Facebook::APIError => err
482
+ expect(err.fb_error_type).to eq("GraphBatchException")
483
+ expect(err.fb_error_message).to eq("Request 0 cannot depend on an unresolved request with name f. Requests can only depend on preceding requests")
484
+ err.http_status == 400
485
+ err.response_body == '{"error":{"message":"Request 0 cannot depend on an unresolved request with name f. Requests can only depend on preceding requests","type":"GraphBatchException"}}'
486
+ end
487
+ end
488
+ end
489
+ end
490
+
491
+ it "returns the result status if http_component is status" do
492
+ allow(Koala).to receive(:make_request).and_return(Koala::HTTPService::Response.new(200, '[{"code":203,"headers":[{"name":"Content-Type","value":"text/javascript; charset=UTF-8"}],"body":"{\"id\":\"1234\"}"}]', {}))
493
+ result = @api.batch do |batch_api|
494
+ batch_api.get_object(KoalaTest.user1, {}, :http_component => :status)
495
+ end
496
+ expect(result[0]).to eq(203)
497
+ end
498
+ end
499
+
500
+ it "is thread safe" do
501
+ # ensure batch operations on one thread don't affect those on another
502
+ thread_one_count = 0
503
+ thread_two_count = 0
504
+ first_count = 20
505
+ second_count = 10
506
+
507
+ allow(Koala).to receive(:make_request).and_return(@fake_response)
508
+
509
+ thread1 = Thread.new do
510
+ @api.batch do |batch_api|
511
+ first_count.times {|i| batch_api.get_object("me"); sleep(0.01) }
512
+ thread_one_count = batch_api.batch_calls.count
513
+ end
514
+ end
515
+
516
+ thread2 = Thread.new do
517
+ @api.batch do |batch_api|
518
+ second_count.times {|i| batch_api.get_object("me"); sleep(0.01) }
519
+ thread_two_count = batch_api.batch_calls.count
520
+ end
521
+ end
522
+
523
+ thread1.join
524
+ thread2.join
525
+
526
+ expect(thread_one_count).to eq(first_count)
527
+ expect(thread_two_count).to eq(second_count)
528
+ end
529
+
530
+ end
531
+ end
532
+
533
+ describe '#big_batches' do
534
+ before :each do
535
+ payload = [{code: 200, headers: [{name: "Content-Type", value: "text/javascript; charset=UTF-8"}], body: "{\"id\":\"1234\"}"}]
536
+ allow(Koala).to receive(:make_request) do |_request, args, _verb, _options|
537
+ request_count = JSON.parse(args['batch']).length
538
+ expect(request_count).to be <= 50 # check FB's limit
539
+ Koala::HTTPService::Response.new(200, (payload * request_count).to_json, {})
540
+ end
541
+ end
542
+
543
+ it 'stays within fb limits' do
544
+ count_calls = 0
545
+ expected_calls = 100
546
+ @api.batch do |batch_api|
547
+ expected_calls.times { |_i| batch_api.get_object('me') { |_ret| count_calls += 1 } }
548
+ end
549
+
550
+ expect(count_calls).to eq(expected_calls)
551
+ end
552
+
553
+ it 'is recursive safe' do
554
+ # ensure batch operations whose callbacks call batch operations don't resubmit
555
+ call_count = 0
556
+ iterations = 60
557
+ @api.batch do |batch_api|
558
+ # must do enough calls to provoke a batch submission
559
+ iterations.times { |_i|
560
+ batch_api.get_object('me') { |_ret|
561
+ call_count += 1
562
+ batch_api.get_object('me') { |_ret|
563
+ call_count += 1
564
+ }
565
+ }
566
+ }
567
+ end
568
+ expect(call_count).to eq(2 * iterations)
569
+ end
570
+ end
571
+
572
+ describe "usage tests" do
573
+ it "gets two results at once" do
574
+ me, barackobama = @api.batch do |batch_api|
575
+ batch_api.get_object('me')
576
+ batch_api.get_object(KoalaTest.user1)
577
+ end
578
+ expect(me['id']).not_to be_nil
579
+ expect(barackobama['id']).not_to be_nil
580
+ end
581
+
582
+ it 'makes mixed calls inside of a batch' do
583
+ me, friends = @api.batch do |batch_api|
584
+ batch_api.get_object('me')
585
+ batch_api.get_connections('me', 'friends')
586
+ end
587
+ expect(friends).to be_a(Koala::Facebook::API::GraphCollection)
588
+ end
589
+
590
+ it 'turns pageable results into GraphCollections' do
591
+ me, friends = @api.batch do |batch_api|
592
+ batch_api.get_object('me')
593
+ batch_api.get_connections('me', 'friends')
594
+ end
595
+ expect(me['id']).not_to be_nil
596
+ expect(friends).to be_an(Array)
597
+ end
598
+
599
+ it 'makes a get_picture call inside of a batch' do
600
+ pictures = @api.batch do |batch_api|
601
+ batch_api.get_picture('me')
602
+ end
603
+ expect(pictures.first).to match(/https\:\/\//) # works both live & stubbed
604
+ end
605
+
606
+ it 'takes an after processing block for a get_picture call inside of a batch' do
607
+ picture = nil
608
+ @api.batch do |batch_api|
609
+ batch_api.get_picture('me') { |pic| picture = pic }
610
+ end
611
+ expect(picture).to match(/https\:\/\//) # works both live & stubbed
612
+ end
613
+
614
+ it "handles requests for two different tokens" do
615
+ me, app_event_types = @api.batch do |batch_api|
616
+ batch_api.get_object('me')
617
+ batch_api.get_connections(@app_id, 'app_event_types', {}, {"access_token" => @app_api.access_token})
618
+ end
619
+ expect(me['id']).not_to be_nil
620
+ expect(app_event_types).to be_an(Koala::Facebook::API::GraphCollection)
621
+ end
622
+
623
+ it "handles requests passing the access token option as a symbol instead of a string" do
624
+ me, app_event_types = @api.batch do |batch_api|
625
+ batch_api.get_object('me')
626
+ batch_api.get_connections(@app_id, 'app_event_types', {}, {:access_token => @app_api.access_token})
627
+ end
628
+ expect(me['id']).not_to be_nil
629
+ expect(app_event_types).to be_an(Koala::Facebook::API::GraphCollection)
630
+ end
631
+
632
+ it "preserves batch-op specific access tokens in GraphCollection returned from batch" do
633
+ # Provide an alternate token for a batch operation
634
+ @other_access_token_args = { 'access_token' => @app_api.access_token }
635
+
636
+ # make a batch call for app_event_types using another token
637
+ me, app_event_types = @api.batch do |batch_api|
638
+ batch_api.get_object('me')
639
+ batch_api.get_connections(@app_id, 'app_event_types', {}, @other_access_token_args)
640
+ end
641
+
642
+ # The alternate token is returned with the next page parameters
643
+ # The GraphCollection should receive a request for the next_page_params during paging
644
+ expect(app_event_types).to receive(:next_page_params).and_return([double("base"), @other_access_token_args.dup])
645
+
646
+ # The alternate access token should pass through to making the request
647
+ # Koala should receive a request during paging using the alternate token
648
+ expect(Koala).to receive(:make_request).with(
649
+ anything,
650
+ hash_including(@other_access_token_args.dup),
651
+ anything,
652
+ anything
653
+ ).and_return(Koala::HTTPService::Response.new(200, "", {}))
654
+
655
+ # Page the collection
656
+ app_event_types.next_page
657
+ end
658
+
659
+ it "inserts errors in the appropriate place, without breaking other results" do
660
+ failed_call, barackobama = @api.batch do |batch_api|
661
+ batch_api.get_connection("2", "invalidconnection")
662
+ batch_api.get_object(KoalaTest.user1, {}, {"access_token" => @app_api.access_token})
663
+ end
664
+ expect(failed_call).to be_a(Koala::Facebook::ClientError)
665
+ expect(barackobama["id"]).not_to be_nil
666
+ end
667
+
668
+ it "handles different request methods" do
669
+ result = @api.put_wall_post("Hello, world, from the test suite batch API!")
670
+ wall_post = result["id"]
671
+
672
+ wall_post, barackobama = @api.batch do |batch_api|
673
+ batch_api.put_like(wall_post)
674
+ batch_api.delete_object(wall_post)
675
+ end
676
+ end
677
+
678
+ describe 'with post-processing callback' do
679
+ let(:me_result) { double("me result") }
680
+ let(:friends_result) { [double("friends result")] }
681
+
682
+ let(:me_callback) { lambda {|arg| {"result" => me_result, "args" => arg} } }
683
+ let(:friends_callback) { lambda {|arg| {"result" => friends_result, "args" => arg} } }
684
+
685
+ it 'calls the callback with the appropriate data' do
686
+ me, friends = @api.batch do |batch_api|
687
+ batch_api.get_object('me', &me_callback)
688
+ batch_api.get_connections('me', 'friends', &friends_callback)
689
+ end
690
+ expect(me["args"]).to include("id" => KoalaTest.user1)
691
+ expect(friends["args"].first).to include("id" => KoalaTest.user2)
692
+ end
693
+
694
+ it 'passes GraphCollections, not raw data' do
695
+ me, friends = @api.batch do |batch_api|
696
+ batch_api.get_object('me')
697
+ batch_api.get_connections('me', 'friends', &friends_callback)
698
+ end
699
+ expect(friends["args"]).to be_a(Koala::Facebook::API::GraphCollection)
700
+ end
701
+
702
+ it "returns the result of the callback" do
703
+ expect(@api.batch do |batch_api|
704
+ batch_api.get_object('me', &me_callback)
705
+ batch_api.get_connections('me', 'friends', &friends_callback)
706
+ end.map {|r| r["result"]}).to eq([me_result, friends_result])
707
+ end
708
+ end
709
+
710
+ describe "binary files" do
711
+ it "posts binary files" do
712
+ file = File.open(File.join(File.dirname(__FILE__), "..", "fixtures", "beach.jpg"))
713
+
714
+ Koala::Facebook::GraphBatchAPI::BatchOperation.instance_variable_set(:@identifier, 0)
715
+ result = @api.batch do |batch_api|
716
+ batch_api.put_picture(file)
717
+ end
718
+
719
+ @temporary_object_id = result[0]["id"]
720
+ expect(@temporary_object_id).not_to be_nil
721
+ end
722
+
723
+ it "posts binary files with multiple requests" do
724
+ file = File.open(File.join(File.dirname(__FILE__), "..", "fixtures", "beach.jpg"))
725
+ file2 = File.open(File.join(File.dirname(__FILE__), "..", "fixtures", "beach.jpg"))
726
+
727
+ Koala::Facebook::GraphBatchAPI::BatchOperation.instance_variable_set(:@identifier, 0)
728
+ results = @api.batch do |batch_api|
729
+ batch_api.put_picture(file)
730
+ batch_api.put_picture(file2, {}, KoalaTest.user1)
731
+ end
732
+ expect(results[0]["id"]).not_to be_nil
733
+ expect(results[1]["id"]).not_to be_nil
734
+ end
735
+ end
736
+
737
+ describe "relating requests" do
738
+ it "allows you create relationships between requests without omit_response_on_success" do
739
+ results = @api.batch do |batch_api|
740
+ batch_api.get_connections("me", "friends", {:limit => 5}, :batch_args => {:name => "get-friends"})
741
+ batch_api.get_objects("{result=get-friends:$.data.*.id}")
742
+ end
743
+
744
+ expect(results[0]).to be_nil
745
+ expect(results[1]).to be_an(Hash)
746
+ end
747
+
748
+ it "allows you create relationships between requests with omit_response_on_success" do
749
+ results = @api.batch do |batch_api|
750
+ batch_api.get_connections("me", "friends", {:limit => 5}, :batch_args => {:name => "get-friends", :omit_response_on_success => false})
751
+ batch_api.get_objects("{result=get-friends:$.data.*.id}")
752
+ end
753
+
754
+ expect(results[0]).to be_an(Array)
755
+ expect(results[1]).to be_an(Hash)
756
+ end
757
+
758
+ it "allows you to create dependencies" do
759
+ me, barackobama = @api.batch do |batch_api|
760
+ batch_api.get_object("me", {}, :batch_args => {:name => "getme"})
761
+ batch_api.get_object(KoalaTest.user1, {}, :batch_args => {:depends_on => "getme"})
762
+ end
763
+
764
+ expect(me).to be_nil # gotcha! it's omitted because it's a successfully-executed dependency
765
+ expect(barackobama["id"]).not_to be_nil
766
+ end
767
+
768
+ it "properly handles dependencies that fail" do
769
+ failed_call, barackobama = @api.batch do |batch_api|
770
+ batch_api.get_connections("2", "invalidconnection", {}, :batch_args => {:name => "getdata"})
771
+ batch_api.get_object(KoalaTest.user1, {}, :batch_args => {:depends_on => "getdata"})
772
+ end
773
+
774
+ expect(failed_call).to be_a(Koala::Facebook::ClientError)
775
+ expect(barackobama).to be_nil
776
+ end
777
+
778
+ it "throws an error for badly-constructed request relationships" do
779
+ expect {
780
+ @api.batch do |batch_api|
781
+ batch_api.get_connections("me", "friends", {:limit => 5})
782
+ batch_api.get_objects("{result=i-dont-exist:$.data.*.id}")
783
+ end
784
+ }.to raise_exception(Koala::Facebook::ClientError)
785
+ end
786
+ end
787
+ end
788
+ end