koala 1.0.0 → 1.1.0rc2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.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/graph_api.rb +71 -31
- data/lib/koala/graph_api_batch.rb +151 -0
- data/lib/koala/http_services/net_http_service.rb +87 -0
- data/lib/koala/http_services/typhoeus_service.rb +37 -0
- data/lib/koala/http_services.rb +10 -112
- 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/lib/koala.rb +29 -194
- 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 +166 -50
- data/spec/cases/http_services/typhoeus_service_spec.rb +29 -21
- 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 +28 -48
|
@@ -24,6 +24,18 @@ rest_api:
|
|
|
24
24
|
with_token: '[{"read_stream":1}]'
|
|
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
|
+
/method/fql.multiquery:
|
|
28
|
+
'queries={"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
|
+
get:
|
|
30
|
+
with_token: '[{"name":"query1", "fql_result_set":[]},{"name":"query2", "fql_result_set":[]},{"name":"query3", "fql_result_set":[]}]'
|
|
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={"query1":"select first_name from user where uid = 216743","query2":"select first_name from user where uid = 2905623"}':
|
|
33
|
+
get:
|
|
34
|
+
with_token: '[{"name":"query1", "fql_result_set":[{"first_name":"Chris"}]},{"name":"query2", "fql_result_set":[{"first_name":"Alex"}]}]'
|
|
35
|
+
no_token: '[{"name":"query1", "fql_result_set":[{"first_name":"Chris"}]},{"name":"query2", "fql_result_set":[{"first_name":"Alex"}]}]'
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
27
39
|
|
|
28
40
|
# ====== GRAPH API =====
|
|
29
41
|
graph_api:
|
|
@@ -49,7 +61,7 @@ graph_api:
|
|
|
49
61
|
|
|
50
62
|
test_user_no_perms: &test_user_no_perms
|
|
51
63
|
post:
|
|
52
|
-
with_token: '{"id": "777777777", "access_token":"<%=
|
|
64
|
+
with_token: '{"id": "777777777", "access_token":"<%= APP_ACCESS_TOKEN %>", "login_url":"https://www.facebook.com/platform/test_account.."}'
|
|
53
65
|
|
|
54
66
|
test_user_befriended: &test_user_befriended
|
|
55
67
|
post:
|
|
@@ -59,8 +71,59 @@ graph_api:
|
|
|
59
71
|
root:
|
|
60
72
|
ids=contextoptional,naitik:
|
|
61
73
|
get:
|
|
62
|
-
with_token: '
|
|
63
|
-
no_token: '
|
|
74
|
+
with_token: '{"contextoptional":"{}","naitik":"{}"}'
|
|
75
|
+
no_token: '{"contextoptional":"{}","naitik":"{}"}'
|
|
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"}].to_json %>:
|
|
78
|
+
post:
|
|
79
|
+
with_token: '[{"body":"{\"id\":\"123\"}"}, {"body":"{\"id\":\"456\"}"}]'
|
|
80
|
+
batch=<%= [{"method" => "get", "relative_url" => "me/picture"}].to_json %>:
|
|
81
|
+
post:
|
|
82
|
+
with_token: '[{"headers":[{"name":"Location","value":"http://google.com"}]}]'
|
|
83
|
+
batch=<%= [{"method" => "get", "relative_url" => "me"},{"method" => "get", "relative_url" => "me/friends"}].to_json %>:
|
|
84
|
+
post:
|
|
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}"}].to_json %>:
|
|
87
|
+
post:
|
|
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}"}].to_json %>:
|
|
90
|
+
post:
|
|
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"}].to_json %>:
|
|
93
|
+
post:
|
|
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}"}"}].to_json %>:
|
|
96
|
+
post:
|
|
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}"}"}].to_json %>:
|
|
99
|
+
post:
|
|
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}"}"}].to_json %>:
|
|
102
|
+
post:
|
|
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"}].to_json %>:
|
|
105
|
+
post:
|
|
106
|
+
with_token: '[{"body":"[{\"name\":\"Mark Zuckerberg\"}]"}]'
|
|
107
|
+
batch=<%= [{"method"=>"get", "relative_url"=>"me", "name" => "getme"}, {"method"=>"get", "relative_url"=>"koppel", "depends_on" => "getme"}].to_json %>:
|
|
108
|
+
post:
|
|
109
|
+
with_token: '[null,{"body":"{\"id\":\"123\"}"}]'
|
|
110
|
+
batch=<%= [{"method"=>"get", "relative_url"=>"#{OAUTH_DATA["app_id"]}/insights", "name" => "getdata"}, {"method"=>"get", "relative_url"=>"koppel", "depends_on" => "getdata"}].to_json %>:
|
|
111
|
+
post:
|
|
112
|
+
with_token: '[{"body": "{\"error\":{\"type\":\"AnError\", \"message\":\"An error occurred!.\"}}"},null]'
|
|
113
|
+
batch=<%= [{"method"=>"get", "relative_url"=>"#{OAUTH_DATA["app_id"]}/insights", "name" => "getdata"}, {"method"=>"get", "relative_url"=>"koppel", "depends_on" => "getdata"}].to_json %>:
|
|
114
|
+
post:
|
|
115
|
+
with_token: '[{"body": "{\"error\":{\"type\":\"AnError\", \"message\":\"An error occurred!.\"}}"},null]'
|
|
116
|
+
batch=<%= [{"method"=>"post", "relative_url"=>"me/photos", "attached_files" => "file0_0"}].to_json %>&file0_0=[FILE]:
|
|
117
|
+
post:
|
|
118
|
+
with_token: '[{"body": "{\"error\":{\"type\":\"AnError\", \"message\":\"An error occurred!.\"}}"},null]'
|
|
119
|
+
batch=<%= [{"method"=>"post", "relative_url"=>"me/photos", "attached_files" => "file0_0"}].to_json %>&file0_0=[FILE]:
|
|
120
|
+
post:
|
|
121
|
+
with_token: '[{"body":"{\"id\": \"MOCK_PHOTO\"}"}]'
|
|
122
|
+
batch=<%= [{"method"=>"post", "relative_url"=>"me/photos", "attached_files" => "file0_0"}, {"method"=>"post", "relative_url"=>"koppel/photos", "attached_files" => "file1_0"}].to_json %>&file0_0=[FILE]&file1_0=[FILE]:
|
|
123
|
+
post:
|
|
124
|
+
with_token: '[{"body":"{\"id\": \"MOCK_PHOTO\"}"}, {"body":"{\"id\": \"MOCK_PHOTO\"}"}]'
|
|
125
|
+
|
|
126
|
+
|
|
64
127
|
/me:
|
|
65
128
|
no_args:
|
|
66
129
|
get:
|
|
@@ -83,6 +146,9 @@ graph_api:
|
|
|
83
146
|
message=Hello, world, from the test suite delete method!:
|
|
84
147
|
post:
|
|
85
148
|
with_token: '{"id": "FEED_ITEM_DELETE"}'
|
|
149
|
+
message=Hello, world, from the test suite batch API!:
|
|
150
|
+
post:
|
|
151
|
+
with_token: '{"id": "FEED_ITEM_BATCH"}'
|
|
86
152
|
link=http://oauth.twoalex.com/&message=Hello, world, from the test suite again!&name=OAuth Playground:
|
|
87
153
|
post:
|
|
88
154
|
with_token: '{"id": "FEED_ITEM_CONTEXT"}'
|
|
@@ -144,6 +210,24 @@ graph_api:
|
|
|
144
210
|
headers:
|
|
145
211
|
Location: http://facebook.com/
|
|
146
212
|
|
|
213
|
+
/comments:
|
|
214
|
+
ids=http://developers.facebook.com/blog/post/472:
|
|
215
|
+
get:
|
|
216
|
+
with_token: '{"http://developers.facebook.com/blog/post/472": []}'
|
|
217
|
+
no_token: '{"http://developers.facebook.com/blog/post/472": []}'
|
|
218
|
+
ids=http://developers.facebook.com/blog/post/490,http://developers.facebook.com/blog/post/472:
|
|
219
|
+
get:
|
|
220
|
+
with_token: '{"http://developers.facebook.com/blog/post/490": [], "http://developers.facebook.com/blog/post/472": []}'
|
|
221
|
+
no_token: '{"http://developers.facebook.com/blog/post/490": [], "http://developers.facebook.com/blog/post/472": []}'
|
|
222
|
+
ids=:
|
|
223
|
+
get:
|
|
224
|
+
body: '{"error": {"type": "OAuthException","message": "Cannot specify an empty identifier"}}'
|
|
225
|
+
code: 400
|
|
226
|
+
no_args:
|
|
227
|
+
get:
|
|
228
|
+
body: '{"error": {"type": "Exception","message": "No node specified"}}'
|
|
229
|
+
code: 500
|
|
230
|
+
|
|
147
231
|
/search:
|
|
148
232
|
q=facebook:
|
|
149
233
|
get:
|
|
@@ -169,7 +253,7 @@ graph_api:
|
|
|
169
253
|
<<: *oauth_error
|
|
170
254
|
client_id=<%= APP_ID %>&client_secret=<%= SECRET %>&type=client_cred:
|
|
171
255
|
post:
|
|
172
|
-
no_token: access_token=<%=
|
|
256
|
+
no_token: access_token=<%= APP_ACCESS_TOKEN %>
|
|
173
257
|
/oauth/exchange_sessions:
|
|
174
258
|
client_id=<%= APP_ID %>&client_secret=<%= SECRET %>&sessions=<%= OAUTH_DATA["session_key"] %>&type=client_cred:
|
|
175
259
|
post:
|
|
@@ -311,4 +395,4 @@ graph_api:
|
|
|
311
395
|
|
|
312
396
|
/777777777:
|
|
313
397
|
no_args:
|
|
314
|
-
<<: *item_deleted
|
|
398
|
+
<<: *item_deleted
|
|
@@ -51,9 +51,19 @@ shared_examples_for "Koala GraphAPI" do
|
|
|
51
51
|
(result["id"] && result["name"]).should
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
+
it "should return [] from get_objects if passed an empty array" do
|
|
55
|
+
results = @api.get_objects([])
|
|
56
|
+
results.should == []
|
|
57
|
+
end
|
|
58
|
+
|
|
54
59
|
it "should be able to get multiple objects" do
|
|
55
60
|
results = @api.get_objects(["contextoptional", "naitik"])
|
|
56
|
-
results.
|
|
61
|
+
results.should have(2).items
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "should be able to get multiple objects if they're a string" do
|
|
65
|
+
results = @api.get_objects("contextoptional,naitik")
|
|
66
|
+
results.should have(2).items
|
|
57
67
|
end
|
|
58
68
|
|
|
59
69
|
it "should be able to access a user's picture" do
|
|
@@ -69,6 +79,16 @@ shared_examples_for "Koala GraphAPI" do
|
|
|
69
79
|
result.should be_a(Array)
|
|
70
80
|
end
|
|
71
81
|
|
|
82
|
+
it "should be able to access comments for a URL" do
|
|
83
|
+
result = @api.get_comments_for_urls(["http://developers.facebook.com/blog/post/472"])
|
|
84
|
+
(result["http://developers.facebook.com/blog/post/472"]).should
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it "should be able to access comments for 2 URLs" do
|
|
88
|
+
result = @api.get_comments_for_urls(["http://developers.facebook.com/blog/post/490", "http://developers.facebook.com/blog/post/472"])
|
|
89
|
+
(result["http://developers.facebook.com/blog/post/490"] && result["http://developers.facebook.com/blog/post/472"]).should
|
|
90
|
+
end
|
|
91
|
+
|
|
72
92
|
# SEARCH
|
|
73
93
|
it "should be able to search" do
|
|
74
94
|
result = @api.search("facebook")
|
|
@@ -86,7 +106,6 @@ end
|
|
|
86
106
|
|
|
87
107
|
|
|
88
108
|
shared_examples_for "Koala GraphAPI with an access token" do
|
|
89
|
-
|
|
90
109
|
it "should get private data about a user" do
|
|
91
110
|
result = @api.get_object("koppel")
|
|
92
111
|
# updated_time should be a pretty fixed test case
|
|
@@ -156,6 +175,14 @@ shared_examples_for "Koala GraphAPI with an access token" do
|
|
|
156
175
|
@temporary_object_id.should_not be_nil
|
|
157
176
|
end
|
|
158
177
|
|
|
178
|
+
it "uses the base HTTP service if the upload is a StringIO or similar" do
|
|
179
|
+
source = stub("UploadIO")
|
|
180
|
+
Koala::UploadableIO.stub(:new).and_return(source)
|
|
181
|
+
source.stub(:requires_base_http_service).and_return(true)
|
|
182
|
+
Koala.should_receive(:make_request).with(anything, anything, anything, hash_including(:http_service => Koala.base_http_service)).and_return(Koala::Response.new(200, "[]", {}))
|
|
183
|
+
@api.put_picture(StringIO.new)
|
|
184
|
+
end
|
|
185
|
+
|
|
159
186
|
it "should be able to post photos to the user's wall without an open file object" do
|
|
160
187
|
content_type = "image/jpg",
|
|
161
188
|
file_path = File.join(File.dirname(__FILE__), "..", "fixtures", "beach.jpg")
|
|
@@ -330,18 +357,18 @@ shared_examples_for "Koala GraphAPI with GraphCollection" do
|
|
|
330
357
|
|
|
331
358
|
it "should return the previous page of results" do
|
|
332
359
|
@result.should_receive(:previous_page_params).and_return([@base, @args])
|
|
333
|
-
@api.should_receive(:graph_call).with(@base, @args).
|
|
360
|
+
@api.should_receive(:graph_call).with(@base, @args).and_yield(@second_page)
|
|
334
361
|
Koala::Facebook::GraphCollection.should_receive(:new).with(@second_page, @api).and_return(@page_of_results)
|
|
335
362
|
|
|
336
|
-
@result.previous_page
|
|
363
|
+
@result.previous_page#.should == @page_of_results
|
|
337
364
|
end
|
|
338
365
|
|
|
339
366
|
it "should return the next page of results" do
|
|
340
367
|
@result.should_receive(:next_page_params).and_return([@base, @args])
|
|
341
|
-
@api.should_receive(:graph_call).with(@base, @args).
|
|
368
|
+
@api.should_receive(:graph_call).with(@base, @args).and_yield(@second_page)
|
|
342
369
|
Koala::Facebook::GraphCollection.should_receive(:new).with(@second_page, @api).and_return(@page_of_results)
|
|
343
370
|
|
|
344
|
-
@result.next_page
|
|
371
|
+
@result.next_page#.should == @page_of_results
|
|
345
372
|
end
|
|
346
373
|
|
|
347
374
|
it "should return nil it there are no other pages" do
|
|
@@ -421,4 +448,4 @@ shared_examples_for "Koala GraphAPI without an access token" do
|
|
|
421
448
|
it "should not be able to delete a like" do
|
|
422
449
|
lambda { @api.delete_like("7204941866_119776748033392") }.should raise_error(Koala::Facebook::APIError)
|
|
423
450
|
end
|
|
424
|
-
end
|
|
451
|
+
end
|
|
@@ -3,11 +3,12 @@ require 'yaml'
|
|
|
3
3
|
|
|
4
4
|
module Koala
|
|
5
5
|
module MockHTTPService
|
|
6
|
-
|
|
7
|
-
IS_MOCK = true # this lets our tests figure out if we want to stub methods
|
|
6
|
+
include Koala::HTTPService
|
|
8
7
|
|
|
8
|
+
# Mocks all HTTP requests for with koala_spec_with_mocks.rb
|
|
9
9
|
# Mocked values to be included in TEST_DATA used in specs
|
|
10
10
|
ACCESS_TOKEN = '*'
|
|
11
|
+
APP_ACCESS_TOKEN = "**"
|
|
11
12
|
OAUTH_CODE = 'OAUTHCODE'
|
|
12
13
|
|
|
13
14
|
# Loads testing data
|
|
@@ -18,7 +19,7 @@ module Koala
|
|
|
18
19
|
# Useful in mock_facebook_responses.yml
|
|
19
20
|
OAUTH_DATA = TEST_DATA['oauth_test_data']
|
|
20
21
|
OAUTH_DATA.merge!({
|
|
21
|
-
'app_access_token' =>
|
|
22
|
+
'app_access_token' => APP_ACCESS_TOKEN,
|
|
22
23
|
'session_key' => "session_key",
|
|
23
24
|
'multiple_session_keys' => ["session_key", "session_key_2"]
|
|
24
25
|
})
|
|
@@ -30,66 +31,63 @@ module Koala
|
|
|
30
31
|
mock_response_file_path = File.join(File.dirname(__FILE__), '..', 'fixtures', 'mock_facebook_responses.yml')
|
|
31
32
|
RESPONSES = YAML.load(ERB.new(IO.read(mock_response_file_path)).result(binding))
|
|
32
33
|
|
|
33
|
-
def self.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
path = 'root' if path == '' || path == '/'
|
|
40
|
-
verb ||= 'get'
|
|
41
|
-
server = options[:rest_api] ? 'rest_api' : 'graph_api'
|
|
42
|
-
with_token = args.delete('access_token') == ACCESS_TOKEN ? 'with_token' : 'no_token'
|
|
43
|
-
|
|
44
|
-
# Assume format is always JSON
|
|
45
|
-
args.delete('format')
|
|
34
|
+
def self.make_request(path, args, verb, options = {})
|
|
35
|
+
path = 'root' if path == '' || path == '/'
|
|
36
|
+
verb ||= 'get'
|
|
37
|
+
server = options[:rest_api] ? 'rest_api' : 'graph_api'
|
|
38
|
+
token = args.delete('access_token')
|
|
39
|
+
with_token = (token == ACCESS_TOKEN || token == APP_ACCESS_TOKEN) ? 'with_token' : 'no_token'
|
|
46
40
|
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
# Assume format is always JSON
|
|
42
|
+
args.delete('format')
|
|
49
43
|
|
|
50
|
-
|
|
51
|
-
|
|
44
|
+
# Create a hash key for the arguments
|
|
45
|
+
args = create_params_key(args)
|
|
52
46
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
# create response class object
|
|
57
|
-
response_object = if response.is_a? String
|
|
58
|
-
Koala::Response.new(200, response, {})
|
|
59
|
-
else
|
|
60
|
-
Koala::Response.new(response["code"] || 200, response["body"] || "", response["headers"] || {})
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
rescue NoMethodError
|
|
64
|
-
# Raises an error message with the place in the data YML
|
|
65
|
-
# to place a mock as well as a URL to request from
|
|
66
|
-
# Facebook's servers for the actual data
|
|
67
|
-
# (Don't forget to replace ACCESS_TOKEN with a real access token)
|
|
68
|
-
data_trace = [server, path, args, verb, with_token] * ': '
|
|
69
|
-
|
|
70
|
-
args = args == 'no_args' ? '' : "#{args}&"
|
|
71
|
-
args += 'format=json'
|
|
72
|
-
args += "&access_token=#{ACCESS_TOKEN}" if with_token
|
|
73
|
-
|
|
74
|
-
raise "Missing a mock response for #{data_trace}\nAPI PATH: #{[path, args].join('?')}"
|
|
75
|
-
end
|
|
47
|
+
begin
|
|
48
|
+
response = RESPONSES[server][path][args][verb][with_token]
|
|
76
49
|
|
|
77
|
-
|
|
78
|
-
|
|
50
|
+
# Raises an error of with_token/no_token key is missing
|
|
51
|
+
raise NoMethodError unless response
|
|
79
52
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
'no_args'
|
|
53
|
+
# create response class object
|
|
54
|
+
response_object = if response.is_a? String
|
|
55
|
+
Koala::Response.new(200, response, {})
|
|
84
56
|
else
|
|
85
|
-
|
|
86
|
-
arr[1] = '[FILE]' if arr[1].kind_of?(Koala::UploadableIO)
|
|
87
|
-
arr.join('=')
|
|
88
|
-
end.join('&')
|
|
57
|
+
Koala::Response.new(response["code"] || 200, response["body"] || "", response["headers"] || {})
|
|
89
58
|
end
|
|
90
|
-
end
|
|
91
59
|
|
|
92
|
-
|
|
93
|
-
|
|
60
|
+
rescue NoMethodError
|
|
61
|
+
# Raises an error message with the place in the data YML
|
|
62
|
+
# to place a mock as well as a URL to request from
|
|
63
|
+
# Facebook's servers for the actual data
|
|
64
|
+
# (Don't forget to replace ACCESS_TOKEN with a real access token)
|
|
65
|
+
data_trace = [server, path, args, verb, with_token] * ': '
|
|
66
|
+
|
|
67
|
+
args = args == 'no_args' ? '' : "#{args}&"
|
|
68
|
+
args += 'format=json'
|
|
69
|
+
args += "&access_token=#{ACCESS_TOKEN}" if with_token
|
|
70
|
+
|
|
71
|
+
raise "Missing a mock response for #{data_trace}\nAPI PATH: #{[path, args].join('?')}"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
response_object
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def self.mock?
|
|
78
|
+
true
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
protected
|
|
82
|
+
def self.create_params_key(params_hash)
|
|
83
|
+
if params_hash.empty?
|
|
84
|
+
'no_args'
|
|
85
|
+
else
|
|
86
|
+
params_hash.sort{ |a,b| a[0].to_s <=> b[0].to_s}.map do |arr|
|
|
87
|
+
arr[1] = '[FILE]' if arr[1].kind_of?(Koala::UploadableIO)
|
|
88
|
+
arr.join('=')
|
|
89
|
+
end.join('&')
|
|
90
|
+
end
|
|
91
|
+
end
|
|
94
92
|
end
|
|
95
93
|
end
|
|
@@ -87,6 +87,29 @@ shared_examples_for "Koala RestAPI" do
|
|
|
87
87
|
|
|
88
88
|
@api.rest_call('anything', {}, options)
|
|
89
89
|
end
|
|
90
|
+
|
|
91
|
+
it "uses get by default" do
|
|
92
|
+
@api.should_receive(:api).with(
|
|
93
|
+
anything,
|
|
94
|
+
anything,
|
|
95
|
+
"get",
|
|
96
|
+
anything
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
@api.rest_call('anything')
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it "allows you to specify other http methods as the last argument" do
|
|
103
|
+
method = 'bar'
|
|
104
|
+
@api.should_receive(:api).with(
|
|
105
|
+
anything,
|
|
106
|
+
anything,
|
|
107
|
+
method,
|
|
108
|
+
anything
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
@api.rest_call('anything', {}, {}, method)
|
|
112
|
+
end
|
|
90
113
|
|
|
91
114
|
it "should throw an APIError if the result hash has an error key" do
|
|
92
115
|
Koala.stub(:make_request).and_return(Koala::Response.new(500, {"error_code" => "An error occurred!"}, {}))
|
|
@@ -96,8 +119,7 @@ shared_examples_for "Koala RestAPI" do
|
|
|
96
119
|
describe "when making a FQL request" do
|
|
97
120
|
it "should call fql.query method" do
|
|
98
121
|
@api.should_receive(:rest_call).with(
|
|
99
|
-
"fql.query",
|
|
100
|
-
anything
|
|
122
|
+
"fql.query", anything, anything
|
|
101
123
|
).and_return(Koala::Response.new(200, "2", {}))
|
|
102
124
|
|
|
103
125
|
@api.fql_query stub('query string')
|
|
@@ -107,17 +129,78 @@ shared_examples_for "Koala RestAPI" do
|
|
|
107
129
|
query = stub('query string')
|
|
108
130
|
|
|
109
131
|
@api.should_receive(:rest_call).with(
|
|
110
|
-
anything,
|
|
111
|
-
hash_including("query" => query)
|
|
132
|
+
anything, hash_including(:query => query), anything
|
|
112
133
|
)
|
|
113
134
|
|
|
114
135
|
@api.fql_query(query)
|
|
115
136
|
end
|
|
137
|
+
|
|
138
|
+
it "should pass on any other arguments provided" do
|
|
139
|
+
args = {:a => 2}
|
|
140
|
+
@api.should_receive(:rest_call).with(anything, hash_including(args), anything)
|
|
141
|
+
@api.fql_query("a query", args)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
it "should pass on any http options provided" do
|
|
145
|
+
opts = {:a => 2}
|
|
146
|
+
@api.should_receive(:rest_call).with(anything, anything, hash_including(opts))
|
|
147
|
+
@api.fql_query("a query", {}, opts)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
describe "when making a FQL-multiquery request" do
|
|
152
|
+
it "should call fql.multiquery method" do
|
|
153
|
+
@api.should_receive(:rest_call).with(
|
|
154
|
+
"fql.multiquery", anything, anything
|
|
155
|
+
).and_return({})
|
|
156
|
+
|
|
157
|
+
@api.fql_multiquery stub('query string')
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it "should pass a queries argument" do
|
|
161
|
+
queries = stub('query string')
|
|
162
|
+
queries_json = "some JSON"
|
|
163
|
+
queries.stub(:to_json).and_return(queries_json)
|
|
164
|
+
|
|
165
|
+
@api.should_receive(:rest_call).with(
|
|
166
|
+
anything,
|
|
167
|
+
hash_including(:queries => queries_json),
|
|
168
|
+
anything
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
@api.fql_multiquery(queries)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
it "simplifies the response format" do
|
|
175
|
+
raw_results = [
|
|
176
|
+
{"name" => "query1", "fql_result_set" => [1, 2, 3]},
|
|
177
|
+
{"name" => "query2", "fql_result_set" => [:a, :b, :c]}
|
|
178
|
+
]
|
|
179
|
+
expected_results = {
|
|
180
|
+
"query1" => [1, 2, 3],
|
|
181
|
+
"query2" => [:a, :b, :c]
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
@api.stub(:rest_call).and_return(raw_results)
|
|
185
|
+
results = @api.fql_multiquery({:query => true})
|
|
186
|
+
results.should == expected_results
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
it "should pass on any other arguments provided" do
|
|
190
|
+
args = {:a => 2}
|
|
191
|
+
@api.should_receive(:rest_call).with(anything, hash_including(args), anything)
|
|
192
|
+
@api.fql_multiquery("a query", args)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it "should pass on any http options provided" do
|
|
196
|
+
opts = {:a => 2}
|
|
197
|
+
@api.should_receive(:rest_call).with(anything, anything, hash_including(opts))
|
|
198
|
+
@api.fql_multiquery("a query", {}, opts)
|
|
199
|
+
end
|
|
116
200
|
end
|
|
117
201
|
end
|
|
118
202
|
end
|
|
119
203
|
|
|
120
|
-
|
|
121
204
|
shared_examples_for "Koala RestAPI with an access token" do
|
|
122
205
|
# FQL
|
|
123
206
|
it "should be able to access public information via FQL" do
|
|
@@ -126,6 +209,16 @@ shared_examples_for "Koala RestAPI with an access token" do
|
|
|
126
209
|
result.first['first_name'].should == 'Chris'
|
|
127
210
|
end
|
|
128
211
|
|
|
212
|
+
it "should be able to access public information via FQL.multiquery" do
|
|
213
|
+
result = @api.fql_multiquery(
|
|
214
|
+
:query1 => 'select first_name from user where uid = 216743',
|
|
215
|
+
:query2 => 'select first_name from user where uid = 2905623'
|
|
216
|
+
)
|
|
217
|
+
result.size.should == 2
|
|
218
|
+
result["query1"].first['first_name'].should == 'Chris'
|
|
219
|
+
result["query2"].first['first_name'].should == 'Alex'
|
|
220
|
+
end
|
|
221
|
+
|
|
129
222
|
it "should be able to access protected information via FQL" do
|
|
130
223
|
# Tests agains the permissions fql table
|
|
131
224
|
|
|
@@ -138,10 +231,21 @@ shared_examples_for "Koala RestAPI with an access token" do
|
|
|
138
231
|
result = @api.fql_query("select read_stream from permissions where uid = #{id}")
|
|
139
232
|
|
|
140
233
|
result.size.should == 1
|
|
141
|
-
# we
|
|
142
|
-
# (should we keep this?)
|
|
234
|
+
# we've verified that you have read_stream permissions, so we can test against that
|
|
143
235
|
result.first["read_stream"].should == 1
|
|
144
236
|
end
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
it "should be able to access protected information via FQL.multiquery" do
|
|
240
|
+
result = @api.fql_multiquery(
|
|
241
|
+
:query1 => "select post_id from stream where source_id = me()",
|
|
242
|
+
:query2 => "select fromid from comment where post_id in (select post_id from #query1)",
|
|
243
|
+
:query3 => "select uid, name from user where uid in (select fromid from #query2)"
|
|
244
|
+
)
|
|
245
|
+
result.size.should == 3
|
|
246
|
+
result.keys.should include("query1", "query2", "query3")
|
|
247
|
+
end
|
|
248
|
+
|
|
145
249
|
end
|
|
146
250
|
|
|
147
251
|
|
|
@@ -153,9 +257,29 @@ shared_examples_for "Koala RestAPI without an access token" do
|
|
|
153
257
|
@result.size.should == 1
|
|
154
258
|
@result.first["first_name"].should == "Chris"
|
|
155
259
|
end
|
|
260
|
+
|
|
261
|
+
it "should be able to access public information via FQL.multiquery" do
|
|
262
|
+
result = @api.fql_multiquery(
|
|
263
|
+
:query1 => 'select first_name from user where uid = 216743',
|
|
264
|
+
:query2 => 'select first_name from user where uid = 2905623'
|
|
265
|
+
)
|
|
266
|
+
result.size.should == 2
|
|
267
|
+
result["query1"].first['first_name'].should == 'Chris'
|
|
268
|
+
result["query2"].first['first_name'].should == 'Alex'
|
|
269
|
+
end
|
|
156
270
|
|
|
157
271
|
it "should not be able to access protected information via FQL" do
|
|
158
272
|
lambda { @api.fql_query("select read_stream from permissions where uid = 216743") }.should raise_error(Koala::Facebook::APIError)
|
|
159
273
|
end
|
|
274
|
+
|
|
275
|
+
it "should not be able to access protected information via FQL.multiquery" do
|
|
276
|
+
lambda {
|
|
277
|
+
@api.fql_multiquery(
|
|
278
|
+
:query1 => "select post_id from stream where source_id = me()",
|
|
279
|
+
:query2 => "select fromid from comment where post_id in (select post_id from #query1)",
|
|
280
|
+
:query3 => "select uid, name from user where uid in (select fromid from #query2)"
|
|
281
|
+
)
|
|
282
|
+
}.should raise_error(Koala::Facebook::APIError)
|
|
283
|
+
end
|
|
160
284
|
end
|
|
161
285
|
end
|
|
@@ -5,11 +5,11 @@ module KoalaTest
|
|
|
5
5
|
# make sure we have the necessary permissions
|
|
6
6
|
api = Koala::Facebook::GraphAndRestAPI.new(token)
|
|
7
7
|
uid = api.get_object("me")["id"]
|
|
8
|
-
perms = api.fql_query("select read_stream, publish_stream, user_photos from permissions where uid = #{uid}")[0]
|
|
8
|
+
perms = api.fql_query("select read_stream, publish_stream, user_photos, read_insights from permissions where uid = #{uid}")[0]
|
|
9
9
|
perms.each_pair do |perm, value|
|
|
10
|
-
|
|
10
|
+
if value == (perm == "read_insights" ? 1 : 0) # live testing depends on insights calls failing
|
|
11
11
|
puts "failed!\n" # put a new line after the print above
|
|
12
|
-
raise ArgumentError, "Your access token must have the read_stream, publish_stream, and user_photos permissions. You have: #{perms.inspect}"
|
|
12
|
+
raise ArgumentError, "Your access token must have the read_stream, publish_stream, and user_photos permissions, and lack read_insights. You have: #{perms.inspect}"
|
|
13
13
|
end
|
|
14
14
|
end
|
|
15
15
|
puts "done!"
|