actionpack 4.2.8 → 5.2.4.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (166) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +285 -444
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +6 -7
  5. data/lib/abstract_controller.rb +12 -5
  6. data/lib/abstract_controller/asset_paths.rb +2 -0
  7. data/lib/abstract_controller/base.rb +45 -49
  8. data/lib/abstract_controller/caching.rb +66 -0
  9. data/lib/{action_controller → abstract_controller}/caching/fragments.rb +78 -15
  10. data/lib/abstract_controller/callbacks.rb +47 -31
  11. data/lib/abstract_controller/collector.rb +8 -11
  12. data/lib/abstract_controller/error.rb +6 -0
  13. data/lib/abstract_controller/helpers.rb +25 -25
  14. data/lib/abstract_controller/logger.rb +2 -0
  15. data/lib/abstract_controller/railties/routes_helpers.rb +4 -2
  16. data/lib/abstract_controller/rendering.rb +42 -41
  17. data/lib/abstract_controller/translation.rb +10 -7
  18. data/lib/abstract_controller/url_for.rb +2 -0
  19. data/lib/action_controller.rb +29 -21
  20. data/lib/action_controller/api.rb +149 -0
  21. data/lib/action_controller/api/api_rendering.rb +16 -0
  22. data/lib/action_controller/base.rb +27 -19
  23. data/lib/action_controller/caching.rb +14 -57
  24. data/lib/action_controller/form_builder.rb +50 -0
  25. data/lib/action_controller/log_subscriber.rb +10 -15
  26. data/lib/action_controller/metal.rb +98 -83
  27. data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
  28. data/lib/action_controller/metal/conditional_get.rb +118 -44
  29. data/lib/action_controller/metal/content_security_policy.rb +52 -0
  30. data/lib/action_controller/metal/cookies.rb +3 -3
  31. data/lib/action_controller/metal/data_streaming.rb +27 -46
  32. data/lib/action_controller/metal/etag_with_flash.rb +18 -0
  33. data/lib/action_controller/metal/etag_with_template_digest.rb +20 -13
  34. data/lib/action_controller/metal/exceptions.rb +8 -14
  35. data/lib/action_controller/metal/flash.rb +4 -3
  36. data/lib/action_controller/metal/force_ssl.rb +23 -21
  37. data/lib/action_controller/metal/head.rb +21 -19
  38. data/lib/action_controller/metal/helpers.rb +24 -14
  39. data/lib/action_controller/metal/http_authentication.rb +64 -57
  40. data/lib/action_controller/metal/implicit_render.rb +62 -8
  41. data/lib/action_controller/metal/instrumentation.rb +19 -21
  42. data/lib/action_controller/metal/live.rb +90 -106
  43. data/lib/action_controller/metal/mime_responds.rb +33 -46
  44. data/lib/action_controller/metal/parameter_encoding.rb +51 -0
  45. data/lib/action_controller/metal/params_wrapper.rb +61 -53
  46. data/lib/action_controller/metal/redirecting.rb +49 -28
  47. data/lib/action_controller/metal/renderers.rb +87 -44
  48. data/lib/action_controller/metal/rendering.rb +72 -50
  49. data/lib/action_controller/metal/request_forgery_protection.rb +203 -92
  50. data/lib/action_controller/metal/rescue.rb +9 -16
  51. data/lib/action_controller/metal/streaming.rb +12 -10
  52. data/lib/action_controller/metal/strong_parameters.rb +582 -165
  53. data/lib/action_controller/metal/testing.rb +2 -17
  54. data/lib/action_controller/metal/url_for.rb +19 -10
  55. data/lib/action_controller/railtie.rb +28 -10
  56. data/lib/action_controller/railties/helpers.rb +2 -0
  57. data/lib/action_controller/renderer.rb +117 -0
  58. data/lib/action_controller/template_assertions.rb +11 -0
  59. data/lib/action_controller/test_case.rb +280 -411
  60. data/lib/action_dispatch.rb +27 -19
  61. data/lib/action_dispatch/http/cache.rb +93 -47
  62. data/lib/action_dispatch/http/content_security_policy.rb +272 -0
  63. data/lib/action_dispatch/http/filter_parameters.rb +26 -20
  64. data/lib/action_dispatch/http/filter_redirect.rb +10 -11
  65. data/lib/action_dispatch/http/headers.rb +55 -22
  66. data/lib/action_dispatch/http/mime_negotiation.rb +60 -41
  67. data/lib/action_dispatch/http/mime_type.rb +134 -121
  68. data/lib/action_dispatch/http/mime_types.rb +20 -6
  69. data/lib/action_dispatch/http/parameter_filter.rb +25 -11
  70. data/lib/action_dispatch/http/parameters.rb +98 -39
  71. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  72. data/lib/action_dispatch/http/request.rb +200 -118
  73. data/lib/action_dispatch/http/response.rb +225 -110
  74. data/lib/action_dispatch/http/upload.rb +12 -6
  75. data/lib/action_dispatch/http/url.rb +110 -28
  76. data/lib/action_dispatch/journey.rb +7 -5
  77. data/lib/action_dispatch/journey/formatter.rb +55 -32
  78. data/lib/action_dispatch/journey/gtg/builder.rb +7 -5
  79. data/lib/action_dispatch/journey/gtg/simulator.rb +3 -9
  80. data/lib/action_dispatch/journey/gtg/transition_table.rb +17 -16
  81. data/lib/action_dispatch/journey/nfa/builder.rb +5 -3
  82. data/lib/action_dispatch/journey/nfa/dot.rb +13 -13
  83. data/lib/action_dispatch/journey/nfa/simulator.rb +3 -1
  84. data/lib/action_dispatch/journey/nfa/transition_table.rb +5 -48
  85. data/lib/action_dispatch/journey/nodes/node.rb +18 -6
  86. data/lib/action_dispatch/journey/parser.rb +23 -22
  87. data/lib/action_dispatch/journey/parser.y +3 -2
  88. data/lib/action_dispatch/journey/parser_extras.rb +12 -4
  89. data/lib/action_dispatch/journey/path/pattern.rb +50 -44
  90. data/lib/action_dispatch/journey/route.rb +106 -28
  91. data/lib/action_dispatch/journey/router.rb +35 -23
  92. data/lib/action_dispatch/journey/router/utils.rb +20 -11
  93. data/lib/action_dispatch/journey/routes.rb +18 -16
  94. data/lib/action_dispatch/journey/scanner.rb +18 -15
  95. data/lib/action_dispatch/journey/visitors.rb +99 -52
  96. data/lib/action_dispatch/middleware/callbacks.rb +1 -2
  97. data/lib/action_dispatch/middleware/cookies.rb +304 -193
  98. data/lib/action_dispatch/middleware/debug_exceptions.rb +152 -57
  99. data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
  100. data/lib/action_dispatch/middleware/exception_wrapper.rb +68 -69
  101. data/lib/action_dispatch/middleware/executor.rb +21 -0
  102. data/lib/action_dispatch/middleware/flash.rb +78 -54
  103. data/lib/action_dispatch/middleware/public_exceptions.rb +27 -25
  104. data/lib/action_dispatch/middleware/reloader.rb +5 -91
  105. data/lib/action_dispatch/middleware/remote_ip.rb +41 -31
  106. data/lib/action_dispatch/middleware/request_id.rb +17 -9
  107. data/lib/action_dispatch/middleware/session/abstract_store.rb +41 -25
  108. data/lib/action_dispatch/middleware/session/cache_store.rb +24 -14
  109. data/lib/action_dispatch/middleware/session/cookie_store.rb +72 -67
  110. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -2
  111. data/lib/action_dispatch/middleware/show_exceptions.rb +26 -22
  112. data/lib/action_dispatch/middleware/ssl.rb +114 -36
  113. data/lib/action_dispatch/middleware/stack.rb +31 -44
  114. data/lib/action_dispatch/middleware/static.rb +57 -50
  115. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
  116. data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +0 -0
  117. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +21 -0
  119. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +13 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +1 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -1
  122. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  123. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
  124. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +64 -64
  125. data/lib/action_dispatch/railtie.rb +19 -11
  126. data/lib/action_dispatch/request/session.rb +106 -59
  127. data/lib/action_dispatch/request/utils.rb +67 -24
  128. data/lib/action_dispatch/routing.rb +17 -18
  129. data/lib/action_dispatch/routing/endpoint.rb +9 -2
  130. data/lib/action_dispatch/routing/inspector.rb +58 -67
  131. data/lib/action_dispatch/routing/mapper.rb +734 -447
  132. data/lib/action_dispatch/routing/polymorphic_routes.rb +161 -139
  133. data/lib/action_dispatch/routing/redirection.rb +36 -26
  134. data/lib/action_dispatch/routing/route_set.rb +321 -291
  135. data/lib/action_dispatch/routing/routes_proxy.rb +32 -5
  136. data/lib/action_dispatch/routing/url_for.rb +65 -25
  137. data/lib/action_dispatch/system_test_case.rb +147 -0
  138. data/lib/action_dispatch/system_testing/browser.rb +49 -0
  139. data/lib/action_dispatch/system_testing/driver.rb +59 -0
  140. data/lib/action_dispatch/system_testing/server.rb +31 -0
  141. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +96 -0
  142. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +31 -0
  143. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
  144. data/lib/action_dispatch/testing/assertion_response.rb +47 -0
  145. data/lib/action_dispatch/testing/assertions.rb +6 -4
  146. data/lib/action_dispatch/testing/assertions/response.rb +45 -20
  147. data/lib/action_dispatch/testing/assertions/routing.rb +30 -26
  148. data/lib/action_dispatch/testing/integration.rb +347 -209
  149. data/lib/action_dispatch/testing/request_encoder.rb +55 -0
  150. data/lib/action_dispatch/testing/test_process.rb +28 -22
  151. data/lib/action_dispatch/testing/test_request.rb +27 -34
  152. data/lib/action_dispatch/testing/test_response.rb +35 -7
  153. data/lib/action_pack.rb +4 -2
  154. data/lib/action_pack/gem_version.rb +5 -3
  155. data/lib/action_pack/version.rb +3 -1
  156. metadata +56 -39
  157. data/lib/action_controller/metal/hide_actions.rb +0 -40
  158. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  159. data/lib/action_controller/middleware.rb +0 -39
  160. data/lib/action_controller/model_naming.rb +0 -12
  161. data/lib/action_dispatch/journey/backwards.rb +0 -5
  162. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  163. data/lib/action_dispatch/middleware/params_parser.rb +0 -60
  164. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  165. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  166. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,31 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionController
