PageRenderTime 49ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/actionpack/lib/action_controller/test_process.rb

https://github.com/dhaas/rails
Ruby | 523 lines | 401 code | 75 blank | 47 comment | 28 complexity | b1a298ce8f14e6db45a7244333a680e3 MD5 | raw file
  1. require 'action_controller/assertions'
  2. require 'action_controller/test_case'
  3. module ActionController #:nodoc:
  4. class Base
  5. # Process a test request called with a +TestRequest+ object.
  6. def self.process_test(request)
  7. new.process_test(request)
  8. end
  9. def process_test(request) #:nodoc:
  10. process(request, TestResponse.new)
  11. end
  12. def process_with_test(*args)
  13. returning process_without_test(*args) do
  14. add_variables_to_assigns
  15. end
  16. end
  17. alias_method_chain :process, :test
  18. end
  19. class TestRequest < AbstractRequest #:nodoc:
  20. attr_accessor :cookies, :session_options
  21. attr_accessor :query_parameters, :request_parameters, :path, :session, :env
  22. attr_accessor :host, :user_agent
  23. def initialize(query_parameters = nil, request_parameters = nil, session = nil)
  24. @query_parameters = query_parameters || {}
  25. @request_parameters = request_parameters || {}
  26. @session = session || TestSession.new
  27. initialize_containers
  28. initialize_default_values
  29. super()
  30. end
  31. def reset_session
  32. @session = TestSession.new
  33. end
  34. # Wraps raw_post in a StringIO.
  35. def body
  36. StringIO.new(raw_post)
  37. end
  38. # Either the RAW_POST_DATA environment variable or the URL-encoded request
  39. # parameters.
  40. def raw_post
  41. env['RAW_POST_DATA'] ||= url_encoded_request_parameters
  42. end
  43. def port=(number)
  44. @env["SERVER_PORT"] = number.to_i
  45. @port_as_int = nil
  46. end
  47. def action=(action_name)
  48. @query_parameters.update({ "action" => action_name })
  49. @parameters = nil
  50. end
  51. # Used to check AbstractRequest's request_uri functionality.
  52. # Disables the use of @path and @request_uri so superclass can handle those.
  53. def set_REQUEST_URI(value)
  54. @env["REQUEST_URI"] = value
  55. @request_uri = nil
  56. @path = nil
  57. end
  58. def request_uri=(uri)
  59. @request_uri = uri
  60. @path = uri.split("?").first
  61. end
  62. def accept=(mime_types)
  63. @env["HTTP_ACCEPT"] = Array(mime_types).collect { |mime_types| mime_types.to_s }.join(",")
  64. end
  65. def remote_addr=(addr)
  66. @env['REMOTE_ADDR'] = addr
  67. end
  68. def remote_addr
  69. @env['REMOTE_ADDR']
  70. end
  71. def request_uri
  72. @request_uri || super
  73. end
  74. def path
  75. @path || super
  76. end
  77. def assign_parameters(controller_path, action, parameters)
  78. parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action)
  79. extra_keys = ActionController::Routing::Routes.extra_keys(parameters)
  80. non_path_parameters = get? ? query_parameters : request_parameters
  81. parameters.each do |key, value|
  82. if value.is_a? Fixnum
  83. value = value.to_s
  84. elsif value.is_a? Array
  85. value = ActionController::Routing::PathSegment::Result.new(value)
  86. end
  87. if extra_keys.include?(key.to_sym)
  88. non_path_parameters[key] = value
  89. else
  90. path_parameters[key.to_s] = value
  91. end
  92. end
  93. @parameters = nil # reset TestRequest#parameters to use the new path_parameters
  94. end
  95. def recycle!
  96. self.request_parameters = {}
  97. self.query_parameters = {}
  98. self.path_parameters = {}
  99. @request_method, @accepts, @content_type = nil, nil, nil
  100. end
  101. def referer
  102. @env["HTTP_REFERER"]
  103. end
  104. private
  105. def initialize_containers
  106. @env, @cookies = {}, {}
  107. end
  108. def initialize_default_values
  109. @host = "test.host"
  110. @request_uri = "/"
  111. @user_agent = "Rails Testing"
  112. self.remote_addr = "0.0.0.0"
  113. @env["SERVER_PORT"] = 80
  114. @env['REQUEST_METHOD'] = "GET"
  115. end
  116. def url_encoded_request_parameters
  117. params = self.request_parameters.dup
  118. %w(controller action only_path).each do |k|
  119. params.delete(k)
  120. params.delete(k.to_sym)
  121. end
  122. params.to_query
  123. end
  124. end
  125. # A refactoring of TestResponse to allow the same behavior to be applied
  126. # to the "real" CgiResponse class in integration tests.
  127. module TestResponseBehavior #:nodoc:
  128. # The response code of the request
  129. def response_code
  130. headers['Status'][0,3].to_i rescue 0
  131. end
  132. # Returns a String to ensure compatibility with Net::HTTPResponse
  133. def code
  134. headers['Status'].to_s.split(' ')[0]
  135. end
  136. def message
  137. headers['Status'].to_s.split(' ',2)[1]
  138. end
  139. # Was the response successful?
  140. def success?
  141. response_code == 200
  142. end
  143. # Was the URL not found?
  144. def missing?
  145. response_code == 404
  146. end
  147. # Were we redirected?
  148. def redirect?
  149. (300..399).include?(response_code)
  150. end
  151. # Was there a server-side error?
  152. def error?
  153. (500..599).include?(response_code)
  154. end
  155. alias_method :server_error?, :error?
  156. # Returns the redirection location or nil
  157. def redirect_url
  158. headers['Location']
  159. end
  160. # Does the redirect location match this regexp pattern?
  161. def redirect_url_match?( pattern )
  162. return false if redirect_url.nil?
  163. p = Regexp.new(pattern) if pattern.class == String
  164. p = pattern if pattern.class == Regexp
  165. return false if p.nil?
  166. p.match(redirect_url) != nil
  167. end
  168. # Returns the template path of the file which was used to
  169. # render this response (or nil)
  170. def rendered_file(with_controller=false)
  171. unless template.first_render.nil?
  172. unless with_controller
  173. template.first_render
  174. else
  175. template.first_render.split('/').last || template.first_render
  176. end
  177. end
  178. end
  179. # Was this template rendered by a file?
  180. def rendered_with_file?
  181. !rendered_file.nil?
  182. end
  183. # A shortcut to the flash. Returns an empyt hash if no session flash exists.
  184. def flash
  185. session['flash'] || {}
  186. end
  187. # Do we have a flash?
  188. def has_flash?
  189. !session['flash'].empty?
  190. end
  191. # Do we have a flash that has contents?
  192. def has_flash_with_contents?
  193. !flash.empty?
  194. end
  195. # Does the specified flash object exist?
  196. def has_flash_object?(name=nil)
  197. !flash[name].nil?
  198. end
  199. # Does the specified object exist in the session?
  200. def has_session_object?(name=nil)
  201. !session[name].nil?
  202. end
  203. # A shortcut to the template.assigns
  204. def template_objects
  205. template.assigns || {}
  206. end
  207. # Does the specified template object exist?
  208. def has_template_object?(name=nil)
  209. !template_objects[name].nil?
  210. end
  211. # Returns the response cookies, converted to a Hash of (name => CGI::Cookie) pairs
  212. #
  213. # assert_equal ['AuthorOfNewPage'], r.cookies['author'].value
  214. def cookies
  215. headers['cookie'].inject({}) { |hash, cookie| hash[cookie.name] = cookie; hash }
  216. end
  217. # Returns binary content (downloadable file), converted to a String
  218. def binary_content
  219. raise "Response body is not a Proc: #{body.inspect}" unless body.kind_of?(Proc)
  220. require 'stringio'
  221. sio = StringIO.new
  222. body.call(self, sio)
  223. sio.rewind
  224. sio.read
  225. end
  226. end
  227. class TestResponse < AbstractResponse #:nodoc:
  228. include TestResponseBehavior
  229. end
  230. class TestSession #:nodoc:
  231. attr_accessor :session_id
  232. def initialize(attributes = nil)
  233. @session_id = ''
  234. @attributes = attributes.nil? ? nil : attributes.stringify_keys
  235. @saved_attributes = nil
  236. end
  237. def data
  238. @attributes ||= @saved_attributes || {}
  239. end
  240. def [](key)
  241. data[key.to_s]
  242. end
  243. def []=(key, value)
  244. data[key.to_s] = value
  245. end
  246. def update
  247. @saved_attributes = @attributes
  248. end
  249. def delete
  250. @attributes = nil
  251. end
  252. def close
  253. update
  254. delete
  255. end
  256. end
  257. # Essentially generates a modified Tempfile object similar to the object
  258. # you'd get from the standard library CGI module in a multipart
  259. # request. This means you can use an ActionController::TestUploadedFile
  260. # object in the params of a test request in order to simulate
  261. # a file upload.
  262. #
  263. # Usage example, within a functional test:
  264. # post :change_avatar, :avatar => ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + '/files/spongebob.png', 'image/png')
  265. #
  266. # Pass a true third parameter to ensure the uploaded file is opened in binary mode (only required for Windows):
  267. # post :change_avatar, :avatar => ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + '/files/spongebob.png', 'image/png', :binary)
  268. require 'tempfile'
  269. class TestUploadedFile
  270. # The filename, *not* including the path, of the "uploaded" file
  271. attr_reader :original_filename
  272. # The content type of the "uploaded" file
  273. attr_reader :content_type
  274. def initialize(path, content_type = Mime::TEXT, binary = false)
  275. raise "#{path} file does not exist" unless File.exist?(path)
  276. @content_type = content_type
  277. @original_filename = path.sub(/^.*#{File::SEPARATOR}([^#{File::SEPARATOR}]+)$/) { $1 }
  278. @tempfile = Tempfile.new(@original_filename)
  279. @tempfile.binmode if binary
  280. FileUtils.copy_file(path, @tempfile.path)
  281. end
  282. def path #:nodoc:
  283. @tempfile.path
  284. end
  285. alias local_path path
  286. def method_missing(method_name, *args, &block) #:nodoc:
  287. @tempfile.send!(method_name, *args, &block)
  288. end
  289. end
  290. module TestProcess
  291. def self.included(base)
  292. # execute the request simulating a specific http method and set/volley the response
  293. %w( get post put delete head ).each do |method|
  294. base.class_eval <<-EOV, __FILE__, __LINE__
  295. def #{method}(action, parameters = nil, session = nil, flash = nil)
  296. @request.env['REQUEST_METHOD'] = "#{method.upcase}" if defined?(@request)
  297. process(action, parameters, session, flash)
  298. end
  299. EOV
  300. end
  301. end
  302. # execute the request and set/volley the response
  303. def process(action, parameters = nil, session = nil, flash = nil)
  304. # Sanity check for required instance variables so we can give an
  305. # understandable error message.
  306. %w(@controller @request @response).each do |iv_name|
  307. if !(instance_variable_names.include?(iv_name) || instance_variable_names.include?(iv_name.to_sym)) || instance_variable_get(iv_name).nil?
  308. raise "#{iv_name} is nil: make sure you set it in your test's setup method."
  309. end
  310. end
  311. @request.recycle!
  312. @html_document = nil
  313. @request.env['REQUEST_METHOD'] ||= "GET"
  314. @request.action = action.to_s
  315. parameters ||= {}
  316. @request.assign_parameters(@controller.class.controller_path, action.to_s, parameters)
  317. @request.session = ActionController::TestSession.new(session) unless session.nil?
  318. @request.session["flash"] = ActionController::Flash::FlashHash.new.update(flash) if flash
  319. build_request_uri(action, parameters)
  320. @controller.process(@request, @response)
  321. end
  322. def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
  323. @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
  324. @request.env['HTTP_ACCEPT'] = 'text/javascript, text/html, application/xml, text/xml, */*'
  325. returning send!(request_method, action, parameters, session, flash) do
  326. @request.env.delete 'HTTP_X_REQUESTED_WITH'
  327. @request.env.delete 'HTTP_ACCEPT'
  328. end
  329. end
  330. alias xhr :xml_http_request
  331. def follow_redirect
  332. redirected_controller = @response.redirected_to[:controller]
  333. if redirected_controller && redirected_controller != @controller.controller_name
  334. raise "Can't follow redirects outside of current controller (from #{@controller.controller_name} to #{redirected_controller})"
  335. end
  336. get(@response.redirected_to.delete(:action), @response.redirected_to.stringify_keys)
  337. end
  338. def assigns(key = nil)
  339. if key.nil?
  340. @response.template.assigns
  341. else
  342. @response.template.assigns[key.to_s]
  343. end
  344. end
  345. def session
  346. @response.session
  347. end
  348. def flash
  349. @response.flash
  350. end
  351. def cookies
  352. @response.cookies
  353. end
  354. def redirect_to_url
  355. @response.redirect_url
  356. end
  357. def build_request_uri(action, parameters)
  358. unless @request.env['REQUEST_URI']
  359. options = @controller.send!(:rewrite_options, parameters)
  360. options.update(:only_path => true, :action => action)
  361. url = ActionController::UrlRewriter.new(@request, parameters)
  362. @request.set_REQUEST_URI(url.rewrite(options))
  363. end
  364. end
  365. def html_document
  366. xml = @response.content_type =~ /xml$/
  367. @html_document ||= HTML::Document.new(@response.body, false, xml)
  368. end
  369. def find_tag(conditions)
  370. html_document.find(conditions)
  371. end
  372. def find_all_tag(conditions)
  373. html_document.find_all(conditions)
  374. end
  375. def method_missing(selector, *args)
  376. return @controller.send!(selector, *args) if ActionController::Routing::Routes.named_routes.helpers.include?(selector)
  377. return super
  378. end
  379. # Shortcut for <tt>ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + path, type)</tt>:
  380. #
  381. # post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png')
  382. #
  383. # To upload binary files on Windows, pass <tt>:binary</tt> as the last parameter.
  384. # This will not affect other platforms:
  385. #
  386. # post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png', :binary)
  387. def fixture_file_upload(path, mime_type = nil, binary = false)
  388. ActionController::TestUploadedFile.new(
  389. Test::Unit::TestCase.respond_to?(:fixture_path) ? Test::Unit::TestCase.fixture_path + path : path,
  390. mime_type,
  391. binary
  392. )
  393. end
  394. # A helper to make it easier to test different route configurations.
  395. # This method temporarily replaces ActionController::Routing::Routes
  396. # with a new RouteSet instance.
  397. #
  398. # The new instance is yielded to the passed block. Typically the block
  399. # will create some routes using <tt>map.draw { map.connect ... }</tt>:
  400. #
  401. # with_routing do |set|
  402. # set.draw do |map|
  403. # map.connect ':controller/:action/:id'
  404. # assert_equal(
  405. # ['/content/10/show', {}],
  406. # map.generate(:controller => 'content', :id => 10, :action => 'show')
  407. # end
  408. # end
  409. # end
  410. #
  411. def with_routing
  412. real_routes = ActionController::Routing::Routes
  413. ActionController::Routing.module_eval { remove_const :Routes }
  414. temporary_routes = ActionController::Routing::RouteSet.new
  415. ActionController::Routing.module_eval { const_set :Routes, temporary_routes }
  416. yield temporary_routes
  417. ensure
  418. if ActionController::Routing.const_defined? :Routes
  419. ActionController::Routing.module_eval { remove_const :Routes }
  420. end
  421. ActionController::Routing.const_set(:Routes, real_routes) if real_routes
  422. end
  423. end
  424. end
  425. module Test
  426. module Unit
  427. class TestCase #:nodoc:
  428. include ActionController::TestProcess
  429. end
  430. end
  431. end