process.rb 9.9 KB
Newer Older
1
require 'rack/session/abstract/id'
2

D
Initial  
David Heinemeier Hansson 已提交
3
module ActionController #:nodoc:
4
  class TestRequest < ActionDispatch::TestRequest #:nodoc:
5
    def initialize(env = {})
6
      super
7

J
Jeremy Kemper 已提交
8 9
      self.session = TestSession.new
      self.session_options = TestSession::DEFAULT_OPTIONS.merge(:id => ActiveSupport::SecureRandom.hex(16))
D
Initial  
David Heinemeier Hansson 已提交
10 11
    end

12
    def assign_parameters(controller_path, action, parameters)
13
      parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action)
14
      extra_keys = ActionController::Routing::Routes.extra_keys(parameters)
15
      non_path_parameters = get? ? query_parameters : request_parameters
16
      parameters.each do |key, value|
17 18 19
        if value.is_a? Fixnum
          value = value.to_s
        elsif value.is_a? Array
20
          value = ActionController::Routing::PathSegment::Result.new(value)
21 22
        end

23 24 25
        if extra_keys.include?(key.to_sym)
          non_path_parameters[key] = value
        else
26
          path_parameters[key.to_s] = value
27
        end
28
      end
29 30 31 32 33 34 35 36 37

      params = self.request_parameters.dup

      %w(controller action only_path).each do |k|
        params.delete(k)
        params.delete(k.to_sym)
      end

      data = params.to_query
38
      @env['CONTENT_LENGTH'] = data.length.to_s
39
      @env['rack.input'] = StringIO.new(data)
40 41
    end

42
    def recycle!
43
      @env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
44
      @env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
45
      @env['action_dispatch.request.query_parameters'] = {}
46
    end
D
Initial  
David Heinemeier Hansson 已提交
47
  end
48

49
  class TestResponse < ActionDispatch::TestResponse
50
    def recycle!
51 52 53 54 55 56 57 58
      @status = 200
      @header = Rack::Utils::HeaderHash.new(DEFAULT_HEADERS)
      @writer = lambda { |x| @body << x }
      @block = nil
      @length = 0
      @body = []

      @request = @template = nil
59
    end
60 61
  end

62 63
  class TestSession < ActionDispatch::Session::AbstractStore::SessionHash #:nodoc:
    DEFAULT_OPTIONS = ActionDispatch::Session::AbstractStore::DEFAULT_OPTIONS
64

65 66 67
    def initialize(session = {})
      replace(session.stringify_keys)
      @loaded = true
68
    end
D
Initial  
David Heinemeier Hansson 已提交
69
  end
70

71 72 73 74 75 76 77
  # Essentially generates a modified Tempfile object similar to the object
  # you'd get from the standard library CGI module in a multipart
  # request. This means you can use an ActionController::TestUploadedFile
  # object in the params of a test request in order to simulate
  # a file upload.
  #
  # Usage example, within a functional test:
78
  #   post :change_avatar, :avatar => ActionController::TestUploadedFile.new(ActionController::TestCase.fixture_path + '/files/spongebob.png', 'image/png')
79
  #
80
  # Pass a true third parameter to ensure the uploaded file is opened in binary mode (only required for Windows):
81
  #   post :change_avatar, :avatar => ActionController::TestUploadedFile.new(ActionController::TestCase.fixture_path + '/files/spongebob.png', 'image/png', :binary)
J
Joshua Peek 已提交
82
  TestUploadedFile = Rack::Utils::Multipart::UploadedFile
83

84
  module TestProcess
85
    def self.included(base)
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
      # Executes a request simulating GET HTTP method and set/volley the response
      def get(action, parameters = nil, session = nil, flash = nil)
        process(action, parameters, session, flash, "GET")
      end

      # Executes a request simulating POST HTTP method and set/volley the response
      def post(action, parameters = nil, session = nil, flash = nil)
        process(action, parameters, session, flash, "POST")
      end

      # Executes a request simulating PUT HTTP method and set/volley the response
      def put(action, parameters = nil, session = nil, flash = nil)
        process(action, parameters, session, flash, "PUT")
      end

      # Executes a request simulating DELETE HTTP method and set/volley the response
      def delete(action, parameters = nil, session = nil, flash = nil)
        process(action, parameters, session, flash, "DELETE")
      end

      # Executes a request simulating HEAD HTTP method and set/volley the response
      def head(action, parameters = nil, session = nil, flash = nil)
        process(action, parameters, session, flash, "HEAD")
109
      end
110
    end
111

112
    def process(action, parameters = nil, session = nil, flash = nil, http_method = 'GET')
113 114
      # Sanity check for required instance variables so we can give an
      # understandable error message.
115
      %w(@controller @request @response).each do |iv_name|
116
        if !(instance_variable_names.include?(iv_name) || instance_variable_names.include?(iv_name.to_sym)) || instance_variable_get(iv_name).nil?
117 118
          raise "#{iv_name} is nil: make sure you set it in your test's setup method."
        end
119
      end
120

121
      @request.recycle!
122
      @response.recycle!
123

124
      @html_document = nil
125
      @request.request_method = http_method
126

127 128
      parameters ||= {}
      @request.assign_parameters(@controller.class.controller_path, action.to_s, parameters)
129