2
4
  module Testing
3
5
  extend ActiveSupport::Concern
4
6
 
5
- include RackDelegation
6
-
7
- # TODO : Rewrite tests using controller.headers= to use Rack env
8
- def headers=(new_headers)
9
- @_response ||= ActionDispatch::Response.new
10
- @_response.headers.replace(new_headers)
11
- end
12
-
13
7
  # Behavior specific to functional tests
14
8
  module Functional # :nodoc:
15
- def set_response!(request)
16
- end
17
-
18
9
  def recycle!
19
10
  @_url_options = nil
20
11
  self.formats = nil
21
12
  self.params = nil
22
13
  end
23
14
  end
24
-
25
- module ClassMethods
26
- def before_filters
27
- _process_action_callbacks.find_all{|x| x.kind == :before}.map{|x| x.name}
28
- end
29
- end
30
15
  end
31
16
  end
@@ -1,10 +1,15 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionController
2
4
  # Includes +url_for+ into the host class. The class has to provide a +RouteSet+ by implementing
3
5
  # the <tt>_routes</tt> method. Otherwise, an exception will be raised.
4
6
  #
5
7
  # In addition to <tt>AbstractController::UrlFor</tt>, this module accesses the HTTP layer to define
6
- # url options like the +host+. In order to do so, this module requires the host class
7
- # to implement +env+ and +request+, which need to be a Rack-compatible.
8
+ # URL options like the +host+. In order to do so, this module requires the host class
9
+ # to implement +env+ which needs to be Rack-compatible and +request+
10
+ # which is either an instance of +ActionDispatch::Request+ or an object
11
+ # that responds to the +host+, +optional_port+, +protocol+ and
12
+ # +symbolized_path_parameter+ methods.
8
13
  #
9
14
  # class RootUrl
10
15
  # include ActionController::UrlFor
@@ -24,21 +29,25 @@ module ActionController
24
29
 
25
30
  def url_options
26
31
  @_url_options ||= {
27
- :host => request.host,
28
- :port => request.optional_port,
29
- :protocol => request.protocol,
30
- :_recall => request.path_parameters
32
+ host: request.host,
33
+ port: request.optional_port,
34
+ protocol: request.protocol,
35
+ _recall: request.path_parameters
31
36
  }.merge!(super).freeze
32
37
 
33
- if (same_origin = _routes.equal?(env["action_dispatch.routes".freeze])) ||
34
- (script_name = env["ROUTES_#{_routes.object_id}_SCRIPT_NAME"]) ||
35
- (original_script_name = env['ORIGINAL_SCRIPT_NAME'.freeze])
38
+ if (same_origin = _routes.equal?(request.routes)) ||
39
+ (script_name = request.engine_script_name(_routes)) ||
40
+ (original_script_name = request.original_script_name)
36
41
 
37
42
  options = @_url_options.dup
38
43
  if original_script_name
39
44
  options[:original_script_name] = original_script_name
40
45
  else
41
- options[:script_name] = same_origin ? request.script_name.dup : script_name
46
+ if same_origin
47
+ options[:script_name] = request.script_name.empty? ? "".freeze : request.script_name.dup
48
+ else
49
+ options[:script_name] = script_name
50
+ end
42
51
  end
43
52
  options.freeze
44
53
  else
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "rails"
2
4
  require "action_controller"
3
5
  require "action_dispatch/railtie"
@@ -11,7 +13,7 @@ module ActionController
11
13
 
12
14
  config.eager_load_namespaces << ActionController
13
15
 
14
- initializer "action_controller.assets_config", :group => :all do |app|
16
+ initializer "action_controller.assets_config", group: :all do |app|
15
17
  app.config.action_controller.assets_dir ||= app.config.paths["public"].first
16
18
  end
17
19
 
@@ -22,13 +24,15 @@ module ActionController
22
24
  initializer "action_controller.parameters_config" do |app|
23
25
  options = app.config.action_controller
24
26
 
