edh 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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