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
data/lib/koala/test_users.rb
CHANGED
|
@@ -3,7 +3,14 @@ require 'koala'
|
|
|
3
3
|
module Koala
|
|
4
4
|
module Facebook
|
|
5
5
|
module TestUserMethods
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
def self.included(base)
|
|
8
|
+
base.class_eval do
|
|
9
|
+
# make the Graph API accessible in case someone wants to make other calls to interact with their users
|
|
10
|
+
attr_reader :api, :app_id, :app_access_token, :secret
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
7
14
|
def initialize(options = {})
|
|
8
15
|
@app_id = options[:app_id]
|
|
9
16
|
@app_access_token = options[:app_access_token]
|
|
@@ -17,29 +24,34 @@ module Koala
|
|
|
17
24
|
oauth = Koala::Facebook::OAuth.new(@app_id, @secret)
|
|
18
25
|
@app_access_token = oauth.get_app_access_token
|
|
19
26
|
end
|
|
20
|
-
@
|
|
27
|
+
@api = API.new(@app_access_token)
|
|
21
28
|
end
|
|
22
29
|
|
|
23
30
|
def create(installed, permissions = nil, args = {}, options = {})
|
|
24
31
|
# Creates and returns a test user
|
|
25
32
|
args['installed'] = installed
|
|
26
33
|
args['permissions'] = (permissions.is_a?(Array) ? permissions.join(",") : permissions) if installed
|
|
27
|
-
result = @
|
|
34
|
+
result = @api.graph_call(accounts_path, args, "post", options)
|
|
28
35
|
end
|
|
29
|
-
|
|
36
|
+
|
|
30
37
|
def list
|
|
31
|
-
@
|
|
38
|
+
@api.graph_call(accounts_path)
|
|
32
39
|
end
|
|
33
|
-
|
|
40
|
+
|
|
34
41
|
def delete(test_user)
|
|
35
42
|
test_user = test_user["id"] if test_user.is_a?(Hash)
|
|
36
|
-
@
|
|
43
|
+
@api.delete_object(test_user)
|
|
37
44
|
end
|
|
38
|
-
|
|
45
|
+
|
|
39
46
|
def delete_all
|
|
40
|
-
list.each {|u| delete u
|
|
47
|
+
list.each {|u| delete u}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def update(test_user, args = {}, http_options = {})
|
|
51
|
+
test_user = test_user["id"] if test_user.is_a?(Hash)
|
|
52
|
+
@api.graph_call(test_user, args, "post", http_options)
|
|
41
53
|
end
|
|
42
|
-
|
|
54
|
+
|
|
43
55
|
def befriend(user1_hash, user2_hash)
|
|
44
56
|
user1_id = user1_hash["id"] || user1_hash[:id]
|
|
45
57
|
user2_id = user2_hash["id"] || user2_hash[:id]
|
|
@@ -51,16 +63,15 @@ module Koala
|
|
|
51
63
|
# but the Facebook call would fail
|
|
52
64
|
raise ArgumentError, "TestUsers#befriend requires hash arguments for both users with id and access_token"
|
|
53
65
|
end
|
|
54
|
-
|
|
55
|
-
u1_graph_api = GraphAPI.new(user1_token)
|
|
56
|
-
u2_graph_api = GraphAPI.new(user2_token)
|
|
57
66
|
|
|
58
|
-
u1_graph_api.
|
|
67
|
+
u1_graph_api = API.new(user1_token)
|
|
68
|
+
u2_graph_api = API.new(user2_token)
|
|
69
|
+
|
|
70
|
+
u1_graph_api.graph_call("#{user1_id}/friends/#{user2_id}", {}, "post") &&
|
|
59
71
|
u2_graph_api.graph_call("#{user2_id}/friends/#{user1_id}", {}, "post")
|
|
60
72
|
end
|
|
61
73
|
|
|
62
74
|
def create_network(network_size, installed = true, permissions = '')
|
|
63
|
-
network_size = 50 if network_size > 50 # FB's max is 50
|
|
64
75
|
users = (0...network_size).collect { create(installed, permissions) }
|
|
65
76
|
friends = users.clone
|
|
66
77
|
users.each do |user|
|
|
@@ -73,9 +84,14 @@ module Koala
|
|
|
73
84
|
end
|
|
74
85
|
return users
|
|
75
86
|
end
|
|
76
|
-
|
|
87
|
+
|
|
88
|
+
def graph_api
|
|
89
|
+
Koala::Utils.deprecate("the TestUsers.graph_api accessor is deprecated and will be removed in a future version; please use .api instead.")
|
|
90
|
+
@api
|
|
91
|
+
end
|
|
92
|
+
|
|
77
93
|
protected
|
|
78
|
-
|
|
94
|
+
|
|
79
95
|
def accounts_path
|
|
80
96
|
@accounts_path ||= "/#{@app_id}/accounts/test-users"
|
|
81
97
|
end
|
data/lib/koala/uploadable_io.rb
CHANGED
|
@@ -1,115 +1,181 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "net/http/post/multipart"
|
|
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, :filename
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
parse_init_mixed_param io_or_path_or_mixed, content_type
|
|
10
|
+
|
|
11
|
+
# filename is used in the Ads API
|
|
12
|
+
# if it's provided, take precedence over the detected filename
|
|
13
|
+
# otherwise, fall back to a dummy name
|
|
14
|
+
@filename = filename || @filename || "koala-io-file.dum"
|
|
15
|
+
|
|
16
16
|
raise KoalaError.new("Invalid arguments to initialize an UploadableIO") unless @io_or_path
|
|
17
|
-
raise KoalaError.new("Unable to determine MIME type for UploadableIO") if !@content_type
|
|
17
|
+
raise KoalaError.new("Unable to determine MIME type for UploadableIO") if !@content_type
|
|
18
18
|
end
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
def to_upload_io
|
|
21
|
-
UploadIO.new(@io_or_path, @content_type,
|
|
21
|
+
UploadIO.new(@io_or_path, @content_type, @filename)
|
|
22
22
|
end
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
def to_file
|
|
25
25
|
@io_or_path.is_a?(String) ? File.open(@io_or_path) : @io_or_path
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
def self.binary_content?(content)
|
|
29
|
+
content.is_a?(UploadableIO) || DETECTION_STRATEGIES.detect {|method| send(method, content)}
|
|
30
|
+
end
|
|
31
|
+
|
|
28
32
|
private
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
33
|
+
DETECTION_STRATEGIES = [
|
|
34
|
+
:sinatra_param?,
|
|
35
|
+
:rails_3_param?,
|
|
36
|
+
:file_param?
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
PARSE_STRATEGIES = [
|
|
40
|
+
:parse_rails_3_param,
|
|
41
|
+
:parse_sinatra_param,
|
|
42
|
+
:parse_file_object,
|
|
43
|
+
:parse_string_path,
|
|
44
|
+
:parse_io
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
def parse_init_mixed_param(mixed, content_type = nil)
|
|
48
|
+
PARSE_STRATEGIES.each do |method|
|
|
49
|
+
send(method, mixed, content_type)
|
|
50
|
+
return if @io_or_path && @content_type
|
|
41
51
|
end
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Expects a parameter of type ActionDispatch::Http::UploadedFile
|
|
55
|
+
def self.rails_3_param?(uploaded_file)
|
|
56
|
+
uploaded_file.respond_to?(:content_type) and uploaded_file.respond_to?(:tempfile) and uploaded_file.tempfile.respond_to?(:path)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def parse_rails_3_param(uploaded_file, content_type = nil)
|
|
60
|
+
if UploadableIO.rails_3_param?(uploaded_file)
|
|
61
|
+
@io_or_path = uploaded_file.tempfile.path
|
|
62
|
+
@content_type = content_type || uploaded_file.content_type
|
|
63
|
+
@filename = uploaded_file.original_filename
|
|
49
64
|
end
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Expects a Sinatra hash of file info
|
|
68
|
+
def self.sinatra_param?(file_hash)
|
|
69
|
+
file_hash.kind_of?(Hash) and file_hash.has_key?(:type) and file_hash.has_key?(:tempfile)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def parse_sinatra_param(file_hash, content_type = nil)
|
|
73
|
+
if UploadableIO.sinatra_param?(file_hash)
|
|
74
|
+
@io_or_path = file_hash[:tempfile]
|
|
75
|
+
@content_type = content_type || file_hash[:type] || detect_mime_type(tempfile)
|
|
76
|
+
@filename = file_hash[:filename]
|
|
57
77
|
end
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# takes a file object
|
|
81
|
+
def self.file_param?(file)
|
|
82
|
+
file.kind_of?(File)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def parse_file_object(file, content_type = nil)
|
|
86
|
+
if UploadableIO.file_param?(file)
|
|
87
|
+
@io_or_path = file
|
|
88
|
+
@content_type = content_type || detect_mime_type(file.path)
|
|
89
|
+
@filename = File.basename(file.path)
|
|
65
90
|
end
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def parse_string_path(path, content_type = nil)
|
|
94
|
+
if path.kind_of?(String)
|
|
95
|
+
@io_or_path = path
|
|
96
|
+
@content_type = content_type || detect_mime_type(path)
|
|
97
|
+
@filename = File.basename(path)
|
|
72
98
|
end
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def detect_mime_type(filename)
|
|
80
|
-
if filename
|
|
81
|
-
MIME_TYPE_STRATEGIES.each do |method|
|
|
82
|
-
result = send(method, filename)
|
|
83
|
-
return result if result
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
nil # if we can't find anything
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def parse_io(io, content_type = nil)
|
|
102
|
+
if io.respond_to?(:read)
|
|
103
|
+
@io_or_path = io
|
|
104
|
+
@content_type = content_type
|
|
87
105
|
end
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
MIME_TYPE_STRATEGIES = [
|
|
109
|
+
:use_mime_module,
|
|
110
|
+
:use_simple_detection
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
def detect_mime_type(filename)
|
|
114
|
+
if filename
|
|
115
|
+
MIME_TYPE_STRATEGIES.each do |method|
|
|
116
|
+
result = send(method, filename)
|
|
117
|
+
return result if result
|
|
97
118
|
end
|
|
98
119
|
end
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
120
|
+
nil # if we can't find anything
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def use_mime_module(filename)
|
|
124
|
+
# if the user has installed mime/types, we can use that
|
|
125
|
+
# if not, rescue and return nil
|
|
126
|
+
begin
|
|
127
|
+
type = MIME::Types.type_for(filename).first
|
|
128
|
+
type ? type.to_s : nil
|
|
129
|
+
rescue
|
|
130
|
+
nil
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def use_simple_detection(filename)
|
|
135
|
+
# very rudimentary extension analysis for images
|
|
136
|
+
# first, get the downcased extension, or an empty string if it doesn't exist
|
|
137
|
+
extension = ((filename.match(/\.([a-zA-Z0-9]+)$/) || [])[1] || "").downcase
|
|
138
|
+
case extension
|
|
139
|
+
when ""
|
|
105
140
|
nil
|
|
106
|
-
|
|
141
|
+
# images
|
|
142
|
+
when "jpg", "jpeg"
|
|
107
143
|
"image/jpeg"
|
|
108
|
-
|
|
144
|
+
when "png"
|
|
109
145
|
"image/png"
|
|
110
|
-
|
|
146
|
+
when "gif"
|
|
111
147
|
"image/gif"
|
|
112
|
-
|
|
148
|
+
|
|
149
|
+
# video
|
|
150
|
+
when "3g2"
|
|
151
|
+
"video/3gpp2"
|
|
152
|
+
when "3gp", "3gpp"
|
|
153
|
+
"video/3gpp"
|
|
154
|
+
when "asf"
|
|
155
|
+
"video/x-ms-asf"
|
|
156
|
+
when "avi"
|
|
157
|
+
"video/x-msvideo"
|
|
158
|
+
when "flv"
|
|
159
|
+
"video/x-flv"
|
|
160
|
+
when "m4v"
|
|
161
|
+
"video/x-m4v"
|
|
162
|
+
when "mkv"
|
|
163
|
+
"video/x-matroska"
|
|
164
|
+
when "mod"
|
|
165
|
+
"video/mod"
|
|
166
|
+
when "mov", "qt"
|
|
167
|
+
"video/quicktime"
|
|
168
|
+
when "mp4", "mpeg4"
|
|
169
|
+
"video/mp4"
|
|
170
|
+
when "mpe", "mpeg", "mpg", "tod", "vob"
|
|
171
|
+
"video/mpeg"
|
|
172
|
+
when "nsv"
|
|
173
|
+
"application/x-winamp"
|
|
174
|
+
when "ogm", "ogv"
|
|
175
|
+
"video/ogg"
|
|
176
|
+
when "wmv"
|
|
177
|
+
"video/x-ms-wmv"
|
|
113
178
|
end
|
|
179
|
+
end
|
|
114
180
|
end
|
|
115
|
-
end
|
|
181
|
+
end
|
data/lib/koala/utils.rb
ADDED