koala 1.0.0 → 1.1.0rc
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +12 -0
- data/.gitignore +2 -1
- data/CHANGELOG +18 -0
- data/autotest/discover.rb +1 -0
- data/koala.gemspec +5 -5
- data/lib/koala.rb +29 -194
- data/lib/koala/graph_api.rb +71 -31
- data/lib/koala/graph_api_batch.rb +151 -0
- data/lib/koala/http_services.rb +10 -112
- data/lib/koala/http_services/net_http_service.rb +87 -0
- data/lib/koala/http_services/typhoeus_service.rb +37 -0
- data/lib/koala/oauth.rb +181 -0
- data/lib/koala/realtime_updates.rb +5 -14
- data/lib/koala/rest_api.rb +13 -8
- data/lib/koala/uploadable_io.rb +35 -7
- data/readme.md +19 -7
- data/spec/cases/api_base_spec.rb +2 -2
- data/spec/cases/graph_api_batch_spec.rb +600 -0
- data/spec/cases/http_services/http_service_spec.rb +76 -1
- data/spec/cases/http_services/net_http_service_spec.rb +164 -48
- data/spec/cases/http_services/typhoeus_service_spec.rb +27 -19
- data/spec/cases/koala_spec.rb +55 -0
- data/spec/cases/test_users_spec.rb +1 -1
- data/spec/cases/uploadable_io_spec.rb +56 -14
- data/spec/fixtures/mock_facebook_responses.yml +89 -5
- data/spec/support/graph_api_shared_examples.rb +34 -7
- data/spec/support/mock_http_service.rb +54 -56
- data/spec/support/rest_api_shared_examples.rb +131 -7
- data/spec/support/setup_mocks_or_live.rb +3 -3
- metadata +36 -24
@@ -46,6 +46,8 @@ module Koala
|
|
46
46
|
oauth = Koala::Facebook::OAuth.new(@app_id, @secret)
|
47
47
|
@app_access_token = oauth.get_app_access_token
|
48
48
|
end
|
49
|
+
|
50
|
+
@graph_api = GraphAPI.new(@app_access_token)
|
49
51
|
end
|
50
52
|
|
51
53
|
# subscribes for realtime updates
|
@@ -59,7 +61,7 @@ module Koala
|
|
59
61
|
:verify_token => verify_token
|
60
62
|
}
|
61
63
|
# a subscription is a success if Facebook returns a 200 (after hitting your server for verification)
|
62
|
-
|
64
|
+
@graph_api.graph_call(subscription_path, args, 'post', :http_component => :status) == 200
|
63
65
|
end
|
64
66
|
|
65
67
|
# removes subscription for object
|
@@ -67,24 +69,13 @@ module Koala
|
|
67
69
|
def unsubscribe(object = nil)
|
68
70
|
args = {}
|
69
71
|
args[:object] = object if object
|
70
|
-
|
72
|
+
@graph_api.graph_call(subscription_path, args, 'delete', :http_component => :status) == 200
|
71
73
|
end
|
72
74
|
|
73
75
|
def list_subscriptions
|
74
|
-
|
76
|
+
@graph_api.graph_call(subscription_path)["data"]
|
75
77
|
end
|
76
78
|
|
77
|
-
def api(*args) # same as GraphAPI
|
78
|
-
response = super(*args) do |response|
|
79
|
-
# check for subscription errors
|
80
|
-
if response.is_a?(Hash) && error_details = response["error"]
|
81
|
-
raise APIError.new(error_details)
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
response
|
86
|
-
end
|
87
|
-
|
88
79
|
protected
|
89
80
|
|
90
81
|
def subscription_path
|
data/lib/koala/rest_api.rb
CHANGED
@@ -3,21 +3,26 @@ module Koala
|
|
3
3
|
REST_SERVER = "api.facebook.com"
|
4
4
|
|
5
5
|
module RestAPIMethods
|
6
|
-
def fql_query(fql)
|
7
|
-
rest_call('fql.query',
|
6
|
+
def fql_query(fql, args = {}, options = {})
|
7
|
+
rest_call('fql.query', args.merge(:query => fql), options)
|
8
8
|
end
|
9
9
|
|
10
|
-
def
|
11
|
-
|
10
|
+
def fql_multiquery(queries = {}, args = {}, options = {})
|
11
|
+
if results = rest_call('fql.multiquery', args.merge(:queries => queries.to_json), options)
|
12
|
+
# simplify the multiquery result format
|
13
|
+
results.inject({}) {|outcome, data| outcome[data["name"]] = data["fql_result_set"]; outcome}
|
14
|
+
end
|
15
|
+
end
|
12
16
|
|
13
|
-
|
17
|
+
def rest_call(fb_method, args = {}, options = {}, method = "get")
|
18
|
+
options = options.merge!(:rest_api => true, :read_only => READ_ONLY_METHODS.include?(fb_method.to_s))
|
19
|
+
|
20
|
+
api("method/#{fb_method}", args.merge('format' => 'json'), method, options) do |response|
|
14
21
|
# check for REST API-specific errors
|
15
22
|
if response.is_a?(Hash) && response["error_code"]
|
16
23
|
raise APIError.new("type" => response["error_code"], "message" => response["error_msg"])
|
17
24
|
end
|
18
25
|
end
|
19
|
-
|
20
|
-
response
|
21
26
|
end
|
22
27
|
|
23
28
|
# read-only methods for which we can use API-read
|
@@ -87,4 +92,4 @@ module Koala
|
|
87
92
|
end
|
88
93
|
|
89
94
|
end # module Facebook
|
90
|
-
end # module Koala
|
95
|
+
end # module Koala
|
data/lib/koala/uploadable_io.rb
CHANGED
@@ -2,9 +2,9 @@ require 'koala'
|
|
2
2
|
|
3
3
|
module Koala
|
4
4
|
class UploadableIO
|
5
|
-
attr_reader :io_or_path, :content_type
|
5
|
+
attr_reader :io_or_path, :content_type, :requires_base_http_service
|
6
6
|
|
7
|
-
def initialize(io_or_path_or_mixed, content_type = nil)
|
7
|
+
def initialize(io_or_path_or_mixed, content_type = nil, filename = nil)
|
8
8
|
# see if we got the right inputs
|
9
9
|
if content_type.nil?
|
10
10
|
parse_init_mixed_param io_or_path_or_mixed
|
@@ -13,19 +13,35 @@ module Koala
|
|
13
13
|
@content_type = content_type
|
14
14
|
end
|
15
15
|
|
16
|
+
# Probably a StringIO or similar object, which won't work with Typhoeus
|
17
|
+
@requires_base_http_service = @io_or_path.respond_to?(:read) && !@io_or_path.kind_of?(File)
|
18
|
+
|
19
|
+
# filename is used in the Ads API
|
20
|
+
@filename = filename || "koala-io-file.dum"
|
21
|
+
|
16
22
|
raise KoalaError.new("Invalid arguments to initialize an UploadableIO") unless @io_or_path
|
17
23
|
raise KoalaError.new("Unable to determine MIME type for UploadableIO") if !@content_type && Koala.multipart_requires_content_type?
|
18
24
|
end
|
19
25
|
|
20
26
|
def to_upload_io
|
21
|
-
UploadIO.new(@io_or_path, @content_type,
|
27
|
+
UploadIO.new(@io_or_path, @content_type, @filename)
|
22
28
|
end
|
23
29
|
|
24
30
|
def to_file
|
25
31
|
@io_or_path.is_a?(String) ? File.open(@io_or_path) : @io_or_path
|
26
32
|
end
|
33
|
+
|
34
|
+
def self.binary_content?(content)
|
35
|
+
content.is_a?(UploadableIO) || DETECTION_STRATEGIES.detect {|method| send(method, content)}
|
36
|
+
end
|
27
37
|
|
28
38
|
private
|
39
|
+
DETECTION_STRATEGIES = [
|
40
|
+
:sinatra_param?,
|
41
|
+
:rails_3_param?,
|
42
|
+
:file_param?
|
43
|
+
]
|
44
|
+
|
29
45
|
PARSE_STRATEGIES = [
|
30
46
|
:parse_rails_3_param,
|
31
47
|
:parse_sinatra_param,
|
@@ -41,24 +57,36 @@ module Koala
|
|
41
57
|
end
|
42
58
|
|
43
59
|
# Expects a parameter of type ActionDispatch::Http::UploadedFile
|
60
|
+
def self.rails_3_param?(uploaded_file)
|
61
|
+
uploaded_file.respond_to?(:content_type) and uploaded_file.respond_to?(:tempfile) and uploaded_file.tempfile.respond_to?(:path)
|
62
|
+
end
|
63
|
+
|
44
64
|
def parse_rails_3_param(uploaded_file)
|
45
|
-
if
|
65
|
+
if UploadableIO.rails_3_param?(uploaded_file)
|
46
66
|
@io_or_path = uploaded_file.tempfile.path
|
47
67
|
@content_type = uploaded_file.content_type
|
48
68
|
end
|
49
69
|
end
|
50
70
|
|
51
71
|
# Expects a Sinatra hash of file info
|
72
|
+
def self.sinatra_param?(file_hash)
|
73
|
+
file_hash.kind_of?(Hash) and file_hash.has_key?(:type) and file_hash.has_key?(:tempfile)
|
74
|
+
end
|
75
|
+
|
52
76
|
def parse_sinatra_param(file_hash)
|
53
|
-
if
|
77
|
+
if UploadableIO.sinatra_param?(file_hash)
|
54
78
|
@io_or_path = file_hash[:tempfile]
|
55
79
|
@content_type = file_hash[:type] || detect_mime_type(tempfile)
|
56
80
|
end
|
57
81
|
end
|
58
82
|
|
59
83
|
# takes a file object
|
84
|
+
def self.file_param?(file)
|
85
|
+
file.kind_of?(File)
|
86
|
+
end
|
87
|
+
|
60
88
|
def parse_file_object(file)
|
61
|
-
if
|
89
|
+
if UploadableIO.file_param?(file)
|
62
90
|
@io_or_path = file
|
63
91
|
@content_type = detect_mime_type(file.path)
|
64
92
|
end
|
@@ -112,4 +140,4 @@ module Koala
|
|
112
140
|
end
|
113
141
|
end
|
114
142
|
end
|
115
|
-
end
|
143
|
+
end
|
data/readme.md
CHANGED
@@ -1,21 +1,24 @@
|
|
1
1
|
Koala
|
2
2
|
====
|
3
|
-
Koala (<a href="http://github.com/arsduo/koala" target="_blank">http://github.com/arsduo/koala</a>) is a new Facebook library for Ruby, supporting the Graph API (including photo uploads), the
|
3
|
+
Koala (<a href="http://github.com/arsduo/koala" target="_blank">http://github.com/arsduo/koala</a>) is a new Facebook library for Ruby, supporting the Graph API (including the batch requests and photo uploads), the REST API, realtime updates, test users, and OAuth validation. We wrote Koala with four goals:
|
4
4
|
|
5
5
|
* Lightweight: Koala should be as light and simple as Facebook’s own new libraries, providing API accessors and returning simple JSON. (We clock in, with comments, just over 750 lines of code.)
|
6
6
|
* Fast: Koala should, out of the box, be quick. In addition to supporting the vanilla Ruby networking libraries, it natively supports Typhoeus, our preferred gem for making fast HTTP requests. Of course, That brings us to our next topic:
|
7
7
|
* Flexible: Koala should be useful to everyone, regardless of their current configuration. (We have no dependencies beyond the JSON gem. Koala also has a built-in mechanism for using whichever HTTP library you prefer to make requests against the graph.)
|
8
8
|
* Tested: Koala should have complete test coverage, so you can rely on it. (Our complete test coverage can be run against either mocked responses or the live Facebook servers.)
|
9
9
|
|
10
|
-
|
10
|
+
Installation
|
11
11
|
---
|
12
|
-
|
12
|
+
|
13
|
+
Easy:
|
14
|
+
|
15
|
+
[sudo|rvm] gem install koala
|
16
|
+
# for 1.1rc add --pre
|
13
17
|
|
14
|
-
sudo gem install koala
|
15
18
|
|
16
|
-
|
19
|
+
Or in Bundler:
|
17
20
|
|
18
|
-
|
21
|
+
gem "koala" # add ', "~> 1.1rc"' for the release candidate
|
19
22
|
|
20
23
|
Graph API
|
21
24
|
----
|
@@ -47,6 +50,15 @@ When retrieving data that returns an array of results (for example, when calling
|
|
47
50
|
# You can use those params to easily get the next (or prevous) page
|
48
51
|
page = graph.get_page(feed.next_page_params)
|
49
52
|
|
53
|
+
You can make multiple calls at once using Facebook's batch API:
|
54
|
+
|
55
|
+
# Returns an array of results as if they were called non-batch
|
56
|
+
graph.batch do
|
57
|
+
graph.get_connections('me', 'friends')
|
58
|
+
graph.get_object('me')
|
59
|
+
graph.get_picture('me')
|
60
|
+
end
|
61
|
+
|
50
62
|
Check out the wiki for more examples.
|
51
63
|
|
52
64
|
The old-school REST API
|
@@ -140,4 +152,4 @@ You can also run live tests against Facebook's servers:
|
|
140
152
|
# Again from anywhere in the project directory:
|
141
153
|
LIVE=true rake spec
|
142
154
|
|
143
|
-
Important Note: to run the live tests, you have to provide some of your own data in spec/fixtures/facebook_data.yml: a valid OAuth access token with publish\_stream, read\_stream, and user\_photos permissions and an OAuth code that can be used to generate an access token. You can get thisdata at the OAuth Playground; if you want to use your own app, remember to swap out the app ID, secret, and other values. (The file also provides valid values for other tests, which you're welcome to swap out for data specific to your own application.)
|
155
|
+
Important Note: to run the live tests, you have to provide some of your own data in spec/fixtures/facebook_data.yml: a valid OAuth access token with publish\_stream, read\_stream, and user\_photos permissions and an OAuth code that can be used to generate an access token. You can get thisdata at the OAuth Playground; if you want to use your own app, remember to swap out the app ID, secret, and other values. (The file also provides valid values for other tests, which you're welcome to swap out for data specific to your own application.)
|
data/spec/cases/api_base_spec.rb
CHANGED
@@ -57,14 +57,14 @@ describe "Koala::Facebook::API" do
|
|
57
57
|
@service.api('anything').should == json_body
|
58
58
|
end
|
59
59
|
|
60
|
-
it "should execute
|
60
|
+
it "should execute an error checking block if provided" do
|
61
61
|
body = '{}'
|
62
62
|
Koala.stub(:make_request).and_return(Koala::Response.new(200, body, {}))
|
63
63
|
|
64
64
|
yield_test = mock('Yield Tester')
|
65
65
|
yield_test.should_receive(:pass)
|
66
66
|
|
67
|
-
@service.api('anything') do |arg|
|
67
|
+
@service.api('anything', {}, "get") do |arg|
|
68
68
|
yield_test.pass
|
69
69
|
arg.should == JSON.parse(body)
|
70
70
|
end
|
@@ -0,0 +1,600 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Koala::Facebook::GraphAPI in batch mode" do
|
4
|
+
include LiveTestingDataHelper
|
5
|
+
before :each do
|
6
|
+
@api = Koala::Facebook::GraphAPI.new(@token)
|
7
|
+
# app API
|
8
|
+
@oauth_data = $testing_data["oauth_test_data"]
|
9
|
+
@app_id = @oauth_data["app_id"]
|
10
|
+
@app_access_token = @oauth_data["app_access_token"]
|
11
|
+
@app_api = Koala::Facebook::GraphAPI.new(@app_access_token)
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "BatchOperations" 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
|
+
Koala::Facebook::BatchOperation.new(@args).http_options.should == @args[:http_options]
|
29
|
+
end
|
30
|
+
|
31
|
+
it "makes post_processing accessible" do
|
32
|
+
Koala::Facebook::BatchOperation.new(@args).post_processing.should == @args[:post_processing]
|
33
|
+
end
|
34
|
+
|
35
|
+
it "makes access_token accessible" do
|
36
|
+
Koala::Facebook::BatchOperation.new(@args).access_token.should == @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::BatchOperation.new(@args).to_batch_params(nil)
|
43
|
+
@args[:http_options].should == expected
|
44
|
+
end
|
45
|
+
|
46
|
+
it "leaves the file array nil by default" do
|
47
|
+
Koala::Facebook::BatchOperation.new(@args).files.should be_nil
|
48
|
+
end
|
49
|
+
|
50
|
+
it "raises a KoalaError if no access token supplied" do
|
51
|
+
expect { Koala::Facebook::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 = stub("Binary file")
|
57
|
+
@uploadable_io = stub("UploadableIO 1")
|
58
|
+
|
59
|
+
@batch_queue = []
|
60
|
+
Koala::Facebook::GraphAPI.stub(:batch_calls).and_return(@batch_queue)
|
61
|
+
|
62
|
+
Koala::UploadableIO.stub(:new).with(@binary).and_return(@uploadable_io)
|
63
|
+
Koala::UploadableIO.stub(:binary_content?).and_return(false)
|
64
|
+
Koala::UploadableIO.stub(:binary_content?).with(@binary).and_return(true)
|
65
|
+
Koala::UploadableIO.stub(:binary_content?).with(@uploadable_io).and_return(true)
|
66
|
+
@uploadable_io.stub(:is_a?).with(Koala::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::BatchOperation.new(@args)
|
74
|
+
batch_op.files.should_not be_nil
|
75
|
+
batch_op.files.find {|k, v| v == @uploadable_io}.should_not 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::BatchOperation.new(@args)
|
82
|
+
batch_op.files.should_not be_nil
|
83
|
+
batch_op.files.find {|k, v| v == @uploadable_io}.should_not 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::BatchOperation.new(@args)
|
89
|
+
# if the name wasn't unique, there'd just be one item
|
90
|
+
batch_op.files.should have(2).items
|
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::BatchOperation.new(@args)
|
96
|
+
# simulate the batch operation, since it's used in determination
|
97
|
+
@batch_queue << batch_op
|
98
|
+
batch_op2 = Koala::Facebook::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
|
+
batch_op.files.merge(batch_op2.files).should have(4).items
|
102
|
+
end
|
103
|
+
|
104
|
+
it "removes the value from the arguments" do
|
105
|
+
@args[:args].merge!("source" => @binary)
|
106
|
+
Koala::Facebook::BatchOperation.new(@args).to_batch_params(nil)[:body].should_not =~ /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
|
+
Koala.http_service.stub(:encode_params).and_return(test_args)
|
119
|
+
|
120
|
+
Koala::Facebook::BatchOperation.new(@args).to_batch_params(nil)[:relative_url].should == "#{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
|
+
Koala.http_service.stub(:encode_params).and_return(test_args)
|
127
|
+
|
128
|
+
Koala::Facebook::BatchOperation.new(@args).to_batch_params(nil)[:relative_url].should == "#{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
|
+
Koala::Facebook::BatchOperation.new(@args).to_batch_params(@args[:access_token])[:relative_url].should == @args[:url]
|
134
|
+
end
|
135
|
+
|
136
|
+
it "adds nothing to the body" do
|
137
|
+
Koala::Facebook::BatchOperation.new(@args).to_batch_params(nil)[:body].should 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
|
+
Koala.http_service.stub(:encode_params).and_return(test_args)
|
145
|
+
|
146
|
+
Koala::Facebook::BatchOperation.new(@args).to_batch_params(nil)[:body].should == test_args
|
147
|
+
end
|
148
|
+
|
149
|
+
it "does not set the body if there are no args" do
|
150
|
+
test_args = ""
|
151
|
+
Koala.http_service.stub(:encode_params).and_return(test_args)
|
152
|
+
Koala::Facebook::BatchOperation.new(@args).to_batch_params(nil)[:body].should be_nil
|
153
|
+
end
|
154
|
+
|
155
|
+
|
156
|
+
it "doesn't change the url" do
|
157
|
+
test_args = "foo"
|
158
|
+
Koala.http_service.stub(:encode_params).and_return(test_args)
|
159
|
+
|
160
|
+
Koala::Facebook::BatchOperation.new(@args).to_batch_params(nil)[:relative_url].should == @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::BatchOperation.new(@args).to_batch_params(nil)
|
199
|
+
params[:relative_url].should =~ /access_token=#{@args[:access_token]}/
|
200
|
+
end
|
201
|
+
|
202
|
+
it "includes the other arguments if the token is not the main one for the request" do
|
203
|
+
@args[:args] = {:a => 2}
|
204
|
+
params = Koala::Facebook::BatchOperation.new(@args).to_batch_params(nil)
|
205
|
+
params[:relative_url].should =~ /a=2/
|
206
|
+
end
|
207
|
+
|
208
|
+
it "does not include the access token if the token is the main one for the request" do
|
209
|
+
params = Koala::Facebook::BatchOperation.new(@args).to_batch_params(@args[:access_token])
|
210
|
+
params[:relative_url].should_not =~ /access_token=#{@args[:access_token]}/
|
211
|
+
end
|
212
|
+
|
213
|
+
it "includes the other arguments if the token is the main one for the request" do
|
214
|
+
@args[:args] = {:a => 2}
|
215
|
+
params = Koala::Facebook::BatchOperation.new(@args).to_batch_params(@args[:access_token])
|
216
|
+
params[:relative_url].should =~ /a=2/
|
217
|
+
end
|
218
|
+
|
219
|
+
it "includes any arguments passed as http_options[:batch_args]" do
|
220
|
+
batch_args = {:name => "baz", :headers => {:some_param => true}}
|
221
|
+
@args[:http_options][:batch_args] = batch_args
|
222
|
+
params = Koala::Facebook::BatchOperation.new(@args).to_batch_params(nil)
|
223
|
+
params.should include(batch_args)
|
224
|
+
end
|
225
|
+
|
226
|
+
it "includes the method" do
|
227
|
+
params = Koala::Facebook::BatchOperation.new(@args).to_batch_params(@args[:access_token])
|
228
|
+
params[:method].should == @args[:method].to_sym
|
229
|
+
end
|
230
|
+
|
231
|
+
it "works with nil http_options" do
|
232
|
+
expect { Koala::Facebook::BatchOperation.new(@args.merge(:http_options => nil)).to_batch_params(nil) }.not_to raise_exception
|
233
|
+
end
|
234
|
+
|
235
|
+
it "works with nil args" do
|
236
|
+
expect { Koala::Facebook::BatchOperation.new(@args.merge(:args => nil)).to_batch_params(nil) }.not_to raise_exception
|
237
|
+
end
|
238
|
+
|
239
|
+
describe "with binary files" do
|
240
|
+
before :each do
|
241
|
+
@binary = stub("Binary file")
|
242
|
+
Koala::UploadableIO.stub(:binary_content?).and_return(false)
|
243
|
+
Koala::UploadableIO.stub(:binary_content?).with(@binary).and_return(true)
|
244
|
+
@uploadable_io = stub("UploadableIO")
|
245
|
+
Koala::UploadableIO.stub(:new).with(@binary).and_return(@uploadable_io)
|
246
|
+
@uploadable_io.stub(:is_a?).with(Koala::UploadableIO).and_return(true)
|
247
|
+
|
248
|
+
@batch_queue = []
|
249
|
+
Koala::Facebook::GraphAPI.stub(:batch_calls).and_return(@batch_queue)
|
250
|
+
|
251
|
+
@args[:method] = "post" # files are always post
|
252
|
+
end
|
253
|
+
|
254
|
+
it "adds file identifiers as attached_files in a comma-separated list" do
|
255
|
+
@args[:args].merge!("source" => @binary, "source2" => @binary)
|
256
|
+
batch_op = Koala::Facebook::BatchOperation.new(@args)
|
257
|
+
file_ids = batch_op.files.find_all {|k, v| v == @uploadable_io}.map {|k, v| k}
|
258
|
+
params = batch_op.to_batch_params(nil)
|
259
|
+
params[:attached_files].should == file_ids.join(",")
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
end
|
265
|
+
|
266
|
+
describe "GraphAPI batch interface" do
|
267
|
+
it "sets the batch_mode flag to false outside batch mode" do
|
268
|
+
Koala::Facebook::GraphAPI.batch_mode?.should be_false
|
269
|
+
end
|
270
|
+
|
271
|
+
it "sets the batch_mode flag inside batch mode" do
|
272
|
+
Koala::Facebook::GraphAPI.batch do
|
273
|
+
Koala::Facebook::GraphAPI.batch_mode?.should be_true
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
it "throws an error if you try to access the batch_calls queue outside a batch block" do
|
278
|
+
expect { Koala::Facebook::GraphAPI.batch_calls << BatchOperation.new(:access_token => "2") }.to raise_exception(Koala::KoalaError)
|
279
|
+
end
|
280
|
+
|
281
|
+
it "clears the batch queue between requests" do
|
282
|
+
Koala.stub(:make_request).and_return(Koala::Response.new(200, "[]", {}))
|
283
|
+
Koala::Facebook::GraphAPI.batch { @api.get_object("me") }
|
284
|
+
Koala.should_receive(:make_request).once.and_return(Koala::Response.new(200, "[]", {}))
|
285
|
+
Koala::Facebook::GraphAPI.batch { @api.get_object("me") }
|
286
|
+
end
|
287
|
+
|
288
|
+
it "returns nothing for a batch operation" do
|
289
|
+
Koala.stub(:make_request).and_return(Koala::Response.new(200, "[]", {}))
|
290
|
+
Koala::Facebook::GraphAPI.batch do
|
291
|
+
@api.get_object("me").should be_nil
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
it "creates a BatchObject when making a GraphAPI request in batch mode" do
|
296
|
+
Koala.should_receive(:make_request).once.and_return(Koala::Response.new(200, "[]", {}))
|
297
|
+
|
298
|
+
args = {:a => :b}
|
299
|
+
method = "post"
|
300
|
+
http_options = {:option => true}
|
301
|
+
url = "/a"
|
302
|
+
access_token = "token"
|
303
|
+
post_processing = lambda {}
|
304
|
+
op = Koala::Facebook::BatchOperation.new(:access_token => access_token, :method => :get, :url => "/")
|
305
|
+
Koala::Facebook::BatchOperation.should_receive(:new).with(
|
306
|
+
:url => url,
|
307
|
+
:args => args,
|
308
|
+
:method => method,
|
309
|
+
:access_token => access_token,
|
310
|
+
:http_options => http_options,
|
311
|
+
:post_processing => post_processing
|
312
|
+
).and_return(op)
|
313
|
+
|
314
|
+
Koala::Facebook::GraphAPI.batch do
|
315
|
+
Koala::Facebook::GraphAPI.new(access_token).graph_call(url, args, method, http_options, &post_processing)
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
describe "#batch_api" do
|
320
|
+
before :each do
|
321
|
+
@fake_response = Koala::Response.new(200, "[]", {})
|
322
|
+
Koala.stub(:make_request).and_return(@fake_response)
|
323
|
+
end
|
324
|
+
|
325
|
+
describe "making the request" do
|
326
|
+
context "with no calls" do
|
327
|
+
it "does not make any requests if batch_calls is empty" do
|
328
|
+
Koala.should_not_receive(:make_request)
|
329
|
+
Koala::Facebook::GraphAPI.batch {}
|
330
|
+
end
|
331
|
+
|
332
|
+
it "returns []" do
|
333
|
+
Koala::Facebook::GraphAPI.batch {}.should == []
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
it "includes the first operation's access token as the main one in the args" do
|
338
|
+
access_token = "foo"
|
339
|
+
Koala.should_receive(:make_request).with(anything, hash_including("access_token" => access_token), anything, anything).and_return(@fake_response)
|
340
|
+
Koala::Facebook::GraphAPI.batch do
|
341
|
+
Koala::Facebook::GraphAPI.new(access_token).get_object('me')
|
342
|
+
Koala::Facebook::GraphAPI.new("bar").get_object('me')
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
it "sets args['batch'] to a json'd map of all the batch params" do
|
347
|
+
access_token = "bar"
|
348
|
+
op = Koala::Facebook::BatchOperation.new(:access_token => access_token, :method => :get, :url => "/")
|
349
|
+
op.stub(:to_batch_params).and_return({:a => 2})
|
350
|
+
Koala::Facebook::BatchOperation.stub(:new).and_return(op)
|
351
|
+
|
352
|
+
# two requests should generate two batch operations
|
353
|
+
expected = [op.to_batch_params(access_token), op.to_batch_params(access_token)].to_json
|
354
|
+
Koala.should_receive(:make_request).with(anything, hash_including("batch" => expected), anything, anything).and_return(@fake_response)
|
355
|
+
Koala::Facebook::GraphAPI.batch do
|
356
|
+
Koala::Facebook::GraphAPI.new(access_token).get_object('me')
|
357
|
+
Koala::Facebook::GraphAPI.new(access_token).get_object('me')
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
it "adds any files from the batch operations to the arguments" do
|
362
|
+
# stub the batch operation
|
363
|
+
# we test above to ensure that files are properly assimilated into the BatchOperation instance
|
364
|
+
# right now, we want to make sure that batch_api handles them properly
|
365
|
+
@key = "file0_0"
|
366
|
+
@uploadable_io = stub("UploadableIO")
|
367
|
+
batch_op = stub("Koala Batch Operation", :files => {@key => @uploadable_io}, :to_batch_params => {}, :access_token => "foo")
|
368
|
+
Koala::Facebook::BatchOperation.stub(:new).and_return(batch_op)
|
369
|
+
|
370
|
+
Koala.should_receive(:make_request).with(anything, hash_including(@key => @uploadable_io), anything, anything).and_return(@fake_response)
|
371
|
+
Koala::Facebook::GraphAPI.batch do
|
372
|
+
Koala::Facebook::GraphAPI.new("bar").put_picture("path/to/file", "image/jpeg")
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
it "preserves operation order" do
|
377
|
+
access_token = "bar"
|
378
|
+
# two requests should generate two batch operations
|
379
|
+
Koala.should_receive(:make_request) do |url, args, method, options|
|
380
|
+
# test the batch operations to make sure they appear in the right order
|
381
|
+
(args ||= {})["batch"].should =~ /.*me\/farglebarg.*otheruser\/bababa/
|
382
|
+
@fake_response
|
383
|
+
end
|
384
|
+
Koala::Facebook::GraphAPI.batch do
|
385
|
+
Koala::Facebook::GraphAPI.new(access_token).get_connections('me', "farglebarg")
|
386
|
+
Koala::Facebook::GraphAPI.new(access_token).get_connections('otheruser', "bababa")
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
it "makes a POST request" do
|
391
|
+
Koala.should_receive(:make_request).with(anything, anything, "post", anything).and_return(@fake_response)
|
392
|
+
Koala::Facebook::GraphAPI.batch do
|
393
|
+
Koala::Facebook::GraphAPI.new("foo").get_object('me')
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
it "makes a request to /" do
|
398
|
+
Koala.should_receive(:make_request).with("/", anything, anything, anything).and_return(@fake_response)
|
399
|
+
Koala::Facebook::GraphAPI.batch do
|
400
|
+
Koala::Facebook::GraphAPI.new("foo").get_object('me')
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
it "includes any http options specified at the top level" do
|
405
|
+
http_options = {"a" => "baz"}
|
406
|
+
Koala.should_receive(:make_request).with(anything, anything, anything, hash_including(http_options)).and_return(@fake_response)
|
407
|
+
Koala::Facebook::GraphAPI.batch(http_options) do
|
408
|
+
Koala::Facebook::GraphAPI.new("foo").get_object('me')
|
409
|
+
end
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
413
|
+
describe "processing the request" do
|
414
|
+
it "throws an error if the response is not 200" do
|
415
|
+
Koala.stub(:make_request).and_return(Koala::Response.new(500, "[]", {}))
|
416
|
+
expect { Koala::Facebook::GraphAPI.batch do
|
417
|
+
Koala::Facebook::GraphAPI.new("foo").get_object('me')
|
418
|
+
end }.to raise_exception(Koala::Facebook::APIError)
|
419
|
+
end
|
420
|
+
|
421
|
+
it "throws an error if the response is a Batch API-style error" do
|
422
|
+
Koala.stub(:make_request).and_return(Koala::Response.new(200, '{"error":190,"error_description":"Error validating access token."}', {}))
|
423
|
+
expect { Koala::Facebook::GraphAPI.batch do
|
424
|
+
Koala::Facebook::GraphAPI.new("foo").get_object('me')
|
425
|
+
end }.to raise_exception(Koala::Facebook::APIError)
|
426
|
+
end
|
427
|
+
|
428
|
+
it "returns the result status if http_component is status" do
|
429
|
+
Koala.stub(:make_request).and_return(Koala::Response.new(200, '[{"code":203,"headers":[{"name":"Content-Type","value":"text/javascript; charset=UTF-8"}],"body":"{\"id\":\"1234\"}"}]', {}))
|
430
|
+
result = Koala::Facebook::GraphAPI.batch do
|
431
|
+
@api.get_object("koppel", {}, :http_component => :status)
|
432
|
+
end
|
433
|
+
result[0].should == 203
|
434
|
+
end
|
435
|
+
|
436
|
+
it "returns the result headers as a hash if http_component is headers" do
|
437
|
+
Koala.stub(:make_request).and_return(Koala::Response.new(200, '[{"code":203,"headers":[{"name":"Content-Type","value":"text/javascript; charset=UTF-8"}],"body":"{\"id\":\"1234\"}"}]', {}))
|
438
|
+
result = Koala::Facebook::GraphAPI.batch do
|
439
|
+
@api.get_object("koppel", {}, :http_component => :headers)
|
440
|
+
end
|
441
|
+
result[0].should == {"Content-Type" => "text/javascript; charset=UTF-8"}
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
it "is not available on the GraphAndRestAPI class" do
|
446
|
+
Koala::Facebook::GraphAndRestAPI.should_not respond_to(:batch)
|
447
|
+
end
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
describe "usage tests" do
|
452
|
+
it "should be able get two results at once" do
|
453
|
+
me, koppel = Koala::Facebook::GraphAPI.batch do
|
454
|
+
@api.get_object('me')
|
455
|
+
@api.get_object('koppel')
|
456
|
+
end
|
457
|
+
me['id'].should_not be_nil
|
458
|
+
koppel['id'].should_not be_nil
|
459
|
+
end
|
460
|
+
|
461
|
+
|
462
|
+
it "works with GraphAndRestAPI instances" do
|
463
|
+
me, koppel = Koala::Facebook::GraphAPI.batch do
|
464
|
+
Koala::Facebook::GraphAndRestAPI.new(@api.access_token).get_object('me')
|
465
|
+
@api.get_object('koppel')
|
466
|
+
end
|
467
|
+
me['id'].should_not be_nil
|
468
|
+
koppel['id'].should_not be_nil
|
469
|
+
end
|
470
|
+
|
471
|
+
it 'should be able to make mixed calls inside of a batch' do
|
472
|
+
me, friends = Koala::Facebook::GraphAPI.batch do
|
473
|
+
@api.get_object('me')
|
474
|
+
@api.get_connections('me', 'friends')
|
475
|
+
end
|
476
|
+
me['id'].should_not be_nil
|
477
|
+
friends.should be_an(Array)
|
478
|
+
end
|
479
|
+
|
480
|
+
it 'should be able to make a get_picture call inside of a batch' do
|
481
|
+
pictures = Koala::Facebook::GraphAPI.batch do
|
482
|
+
@api.get_picture('me')
|
483
|
+
end
|
484
|
+
pictures.first.should_not be_empty
|
485
|
+
end
|
486
|
+
|
487
|
+
it "should handle requests for two different tokens" do
|
488
|
+
me, insights = Koala::Facebook::GraphAPI.batch do
|
489
|
+
@api.get_object('me')
|
490
|
+
@app_api.get_connections(@app_id, 'insights')
|
491
|
+
end
|
492
|
+
me['id'].should_not be_nil
|
493
|
+
insights.should be_an(Array)
|
494
|
+
end
|
495
|
+
|
496
|
+
it "inserts errors in the appropriate place, without breaking other results" do
|
497
|
+
failed_insights, koppel = Koala::Facebook::GraphAPI.batch do
|
498
|
+
@api.get_connections(@app_id, 'insights')
|
499
|
+
@app_api.get_object("koppel")
|
500
|
+
end
|
501
|
+
failed_insights.should be_a(Koala::Facebook::APIError)
|
502
|
+
koppel["id"].should_not be_nil
|
503
|
+
end
|
504
|
+
|
505
|
+
it "handles different request methods" do
|
506
|
+
result = @api.put_wall_post("Hello, world, from the test suite batch API!")
|
507
|
+
wall_post = result["id"]
|
508
|
+
|
509
|
+
wall_post, koppel = Koala::Facebook::GraphAPI.batch do
|
510
|
+
@api.put_like(wall_post)
|
511
|
+
@api.delete_object(wall_post)
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
515
|
+
it "allows FQL" do
|
516
|
+
result = Koala::Facebook::GraphAPI.batch do
|
517
|
+
@api.graph_call("method/fql.query", {:query=>"select name from user where uid=4"}, "post")
|
518
|
+
end
|
519
|
+
|
520
|
+
fql_result = result[0]
|
521
|
+
fql_result[0].should be_a(Hash)
|
522
|
+
fql_result[0]["name"].should == "Mark Zuckerberg"
|
523
|
+
end
|
524
|
+
|
525
|
+
describe "binary files" do
|
526
|
+
it "posts binary files" do
|
527
|
+
file = File.open(File.join(File.dirname(__FILE__), "..", "fixtures", "beach.jpg"))
|
528
|
+
|
529
|
+
result = Koala::Facebook::GraphAPI.batch do
|
530
|
+
@api.put_picture(file)
|
531
|
+
end
|
532
|
+
|
533
|
+
@temporary_object_id = result[0]["id"]
|
534
|
+
@temporary_object_id.should_not be_nil
|
535
|
+
end
|
536
|
+
|
537
|
+
it "posts binary files with multiple requests" do
|
538
|
+
file = File.open(File.join(File.dirname(__FILE__), "..", "fixtures", "beach.jpg"))
|
539
|
+
|
540
|
+
results = Koala::Facebook::GraphAPI.batch do
|
541
|
+
@api.put_picture(file)
|
542
|
+
@api.put_picture(file, {}, "koppel")
|
543
|
+
end
|
544
|
+
results[0]["id"].should_not be_nil
|
545
|
+
results[1]["id"].should_not be_nil
|
546
|
+
end
|
547
|
+
end
|
548
|
+
|
549
|
+
describe "relating requests" do
|
550
|
+
it "allows you create relationships between requests without omit_response_on_success" do
|
551
|
+
results = Koala::Facebook::GraphAPI.batch do
|
552
|
+
@api.get_connections("me", "friends", {:limit => 5}, :batch_args => {:name => "get-friends"})
|
553
|
+
@api.get_objects("{result=get-friends:$.data.*.id}")
|
554
|
+
end
|
555
|
+
|
556
|
+
results[0].should be_nil
|
557
|
+
results[1].should be_an(Hash)
|
558
|
+
end
|
559
|
+
|
560
|
+
it "allows you create relationships between requests with omit_response_on_success" do
|
561
|
+
results = Koala::Facebook::GraphAPI.batch do
|
562
|
+
@api.get_connections("me", "friends", {:limit => 5}, :batch_args => {:name => "get-friends", :omit_response_on_success => false})
|
563
|
+
@api.get_objects("{result=get-friends:$.data.*.id}")
|
564
|
+
end
|
565
|
+
|
566
|
+
results[0].should be_an(Array)
|
567
|
+
results[1].should be_an(Hash)
|
568
|
+
end
|
569
|
+
|
570
|
+
it "allows you to create dependencies" do
|
571
|
+
me, koppel = Koala::Facebook::GraphAPI.batch do
|
572
|
+
@api.get_object("me", {}, :batch_args => {:name => "getme"})
|
573
|
+
@api.get_object("koppel", {}, :batch_args => {:depends_on => "getme"})
|
574
|
+
end
|
575
|
+
|
576
|
+
me.should be_nil # gotcha! it's omitted because it's a successfully-executed dependency
|
577
|
+
koppel["id"].should_not be_nil
|
578
|
+
end
|
579
|
+
|
580
|
+
it "properly handles dependencies that fail" do
|
581
|
+
data, koppel = Koala::Facebook::GraphAPI.batch do
|
582
|
+
@api.get_connections(@app_id, 'insights', {}, :batch_args => {:name => "getdata"})
|
583
|
+
@api.get_object("koppel", {}, :batch_args => {:depends_on => "getdata"})
|
584
|
+
end
|
585
|
+
|
586
|
+
data.should be_a(Koala::Facebook::APIError)
|
587
|
+
koppel.should be_nil
|
588
|
+
end
|
589
|
+
|
590
|
+
it "throws an error for badly-constructed request relationships" do
|
591
|
+
expect {
|
592
|
+
Koala::Facebook::GraphAPI.batch do
|
593
|
+
@api.get_connections("me", "friends", {:limit => 5})
|
594
|
+
@api.get_objects("{result=i-dont-exist:$.data.*.id}")
|
595
|
+
end
|
596
|
+
}.to raise_exception(Koala::Facebook::APIError)
|
597
|
+
end
|
598
|
+
end
|
599
|
+
end
|
600
|
+
end
|