koala 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.autotest +12 -0
- data/.gitignore +3 -1
- data/.travis.yml +9 -0
- data/CHANGELOG +62 -2
- data/Gemfile +8 -0
- data/Rakefile +0 -1
- data/autotest/discover.rb +1 -0
- data/koala.gemspec +13 -14
- data/lib/koala/batch_operation.rb +74 -0
- data/lib/koala/graph_api.rb +145 -132
- data/lib/koala/graph_batch_api.rb +97 -0
- data/lib/koala/graph_collection.rb +59 -0
- data/lib/koala/http_service.rb +176 -0
- data/lib/koala/oauth.rb +191 -0
- data/lib/koala/realtime_updates.rb +23 -29
- data/lib/koala/rest_api.rb +13 -8
- data/lib/koala/test_users.rb +33 -17
- data/lib/koala/uploadable_io.rb +153 -87
- data/lib/koala/utils.rb +11 -0
- data/lib/koala/version.rb +3 -0
- data/lib/koala.rb +59 -217
- data/readme.md +92 -53
- data/spec/cases/{api_base_spec.rb → api_spec.rb} +31 -6
- data/spec/cases/error_spec.rb +32 -0
- data/spec/cases/graph_and_rest_api_spec.rb +12 -21
- data/spec/cases/graph_api_batch_spec.rb +582 -0
- data/spec/cases/graph_api_spec.rb +11 -14
- data/spec/cases/graph_collection_spec.rb +116 -0
- data/spec/cases/http_service_spec.rb +446 -0
- data/spec/cases/koala_spec.rb +54 -0
- data/spec/cases/oauth_spec.rb +319 -213
- data/spec/cases/realtime_updates_spec.rb +45 -31
- data/spec/cases/rest_api_spec.rb +23 -7
- data/spec/cases/test_users_spec.rb +123 -75
- data/spec/cases/uploadable_io_spec.rb +120 -37
- data/spec/cases/utils_spec.rb +10 -0
- data/spec/fixtures/cat.m4v +0 -0
- data/spec/fixtures/facebook_data.yml +26 -24
- data/spec/fixtures/mock_facebook_responses.yml +203 -78
- data/spec/spec_helper.rb +30 -5
- data/spec/support/graph_api_shared_examples.rb +149 -118
- data/spec/support/json_testing_fix.rb +42 -0
- data/spec/support/koala_test.rb +187 -0
- data/spec/support/mock_http_service.rb +62 -58
- data/spec/support/ordered_hash.rb +205 -0
- data/spec/support/rest_api_shared_examples.rb +139 -15
- data/spec/support/uploadable_io_shared_examples.rb +2 -8
- metadata +90 -114
- data/lib/koala/http_services.rb +0 -146
- data/spec/cases/http_services/http_service_spec.rb +0 -54
- data/spec/cases/http_services/net_http_service_spec.rb +0 -350
- data/spec/cases/http_services/typhoeus_service_spec.rb +0 -144
- data/spec/support/live_testing_data_helper.rb +0 -40
- data/spec/support/setup_mocks_or_live.rb +0 -52
|
@@ -3,22 +3,27 @@ require 'yaml'
|
|
|
3
3
|
|
|
4
4
|
module Koala
|
|
5
5
|
module MockHTTPService
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
include Koala::HTTPService
|
|
7
|
+
|
|
8
|
+
# fix our specs to use ok_json, so we always get the same results from to_json
|
|
9
|
+
MultiJson.engine = :ok_json
|
|
8
10
|
|
|
11
|
+
# Mocks all HTTP requests for with koala_spec_with_mocks.rb
|
|
9
12
|
# Mocked values to be included in TEST_DATA used in specs
|
|
10
13
|
ACCESS_TOKEN = '*'
|
|
14
|
+
APP_ACCESS_TOKEN = "**"
|
|
11
15
|
OAUTH_CODE = 'OAUTHCODE'
|
|
12
16
|
|
|
13
17
|
# Loads testing data
|
|
14
18
|
TEST_DATA = YAML.load_file(File.join(File.dirname(__FILE__), '..', 'fixtures', 'facebook_data.yml'))
|
|
15
19
|
TEST_DATA.merge!('oauth_token' => Koala::MockHTTPService::ACCESS_TOKEN)
|
|
16
20
|
TEST_DATA['oauth_test_data'].merge!('code' => Koala::MockHTTPService::OAUTH_CODE)
|
|
17
|
-
|
|
21
|
+
TEST_DATA['search_time'] = (Time.now - 3600).to_s
|
|
22
|
+
|
|
18
23
|
# Useful in mock_facebook_responses.yml
|
|
19
24
|
OAUTH_DATA = TEST_DATA['oauth_test_data']
|
|
20
25
|
OAUTH_DATA.merge!({
|
|
21
|
-
'app_access_token' =>
|
|
26
|
+
'app_access_token' => APP_ACCESS_TOKEN,
|
|
22
27
|
'session_key' => "session_key",
|
|
23
28
|
'multiple_session_keys' => ["session_key", "session_key_2"]
|
|
24
29
|
})
|
|
@@ -30,66 +35,65 @@ module Koala
|
|
|
30
35
|
mock_response_file_path = File.join(File.dirname(__FILE__), '..', 'fixtures', 'mock_facebook_responses.yml')
|
|
31
36
|
RESPONSES = YAML.load(ERB.new(IO.read(mock_response_file_path)).result(binding))
|
|
32
37
|
|
|
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'
|
|
38
|
+
def self.make_request(path, args, verb, options = {})
|
|
39
|
+
path = 'root' if path == '' || path == '/'
|
|
40
|
+
verb ||= 'get'
|
|
41
|
+
server = options[:rest_api] ? 'rest_api' : 'graph_api'
|
|
42
|
+
token = args.delete('access_token')
|
|
43
|
+
with_token = (token == ACCESS_TOKEN || token == APP_ACCESS_TOKEN) ? 'with_token' : 'no_token'
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
# Assume format is always JSON
|
|
46
|
+
args.delete('format')
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
# Create a hash key for the arguments
|
|
49
|
+
args = create_params_key(args)
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
# Raises an error of with_token/no_token key is missing
|
|
54
|
-
raise NoMethodError unless response
|
|
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
|
|
51
|
+
begin
|
|
52
|
+
response = RESPONSES[server][path][args][verb][with_token]
|
|
76
53
|
|
|
77
|
-
|
|
78
|
-
|
|
54
|
+
# Raises an error of with_token/no_token key is missing
|
|
55
|
+
raise NoMethodError unless response
|
|
79
56
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
'no_args'
|
|
57
|
+
# create response class object
|
|
58
|
+
response_object = if response.is_a? String
|
|
59
|
+
Koala::Response.new(200, response, {})
|
|
84
60
|
else
|
|
85
|
-
|
|
86
|
-
arr[1] = '[FILE]' if arr[1].kind_of?(Koala::UploadableIO)
|
|
87
|
-
arr.join('=')
|
|
88
|
-
end.join('&')
|
|
61
|
+
Koala::Response.new(response["code"] || 200, response["body"] || "", response["headers"] || {})
|
|
89
62
|
end
|
|
90
|
-
end
|
|
91
63
|
|
|
92
|
-
|
|
93
|
-
|
|
64
|
+
rescue NoMethodError
|
|
65
|
+
# Raises an error message with the place in the data YML
|
|
66
|
+
# to place a mock as well as a URL to request from
|
|
67
|
+
# Facebook's servers for the actual data
|
|
68
|
+
# (Don't forget to replace ACCESS_TOKEN with a real access token)
|
|
69
|
+
data_trace = [server, path, args, verb, with_token] * ': '
|
|
70
|
+
|
|
71
|
+
args = args == 'no_args' ? '' : "#{args}&"
|
|
72
|
+
args += 'format=json'
|
|
73
|
+
args += "&access_token=#{ACCESS_TOKEN}" if with_token
|
|
74
|
+
|
|
75
|
+
raise "Missing a mock response for #{data_trace}\nAPI PATH: #{[path, args].join('?')}"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
response_object
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def self.encode_params(*args)
|
|
82
|
+
# use HTTPService's encode_params
|
|
83
|
+
HTTPService.encode_params(*args)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
protected
|
|
87
|
+
|
|
88
|
+
def self.create_params_key(params_hash)
|
|
89
|
+
if params_hash.empty?
|
|
90
|
+
'no_args'
|
|
91
|
+
else
|
|
92
|
+
params_hash.sort{ |a,b| a[0].to_s <=> b[0].to_s}.map do |arr|
|
|
93
|
+
arr[1] = '[FILE]' if arr[1].kind_of?(Koala::UploadableIO)
|
|
94
|
+
arr.join('=')
|
|
95
|
+
end.join('&')
|
|
96
|
+
end
|
|
97
|
+
end
|
|
94
98
|
end
|
|
95
|
-
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
module KoalaTest
|
|
2
|
+
# directly taken from Rails 3.1's OrderedHash
|
|
3
|
+
# see https://github.com/rails/rails/blob/master/activesupport/lib/active_support/ordered_hash.rb
|
|
4
|
+
|
|
5
|
+
# The order of iteration over hashes in Ruby 1.8 is undefined. For example, you do not know the
|
|
6
|
+
# order in which +keys+ will return keys, or +each+ yield pairs. <tt>ActiveSupport::OrderedHash</tt>
|
|
7
|
+
# implements a hash that preserves insertion order, as in Ruby 1.9:
|
|
8
|
+
#
|
|
9
|
+
# oh = ActiveSupport::OrderedHash.new
|
|
10
|
+
# oh[:a] = 1
|
|
11
|
+
# oh[:b] = 2
|
|
12
|
+
# oh.keys # => [:a, :b], this order is guaranteed
|
|
13
|
+
#
|
|
14
|
+
# <tt>ActiveSupport::OrderedHash</tt> is namespaced to prevent conflicts with other implementations.
|
|
15
|
+
class OrderedHash < ::Hash #:nodoc:
|
|
16
|
+
def to_yaml_type
|
|
17
|
+
"!tag:yaml.org,2002:omap"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def encode_with(coder)
|
|
21
|
+
coder.represent_seq '!omap', map { |k,v| { k => v } }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def to_yaml(opts = {})
|
|
25
|
+
if YAML.const_defined?(:ENGINE) && !YAML::ENGINE.syck?
|
|
26
|
+
return super
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
YAML.quick_emit(self, opts) do |out|
|
|
30
|
+
out.seq(taguri) do |seq|
|
|
31
|
+
each do |k, v|
|
|
32
|
+
seq.add(k => v)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def nested_under_indifferent_access
|
|
39
|
+
self
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Hash is ordered in Ruby 1.9!
|
|
43
|
+
if RUBY_VERSION < '1.9'
|
|
44
|
+
|
|
45
|
+
# In MRI the Hash class is core and written in C. In particular, methods are
|
|
46
|
+
# programmed with explicit C function calls and polymorphism is not honored.
|
|
47
|
+
#
|
|
48
|
+
# For example, []= is crucial in this implementation to maintain the @keys
|
|
49
|
+
# array but hash.c invokes rb_hash_aset() originally. This prevents method
|
|
50
|
+
# reuse through inheritance and forces us to reimplement stuff.
|
|
51
|
+
#
|
|
52
|
+
# For instance, we cannot use the inherited #merge! because albeit the algorithm
|
|
53
|
+
# itself would work, our []= is not being called at all by the C code.
|
|
54
|
+
|
|
55
|
+
def initialize(*args, &block)
|
|
56
|
+
super
|
|
57
|
+
@keys = []
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.[](*args)
|
|
61
|
+
ordered_hash = new
|
|
62
|
+
|
|
63
|
+
if (args.length == 1 && args.first.is_a?(Array))
|
|
64
|
+
args.first.each do |key_value_pair|
|
|
65
|
+
next unless (key_value_pair.is_a?(Array))
|
|
66
|
+
ordered_hash[key_value_pair[0]] = key_value_pair[1]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
return ordered_hash
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
unless (args.size % 2 == 0)
|
|
73
|
+
raise ArgumentError.new("odd number of arguments for Hash")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
args.each_with_index do |val, ind|
|
|
77
|
+
next if (ind % 2 != 0)
|
|
78
|
+
ordered_hash[val] = args[ind + 1]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
ordered_hash
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def initialize_copy(other)
|
|
85
|
+
super
|
|
86
|
+
# make a deep copy of keys
|
|
87
|
+
@keys = other.keys
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def []=(key, value)
|
|
91
|
+
@keys << key unless has_key?(key)
|
|
92
|
+
super
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def delete(key)
|
|
96
|
+
if has_key? key
|
|
97
|
+
index = @keys.index(key)
|
|
98
|
+
@keys.delete_at index
|
|
99
|
+
end
|
|
100
|
+
super
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def delete_if
|
|
104
|
+
super
|
|
105
|
+
sync_keys!
|
|
106
|
+
self
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def reject!
|
|
110
|
+
super
|
|
111
|
+
sync_keys!
|
|
112
|
+
self
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def reject(&block)
|
|
116
|
+
dup.reject!(&block)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def keys
|
|
120
|
+
@keys.dup
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def values
|
|
124
|
+
@keys.collect { |key| self[key] }
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def to_hash
|
|
128
|
+
self
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def to_a
|
|
132
|
+
@keys.map { |key| [ key, self[key] ] }
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def each_key
|
|
136
|
+
return to_enum(:each_key) unless block_given?
|
|
137
|
+
@keys.each { |key| yield key }
|
|
138
|
+
self
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def each_value
|
|
142
|
+
return to_enum(:each_value) unless block_given?
|
|
143
|
+
@keys.each { |key| yield self[key]}
|
|
144
|
+
self
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def each
|
|
148
|
+
return to_enum(:each) unless block_given?
|
|
149
|
+
@keys.each {|key| yield [key, self[key]]}
|
|
150
|
+
self
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
alias_method :each_pair, :each
|
|
154
|
+
|
|
155
|
+
alias_method :select, :find_all
|
|
156
|
+
|
|
157
|
+
def clear
|
|
158
|
+
super
|
|
159
|
+
@keys.clear
|
|
160
|
+
self
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def shift
|
|
164
|
+
k = @keys.first
|
|
165
|
+
v = delete(k)
|
|
166
|
+
[k, v]
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def merge!(other_hash)
|
|
170
|
+
if block_given?
|
|
171
|
+
other_hash.each { |k, v| self[k] = key?(k) ? yield(k, self[k], v) : v }
|
|
172
|
+
else
|
|
173
|
+
other_hash.each { |k, v| self[k] = v }
|
|
174
|
+
end
|
|
175
|
+
self
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
alias_method :update, :merge!
|
|
179
|
+
|
|
180
|
+
def merge(other_hash, &block)
|
|
181
|
+
dup.merge!(other_hash, &block)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# When replacing with another hash, the initial order of our keys must come from the other hash -ordered or not.
|
|
185
|
+
def replace(other)
|
|
186
|
+
super
|
|
187
|
+
@keys = other.keys
|
|
188
|
+
self
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def invert
|
|
192
|
+
OrderedHash[self.to_a.map!{|key_value_pair| key_value_pair.reverse}]
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def inspect
|
|
196
|
+
"#<OrderedHash #{super}>"
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
private
|
|
200
|
+
def sync_keys!
|
|
201
|
+
@keys.delete_if {|k| !has_key?(k)}
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
@@ -88,6 +88,29 @@ shared_examples_for "Koala RestAPI" do
|
|
|
88
88
|
@api.rest_call('anything', {}, options)
|
|
89
89
|
end
|
|
90
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
|
|
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!"}, {}))
|
|
93
116
|
lambda { @api.rest_call("koppel", {}) }.should raise_exception(Koala::Facebook::APIError)
|
|
@@ -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,23 +129,94 @@ 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 'query string'
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it "should pass a queries argument" do
|
|
161
|
+
queries = stub('query string')
|
|
162
|
+
queries_json = "some JSON"
|
|
163
|
+
MultiJson.stub(:encode).with(queries).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
|
|
124
|
-
result = @api.fql_query(
|
|
207
|
+
result = @api.fql_query("select first_name from user where uid = #{KoalaTest.user2_id}")
|
|
125
208
|
result.size.should == 1
|
|
126
|
-
result.first['first_name'].should ==
|
|
209
|
+
result.first['first_name'].should == KoalaTest.user2_name
|
|
210
|
+
end
|
|
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 = #{KoalaTest.user2_id}",
|
|
215
|
+
:query2 => "select first_name from user where uid = #{KoalaTest.user1_id}"
|
|
216
|
+
)
|
|
217
|
+
result.size.should == 2
|
|
218
|
+
result["query1"].first['first_name'].should == KoalaTest.user2_name
|
|
219
|
+
result["query2"].first['first_name'].should == KoalaTest.user1_name
|
|
127
220
|
end
|
|
128
221
|
|
|
129
222
|
it "should be able to access protected information via FQL" do
|
|
@@ -131,17 +224,28 @@ shared_examples_for "Koala RestAPI with an access token" do
|
|
|
131
224
|
|
|
132
225
|
# get the current user's ID
|
|
133
226
|
# we're sneakily using the Graph API, which should be okay since it has its own tests
|
|
134
|
-
g = Koala::Facebook::
|
|
227
|
+
g = Koala::Facebook::API.new(@token)
|
|
135
228
|
id = g.get_object("me", :fields => "id")["id"]
|
|
136
229
|
|
|
137
230
|
# now send a query about your permissions
|
|
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
|
|
|
@@ -149,13 +253,33 @@ shared_examples_for "Koala RestAPI without an access token" do
|
|
|
149
253
|
# FQL_QUERY
|
|
150
254
|
describe "when making a FQL request" do
|
|
151
255
|
it "should be able to access public information via FQL" do
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
256
|
+
result = @api.fql_query("select first_name from user where uid = #{KoalaTest.user2_id}")
|
|
257
|
+
result.size.should == 1
|
|
258
|
+
result.first['first_name'].should == KoalaTest.user2_name
|
|
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 = #{KoalaTest.user2_id}",
|
|
264
|
+
:query2 => "select first_name from user where uid = #{KoalaTest.user1_id}"
|
|
265
|
+
)
|
|
266
|
+
result.size.should == 2
|
|
267
|
+
result["query1"].first['first_name'].should == KoalaTest.user2_name
|
|
268
|
+
result["query2"].first['first_name'].should == KoalaTest.user1_name
|
|
155
269
|
end
|
|
156
270
|
|
|
157
271
|
it "should not be able to access protected information via FQL" do
|
|
158
|
-
lambda { @api.fql_query("select read_stream from permissions where uid =
|
|
272
|
+
lambda { @api.fql_query("select read_stream from permissions where uid = #{KoalaTest.user2_id}") }.should raise_error(Koala::Facebook::APIError)
|
|
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)
|
|
159
283
|
end
|
|
160
284
|
end
|
|
161
|
-
end
|
|
285
|
+
end
|
|
@@ -36,15 +36,9 @@ shared_examples_for "MIME::Types can't return results" do
|
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
it "should throw an exception
|
|
40
|
-
Koala.stub!(:multipart_requires_content_type?).and_return(true)
|
|
39
|
+
it "should throw an exception" do
|
|
41
40
|
lambda { Koala::UploadableIO.new(*@koala_io_params) }.should raise_exception(Koala::KoalaError)
|
|
42
41
|
end
|
|
43
|
-
|
|
44
|
-
it "should just have @content_type == nil if the HTTP service doesn't require content type" do
|
|
45
|
-
Koala.stub!(:multipart_requires_content_type?).and_return(false)
|
|
46
|
-
Koala::UploadableIO.new(*@koala_io_params).content_type.should be_nil
|
|
47
|
-
end
|
|
48
42
|
end
|
|
49
43
|
end
|
|
50
44
|
|
|
@@ -73,4 +67,4 @@ shared_examples_for "determining a mime type" do
|
|
|
73
67
|
|
|
74
68
|
it_should_behave_like "MIME::Types can't return results"
|
|
75
69
|
end
|
|
76
|
-
end
|
|
70
|
+
end
|