25
- ActionController::Parameters.permit_all_parameters = options.delete(:permit_all_parameters) { false }
26
- if app.config.action_controller[:always_permitted_parameters]
27
- ActionController::Parameters.always_permitted_parameters =
28
- app.config.action_controller.delete(:always_permitted_parameters)
29
- end
30
- ActionController::Parameters.action_on_unpermitted_parameters = options.delete(:action_on_unpermitted_parameters) do
31
- (Rails.env.test? || Rails.env.development?) ? :log : false
27
+ ActiveSupport.on_load(:action_controller, run_once: true) do
28
+ ActionController::Parameters.permit_all_parameters = options.delete(:permit_all_parameters) { false }
29
+ if app.config.action_controller[:always_permitted_parameters]
30
+ ActionController::Parameters.always_permitted_parameters =
31
+ app.config.action_controller.delete(:always_permitted_parameters)
32
+ end
33
+ ActionController::Parameters.action_on_unpermitted_parameters = options.delete(:action_on_unpermitted_parameters) do
34
+ (Rails.env.test? || Rails.env.development?) ? :log : false
35
+ end
32
36
  end
33
37
  end
34
38
 
@@ -42,7 +46,7 @@ module ActionController
42
46
  options.javascripts_dir ||= paths["public/javascripts"].first
43
47
  options.stylesheets_dir ||= paths["public/stylesheets"].first
44
48
 
45
- # Ensure readers methods get compiled
49
+ # Ensure readers methods get compiled.
46
50
  options.asset_host ||= app.config.asset_host
47
51
  options.relative_url_root ||= app.config.relative_url_root
48
52
 
@@ -51,7 +55,7 @@ module ActionController
51
55
  extend ::AbstractController::Railties::RoutesHelpers.with(app.routes)
52
56
  extend ::ActionController::Railties::Helpers
53
57
 
54
- options.each do |k,v|
58
+ options.each do |k, v|
55
59
  k = "#{k}="
56
60
  if respond_to?(k)
57
61
  send(k, v)
@@ -67,5 +71,19 @@ module ActionController
67
71
  config.compile_methods! if config.respond_to?(:compile_methods!)
68
72
  end
69
73
  end
74
+
75
+ initializer "action_controller.request_forgery_protection" do |app|
76
+ ActiveSupport.on_load(:action_controller_base) do
77
+ if app.config.action_controller.default_protect_from_forgery
78
+ protect_from_forgery with: :exception
79
+ end
80
+ end
81
+ end
82
+
83
+ initializer "action_controller.eager_load_actions" do
84
+ ActiveSupport.on_load(:after_initialize) do
85
+ ActionController::Metal.descendants.each(&:action_methods) if config.eager_load
86
+ end
87
+ end
70
88
  end
71
89
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionController
2
4
  module Railties
3
5
  module Helpers
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/keys"
4
+
5
+ module ActionController
6
+ # ActionController::Renderer allows you to render arbitrary templates
7
+ # without requirement of being in controller actions.
8
+ #
9
+ # You get a concrete renderer class by invoking ActionController::Base#renderer.
10
+ # For example:
11
+ #
12
+ # ApplicationController.renderer
13
+ #
14
+ # It allows you to call method #render directly.
15
+ #
16
+ # ApplicationController.renderer.render template: '...'
17
+ #
18
+ # You can use this shortcut in a controller, instead of the previous example:
19
+ #
20
+ # ApplicationController.render template: '...'
21
+ #
22
+ # #render allows you to use the same options that you can use when rendering in a controller.
23
+ # For example:
24
+ #
25
+ # FooController.render :action, locals: { ... }, assigns: { ... }
26
+ #
27
+ # The template will be rendered in a Rack environment which is accessible through
28
+ # ActionController::Renderer#env. You can set it up in two ways:
29
+ #
30
+ # * by changing renderer defaults, like
31
+ #
32
+ # ApplicationController.renderer.defaults # => hash with default Rack environment
33
+ #
34
+ # * by initializing an instance of renderer by passing it a custom environment.
35
+ #
36
+ # ApplicationController.renderer.new(method: 'post', https: true)
37
+ #
38
+ class Renderer
39
+ attr_reader :defaults, :controller
40
+
41
+ DEFAULTS = {
42
+ http_host: "example.org",
43
+ https: false,
44
+ method: "get",
45
+ script_name: "",
46
+ input: ""
47
+ }.freeze
48
+
49
+ # Create a new renderer instance for a specific controller class.
50
+ def self.for(controller, env = {}, defaults = DEFAULTS.dup)
51
+ new(controller, env, defaults)
52
+ end
53
+
54
+ # Create a new renderer for the same controller but with a new env.
55
+ def new(env = {})
56
+ self.class.new controller, env, defaults
57
+ end
58
+
59
+ # Create a new renderer for the same controller but with new defaults.
60
+ def with_defaults(defaults)
61
+ self.class.new controller, @env, self.defaults.merge(defaults)
62
+ end
63
+
64
+ # Accepts a custom Rack environment to render templates in.
65
+ # It will be merged with the default Rack environment defined by
66
+ # +ActionController::Renderer::DEFAULTS+.
67
+ def initialize(controller, env, defaults)
68
+ @controller = controller
69
+ @defaults = defaults
70
+ @env = normalize_keys defaults.merge(env)
71
+ end
72
+
73
+ # Render templates with any options from ActionController::Base#render_to_string.
74
+ def render(*args)
75
+ raise "missing controller" unless controller
76
+
77
+ request = ActionDispatch::Request.new @env
78
+ request.routes = controller._routes
79
+
80
+ instance = controller.new
81
+ instance.set_request! request
82
+ instance.set_response! controller.make_response!(request)
83
+ instance.render_to_string(*args)
84
+ end
85
+
86
+ private
87
+ def normalize_keys(env)
88
+ new_env = {}
89
+ env.each_pair { |k, v| new_env[rack_key_for(k)] = rack_value_for(k, v) }
90
+ new_env["rack.url_scheme"] = new_env["HTTPS"] == "on" ? "https" : "http"
91
+ new_env
92
+ end
93
+
94
+ RACK_KEY_TRANSLATION = {
95
+ http_host: "HTTP_HOST",
96
+ https: "HTTPS",
97
+ method: "REQUEST_METHOD",
98
+ script_name: "SCRIPT_NAME",
99
+ input: "rack.input"
100
+ }
101
+
102
+ IDENTITY = ->(_) { _ }
103
+
104
+ RACK_VALUE_TRANSLATION = {
105
+ https: ->(v) { v ? "on" : "off" },
106
+ method: ->(v) { v.upcase },
107
+ }
108
+
109
+ def rack_key_for(key)
110
+ RACK_KEY_TRANSLATION.fetch(key, key.to_s)
111
+ end
112
+
113
+ def rack_value_for(key, value)
114
+ RACK_VALUE_TRANSLATION.fetch(key, IDENTITY).call value
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionController
4
+ module TemplateAssertions
5
+ def assert_template(options = {}, message = nil)
6
+ raise NoMethodError,
7
+ "assert_template has been extracted to a gem. To continue using it,
8
+ add `gem 'rails-controller-testing'` to your Gemfile."
9
+ end
10
+ end
11
+ end
@@ -1,224 +1,81 @@
1
- require 'rack/session/abstract/id'
2
- require 'active_support/core_ext/object/to_query'
3
- require 'active_support/core_ext/module/anonymous'
4
- require 'active_support/core_ext/hash/keys'
5
- require 'active_support/deprecation'
6
-
7
- require 'rails-dom-testing'
1
+ # frozen_string_literal: true
2
+
3
+ require "rack/session/abstract/id"
4
+ require "active_support/core_ext/hash/conversions"
5
+ require "active_support/core_ext/object/to_query"
6
+ require "active_support/core_ext/module/anonymous"
7
+ require "active_support/core_ext/module/redefine_method"
8
+ require "active_support/core_ext/hash/keys"
9
+ require "active_support/testing/constant_lookup"
10
+ require "action_controller/template_assertions"
11
+ require "rails-dom-testing"
8
12
 
