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

D
Initial  
David Heinemeier Hansson 已提交
3
module ActionController #:nodoc:
4 5
  class TestRequest < ActionDispatch::TestRequest #:nodoc:
    attr_accessor :query_parameters
6 7

    def initialize(env = {})
8
      super
9

10
      @query_parameters = {}
J
Jeremy Kemper 已提交
11 12
      self.session = TestSession.new
      self.session_options = TestSession::DEFAULT_OPTIONS.merge(:id => ActiveSupport::SecureRandom.hex(16))
D
Initial  
David Heinemeier Hansson 已提交
13 14 15
    end

    def action=(action_name)
16
      query_parameters.update({ "action" => action_name })
17
    end
18

19
    def assign_parameters(controller_path, action, parameters)
20
      parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action)
21
      extra_keys = ActionController::Routing::Routes.extra_keys(parameters)
22
      non_path_parameters = get? ? query_parameters : request_parameters
23
      parameters.each do |key, value|
24 25 26
        if value.is_a? Fixnum
          value = value.to_s
        elsif value.is_a? Array
27
          value = ActionController::Routing::PathSegment::Result.new(value)
28 29
        end

30 31 32
        if extra_keys.include?(key.to_sym)
          non_path_parameters[key] = value
        else
33
          path_parameters[key.to_s] = value
34
        end
35
      end
36 37 38 39 40 41 42 43 44 45 46

      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
      @env['CONTENT_LENGTH'] = data.length
      @env['rack.input'] = StringIO.new(data)
47 48
    end

49
    def recycle!
50 51 52
      @env.delete_if { |k, v| k =~ /^action_dispatch\.request/ }
      self.query_parameters = {}
      @headers = nil
53
    end
D
Initial  
David Heinemeier Hansson 已提交
54
  end
55

P
Pratik Naik 已提交
56 57 58 59 60
  # Integration test methods such as ActionController::Integration::Session#get
  # and ActionController::Integration::Session#post return objects of class
  # TestResponse, which represent the HTTP response results of the requested
  # controller actions.
  #
61
  # See Response for more information on controller response objects.
62
  class TestResponse < ActionDispatch::TestResponse
63
    def recycle!
64
      body_parts.clear
65 66 67
      headers.delete('ETag')
      headers.delete('Last-Modified')
    end
68 69
  end

70 71
  class TestSession < ActionDispatch::Session::AbstractStore::SessionHash #:nodoc:
    DEFAULT_OPTIONS = ActionDispatch::Session::AbstractStore::DEFAULT_OPTIONS
72

73 74 75
    def initialize(session = {})
      replace(session.stringify_keys)
      @loaded = true
76
    end
D
Initial  
David Heinemeier Hansson 已提交
77
  end
78

79 80 81 82 83 84 85
  # 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:
86
  #   post :change_avatar, :avatar => ActionController::TestUploadedFile.new(ActionController::TestCase.fixture_path + '/files/spongebob.png', 'image/png')
87
  #
88
  # Pass a true third parameter to ensure the uploaded file is opened in binary mode (only required for Windows):
89
  #   post :change_avatar, :avatar => ActionController::TestUploadedFile.new(ActionController::TestCase.fixture_path + '/files/spongebob.png', 'image/png', :binary)
J
Joshua Peek 已提交
90
  TestUploadedFile = Rack::Utils::Multipart::UploadedFile
91

92
  module TestProcess
93
    def self.included(base)
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
      # 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")
117
      end
118
    end
119

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

129
      @request.recycle!
130
      @response.recycle!
131

132
      @html_document = nil
133
      @request.request_method = http_method
134

135
      @request.action = action.to_s
136

137 138
      parameters ||= {}
      @request.assign_parameters(@controller.class.controller_path, action.to_s, parameters)
139

140 141 142
      @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)
143 144 145

      Base.class_eval { include ProcessWithTest } unless Base < ProcessWithTest
      @controller.process_with_test(@request, @response)
146
    end
147

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

158 159
    def assigns(key = nil)
      if key.nil?
160
        @controller.template.assigns
161
      else
162
        @controller.template.assigns[key.to_s]
163 164
      end
    end
165

166
    def session
167
      @request.session
168
    end
169

170
    def flash
171
      @request.flash
172
    end
173

174 175 176
    def cookies
      @response.cookies
    end
177

178 179 180
    def redirect_to_url
      @response.redirect_url
    end
181

182 183
    def build_request_uri(action, parameters)
      unless @request.env['REQUEST_URI']
184
        options = @controller.__send__(:rewrite_options, parameters)
185
        options.update(:only_path => true, :action => action)
186

187
        url = ActionController::UrlRewriter.new(@request, parameters)
188
        @request.request_uri = url.rewrite(options)
189
      end
190
    end
191

192
    def html_document
193 194
      xml = @response.content_type =~ /xml$/
      @html_document ||= HTML::Document.new(@response.body, false, xml)
195
    end
196

197 198 199
    def find_tag(conditions)
      html_document.find(conditions)
    end
200

201 202 203
    def find_all_tag(conditions)
      html_document.find_all(conditions)
    end
204

205 206 207
    def method_missing(selector, *args, &block)
      if @controller && ActionController::Routing::Routes.named_routes.helpers.include?(selector)
        @controller.send(selector, *args, &block)
208 209 210
      else
        super
      end
211
    end
212

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

226 227
    # A helper to make it easier to test different route configurations.
    # This method temporarily replaces ActionController::Routing::Routes
228
    # with a new RouteSet instance.
229 230
    #
    # The new instance is yielded to the passed block. Typically the block
231
    # will create some routes using <tt>map.draw { map.connect ... }</tt>:
232
    #
233 234 235 236 237 238 239 240 241
    #   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
242 243 244
    #
    def with_routing
      real_routes = ActionController::Routing::Routes
245
      ActionController::Routing.module_eval { remove_const :Routes }
246 247

      temporary_routes = ActionController::Routing::RouteSet.new
248 249
      ActionController::Routing.module_eval { const_set :Routes, temporary_routes }

250 251 252
      yield temporary_routes
    ensure
      if ActionController::Routing.const_defined? :Routes
253
        ActionController::Routing.module_eval { remove_const :Routes }
254
      end
255 256
      ActionController::Routing.const_set(:Routes, real_routes) if real_routes
    end
257 258
  end

259 260 261 262 263 264 265
  module ProcessWithTest #:nodoc:
    def self.included(base)
      base.class_eval { attr_reader :assigns }
    end

    def process_with_test(*args)
      process(*args).tap { set_test_assigns }
266
    end
267 268 269 270 271 272 273

    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
274
          @template.assigns[name] = value if response
275 276
        end
      end
D
David Heinemeier Hansson 已提交
277
  end
278
end