koala 1.1.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.
Files changed (52) hide show
  1. data/.travis.yml +2 -1
  2. data/CHANGELOG +36 -0
  3. data/Gemfile +6 -2
  4. data/Rakefile +0 -1
  5. data/koala.gemspec +7 -8
  6. data/lib/koala/batch_operation.rb +15 -15
  7. data/lib/koala/graph_api.rb +85 -73
  8. data/lib/koala/graph_batch_api.rb +21 -11
  9. data/lib/koala/graph_collection.rb +13 -8
  10. data/lib/koala/http_service.rb +176 -0
  11. data/lib/koala/oauth.rb +34 -24
  12. data/lib/koala/realtime_updates.rb +21 -18
  13. data/lib/koala/rest_api.rb +1 -1
  14. data/lib/koala/test_users.rb +33 -17
  15. data/lib/koala/uploadable_io.rb +49 -43
  16. data/lib/koala/utils.rb +11 -0
  17. data/lib/koala/version.rb +3 -0
  18. data/lib/koala.rb +47 -45
  19. data/readme.md +58 -38
  20. data/spec/cases/{api_base_spec.rb → api_spec.rb} +27 -2
  21. data/spec/cases/error_spec.rb +32 -0
  22. data/spec/cases/graph_and_rest_api_spec.rb +12 -21
  23. data/spec/cases/graph_api_batch_spec.rb +92 -119
  24. data/spec/cases/graph_api_spec.rb +11 -14
  25. data/spec/cases/graph_collection_spec.rb +116 -0
  26. data/spec/cases/http_service_spec.rb +446 -0
  27. data/spec/cases/koala_spec.rb +36 -37
  28. data/spec/cases/oauth_spec.rb +318 -212
  29. data/spec/cases/realtime_updates_spec.rb +45 -31
  30. data/spec/cases/rest_api_spec.rb +23 -7
  31. data/spec/cases/test_users_spec.rb +123 -75
  32. data/spec/cases/uploadable_io_spec.rb +77 -36
  33. data/spec/cases/utils_spec.rb +10 -0
  34. data/spec/fixtures/facebook_data.yml +26 -24
  35. data/spec/fixtures/mock_facebook_responses.yml +131 -101
  36. data/spec/spec_helper.rb +29 -5
  37. data/spec/support/graph_api_shared_examples.rb +80 -120
  38. data/spec/support/json_testing_fix.rb +35 -11
  39. data/spec/support/koala_test.rb +187 -0
  40. data/spec/support/mock_http_service.rb +8 -5
  41. data/spec/support/ordered_hash.rb +205 -0
  42. data/spec/support/rest_api_shared_examples.rb +37 -37
  43. data/spec/support/uploadable_io_shared_examples.rb +2 -8
  44. metadata +72 -83
  45. data/lib/koala/http_services/net_http_service.rb +0 -92
  46. data/lib/koala/http_services/typhoeus_service.rb +0 -37
  47. data/lib/koala/http_services.rb +0 -46
  48. data/spec/cases/http_services/http_service_spec.rb +0 -129
  49. data/spec/cases/http_services/net_http_service_spec.rb +0 -532
  50. data/spec/cases/http_services/typhoeus_service_spec.rb +0 -152
  51. data/spec/support/live_testing_data_helper.rb +0 -40
  52. data/spec/support/setup_mocks_or_live.rb +0 -51