9
13
  module ActionController
10
- module TemplateAssertions
11
- extend ActiveSupport::Concern
14
+ class Metal
15
+ include Testing::Functional
16
+ end
12
17
 
13
- included do
14
- setup :setup_subscriptions
15
- teardown :teardown_subscriptions
18
+ module Live
19
+ # Disable controller / rendering threads in tests. User tests can access
20
+ # the database on the main thread, so they could open a txn, then the
21
+ # controller thread will open a new connection and try to access data
22
+ # that's only visible to the main thread's txn. This is the problem in #23483.
23
+ silence_redefinition_of_method :new_controller_thread
24
+ def new_controller_thread # :nodoc:
25
+ yield
16
26
  end
27
+ end
17
28
 
18
- RENDER_TEMPLATE_INSTANCE_VARIABLES = %w{partials templates layouts files}.freeze
19
-
20
- def setup_subscriptions
21
- RENDER_TEMPLATE_INSTANCE_VARIABLES.each do |instance_variable|
22
- instance_variable_set("@_#{instance_variable}", Hash.new(0))
23
- end
24
-
25
- @_subscribers = []
26
-
27
- @_subscribers << ActiveSupport::Notifications.subscribe("render_template.action_view") do |_name, _start, _finish, _id, payload|
28
- path = payload[:layout]
29
- if path
30
- @_layouts[path] += 1
31
- if path =~ /^layouts\/(.*)/
32
- @_layouts[$1] += 1
33
- end
34
- end
35
- end
36
-
37
- @_subscribers << ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload|
38
- if virtual_path = payload[:virtual_path]
39
- partial = virtual_path =~ /^.*\/_[^\/]*$/
40
-
41
- if partial
42
- @_partials[virtual_path] += 1
43
- @_partials[virtual_path.split("/").last] += 1
44
- end
29
+ # ActionController::TestCase will be deprecated and moved to a gem in Rails 5.1.
30
+ # Please use ActionDispatch::IntegrationTest going forward.
31
+ class TestRequest < ActionDispatch::TestRequest #:nodoc:
32
+ DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup
33
+ DEFAULT_ENV.delete "PATH_INFO"
45
34
 
46
- @_templates[virtual_path] += 1
47
- else
48
- path = payload[:identifier]
49
- if path
50
- @_files[path] += 1
51
- @_files[path.split("/").last] += 1
52
- end
53
- end
54
- end
35
+ def self.new_session
36
+ TestSession.new
55
37
  end
56
38
 
57
- def teardown_subscriptions
58
- return unless defined?(@_subscribers)
59
-
60
- @_subscribers.each do |subscriber|
61
- ActiveSupport::Notifications.unsubscribe(subscriber)
62
- end
63
- end
39
+ attr_reader :controller_class
64
40
 
65
- def process(*args)
66
- reset_template_assertion
67
- super
41
+ # Create a new test request with default `env` values.
42
+ def self.create(controller_class)
43
+ env = {}
44
+ env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
45
+ env["rack.request.cookie_hash"] = {}.with_indifferent_access
46
+ new(default_env.merge(env), new_session, controller_class)
68
47
  end
69
48
 
70
- def reset_template_assertion
71
- RENDER_TEMPLATE_INSTANCE_VARIABLES.each do |instance_variable|
72
- ivar_name = "@_#{instance_variable}"
73
- if instance_variable_defined?(ivar_name)
74
- instance_variable_get(ivar_name).clear
75
- end
76
- end
49
+ def self.default_env
50
+ DEFAULT_ENV
77
51
  end
52
+ private_class_method :default_env
78
53
 
79
- # Asserts that the request was rendered with the appropriate template file or partials.
80
- #
81
- # # assert that the "new" view template was rendered
82
- # assert_template "new"
83
- #
84
- # # assert that the exact template "admin/posts/new" was rendered
85
- # assert_template %r{\Aadmin/posts/new\Z}
86
- #
87
- # # assert that the layout 'admin' was rendered
88
- # assert_template layout: 'admin'
89
- # assert_template layout: 'layouts/admin'
90
- # assert_template layout: :admin
91
- #
92
- # # assert that no layout was rendered
93
- # assert_template layout: nil
94
- # assert_template layout: false
95
- #
96
- # # assert that the "_customer" partial was rendered twice
97
- # assert_template partial: '_customer', count: 2
98
- #
99
- # # assert that no partials were rendered
100
- # assert_template partial: false
101
- #
102
- # # assert that a file was rendered
103
- # assert_template file: "README.rdoc"
104
- #
105
- # # assert that no file was rendered
106
- # assert_template file: nil
107
- # assert_template file: false
108
- #
109
- # In a view test case, you can also assert that specific locals are passed
110
- # to partials:
111
- #
112
- # # assert that the "_customer" partial was rendered with a specific object
113
- # assert_template partial: '_customer', locals: { customer: @customer }
114
- def assert_template(options = {}, message = nil)
115
- # Force body to be read in case the template is being streamed.
116
- response.body
117
-
118
- case options
119
- when NilClass, Regexp, String, Symbol
120
- options = options.to_s if Symbol === options
121
- rendered = @_templates
122
- msg = message || sprintf("expecting <%s> but rendering with <%s>",
123
- options.inspect, rendered.keys)
124
- matches_template =
125
- case options
126
- when String
127
- !options.empty? && rendered.any? do |t, num|
128
- options_splited = options.split(File::SEPARATOR)
129
- t_splited = t.split(File::SEPARATOR)
130
- t_splited.last(options_splited.size) == options_splited
131
- end
132
- when Regexp
133
- rendered.any? { |t,num| t.match(options) }
134
- when NilClass
135
- rendered.blank?
136
- end
137
- assert matches_template, msg
138
- when Hash
139
- options.assert_valid_keys(:layout, :partial, :locals, :count, :file)
140
-
141
- if options.key?(:layout)
142
- expected_layout = options[:layout]
143
- msg = message || sprintf("expecting layout <%s> but action rendered <%s>",
144
- expected_layout, @_layouts.keys)
145
-
146
- case expected_layout
147
- when String, Symbol
148
- assert_includes @_layouts.keys, expected_layout.to_s, msg
149
- when Regexp
150
- assert(@_layouts.keys.any? {|l| l =~ expected_layout }, msg)
151
- when nil, false
152
- assert(@_layouts.empty?, msg)
153
- end
154
- end
54
+ def initialize(env, session, controller_class)
55
+ super(env)
155
56
 
