Froze rails gems
[depot.git] / vendor / rails / activeresource / lib / active_resource / http_mock.rb
1 require 'active_resource/connection'
2
3 module ActiveResource
4 class InvalidRequestError < StandardError; end #:nodoc:
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 #
50 class HttpMock
51 class Responder #:nodoc:
52 def initialize(responses)
53 @responses = responses
54 end
55
56 for method in [ :post, :put, :get, :delete, :head ]
57 module_eval <<-EOE, __FILE__, __LINE__
58 def #{method}(path, request_headers = {}, body = nil, status = 200, response_headers = {})
59 @responses[Request.new(:#{method}, path, nil, request_headers)] = Response.new(body || "", status, response_headers)
60 end
61 EOE
62 end
63 end
64
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 # if 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
87 def requests
88 @@requests ||= []
89 end
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.
94 def responses
95 @@responses ||= {}
96 end
97
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
101 reset!
102 pairs.each do |(path, response)|
103 responses[path] = response
104 end
105
106 if block_given?
107 yield Responder.new(responses)
108 else
109 Responder.new(responses)
110 end
111 end
112
113 # Deletes all logged requests and responses.
114 def reset!
115 requests.clear
116 responses.clear
117 end
118 end
119
120 for method in [ :post, :put ]
121 module_eval <<-EOE, __FILE__, __LINE__
122 def #{method}(path, body, headers)
123 request = ActiveResource::Request.new(:#{method}, path, body, headers)
124 self.class.requests << request
125 self.class.responses[request] || raise(InvalidRequestError.new("No response recorded for \#{request}"))
126 end
127 EOE
128 end
129
130 for method in [ :get, :delete, :head ]
131 module_eval <<-EOE, __FILE__, __LINE__
132 def #{method}(path, headers)
133 request = ActiveResource::Request.new(:#{method}, path, nil, headers)
134 self.class.requests << request
135 self.class.responses[request] || raise(InvalidRequestError.new("No response recorded for \#{request}"))
136 end
137 EOE
138 end
139
140 def initialize(site) #:nodoc:
141 @site = site
142 end
143 end
144
145 class Request
146 attr_accessor :path, :method, :body, :headers
147
148 def initialize(method, path, body = nil, headers = {})
149 @method, @path, @body, @headers = method, path, body, headers.merge(ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[method] => 'application/xml')
150 end
151
152 def ==(other_request)
153 other_request.hash == hash
154 end
155
156 def eql?(other_request)
157 self == other_request
158 end
159
160 def to_s
161 "<#{method.to_s.upcase}: #{path} [#{headers}] (#{body})>"
162 end
163
164 def hash
165 "#{path}#{method}#{headers}".hash
166 end
167 end
168
169 class Response
170 attr_accessor :body, :message, :code, :headers
171
172 def initialize(body, message = 200, headers = {})
173 @body, @message, @headers = body, message.to_s, headers
174 @code = @message[0,3].to_i
175
176 resp_cls = Net::HTTPResponse::CODE_TO_OBJ[@code.to_s]
177 if resp_cls && !resp_cls.body_permitted?
178 @body = nil
179 end
180
181 if @body.nil?
182 self['Content-Length'] = "0"
183 else
184 self['Content-Length'] = body.size.to_s
185 end
186 end
187
188 def success?
189 (200..299).include?(code)
190 end
191
192 def [](key)
193 headers[key]
194 end
195
196 def []=(key, value)
197 headers[key] = value
198 end
199
200 def ==(other)
201 if (other.is_a?(Response))
202 other.body == body && other.message == message && other.headers == headers
203 else
204 false
205 end
206 end
207 end
208
209 class Connection
210 private
211 silence_warnings do
212 def http
213 @http ||= HttpMock.new(@site)
214 end
215 end
216 end
217 end