130 131 132
      @request.session = ActionController::TestSession.new(session) unless session.nil?
      @request.session["flash"] = ActionController::Flash::FlashHash.new.update(flash) if flash
      build_request_uri(action, parameters)
133

134
      @request.env["action_controller.rescue.request"] = @request
135
      @request.env["action_controller.rescue.response"] = @response
136

137
      Base.class_eval { include ProcessWithTest } unless Base < ProcessWithTest
138

139 140 141 142 143 144
      env = @request.env
      app = @controller

      # TODO: Enable Lint
      # app = Rack::Lint.new(app)

145
      status, headers, body = app.action(action, env)
146 147
      response = Rack::MockResponse.new(status, headers, body)

148 149 150
      @response.request, @response.template = @request, @controller.template
      @response.status, @response.headers, @response.body = response.status, response.headers, response.body
      @response
151
    end
152

153 154
    def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
      @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
155
      @request.env['HTTP_ACCEPT'] =  [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
156
      returning __send__(request_method, action, parameters, session, flash) do
157
        @request.env.delete 'HTTP_X_REQUESTED_WITH'
158
        @request.env.delete 'HTTP_ACCEPT'
159
      end
160 161
    end
    alias xhr :xml_http_request
162

163 164
    def assigns(key = nil)
      if key.nil?
165
        @controller.template.assigns
166
      else
167
        @controller.template.assigns[key.to_s]
168 169
      end
    end
170

171
    def session
172
      @request.session
173
    end
174

175
    def flash
176
      @request.flash
177
    end
178

179 180 181
    def cookies
      @response.cookies
    end
182

183 184 185
    def redirect_to_url
      @response.redirect_url
    end
186

187 188
    def build_request_uri(action, parameters)
      unless @request.env['REQUEST_URI']
189
        options = @controller.__send__(:rewrite_options, parameters)
190
        options.update(:only_path => true, :action => action)
191

192
        url = ActionController::UrlRewriter.new(@request, parameters)
193
        @request.request_uri = url.rewrite(options)
194
      end
195
    end
196

197
    def html_document
198 199
      xml = @response.content_type =~ /xml$/
      @html_document ||= HTML::Document.new(@response.body, false, xml)
200
    end
201

202 203 204
    def find_tag(conditions)
      html_document.find(conditions)
    end
205

206 207 208
    def find_all_tag(conditions)
      html_document.find_all(conditions)
    end
209

210 211 212
    def method_missing(selector, *args, &block)
      if @controller && ActionController::Routing::Routes.named_routes.helpers.include?(selector)
        @controller.send(selector, *args, &block)
213 214 215
      else
        super
      end
216
    end
217

218
    # Shortcut for <tt>ActionController::TestUploadedFile.new(ActionController::TestCase.fixture_path + path, type)</tt>:
219
    #
220
    #   post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png')
221
    #
222 223 224
    # To upload binary files on Windows, pass <tt>:binary</tt> as the last parameter.
    # This will not affect other platforms:
    #
225 226
    #   post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png', :binary)
    def fixture_file_upload(path, mime_type = nil, binary = false)
227 228
      fixture_path = ActionController::TestCase.send(:fixture_path) if ActionController::TestCase.respond_to?(:fixture_path)
      ActionController::TestUploadedFile.new("#{fixture_path}#{path}", mime_type, binary)
229
    end
230

231 232
    # A helper to make it easier to test different route configurations.
    # This method temporarily replaces ActionController::Routing::Routes
233
    # with a new RouteSet instance.
234 235
    #
    # The new instance is yielded to the passed block. Typically the block
236
    # will create some routes using <tt>map.draw { map.connect ... }</tt>:
237
    #
238 239 240 241 242 243 244 245 246
    #   with_routing do |set|
    #     set.draw do |map|
    #       map.connect ':controller/:action/:id'
    #         assert_equal(
    #           ['/content/10/show', {}],
    #           map.generate(:controller => 'content', :id => 10, :action => 'show')
    #       end
    #     end
    #   end
247 248 249
    #
    def with_routing
      real_routes = ActionController::Routing::Routes
250
      ActionController::Routing.module_eval { remove_const :Routes }
251 252

      temporary_routes = ActionController::Routing::RouteSet.new
253 254
      ActionController::Routing.module_eval { const_set :Routes, temporary_routes }

255 256 257
      yield temporary_routes
    ensure
      if ActionController::Routing.const_defined? :Routes
258
        ActionController::Routing.module_eval { remove_const :Routes }
259
      end
260 261
      ActionController::Routing.const_set(:Routes, real_routes) if real_routes
    end
262 263
  end

264 265
  module ProcessWithTest #:nodoc:
    def self.included(base)
266 267 268 269
      base.class_eval {
        attr_reader :assigns
        alias_method_chain :process, :test
      }
270 271 272
    end

    def process_with_test(*args)
273
      process_without_test(*args).tap { set_test_assigns }
274
    end
275 276 277 278 279 280 281

    private
      def set_test_assigns
        @assigns = {}
        (instance_variable_names - self.class.protected_instance_variables).each do |var|
          name, value = var[1..-1], instance_variable_get(var)
          @assigns[name] = value
282
          @template.assigns[name] = value if response
283 284
        end
      end
D
David Heinemeier Hansson 已提交
285
  end
286
end