156
- if options[:file]
157
- assert_includes @_files.keys, options[:file]
158
- elsif options.key?(:file)
159
- assert @_files.blank?, "expected no files but #{@_files.keys} was rendered"
160
- end
161
-
162
- if expected_partial = options[:partial]
163
- if expected_locals = options[:locals]
164
- if defined?(@_rendered_views)
165
- view = expected_partial.to_s.sub(/^_/, '').sub(/\/_(?=[^\/]+\z)/, '/')
166
-
167
- partial_was_not_rendered_msg = "expected %s to be rendered but it was not." % view
168
- assert_includes @_rendered_views.rendered_views, view, partial_was_not_rendered_msg
169
-
170
- msg = 'expecting %s to be rendered with %s but was with %s' % [expected_partial,
171
- expected_locals,
172
- @_rendered_views.locals_for(view)]
173
- assert(@_rendered_views.view_rendered?(view, options[:locals]), msg)
174
- else
175
- warn "the :locals option to #assert_template is only supported in a ActionView::TestCase"
176
- end
177
- elsif expected_count = options[:count]
178
- actual_count = @_partials[expected_partial]
179
- msg = message || sprintf("expecting %s to be rendered %s time(s) but rendered %s time(s)",
180
- expected_partial, expected_count, actual_count)
181
- assert(actual_count == expected_count.to_i, msg)
182
- else
183
- msg = message || sprintf("expecting partial <%s> but action rendered <%s>",
184
- options[:partial], @_partials.keys)
185
- assert_includes @_partials, expected_partial, msg
186
- end
187
- elsif options.key?(:partial)
188
- assert @_partials.empty?,
189
- "Expected no partials to be rendered"
190
- end
191
- else
192
- raise ArgumentError, "assert_template only accepts a String, Symbol, Hash, Regexp, or nil"
193
- end
57
+ self.session = session
58
+ self.session_options = TestSession::DEFAULT_OPTIONS.dup
59
+ @controller_class = controller_class
60
+ @custom_param_parsers = {
61
+ xml: lambda { |raw_post| Hash.from_xml(raw_post)["hash"] }
62
+ }
194
63
  end
195
- end
196
-
197
- class TestRequest < ActionDispatch::TestRequest #:nodoc:
198
- DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup
199
- DEFAULT_ENV.delete 'PATH_INFO'
200
64
 
201
- def initialize(env = {})
202
- super
65
+ def query_string=(string)
66
+ set_header Rack::QUERY_STRING, string
67
+ end
203
68
 
204
- self.session = TestSession.new
205
- self.session_options = TestSession::DEFAULT_OPTIONS.merge(:id => SecureRandom.hex(16))
69
+ def content_type=(type)
70
+ set_header "CONTENT_TYPE", type
206
71
  end
207
72
 
208
- def assign_parameters(routes, controller_path, action, parameters = {})
209
- parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action)
210
- extra_keys = routes.extra_keys(parameters)
211
- non_path_parameters = get? ? query_parameters : request_parameters
212
- parameters.each do |key, value|
213
- if value.is_a?(Array) && (value.frozen? || value.any?(&:frozen?))
214
- value = value.map{ |v| v.duplicable? ? v.dup : v }
215
- elsif value.is_a?(Hash) && (value.frozen? || value.any?{ |k,v| v.frozen? })
216
- value = Hash[value.map{ |k,v| [k, v.duplicable? ? v.dup : v] }]
217
- elsif value.frozen? && value.duplicable?
218
- value = value.dup
219
- end
73
+ def assign_parameters(routes, controller_path, action, parameters, generated_path, query_string_keys)
74
+ non_path_parameters = {}
75
+ path_parameters = {}
220
76
 
221
- if extra_keys.include?(key)
77
+ parameters.each do |key, value|
78
+ if query_string_keys.include?(key)
222
79
  non_path_parameters[key] = value
223
80
  else
224
81
  if value.is_a?(Array)
@@ -231,72 +88,89 @@ module ActionController
231
88
  end
232
89
  end
233
90
 
234
- # Clear the combined params hash in case it was already referenced.
235
- @env.delete("action_dispatch.request.parameters")
91
+ if get?
92
+ if query_string.blank?
93
+ self.query_string = non_path_parameters.to_query
94
+ end
95
+ else
96
+ if ENCODER.should_multipart?(non_path_parameters)
97
+ self.content_type = ENCODER.content_type
98
+ data = ENCODER.build_multipart non_path_parameters
99
+ else
100
+ fetch_header("CONTENT_TYPE") do |k|
101
+ set_header k, "application/x-www-form-urlencoded"
102
+ end
236
103
 
237
- # Clear the filter cache variables so they're not stale
238
- @filtered_parameters = @filtered_env = @filtered_path = nil
104
+ case content_mime_type.to_sym
105
+ when nil
106
+ raise "Unknown Content-Type: #{content_type}"
107
+ when :json
108
+ data = ActiveSupport::JSON.encode(non_path_parameters)
109
+ when :xml
110
+ data = non_path_parameters.to_xml
111
+ when :url_encoded_form
112
+ data = non_path_parameters.to_query
113
+ else
114
+ @custom_param_parsers[content_mime_type.symbol] = ->(_) { non_path_parameters }
115
+ data = non_path_parameters.to_query
116
+ end
117
+ end
239
118
 
240
- params = self.request_parameters.dup
241
- %w(controller action only_path).each do |k|
242
- params.delete(k)
243
- params.delete(k.to_sym)
119
+ data_stream = StringIO.new(data)
120
+ set_header "CONTENT_LENGTH", data_stream.length.to_s
121
+ set_header "rack.input", data_stream
244
122
  end
245
- data = params.to_query
246
123
 
247
- @env['CONTENT_LENGTH'] = data.length.to_s
248
- @env['rack.input'] = StringIO.new(data)
249
- end
124
+ fetch_header("PATH_INFO") do |k|
125
+ set_header k, generated_path
126
+ end
127
+ path_parameters[:controller] = controller_path
128
+ path_parameters[:action] = action
250
129
 
251
- def recycle!
252
- @formats = nil
253
- @env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
254
- @env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
255
- @method = @request_method = nil
256
- @fullpath = @ip = @remote_ip = @protocol = nil
257
- @env['action_dispatch.request.query_parameters'] = {}
258
- @set_cookies ||= {}
259
- @set_cookies.update(Hash[cookie_jar.instance_variable_get("@set_cookies").map{ |k,o| [k,o[:value]] }])
260
- deleted_cookies = cookie_jar.instance_variable_get("@delete_cookies")
261
- @set_cookies.reject!{ |k,v| deleted_cookies.include?(k) }
262
- cookie_jar.update(rack_cookies)
263
- cookie_jar.update(cookies)
264
- cookie_jar.update(@set_cookies)
265
- cookie_jar.recycle!
130
+ self.path_parameters = path_parameters
266
131
  end
267
132
 
268
- private
133
+ ENCODER = Class.new do
134
+ include Rack::Test::Utils
135
+
136
+ def should_multipart?(params)
137
+ # FIXME: lifted from Rack-Test. We should push this separation upstream.
138
+ multipart = false
139
+ query = lambda { |value|
140
+ case value
141
+ when Array
142
+ value.each(&query)
143
+ when Hash
144
+ value.values.each(&query)
145
+ when Rack::Test::UploadedFile
146
+ multipart = true
147
+ end
148
+ }
149
+ params.values.each(&query)
150
+ multipart
151
+ end
269
152
 
270
- def default_env
271
- DEFAULT_ENV
272
- end
273
- end
153
+ public :build_multipart
274
154
 
