edh 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,142 @@
1
+ require 'faraday'
2
+ require 'edh/http_service/multipart_request'
3
+ require 'edh/http_service/response'
4
+
5
+ module EDH
6
+ module HTTPService
7
+ class << self
8
+ # A customized stack of Faraday middleware that will be used to make each request.
9
+ attr_accessor :faraday_middleware
10
+ attr_accessor :http_options
11
+ end
12
+
13
+ @http_options ||= {}
14
+
15
+ # EDH's default middleware stack.
16
+ # We encode requests in a Passport-compatible multipart request,
17
+ # and use whichever adapter has been configured for this application.
18
+ DEFAULT_MIDDLEWARE = Proc.new do |builder|
19
+ builder.use EDH::HTTPService::MultipartRequest
20
+ builder.request :url_encoded
21
+ builder.adapter Faraday.default_adapter
22
+ end
23
+
24
+ # The address of the appropriate Passport server.
25
+ #
26
+ # @param options various flags to indicate which server to use.
27
+ # @option options :rest_api use the old REST API instead of the Graph API
28
+ # @option options :use_ssl force https, even if not needed
29
+ #
30
+ # @return a complete server address with protocol
31
+ def self.server(options = {})
32
+ if options[:server]
33
+ options[:server]
34
+ else
35
+ server = Passport::REST_SERVER
36
+ "#{options[:use_ssl] ? "https" : "http"}://#{server}"
37
+ end
38
+ end
39
+
40
+ # Makes a request directly to Passport.
41
+ # @note You'll rarely need to call this method directly.
42
+ #
43
+ # @see EDH::Passport::API#api
44
+ # @see EDH::Passport::RestAPIMethods#rest_call
45
+ #
46
+ # @param path the server path for this request
47
+ # @param args (see EDH::Passport::API#api)
48
+ # @param verb the HTTP method to use.
49
+ # If not get or post, this will be turned into a POST request with the appropriate :method
50
+ # specified in the arguments.
51
+ # @param options (see EDH::Passport::API#api)
52
+ #
53
+ # @raise an appropriate connection error if unable to make the request to Passport
54
+ #
55
+ # @return [EDH::HTTPService::Response] a response object representing the results from Passport
56
+ def self.make_request(path, args, verb, options = {})
57
+ # if the verb isn't get or post, send it as a post argument
58
+ args.merge!({:method => verb}) && verb = "post" if verb != "get" && verb != "post"
59
+
60
+ # turn all the keys to strings (Faraday has issues with symbols under 1.8.7)
61
+ params = args.inject({}) {|hash, kv| hash[kv.first.to_s] = kv.last; hash}
62
+
63
+ # figure out our options for this request
64
+ request_options = {:params => (verb == "get" ? params : {})}.merge(http_options || {}).merge(process_options(options))
65
+ if request_options[:use_ssl]
66
+ ssl = (request_options[:ssl] ||= {})
67
+ ssl[:verify] = true unless ssl.has_key?(:verify)
68
+ end
69
+
70
+ # set up our Faraday connection
71
+ # we have to manually assign params to the URL or the
72
+ conn = Faraday.new(server(request_options), request_options, &(faraday_middleware || DEFAULT_MIDDLEWARE))
73
+
74
+ response = conn.send(verb, path, (verb == "post" ? params : {}))
75
+
76
+ # Log URL information
77
+ EDH::Utils.debug "#{verb.upcase}: #{path} params: #{params.inspect}"
78
+ EDH::HTTPService::Response.new(response.status.to_i, response.body, response.headers)
79
+ end
80
+
81
+ # Encodes a given hash into a query string.
82
+ # This is used mainly by the Batch API nowadays, since Faraday handles this for regular cases.
83
+ #
84
+ # @param params_hash a hash of values to CGI-encode and appropriately join
85
+ #
86
+ # @example
87
+ # EDH.http_service.encode_params({:a => 2, :b => "My String"})
88
+ # => "a=2&b=My+String"
89
+ #
90
+ # @return the appropriately-encoded string
91
+ def self.encode_params(param_hash)
92
+ ((param_hash || {}).sort_by{|k, v| k.to_s}.collect do |key_and_value|
93
+ key_and_value[1] = MultiJson.dump(key_and_value[1]) unless key_and_value[1].is_a? String
94
+ "#{key_and_value[0].to_s}=#{CGI.escape key_and_value[1]}"
95
+ end).join("&")
96
+ end
97
+
98
+ private
99
+
100
+ def self.process_options(options)
101
+ if typhoeus_options = options.delete(:typhoeus_options)
102
+ EDH::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.")
103
+ options = options.merge(typhoeus_options)
104
+ end
105
+
106
+ if ca_file = options.delete(:ca_file)
107
+ EDH::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.")
108
+ (options[:ssl] ||= {})[:ca_file] = ca_file
109
+ end
110
+
111
+ if ca_path = options.delete(:ca_path)
112
+ EDH::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.")
113
+ (options[:ssl] ||= {})[:ca_path] = ca_path
114
+ end
115
+
116
+ if verify_mode = options.delete(:verify_mode)
117
+ EDH::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.")
118
+ (options[:ssl] ||= {})[:verify_mode] = verify_mode
119
+ end
120
+
121
+ options
122
+ end
123
+ end
124
+
125
+ # @private
126
+ module TyphoeusService
127
+ def self.deprecated_interface
128
+ # support old-style interface with a warning
129
+ EDH::Utils.deprecate("the TyphoeusService module is deprecated; to use Typhoeus, set Faraday.default_adapter = :typhoeus. Enabling Typhoeus for all Faraday connections.")
130
+ Faraday.default_adapter = :typhoeus
131
+ end
132
+ end
133
+
134
+ # @private
135
+ module NetHTTPService
136
+ def self.deprecated_interface
137
+ # support old-style interface with a warning
138
+ EDH::Utils.deprecate("the NetHTTPService module is deprecated; to use Net::HTTP, set Faraday.default_adapter = :net_http. Enabling Net::HTTP for all Faraday connections.")
139
+ Faraday.default_adapter = :net_http
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,40 @@
1
+ require 'faraday'
2
+
3
+ module EDH
4
+ module HTTPService
5
+ class MultipartRequest < Faraday::Request::Multipart
6
+ # Passport expects nested parameters to be passed in a certain way
7
+ # Faraday needs two changes to make that work:
8
+ # 1) [] need to be escaped (e.g. params[foo]=bar ==> params%5Bfoo%5D=bar)
9
+ # 2) such messages need to be multipart-encoded
10
+
11
+ self.mime_type = 'multipart/form-data'.freeze
12
+
13
+ def process_request?(env)
14
+ # if the request values contain any hashes or arrays, multipart it
15
+ super || !!(env[:body].respond_to?(:values) && env[:body].values.find {|v| v.is_a?(Hash) || v.is_a?(Array)})
16
+ end
17
+
18
+
19
+ def process_params(params, prefix = nil, pieces = nil, &block)
20
+ params.inject(pieces || []) do |all, (key, value)|
21
+ key = "#{prefix}%5B#{key}%5D" if prefix
22
+
23
+ case value
24
+ when Array
25
+ values = value.inject([]) { |a,v| a << [nil, v] }
26
+ process_params(values, key, all, &block)
27
+ when Hash
28
+ process_params(value, key, all, &block)
29
+ else
30
+ all << block.call(key, value)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ # @private
38
+ # legacy support for when MultipartRequest lived directly under EDH
39
+ MultipartRequest = HTTPService::MultipartRequest
40
+ end
@@ -0,0 +1,18 @@
1
+ module EDH
2
+ module HTTPService
3
+ class Response
4
+ attr_reader :status, :body, :headers
5
+
6
+ # Creates a new Response object, which standardizes the response received by Passport for use within EDH.
7
+ def initialize(status, body, headers)
8
+ @status = status
9
+ @body = body
10
+ @headers = headers
11
+ end
12
+ end
13
+ end
14
+
15
+ # @private
16
+ # legacy support for when Response lived directly under EDH
17
+ Response = HTTPService::Response
18
+ end
@@ -0,0 +1,188 @@
1
+ require 'edh'
2
+
3
+ module EDH
4
+ module Passport
5
+
6
+ # Create and manage test users for your application.
7
+ # A test user is a user account associated with an app created for the purpose
8
+ # of testing the functionality of that app.
9
+ # You can use test users for manual or automated testing --
10
+ # EDH's live test suite uses test users to verify the library works with Passport.
11
+ #
12
+ # @note the test user API is fairly slow compared to other interfaces
13
+ # (which makes sense -- it's creating whole new user accounts!).
14
+ #
15
+ class TestUsers
16
+
17
+ # The application API interface used to communicate with Passport.
18
+ # @return [EDH::Passport::API]
19
+ attr_reader :api
20
+ attr_reader :app_id, :app_access_token, :secret
21
+
22
+ # Create a new TestUsers instance.
23
+ # If you don't have your app's access token, provide the app's secret and
24
+ # EDH will make a request to Passport for the appropriate token.
25
+ #
26
+ # @param options initialization options.
27
+ # @option options :app_id the application's ID.
28
+ # @option options :app_access_token an application access token, if known.
29
+ # @option options :secret the application's secret.
30
+ #
31
+ # @raise ArgumentError if the application ID and one of the app access token or the secret are not provided.
32
+ def initialize(options = {})
33
+ @app_id = options[:app_id]
34
+ @app_access_token = options[:app_access_token]
35
+ @secret = options[:secret]
36
+ unless @app_id && (@app_access_token || @secret) # make sure we have what we need
37
+ raise ArgumentError, "Initialize must receive a hash with :app_id and either :app_access_token or :secret! (received #{options.inspect})"
38
+ end
39
+
40
+ # fetch the access token if we're provided a secret
41
+ if @secret && !@app_access_token
42
+ oauth = EDH::Passport::OAuth.new(@app_id, @secret)
43
+ @app_access_token = oauth.get_app_access_token
44
+ end
45
+
46
+ @api = API.new(@app_access_token)
47
+ end
48
+
49
+ # Create a new test user.
50
+ #
51
+ # @param installed whether the user has installed your app
52
+ # @param permissions a comma-separated string or array of permissions the user has granted (if installed)
53
+ # @param args any additional arguments for the create call (name, etc.)
54
+ # @param options (see EDH::Passport::API#api)
55
+ #
56
+ # @return a hash of information for the new user (id, access token, login URL, etc.)
57
+ def create(installed, permissions = nil, args = {}, options = {})
58
+ # Creates and returns a test user
59
+ args['installed'] = installed
60
+ args['permissions'] = (permissions.is_a?(Array) ? permissions.join(",") : permissions) if installed
61
+ @api.graph_call(test_user_accounts_path, args, "post", options)
62
+ end
63
+
64
+ # List all test users for the app.
65
+ #
66
+ # @param options (see EDH::Passport::API#api)
67
+ #
68
+ # @return an array of hashes of user information (id, access token, etc.)
69
+ def list(options = {})
70
+ @api.graph_call(test_user_accounts_path, {}, "get", options)
71
+ end
72
+
73
+ # Delete a test user.
74
+ #
75
+ # @param test_user the user to delete; can be either a Passport ID or the hash returned by {#create}
76
+ # @param options (see EDH::Passport::API#api)
77
+ #
78
+ # @return true if successful, false (or an {EDH::Passport::APIError APIError}) if not
79
+ def delete(test_user, options = {})
80
+ test_user = test_user["id"] if test_user.is_a?(Hash)
81
+ @api.delete_object(test_user, options)
82
+ end
83
+
84
+ # Deletes all test users in batches of 50.
85
+ #
86
+ # @note if you have a lot of test users (> 20), this operation can take a long time.
87
+ #
88
+ # @param options (see EDH::Passport::API#api)
89
+ #
90
+ # @return a list of the test users that have been deleted
91
+ def delete_all(options = {})
92
+ # ideally we'd save a call by checking next_page_params, but at the time of writing
93
+ # Passport isn't consistently returning full pages after the first one
94
+ previous_list = nil
95
+ while (test_user_list = list(options)).length > 0
96
+ break if test_user_list == previous_list
97
+
98
+ test_user_list.each_slice(50) do |users|
99
+ self.api.batch(options) {|batch_api| users.each {|u| batch_api.delete_object(u["id"]) }}
100
+ end
101
+
102
+ previous_list = test_user_list
103
+ end
104
+ end
105
+
106
+ # Updates a test user's attributes.
107
+ #
108
+ # @note currently, only name and password can be changed;
109
+ # see {http://developers.facebook.com/docs/test_users/ the Facebook documentation}.
110
+ #
111
+ # @param test_user the user to update; can be either a Facebook ID or the hash returned by {#create}
112
+ # @param args the updates to make
113
+ # @param options (see EDH::Passport::API#api)
114
+ #
115
+ # @return true if successful, false (or an {EDH::Passport::APIError APIError}) if not
116
+ def update(test_user, args = {}, options = {})
117
+ test_user = test_user["id"] if test_user.is_a?(Hash)
118
+ @api.graph_call(test_user, args, "post", options)
119
+ end
120
+
121
+ # Make two test users friends.
122
+ #
123
+ # @note there's no way to unfriend test users; you can always just create a new one.
124
+ #
125
+ # @param user1_hash one of the users to friend; the hash must contain both ID and access token (as returned by {#create})
126
+ # @param user2_hash the other user to friend
127
+ # @param options (see EDH::Passport::API#api)
128
+ #
129
+ # @return true if successful, false (or an {EDH::Passport::APIError APIError}) if not
130
+ def befriend(user1_hash, user2_hash, options = {})
131
+ user1_id = user1_hash["id"] || user1_hash[:id]
132
+ user2_id = user2_hash["id"] || user2_hash[:id]
133
+ user1_token = user1_hash["access_token"] || user1_hash[:access_token]
134
+ user2_token = user2_hash["access_token"] || user2_hash[:access_token]
135
+ unless user1_id && user2_id && user1_token && user2_token
136
+ # we explicitly raise an error here to minimize the risk of confusing output
137
+ # if you pass in a string (as was previously supported) no local exception would be raised
138
+ # but the Passport call would fail
139
+ raise ArgumentError, "TestUsers#befriend requires hash arguments for both users with id and access_token"
140
+ end
141
+
142
+ u1_graph_api = API.new(user1_token)
143
+ u2_graph_api = API.new(user2_token)
144
+
145
+ u1_graph_api.graph_call("#{user1_id}/friends/#{user2_id}", {}, "post", options) &&
146
+ u2_graph_api.graph_call("#{user2_id}/friends/#{user1_id}", {}, "post", options)
147
+ end
148
+
149
+ # Create a network of test users, all of whom are friends and have the same permissions.
150
+ #
151
+ # @note this call slows down dramatically the more users you create
152
+ # (test user calls are slow, and more users => more 1-on-1 connections to be made).
153
+ # Use carefully.
154
+ #
155
+ # @param network_size how many users to create
156
+ # @param installed whether the users have installed your app (see {#create})
157
+ # @param permissions what permissions the users have granted (see {#create})
158
+ # @param options (see EDH::Passport::API#api)
159
+ #
160
+ # @return the list of users created
161
+ def create_network(network_size, installed = true, permissions = '', options = {})
162
+ users = (0...network_size).collect { create(installed, permissions, options) }
163
+ friends = users.clone
164
+ users.each do |user|
165
+ # Remove this user from list of friends
166
+ friends.delete_at(0)
167
+ # befriend all the others
168
+ friends.each do |friend|
169
+ befriend(user, friend, options)
170
+ end
171
+ end
172
+ return users
173
+ end
174
+
175
+ # The Passport test users management URL for your application.
176
+ def test_user_accounts_path
177
+ @test_user_accounts_path ||= "/#{@app_id}/accounts/test-users"
178
+ end
179
+
180
+ # @private
181
+ # Legacy accessor for before GraphAPI was unified into API
182
+ def graph_api
183
+ EDH::Utils.deprecate("the TestUsers.graph_api accessor is deprecated and will be removed in a future version; please use .api instead.")
184
+ @api
185
+ end
186
+ end # TestUserMethods
187
+ end # Passport
188
+ end # EDH
data/lib/edh/utils.rb ADDED
@@ -0,0 +1,20 @@
1
+ module EDH
2
+ module Utils
3
+
4
+ # Utility methods used by EDH.
5
+ require 'logger'
6
+ require 'forwardable'
7
+
8
+ extend Forwardable
9
+ extend self
10
+
11
+ def_delegators :logger, :debug, :info, :warn, :error, :fatal, :level, :level=
12
+
13
+ # The EDH logger, an instance of the standard Ruby logger, pointing to STDOUT by default.
14
+ # In Rails projects, you can set this to Rails.logger.
15
+ attr_accessor :logger
16
+ self.logger = Logger.new(STDOUT)
17
+ self.logger.level = Logger::ERROR
18
+
19
+ end
20
+ end
@@ -0,0 +1,3 @@
1
+ module EDH
2
+ VERSION = "0.1"
3
+ end
data/readme.md ADDED
@@ -0,0 +1,42 @@
1
+ ####config/initializers
2
+ #### defaults to using production
3
+ ```ruby
4
+ $passport_client = EDH::Passport::API.new
5
+ ```
6
+
7
+ ####optional access_token
8
+ ```ruby
9
+ $passport_client = EDH::Passport::API.new(nil, "http://dummy-passport.dev")
10
+ ```
11
+ ####set the user token
12
+ ```ruby
13
+ $passport_client.access_token = "abc"
14
+ ```
15
+
16
+ ####create returns an action id
17
+ ```ruby
18
+ $passport_client.create("pages.fundraise", {:abc => "def", :cats => "dogs"})
19
+ => 1234
20
+ #sending json example
21
+ $passport_client.create("pages.fundraise", "{\"abc\":\"def\",\"cats\":\"dogs\"}")
22
+ ```
23
+
24
+ ####update an action
25
+ ```ruby
26
+ $passport_client.update(1234, {:abc => "12345", :cats => "6789"})
27
+ ```
28
+
29
+ ####delete an action
30
+ ```ruby
31
+ $passport_client.delete(1234)
32
+ ```
33
+
34
+
35
+ Testing
36
+ -----
37
+
38
+ Unit tests are provided for all of EDH's methods. By default, these tests run against mock responses and hence are ready out of the box:
39
+ ```bash
40
+ # From anywhere in the project directory:
41
+ bundle exec rake spec
42
+ ```