aboutsummaryrefslogtreecommitdiffstats
path: root/activeresource
diff options
context:
space:
mode:
authorPratik Naik <pratiknaik@gmail.com>2009-08-31 22:11:50 +0100
committerPratik Naik <pratiknaik@gmail.com>2009-08-31 22:11:50 +0100
commitbae00bb1cc392e1cf408369809b9cf85468bef42 (patch)
tree17103af6eeb5de96c72beda1debce28950cc7fea /activeresource
parent93c76b2fb08668bc4b8364cc8051476e6d1d15ba (diff)
parentffd2cf167040b60c26d97c01598560c87bd4b2d3 (diff)
downloadrails-bae00bb1cc392e1cf408369809b9cf85468bef42.tar.gz
rails-bae00bb1cc392e1cf408369809b9cf85468bef42.tar.bz2
rails-bae00bb1cc392e1cf408369809b9cf85468bef42.zip
Merge commit 'mainstream/master'
Diffstat (limited to 'activeresource')
-rw-r--r--activeresource/CHANGELOG6
-rw-r--r--activeresource/lib/active_resource/base.rb136
-rw-r--r--activeresource/lib/active_resource/connection.rb42
-rw-r--r--activeresource/lib/active_resource/exceptions.rb11
-rw-r--r--activeresource/lib/active_resource/validations.rb66
-rw-r--r--activeresource/test/base_errors_test.rb48
-rw-r--r--activeresource/test/cases/authorization_test.rb (renamed from activeresource/test/authorization_test.rb)0
-rw-r--r--activeresource/test/cases/base/custom_methods_test.rb (renamed from activeresource/test/base/custom_methods_test.rb)0
-rw-r--r--activeresource/test/cases/base/equality_test.rb (renamed from activeresource/test/base/equality_test.rb)0
-rw-r--r--activeresource/test/cases/base/load_test.rb (renamed from activeresource/test/base/load_test.rb)17
-rw-r--r--activeresource/test/cases/base_errors_test.rb83
-rw-r--r--activeresource/test/cases/base_test.rb (renamed from activeresource/test/base_test.rb)165
-rw-r--r--activeresource/test/cases/finder_test.rb233
-rw-r--r--activeresource/test/cases/format_test.rb (renamed from activeresource/test/format_test.rb)0
-rw-r--r--activeresource/test/cases/observing_test.rb (renamed from activeresource/test/observing_test.rb)0
-rw-r--r--activeresource/test/cases/validations_test.rb55
-rw-r--r--activeresource/test/connection_test.rb21
-rw-r--r--activeresource/test/fixtures/project.rb25
18 files changed, 757 insertions, 151 deletions
diff --git a/activeresource/CHANGELOG b/activeresource/CHANGELOG
index 1f9ecea6c5..113694e895 100644
--- a/activeresource/CHANGELOG
+++ b/activeresource/CHANGELOG
@@ -1,5 +1,11 @@
*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]
diff --git a/activeresource/lib/active_resource/base.rb b/activeresource/lib/active_resource/base.rb
index 3d58660ffc..e1f221bd3e 100644
--- a/activeresource/lib/active_resource/base.rb
+++ b/activeresource/lib/active_resource/base.rb
@@ -110,6 +110,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
@@ -130,6 +132,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
@@ -156,6 +171,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
@@ -176,7 +192,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)
@@ -185,10 +201,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
@@ -349,6 +369,31 @@ 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>).
@@ -359,6 +404,7 @@ module ActiveResource
@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
@@ -546,6 +592,19 @@ module ActiveResource
#
# StreetAddress.find(1, :params => { :person_id => 1 })
# # => GET /people/1/street_addresses/1.xml
+ #
+ # == Failure or missing data
+ # A failure to find the requested object raises a ResourceNotFound
+ # exception if the find was called with an id.
+ # With any other scope, find returns nil when no data is returned.
+ #
+ # Person.find(1)
+ # # => raises ResourcenotFound
+ #
+ # Person.find(:all)
+ # Person.find(:first)
+ # Person.find(:last)
+ # # => nil
def find(*arguments)
scope = arguments.slice!(0)
options = arguments.slice!(0) || {}
@@ -559,6 +618,28 @@ module ActiveResource
end
end
+
+ # A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass
+ # in all the same arguments to this method as you can to
+ # <tt>find(:first)</tt>.
+ def first(*args)
+ find(:first, *args)
+ end
+
+ # A convenience wrapper for <tt>find(:last, *args)</tt>. You can pass
+ # in all the same arguments to this method as you can to
+ # <tt>find(:last)</tt>.
+ def last(*args)
+ find(:last, *args)
+ end
+
+ # This is an alias for find(:all). You can pass in all the same
+ # arguments to this method as you can to <tt>find(:all)</tt>
+ def all(*args)
+ find(:all, *args)
+ end
+
+
# Deletes the resources with the ID in the +id+ parameter.
#
# ==== Options
@@ -592,23 +673,29 @@ module ActiveResource
response.code.to_i == 200
end
# id && !find_single(id, options).nil?
- rescue ActiveResource::ResourceNotFound
+ rescue ActiveResource::ResourceNotFound, ActiveResource::ResourceGone
false
end
private
# Find every resource
def find_every(options)
- case from = options[:from]
- when Symbol
- instantiate_collection(get(from, options[:params]))
- when String
- path = "#{from}#{query_string(options[:params])}"
- instantiate_collection(connection.get(path, headers) || [])
- else
- prefix_options, query_options = split_options(options[:params])
- path = collection_path(prefix_options, query_options)
- instantiate_collection( (connection.get(path, headers) || []), prefix_options )
+ begin
+ case from = options[:from]
+ when Symbol
+ instantiate_collection(get(from, options[:params]))
+ when String
+ path = "#{from}#{query_string(options[:params])}"
+ instantiate_collection(connection.get(path, headers) || [])
+ else
+ prefix_options, query_options = split_options(options[:params])
+ path = collection_path(prefix_options, query_options)
+ instantiate_collection( (connection.get(path, headers) || []), prefix_options )
+ end
+ rescue ActiveResource::ResourceNotFound
+ # Swallowing ResourceNotFound exceptions and return nil - as per
+ # ActiveRecord.
+ nil
end
end
@@ -835,6 +922,23 @@ module ActiveResource
def save
new? ? create : update
end
+
+ # Saves the resource.
+ #
+ # If the resource is new, it is created via +POST+, otherwise the
+ # existing resource is updated via +PUT+.
+ #
+ # With <tt>save!</tt> validations always run. If any of them fail
+ # ActiveResource::ResourceInvalid gets raised, and nothing is POSTed to
+ # the remote system.
+ # See ActiveResource::Validations for more information.
+ #
+ # There's a series of callbacks associated with <tt>save!</tt>. If any
+ # of the <tt>before_*</tt> callbacks return +false+ the action is
+ # cancelled and <tt>save!</tt> raises ActiveResource::ResourceInvalid.
+ def save!
+ save || raise(ResourceInvalid.new(self))
+ end
# Deletes the resource from the remote service.
#
@@ -985,7 +1089,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 ef57c1f8b2..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, :proxy
+ attr_reader :site, :user, :password, :timeout, :proxy, :ssl_options
attr_accessor :format
class << self
@@ -61,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 = {})
@@ -88,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
@@ -102,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.
@@ -123,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
@@ -149,8 +159,7 @@ module ActiveResource
end
def configure_http(http)
- http.use_ssl = @site.is_a?(URI::HTTPS)
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl?
+ http = apply_ssl_options(http)
# Net::HTTP timeouts default to 60 seconds.
if @timeout
@@ -161,6 +170,29 @@ module ActiveResource
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
+
def default_header
@default_header ||= {}
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..d4d282e273 100644
--- a/activeresource/lib/active_resource/validations.rb
+++ b/activeresource/lib/active_resource/validations.rb
@@ -7,11 +7,12 @@ 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)
- clear
+ # Grabs errors from an array of messages (like ActiveRecord::Validations)
+ # The second parameter directs the errors cache to be cleared (default)
+ # or not (by passing true)
+ def from_array(messages, save_cache = false)
+ clear unless save_cache
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 +23,18 @@ module ActiveResource
self[:base] << message if attr_message.nil?
end
end
+
+ # Grabs errors from a json response.
+ def from_json(json, save_cache = false)
+ array = ActiveSupport::JSON.decode(json)['errors'] rescue []
+ from_array array, save_cache
+ end
+
+ # Grabs errors from an XML response.
+ def from_xml(xml, save_cache = false)
+ array = Array.wrap(Hash.from_xml(xml)['errors']['error']) rescue []
+ from_array array, save_cache
+ end
end
# Module to support validation and errors with Active Resource objects. The module overrides
@@ -46,21 +59,55 @@ module ActiveResource
#
module Validations
extend ActiveSupport::Concern
+ include ActiveModel::Validations
+ extend ActiveModel::Validations::ClassMethods
included do
alias_method_chain :save, :validation
end
# Validate a resource and save (POST) it to the remote web service.
- def save_with_validation
- save_without_validation
- true
+ # If any local validations fail - the save (POST) will not be attempted.
+ def save_with_validation(perform_validation = true)
+ # clear the remote validations so they don't interfere with the local
+ # ones. Otherwise we get an endless loop and can never change the
+ # fields so as to make the resource valid
+ @remote_errors = nil
+ if perform_validation && valid? || !perform_validation
+ save_without_validation
+ true
+ else
+ false
+ end
rescue ResourceInvalid => error
- errors.from_xml(error.response.body)
+ # cache the remote errors because every call to <tt>valid?</tt> clears
+ # all errors. We must keep a copy to add these back after local
+ # validations
+ @remote_errors = error
+ load_remote_errors(@remote_errors, true)
false
end
+
+ # Loads the set of remote errors into the object's Errors based on the
+ # content-type of the error-block received
+ def load_remote_errors(remote_errors, save_cache = false ) #:nodoc:
+ case remote_errors.response['Content-Type']
+ when 'application/xml'
+ errors.from_xml(remote_errors.response.body, save_cache)
+ when 'application/json'
+ errors.from_json(remote_errors.response.body, save_cache)
+ end
+ end
+
# Checks for errors on an object (i.e., is resource.errors empty?).
+ #
+ # Runs all the specified local validations and returns true if no errors
+ # were added, otherwise false.
+ # Runs local validations (eg those on your Active Resource model), and
+ # also any errors returned from the remote system the last time we
+ # saved.
+ # Remote errors can only be cleared by trying to re-save the resource.
#
# ==== Examples
# my_person = Person.create(params[:person])
@@ -70,7 +117,10 @@ module ActiveResource
# my_person.errors.add('login', 'can not be empty') if my_person.login == ''
# my_person.valid?
# # => false
+ #
def valid?
+ super
+ load_remote_errors(@remote_errors, true) if defined?(@remote_errors) && @remote_errors.present?
errors.empty?
end
diff --git a/activeresource/test/base_errors_test.rb b/activeresource/test/base_errors_test.rb
deleted file mode 100644
index 28813821df..0000000000
--- a/activeresource/test/base_errors_test.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-require 'abstract_unit'
-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
- end
- @person = Person.new(:name => '', :age => '')
- assert_equal @person.save, false
- end
-
- def test_should_mark_as_invalid
- assert !@person.valid?
- end
-
- def test_should_parse_xml_errors
- assert_kind_of ActiveResource::Errors, @person.errors
- assert_equal 4, @person.errors.size
- 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]
- 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"])
- 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")
- 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.")
- end
-end
diff --git a/activeresource/test/authorization_test.rb b/activeresource/test/cases/authorization_test.rb
index ca25f437e3..ca25f437e3 100644
--- a/activeresource/test/authorization_test.rb
+++ b/activeresource/test/cases/authorization_test.rb
diff --git a/activeresource/test/base/custom_methods_test.rb b/activeresource/test/cases/base/custom_methods_test.rb
index 2d81549a65..2d81549a65 100644
--- a/activeresource/test/base/custom_methods_test.rb
+++ b/activeresource/test/cases/base/custom_methods_test.rb
diff --git a/activeresource/test/base/equality_test.rb b/activeresource/test/cases/base/equality_test.rb
index 84f1a7b998..84f1a7b998 100644
--- a/activeresource/test/base/equality_test.rb
+++ b/activeresource/test/cases/base/equality_test.rb
diff --git a/activeresource/test/base/load_test.rb b/activeresource/test/cases/base/load_test.rb
index 035bd965c2..1952f5b5f0 100644
--- a/activeresource/test/base/load_test.rb
+++ b/activeresource/test/cases/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 Numeric, 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/cases/base_errors_test.rb b/activeresource/test/cases/base_errors_test.rb
new file mode 100644
index 0000000000..eca00e9ca8
--- /dev/null
+++ b/activeresource/test/cases/base_errors_test.rb
@@ -0,0 +1,83 @@
+require 'abstract_unit'
+require "fixtures/person"
+
+class BaseErrorsTest < Test::Unit::TestCase
+ def setup
+ ActiveResource::HttpMock.respond_to do |mock|
+ 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
+ end
+
+ def test_should_mark_as_invalid
+ [ :json, :xml ].each do |format|
+ invalid_user_using_format(format) do
+ assert !@person.valid?
+ end
+ end
+ end
+
+ def test_should_parse_xml_errors
+ [ :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
+ [ :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
+ [ :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
+ [ :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
+ [ :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/cases/base_test.rb
index e68d562d97..8c0217aad6 100644
--- a/activeresource/test/base_test.rb
+++ b/activeresource/test/cases/base_test.rb
@@ -166,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'
@@ -196,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'
@@ -395,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)
@@ -586,13 +637,6 @@ class BaseTest < Test::Unit::TestCase
assert_equal [:person_id].to_set, StreetAddress.__send__(:prefix_parameters)
end
- def test_find_by_id
- matz = Person.find(1)
- assert_kind_of Person, matz
- assert_equal "Matz", matz.name
- assert matz.name?
- end
-
def test_respond_to
matz = Person.find(1)
assert matz.respond_to?(:name)
@@ -601,32 +645,6 @@ class BaseTest < Test::Unit::TestCase
assert !matz.respond_to?(:super_scalable_stuff)
end
- def test_find_by_id_with_custom_prefix
- addy = StreetAddress.find(1, :params => { :person_id => 1 })
- assert_kind_of StreetAddress, addy
- assert_equal '12345 Street', addy.street
- end
-
- def test_find_all
- all = Person.find(:all)
- assert_equal 2, all.size
- assert_kind_of Person, all.first
- assert_equal "Matz", all.first.name
- assert_equal "David", all.last.name
- end
-
- def test_find_first
- matz = Person.find(:first)
- assert_kind_of Person, matz
- assert_equal "Matz", matz.name
- end
-
- def test_find_last
- david = Person.find(:last)
- assert_kind_of Person, david
- assert_equal 'David', david.name
- end
-
def test_custom_header
Person.headers['key'] = 'value'
assert_raise(ActiveResource::ResourceNotFound) { Person.find(4) }
@@ -634,52 +652,15 @@ class BaseTest < Test::Unit::TestCase
Person.headers.delete('key')
end
- def test_find_by_id_not_found
- assert_raise(ActiveResource::ResourceNotFound) { Person.find(99) }
- assert_raise(ActiveResource::ResourceNotFound) { StreetAddress.find(1) }
- end
-
- def test_find_all_by_from
- ActiveResource::HttpMock.respond_to { |m| m.get "/companies/1/people.xml", {}, @people_david }
-
- people = Person.find(:all, :from => "/companies/1/people.xml")
- assert_equal 1, people.size
- assert_equal "David", people.first.name
- end
-
- def test_find_all_by_from_with_options
- ActiveResource::HttpMock.respond_to { |m| m.get "/companies/1/people.xml", {}, @people_david }
-
- people = Person.find(:all, :from => "/companies/1/people.xml")
- assert_equal 1, people.size
- assert_equal "David", people.first.name
- end
-
- def test_find_all_by_symbol_from
- ActiveResource::HttpMock.respond_to { |m| m.get "/people/managers.xml", {}, @people_david }
-
- people = Person.find(:all, :from => :managers)
- assert_equal 1, people.size
- assert_equal "David", people.first.name
- end
-
- def test_find_single_by_from
- ActiveResource::HttpMock.respond_to { |m| m.get "/companies/1/manager.xml", {}, @david }
-
- david = Person.find(:one, :from => "/companies/1/manager.xml")
- assert_equal "David", david.name
- end
-
- def test_find_single_by_symbol_from
- ActiveResource::HttpMock.respond_to { |m| m.get "/people/leader.xml", {}, @david }
-
- david = Person.find(:one, :from => :leader)
- assert_equal "David", david.name
+ def test_save
+ rick = Person.new
+ assert rick.save
+ assert_equal '5', rick.id
end
- def test_save
+ def test_save!
rick = Person.new
- assert_equal true, rick.save
+ assert rick.save!
assert_equal '5', rick.id
end
@@ -848,6 +829,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|
@@ -863,6 +852,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.
@@ -915,6 +912,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/cases/finder_test.rb b/activeresource/test/cases/finder_test.rb
new file mode 100644
index 0000000000..535b6f4198
--- /dev/null
+++ b/activeresource/test/cases/finder_test.rb
@@ -0,0 +1,233 @@
+require 'abstract_unit'
+require "fixtures/person"
+require "fixtures/customer"
+require "fixtures/street_address"
+require "fixtures/beast"
+require "fixtures/proxy"
+require 'active_support/core_ext/hash/conversions'
+
+class FinderTest < Test::Unit::TestCase
+ def setup
+ # TODO: refactor/DRY this setup - it's a copy of the BaseTest setup.
+ # We can probably put this into abstract_unit
+ @matz = { :id => 1, :name => 'Matz' }.to_xml(:root => 'person')
+ @david = { :id => 2, :name => 'David' }.to_xml(:root => 'person')
+ @greg = { :id => 3, :name => 'Greg' }.to_xml(:root => 'person')
+ @addy = { :id => 1, :street => '12345 Street' }.to_xml(:root => 'address')
+ @default_request_headers = { 'Content-Type' => 'application/xml' }
+ @rick = { :name => "Rick", :age => 25 }.to_xml(:root => "person")
+ @people = [{ :id => 1, :name => 'Matz' }, { :id => 2, :name => 'David' }].to_xml(:root => 'people')
+ @people_david = [{ :id => 2, :name => 'David' }].to_xml(:root => 'people')
+ @addresses = [{ :id => 1, :street => '12345 Street' }].to_xml(:root => 'addresses')
+
+ # - deep nested resource -
+ # - Luis (Customer)
+ # - JK (Customer::Friend)
+ # - Mateo (Customer::Friend::Brother)
+ # - Edith (Customer::Friend::Brother::Child)
+ # - Martha (Customer::Friend::Brother::Child)
+ # - Felipe (Customer::Friend::Brother)
+ # - Bryan (Customer::Friend::Brother::Child)
+ # - Luke (Customer::Friend::Brother::Child)
+ # - Eduardo (Customer::Friend)
+ # - Sebas (Customer::Friend::Brother)
+ # - Andres (Customer::Friend::Brother::Child)
+ # - Jorge (Customer::Friend::Brother::Child)
+ # - Elsa (Customer::Friend::Brother)
+ # - Natacha (Customer::Friend::Brother::Child)
+ # - Milena (Customer::Friend::Brother)
+ #
+ @luis = {:id => 1, :name => 'Luis',
+ :friends => [{:name => 'JK',
+ :brothers => [{:name => 'Mateo',
+ :children => [{:name => 'Edith'},{:name => 'Martha'}]},
+ {:name => 'Felipe',
+ :children => [{:name => 'Bryan'},{:name => 'Luke'}]}]},
+ {:name => 'Eduardo',
+ :brothers => [{:name => 'Sebas',
+ :children => [{:name => 'Andres'},{:name => 'Jorge'}]},
+ {:name => 'Elsa',
+ :children => [{:name => 'Natacha'}]},
+ {:name => 'Milena',
+ :children => []}]}]}.to_xml(:root => 'customer')
+ # - resource with yaml array of strings; for ActiveRecords using serialize :bar, Array
+ @marty = <<-eof.strip
+ <?xml version=\"1.0\" encoding=\"UTF-8\"?>
+ <person>
+ <id type=\"integer\">5</id>
+ <name>Marty</name>
+ <colors type=\"yaml\">---
+ - \"red\"
+ - \"green\"
+ - \"blue\"
+ </colors>
+ </person>
+ eof
+
+ ActiveResource::HttpMock.respond_to do |mock|
+ mock.get "/people/1.xml", {}, @matz
+ mock.get "/people/2.xml", {}, @david
+ mock.get "/people/5.xml", {}, @marty
+ mock.get "/people/Greg.xml", {}, @greg
+ mock.get "/people/4.xml", {'key' => 'value'}, nil, 404
+ mock.put "/people/1.xml", {}, nil, 204
+ mock.delete "/people/1.xml", {}, nil, 200
+ mock.delete "/people/2.xml", {}, nil, 400
+ mock.get "/people/99.xml", {}, nil, 404
+ mock.post "/people.xml", {}, @rick, 201, 'Location' => '/people/5.xml'
+ mock.get "/people.xml", {}, @people
+ mock.get "/people/1/addresses.xml", {}, @addresses
+ mock.get "/people/1/addresses/1.xml", {}, @addy
+ mock.get "/people/1/addresses/2.xml", {}, nil, 404
+ mock.get "/people/2/addresses.xml", {}, nil, 404
+ mock.get "/people/2/addresses/1.xml", {}, nil, 404
+ mock.get "/people/Greg/addresses/1.xml", {}, @addy
+ mock.put "/people/1/addresses/1.xml", {}, nil, 204
+ mock.delete "/people/1/addresses/1.xml", {}, nil, 200
+ mock.post "/people/1/addresses.xml", {}, nil, 201, 'Location' => '/people/1/addresses/5'
+ mock.get "/people//addresses.xml", {}, nil, 404
+ mock.get "/people//addresses/1.xml", {}, nil, 404
+ mock.put "/people//addresses/1.xml", {}, nil, 404
+ mock.delete "/people//addresses/1.xml", {}, nil, 404
+ mock.post "/people//addresses.xml", {}, nil, 404
+ mock.head "/people/1.xml", {}, nil, 200
+ mock.head "/people/Greg.xml", {}, nil, 200
+ mock.head "/people/99.xml", {}, nil, 404
+ mock.head "/people/1/addresses/1.xml", {}, nil, 200
+ mock.head "/people/1/addresses/2.xml", {}, nil, 404
+ mock.head "/people/2/addresses/1.xml", {}, nil, 404
+ mock.head "/people/Greg/addresses/1.xml", {}, nil, 200
+ # customer
+ mock.get "/customers/1.xml", {}, @luis
+ end
+
+ Person.user = nil
+ Person.password = nil
+ end
+
+ def test_find_by_id
+ matz = Person.find(1)
+ assert_kind_of Person, matz
+ assert_equal "Matz", matz.name
+ assert matz.name?
+ end
+
+ def test_find_by_id_with_custom_prefix
+ addy = StreetAddress.find(1, :params => { :person_id => 1 })
+ assert_kind_of StreetAddress, addy
+ assert_equal '12345 Street', addy.street
+ end
+
+ def test_find_all
+ all = Person.find(:all)
+ assert_equal 2, all.size
+ assert_kind_of Person, all.first
+ assert_equal "Matz", all.first.name
+ assert_equal "David", all.last.name
+ end
+
+ def test_all
+ all = Person.all
+ assert_equal 2, all.size
+ assert_kind_of Person, all.first
+ assert_equal "Matz", all.first.name
+ assert_equal "David", all.last.name
+ end
+
+ def test_all_with_params
+ all = StreetAddress.all(:params => { :person_id => 1 })
+ assert_equal 1, all.size
+ assert_kind_of StreetAddress, all.first
+ end
+
+ def test_find_first
+ matz = Person.find(:first)
+ assert_kind_of Person, matz
+ assert_equal "Matz", matz.name
+ end
+
+ def test_first
+ matz = Person.first
+ assert_kind_of Person, matz
+ assert_equal "Matz", matz.name
+ end
+
+ def test_first_with_params
+ addy = StreetAddress.first(:params => { :person_id => 1 })
+ assert_kind_of StreetAddress, addy
+ assert_equal '12345 Street', addy.street
+ end
+
+ def test_find_last
+ david = Person.find(:last)
+ assert_kind_of Person, david
+ assert_equal 'David', david.name
+ end
+
+ def test_last
+ david = Person.last
+ assert_kind_of Person, david
+ assert_equal 'David', david.name
+ end
+
+ def test_last_with_params
+ addy = StreetAddress.last(:params => { :person_id => 1 })
+ assert_kind_of StreetAddress, addy
+ assert_equal '12345 Street', addy.street
+ end
+
+ def test_find_by_id_not_found
+ assert_raise(ActiveResource::ResourceNotFound) { Person.find(99) }
+ assert_raise(ActiveResource::ResourceNotFound) { StreetAddress.find(1) }
+ end
+
+ def test_find_all_sub_objects
+ all = StreetAddress.find(:all, :params => { :person_id => 1 })
+ assert_equal 1, all.size
+ assert_kind_of StreetAddress, all.first
+ end
+
+ def test_find_all_sub_objects_not_found
+ assert_nothing_raised do
+ addys = StreetAddress.find(:all, :params => { :person_id => 2 })
+ end
+ end
+
+ def test_find_all_by_from
+ ActiveResource::HttpMock.respond_to { |m| m.get "/companies/1/people.xml", {}, @people_david }
+
+ people = Person.find(:all, :from => "/companies/1/people.xml")
+ assert_equal 1, people.size
+ assert_equal "David", people.first.name
+ end
+
+ def test_find_all_by_from_with_options
+ ActiveResource::HttpMock.respond_to { |m| m.get "/companies/1/people.xml", {}, @people_david }
+
+ people = Person.find(:all, :from => "/companies/1/people.xml")
+ assert_equal 1, people.size
+ assert_equal "David", people.first.name
+ end
+
+ def test_find_all_by_symbol_from
+ ActiveResource::HttpMock.respond_to { |m| m.get "/people/managers.xml", {}, @people_david }
+
+ people = Person.find(:all, :from => :managers)
+ assert_equal 1, people.size
+ assert_equal "David", people.first.name
+ end
+
+ def test_find_single_by_from
+ ActiveResource::HttpMock.respond_to { |m| m.get "/companies/1/manager.xml", {}, @david }
+
+ david = Person.find(:one, :from => "/companies/1/manager.xml")
+ assert_equal "David", david.name
+ end
+
+ def test_find_single_by_symbol_from
+ ActiveResource::HttpMock.respond_to { |m| m.get "/people/leader.xml", {}, @david }
+
+ david = Person.find(:one, :from => :leader)
+ assert_equal "David", david.name
+ end
+end
diff --git a/activeresource/test/format_test.rb b/activeresource/test/cases/format_test.rb
index c3733e13d8..c3733e13d8 100644
--- a/activeresource/test/format_test.rb
+++ b/activeresource/test/cases/format_test.rb
diff --git a/activeresource/test/observing_test.rb b/activeresource/test/cases/observing_test.rb
index 334b256772..334b256772 100644
--- a/activeresource/test/observing_test.rb
+++ b/activeresource/test/cases/observing_test.rb
diff --git a/activeresource/test/cases/validations_test.rb b/activeresource/test/cases/validations_test.rb
new file mode 100644
index 0000000000..a8ab7d64e7
--- /dev/null
+++ b/activeresource/test/cases/validations_test.rb
@@ -0,0 +1,55 @@
+require 'abstract_unit'
+require "fixtures/project"
+
+# The validations are tested thoroughly under ActiveModel::Validations
+# This test case simply makes sur that they are all accessible by
+# Active Resource objects.
+class ValidationsTest < ActiveModel::TestCase
+ VALID_PROJECT_HASH = { :name => "My Project", :description => "A project" }
+ def setup
+ @my_proj = VALID_PROJECT_HASH.to_xml(:root => "person")
+ ActiveResource::HttpMock.respond_to do |mock|
+ mock.post "/projects.xml", {}, @my_proj, 201, 'Location' => '/projects/5.xml'
+ end
+ end
+
+ def test_validates_presence_of
+ p = new_project(:name => nil)
+ assert !p.valid?, "should not be a valid record without name"
+ assert !p.save, "should not have saved an invalid record"
+ assert_equal ["can't be blank"], p.errors[:name], "should have an error on name"
+
+ p.name = "something"
+
+ assert p.save, "should have saved after fixing the validation, but had: #{p.errors.inspect}"
+ end
+
+ def test_fails_save!
+ p = new_project(:name => nil)
+ assert_raise(ActiveResource::ResourceInvalid) { p.save! }
+ end
+
+
+ def test_validate_callback
+ # we have a callback ensuring the description is longer than three letters
+ p = new_project(:description => 'a')
+ assert !p.valid?, "should not be a valid record when it fails a validation callback"
+ assert !p.save, "should not have saved an invalid record"
+ assert_equal ["must be greater than three letters long"], p.errors[:description], "should be an error on description"
+
+ # should now allow this description
+ p.description = 'abcd'
+ assert p.save, "should have saved after fixing the validation, but had: #{p.errors.inspect}"
+ end
+
+ protected
+
+ # quickie helper to create a new project with all the required
+ # attributes.
+ # Pass in any params you specifically want to override
+ def new_project(opts = {})
+ Project.new(VALID_PROJECT_HASH.merge(opts))
+ end
+
+end
+
diff --git a/activeresource/test/connection_test.rb b/activeresource/test/connection_test.rb
index 12e8058b0c..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
@@ -204,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/project.rb b/activeresource/test/fixtures/project.rb
new file mode 100644
index 0000000000..e15fa6f620
--- /dev/null
+++ b/activeresource/test/fixtures/project.rb
@@ -0,0 +1,25 @@
+# used to test validations
+class Project < ActiveResource::Base
+ self.site = "http://37s.sunrise.i:3000"
+
+ validates_presence_of :name
+ validate :description_greater_than_three_letters
+
+ # to test the validate *callback* works
+ def description_greater_than_three_letters
+ errors.add :description, 'must be greater than three letters long' if description.length < 3 unless description.blank?
+ end
+
+
+ # stop-gap accessor to default this attribute to nil
+ # Otherwise the validations fail saying that the method does not exist.
+ # In future, method_missing will be updated to not explode on a known
+ # attribute.
+ def name
+ attributes['name'] || nil
+ end
+ def description
+ attributes['description'] || nil
+ end
+end
+