activeresource 2.0.5 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activeresource might be problematic. Click here for more details.
- data/CHANGELOG +6 -3
- data/Rakefile +4 -4
- data/lib/active_resource/base.rb +257 -138
- data/lib/active_resource/connection.rb +41 -6
- data/lib/active_resource/custom_methods.rb +7 -7
- data/lib/active_resource/formats/xml_format.rb +1 -1
- data/lib/active_resource/http_mock.rb +76 -5
- data/lib/active_resource/validations.rb +2 -2
- data/lib/active_resource/version.rb +2 -2
- data/test/abstract_unit.rb +13 -1
- data/test/authorization_test.rb +42 -1
- data/test/base/custom_methods_test.rb +6 -3
- data/test/base/equality_test.rb +1 -1
- data/test/base/load_test.rb +1 -1
- data/test/base_errors_test.rb +1 -1
- data/test/base_test.rb +364 -27
- data/test/connection_test.rb +22 -1
- data/test/format_test.rb +2 -2
- data/test/setter_trap.rb +1 -1
- metadata +4 -5
@@ -18,6 +18,14 @@ module ActiveResource
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
+
# Raised when a Timeout::Error occurs.
|
22
|
+
class TimeoutError < ConnectionError
|
23
|
+
def initialize(message)
|
24
|
+
@message = message
|
25
|
+
end
|
26
|
+
def to_s; @message ;end
|
27
|
+
end
|
28
|
+
|
21
29
|
# 3xx Redirection
|
22
30
|
class Redirection < ConnectionError # :nodoc:
|
23
31
|
def to_s; response['Location'] ? "#{super} => #{response['Location']}" : super; end
|
@@ -55,7 +63,7 @@ module ActiveResource
|
|
55
63
|
# This class is used by ActiveResource::Base to interface with REST
|
56
64
|
# services.
|
57
65
|
class Connection
|
58
|
-
attr_reader :site
|
66
|
+
attr_reader :site, :user, :password, :timeout
|
59
67
|
attr_accessor :format
|
60
68
|
|
61
69
|
class << self
|
@@ -68,6 +76,7 @@ module ActiveResource
|
|
68
76
|
# attribute to the URI for the remote resource service.
|
69
77
|
def initialize(site, format = ActiveResource::Formats[:xml])
|
70
78
|
raise ArgumentError, 'Missing site URI' unless site
|
79
|
+
@user = @password = nil
|
71
80
|
self.site = site
|
72
81
|
self.format = format
|
73
82
|
end
|
@@ -75,6 +84,23 @@ module ActiveResource
|
|
75
84
|
# Set URI for remote service.
|
76
85
|
def site=(site)
|
77
86
|
@site = site.is_a?(URI) ? site : URI.parse(site)
|
87
|
+
@user = URI.decode(@site.user) if @site.user
|
88
|
+
@password = URI.decode(@site.password) if @site.password
|
89
|
+
end
|
90
|
+
|
91
|
+
# Set user for remote service.
|
92
|
+
def user=(user)
|
93
|
+
@user = user
|
94
|
+
end
|
95
|
+
|
96
|
+
# Set password for remote service.
|
97
|
+
def password=(password)
|
98
|
+
@password = password
|
99
|
+
end
|
100
|
+
|
101
|
+
# Set the number of seconds after which HTTP requests to the remote service should time out.
|
102
|
+
def timeout=(timeout)
|
103
|
+
@timeout = timeout
|
78
104
|
end
|
79
105
|
|
80
106
|
# Execute a GET request.
|
@@ -101,6 +127,12 @@ module ActiveResource
|
|
101
127
|
request(:post, path, body.to_s, build_request_headers(headers))
|
102
128
|
end
|
103
129
|
|
130
|
+
# Execute a HEAD request.
|
131
|
+
# Used to obtain meta-information about resources, such as whether they exist and their size (via response headers).
|
132
|
+
def head(path, headers = {})
|
133
|
+
request(:head, path, build_request_headers(headers))
|
134
|
+
end
|
135
|
+
|
104
136
|
|
105
137
|
private
|
106
138
|
# Makes request to remote service.
|
@@ -108,8 +140,10 @@ module ActiveResource
|
|
108
140
|
logger.info "#{method.to_s.upcase} #{site.scheme}://#{site.host}:#{site.port}#{path}" if logger
|
109
141
|
result = nil
|
110
142
|
time = Benchmark.realtime { result = http.send(method, path, *arguments) }
|
111
|
-
logger.info "--> #{result.code} #{result.message} (#{result.body ? result.body : 0}b %.2fs)" % time if logger
|
143
|
+
logger.info "--> #{result.code} #{result.message} (#{result.body ? result.body.length : 0}b %.2fs)" % time if logger
|
112
144
|
handle_response(result)
|
145
|
+
rescue Timeout::Error => e
|
146
|
+
raise TimeoutError.new(e.message)
|
113
147
|
end
|
114
148
|
|
115
149
|
# Handles response and error codes from remote service.
|
@@ -148,21 +182,22 @@ module ActiveResource
|
|
148
182
|
http = Net::HTTP.new(@site.host, @site.port)
|
149
183
|
http.use_ssl = @site.is_a?(URI::HTTPS)
|
150
184
|
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl
|
185
|
+
http.read_timeout = @timeout if @timeout # If timeout is not set, the default Net::HTTP timeout (60s) is used.
|
151
186
|
http
|
152
187
|
end
|
153
188
|
|
154
189
|
def default_header
|
155
190
|
@default_header ||= { 'Content-Type' => format.mime_type }
|
156
191
|
end
|
157
|
-
|
192
|
+
|
158
193
|
# Builds headers for request to remote service.
|
159
194
|
def build_request_headers(headers)
|
160
195
|
authorization_header.update(default_header).update(headers)
|
161
196
|
end
|
162
|
-
|
163
|
-
# Sets authorization header
|
197
|
+
|
198
|
+
# Sets authorization header
|
164
199
|
def authorization_header
|
165
|
-
(@
|
200
|
+
(@user || @password ? { 'Authorization' => 'Basic ' + ["#{@user}:#{ @password}"].pack('m').delete("\r\n") } : {})
|
166
201
|
end
|
167
202
|
|
168
203
|
def logger #:nodoc:
|
@@ -7,12 +7,12 @@ module ActiveResource
|
|
7
7
|
# :member => { :promote => :put, :deactivate => :delete }
|
8
8
|
# :collection => { :active => :get }
|
9
9
|
#
|
10
|
-
# This route set creates routes for the following
|
10
|
+
# This route set creates routes for the following HTTP requests:
|
11
11
|
#
|
12
|
-
# POST /people/new/register.xml
|
13
|
-
# PUT /people/1/promote.xml
|
14
|
-
# DELETE /people/1/deactivate.xml
|
15
|
-
# GET /people/active.xml
|
12
|
+
# POST /people/new/register.xml # PeopleController.register
|
13
|
+
# PUT /people/1/promote.xml # PeopleController.promote with :id => 1
|
14
|
+
# DELETE /people/1/deactivate.xml # PeopleController.deactivate with :id => 1
|
15
|
+
# GET /people/active.xml # PeopleController.active
|
16
16
|
#
|
17
17
|
# Using this module, Active Resource can use these custom REST methods just like the
|
18
18
|
# standard methods.
|
@@ -48,8 +48,8 @@ module ActiveResource
|
|
48
48
|
# # => [{:id => 1, :name => 'Ryan'}]
|
49
49
|
#
|
50
50
|
# Note: the objects returned from this method are not automatically converted
|
51
|
-
# into
|
52
|
-
#
|
51
|
+
# into Active Resource instances - they are ordinary Hashes. If you are expecting
|
52
|
+
# Active Resource instances, use the <tt>find</tt> class method with the
|
53
53
|
# <tt>:from</tt> option. For example:
|
54
54
|
#
|
55
55
|
# Person.find(:all, :from => :active)
|
@@ -21,7 +21,7 @@ module ActiveResource
|
|
21
21
|
|
22
22
|
private
|
23
23
|
# Manipulate from_xml Hash, because xml_simple is not exactly what we
|
24
|
-
# want for
|
24
|
+
# want for Active Resource.
|
25
25
|
def from_xml_data(data)
|
26
26
|
if data.is_a?(Hash) && data.keys.size == 1
|
27
27
|
data.values.first
|
@@ -3,13 +3,57 @@ require 'active_resource/connection'
|
|
3
3
|
module ActiveResource
|
4
4
|
class InvalidRequestError < StandardError; end #:nodoc:
|
5
5
|
|
6
|
+
# One thing that has always been a pain with remote web services is testing. The HttpMock
|
7
|
+
# class makes it easy to test your Active Resource models by creating a set of mock responses to specific
|
8
|
+
# requests.
|
9
|
+
#
|
10
|
+
# To test your Active Resource model, you simply call the ActiveResource::HttpMock.respond_to
|
11
|
+
# method with an attached block. The block declares a set of URIs with expected input, and the output
|
12
|
+
# each request should return. The passed in block has any number of entries in the following generalized
|
13
|
+
# format:
|
14
|
+
#
|
15
|
+
# mock.http_method(path, request_headers = {}, body = nil, status = 200, response_headers = {})
|
16
|
+
#
|
17
|
+
# * <tt>http_method</tt> - The HTTP method to listen for. This can be +get+, +post+, +put+, +delete+ or
|
18
|
+
# +head+.
|
19
|
+
# * <tt>path</tt> - A string, starting with a "/", defining the URI that is expected to be
|
20
|
+
# called.
|
21
|
+
# * <tt>request_headers</tt> - Headers that are expected along with the request. This argument uses a
|
22
|
+
# hash format, such as <tt>{ "Content-Type" => "application/xml" }</tt>. This mock will only trigger
|
23
|
+
# if your tests sends a request with identical headers.
|
24
|
+
# * <tt>body</tt> - The data to be returned. This should be a string of Active Resource parseable content,
|
25
|
+
# such as XML.
|
26
|
+
# * <tt>status</tt> - The HTTP response code, as an integer, to return with the response.
|
27
|
+
# * <tt>response_headers</tt> - Headers to be returned with the response. Uses the same hash format as
|
28
|
+
# <tt>request_headers</tt> listed above.
|
29
|
+
#
|
30
|
+
# In order for a mock to deliver its content, the incoming request must match by the <tt>http_method</tt>,
|
31
|
+
# +path+ and <tt>request_headers</tt>. If no match is found an InvalidRequestError exception
|
32
|
+
# will be raised letting you know you need to create a new mock for that request.
|
33
|
+
#
|
34
|
+
# ==== Example
|
35
|
+
# def setup
|
36
|
+
# @matz = { :id => 1, :name => "Matz" }.to_xml(:root => "person")
|
37
|
+
# ActiveResource::HttpMock.respond_to do |mock|
|
38
|
+
# mock.post "/people.xml", {}, @matz, 201, "Location" => "/people/1.xml"
|
39
|
+
# mock.get "/people/1.xml", {}, @matz
|
40
|
+
# mock.put "/people/1.xml", {}, nil, 204
|
41
|
+
# mock.delete "/people/1.xml", {}, nil, 200
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# def test_get_matz
|
46
|
+
# person = Person.find(1)
|
47
|
+
# assert_equal "Matz", person.name
|
48
|
+
# end
|
49
|
+
#
|
6
50
|
class HttpMock
|
7
|
-
class Responder
|
51
|
+
class Responder #:nodoc:
|
8
52
|
def initialize(responses)
|
9
53
|
@responses = responses
|
10
54
|
end
|
11
55
|
|
12
|
-
for method in [ :post, :put, :get, :delete ]
|
56
|
+
for method in [ :post, :put, :get, :delete, :head ]
|
13
57
|
module_eval <<-EOE, __FILE__, __LINE__
|
14
58
|
def #{method}(path, request_headers = {}, body = nil, status = 200, response_headers = {})
|
15
59
|
@responses[Request.new(:#{method}, path, nil, request_headers)] = Response.new(body || "", status, response_headers)
|
@@ -19,15 +63,41 @@ module ActiveResource
|
|
19
63
|
end
|
20
64
|
|
21
65
|
class << self
|
66
|
+
|
67
|
+
# Returns an array of all request objects that have been sent to the mock. You can use this to check
|
68
|
+
# wether or not your model actually sent an HTTP request.
|
69
|
+
#
|
70
|
+
# ==== Example
|
71
|
+
# def setup
|
72
|
+
# @matz = { :id => 1, :name => "Matz" }.to_xml(:root => "person")
|
73
|
+
# ActiveResource::HttpMock.respond_to do |mock|
|
74
|
+
# mock.get "/people/1.xml", {}, @matz
|
75
|
+
# end
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# def test_should_request_remote_service
|
79
|
+
# person = Person.find(1) # Call the remote service
|
80
|
+
#
|
81
|
+
# # This request object has the same HTTP method and path as declared by the mock
|
82
|
+
# expected_request = ActiveResource::Request.new(:get, "/people/1.xml")
|
83
|
+
#
|
84
|
+
# # Assert that the mock received, and responded to, the expected request from the model
|
85
|
+
# assert ActiveResource::HttpMock.requests.include?(expected_request)
|
86
|
+
# end
|
22
87
|
def requests
|
23
88
|
@@requests ||= []
|
24
89
|
end
|
25
90
|
|
91
|
+
# Returns a hash of <tt>request => response</tt> pairs for all all responses this mock has delivered, where +request+
|
92
|
+
# is an instance of ActiveResource::Request and the response is, naturally, an instance of
|
93
|
+
# ActiveResource::Response.
|
26
94
|
def responses
|
27
95
|
@@responses ||= {}
|
28
96
|
end
|
29
97
|
|
30
|
-
|
98
|
+
# Accepts a block which declares a set of requests and responses for the HttpMock to respond to. See the main
|
99
|
+
# ActiveResource::HttpMock description for a more detailed explanation.
|
100
|
+
def respond_to(pairs = {}) #:yields: mock
|
31
101
|
reset!
|
32
102
|
pairs.each do |(path, response)|
|
33
103
|
responses[path] = response
|
@@ -40,6 +110,7 @@ module ActiveResource
|
|
40
110
|
end
|
41
111
|
end
|
42
112
|
|
113
|
+
# Deletes all logged requests and responses.
|
43
114
|
def reset!
|
44
115
|
requests.clear
|
45
116
|
responses.clear
|
@@ -56,7 +127,7 @@ module ActiveResource
|
|
56
127
|
EOE
|
57
128
|
end
|
58
129
|
|
59
|
-
for method in [ :get, :delete ]
|
130
|
+
for method in [ :get, :delete, :head ]
|
60
131
|
module_eval <<-EOE, __FILE__, __LINE__
|
61
132
|
def #{method}(path, headers)
|
62
133
|
request = ActiveResource::Request.new(:#{method}, path, nil, headers)
|
@@ -66,7 +137,7 @@ module ActiveResource
|
|
66
137
|
EOE
|
67
138
|
end
|
68
139
|
|
69
|
-
def initialize(site)
|
140
|
+
def initialize(site) #:nodoc:
|
70
141
|
@site = site
|
71
142
|
end
|
72
143
|
end
|
@@ -216,8 +216,8 @@ module ActiveResource
|
|
216
216
|
end
|
217
217
|
end
|
218
218
|
|
219
|
-
# Module to allow validation of
|
220
|
-
# Methods are implemented by overriding
|
219
|
+
# Module to allow validation of Active Resource objects, which creates an Errors instance for every resource.
|
220
|
+
# Methods are implemented by overriding Base#validate or its variants Each of these methods can inspect
|
221
221
|
# the state of the object, which usually means ensuring that a number of attributes have a certain value
|
222
222
|
# (such as not empty, within a given range, matching a certain regular expression and so on).
|
223
223
|
#
|
data/test/abstract_unit.rb
CHANGED
@@ -7,4 +7,16 @@ require 'active_resource/http_mock'
|
|
7
7
|
$:.unshift "#{File.dirname(__FILE__)}/../test"
|
8
8
|
require 'setter_trap'
|
9
9
|
|
10
|
-
ActiveResource::Base.logger = Logger.new("#{File.dirname(__FILE__)}/debug.log")
|
10
|
+
ActiveResource::Base.logger = Logger.new("#{File.dirname(__FILE__)}/debug.log")
|
11
|
+
|
12
|
+
# Wrap tests that use Mocha and skip if unavailable.
|
13
|
+
def uses_mocha(test_name)
|
14
|
+
unless Object.const_defined?(:Mocha)
|
15
|
+
require 'mocha'
|
16
|
+
require 'stubba'
|
17
|
+
end
|
18
|
+
yield
|
19
|
+
rescue LoadError => load_error
|
20
|
+
raise unless load_error.message =~ /mocha/i
|
21
|
+
$stderr.puts "Skipping #{test_name} tests. `gem install mocha` and try again."
|
22
|
+
end
|
data/test/authorization_test.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'abstract_unit'
|
2
2
|
|
3
3
|
class AuthorizationTest < Test::Unit::TestCase
|
4
4
|
Response = Struct.new(:code)
|
@@ -45,6 +45,47 @@ class AuthorizationTest < Test::Unit::TestCase
|
|
45
45
|
assert_equal ["", "test123"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1]
|
46
46
|
end
|
47
47
|
|
48
|
+
def test_authorization_header_with_decoded_credentials_from_url
|
49
|
+
@conn = ActiveResource::Connection.new("http://my%40email.com:%31%32%33@localhost")
|
50
|
+
authorization_header = @conn.send!(:authorization_header)
|
51
|
+
authorization = authorization_header["Authorization"].to_s.split
|
52
|
+
|
53
|
+
assert_equal "Basic", authorization[0]
|
54
|
+
assert_equal ["my@email.com", "123"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1]
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_authorization_header_explicitly_setting_username_and_password
|
58
|
+
@authenticated_conn = ActiveResource::Connection.new("http://@localhost")
|
59
|
+
@authenticated_conn.user = 'david'
|
60
|
+
@authenticated_conn.password = 'test123'
|
61
|
+
authorization_header = @authenticated_conn.send!(:authorization_header)
|
62
|
+
assert_equal @authorization_request_header['Authorization'], authorization_header['Authorization']
|
63
|
+
authorization = authorization_header["Authorization"].to_s.split
|
64
|
+
|
65
|
+
assert_equal "Basic", authorization[0]
|
66
|
+
assert_equal ["david", "test123"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1]
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_authorization_header_explicitly_setting_username_but_no_password
|
70
|
+
@conn = ActiveResource::Connection.new("http://@localhost")
|
71
|
+
@conn.user = "david"
|
72
|
+
authorization_header = @conn.send!(:authorization_header)
|
73
|
+
authorization = authorization_header["Authorization"].to_s.split
|
74
|
+
|
75
|
+
assert_equal "Basic", authorization[0]
|
76
|
+
assert_equal ["david"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1]
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_authorization_header_explicitly_setting_password_but_no_username
|
80
|
+
@conn = ActiveResource::Connection.new("http://@localhost")
|
81
|
+
@conn.password = "test123"
|
82
|
+
authorization_header = @conn.send!(:authorization_header)
|
83
|
+
authorization = authorization_header["Authorization"].to_s.split
|
84
|
+
|
85
|
+
assert_equal "Basic", authorization[0]
|
86
|
+
assert_equal ["", "test123"], ActiveSupport::Base64.decode64(authorization[1]).split(":")[0..1]
|
87
|
+
end
|
88
|
+
|
48
89
|
def test_get
|
49
90
|
david = @authenticated_conn.get("/people/2.xml")
|
50
91
|
assert_equal "David", david["name"]
|
@@ -1,6 +1,6 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require 'abstract_unit'
|
2
|
+
require 'fixtures/person'
|
3
|
+
require 'fixtures/street_address'
|
4
4
|
|
5
5
|
class CustomMethodsTest < Test::Unit::TestCase
|
6
6
|
def setup
|
@@ -32,6 +32,9 @@ class CustomMethodsTest < Test::Unit::TestCase
|
|
32
32
|
mock.put "/people/1/addresses/sort.xml?by=name", {}, nil, 204
|
33
33
|
mock.post "/people/1/addresses/new/link.xml", {}, { :street => '12345 Street' }.to_xml(:root => 'address'), 201, 'Location' => '/people/1/addresses/2.xml'
|
34
34
|
end
|
35
|
+
|
36
|
+
Person.user = nil
|
37
|
+
Person.password = nil
|
35
38
|
end
|
36
39
|
|
37
40
|
def teardown
|
data/test/base/equality_test.rb
CHANGED
data/test/base/load_test.rb
CHANGED
data/test/base_errors_test.rb
CHANGED
data/test/base_test.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'abstract_unit'
|
2
2
|
require "fixtures/person"
|
3
3
|
require "fixtures/street_address"
|
4
4
|
require "fixtures/beast"
|
@@ -7,36 +7,49 @@ class BaseTest < Test::Unit::TestCase
|
|
7
7
|
def setup
|
8
8
|
@matz = { :id => 1, :name => 'Matz' }.to_xml(:root => 'person')
|
9
9
|
@david = { :id => 2, :name => 'David' }.to_xml(:root => 'person')
|
10
|
+
@greg = { :id => 3, :name => 'Greg' }.to_xml(:root => 'person')
|
10
11
|
@addy = { :id => 1, :street => '12345 Street' }.to_xml(:root => 'address')
|
11
12
|
@default_request_headers = { 'Content-Type' => 'application/xml' }
|
12
13
|
@rick = { :name => "Rick", :age => 25 }.to_xml(:root => "person")
|
13
14
|
@people = [{ :id => 1, :name => 'Matz' }, { :id => 2, :name => 'David' }].to_xml(:root => 'people')
|
14
15
|
@people_david = [{ :id => 2, :name => 'David' }].to_xml(:root => 'people')
|
15
16
|
@addresses = [{ :id => 1, :street => '12345 Street' }].to_xml(:root => 'addresses')
|
16
|
-
|
17
|
+
|
17
18
|
ActiveResource::HttpMock.respond_to do |mock|
|
18
|
-
mock.get "/people/1.xml",
|
19
|
-
mock.get "/people/2.xml",
|
20
|
-
mock.get "/people/
|
21
|
-
mock.
|
22
|
-
mock.
|
23
|
-
mock.delete "/people/
|
24
|
-
mock.
|
25
|
-
mock.
|
26
|
-
mock.
|
27
|
-
mock.get "/people
|
28
|
-
mock.get "/people/1/addresses
|
29
|
-
mock.get "/people/1/addresses/
|
30
|
-
mock.get "/people/
|
31
|
-
mock.
|
32
|
-
mock.
|
33
|
-
mock.
|
34
|
-
mock.
|
35
|
-
mock.
|
36
|
-
mock.
|
37
|
-
mock.
|
38
|
-
mock.
|
19
|
+
mock.get "/people/1.xml", {}, @matz
|
20
|
+
mock.get "/people/2.xml", {}, @david
|
21
|
+
mock.get "/people/Greg.xml", {}, @greg
|
22
|
+
mock.get "/people/4.xml", {'key' => 'value'}, nil, 404
|
23
|
+
mock.put "/people/1.xml", {}, nil, 204
|
24
|
+
mock.delete "/people/1.xml", {}, nil, 200
|
25
|
+
mock.delete "/people/2.xml", {}, nil, 400
|
26
|
+
mock.get "/people/99.xml", {}, nil, 404
|
27
|
+
mock.post "/people.xml", {}, @rick, 201, 'Location' => '/people/5.xml'
|
28
|
+
mock.get "/people.xml", {}, @people
|
29
|
+
mock.get "/people/1/addresses.xml", {}, @addresses
|
30
|
+
mock.get "/people/1/addresses/1.xml", {}, @addy
|
31
|
+
mock.get "/people/1/addresses/2.xml", {}, nil, 404
|
32
|
+
mock.get "/people/2/addresses/1.xml", {}, nil, 404
|
33
|
+
mock.get "/people/Greg/addresses/1.xml", {}, @addy
|
34
|
+
mock.put "/people/1/addresses/1.xml", {}, nil, 204
|
35
|
+
mock.delete "/people/1/addresses/1.xml", {}, nil, 200
|
36
|
+
mock.post "/people/1/addresses.xml", {}, nil, 201, 'Location' => '/people/1/addresses/5'
|
37
|
+
mock.get "/people//addresses.xml", {}, nil, 404
|
38
|
+
mock.get "/people//addresses/1.xml", {}, nil, 404
|
39
|
+
mock.put "/people//addresses/1.xml", {}, nil, 404
|
40
|
+
mock.delete "/people//addresses/1.xml", {}, nil, 404
|
41
|
+
mock.post "/people//addresses.xml", {}, nil, 404
|
42
|
+
mock.head "/people/1.xml", {}, nil, 200
|
43
|
+
mock.head "/people/Greg.xml", {}, nil, 200
|
44
|
+
mock.head "/people/99.xml", {}, nil, 404
|
45
|
+
mock.head "/people/1/addresses/1.xml", {}, nil, 200
|
46
|
+
mock.head "/people/1/addresses/2.xml", {}, nil, 404
|
47
|
+
mock.head "/people/2/addresses/1.xml", {}, nil, 404
|
48
|
+
mock.head "/people/Greg/addresses/1.xml", {}, nil, 200
|
39
49
|
end
|
50
|
+
|
51
|
+
Person.user = nil
|
52
|
+
Person.password = nil
|
40
53
|
end
|
41
54
|
|
42
55
|
|
@@ -63,6 +76,61 @@ class BaseTest < Test::Unit::TestCase
|
|
63
76
|
assert_nil actor.site
|
64
77
|
end
|
65
78
|
|
79
|
+
def test_should_accept_setting_user
|
80
|
+
Forum.user = 'david'
|
81
|
+
assert_equal('david', Forum.user)
|
82
|
+
assert_equal('david', Forum.connection.user)
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_should_accept_setting_password
|
86
|
+
Forum.password = 'test123'
|
87
|
+
assert_equal('test123', Forum.password)
|
88
|
+
assert_equal('test123', Forum.connection.password)
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_should_accept_setting_timeout
|
92
|
+
Forum.timeout = 5
|
93
|
+
assert_equal(5, Forum.timeout)
|
94
|
+
assert_equal(5, Forum.connection.timeout)
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_user_variable_can_be_reset
|
98
|
+
actor = Class.new(ActiveResource::Base)
|
99
|
+
actor.site = 'http://cinema'
|
100
|
+
assert_nil actor.user
|
101
|
+
actor.user = 'username'
|
102
|
+
actor.user = nil
|
103
|
+
assert_nil actor.user
|
104
|
+
assert_nil actor.connection.user
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_password_variable_can_be_reset
|
108
|
+
actor = Class.new(ActiveResource::Base)
|
109
|
+
actor.site = 'http://cinema'
|
110
|
+
assert_nil actor.password
|
111
|
+
actor.password = 'username'
|
112
|
+
actor.password = nil
|
113
|
+
assert_nil actor.password
|
114
|
+
assert_nil actor.connection.password
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_timeout_variable_can_be_reset
|
118
|
+
actor = Class.new(ActiveResource::Base)
|
119
|
+
actor.site = 'http://cinema'
|
120
|
+
assert_nil actor.timeout
|
121
|
+
actor.timeout = 5
|
122
|
+
actor.timeout = nil
|
123
|
+
assert_nil actor.timeout
|
124
|
+
assert_nil actor.connection.timeout
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_credentials_from_site_are_decoded
|
128
|
+
actor = Class.new(ActiveResource::Base)
|
129
|
+
actor.site = 'http://my%40email.com:%31%32%33@cinema'
|
130
|
+
assert_equal("my@email.com", actor.user)
|
131
|
+
assert_equal("123", actor.password)
|
132
|
+
end
|
133
|
+
|
66
134
|
def test_site_reader_uses_superclass_site_until_written
|
67
135
|
# Superclass is Object so returns nil.
|
68
136
|
assert_nil ActiveResource::Base.site
|
@@ -98,12 +166,122 @@ class BaseTest < Test::Unit::TestCase
|
|
98
166
|
apple = Class.new(fruit)
|
99
167
|
|
100
168
|
fruit.site = 'http://market'
|
101
|
-
assert_equal fruit.site, apple.site, 'subclass did not adopt changes
|
169
|
+
assert_equal fruit.site, apple.site, 'subclass did not adopt changes from parent class'
|
102
170
|
|
103
171
|
fruit.site = 'http://supermarket'
|
104
|
-
assert_equal fruit.site, apple.site, 'subclass did not adopt changes
|
172
|
+
assert_equal fruit.site, apple.site, 'subclass did not adopt changes from parent class'
|
105
173
|
end
|
106
174
|
|
175
|
+
def test_user_reader_uses_superclass_user_until_written
|
176
|
+
# Superclass is Object so returns nil.
|
177
|
+
assert_nil ActiveResource::Base.user
|
178
|
+
assert_nil Class.new(ActiveResource::Base).user
|
179
|
+
Person.user = 'anonymous'
|
180
|
+
|
181
|
+
# Subclass uses superclass user.
|
182
|
+
actor = Class.new(Person)
|
183
|
+
assert_equal Person.user, actor.user
|
184
|
+
|
185
|
+
# Subclass returns frozen superclass copy.
|
186
|
+
assert !Person.user.frozen?
|
187
|
+
assert actor.user.frozen?
|
188
|
+
|
189
|
+
# Changing subclass user doesn't change superclass user.
|
190
|
+
actor.user = 'david'
|
191
|
+
assert_not_equal Person.user, actor.user
|
192
|
+
|
193
|
+
# Changing superclass user doesn't overwrite subclass user.
|
194
|
+
Person.user = 'john'
|
195
|
+
assert_not_equal Person.user, actor.user
|
196
|
+
|
197
|
+
# Changing superclass user after subclassing changes subclass user.
|
198
|
+
jester = Class.new(actor)
|
199
|
+
actor.user = 'john.doe'
|
200
|
+
assert_equal actor.user, jester.user
|
201
|
+
|
202
|
+
# Subclasses are always equal to superclass user when not overridden
|
203
|
+
fruit = Class.new(ActiveResource::Base)
|
204
|
+
apple = Class.new(fruit)
|
205
|
+
|
206
|
+
fruit.user = 'manager'
|
207
|
+
assert_equal fruit.user, apple.user, 'subclass did not adopt changes from parent class'
|
208
|
+
|
209
|
+
fruit.user = 'client'
|
210
|
+
assert_equal fruit.user, apple.user, 'subclass did not adopt changes from parent class'
|
211
|
+
end
|
212
|
+
|
213
|
+
def test_password_reader_uses_superclass_password_until_written
|
214
|
+
# Superclass is Object so returns nil.
|
215
|
+
assert_nil ActiveResource::Base.password
|
216
|
+
assert_nil Class.new(ActiveResource::Base).password
|
217
|
+
Person.password = 'my-password'
|
218
|
+
|
219
|
+
# Subclass uses superclass password.
|
220
|
+
actor = Class.new(Person)
|
221
|
+
assert_equal Person.password, actor.password
|
222
|
+
|
223
|
+
# Subclass returns frozen superclass copy.
|
224
|
+
assert !Person.password.frozen?
|
225
|
+
assert actor.password.frozen?
|
226
|
+
|
227
|
+
# Changing subclass password doesn't change superclass password.
|
228
|
+
actor.password = 'secret'
|
229
|
+
assert_not_equal Person.password, actor.password
|
230
|
+
|
231
|
+
# Changing superclass password doesn't overwrite subclass password.
|
232
|
+
Person.password = 'super-secret'
|
233
|
+
assert_not_equal Person.password, actor.password
|
234
|
+
|
235
|
+
# Changing superclass password after subclassing changes subclass password.
|
236
|
+
jester = Class.new(actor)
|
237
|
+
actor.password = 'even-more-secret'
|
238
|
+
assert_equal actor.password, jester.password
|
239
|
+
|
240
|
+
# Subclasses are always equal to superclass password when not overridden
|
241
|
+
fruit = Class.new(ActiveResource::Base)
|
242
|
+
apple = Class.new(fruit)
|
243
|
+
|
244
|
+
fruit.password = 'mega-secret'
|
245
|
+
assert_equal fruit.password, apple.password, 'subclass did not adopt changes from parent class'
|
246
|
+
|
247
|
+
fruit.password = 'ok-password'
|
248
|
+
assert_equal fruit.password, apple.password, 'subclass did not adopt changes from parent class'
|
249
|
+
end
|
250
|
+
|
251
|
+
def test_timeout_reader_uses_superclass_timeout_until_written
|
252
|
+
# Superclass is Object so returns nil.
|
253
|
+
assert_nil ActiveResource::Base.timeout
|
254
|
+
assert_nil Class.new(ActiveResource::Base).timeout
|
255
|
+
Person.timeout = 5
|
256
|
+
|
257
|
+
# Subclass uses superclass timeout.
|
258
|
+
actor = Class.new(Person)
|
259
|
+
assert_equal Person.timeout, actor.timeout
|
260
|
+
|
261
|
+
# Changing subclass timeout doesn't change superclass timeout.
|
262
|
+
actor.timeout = 10
|
263
|
+
assert_not_equal Person.timeout, actor.timeout
|
264
|
+
|
265
|
+
# Changing superclass timeout doesn't overwrite subclass timeout.
|
266
|
+
Person.timeout = 15
|
267
|
+
assert_not_equal Person.timeout, actor.timeout
|
268
|
+
|
269
|
+
# Changing superclass timeout after subclassing changes subclass timeout.
|
270
|
+
jester = Class.new(actor)
|
271
|
+
actor.timeout = 20
|
272
|
+
assert_equal actor.timeout, jester.timeout
|
273
|
+
|
274
|
+
# Subclasses are always equal to superclass timeout when not overridden.
|
275
|
+
fruit = Class.new(ActiveResource::Base)
|
276
|
+
apple = Class.new(fruit)
|
277
|
+
|
278
|
+
fruit.timeout = 25
|
279
|
+
assert_equal fruit.timeout, apple.timeout, 'subclass did not adopt changes from parent class'
|
280
|
+
|
281
|
+
fruit.timeout = 30
|
282
|
+
assert_equal fruit.timeout, apple.timeout, 'subclass did not adopt changes from parent class'
|
283
|
+
end
|
284
|
+
|
107
285
|
def test_updating_baseclass_site_object_wipes_descendent_cached_connection_objects
|
108
286
|
# Subclasses are always equal to superclass site when not overridden
|
109
287
|
fruit = Class.new(ActiveResource::Base)
|
@@ -111,9 +289,60 @@ class BaseTest < Test::Unit::TestCase
|
|
111
289
|
|
112
290
|
fruit.site = 'http://market'
|
113
291
|
assert_equal fruit.connection.site, apple.connection.site
|
292
|
+
first_connection = apple.connection.object_id
|
114
293
|
|
115
294
|
fruit.site = 'http://supermarket'
|
116
|
-
assert_equal fruit.connection.site, apple.connection.site
|
295
|
+
assert_equal fruit.connection.site, apple.connection.site
|
296
|
+
second_connection = apple.connection.object_id
|
297
|
+
assert_not_equal(first_connection, second_connection, 'Connection should be re-created')
|
298
|
+
end
|
299
|
+
|
300
|
+
def test_updating_baseclass_user_wipes_descendent_cached_connection_objects
|
301
|
+
# Subclasses are always equal to superclass user when not overridden
|
302
|
+
fruit = Class.new(ActiveResource::Base)
|
303
|
+
apple = Class.new(fruit)
|
304
|
+
fruit.site = 'http://market'
|
305
|
+
|
306
|
+
fruit.user = 'david'
|
307
|
+
assert_equal fruit.connection.user, apple.connection.user
|
308
|
+
first_connection = apple.connection.object_id
|
309
|
+
|
310
|
+
fruit.user = 'john'
|
311
|
+
assert_equal fruit.connection.user, apple.connection.user
|
312
|
+
second_connection = apple.connection.object_id
|
313
|
+
assert_not_equal(first_connection, second_connection, 'Connection should be re-created')
|
314
|
+
end
|
315
|
+
|
316
|
+
def test_updating_baseclass_password_wipes_descendent_cached_connection_objects
|
317
|
+
# Subclasses are always equal to superclass password when not overridden
|
318
|
+
fruit = Class.new(ActiveResource::Base)
|
319
|
+
apple = Class.new(fruit)
|
320
|
+
fruit.site = 'http://market'
|
321
|
+
|
322
|
+
fruit.password = 'secret'
|
323
|
+
assert_equal fruit.connection.password, apple.connection.password
|
324
|
+
first_connection = apple.connection.object_id
|
325
|
+
|
326
|
+
fruit.password = 'supersecret'
|
327
|
+
assert_equal fruit.connection.password, apple.connection.password
|
328
|
+
second_connection = apple.connection.object_id
|
329
|
+
assert_not_equal(first_connection, second_connection, 'Connection should be re-created')
|
330
|
+
end
|
331
|
+
|
332
|
+
def test_updating_baseclass_timeout_wipes_descendent_cached_connection_objects
|
333
|
+
# Subclasses are always equal to superclass timeout when not overridden
|
334
|
+
fruit = Class.new(ActiveResource::Base)
|
335
|
+
apple = Class.new(fruit)
|
336
|
+
fruit.site = 'http://market'
|
337
|
+
|
338
|
+
fruit.timeout = 5
|
339
|
+
assert_equal fruit.connection.timeout, apple.connection.timeout
|
340
|
+
first_connection = apple.connection.object_id
|
341
|
+
|
342
|
+
fruit.timeout = 10
|
343
|
+
assert_equal fruit.connection.timeout, apple.connection.timeout
|
344
|
+
second_connection = apple.connection.object_id
|
345
|
+
assert_not_equal(first_connection, second_connection, 'Connection should be re-created')
|
117
346
|
end
|
118
347
|
|
119
348
|
def test_collection_name
|
@@ -144,6 +373,30 @@ class BaseTest < Test::Unit::TestCase
|
|
144
373
|
def test_custom_element_path
|
145
374
|
assert_equal '/people/1/addresses/1.xml', StreetAddress.element_path(1, :person_id => 1)
|
146
375
|
assert_equal '/people/1/addresses/1.xml', StreetAddress.element_path(1, 'person_id' => 1)
|
376
|
+
assert_equal '/people/Greg/addresses/1.xml', StreetAddress.element_path(1, 'person_id' => 'Greg')
|
377
|
+
end
|
378
|
+
|
379
|
+
def test_custom_element_path_with_redefined_to_param
|
380
|
+
Person.module_eval do
|
381
|
+
alias_method :original_to_param_element_path, :to_param
|
382
|
+
def to_param
|
383
|
+
name
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
# Class method.
|
388
|
+
assert_equal '/people/Greg.xml', Person.element_path('Greg')
|
389
|
+
|
390
|
+
# Protected Instance method.
|
391
|
+
assert_equal '/people/Greg.xml', Person.find('Greg').send(:element_path)
|
392
|
+
|
393
|
+
ensure
|
394
|
+
# revert back to original
|
395
|
+
Person.module_eval do
|
396
|
+
# save the 'new' to_param so we don't get a warning about discarding the method
|
397
|
+
alias_method :element_path_to_param, :to_param
|
398
|
+
alias_method :to_param, :original_to_param_element_path
|
399
|
+
end
|
147
400
|
end
|
148
401
|
|
149
402
|
def test_custom_element_path_with_parameters
|
@@ -248,7 +501,7 @@ class BaseTest < Test::Unit::TestCase
|
|
248
501
|
|
249
502
|
def test_custom_header
|
250
503
|
Person.headers['key'] = 'value'
|
251
|
-
assert_raises(ActiveResource::ResourceNotFound) { Person.find(
|
504
|
+
assert_raises(ActiveResource::ResourceNotFound) { Person.find(4) }
|
252
505
|
ensure
|
253
506
|
Person.headers.delete('key')
|
254
507
|
end
|
@@ -329,6 +582,26 @@ class BaseTest < Test::Unit::TestCase
|
|
329
582
|
assert_equal address, address.reload
|
330
583
|
end
|
331
584
|
|
585
|
+
def test_reload_with_redefined_to_param
|
586
|
+
Person.module_eval do
|
587
|
+
alias_method :original_to_param_reload, :to_param
|
588
|
+
def to_param
|
589
|
+
name
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
593
|
+
person = Person.find('Greg')
|
594
|
+
assert_equal person, person.reload
|
595
|
+
|
596
|
+
ensure
|
597
|
+
# revert back to original
|
598
|
+
Person.module_eval do
|
599
|
+
# save the 'new' to_param so we don't get a warning about discarding the method
|
600
|
+
alias_method :reload_to_param, :to_param
|
601
|
+
alias_method :to_param, :original_to_param_reload
|
602
|
+
end
|
603
|
+
end
|
604
|
+
|
332
605
|
def test_reload_works_without_prefix_options
|
333
606
|
person = Person.find(:first)
|
334
607
|
assert_equal person, person.reload
|
@@ -351,6 +624,41 @@ class BaseTest < Test::Unit::TestCase
|
|
351
624
|
assert_raises(ActiveResource::ResourceConflict) { Person.create(:name => 'Rick') }
|
352
625
|
end
|
353
626
|
|
627
|
+
def test_clone
|
628
|
+
matz = Person.find(1)
|
629
|
+
matz_c = matz.clone
|
630
|
+
assert matz_c.new?
|
631
|
+
matz.attributes.each do |k, v|
|
632
|
+
assert_equal v, matz_c.send(k) if k != Person.primary_key
|
633
|
+
end
|
634
|
+
end
|
635
|
+
|
636
|
+
def test_nested_clone
|
637
|
+
addy = StreetAddress.find(1, :params => {:person_id => 1})
|
638
|
+
addy_c = addy.clone
|
639
|
+
assert addy_c.new?
|
640
|
+
addy.attributes.each do |k, v|
|
641
|
+
assert_equal v, addy_c.send(k) if k != StreetAddress.primary_key
|
642
|
+
end
|
643
|
+
assert_equal addy.prefix_options, addy_c.prefix_options
|
644
|
+
end
|
645
|
+
|
646
|
+
def test_complex_clone
|
647
|
+
matz = Person.find(1)
|
648
|
+
matz.address = StreetAddress.find(1, :params => {:person_id => matz.id})
|
649
|
+
matz.non_ar_hash = {:not => "an ARes instance"}
|
650
|
+
matz.non_ar_arr = ["not", "ARes"]
|
651
|
+
matz_c = matz.clone
|
652
|
+
assert matz_c.new?
|
653
|
+
assert_raises(NoMethodError) {matz_c.address}
|
654
|
+
assert_equal matz.non_ar_hash, matz_c.non_ar_hash
|
655
|
+
assert_equal matz.non_ar_arr, matz_c.non_ar_arr
|
656
|
+
|
657
|
+
# Test that actual copy, not just reference copy
|
658
|
+
matz.non_ar_hash[:not] = "changed"
|
659
|
+
assert_not_equal matz.non_ar_hash, matz_c.non_ar_hash
|
660
|
+
end
|
661
|
+
|
354
662
|
def test_update
|
355
663
|
matz = Person.find(:first)
|
356
664
|
matz.name = "David"
|
@@ -437,6 +745,35 @@ class BaseTest < Test::Unit::TestCase
|
|
437
745
|
assert !StreetAddress.new({:id => 2, :person_id => 1}).exists?
|
438
746
|
end
|
439
747
|
|
748
|
+
def test_exists_with_redefined_to_param
|
749
|
+
Person.module_eval do
|
750
|
+
alias_method :original_to_param_exists, :to_param
|
751
|
+
def to_param
|
752
|
+
name
|
753
|
+
end
|
754
|
+
end
|
755
|
+
|
756
|
+
# Class method.
|
757
|
+
assert Person.exists?('Greg')
|
758
|
+
|
759
|
+
# Instance method.
|
760
|
+
assert Person.find('Greg').exists?
|
761
|
+
|
762
|
+
# Nested class method.
|
763
|
+
assert StreetAddress.exists?(1, :params => { :person_id => Person.find('Greg').to_param })
|
764
|
+
|
765
|
+
# Nested instance method.
|
766
|
+
assert StreetAddress.find(1, :params => { :person_id => Person.find('Greg').to_param }).exists?
|
767
|
+
|
768
|
+
ensure
|
769
|
+
# revert back to original
|
770
|
+
Person.module_eval do
|
771
|
+
# save the 'new' to_param so we don't get a warning about discarding the method
|
772
|
+
alias_method :exists_to_param, :to_param
|
773
|
+
alias_method :to_param, :original_to_param_exists
|
774
|
+
end
|
775
|
+
end
|
776
|
+
|
440
777
|
def test_to_xml
|
441
778
|
matz = Person.find(1)
|
442
779
|
xml = matz.to_xml
|