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,66 @@
1
+ require 'spec_helper'
2
+
3
+ describe EDH::HTTPService::MultipartRequest do
4
+ it "is a subclass of Faraday::Request::Multipart" do
5
+ EDH::HTTPService::MultipartRequest.superclass.should == Faraday::Request::Multipart
6
+ end
7
+
8
+ it "defines mime_type as multipart/form-data" do
9
+ EDH::HTTPService::MultipartRequest.mime_type.should == 'multipart/form-data'
10
+ end
11
+
12
+ describe "#process_request?" do
13
+ before :each do
14
+ @env = {}
15
+ @multipart = EDH::HTTPService::MultipartRequest.new
16
+ @multipart.stub(:request_type).and_return("")
17
+ end
18
+
19
+ # no way to test the call to super, unfortunately
20
+ it "returns true if env[:body] is a hash with at least one hash in its values" do
21
+ @env[:body] = {:a => {:c => 2}}
22
+ @multipart.process_request?(@env).should be_true
23
+ end
24
+
25
+ it "returns true if env[:body] is a hash with at least one array in its values" do
26
+ @env[:body] = {:a => [:c, 2]}
27
+ @multipart.process_request?(@env).should be_true
28
+ end
29
+
30
+ it "returns true if env[:body] is a hash with mixed objects in its values" do
31
+ @env[:body] = {:a => [:c, 2], :b => {:e => :f}}
32
+ @multipart.process_request?(@env).should be_true
33
+ end
34
+
35
+ it "returns false if env[:body] is a string" do
36
+ @env[:body] = "my body"
37
+ @multipart.process_request?(@env).should be_false
38
+ end
39
+
40
+ it "returns false if env[:body] is a hash without an array or hash value" do
41
+ @env[:body] = {:a => 3}
42
+ @multipart.process_request?(@env).should be_false
43
+ end
44
+ end
45
+
46
+ describe "#process_params" do
47
+ before :each do
48
+ @parent = Faraday::Request::Multipart.new
49
+ @multipart = EDH::HTTPService::MultipartRequest.new
50
+ @block = lambda {|k, v| "#{k}=#{v}"}
51
+ end
52
+
53
+ it "is identical to the parent for requests without a prefix" do
54
+ hash = {:a => 2, :c => "3"}
55
+ @multipart.process_params(hash, &@block).should == @parent.process_params(hash, &@block)
56
+ end
57
+
58
+ it "replaces encodes [ and ] if the request has a prefix" do
59
+ hash = {:a => 2, :c => "3"}
60
+ prefix = "foo"
61
+ # process_params returns an array
62
+ @multipart.process_params(hash, prefix, &@block).join("&").should == @parent.process_params(hash, prefix, &@block).join("&").gsub(/\[/, "%5B").gsub(/\]/, "%5D")
63
+ end
64
+ end
65
+
66
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe EDH::Utils do
4
+ describe ".logger" do
5
+ it "has an accessor for logger" do
6
+ EDH::Utils.methods.map(&:to_sym).should include(:logger)
7
+ EDH::Utils.methods.map(&:to_sym).should include(:logger=)
8
+ end
9
+
10
+ it "defaults to the standard ruby logger with level set to ERROR" do |variable|
11
+ EDH::Utils.logger.should be_kind_of(Logger)
12
+ EDH::Utils.logger.level.should == Logger::ERROR
13
+ end
14
+
15
+ logger_methods = [:debug, :info, :warn, :error, :fatal]
16
+
17
+ logger_methods.each do |logger_method|
18
+ it "should delegate #{logger_method} to the attached logger" do
19
+ EDH::Utils.logger.should_receive(logger_method)
20
+ EDH::Utils.send(logger_method, "Test #{logger_method} message")
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,27 @@
1
+ if RUBY_VERSION == '1.9.2' && RUBY_PATCHLEVEL < 290 && RUBY_ENGINE != "macruby"
2
+ # In Ruby 1.9.2 versions before patchlevel 290, the default Psych
3
+ # parser has an issue with YAML merge keys, which
4
+ #
5
+ # Anyone using an earlier version will see missing mock response
6
+ # errors when running the test suite similar to this:
7
+ #
8
+ # RuntimeError:
9
+ # Missing a mock response for graph_api: /me/videos: source=[FILE]: post: with_token
10
+ # API PATH: /me/videos?source=[FILE]&format=json&access_token=*
11
+ #
12
+ # For now, it seems the best fix is to just downgrade to the old syck YAML parser
13
+ # for these troubled versions.
14
+ #
15
+ # See https://github.com/tenderlove/psych/issues/8 for more details
16
+ YAML::ENGINE.yamler = 'syck'
17
+ end
18
+
19
+ # load the library
20
+ require 'edh'
21
+
22
+ # Support files
23
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
24
+
25
+ # set up our testing environment
26
+ # load testing data and (if needed) create test users or validate real users
27
+ EDHTest.setup_test_environment!
@@ -0,0 +1,28 @@
1
+ # Verifies that two URLs are equal, ignoring the order of the query string parameters
2
+ RSpec::Matchers.define :match_url do |url|
3
+ match do |original_url|
4
+ base, query_string = url.split("?")
5
+ original_base, original_query_string = original_url.split("?")
6
+ query_hash = query_to_params(query_string)
7
+ original_query_hash = query_to_params(original_query_string)
8
+
9
+ # the base URLs need to match
10
+ base.should == original_base
11
+
12
+ # the number of parameters should match (avoid one being a subset of the other)
13
+ query_hash.values.length.should == original_query_hash.values.length
14
+
15
+ # and ensure all the keys and values match
16
+ query_hash.each_pair do |key, value|
17
+ original_query_hash[key].should == value
18
+ end
19
+ end
20
+
21
+ def query_to_params(query_string)
22
+ query_string.split("&").inject({}) do |hash, segment|
23
+ k, v = segment.split("=")
24
+ hash[k] = v
25
+ hash
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,185 @@
1
+ # small helper method for live testing
2
+ module EDHTest
3
+
4
+ class << self
5
+ attr_accessor :oauth_token, :app_id, :secret, :app_access_token, :code, :session_key
6
+ attr_accessor :search_time
7
+ attr_accessor :test_user_api
8
+ end
9
+
10
+ # Test setup
11
+
12
+ def self.setup_test_environment!
13
+ setup_rspec
14
+
15
+ unless ENV['LIVE']
16
+ # By default the EDH specs are run using stubs for HTTP requests,
17
+ # so they won't fail due to Passport-imposed rate limits or server timeouts.
18
+ #
19
+ # However as a result they are more brittle since
20
+ # we are not testing the latest responses from the Passport servers.
21
+ # To be certain all specs pass with the current Passport services,
22
+ # run LIVE=true bundle exec rake spec.
23
+ EDH.http_service = EDH::MockHTTPService
24
+ EDHTest.setup_test_data(EDH::MockHTTPService::TEST_DATA)
25
+ else
26
+ # Runs EDH specs through the Passport servers
27
+ # using data for a real app
28
+
29
+ # allow live tests with different adapters
30
+ adapter = ENV['ADAPTER'] || "typhoeus" # use Typhoeus by default if available
31
+ begin
32
+ require adapter
33
+ require 'typhoeus/adapters/faraday' if adapter.to_s == "typhoeus"
34
+ Faraday.default_adapter = adapter.to_sym
35
+ rescue LoadError
36
+ puts "Unable to load adapter #{adapter}, using Net::HTTP."
37
+ end
38
+
39
+ # use a test user unless the developer wants to test against a real profile
40
+ unless token = EDHTest.oauth_token
41
+ EDHTest.setup_test_users
42
+ else
43
+ EDHTest.validate_user_info(token)
44
+ end
45
+ end
46
+ end
47
+
48
+ def self.setup_rspec
49
+ # set up a global before block to set the token for tests
50
+ # set the token up for
51
+ RSpec.configure do |config|
52
+ config.before :each do
53
+ @token = EDHTest.oauth_token
54
+ EDH::Utils.stub(:deprecate) # never fire deprecation warnings
55
+ end
56
+
57
+ config.after :each do
58
+ # if we're working with a real user, clean up any objects posted to Passport
59
+ # no need to do so for test users, since they get deleted at the end
60
+ if @temporary_object_id && EDHTest.real_user?
61
+ raise "Unable to locate API when passed temporary object to delete!" unless @api
62
+
63
+ # wait 10ms to allow Passport to propagate data so we can delete it
64
+ sleep(0.01)
65
+
66
+ # clean up any objects we've posted
67
+ result = (@api.delete_object(@temporary_object_id) rescue false)
68
+ # if we errored out or Passport returned false, track that
69
+ puts "Unable to delete #{@temporary_object_id}: #{result} (probably a photo or video, which can't be deleted through the API)" unless result
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ def self.setup_test_data(data)
76
+ # fix the search time so it can be used in the mock responses
77
+ self.search_time = data["search_time"] || (Time.now - 3600).to_s
78
+ end
79
+
80
+ def self.testing_permissions
81
+ "read_stream, publish_stream, user_photos, user_videos, read_insights"
82
+ end
83
+
84
+ def self.setup_test_users
85
+ print "Setting up test users..."
86
+ @test_user_api = EDH::Passport::TestUsers.new(:app_id => self.app_id, :secret => self.secret)
87
+
88
+ RSpec.configure do |config|
89
+ config.before :suite do
90
+ # before each test module, create two test users with specific names and befriend them
91
+ EDHTest.create_test_users
92
+ end
93
+
94
+ config.after :suite do
95
+ # after each test module, delete the test users to avoid cluttering up the application
96
+ EDHTest.destroy_test_users
97
+ end
98
+ end
99
+
100
+ puts "done."
101
+ end
102
+
103
+ def self.create_test_users
104
+ begin
105
+ @live_testing_user = @test_user_api.create(true, EDHTest.testing_permissions, :name => EDHTest.user1_name)
106
+ @live_testing_friend = @test_user_api.create(true, EDHTest.testing_permissions, :name => EDHTest.user2_name)
107
+ @test_user_api.befriend(@live_testing_user, @live_testing_friend)
108
+ self.oauth_token = @live_testing_user["access_token"]
109
+ rescue Exception => e
110
+ Kernel.warn("Problem creating test users! #{e.message}")
111
+ raise
112
+ end
113
+ end
114
+
115
+ def self.destroy_test_users
116
+ [@live_testing_user, @live_testing_friend].each do |u|
117
+ puts "Unable to delete test user #{u.inspect}" if u && !(@test_user_api.delete(u) rescue false)
118
+ end
119
+ end
120
+
121
+ def self.validate_user_info(token)
122
+ print "Validating permissions for live testing..."
123
+ # make sure we have the necessary permissions
124
+ api = EDH::Passport::API.new(token)
125
+ perms = api.fql_query("select #{testing_permissions} from permissions where uid = me()")[0]
126
+ perms.each_pair do |perm, value|
127
+ if value == (perm == "read_insights" ? 1 : 0) # live testing depends on insights calls failing
128
+ puts "failed!\n" # put a new line after the print above
129
+ raise ArgumentError, "Your access token must have the read_stream, publish_stream, and user_photos permissions, and lack read_insights. You have: #{perms.inspect}"
130
+ end
131
+ end
132
+ puts "done!"
133
+ end
134
+
135
+ # Info about the testing environment
136
+ def self.real_user?
137
+ !(mock_interface? || @test_user_api)
138
+ end
139
+
140
+ def self.test_user?
141
+ !!@test_user_api
142
+ end
143
+
144
+ def self.mock_interface?
145
+ EDH.http_service == EDH::MockHTTPService
146
+ end
147
+
148
+ # Data for testing
149
+ def self.user1
150
+ # user ID, either numeric or username
151
+ test_user? ? @live_testing_user["id"] : "koppel"
152
+ end
153
+
154
+ def self.user1_id
155
+ # numerical ID, used for FQL
156
+ # (otherwise the two IDs are interchangeable)
157
+ test_user? ? @live_testing_user["id"] : 2905623
158
+ end
159
+
160
+ def self.user1_name
161
+ "Alex"
162
+ end
163
+
164
+ def self.user2
165
+ # see notes for user1
166
+ test_user? ? @live_testing_friend["id"] : "lukeshepard"
167
+ end
168
+
169
+ def self.user2_id
170
+ # see notes for user1
171
+ test_user? ? @live_testing_friend["id"] : 2901279
172
+ end
173
+
174
+ def self.user2_name
175
+ "Luke"
176
+ end
177
+
178
+ def self.page
179
+ "facebook"
180
+ end
181
+
182
+ def self.app_properties
183
+ mock_interface? ? {"desktop" => 0} : {"description" => "A test framework for EDH and its users. (#{rand(10000).to_i})"}
184
+ end
185
+ end
@@ -0,0 +1,124 @@
1
+ require 'erb'
2
+ require 'yaml'
3
+
4
+ module EDH
5
+ module MockHTTPService
6
+ include EDH::HTTPService
7
+
8
+ # fix our specs to use ok_json, so we always get the same results from to_json
9
+ MultiJson.use :ok_json
10
+
11
+ # Mocks all HTTP requests for with edh_spec_with_mocks.rb
12
+ # Mocked values to be included in TEST_DATA used in specs
13
+ ACCESS_TOKEN = '*'
14
+ APP_ACCESS_TOKEN = "**"
15
+ OAUTH_CODE = 'OAUTHCODE'
16
+
17
+ # Loads testing data
18
+ TEST_DATA = {}
19
+ TEST_DATA['search_time'] = (Time.now - 3600).to_s
20
+
21
+ RESPONSES = {}
22
+ def self.make_request(path, args, verb, options = {})
23
+ if response = match_response(path, args, verb, options)
24
+ # create response class object
25
+ response_object = if response.is_a? String
26
+ EDH::HTTPService::Response.new(200, response, {})
27
+ else
28
+ EDH::HTTPService::Response.new(response["code"] || 200, response["body"] || "", response["headers"] || {})
29
+ end
30
+ else
31
+ # Raises an error message with the place in the data YML
32
+ # to place a mock as well as a URL to request from
33
+ # Passport's servers for the actual data
34
+ # (Don't forget to replace ACCESS_TOKEN with a real access token)
35
+ data_trace = [path, args, verb, options] * ': '
36
+
37
+ args = args == 'no_args' ? '' : "#{args}&"
38
+ args += 'format=json'
39
+
40
+ raise "Missing a mock response for #{data_trace}\nAPI PATH: #{[path, args].join('?')}"
41
+ end
42
+
43
+ response_object
44
+ end
45
+
46
+ def self.encode_params(*args)
47
+ # use HTTPService's encode_params
48
+ HTTPService.encode_params(*args)
49
+ end
50
+
51
+ protected
52
+
53
+ # For a given query, see if our mock responses YAML has a resopnse for it.
54
+ def self.match_response(path, args, verb, options = {})
55
+ server = 'rest_api'
56
+ path = 'root' if path == '' || path == '/'
57
+ verb = (verb || 'get').to_s
58
+ token = args.delete('access_token')
59
+ with_token = (token == ACCESS_TOKEN || token == APP_ACCESS_TOKEN) ? 'with_token' : 'no_token'
60
+
61
+ if endpoint = RESPONSES[server][path]
62
+ # see if we have a match for the arguments
63
+ if arg_match = endpoint.find {|query, v| decode_query(query) == massage_args(args)}
64
+ # we have a match for the server/path/arguments
65
+ # so if it's the right verb and authentication, we're good
66
+ # arg_match will be [query, hash_response]
67
+ arg_match.last[verb][with_token]
68
+ end
69
+ end
70
+ end
71
+
72
+ # Since we're comparing the arguments with data in a yaml file, we need to
73
+ # massage them slightly to get to the format we expect.
74
+ def self.massage_args(arguments)
75
+ args = arguments.inject({}) do |hash, (k, v)|
76
+ # ensure our args are all stringified
77
+ value = if v.is_a?(String)
78
+ should_json_decode?(v) ? MultiJson.load(v) : v
79
+ else
80
+ v
81
+ end
82
+ # make sure all keys are strings
83
+ hash.merge(k.to_s => value)
84
+ end
85
+
86
+ # Assume format is always JSON
87
+ args.delete('format')
88
+
89
+ # if there are no args, return the special keyword no_args
90
+ args.empty? ? "no_args" : args
91
+ end
92
+
93
+ # Passport sometimes requires us to encode JSON values in an HTTP query
94
+ # param. This complicates test matches, since we get into JSON-encoding
95
+ # issues (what order keys are written into). To avoid string comparisons
96
+ # and the hacks required to make it work, we decode the query into a
97
+ # Ruby object.
98
+ def self.decode_query(string)
99
+ if string == "no_args"
100
+ string
101
+ else
102
+ # we can't use Faraday's decode_query because that CGI-unencodes, which
103
+ # will remove +'s in restriction strings
104
+ string.split("&").reduce({}) do |hash, component|
105
+ k, v = component.split("=", 2) # we only care about the first =
106
+ value = should_json_decode?(v) ? MultiJson.decode(v) : v.to_s rescue v.to_s
107
+ # some special-casing, unfortunate but acceptable in this testing
108
+ # environment
109
+ value = nil if value.empty?
110
+ value = true if value == "true"
111
+ value = false if value == "false"
112
+ hash.merge(k => value)
113
+ end
114
+ end
115
+ end
116
+
117
+ # We want to decode JSON because it may not be encoded in the same order
118
+ # all the time -- different Rubies may create a strings with equivalent
119
+ # content but different order. We want to compare the objects.
120
+ def self.should_json_decode?(v)
121
+ v.match(/^[\[\{]/)
122
+ end
123
+ end
124
+ end