@@ -0,0 +1,176 @@
1
+ require 'faraday'
2
+
3
+ module Koala
4
+ class Response
5
+ attr_reader :status, :body, :headers
6
+ def initialize(status, body, headers)
7
+ @status = status
8
+ @body = body
9
+ @headers = headers
10
+ end
11
+ end
12
+
13
+ module HTTPService
14
+ # common functionality for all HTTP services
15
+
16
+ class << self
17
+ attr_accessor :faraday_middleware, :http_options
18
+ end
19
+
20
+ @http_options ||= {}
21
+
22
+ DEFAULT_MIDDLEWARE = Proc.new do |builder|
23
+ builder.request :multipart
24
+ builder.request :url_encoded
25
+ builder.adapter Faraday.default_adapter
26
+ end
27
+
28
+ def self.server(options = {})
29
+ server = "#{options[:rest_api] ? Facebook::REST_SERVER : Facebook::GRAPH_SERVER}"
30
+ server.gsub!(/\.facebook/, "-video.facebook") if options[:video]
31
+ "#{options[:use_ssl] ? "https" : "http"}://#{options[:beta] ? "beta." : ""}#{server}"
32
+ end
33
+
34
+ def self.make_request(path, args, verb, options = {})
35
+ # if the verb isn't get or post, send it as a post argument
36
+ args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
37
+
38
+ # turn all the keys to strings (Faraday has issues with symbols under 1.8.7) and resolve UploadableIOs
39
+ params = args.inject({}) {|hash, kv| hash[kv.first.to_s] = kv.last.is_a?(UploadableIO) ? kv.last.to_upload_io : kv.last; hash}
40
+
41
+ # figure out our options for this request
42
+ request_options = {:params => (verb == "get" ? params : {})}.merge(http_options || {}).merge(process_options(options))
43
+ request_options[:use_ssl] = true if args["access_token"] # require http if there's a token
44
+
45
+ # set up our Faraday connection
46
+ # we have to manually assign params to the URL or the
47
+ conn = Faraday.new(server(request_options), request_options, &(faraday_middleware || DEFAULT_MIDDLEWARE))
48
+
49
+ response = conn.send(verb, path, (verb == "post" ? params : {}))
50
+ Koala::Response.new(response.status.to_i, response.body, response.headers)
51
+ end
52
+
53
+ def self.encode_params(param_hash)
54
+ # unfortunately, we can't use to_query because that's Rails, not Ruby
55
+ # if no hash (e.g. no auth token) return empty string
56
+ # this is used mainly by the Batch API nowadays
57
+ ((param_hash || {}).collect do |key_and_value|
58
+ key_and_value[1] = MultiJson.encode(key_and_value[1]) unless key_and_value[1].is_a? String
59
+ "#{key_and_value[0].to_s}=#{CGI.escape key_and_value[1]}"
60
+ end).join("&")
61
+ end
62
+
63
+ # deprecations
64
+ # not elegant or compact code, but temporary
65
+
66
+ def self.always_use_ssl
67
+ Koala::Utils.deprecate("HTTPService.always_use_ssl is now HTTPService.http_options[:use_ssl]; always_use_ssl will be removed in a future version.")
68
+ http_options[:use_ssl]
69
+ end
70
+
71
+ def self.always_use_ssl=(value)
72
+ Koala::Utils.deprecate("HTTPService.always_use_ssl is now HTTPService.http_options[:use_ssl]; always_use_ssl will be removed in a future version.")
73
+ http_options[:use_ssl] = value
74
+ end
75
+
76
+ def self.timeout
77
+ Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.http_options[:timeout]; .timeout will be removed in a future version.")
78
+ http_options[:timeout]
79
+ end
80
+
81
+ def self.timeout=(value)
82
+ Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.http_options[:timeout]; .timeout will be removed in a future version.")
83
+ http_options[:timeout] = value
84
+ end
85
+
86
+ def self.timeout
87
+ Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.http_options[:timeout]; .timeout will be removed in a future version.")
88
+ http_options[:timeout]
89
+ end
90
+
91
+ def self.timeout=(value)
92
+ Koala::Utils.deprecate("HTTPService.timeout is now HTTPService.http_options[:timeout]; .timeout will be removed in a future version.")
93
+ http_options[:timeout] = value
94
+ end
95
+
96
+ def self.proxy
97
+ Koala::Utils.deprecate("HTTPService.proxy is now HTTPService.http_options[:proxy]; .proxy will be removed in a future version.")
98
+ http_options[:proxy]
99
+ end
100
+
101
+ def self.proxy=(value)
102
+ Koala::Utils.deprecate("HTTPService.proxy is now HTTPService.http_options[:proxy]; .proxy will be removed in a future version.")
103
+ http_options[:proxy] = value
104
+ end
105
+
106
+ def self.ca_path
107
+ Koala::Utils.deprecate("HTTPService.ca_path is now (HTTPService.http_options[:ssl] ||= {})[:ca_path]; .ca_path will be removed in a future version.")
108
+ (http_options[:ssl] || {})[:ca_path]
109
+ end
110
+
111
+ def self.ca_path=(value)
112
+ Koala::Utils.deprecate("HTTPService.ca_path is now (HTTPService.http_options[:ssl] ||= {})[:ca_path]; .ca_path will be removed in a future version.")
113
+ (http_options[:ssl] ||= {})[:ca_path] = value
114
+ end
115
+
116
+ def self.ca_file
117
+ Koala::Utils.deprecate("HTTPService.ca_file is now (HTTPService.http_options[:ssl] ||= {})[:ca_file]; .ca_file will be removed in a future version.")
118
+ (http_options[:ssl] || {})[:ca_file]
119
+ end
120
+
121
+ def self.ca_file=(value)
122
+ Koala::Utils.deprecate("HTTPService.ca_file is now (HTTPService.http_options[:ssl] ||= {})[:ca_file]; .ca_file will be removed in a future version.")
123
+ (http_options[:ssl] ||= {})[:ca_file] = value
124
+ end
125
+
126
+ def self.verify_mode
127
+ Koala::Utils.deprecate("HTTPService.verify_mode is now (HTTPService.http_options[:ssl] ||= {})[:verify_mode]; .verify_mode will be removed in a future version.")
128
+ (http_options[:ssl] || {})[:verify_mode]
129
+ end
130
+
131
+ def self.verify_mode=(value)
132
+ Koala::Utils.deprecate("HTTPService.verify_mode is now (HTTPService.http_options[:ssl] ||= {})[:verify_mode]; .verify_mode will be removed in a future version.")
133
+ (http_options[:ssl] ||= {})[:verify_mode] = value
134
+ end
135
+
136
+ def self.process_options(options)
137
+ if typhoeus_options = options.delete(:typhoeus_options)
138
+ Koala::Utils.deprecate("typhoeus_options should now be included directly in the http_options hash. Support for this key will be removed in a future version.")
139
+ options = options.merge(typhoeus_options)
140
+ end
141
+
142
+ if ca_file = options.delete(:ca_file)
143
+ Koala::Utils.deprecate("http_options[:ca_file] should now be passed inside (http_options[:ssl] = {}) -- that is, http_options[:ssl][:ca_file]. Support for this key will be removed in a future version.")
144
+ (options[:ssl] ||= {})[:ca_file] = ca_file
145
+ end
146
+
147
+ if ca_path = options.delete(:ca_path)
148
+ Koala::Utils.deprecate("http_options[:ca_path] should now be passed inside (http_options[:ssl] = {}) -- that is, http_options[:ssl][:ca_path]. Support for this key will be removed in a future version.")
149
+ (options[:ssl] ||= {})[:ca_path] = ca_path
150
+ end
151
+
152
+ if verify_mode = options.delete(:verify_mode)
153
+ Koala::Utils.deprecate("http_options[:verify_mode] should now be passed inside (http_options[:ssl] = {}) -- that is, http_options[:ssl][:verify_mode]. Support for this key will be removed in a future version.")
154
+ (options[:ssl] ||= {})[:verify_mode] = verify_mode
155
+ end
156
+
157
+ options
158
+ end
159
+ end
160
+
161
+ module TyphoeusService
162
+ def self.deprecated_interface
163
+ # support old-style interface with a warning
164
+ Koala::Utils.deprecate("the TyphoeusService module is deprecated; to use Typhoeus, set Faraday.default_adapter = :typhoeus. Enabling Typhoeus for all Faraday connections.")
165
+ Faraday.default_adapter = :typhoeus
166
+ end
167
+ end
168
+
169
+ module NetHTTPService
170
+ def self.deprecated_interface
171
+ # support old-style interface with a warning
172
+ Koala::Utils.deprecate("the NetHTTPService module is deprecated; to use Net::HTTP, set Faraday.default_adapter = :net_http. Enabling Net::HTTP for all Faraday connections.")
173
+ Faraday.default_adapter = :net_http
174
+ end
175
+ end
176
+ end
data/lib/koala/oauth.rb CHANGED
@@ -9,31 +9,18 @@ module Koala
9
9
  end
