~michaelforrest/use-case-mapper/trunk

« back to all changes in this revision

Viewing changes to vendor/rails/activeresource/lib/active_resource/http_mock.rb

  • Committer: Richard Lee (Canonical)
  • Date: 2010-10-15 15:17:58 UTC
  • mfrom: (190.1.3 use-case-mapper)
  • Revision ID: richard.lee@canonical.com-20101015151758-wcvmfxrexsongf9d
Merge

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
 
        # def post(path, request_headers = {}, body = nil, status = 200, response_headers = {})
58
 
        #   @responses[Request.new(:post, path, nil, request_headers)] = Response.new(body || "", status, response_headers)
59
 
        # end
60
 
        module_eval <<-EOE, __FILE__, __LINE__
61
 
          def #{method}(path, request_headers = {}, body = nil, status = 200, response_headers = {})
62
 
            @responses << [Request.new(:#{method}, path, nil, request_headers), Response.new(body || "", status, response_headers)]
63
 
          end
64
 
        EOE
65
 
      end
66
 
    end
67
 
 
68
 
    class << self
69
 
 
70
 
      # Returns an array of all request objects that have been sent to the mock.  You can use this to check
71
 
      # if your model actually sent an HTTP request.
72
 
      #
73
 
      # ==== Example
74
 
      #   def setup
75
 
      #     @matz  = { :id => 1, :name => "Matz" }.to_xml(:root => "person")
76
 
      #     ActiveResource::HttpMock.respond_to do |mock|
77
 
      #       mock.get "/people/1.xml", {}, @matz
78
 
      #     end
79
 
      #   end
80
 
      #   
81
 
      #   def test_should_request_remote_service
82
 
      #     person = Person.find(1)  # Call the remote service
83
 
      #     
84
 
      #     # This request object has the same HTTP method and path as declared by the mock
85
 
      #     expected_request = ActiveResource::Request.new(:get, "/people/1.xml")
86
 
      #     
87
 
      #     # Assert that the mock received, and responded to, the expected request from the model
88
 
      #     assert ActiveResource::HttpMock.requests.include?(expected_request)
89
 
      #   end
90
 
      def requests
91
 
        @@requests ||= []
92
 
      end
93
 
 
94
 
      # Returns the list of requests and their mocked responses. Look up a
95
 
      # response for a request using responses.assoc(request).
96
 
      def responses
97
 
        @@responses ||= []
98
 
      end
99
 
 
100
 
      # Accepts a block which declares a set of requests and responses for the HttpMock to respond to. See the main
101
 
      # ActiveResource::HttpMock description for a more detailed explanation.
102
 
      def respond_to(pairs = {}) #:yields: mock
103
 
        reset!
104
 
        responses.concat pairs.to_a
105
 
        if block_given?
106
 
          yield Responder.new(responses)
107
 
        else
108
 
          Responder.new(responses)
109
 
        end
110
 
      end
111
 
 
112
 
      # Deletes all logged requests and responses.
113
 
      def reset!
114
 
        requests.clear
115
 
        responses.clear
116
 
      end
117
 
    end
118
 
 
119
 
    # body?       methods
120
 
    { true  => %w(post put),
121
 
      false => %w(get delete head) }.each do |has_body, methods|
122
 
      methods.each do |method|
123
 
        # def post(path, body, headers)
124
 
        #   request = ActiveResource::Request.new(:post, path, body, headers)
125
 
        #   self.class.requests << request
126
 
        #   self.class.responses.assoc(request).try(:second) || raise(InvalidRequestError.new("No response recorded for #{request}"))
127
 
        # end
128
 
        module_eval <<-EOE, __FILE__, __LINE__
129
 
          def #{method}(path, #{'body, ' if has_body}headers)
130
 
            request = ActiveResource::Request.new(:#{method}, path, #{has_body ? 'body, ' : 'nil, '}headers)
131
 
            self.class.requests << request
132
 
            self.class.responses.assoc(request).try(:second) || raise(InvalidRequestError.new("No response recorded for \#{request}"))
133
 
          end
134
 
        EOE
135
 
      end
136
 
    end
137
 
 
138
 
    def initialize(site) #:nodoc:
139
 
      @site = site
140
 
    end
141
 
  end
142
 
 
143
 
  class Request
144
 
    attr_accessor :path, :method, :body, :headers
145
 
 
146
 
    def initialize(method, path, body = nil, headers = {})
147
 
      @method, @path, @body, @headers = method, path, body, headers.merge(ActiveResource::Connection::HTTP_FORMAT_HEADER_NAMES[method] => 'application/xml')
148
 
    end
149
 
 
150
 
    def ==(req)
151
 
      path == req.path && method == req.method && headers == req.headers
152
 
    end
153
 
 
154
 
    def to_s
155
 
      "<#{method.to_s.upcase}: #{path} [#{headers}] (#{body})>"
156
 
    end
157
 
  end
158
 
 
159
 
  class Response
160
 
    attr_accessor :body, :message, :code, :headers
161
 
 
162
 
    def initialize(body, message = 200, headers = {})
163
 
      @body, @message, @headers = body, message.to_s, headers
164
 
      @code = @message[0,3].to_i
165
 
 
166
 
      resp_cls = Net::HTTPResponse::CODE_TO_OBJ[@code.to_s]
167
 
      if resp_cls && !resp_cls.body_permitted?
168
 
        @body = nil
169
 
      end
170
 
 
171
 
      if @body.nil?
172
 
        self['Content-Length'] = "0"
173
 
      else
174
 
        self['Content-Length'] = body.size.to_s
175
 
      end
176
 
    end
177
 
 
178
 
    def success?
179
 
      (200..299).include?(code)
180
 
    end
181
 
 
182
 
    def [](key)
183
 
      headers[key]
184
 
    end
185
 
 
186
 
    def []=(key, value)
187
 
      headers[key] = value
188
 
    end
189
 
 
190
 
    def ==(other)
191
 
      if (other.is_a?(Response))
192
 
        other.body == body && other.message == message && other.headers == headers
193
 
      else
194
 
        false
195
 
      end
196
 
    end
197
 
  end
198
 
 
199
 
  class Connection
200
 
    private
201
 
      silence_warnings do
202
 
        def http
203
 
          @http ||= HttpMock.new(@site)
204
 
        end
205
 
      end
206
 
  end
207
 
end