process.rb 9.2 KB
Newer Older
Y
Yehuda Katz 已提交
1
require 'action_dispatch'
2
require 'rack/session/abstract/id'
J
Jeremy Kemper 已提交
3
require 'active_support/core_ext/object/conversions'
4

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

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

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

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

      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
40
      @env['CONTENT_LENGTH'] = data.length.to_s
41
      @env['rack.input'] = StringIO.new(data)
42 43
    end

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

52
  class TestResponse < ActionDispatch::TestResponse
53
    def recycle!
54
      @status = 200
Y
Yehuda Katz 已提交
55
      @header = {}
56 57 58 59
      @writer = lambda { |x| @body << x }
      @block = nil
      @length = 0
      @body = []
60 61
      @charset = nil
      @content_type = nil
62 63

      @request = @template = nil
64
    end
65 66
  end

67 68
  class TestSession < ActionDispatch::Session::AbstractStore::SessionHash #:nodoc:
    DEFAULT_OPTIONS = ActionDispatch::Session::AbstractStore::DEFAULT_OPTIONS
69

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

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

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

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

126
      @request.recycle!
127
      @response.recycle!
128 129 130
      @controller.response_body = nil
      @controller.formats = nil
      @controller.params = nil
131

132
      @html_document = nil
133
      @request.env['REQUEST_METHOD'] = http_method
134

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

138 139
      @request.session = ActionController::TestSession.new(session) unless session.nil?
      @request.session["flash"] = ActionController::Flash::FlashHash.new.update(flash) if flash
140

141 142 143 144 145
      @controller.request = @request
      @controller.params.merge!(parameters)
      build_request_uri(action, parameters)
      Base.class_eval { include Testing }
      @controller.process_with_new_base_test(@request, @response)
146
      @response
147
    end
148

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

159
    def assigns(key = nil)
160 161 162 163
      assigns = {}
      @controller.instance_variable_names.each do |ivar|
        next if ActionController::Base.protected_instance_variables.include?(ivar)
        assigns[ivar[1..-1]] = @controller.instance_variable_get(ivar)
164
      end
165

166
      key.nil? ? assigns : assigns[key.to_s]
167
    end
168

169
    def session
170
      @request.session
171
    end
172

173
    def flash
174
      @request.flash
175
    end
176

177 178 179
    def cookies
      @response.cookies
    end
180

181 182 183
    def redirect_to_url
      @response.redirect_url
    end
184

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

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

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

200 201 202
    def find_tag(conditions)
      html_document.find(conditions)
    end
203

204 205 206
    def find_all_tag(conditions)
      html_document.find_all(conditions)
    end
207

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

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

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

      temporary_routes = ActionController::Routing::RouteSet.new
251 252
      ActionController::Routing.module_eval { const_set :Routes, temporary_routes }

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