275
- class TestResponse < ActionDispatch::TestResponse
276
- def recycle!
277
- initialize
278
- end
279
- end
155
+ def content_type
156
+ "multipart/form-data; boundary=#{Rack::Test::MULTIPART_BOUNDARY}"
157
+ end
158
+ end.new
280
159
 
281
- class LiveTestResponse < Live::Response
282
- def recycle!
283
- @body = nil
284
- initialize
285
- end
160
+ private
286
161
 
287
- def body
288
- @body ||= super
289
- end
162
+ def params_parsers
163
+ super.merge @custom_param_parsers
164
+ end
165
+ end
290
166
 
167
+ class LiveTestResponse < Live::Response
291
168
  # Was the response successful?
292
169
  alias_method :success?, :successful?
293
170
 
294
171
  # Was the URL not found?
295
172
  alias_method :missing?, :not_found?
296
173
 
297
- # Were we redirected?
298
- alias_method :redirect?, :redirection?
299
-
300
174
  # Was there a server-side error?
301
175
  alias_method :error?, :server_error?
302
176
  end
@@ -304,7 +178,7 @@ module ActionController
304
178
  # Methods #destroy and #load! are overridden to avoid calling methods on the
305
179
  # @store object, which does not exist for the TestSession class.
306
180
  class TestSession < Rack::Session::Abstract::SessionHash #:nodoc:
307
- DEFAULT_OPTIONS = Rack::Session::Abstract::ID::DEFAULT_OPTIONS
181
+ DEFAULT_OPTIONS = Rack::Session::Abstract::Persisted::DEFAULT_OPTIONS
308
182
 
309
183
  def initialize(session = {})
310
184
  super(nil, nil)
@@ -341,10 +215,18 @@ module ActionController
341
215
  end
342
216
 
343
217
  # Superclass for ActionController functional tests. Functional tests allow you to
344
- # test a single controller action per test method. This should not be confused with
345
- # integration tests (see ActionDispatch::IntegrationTest), which are more like
346
- # "stories" that can involve multiple controllers and multiple actions (i.e. multiple
347
- # different HTTP requests).
218
+ # test a single controller action per test method.
219
+ #
220
+ # == Use integration style controller tests over functional style controller tests.
221
+ #
222
+ # Rails discourages the use of functional tests in favor of integration tests
223
+ # (use ActionDispatch::IntegrationTest).
224
+ #
225
+ # New Rails applications no longer generate functional style controller tests and they should
226
+ # only be used for backward compatibility. Integration style controller tests perform actual
227
+ # requests, whereas functional style controller tests merely simulate a request. Besides,
228
+ # integration tests are as fast as functional tests and provide lot of helpers such as +as+,
229
+ # +parsed_body+ for effective testing of controller actions including even API endpoints.
348
230
  #
349
231
  # == Basic example
350
232
  #
@@ -359,13 +241,13 @@ module ActionController
359
241
  # class BooksControllerTest < ActionController::TestCase
360
242
  # def test_create
361
243
  # # Simulate a POST response with the given HTTP parameters.
362
- # post(:create, book: { title: "Love Hina" })
244
+ # post(:create, params: { book: { title: "Love Hina" }})
363
245
  #
364
- # # Assert that the controller tried to redirect us to
246
+ # # Asserts that the controller tried to redirect us to
365
247
  # # the created book's URI.
366
248
  # assert_response :found
367
249
  #
368
- # # Assert that the controller really put the book in the database.
250
+ # # Asserts that the controller really put the book in the database.
369
251
  # assert_not_nil Book.find_by(title: "Love Hina")
370
252
  # end
371
253
  # end
@@ -374,7 +256,7 @@ module ActionController
374
256
  #
375
257
  # def test_create
376
258
  # json = {book: { title: "Love Hina" }}.to_json
377
- # post :create, json
259
+ # post :create, body: json
378
260
  # end
379
261
  #
380
262
  # == Special instance variables
@@ -389,7 +271,7 @@ module ActionController
389
271
  # request. You can modify this object before sending the HTTP request. For example,
390
272
  # you might want to set some session properties before sending a GET request.
391
273
  # <b>@response</b>::
392
- # An ActionController::TestResponse object, representing the response
274
+ # An ActionDispatch::TestResponse object, representing the response
393
275
  # of the last HTTP response. In the above example, <tt>@response</tt> becomes valid
394
276
  # after calling +post+. If the various assert methods are not sufficient, then you
395
277
  # may use this object to inspect the HTTP response in detail.
@@ -412,22 +294,16 @@ module ActionController
412
294
  # In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions
413
295
  # can be used against. These collections are:
414
296
  #
415
- # * assigns: Instance variables assigned in the action that are available for the view.
416
297
  # * session: Objects being saved in the session.
417
298
  # * flash: The flash objects currently in the session.
418
299
  # * cookies: \Cookies being sent to the user on this request.
419
300
  #
420
301
  # These collections can be used just like any other hash:
421
302
  #
422
- # assert_not_nil assigns(:person) # makes sure that a @person instance variable was set
423
303
  # assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
424
304
  # assert flash.empty? # makes sure that there's nothing in the flash
425
305
  #
426
- # For historic reasons, the assigns hash uses string-based keys. So <tt>assigns[:person]</tt> won't work, but <tt>assigns["person"]</tt> will. To
427
- # appease our yearning for symbols, though, an alternative accessor has been devised using a method call instead of index referencing.
428
- # So <tt>assigns(:person)</tt> will work just like <tt>assigns["person"]</tt>, but again, <tt>assigns[:person]</tt> will not work.
429
- #
430
- # On top of the collections, you have the complete url that a given action redirected to available in <tt>redirect_to_url</tt>.
306
+ # On top of the collections, you have the complete URL that a given action redirected to available in <tt>redirect_to_url</tt>.
431
307
  #
432
308
  # For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
433
309
  # action call which can then be asserted against.
@@ -459,7 +335,6 @@ module ActionController
459
335
  attr_reader :response, :request
460
336
 
461
337
  module ClassMethods
462
-
463
338
  # Sets the controller class name. Useful if the name can't be inferred from test class.
464
339
  # Normalizes +controller_class+ before using.
465
340
  #
@@ -482,7 +357,7 @@ module ActionController
482
357
  end
483
358
 
484
359
  def controller_class
485
- if current_controller_class = self._controller_class
360
+ if current_controller_class = _controller_class
486
361
  current_controller_class
487
362
  else
488
363
  self.controller_class = determine_default_controller_class(name)
@@ -499,169 +374,197 @@ module ActionController
499
374
  # Simulate a GET request with the given parameters.
500
375
  #
501
376
  # - +action+: The controller action to call.
502
- # - +parameters+: The HTTP parameters that you want to pass. This may
503
- # be +nil+, a hash, or a string that is appropriately encoded
377
+ # - +params+: The hash with HTTP parameters that you want to pass. This may be +nil+.
378
+ # - +body+: The request body with a string that is appropriately encoded
504
379
  # (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
505
380
  # - +session+: A hash of parameters to store in the session. This may be +nil+.
506
381
  # - +flash+: A hash of parameters to store in the flash. This may be +nil+.
507
382
  #
508
383
  # You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with
509
384
  # +post+, +patch+, +put+, +delete+, and +head+.
385
+ # Example sending parameters, session and setting a flash message:
386
+ #
387
+ # get :show,
388
+ # params: { id: 7 },
389
+ # session: { user_id: 1 },
390
+ # flash: { notice: 'This is flash message' }
510
391
  #