10
10
 
11
11
  def get_user_info_from_cookie(cookie_hash)
12
- # Parses the cookie set by the official Facebook JavaScript SDK.
13
- #
14
- # cookies should be a Hash, like the one Rails provides
12
+ # Parses the cookie set Facebook's JavaScript SDK.
13
+ # You can pass Rack/Rails/Sinatra's cookie hash directly to this method.
15
14
  #
16
15
  # If the user is logged in via Facebook, we return a dictionary with the
17
16
  # keys "uid" and "access_token". The former is the user's Facebook ID,
18
17
  # and the latter can be used to make authenticated requests to the Graph API.
19
18
  # If the user is not logged in, we return None.
20
- #
21
- # Download the official Facebook JavaScript SDK at
22
- # http://github.com/facebook/connect-js/. Read more about Facebook
23
- # authentication at http://developers.facebook.com/docs/authentication/.
24
-
25
- if fb_cookie = cookie_hash["fbs_" + @app_id.to_s]
26
- # remove the opening/closing quote
27
- fb_cookie = fb_cookie.gsub(/\"/, "")
28
-
29
- # since we no longer get individual cookies, we have to separate out the components ourselves
30
- components = {}
31
- fb_cookie.split("&").map {|param| param = param.split("="); components[param[0]] = param[1]}
32
-
33
- # generate the signature and make sure it matches what we expect
34
- auth_string = components.keys.sort.collect {|a| a == "sig" ? nil : "#{a}=#{components[a]}"}.reject {|a| a.nil?}.join("")
35
- sig = Digest::MD5.hexdigest(auth_string + @app_secret)
36
- sig == components["sig"] && (components["expires"] == "0" || Time.now.to_i < components["expires"].to_i) ? components : nil
19
+
20
+ if signed_cookie = cookie_hash["fbsr_#{@app_id}"]
21
+ parse_signed_cookie(signed_cookie)
22
+ elsif unsigned_cookie = cookie_hash["fbs_#{@app_id}"]
23
+ parse_unsigned_cookie(unsigned_cookie)
37
24
  end
38
25
  end
39
26
  alias_method :get_user_info_from_cookies, :get_user_info_from_cookie
@@ -52,7 +39,7 @@ module Koala
52
39
  permissions = options[:permissions]
53
40
  scope = permissions ? "&scope=#{permissions.is_a?(Array) ? permissions.join(",") : permissions}" : ""
54
41
  display = options.has_key?(:display) ? "&display=#{options[:display]}" : ""
55
-
42
+
56
43
  callback = options[:callback] || @oauth_callback_url
57
44
  raise ArgumentError, "url_for_oauth_code must get a callback either from the OAuth object or in the options!" unless callback
58
45
 
@@ -70,7 +57,7 @@ module Koala
70
57
  def get_access_token_info(code, options = {})
71
58
  # convenience method to get a parsed token from Facebook for a given code
72
59
  # should this require an OAuth callback URL?
73
- get_token_from_server({:code => code, :redirect_uri => @oauth_callback_url}, false, options)
60
+ get_token_from_server({:code => code, :redirect_uri => options[:redirect_uri] || @oauth_callback_url}, false, options)
74
61
  end
75
62
 
76
63
  def get_access_token(code, options = {})
@@ -104,7 +91,7 @@ module Koala
104
91
  raise "SignedRequest: Unsupported algorithm #{envelope['algorithm']}" if envelope['algorithm'] != 'HMAC-SHA256'
105
92
 
106
93
  # now see if the signature is valid (digest, key, data)
107
- hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, @app_secret, encoded_envelope.tr("-_", "+/"))
94
+ hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, @app_secret, encoded_envelope)
108
95
  raise 'SignedRequest: Invalid signature' if (signature != hmac)
