aboutsummaryrefslogtreecommitdiffstats
path: root/activeresource
diff options
context:
space:
mode:
authorEmilio Tagua <miloops@gmail.com>2009-08-10 18:07:33 -0300
committerEmilio Tagua <miloops@gmail.com>2009-08-10 18:07:33 -0300
commit0e2fbd80e2420329738b891240d44a056cea1de4 (patch)
tree5b16755670be58e168b5e86e2cdcb43ee5aa3918 /activeresource
parenteb3ae44ccaff1dc63eb31bf86d8db07c88ddc413 (diff)
parent600a89f2082beadf4af9fe140a1a2ae56386cd49 (diff)
downloadrails-0e2fbd80e2420329738b891240d44a056cea1de4.tar.gz
rails-0e2fbd80e2420329738b891240d44a056cea1de4.tar.bz2
rails-0e2fbd80e2420329738b891240d44a056cea1de4.zip
Merge commit 'rails/master'
Conflicts: activerecord/lib/active_record/calculations.rb activerecord/lib/active_record/connection_adapters/mysql_adapter.rb activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
Diffstat (limited to 'activeresource')
-rw-r--r--activeresource/CHANGELOG11
-rw-r--r--activeresource/lib/active_resource/base.rb80
-rw-r--r--activeresource/lib/active_resource/connection.rb68
-rw-r--r--activeresource/lib/active_resource/exceptions.rb11
-rw-r--r--activeresource/lib/active_resource/validations.rb24
-rw-r--r--activeresource/test/base/load_test.rb17
-rw-r--r--activeresource/test/base_errors_test.rb77
-rw-r--r--activeresource/test/base_test.rb147
-rw-r--r--activeresource/test/connection_test.rb42
-rw-r--r--activeresource/test/fixtures/proxy.rb4
10 files changed, 445 insertions, 36 deletions
diff --git a/activeresource/CHANGELOG b/activeresource/CHANGELOG
index 6572934893..113694e895 100644
--- a/activeresource/CHANGELOG
+++ b/activeresource/CHANGELOG
@@ -1,3 +1,14 @@
+*Edge*
+
+* Add support for errors in JSON format. #1956 [Fabien Jakimowicz]
+
+* Recognizes 410 as Resource Gone. #2316 [Jordan Brough, Jatinder Singh]
+
+* More thorough SSL support. #2370 [Roy Nicholson]
+
+* HTTP proxy support. #2133 [Marshall Huss, Sébastien Dabet]
+
+
*2.3.2 [Final] (March 15, 2009)*
* Nothing new, just included in 2.3.2
diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb
index bc82139dac..88de8b1c66 100644
--- a/activeresource/lib/active_resource/base.rb
+++ b/activeresource/lib/active_resource/base.rb
@@ -103,6 +103,8 @@ module ActiveResource
#
# Many REST APIs will require authentication, usually in the form of basic
# HTTP authentication. Authentication can be specified by:
+ #
+ # === HTTP Basic Authentication
# * putting the credentials in the URL for the +site+ variable.
#
# class Person < ActiveResource::Base
@@ -123,6 +125,19 @@ module ActiveResource
# Note: Some values cannot be provided in the URL passed to site. e.g. email addresses
# as usernames. In those situations you should use the separate user and password option.
#
+ # === Certificate Authentication
+ #
+ # * End point uses an X509 certificate for authentication. <tt>See ssl_options=</tt> for all options.
+ #
+ # class Person < ActiveResource::Base
+ # self.site = "https://secure.api.people.com/"
+ # self.ssl_options = {:cert => OpenSSL::X509::Certificate.new(File.open(pem_file))
+ # :key => OpenSSL::PKey::RSA.new(File.open(pem_file)),
+ # :ca_path => "/path/to/OpenSSL/formatted/CA_Certs",
+ # :verify_mode => OpenSSL::SSL::VERIFY_PEER}
+ # end
+ #
+ #
# == Errors & Validation
#
# Error handling and validation is handled in much the same manner as you're used to seeing in
@@ -149,6 +164,7 @@ module ActiveResource
# * 404 - ActiveResource::ResourceNotFound
# * 405 - ActiveResource::MethodNotAllowed
# * 409 - ActiveResource::ResourceConflict
+ # * 410 - ActiveResource::ResourceGone
# * 422 - ActiveResource::ResourceInvalid (rescued by save as validation errors)
# * 401..499 - ActiveResource::ClientError
# * 500..599 - ActiveResource::ServerError
@@ -169,7 +185,7 @@ module ActiveResource
#
# Active Resource supports validations on resources and will return errors if any of these validations fail
# (e.g., "First name can not be blank" and so on). These types of errors are denoted in the response by
- # a response code of <tt>422</tt> and an XML representation of the validation errors. The save operation will
+ # a response code of <tt>422</tt> and an XML or JSON representation of the validation errors. The save operation will
# then fail (with a <tt>false</tt> return value) and the validation errors can be accessed on the resource in question.
#
# ryan = Person.find(1)
@@ -178,10 +194,14 @@ module ActiveResource
#
# # When
# # PUT http://api.people.com:3000/people/1.xml
+ # # or
+ # # PUT http://api.people.com:3000/people/1.json
# # is requested with invalid values, the response is:
# #
# # Response (422):
# # <errors type="array"><error>First cannot be empty</error></errors>
+ # # or
+ # # {"errors":["First cannot be empty"]}
# #
#
# ryan.errors.invalid?(:first) # => true
@@ -257,6 +277,22 @@ module ActiveResource
end
end
+ # Gets the \proxy variable if a proxy is required
+ def proxy
+ # Not using superclass_delegating_reader. See +site+ for explanation
+ if defined?(@proxy)
+ @proxy
+ elsif superclass != Object && superclass.proxy
+ superclass.proxy.dup.freeze
+ end
+ end
+
+ # Sets the URI of the http proxy to the value in the +proxy+ argument.
+ def proxy=(proxy)
+ @connection = nil
+ @proxy = proxy.nil? ? nil : create_proxy_uri_from(proxy)
+ end
+
# Gets the \user for REST HTTP authentication.
def user
# Not using superclass_delegating_reader. See +site+ for explanation
@@ -326,15 +362,42 @@ module ActiveResource
end
end
+ # Options that will get applied to an SSL connection.
+ #
+ # * <tt>:key</tt> - An OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object.
+ # * <tt>:cert</tt> - An OpenSSL::X509::Certificate object as client certificate
+ # * <tt>:ca_file</tt> - Path to a CA certification file in PEM format. The file can contrain several CA certificates.
+ # * <tt>:ca_path</tt> - Path of a CA certification directory containing certifications in PEM format.
+ # * <tt>:verify_mode</tt> - Flags for server the certification verification at begining of SSL/TLS session. (OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER is acceptable)
+ # * <tt>:verify_callback</tt> - The verify callback for the server certification verification.
+ # * <tt>:verify_depth</tt> - The maximum depth for the certificate chain verification.
+ # * <tt>:cert_store</tt> - OpenSSL::X509::Store to verify peer certificate.
+ # * <tt>:ssl_timeout</tt> -The SSL timeout in seconds.
+ def ssl_options=(opts={})
+ @connection = nil
+ @ssl_options = opts
+ end
+
+ # Returns the SSL options hash.
+ def ssl_options
+ if defined?(@ssl_options)
+ @ssl_options
+ elsif superclass != Object && superclass.ssl_options
+ superclass.ssl_options
+ end
+ end
+
# An instance of ActiveResource::Connection that is the base \connection to the remote service.
# The +refresh+ parameter toggles whether or not the \connection is refreshed at every request
# or not (defaults to <tt>false</tt>).
def connection(refresh = false)
if defined?(@connection) || superclass == Object
@connection = Connection.new(site, format) if refresh || @connection.nil?
+ @connection.proxy = proxy if proxy
@connection.user = user if user
@connection.password = password if password
@connection.timeout = timeout if timeout
+ @connection.ssl_options = ssl_options if ssl_options
@connection
else
superclass.connection
@@ -568,7 +631,7 @@ module ActiveResource
response.code.to_i == 200
end
# id && !find_single(id, options).nil?
- rescue ActiveResource::ResourceNotFound
+ rescue ActiveResource::ResourceNotFound, ActiveResource::ResourceGone
false
end
@@ -622,6 +685,11 @@ module ActiveResource
site.is_a?(URI) ? site.dup : URI.parse(site)
end
+ # Accepts a URI and creates the proxy URI from that.
+ def create_proxy_uri_from(proxy)
+ proxy.is_a?(URI) ? proxy.dup : URI.parse(proxy)
+ end
+
# contains a set of the current prefix parameters.
def prefix_parameters
@prefix_parameters ||= prefix_source.scan(/:\w+/).map { |key| key[1..-1].to_sym }.to_set
@@ -956,7 +1024,13 @@ module ActiveResource
case value
when Array
resource = find_or_create_resource_for_collection(key)
- value.map { |attrs| attrs.is_a?(String) ? attrs.dup : resource.new(attrs) }
+ value.map do |attrs|
+ if attrs.is_a?(String) || attrs.is_a?(Numeric)
+ attrs.duplicable? ? attrs.dup : attrs
+ else
+ resource.new(attrs)
+ end
+ end
when Hash
resource = find_or_create_resource_for(key)
resource.new(value)
diff --git a/activeresource/lib/active_resource/connection.rb b/activeresource/lib/active_resource/connection.rb
index 99d4b8f2ca..9d551f04e7 100644
--- a/activeresource/lib/active_resource/connection.rb
+++ b/activeresource/lib/active_resource/connection.rb
@@ -13,10 +13,11 @@ module ActiveResource
HTTP_FORMAT_HEADER_NAMES = { :get => 'Accept',
:put => 'Content-Type',
:post => 'Content-Type',
- :delete => 'Accept'
+ :delete => 'Accept',
+ :head => 'Accept'
}
- attr_reader :site, :user, :password, :timeout
+ attr_reader :site, :user, :password, :timeout, :proxy, :ssl_options
attr_accessor :format
class << self
@@ -41,6 +42,11 @@ module ActiveResource
@password = URI.decode(@site.password) if @site.password
end
+ # Set the proxy for remote service.
+ def proxy=(proxy)
+ @proxy = proxy.is_a?(URI) ? proxy : URI.parse(proxy)
+ end
+
# Sets the user for remote service.
def user=(user)
@user = user
@@ -56,6 +62,11 @@ module ActiveResource
@timeout = timeout
end
+ # Hash of options applied to Net::HTTP instance when +site+ protocol is 'https'.
+ def ssl_options=(opts={})
+ @ssl_options = opts
+ end
+
# Executes a GET request.
# Used to get (find) resources.
def get(path, headers = {})
@@ -83,7 +94,7 @@ module ActiveResource
# Executes a HEAD request.
# Used to obtain meta-information about resources, such as whether they exist and their size (via response headers).
def head(path, headers = {})
- request(:head, path, build_request_headers(headers))
+ request(:head, path, build_request_headers(headers, :head))
end
@@ -97,6 +108,8 @@ module ActiveResource
handle_response(result)
rescue Timeout::Error => e
raise TimeoutError.new(e.message)
+ rescue OpenSSL::SSL::SSLError => e
+ raise SSLError.new(e.message)
end
# Handles response and error codes from the remote service.
@@ -118,6 +131,8 @@ module ActiveResource
raise(MethodNotAllowed.new(response))
when 409
raise(ResourceConflict.new(response))
+ when 410
+ raise(ResourceGone.new(response))
when 422
raise(ResourceInvalid.new(response))
when 401...500
@@ -132,10 +147,49 @@ module ActiveResource
# Creates new Net::HTTP instance for communication with the
# remote service and resources.
def http
- http = Net::HTTP.new(@site.host, @site.port)
- http.use_ssl = @site.is_a?(URI::HTTPS)
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl?
- http.read_timeout = @timeout if @timeout # If timeout is not set, the default Net::HTTP timeout (60s) is used.
+ configure_http(new_http)
+ end
+
+ def new_http
+ if @proxy
+ Net::HTTP.new(@site.host, @site.port, @proxy.host, @proxy.port, @proxy.user, @proxy.password)
+ else
+ Net::HTTP.new(@site.host, @site.port)
+ end
+ end
+
+ def configure_http(http)
+ http = apply_ssl_options(http)
+
+ # Net::HTTP timeouts default to 60 seconds.
+ if @timeout
+ http.open_timeout = @timeout
+ http.read_timeout = @timeout
+ end
+
+ http
+ end
+
+ def apply_ssl_options(http)
+ return http unless @site.is_a?(URI::HTTPS)
+
+ http.use_ssl = true
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ return http unless defined?(@ssl_options)
+
+ http.ca_path = @ssl_options[:ca_path] if @ssl_options[:ca_path]
+ http.ca_file = @ssl_options[:ca_file] if @ssl_options[:ca_file]
+
+ http.cert = @ssl_options[:cert] if @ssl_options[:cert]
+ http.key = @ssl_options[:key] if @ssl_options[:key]
+
+ http.cert_store = @ssl_options[:cert_store] if @ssl_options[:cert_store]
+ http.ssl_timeout = @ssl_options[:ssl_timeout] if @ssl_options[:ssl_timeout]
+
+ http.verify_mode = @ssl_options[:verify_mode] if @ssl_options[:verify_mode]
+ http.verify_callback = @ssl_options[:verify_callback] if @ssl_options[:verify_callback]
+ http.verify_depth = @ssl_options[:verify_depth] if @ssl_options[:verify_depth]
+
http
end
diff --git a/activeresource/lib/active_resource/exceptions.rb b/activeresource/lib/active_resource/exceptions.rb
index 5e4b1d4487..0631cdcf9f 100644
--- a/activeresource/lib/active_resource/exceptions.rb
+++ b/activeresource/lib/active_resource/exceptions.rb
@@ -20,6 +20,14 @@ module ActiveResource
def to_s; @message ;end
end
+ # Raised when a OpenSSL::SSL::SSLError occurs.
+ class SSLError < ConnectionError
+ def initialize(message)
+ @message = message
+ end
+ def to_s; @message ;end
+ end
+
# 3xx Redirection
class Redirection < ConnectionError # :nodoc:
def to_s; response['Location'] ? "#{super} => #{response['Location']}" : super; end
@@ -43,6 +51,9 @@ module ActiveResource
# 409 Conflict
class ResourceConflict < ClientError; end # :nodoc:
+ # 410 Gone
+ class ResourceGone < ClientError; end # :nodoc:
+
# 5xx Server Error
class ServerError < ConnectionError; end # :nodoc:
diff --git a/activeresource/lib/active_resource/validations.rb b/activeresource/lib/active_resource/validations.rb
index a2ba224998..4ff7be6a9e 100644
--- a/activeresource/lib/active_resource/validations.rb
+++ b/activeresource/lib/active_resource/validations.rb
@@ -7,11 +7,10 @@ module ActiveResource
# Active Resource validation is reported to and from this object, which is used by Base#save
# to determine whether the object in a valid state to be saved. See usage example in Validations.
class Errors < ActiveModel::Errors
- # Grabs errors from the XML response.
- def from_xml(xml)
+ # Grabs errors from an array of messages (like ActiveRecord::Validations)
+ def from_array(messages)
clear
humanized_attributes = @base.attributes.keys.inject({}) { |h, attr_name| h.update(attr_name.humanize => attr_name) }
- messages = Array.wrap(Hash.from_xml(xml)['errors']['error']) rescue []
messages.each do |message|
attr_message = humanized_attributes.keys.detect do |attr_name|
if message[0, attr_name.size + 1] == "#{attr_name} "
@@ -22,6 +21,18 @@ module ActiveResource
self[:base] << message if attr_message.nil?
end
end
+
+ # Grabs errors from the json response.
+ def from_json(json)
+ array = ActiveSupport::JSON.decode(json)['errors'] rescue []
+ from_array array
+ end
+
+ # Grabs errors from the XML response.
+ def from_xml(xml)
+ array = Array.wrap(Hash.from_xml(xml)['errors']['error']) rescue []
+ from_array array
+ end
end
# Module to support validation and errors with Active Resource objects. The module overrides
@@ -56,7 +67,12 @@ module ActiveResource
save_without_validation
true
rescue ResourceInvalid => error
- errors.from_xml(error.response.body)
+ case error.response['Content-Type']
+ when 'application/xml'
+ errors.from_xml(error.response.body)
+ when 'application/json'
+ errors.from_json(error.response.body)
+ end
false
end
diff --git a/activeresource/test/base/load_test.rb b/activeresource/test/base/load_test.rb
index 035bd965c2..5f5a580445 100644
--- a/activeresource/test/base/load_test.rb
+++ b/activeresource/test/base/load_test.rb
@@ -51,7 +51,9 @@ class BaseLoadTest < Test::Unit::TestCase
:id => 1, :state => { :id => 1, :name => 'Oregon',
:notable_rivers => [
{ :id => 1, :name => 'Willamette' },
- { :id => 2, :name => 'Columbia', :rafted_by => @matz }] }}}
+ { :id => 2, :name => 'Columbia', :rafted_by => @matz }],
+ :postal_codes => [97018,1234567890],
+ :places => ["Columbia City", "Unknown"]}}}
@person = Person.new
end
@@ -127,6 +129,19 @@ class BaseLoadTest < Test::Unit::TestCase
assert_kind_of Person::Street::State::NotableRiver, rivers.first
assert_equal @deep[:street][:state][:notable_rivers].first[:id], rivers.first.id
assert_equal @matz[:id], rivers.last.rafted_by.id
+
+ postal_codes = state.postal_codes
+ assert_kind_of Array, postal_codes
+ assert_equal 2, postal_codes.size
+ assert_kind_of Fixnum, postal_codes.first
+ assert_equal @deep[:street][:state][:postal_codes].first, postal_codes.first
+ assert_kind_of Bignum, postal_codes.last
+ assert_equal @deep[:street][:state][:postal_codes].last, postal_codes.last
+
+ places = state.places
+ assert_kind_of Array, places
+ assert_kind_of String, places.first
+ assert_equal @deep[:street][:state][:places].first, places.first
end
def test_nested_collections_within_the_same_namespace
diff --git a/activeresource/test/base_errors_test.rb b/activeresource/test/base_errors_test.rb
index 28813821df..eca00e9ca8 100644
--- a/activeresource/test/base_errors_test.rb
+++ b/activeresource/test/base_errors_test.rb
@@ -4,45 +4,80 @@ require "fixtures/person"
class BaseErrorsTest < Test::Unit::TestCase
def setup
ActiveResource::HttpMock.respond_to do |mock|
- mock.post "/people.xml", {}, "<?xml version=\"1.0\" encoding=\"UTF-8\"?><errors><error>Age can't be blank</error><error>Name can't be blank</error><error>Name must start with a letter</error><error>Person quota full for today.</error></errors>", 422
+ mock.post "/people.xml", {}, %q(<?xml version="1.0" encoding="UTF-8"?><errors><error>Age can't be blank</error><error>Name can't be blank</error><error>Name must start with a letter</error><error>Person quota full for today.</error></errors>), 422, {'Content-Type' => 'application/xml'}
+ mock.post "/people.json", {}, %q({"errors":["Age can't be blank","Name can't be blank","Name must start with a letter","Person quota full for today."]}), 422, {'Content-Type' => 'application/json'}
end
- @person = Person.new(:name => '', :age => '')
- assert_equal @person.save, false
end
def test_should_mark_as_invalid
- assert !@person.valid?
+ [ :json, :xml ].each do |format|
+ invalid_user_using_format(format) do
+ assert !@person.valid?
+ end
+ end
end
def test_should_parse_xml_errors
- assert_kind_of ActiveResource::Errors, @person.errors
- assert_equal 4, @person.errors.size
+ [ :json, :xml ].each do |format|
+ invalid_user_using_format(format) do
+ assert_kind_of ActiveResource::Errors, @person.errors
+ assert_equal 4, @person.errors.size
+ end
+ end
end
def test_should_parse_errors_to_individual_attributes
- assert @person.errors[:name].any?
- assert_equal ["can't be blank"], @person.errors[:age]
- assert_equal ["can't be blank", "must start with a letter"], @person.errors[:name]
- assert_equal ["Person quota full for today."], @person.errors[:base]
+ [ :json, :xml ].each do |format|
+ invalid_user_using_format(format) do
+ assert @person.errors[:name].any?
+ assert_equal ["can't be blank"], @person.errors[:age]
+ assert_equal ["can't be blank", "must start with a letter"], @person.errors[:name]
+ assert_equal ["Person quota full for today."], @person.errors[:base]
+ end
+ end
end
def test_should_iterate_over_errors
- errors = []
- @person.errors.each { |attribute, message| errors << [attribute.to_s, message] }
- assert errors.include?(["name", "can't be blank"])
+ [ :json, :xml ].each do |format|
+ invalid_user_using_format(format) do
+ errors = []
+ @person.errors.each { |attribute, message| errors << [attribute, message] }
+ assert errors.include?([:name, "can't be blank"])
+ end
+ end
end
def test_should_iterate_over_full_errors
- errors = []
- @person.errors.to_a.each { |message| errors << message }
- assert errors.include?("Name can't be blank")
+ [ :json, :xml ].each do |format|
+ invalid_user_using_format(format) do
+ errors = []
+ @person.errors.to_a.each { |message| errors << message }
+ assert errors.include?("Name can't be blank")
+ end
+ end
end
def test_should_format_full_errors
- full = @person.errors.full_messages
- assert full.include?("Age can't be blank")
- assert full.include?("Name can't be blank")
- assert full.include?("Name must start with a letter")
- assert full.include?("Person quota full for today.")
+ [ :json, :xml ].each do |format|
+ invalid_user_using_format(format) do
+ full = @person.errors.full_messages
+ assert full.include?("Age can't be blank")
+ assert full.include?("Name can't be blank")
+ assert full.include?("Name must start with a letter")
+ assert full.include?("Person quota full for today.")
+ end
+ end
+ end
+
+ private
+ def invalid_user_using_format(mime_type_reference)
+ previous_format = Person.format
+ Person.format = mime_type_reference
+ @person = Person.new(:name => '', :age => '')
+ assert_equal false, @person.save
+
+ yield
+ ensure
+ Person.format = previous_format
end
end
diff --git a/activeresource/test/base_test.rb b/activeresource/test/base_test.rb
index 82d3b2ae96..9c236bc893 100644
--- a/activeresource/test/base_test.rb
+++ b/activeresource/test/base_test.rb
@@ -3,6 +3,7 @@ require "fixtures/person"
require "fixtures/customer"
require "fixtures/street_address"
require "fixtures/beast"
+require "fixtures/proxy"
require 'active_support/core_ext/hash/conversions'
class BaseTest < Test::Unit::TestCase
@@ -125,6 +126,28 @@ class BaseTest < Test::Unit::TestCase
assert_nil actor.site
end
+ def test_proxy_accessor_accepts_uri_or_string_argument
+ proxy = URI.parse('http://localhost')
+
+ assert_nothing_raised { Person.proxy = 'http://localhost' }
+ assert_equal proxy, Person.proxy
+
+ assert_nothing_raised { Person.proxy = proxy }
+ assert_equal proxy, Person.proxy
+ end
+
+ def test_should_use_proxy_prefix_and_credentials
+ assert_equal 'http://user:password@proxy.local:3000', ProxyResource.proxy.to_s
+ end
+
+ def test_proxy_variable_can_be_reset
+ actor = Class.new(ActiveResource::Base)
+ assert_nil actor.site
+ actor.proxy = 'http://localhost:31337'
+ actor.proxy = nil
+ assert_nil actor.site
+ end
+
def test_should_accept_setting_user
Forum.user = 'david'
assert_equal('david', Forum.user)
@@ -143,6 +166,13 @@ class BaseTest < Test::Unit::TestCase
assert_equal(5, Forum.connection.timeout)
end
+ def test_should_accept_setting_ssl_options
+ expected = {:verify => 1}
+ Forum.ssl_options= expected
+ assert_equal(expected, Forum.ssl_options)
+ assert_equal(expected, Forum.connection.ssl_options)
+ end
+
def test_user_variable_can_be_reset
actor = Class.new(ActiveResource::Base)
actor.site = 'http://cinema'
@@ -173,6 +203,16 @@ class BaseTest < Test::Unit::TestCase
assert_nil actor.connection.timeout
end
+ def test_ssl_options_hash_can_be_reset
+ actor = Class.new(ActiveResource::Base)
+ actor.site = 'https://cinema'
+ assert_nil actor.ssl_options
+ actor.ssl_options = {:foo => 5}
+ actor.ssl_options = nil
+ assert_nil actor.ssl_options
+ assert_nil actor.connection.ssl_options
+ end
+
def test_credentials_from_site_are_decoded
actor = Class.new(ActiveResource::Base)
actor.site = 'http://my%40email.com:%31%32%33@cinema'
@@ -221,6 +261,47 @@ class BaseTest < Test::Unit::TestCase
assert_equal fruit.site, apple.site, 'subclass did not adopt changes from parent class'
end
+ def test_proxy_reader_uses_superclass_site_until_written
+ # Superclass is Object so returns nil.
+ assert_nil ActiveResource::Base.proxy
+ assert_nil Class.new(ActiveResource::Base).proxy
+
+ # Subclass uses superclass proxy.
+ actor = Class.new(Person)
+ assert_equal Person.proxy, actor.proxy
+
+ # Subclass returns frozen superclass copy.
+ assert !Person.proxy.frozen?
+ assert actor.proxy.frozen?
+
+ # Changing subclass proxy doesn't change superclass site.
+ actor.proxy = 'http://localhost:31337'
+ assert_not_equal Person.proxy, actor.proxy
+
+ # Changed subclass proxy is not frozen.
+ assert !actor.proxy.frozen?
+
+ # Changing superclass proxy doesn't overwrite subclass site.
+ Person.proxy = 'http://somewhere.else'
+ assert_not_equal Person.proxy, actor.proxy
+
+ # Changing superclass proxy after subclassing changes subclass site.
+ jester = Class.new(actor)
+ actor.proxy = 'http://nomad'
+ assert_equal actor.proxy, jester.proxy
+ assert jester.proxy.frozen?
+
+ # Subclasses are always equal to superclass proxy when not overridden
+ fruit = Class.new(ActiveResource::Base)
+ apple = Class.new(fruit)
+
+ fruit.proxy = 'http://market'
+ assert_equal fruit.proxy, apple.proxy, 'subclass did not adopt changes from parent class'
+
+ fruit.proxy = 'http://supermarket'
+ assert_equal fruit.proxy, apple.proxy, 'subclass did not adopt changes from parent class'
+ end
+
def test_user_reader_uses_superclass_user_until_written
# Superclass is Object so returns nil.
assert_nil ActiveResource::Base.user
@@ -331,6 +412,40 @@ class BaseTest < Test::Unit::TestCase
assert_equal fruit.timeout, apple.timeout, 'subclass did not adopt changes from parent class'
end
+ def test_ssl_options_reader_uses_superclass_ssl_options_until_written
+ # Superclass is Object so returns nil.
+ assert_nil ActiveResource::Base.ssl_options
+ assert_nil Class.new(ActiveResource::Base).ssl_options
+ Person.ssl_options = {:foo => 'bar'}
+
+ # Subclass uses superclass ssl_options.
+ actor = Class.new(Person)
+ assert_equal Person.ssl_options, actor.ssl_options
+
+ # Changing subclass ssl_options doesn't change superclass ssl_options.
+ actor.ssl_options = {:baz => ''}
+ assert_not_equal Person.ssl_options, actor.ssl_options
+
+ # Changing superclass ssl_options doesn't overwrite subclass ssl_options.
+ Person.ssl_options = {:color => 'blue'}
+ assert_not_equal Person.ssl_options, actor.ssl_options
+
+ # Changing superclass ssl_options after subclassing changes subclass ssl_options.
+ jester = Class.new(actor)
+ actor.ssl_options = {:color => 'red'}
+ assert_equal actor.ssl_options, jester.ssl_options
+
+ # Subclasses are always equal to superclass ssl_options when not overridden.
+ fruit = Class.new(ActiveResource::Base)
+ apple = Class.new(fruit)
+
+ fruit.ssl_options = {:alpha => 'betas'}
+ assert_equal fruit.ssl_options, apple.ssl_options, 'subclass did not adopt changes from parent class'
+
+ fruit.ssl_options = {:omega => 'moos'}
+ assert_equal fruit.ssl_options, apple.ssl_options, 'subclass did not adopt changes from parent class'
+ end
+
def test_updating_baseclass_site_object_wipes_descendent_cached_connection_objects
# Subclasses are always equal to superclass site when not overridden
fruit = Class.new(ActiveResource::Base)
@@ -784,6 +899,14 @@ class BaseTest < Test::Unit::TestCase
assert_raise(ActiveResource::ResourceNotFound) { StreetAddress.find(1, :params => { :person_id => 1 }) }
end
+ def test_destroy_with_410_gone
+ assert Person.find(1).destroy
+ ActiveResource::HttpMock.respond_to do |mock|
+ mock.get "/people/1.xml", {}, nil, 410
+ end
+ assert_raise(ActiveResource::ResourceGone) { Person.find(1).destroy }
+ end
+
def test_delete
assert Person.delete(1)
ActiveResource::HttpMock.respond_to do |mock|
@@ -799,6 +922,14 @@ class BaseTest < Test::Unit::TestCase
end
assert_raise(ActiveResource::ResourceNotFound) { StreetAddress.find(1, :params => { :person_id => 1 }) }
end
+
+ def test_delete_with_410_gone
+ assert Person.delete(1)
+ ActiveResource::HttpMock.respond_to do |mock|
+ mock.get "/people/1.xml", {}, nil, 410
+ end
+ assert_raise(ActiveResource::ResourceGone) { Person.find(1) }
+ end
def test_exists
# Class method.
@@ -851,6 +982,22 @@ class BaseTest < Test::Unit::TestCase
end
end
+ def test_exists_without_http_mock
+ http = Net::HTTP.new(Person.site.host, Person.site.port)
+ ActiveResource::Connection.any_instance.expects(:http).returns(http)
+ http.expects(:request).returns(ActiveResource::Response.new(""))
+
+ assert Person.exists?('not-mocked')
+ end
+
+ def test_exists_with_410_gone
+ ActiveResource::HttpMock.respond_to do |mock|
+ mock.head "/people/1.xml", {}, nil, 410
+ end
+
+ assert !Person.exists?(1)
+ end
+
def test_to_xml
matz = Person.find(1)
xml = matz.encode
diff --git a/activeresource/test/connection_test.rb b/activeresource/test/connection_test.rb
index 831fbc4003..d7466c65b4 100644
--- a/activeresource/test/connection_test.rb
+++ b/activeresource/test/connection_test.rb
@@ -56,6 +56,9 @@ class ConnectionTest < Test::Unit::TestCase
# 409 is an optimistic locking error
assert_response_raises ActiveResource::ResourceConflict, 409
+ # 410 is a removed resource
+ assert_response_raises ActiveResource::ResourceGone, 410
+
# 422 is a validation error
assert_response_raises ActiveResource::ResourceInvalid, 422
@@ -101,6 +104,16 @@ class ConnectionTest < Test::Unit::TestCase
assert_equal site, @conn.site
end
+ def test_proxy_accessor_accepts_uri_or_string_argument
+ proxy = URI.parse("http://proxy_user:proxy_password@proxy.local:4242")
+
+ assert_nothing_raised { @conn.proxy = "http://proxy_user:proxy_password@proxy.local:4242" }
+ assert_equal proxy, @conn.proxy
+
+ assert_nothing_raised { @conn.proxy = proxy }
+ assert_equal proxy, @conn.proxy
+ end
+
def test_timeout_accessor
@conn.timeout = 5
assert_equal 5, @conn.timeout
@@ -175,6 +188,17 @@ class ConnectionTest < Test::Unit::TestCase
assert_raise(ActiveResource::TimeoutError) { @conn.get('/people_timeout.xml') }
end
+ def test_setting_timeout
+ http = Net::HTTP.new('')
+
+ [10, 20].each do |timeout|
+ @conn.timeout = timeout
+ @conn.send(:configure_http, http)
+ assert_equal timeout, http.open_timeout
+ assert_equal timeout, http.read_timeout
+ end
+ end
+
def test_accept_http_header
@http = mock('new Net::HTTP')
@conn.expects(:http).returns(@http)
@@ -183,6 +207,24 @@ class ConnectionTest < Test::Unit::TestCase
assert_nothing_raised(Mocha::ExpectationError) { @conn.get(path, {'Accept' => 'application/xhtml+xml'}) }
end
+ def test_ssl_options_get_applied_to_http
+ http = Net::HTTP.new('')
+ @conn.site="https://secure"
+ @conn.ssl_options={:verify_mode => OpenSSL::SSL::VERIFY_PEER}
+ @conn.timeout = 10 # prevent warning about uninitialized.
+ @conn.send(:configure_http, http)
+
+ assert http.use_ssl?
+ assert_equal http.verify_mode, OpenSSL::SSL::VERIFY_PEER
+ end
+
+ def test_ssl_error
+ http = Net::HTTP.new('')
+ @conn.expects(:http).returns(http)
+ http.expects(:get).raises(OpenSSL::SSL::SSLError, 'Expired certificate')
+ assert_raise(ActiveResource::SSLError) { @conn.get('/people/1.xml') }
+ end
+
protected
def assert_response_raises(klass, code)
assert_raise(klass, "Expected response code #{code} to raise #{klass}") do
diff --git a/activeresource/test/fixtures/proxy.rb b/activeresource/test/fixtures/proxy.rb
new file mode 100644
index 0000000000..bb8e015df0
--- /dev/null
+++ b/activeresource/test/fixtures/proxy.rb
@@ -0,0 +1,4 @@
+class ProxyResource < ActiveResource::Base
+ self.site = "http://localhost"
+ self.proxy = "http://user:password@proxy.local:3000"
+end \ No newline at end of file