511
392
  # Note that the request method is not verified. The different methods are
512
393
  # available to make the tests more expressive.
513
- def get(action, *args)
514
- process(action, "GET", *args)
394
+ def get(action, **args)
395
+ res = process(action, method: "GET", **args)
396
+ cookies.update res.cookies
397
+ res
515
398
  end
516
399
 
517
400
  # Simulate a POST request with the given parameters and set/volley the response.
518
401
  # See +get+ for more details.
519
- def post(action, *args)
520
- process(action, "POST", *args)
402
+ def post(action, **args)
403
+ process(action, method: "POST", **args)
521
404
  end
522
405
 
523
406
  # Simulate a PATCH request with the given parameters and set/volley the response.
524
407
  # See +get+ for more details.
525
- def patch(action, *args)
526
- process(action, "PATCH", *args)
408
+ def patch(action, **args)
409
+ process(action, method: "PATCH", **args)
527
410
  end
528
411
 
529
412
  # Simulate a PUT request with the given parameters and set/volley the response.
530
413
  # See +get+ for more details.
531
- def put(action, *args)
532
- process(action, "PUT", *args)
414
+ def put(action, **args)
415
+ process(action, method: "PUT", **args)
533
416
  end
534
417
 
535
418
  # Simulate a DELETE request with the given parameters and set/volley the response.
536
419
  # See +get+ for more details.
537
- def delete(action, *args)
538
- process(action, "DELETE", *args)
420
+ def delete(action, **args)
421
+ process(action, method: "DELETE", **args)
539
422
  end
540
423
 
541
424
  # Simulate a HEAD request with the given parameters and set/volley the response.
542
425
  # See +get+ for more details.
543
- def head(action, *args)
544
- process(action, "HEAD", *args)
545
- end
546
-
547
- def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
548
- @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
549
- @request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
550
- __send__(request_method, action, parameters, session, flash).tap do
551
- @request.env.delete 'HTTP_X_REQUESTED_WITH'
552
- @request.env.delete 'HTTP_ACCEPT'
553
- end
554
- end
555
- alias xhr :xml_http_request
556
-
557
- def paramify_values(hash_or_array_or_value)
558
- case hash_or_array_or_value
559
- when Hash
560
- Hash[hash_or_array_or_value.map{|key, value| [key, paramify_values(value)] }]
561
- when Array
562
- hash_or_array_or_value.map {|i| paramify_values(i)}
563
- when Rack::Test::UploadedFile, ActionDispatch::Http::UploadedFile
564
- hash_or_array_or_value
565
- else
566
- hash_or_array_or_value.to_param
567
- end
426
+ def head(action, **args)
427
+ process(action, method: "HEAD", **args)
568
428
  end
569
429
 
570
- # Simulate a HTTP request to +action+ by specifying request method,
430
+ # Simulate an HTTP request to +action+ by specifying request method,
571
431
  # parameters and set/volley the response.
572
432
  #
573
433
  # - +action+: The controller action to call.
574
- # - +http_method+: Request method used to send the http request. Possible values
575
- # are +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, +HEAD+. Defaults to +GET+.
576
- # - +parameters+: The HTTP parameters. This may be +nil+, a hash, or a
577
- # string that is appropriately encoded (+application/x-www-form-urlencoded+
578
- # or +multipart/form-data+).
434
+ # - +method+: Request method used to send the HTTP request. Possible values
435
+ # are +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, +HEAD+. Defaults to +GET+. Can be a symbol.
436
+ # - +params+: The hash with HTTP parameters that you want to pass. This may be +nil+.
437
+ # - +body+: The request body with a string that is appropriately encoded
438
+ # (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
579
439
  # - +session+: A hash of parameters to store in the session. This may be +nil+.
580
440
  # - +flash+: A hash of parameters to store in the flash. This may be +nil+.
441
+ # - +format+: Request format. Defaults to +nil+. Can be string or symbol.
442
+ # - +as+: Content type. Defaults to +nil+. Must be a symbol that corresponds
443
+ # to a mime type.
581
444
  #
582
445
  # Example calling +create+ action and sending two params:
583
446
  #
584
- # process :create, 'POST', user: { name: 'Gaurish Sharma', email: 'user@example.com' }
585
- #
586
- # Example sending parameters, +nil+ session and setting a flash message:
587
- #
588
- # process :view, 'GET', { id: 7 }, nil, { notice: 'This is flash message' }
447
+ # process :create,
448
+ # method: 'POST',
449
+ # params: {
450
+ # user: { name: 'Gaurish Sharma', email: 'user@example.com' }
451
+ # },
452
+ # session: { user_id: 1 },
453
+ # flash: { notice: 'This is flash message' }
589
454
  #
590
455
  # To simulate +GET+, +POST+, +PATCH+, +PUT+, +DELETE+ and +HEAD+ requests
591
456
  # prefer using #get, #post, #patch, #put, #delete and #head methods
592
457
  # respectively which will make tests more expressive.
593
458
  #
594
459
  # Note that the request method is not verified.
595
- def process(action, http_method = 'GET', *args)
460
+ def process(action, method: "GET", params: nil, session: nil, body: nil, flash: {}, format: nil, xhr: false, as: nil)
596
461
  check_required_ivars
597
462
 
598
- if args.first.is_a?(String) && http_method != 'HEAD'
599
- @request.env['RAW_POST_DATA'] = args.shift
600
- end
463
+ action = action.to_s.dup
464
+ http_method = method.to_s.upcase
601
465
 
602
- parameters, session, flash = args
603
- parameters ||= {}
466
+ @html_document = nil
604
467
 
605
- # Ensure that numbers and symbols passed as params are converted to
606
- # proper params, as is the case when engaging rack.
607
- parameters = paramify_values(parameters) if html_format?(parameters)
468
+ cookies.update(@request.cookies)
469
+ cookies.update_cookies_from_jar
470
+ @request.set_header "HTTP_COOKIE", cookies.to_header
471
+ @request.delete_header "action_dispatch.cookies"
608
472
 
609
- @html_document = nil
610
- @html_scanner_document = nil
473
+ @request = TestRequest.new scrub_env!(@request.env), @request.session, @controller.class
474
+ @response = build_response @response_klass
475
+ @response.request = @request
476
+ @controller.recycle!
611
477
 
612
- unless @controller.respond_to?(:recycle!)
613
- @controller.extend(Testing::Functional)
478
+ if body
479
+ @request.set_header "RAW_POST_DATA", body
614
480
  end
615
481
 
616
- @request.recycle!
617
- @response.recycle!
618
- @controller.recycle!
482
+ @request.set_header "REQUEST_METHOD", http_method
619
483
 
620
- @request.env['REQUEST_METHOD'] = http_method
484
+ if as
485
+ @request.content_type = Mime[as].to_s
486
+ format ||= as
487
+ end
488
+
489
+ parameters = (params || {}).symbolize_keys
490
+
491
+ if format
492
+ parameters[:format] = format
493
+ end
621
494
 
622
- controller_class_name = @controller.class.anonymous? ?
623
- "anonymous" :
624
- @controller.class.controller_path
495
+ generated_extras = @routes.generate_extras(parameters.merge(controller: controller_class_name, action: action))
496
+ generated_path = generated_path(generated_extras)
497
+ query_string_keys = query_parameter_names(generated_extras)
625
498
 
