edh 0.1

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.
@@ -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
+ ```