109
96
 
110
97
  return envelope
@@ -162,6 +149,29 @@ module Koala
162
149
  end
163
150
  components
164
151
  end
152
+
153
+ def parse_unsigned_cookie(fb_cookie)
154
+ # remove the opening/closing quote
155
+ fb_cookie = fb_cookie.gsub(/\"/, "")
156
+
157
+ # since we no longer get individual cookies, we have to separate out the components ourselves
158
+ components = {}
159
+ fb_cookie.split("&").map {|param| param = param.split("="); components[param[0]] = param[1]}
160
+
161
+ # generate the signature and make sure it matches what we expect
162
+ auth_string = components.keys.sort.collect {|a| a == "sig" ? nil : "#{a}=#{components[a]}"}.reject {|a| a.nil?}.join("")
163
+ sig = Digest::MD5.hexdigest(auth_string + @app_secret)
164
+ sig == components["sig"] && (components["expires"] == "0" || Time.now.to_i < components["expires"].to_i) ? components : nil
165
+ end
166
+
167
+ def parse_signed_cookie(fb_cookie)
168
+ components = parse_signed_request(fb_cookie)
169
+ if (code = components["code"]) && token_info = get_access_token_info(code, :redirect_uri => '')
170
+ components.merge(token_info)
171
+ else
172
+ nil
173
+ end
174
+ end
165
175
 
166
176
  def fetch_token_string(args, post = false, endpoint = "access_token", options = {})
167
177
  Koala.make_request("/oauth/#{endpoint}", {
@@ -178,4 +188,4 @@ module Koala
178
188
  end
179
189
  end
180
190
  end
181
- end
191
+ end
@@ -1,15 +1,13 @@
1
- require 'koala'
2
-
3
1
  module Koala
4
2
  module Facebook
5
3
  module RealtimeUpdateMethods
6
4
  # note: to subscribe to real-time updates, you must have an application access token
7
-
5
+
8
6
  def self.included(base)
9
7
  # make the attributes readable
10
8
  base.class_eval do
11
- attr_reader :app_id, :app_access_token, :secret
12
-
9
+ attr_reader :api, :app_id, :app_access_token, :secret
10
+
13
11
  # parses the challenge params and makes sure the call is legitimate
14
12
  # returns the challenge string to be sent back to facebook if true
15
13
  # returns false otherwise
@@ -20,7 +18,7 @@ module Koala
20
18
  # you can make sure this is legitimate through two ways
21
19
  # if your store the token across the calls, you can pass in the token value
22
20
  # and we'll make sure it matches
23
- (verify_token && params["hub.verify_token"] == verify_token) ||
21
+ (verify_token && params["hub.verify_token"] == verify_token) ||
24
22
  # alternately, if you sent a specially-constructed value (such as a hash of various secret values)
25
23
  # you can pass in a block, which we'll call with the verify_token sent by Facebook
26
24
  # if it's legit, return anything that evaluates to true; otherwise, return nil or false
@@ -32,7 +30,7 @@ module Koala
32
30
  end
33
31
  end
34
32
  end
35
-
33
+
36
34
  def initialize(options = {})
37
35
  @app_id = options[:app_id]
38
36
  @app_access_token = options[:app_access_token]
@@ -40,22 +38,22 @@ module Koala
40
38
  unless @app_id && (@app_access_token || @secret) # make sure we have what we need
41
39
  raise ArgumentError, "Initialize must receive a hash with :app_id and either :app_access_token or :secret! (received #{options.inspect})"
42
40
  end
43
-
41
+
44
42
  # fetch the access token if we're provided a secret
45
43
  if @secret && !@app_access_token
46
44
  oauth = Koala::Facebook::OAuth.new(@app_id, @secret)
47
45
  @app_access_token = oauth.get_app_access_token
48
46
  end
49
-
50
- @graph_api = GraphAPI.new(@app_access_token)
47
+
48
+ @graph_api = API.new(@app_access_token)
51
49
  end
52
-
50
+
53
51
  # subscribes for realtime updates
54
52
  # your callback_url must be set up to handle the verification request or the subscription will not be set up
55
53
  # http://developers.facebook.com/docs/api/realtime
56
54
  def subscribe(object, fields, callback_url, verify_token)
57
55
  args = {
58
- :object => object,
56
+ :object => object,
59
57
  :fields => fields,
60
58
  :callback_url => callback_url,
61
59
  :verify_token => verify_token
@@ -63,7 +61,7 @@ module Koala
63
61
  # a subscription is a success if Facebook returns a 200 (after hitting your server for verification)
64
62
  @graph_api.graph_call(subscription_path, args, 'post', :http_component => :status) == 200
65
63
  end
66
-
64
+
67
65
  # removes subscription for object
68
66
  # if object is nil, it will remove all subscriptions
69
67
  def unsubscribe(object = nil)
@@ -71,16 +69,21 @@ module Koala
71
69
  args[:object] = object if object
72
70
  @graph_api.graph_call(subscription_path, args, 'delete', :http_component => :status) == 200
73
71
  end
74
-
72
+
75
73
  def list_subscriptions
76
- @graph_api.graph_call(subscription_path)["data"]
74
+ @graph_api.graph_call(subscription_path)
75
+ end
76
+
77
+ def graph_api
78
+ Koala::Utils.deprecate("the TestUsers.graph_api accessor is deprecated and will be removed in a future version; please use .api instead.")
79
+ @api
77
80
  end
78
-
81
+
79
82
  protected
80
-
83
+
81
84
  def subscription_path
82
85
  @subscription_path ||= "#{@app_id}/subscriptions"
83
86
  end
84
87
  end
85
88
  end
86
- end
89
+ end
@@ -4,7 +4,7 @@ module Koala
4
4
 
5
5
  module RestAPIMethods
6
6
  def fql_query(fql, args = {}, options = {})
7
- rest_call('fql.query', args.merge(:query => fql), options)
7
+ rest_call('fql.query', args.merge(:query => fql), options)
8
8
  end
9
9
 
10
10
  def fql_multiquery(queries = {}, args = {}, options = {})
@@ -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
- @graph_api = GraphAPI.new(@app_access_token)
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 = @graph_api.graph_call(accounts_path, args, "post", options)
34
+ result = @api.graph_call(accounts_path, args, "post", options)
28
35
  end
29
-
36
+
30
37
  def list
31
- @graph_api.graph_call(accounts_path)["data"]
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
- @graph_api.delete_object(test_user)
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.graph_call("#{user1_id}/friends/#{user2_id}", {}, "post") &&
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
@@ -1,36 +1,30 @@
1
- require 'koala'
1
+ require "net/http/post/multipart"
2
2
 
3
3
  module Koala
4
4
  class UploadableIO
5
- attr_reader :io_or_path, :content_type, :requires_base_http_service
5
+ attr_reader :io_or_path, :content_type, :filename
6
6
 
7
7
  def initialize(io_or_path_or_mixed, content_type = nil, filename = nil)
8
8
  # see if we got the right inputs
9
- if content_type.nil?
10
- parse_init_mixed_param io_or_path_or_mixed
11
- elsif !content_type.nil? && (io_or_path_or_mixed.respond_to?(:read) or io_or_path_or_mixed.kind_of?(String))
12
- @io_or_path = io_or_path_or_mixed
13
- @content_type = content_type
14
- end
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)
9
+ parse_init_mixed_param io_or_path_or_mixed, content_type
18
10
 
19
11
  # filename is used in the Ads API
20
- @filename = filename || "koala-io-file.dum"
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"
21
15
 
22
16
  raise KoalaError.new("Invalid arguments to initialize an UploadableIO") unless @io_or_path
23
- raise KoalaError.new("Unable to determine MIME type for UploadableIO") if !@content_type && Koala.multipart_requires_content_type?
17
+ raise KoalaError.new("Unable to determine MIME type for UploadableIO") if !@content_type
24
18
  end
25
-
19
+
26
20
  def to_upload_io
27
21
  UploadIO.new(@io_or_path, @content_type, @filename)
28
22
  end
29
-
23
+
30
24
  def to_file
31
25
  @io_or_path.is_a?(String) ? File.open(@io_or_path) : @io_or_path
32
26
  end
33
-
27
+
34
28
  def self.binary_content?(content)
35
29
  content.is_a?(UploadableIO) || DETECTION_STRATEGIES.detect {|method| send(method, content)}
36
30
  end
@@ -41,69 +35,81 @@ module Koala
41
35
  :rails_3_param?,
42
36
  :file_param?
43
37
  ]
44
-
38
+
45
39
  PARSE_STRATEGIES = [
46
40
  :parse_rails_3_param,
47
41
  :parse_sinatra_param,
48
42
  :parse_file_object,
49
- :parse_string_path
43
+ :parse_string_path,
44
+ :parse_io
50
45
  ]
51
-
52
- def parse_init_mixed_param(mixed)
46
+
47
+ def parse_init_mixed_param(mixed, content_type = nil)
53
48
  PARSE_STRATEGIES.each do |method|
54
- send(method, mixed)
49
+ send(method, mixed, content_type)
55
50
  return if @io_or_path && @content_type
56
51
  end
57
52
  end
58
-
53
+
59
54
  # Expects a parameter of type ActionDispatch::Http::UploadedFile
60
55
  def self.rails_3_param?(uploaded_file)
61
56
  uploaded_file.respond_to?(:content_type) and uploaded_file.respond_to?(:tempfile) and uploaded_file.tempfile.respond_to?(:path)
62
57
  end
63
-
64
- def parse_rails_3_param(uploaded_file)
65
- if UploadableIO.rails_3_param?(uploaded_file)
58
+
59
+ def parse_rails_3_param(uploaded_file, content_type = nil)
60
+ if UploadableIO.rails_3_param?(uploaded_file)
66
61
  @io_or_path = uploaded_file.tempfile.path
67
- @content_type = uploaded_file.content_type
62
+ @content_type = content_type || uploaded_file.content_type
63
+ @filename = uploaded_file.original_filename
68
64
  end
69
65
  end
70
-
66
+
71
67
  # Expects a Sinatra hash of file info
72
68
  def self.sinatra_param?(file_hash)
73
69
  file_hash.kind_of?(Hash) and file_hash.has_key?(:type) and file_hash.has_key?(:tempfile)
74
70
  end
75
-
76
- def parse_sinatra_param(file_hash)
71
+
72
+ def parse_sinatra_param(file_hash, content_type = nil)
77
73
  if UploadableIO.sinatra_param?(file_hash)
78
74
  @io_or_path = file_hash[:tempfile]
79
- @content_type = file_hash[:type] || detect_mime_type(tempfile)
75
+ @content_type = content_type || file_hash[:type] || detect_mime_type(tempfile)
76
+ @filename = file_hash[:filename]
80
77
  end
81
78
  end
82
-
79
+
83
80
  # takes a file object
84
81
  def self.file_param?(file)
85
82
  file.kind_of?(File)
86
83
  end
87
-
88
- def parse_file_object(file)
84
+
85
+ def parse_file_object(file, content_type = nil)
89
86
  if UploadableIO.file_param?(file)
90
87
  @io_or_path = file
91
- @content_type = detect_mime_type(file.path)
88
+ @content_type = content_type || detect_mime_type(file.path)
89
+ @filename = File.basename(file.path)
92
90
  end
93
91
  end
94
-
95
- def parse_string_path(path)
92
+
93
+ def parse_string_path(path, content_type = nil)
96
94
  if path.kind_of?(String)
97
95
  @io_or_path = path
98
- @content_type = detect_mime_type(path)
96
+ @content_type = content_type || detect_mime_type(path)
97
+ @filename = File.basename(path)
98
+ end
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
99
105
  end
100
106
  end
101
-
107
+
102
108
  MIME_TYPE_STRATEGIES = [
103
109
  :use_mime_module,
104
110
  :use_simple_detection
105
111
  ]
106
-
112
+
107
113
  def detect_mime_type(filename)
108
114
  if filename
109
115
  MIME_TYPE_STRATEGIES.each do |method|
@@ -113,7 +119,7 @@ module Koala
113
119
  end
114
120
  nil # if we can't find anything
115
121
  end
116
-
122
+
117
123
  def use_mime_module(filename)
118
124
  # if the user has installed mime/types, we can use that
119
125
  # if not, rescue and return nil
@@ -124,12 +130,12 @@ module Koala
124
130
  nil
125
131
  end
126
132
  end
127
-
133
+
128
134
  def use_simple_detection(filename)
129
135
  # very rudimentary extension analysis for images
130
136
  # first, get the downcased extension, or an empty string if it doesn't exist
131
137
  extension = ((filename.match(/\.([a-zA-Z0-9]+)$/) || [])[1] || "").downcase
132
- case extension
138
+ case extension
133
139
  when ""
134
140
  nil
135
141
  # images
@@ -169,7 +175,7 @@ module Koala
169
175
  "video/ogg"
170
176
  when "wmv"
171
177
  "video/x-ms-wmv"
172
- end
178
+ end
173
179
  end
174
180
  end
175
181
  end
@@ -0,0 +1,11 @@
1
+ module Koala
2
+ module Utils
3
+ def self.deprecate(message)
4
+ begin
5
+ send(:warn, "KOALA: Deprecation warning: #{message}")
6
+ rescue Exception => err
7
+ puts "Unable to issue Koala deprecation warning! #{err.message}"
8
+ end
9
+ end
10
+ end
11
+ end