626
- @request.assign_parameters(@routes, controller_class_name, action.to_s, parameters)
499
+ @request.assign_parameters(@routes, controller_class_name, action, parameters, generated_path, query_string_keys)
627
500
 
628
501
  @request.session.update(session) if session
629
502
  @request.flash.update(flash || {})
630
503
 
631
- @controller.request = @request
632
- @controller.response = @response
633
-
634
- build_request_uri(action, parameters)
504
+ if xhr
505
+ @request.set_header "HTTP_X_REQUESTED_WITH", "XMLHttpRequest"
506
+ @request.fetch_header("HTTP_ACCEPT") do |k|
507
+ @request.set_header k, [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(", ")
508
+ end
509
+ end
635
510
 
636
- name = @request.parameters[:action]
511
+ @request.fetch_header("SCRIPT_NAME") do |k|
512
+ @request.set_header k, @controller.config.relative_url_root
513
+ end
637
514
 
638
- @controller.recycle!
639
- @controller.process(name)
515
+ begin
516
+ @controller.recycle!
517
+ @controller.dispatch(action, @request, @response)
518
+ ensure
519
+ @request = @controller.request
520
+ @response = @controller.response
521
+
522
+ if @request.have_cookie_jar?
523
+ unless @request.cookie_jar.committed?
524
+ @request.cookie_jar.write(@response)
525
+ cookies.update(@request.cookie_jar.instance_variable_get(:@cookies))
526
+ end
527
+ end
528
+ @response.prepare!
640
529
 
641
- if cookies = @request.env['action_dispatch.cookies']
642
- unless @response.committed?
643
- cookies.write(@response)
530
+ if flash_value = @request.flash.to_session_value
531
+ @request.session["flash"] = flash_value
532
+ else
533
+ @request.session.delete("flash")
644
534
  end
645
- end
646
- @response.prepare!
647
535
 
648
- @assigns = @controller.respond_to?(:view_assigns) ? @controller.view_assigns : {}
536
+ if xhr
537
+ @request.delete_header "HTTP_X_REQUESTED_WITH"
538
+ @request.delete_header "HTTP_ACCEPT"
539
+ end
540
+ @request.query_string = ""
649
541
 
650
- if flash_value = @request.flash.to_session_value
651
- @request.session['flash'] = flash_value
542
+ @response.sent!
652
543
  end
653
544
 
654
545
  @response
655
546
  end
656
547
 
548
+ def controller_class_name
549
+ @controller.class.anonymous? ? "anonymous" : @controller.class.controller_path
550
+ end
551
+
552
+ def generated_path(generated_extras)
553
+ generated_extras[0]
554
+ end
555
+
556
+ def query_parameter_names(generated_extras)
557
+ generated_extras[1] + [:controller, :action]
558
+ end
559
+
657
560
  def setup_controller_request_and_response
658
561
  @controller = nil unless defined? @controller
659
562
 
660
- response_klass = TestResponse
563
+ @response_klass = ActionDispatch::TestResponse
661
564
 
662
565
  if klass = self.class.controller_class
663
566
  if klass < ActionController::Live
664
- response_klass = LiveTestResponse
567
+ @response_klass = LiveTestResponse
665
568
  end
666
569
  unless @controller
667
570
  begin
@@ -672,8 +575,8 @@ module ActionController
672
575
  end
673
576
  end
674
577
 
675
- @request = build_request
676
- @response = build_response response_klass
578
+ @request = TestRequest.create(@controller.class)
579
+ @response = build_response @response_klass
677
580
  @response.request = @request
678
581
 
679
582
  if @controller
@@ -682,12 +585,8 @@ module ActionController
682
585
  end
683
586
  end
684
587
 
685
- def build_request
686
- TestRequest.new
687
- end
688
-
689
588
  def build_response(klass)
690
- klass.new
589
+ klass.create
691
590
  end
692
591
 
693
592
  included do
@@ -695,65 +594,35 @@ module ActionController
695
594
  include ActionDispatch::Assertions
696
595
  class_attribute :_controller_class
697
596
  setup :setup_controller_request_and_response
597
+ ActiveSupport.run_load_hooks(:action_controller_test_case, self)
698
598
  end
699
599
 
700
600
  private
701
601
 
702
- def document_root_element
703
- html_document.root
704
- end
602
+ def scrub_env!(env)
603
+ env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
604
+ env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
605
+ env.delete "action_dispatch.request.query_parameters"
606
+ env.delete "action_dispatch.request.request_parameters"
607
+ env["rack.input"] = StringIO.new
608
+ env.delete "CONTENT_LENGTH"
609
+ env.delete "RAW_POST_DATA"
610
+ env
611
+ end
705
612
 
706
- def check_required_ivars
707
- # Sanity check for required instance variables so we can give an
708
- # understandable error message.
709
- [:@routes, :@controller, :@request, :@response].each do |iv_name|
710
- if !instance_variable_defined?(iv_name) || instance_variable_get(iv_name).nil?
711
- raise "#{iv_name} is nil: make sure you set it in your test's setup method."
712
- end
613
+ def document_root_element
614
+ html_document.root
713
615
  end
714
- end
715
616
 
716
- def build_request_uri(action, parameters)
717
- unless @request.env["PATH_INFO"]
718
- options = @controller.respond_to?(:url_options) ? @controller.__send__(:url_options).merge(parameters) : parameters
719
- options.update(
720
- :action => action,
721
- :relative_url_root => nil,
722
- :_recall => @request.path_parameters)
723
-
724
- if route_name = options.delete(:use_route)
725
- ActiveSupport::Deprecation.warn <<-MSG.squish
726
- Passing the `use_route` option in functional tests are deprecated.
727
- Support for this option in the `process` method (and the related
728
- `get`, `head`, `post`, `patch`, `put` and `delete` helpers) will
729
- be removed in the next version without replacement.
730
-
731
- Functional tests are essentially unit tests for controllers and
732
- they should not require knowledge to how the application's routes
733
- are configured. Instead, you should explicitly pass the appropiate
734
- params to the `process` method.
735
-
736
- Previously the engines guide also contained an incorrect example
737
- that recommended using this option to test an engine's controllers
738
- within the dummy application. That recommendation was incorrect
739
- and has since been corrected. Instead, you should override the
740
- `@routes` variable in the test case with `Foo::Engine.routes`. See
741
- the updated engines guide for details.
742
- MSG
617
+ def check_required_ivars
618
+ # Sanity check for required instance variables so we can give an
619
+ # understandable error message.
620
+ [:@routes, :@controller, :@request, :@response].each do |iv_name|
621
+ if !instance_variable_defined?(iv_name) || instance_variable_get(iv_name).nil?
622
+ raise "#{iv_name} is nil: make sure you set it in your test's setup method."
623
+ end
743
624
  end
744
-
745
- url, query_string = @routes.path_for(options, route_name).split("?", 2)
746
-
747
- @request.env["SCRIPT_NAME"] = @controller.config.relative_url_root
748
- @request.env["PATH_INFO"] = url
749
- @request.env["QUERY_STRING"] = query_string || ""
750
625
  end
751
- end
752
-
753
- def html_format?(parameters)
754
- return true unless parameters.key?(:format)
755
- Mime.fetch(parameters[:format]) { Mime['html'] }.html?
756
- end
757
626
  end
758
627
 
759
628
  include Behavior