2006-06-25 10:44:22 -04:00
require 'active_resource/connection'
module ActiveResource
2007-01-16 01:34:10 -05:00
class InvalidRequestError < StandardError ; end #:nodoc:
2006-12-05 14:12:51 -05:00
2008-05-25 07:29:00 -04:00
# One thing that has always been a pain with remote web services is testing. The HttpMock
# class makes it easy to test your Active Resource models by creating a set of mock responses to specific
# requests.
#
# To test your Active Resource model, you simply call the ActiveResource::HttpMock.respond_to
# method with an attached block. The block declares a set of URIs with expected input, and the output
# each request should return. The passed in block has any number of entries in the following generalized
# format:
#
# mock.http_method(path, request_headers = {}, body = nil, status = 200, response_headers = {})
#
# * <tt>http_method</tt> - The HTTP method to listen for. This can be +get+, +post+, +put+, +delete+ or
# +head+.
# * <tt>path</tt> - A string, starting with a "/", defining the URI that is expected to be
# called.
# * <tt>request_headers</tt> - Headers that are expected along with the request. This argument uses a
# hash format, such as <tt>{ "Content-Type" => "application/xml" }</tt>. This mock will only trigger
# if your tests sends a request with identical headers.
# * <tt>body</tt> - The data to be returned. This should be a string of Active Resource parseable content,
# such as XML.
# * <tt>status</tt> - The HTTP response code, as an integer, to return with the response.
# * <tt>response_headers</tt> - Headers to be returned with the response. Uses the same hash format as
# <tt>request_headers</tt> listed above.
#
# In order for a mock to deliver its content, the incoming request must match by the <tt>http_method</tt>,
# +path+ and <tt>request_headers</tt>. If no match is found an InvalidRequestError exception
# will be raised letting you know you need to create a new mock for that request.
#
# ==== Example
# def setup
# @matz = { :id => 1, :name => "Matz" }.to_xml(:root => "person")
# ActiveResource::HttpMock.respond_to do |mock|
# mock.post "/people.xml", {}, @matz, 201, "Location" => "/people/1.xml"
# mock.get "/people/1.xml", {}, @matz
# mock.put "/people/1.xml", {}, nil, 204
# mock.delete "/people/1.xml", {}, nil, 200
# end
# end
#
# def test_get_matz
# person = Person.find(1)
# assert_equal "Matz", person.name
# end
#
2006-06-25 10:44:22 -04:00
class HttpMock
2008-05-25 07:29:00 -04:00
class Responder #:nodoc:
2006-08-31 21:15:10 -04:00
def initialize ( responses )
@responses = responses
end
2006-12-05 14:12:51 -05:00
2008-02-09 17:21:55 -05:00
for method in [ :post , :put , :get , :delete , :head ]
2008-12-28 14:48:05 -05:00
# def post(path, request_headers = {}, body = nil, status = 200, response_headers = {})
# @responses[Request.new(:post, path, nil, request_headers)] = Response.new(body || "", status, response_headers)
# end
2007-12-21 06:22:03 -05:00
module_eval <<-EOE, __FILE__, __LINE__
2006-09-29 12:25:49 -04:00
def #{method}(path, request_headers = {}, body = nil, status = 200, response_headers = {})
2009-02-06 15:47:01 -05:00
@responses << [ Request . new ( : #{method}, path, nil, request_headers), Response.new(body || "", status, response_headers)]
2006-08-31 21:15:10 -04:00
end
EOE
end
end
2006-06-25 10:44:22 -04:00
class << self
2008-05-25 07:29:00 -04:00
# Returns an array of all request objects that have been sent to the mock. You can use this to check
2008-07-16 08:00:36 -04:00
# if your model actually sent an HTTP request.
2008-05-25 07:29:00 -04:00
#
# ==== Example
# def setup
# @matz = { :id => 1, :name => "Matz" }.to_xml(:root => "person")
# ActiveResource::HttpMock.respond_to do |mock|
# mock.get "/people/1.xml", {}, @matz
# end
# end
#
# def test_should_request_remote_service
# person = Person.find(1) # Call the remote service
#
# # This request object has the same HTTP method and path as declared by the mock
# expected_request = ActiveResource::Request.new(:get, "/people/1.xml")
#
# # Assert that the mock received, and responded to, the expected request from the model
# assert ActiveResource::HttpMock.requests.include?(expected_request)
# end
2006-06-25 10:44:22 -04:00
def requests
@@requests || = [ ]
end
2009-02-06 15:47:01 -05:00
# Returns the list of requests and their mocked responses. Look up a
# response for a request using responses.assoc(request).
2006-06-25 10:44:22 -04:00
def responses
2009-02-06 15:47:01 -05:00
@@responses || = [ ]
2006-06-25 10:44:22 -04:00
end
2008-05-25 07:29:00 -04:00
# Accepts a block which declares a set of requests and responses for the HttpMock to respond to. See the main
# ActiveResource::HttpMock description for a more detailed explanation.
def respond_to ( pairs = { } ) #:yields: mock
2006-06-25 10:44:22 -04:00
reset!
2009-02-06 15:47:01 -05:00
responses . concat pairs . to_a
2007-09-20 19:18:05 -04:00
if block_given?
2007-12-10 00:53:56 -05:00
yield Responder . new ( responses )
2007-09-20 19:18:05 -04:00
else
Responder . new ( responses )
end
2006-06-25 10:44:22 -04:00
end
2008-05-25 07:29:00 -04:00
# Deletes all logged requests and responses.
2006-06-25 10:44:22 -04:00
def reset!
requests . clear
responses . clear
end
end
2009-02-06 15:47:01 -05:00
# body? methods
{ true = > %w( post put ) ,
false = > %w( get delete head ) } . each do | has_body , methods |
methods . each do | method |
# def post(path, body, headers)
# request = ActiveResource::Request.new(:post, path, body, headers)
# self.class.requests << request
# self.class.responses.assoc(request)[0] || raise(InvalidRequestError.new("No response recorded for #{request}"))
# end
module_eval <<-EOE, __FILE__, __LINE__
def #{method}(path, #{'body, ' if has_body}headers)
request = ActiveResource :: Request . new ( : #{method}, path, #{has_body ? 'body, ' : 'nil, '}headers)
self . class . requests << request
self . class . responses . assoc ( request ) . try ( :second ) || raise ( InvalidRequestError . new ( " No response recorded for \# {request.inspect} in \# {self.class.responses.select { |req, res| req.path == request.path }.inspect} " ) )
end
EOE
end
2006-06-25 10:44:22 -04:00
end
2006-12-05 14:12:51 -05:00
2008-05-25 07:29:00 -04:00
def initialize ( site ) #:nodoc:
2006-06-25 10:44:22 -04:00
@site = site
end
end
class Request
2006-09-29 12:25:49 -04:00
attr_accessor :path , :method , :body , :headers
2006-12-05 14:12:51 -05:00
2006-10-02 10:20:18 -04:00
def initialize ( method , path , body = nil , headers = { } )
2008-08-29 21:19:18 -04:00
@method , @path , @body , @headers = method , path , body , headers . merge ( ActiveResource :: Connection :: HTTP_FORMAT_HEADER_NAMES [ method ] = > 'application/xml' )
2006-06-25 10:44:22 -04:00
end
2009-02-06 15:47:01 -05:00
def == ( req )
path == req . path && method == req . method && headers == req . headers
2006-06-25 10:44:22 -04:00
end
2006-12-05 14:12:51 -05:00
2006-06-25 10:44:22 -04:00
def to_s
2006-09-29 12:25:49 -04:00
" < #{ method . to_s . upcase } : #{ path } [ #{ headers } ] ( #{ body } )> "
2006-06-25 10:44:22 -04:00
end
end
2006-12-05 14:12:51 -05:00
2006-06-25 10:44:22 -04:00
class Response
2006-12-05 14:12:51 -05:00
attr_accessor :body , :message , :code , :headers
def initialize ( body , message = 200 , headers = { } )
@body , @message , @headers = body , message . to_s , headers
@code = @message [ 0 , 3 ] . to_i
2007-12-10 00:53:56 -05:00
resp_cls = Net :: HTTPResponse :: CODE_TO_OBJ [ @code . to_s ]
if resp_cls && ! resp_cls . body_permitted?
@body = nil
end
if @body . nil?
self [ 'Content-Length' ] = " 0 "
else
self [ 'Content-Length' ] = body . size . to_s
end
2006-06-25 10:44:22 -04:00
end
2006-12-05 14:12:51 -05:00
2006-06-25 10:44:22 -04:00
def success?
( 200 .. 299 ) . include? ( code )
end
2006-08-31 21:15:10 -04:00
def [] ( key )
headers [ key ]
end
2006-12-05 14:12:51 -05:00
2006-08-31 21:15:10 -04:00
def []= ( key , value )
headers [ key ] = value
end
2007-12-10 00:53:56 -05:00
2007-04-25 21:53:01 -04:00
def == ( other )
if ( other . is_a? ( Response ) )
other . body == body && other . message == message && other . headers == headers
else
false
end
end
2006-06-25 10:44:22 -04:00
end
class Connection
private
2006-09-04 06:04:23 -04:00
silence_warnings do
def http
@http || = HttpMock . new ( @site )
end
2006-06-25 10:44:22 -04:00
end
end
2006-09-04 06:04:23 -04:00
end