actionpack 4.2.10 → 7.2.0.rc1

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 (202) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +86 -600
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +13 -14
  5. data/lib/abstract_controller/asset_paths.rb +5 -1
  6. data/lib/abstract_controller/base.rb +166 -136
  7. data/lib/abstract_controller/caching/fragments.rb +149 -0
  8. data/lib/abstract_controller/caching.rb +68 -0
  9. data/lib/abstract_controller/callbacks.rb +126 -57
  10. data/lib/abstract_controller/collector.rb +13 -15
  11. data/lib/abstract_controller/deprecator.rb +9 -0
  12. data/lib/abstract_controller/error.rb +8 -0
  13. data/lib/abstract_controller/helpers.rb +181 -132
  14. data/lib/abstract_controller/logger.rb +5 -1
  15. data/lib/abstract_controller/railties/routes_helpers.rb +10 -3
  16. data/lib/abstract_controller/rendering.rb +56 -56
  17. data/lib/abstract_controller/translation.rb +29 -15
  18. data/lib/abstract_controller/url_for.rb +15 -11
  19. data/lib/abstract_controller.rb +21 -5
  20. data/lib/action_controller/api/api_rendering.rb +18 -0
  21. data/lib/action_controller/api.rb +154 -0
  22. data/lib/action_controller/base.rb +219 -155
  23. data/lib/action_controller/caching.rb +28 -68
  24. data/lib/action_controller/deprecator.rb +9 -0
  25. data/lib/action_controller/form_builder.rb +55 -0
  26. data/lib/action_controller/log_subscriber.rb +35 -22
  27. data/lib/action_controller/metal/allow_browser.rb +119 -0
  28. data/lib/action_controller/metal/basic_implicit_render.rb +17 -0
  29. data/lib/action_controller/metal/conditional_get.rb +259 -122
  30. data/lib/action_controller/metal/content_security_policy.rb +86 -0
  31. data/lib/action_controller/metal/cookies.rb +9 -5
  32. data/lib/action_controller/metal/data_streaming.rb +87 -104
  33. data/lib/action_controller/metal/default_headers.rb +21 -0
  34. data/lib/action_controller/metal/etag_with_flash.rb +22 -0
  35. data/lib/action_controller/metal/etag_with_template_digest.rb +35 -26
  36. data/lib/action_controller/metal/exceptions.rb +71 -24
  37. data/lib/action_controller/metal/flash.rb +26 -19
  38. data/lib/action_controller/metal/head.rb +45 -36
  39. data/lib/action_controller/metal/helpers.rb +80 -64
  40. data/lib/action_controller/metal/http_authentication.rb +297 -244
  41. data/lib/action_controller/metal/implicit_render.rb +57 -9
  42. data/lib/action_controller/metal/instrumentation.rb +76 -64
  43. data/lib/action_controller/metal/live.rb +238 -176
  44. data/lib/action_controller/metal/logging.rb +22 -0
  45. data/lib/action_controller/metal/mime_responds.rb +177 -166
  46. data/lib/action_controller/metal/parameter_encoding.rb +84 -0
  47. data/lib/action_controller/metal/params_wrapper.rb +145 -118
  48. data/lib/action_controller/metal/permissions_policy.rb +38 -0
  49. data/lib/action_controller/metal/rate_limiting.rb +62 -0
  50. data/lib/action_controller/metal/redirecting.rb +203 -64
  51. data/lib/action_controller/metal/renderers.rb +108 -65
  52. data/lib/action_controller/metal/rendering.rb +216 -56
  53. data/lib/action_controller/metal/request_forgery_protection.rb +496 -163
  54. data/lib/action_controller/metal/rescue.rb +19 -21
  55. data/lib/action_controller/metal/streaming.rb +179 -138
  56. data/lib/action_controller/metal/strong_parameters.rb +1058 -382
  57. data/lib/action_controller/metal/testing.rb +11 -17
  58. data/lib/action_controller/metal/url_for.rb +37 -21
  59. data/lib/action_controller/metal.rb +236 -138
  60. data/lib/action_controller/railtie.rb +89 -11
  61. data/lib/action_controller/railties/helpers.rb +5 -1
  62. data/lib/action_controller/renderer.rb +161 -0
  63. data/lib/action_controller/template_assertions.rb +13 -0
  64. data/lib/action_controller/test_case.rb +425 -497
  65. data/lib/action_controller.rb +44 -22
  66. data/lib/action_dispatch/constants.rb +34 -0
  67. data/lib/action_dispatch/deprecator.rb +9 -0
  68. data/lib/action_dispatch/http/cache.rb +119 -63
  69. data/lib/action_dispatch/http/content_disposition.rb +47 -0
  70. data/lib/action_dispatch/http/content_security_policy.rb +364 -0
  71. data/lib/action_dispatch/http/filter_parameters.rb +36 -34
  72. data/lib/action_dispatch/http/filter_redirect.rb +24 -12
  73. data/lib/action_dispatch/http/headers.rb +66 -31
  74. data/lib/action_dispatch/http/mime_negotiation.rb +106 -75
  75. data/lib/action_dispatch/http/mime_type.rb +196 -136
  76. data/lib/action_dispatch/http/mime_types.rb +25 -7
  77. data/lib/action_dispatch/http/parameters.rb +97 -45
  78. data/lib/action_dispatch/http/permissions_policy.rb +187 -0
  79. data/lib/action_dispatch/http/rack_cache.rb +6 -0
  80. data/lib/action_dispatch/http/request.rb +299 -170
  81. data/lib/action_dispatch/http/response.rb +311 -160
  82. data/lib/action_dispatch/http/upload.rb +52 -23
  83. data/lib/action_dispatch/http/url.rb +201 -125
  84. data/lib/action_dispatch/journey/formatter.rb +110 -50
  85. data/lib/action_dispatch/journey/gtg/builder.rb +37 -50
  86. data/lib/action_dispatch/journey/gtg/simulator.rb +20 -17
  87. data/lib/action_dispatch/journey/gtg/transition_table.rb +96 -36
  88. data/lib/action_dispatch/journey/nfa/dot.rb +5 -14
  89. data/lib/action_dispatch/journey/nodes/node.rb +100 -20
  90. data/lib/action_dispatch/journey/parser.rb +19 -17
  91. data/lib/action_dispatch/journey/parser.y +4 -3
  92. data/lib/action_dispatch/journey/parser_extras.rb +14 -4
  93. data/lib/action_dispatch/journey/path/pattern.rb +79 -63
  94. data/lib/action_dispatch/journey/route.rb +108 -44
  95. data/lib/action_dispatch/journey/router/utils.rb +41 -29
  96. data/lib/action_dispatch/journey/router.rb +64 -57
  97. data/lib/action_dispatch/journey/routes.rb +23 -21
  98. data/lib/action_dispatch/journey/scanner.rb +28 -17
  99. data/lib/action_dispatch/journey/visitors.rb +100 -54
  100. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  101. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  102. data/lib/action_dispatch/journey.rb +7 -5
  103. data/lib/action_dispatch/log_subscriber.rb +25 -0
  104. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  105. data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
  106. data/lib/action_dispatch/middleware/callbacks.rb +7 -6
  107. data/lib/action_dispatch/middleware/cookies.rb +471 -328
  108. data/lib/action_dispatch/middleware/debug_exceptions.rb +149 -66
  109. data/lib/action_dispatch/middleware/debug_locks.rb +129 -0
  110. data/lib/action_dispatch/middleware/debug_view.rb +73 -0
  111. data/lib/action_dispatch/middleware/exception_wrapper.rb +275 -73
  112. data/lib/action_dispatch/middleware/executor.rb +32 -0
  113. data/lib/action_dispatch/middleware/flash.rb +143 -101
  114. data/lib/action_dispatch/middleware/host_authorization.rb +171 -0
  115. data/lib/action_dispatch/middleware/public_exceptions.rb +36 -27
  116. data/lib/action_dispatch/middleware/reloader.rb +10 -92
  117. data/lib/action_dispatch/middleware/remote_ip.rb +133 -107
  118. data/lib/action_dispatch/middleware/request_id.rb +29 -15
  119. data/lib/action_dispatch/middleware/server_timing.rb +78 -0
  120. data/lib/action_dispatch/middleware/session/abstract_store.rb +49 -27
  121. data/lib/action_dispatch/middleware/session/cache_store.rb +33 -16
  122. data/lib/action_dispatch/middleware/session/cookie_store.rb +86 -80
  123. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +15 -3
  124. data/lib/action_dispatch/middleware/show_exceptions.rb +66 -36
  125. data/lib/action_dispatch/middleware/ssl.rb +134 -36
  126. data/lib/action_dispatch/middleware/stack.rb +109 -44
  127. data/lib/action_dispatch/middleware/static.rb +159 -90
  128. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +7 -24
  132. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  133. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +36 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +46 -36
  136. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +12 -0
  137. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +9 -0
  138. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +26 -7
  139. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +3 -3
  140. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
  141. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +16 -0
  142. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +139 -15
  143. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +23 -0
  144. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  145. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +6 -6
  146. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +7 -7
  147. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +9 -9
  148. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  149. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +4 -4
  150. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  151. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +7 -4
  152. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +125 -93
  153. data/lib/action_dispatch/railtie.rb +44 -16
  154. data/lib/action_dispatch/request/session.rb +159 -69
  155. data/lib/action_dispatch/request/utils.rb +97 -23
  156. data/lib/action_dispatch/routing/endpoint.rb +11 -2
  157. data/lib/action_dispatch/routing/inspector.rb +195 -106
  158. data/lib/action_dispatch/routing/mapper.rb +1338 -955
  159. data/lib/action_dispatch/routing/polymorphic_routes.rb +234 -201
  160. data/lib/action_dispatch/routing/redirection.rb +78 -51
  161. data/lib/action_dispatch/routing/route_set.rb +460 -374
  162. data/lib/action_dispatch/routing/routes_proxy.rb +36 -12
  163. data/lib/action_dispatch/routing/url_for.rb +172 -124
  164. data/lib/action_dispatch/routing.rb +159 -158
  165. data/lib/action_dispatch/system_test_case.rb +206 -0
  166. data/lib/action_dispatch/system_testing/browser.rb +84 -0
  167. data/lib/action_dispatch/system_testing/driver.rb +85 -0
  168. data/lib/action_dispatch/system_testing/server.rb +33 -0
  169. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +164 -0
  170. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +23 -0
  171. data/lib/action_dispatch/testing/assertion_response.rb +48 -0
  172. data/lib/action_dispatch/testing/assertions/response.rb +71 -39
  173. data/lib/action_dispatch/testing/assertions/routing.rb +228 -103
  174. data/lib/action_dispatch/testing/assertions.rb +9 -6
  175. data/lib/action_dispatch/testing/integration.rb +486 -306
  176. data/lib/action_dispatch/testing/request_encoder.rb +60 -0
  177. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  178. data/lib/action_dispatch/testing/test_process.rb +35 -22
  179. data/lib/action_dispatch/testing/test_request.rb +29 -34
  180. data/lib/action_dispatch/testing/test_response.rb +48 -15
  181. data/lib/action_dispatch.rb +82 -40
  182. data/lib/action_pack/gem_version.rb +8 -4
  183. data/lib/action_pack/version.rb +6 -2
  184. data/lib/action_pack.rb +21 -18
  185. metadata +146 -56
  186. data/lib/action_controller/caching/fragments.rb +0 -103
  187. data/lib/action_controller/metal/force_ssl.rb +0 -97
  188. data/lib/action_controller/metal/hide_actions.rb +0 -40
  189. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  190. data/lib/action_controller/middleware.rb +0 -39
  191. data/lib/action_controller/model_naming.rb +0 -12
  192. data/lib/action_dispatch/http/parameter_filter.rb +0 -72
  193. data/lib/action_dispatch/journey/backwards.rb +0 -5
  194. data/lib/action_dispatch/journey/nfa/builder.rb +0 -76
  195. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
  196. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -163
  197. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  198. data/lib/action_dispatch/middleware/params_parser.rb +0 -60
  199. data/lib/action_dispatch/middleware/templates/rescues/_source.erb +0 -27
  200. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  201. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  202. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,224 +1,86 @@
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'
1
+ # frozen_string_literal: true
6
2
 
7
- require 'rails-dom-testing'
3
+ # :markup: markdown
4
+
5
+ require "rack/session/abstract/id"
6
+ require "active_support/core_ext/hash/conversions"
7
+ require "active_support/core_ext/object/to_query"
8
+ require "active_support/core_ext/module/anonymous"
9
+ require "active_support/core_ext/module/redefine_method"
10
+ require "active_support/core_ext/hash/keys"
11
+ require "active_support/testing/constant_lookup"
12
+ require "action_controller/template_assertions"
13
+ require "rails-dom-testing"
8
14
 
9
15
  module ActionController
10
- module TemplateAssertions
11
- extend ActiveSupport::Concern
16
+ class Metal
17
+ include Testing::Functional
18
+ end
12
19
 
13
- included do
14
- setup :setup_subscriptions
15
- teardown :teardown_subscriptions
20
+ module Live
21
+ # Disable controller / rendering threads in tests. User tests can access the
22
+ # database on the main thread, so they could open a txn, then the controller
23
+ # thread will open a new connection and try to access data that's only visible
24
+ # to the main thread's txn. This is the problem in #23483.
25
+ silence_redefinition_of_method :new_controller_thread
26
+ def new_controller_thread # :nodoc:
27
+ yield
16
28
  end
17
29
 
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 =~ /^.*\/_[^\/]*$/
30
+ # Avoid a deadlock from the queue filling up
31
+ Buffer.queue_size = nil
32
+ end
40
33
 
41
- if partial
42
- @_partials[virtual_path] += 1
43
- @_partials[virtual_path.split("/").last] += 1
44
- end
34
+ # ActionController::TestCase will be deprecated and moved to a gem in the
35
+ # future. Please use ActionDispatch::IntegrationTest going forward.
36
+ class TestRequest < ActionDispatch::TestRequest # :nodoc:
37
+ DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup
38
+ DEFAULT_ENV.delete "PATH_INFO"
45
39
 
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
40
+ def self.new_session
41
+ TestSession.new
55
42
  end
56
43
 
57
- def teardown_subscriptions
58
- return unless defined?(@_subscribers)
59
-
60
- @_subscribers.each do |subscriber|
61
- ActiveSupport::Notifications.unsubscribe(subscriber)
62
- end
63
- end
44
+ attr_reader :controller_class
64
45
 
65
- def process(*args)
66
- reset_template_assertion
67
- super
46
+ # Create a new test request with default `env` values.
47
+ def self.create(controller_class)
48
+ env = {}
49
+ env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
50
+ env["rack.request.cookie_hash"] = {}.with_indifferent_access
51
+ new(default_env.merge(env), new_session, controller_class)
68
52
  end
69
53
 
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
54
+ def self.default_env
55
+ DEFAULT_ENV
77
56
  end
57
+ private_class_method :default_env
78
58
 
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
59
+ def initialize(env, session, controller_class)
60
+ super(env)
155
61
 
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
62
+ self.session = session
63
+ self.session_options = TestSession::DEFAULT_OPTIONS.dup
64
+ @controller_class = controller_class
65
+ @custom_param_parsers = {
66
+ xml: lambda { |raw_post| Hash.from_xml(raw_post)["hash"] }
67
+ }
194
68
  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
69
 
201
- def initialize(env = {})
202
- super
70
+ def query_string=(string)
71
+ set_header Rack::QUERY_STRING, string
72
+ end
203
73
 
204
- self.session = TestSession.new
205
- self.session_options = TestSession::DEFAULT_OPTIONS.merge(:id => SecureRandom.hex(16))
74
+ def content_type=(type)
75
+ set_header "CONTENT_TYPE", type
206
76
  end
207
77
 
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
78
+ def assign_parameters(routes, controller_path, action, parameters, generated_path, query_string_keys)
79
+ non_path_parameters = {}
80
+ path_parameters = {}
220
81
 
221
- if extra_keys.include?(key)
82
+ parameters.each do |key, value|
83
+ if query_string_keys.include?(key)
222
84
  non_path_parameters[key] = value
223
85
  else
224
86
  if value.is_a?(Array)
@@ -227,90 +89,110 @@ module ActionController
227
89
  value = value.to_param
228
90
  end
229
91
 
230
- path_parameters[key] = value
92
+ path_parameters[key.to_sym] = value
231
93
  end
232
94
  end
233
95
 
234
- # Clear the combined params hash in case it was already referenced.
235
- @env.delete("action_dispatch.request.parameters")
96
+ if get?
97
+ if query_string.blank?
98
+ self.query_string = non_path_parameters.to_query
99
+ end
100
+ else
101
+ if ENCODER.should_multipart?(non_path_parameters)
102
+ self.content_type = ENCODER.content_type
103
+ data = ENCODER.build_multipart non_path_parameters
104
+ else
105
+ fetch_header("CONTENT_TYPE") do |k|
106
+ set_header k, "application/x-www-form-urlencoded"
107
+ end
236
108
 
237
- # Clear the filter cache variables so they're not stale
238
- @filtered_parameters = @filtered_env = @filtered_path = nil
109
+ case content_mime_type.to_sym
110
+ when nil
111
+ raise "Unknown Content-Type: #{content_type}"
112
+ when :json
113
+ data = ActiveSupport::JSON.encode(non_path_parameters)
114
+ when :xml
115
+ data = non_path_parameters.to_xml
116
+ when :url_encoded_form
117
+ data = non_path_parameters.to_query
118
+ else
119
+ @custom_param_parsers[content_mime_type.symbol] = ->(_) { non_path_parameters }
120
+ data = non_path_parameters.to_query
121
+ end
122
+ end
239
123
 
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)
124
+ data_stream = StringIO.new(data)
125
+ set_header "CONTENT_LENGTH", data_stream.length.to_s
126
+ set_header "rack.input", data_stream
244
127
  end
245
- data = params.to_query
246
128
 
247
- @env['CONTENT_LENGTH'] = data.length.to_s
248
- @env['rack.input'] = StringIO.new(data)
249
- end
129
+ fetch_header("PATH_INFO") do |k|
130
+ set_header k, generated_path
131
+ end
132
+ fetch_header("ORIGINAL_FULLPATH") do |k|
133
+ set_header k, fullpath
134
+ end
135
+ path_parameters[:controller] = controller_path
136
+ path_parameters[:action] = action
250
137
 
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!
138
+ self.path_parameters = path_parameters
266
139
  end
267
140
 
268
- private
141
+ ENCODER = Class.new do
142
+ include Rack::Test::Utils
143
+
144
+ def should_multipart?(params)
145
+ # FIXME: lifted from Rack-Test. We should push this separation upstream.
146
+ multipart = false
147
+ query = lambda { |value|
148
+ case value
149
+ when Array
150
+ value.each(&query)
151
+ when Hash
152
+ value.values.each(&query)
153
+ when Rack::Test::UploadedFile
154
+ multipart = true
155
+ end
156
+ }
157
+ params.values.each(&query)
158
+ multipart
159
+ end
269
160
 
270
- def default_env
271
- DEFAULT_ENV
272
- end
273
- end
161
+ public :build_multipart
274
162
 
275
- class TestResponse < ActionDispatch::TestResponse
276
- def recycle!
277
- initialize
278
- end
163
+ def content_type
164
+ "multipart/form-data; boundary=#{Rack::Test::MULTIPART_BOUNDARY}"
165
+ end
166
+ end.new
167
+
168
+ private
169
+ def params_parsers
170
+ super.merge @custom_param_parsers
171
+ end
279
172
  end
280
173
 
281
174
  class LiveTestResponse < Live::Response
282
- def recycle!
283
- @body = nil
284
- initialize
285
- end
286
-
287
- def body
288
- @body ||= super
289
- end
290
-
291
175
  # Was the response successful?
292
176
  alias_method :success?, :successful?
293
177
 
294
178
  # Was the URL not found?
295
179
  alias_method :missing?, :not_found?
296
180
 
297
- # Were we redirected?
298
- alias_method :redirect?, :redirection?
299
-
300
181
  # Was there a server-side error?
301
182
  alias_method :error?, :server_error?
302
183
  end
303
184
 
304
185
  # Methods #destroy and #load! are overridden to avoid calling methods on the
305
186
  # @store object, which does not exist for the TestSession class.
306
- class TestSession < Rack::Session::Abstract::SessionHash #:nodoc:
307
- DEFAULT_OPTIONS = Rack::Session::Abstract::ID::DEFAULT_OPTIONS
187
+ class TestSession < Rack::Session::Abstract::PersistedSecure::SecureSessionHash # :nodoc:
188
+ DEFAULT_OPTIONS = Rack::Session::Abstract::Persisted::DEFAULT_OPTIONS
308
189
 
309
- def initialize(session = {})
190
+ def initialize(session = {}, id = Rack::Session::SessionId.new(SecureRandom.hex(16)))
310
191
  super(nil, nil)
311
- @id = SecureRandom.hex(16)
192
+ @id = id
312
193
  @data = stringify_keys(session)
313
194
  @loaded = true
195
+ @initially_empty = @data.empty?
314
196
  end
315
197
 
316
198
  def exists?
@@ -329,127 +211,153 @@ module ActionController
329
211
  clear
330
212
  end
331
213
 
214
+ def dig(*keys)
215
+ keys = keys.map.with_index { |key, i| i.zero? ? key.to_s : key }
216
+ @data.dig(*keys)
217
+ end
218
+
332
219
  def fetch(key, *args, &block)
333
220
  @data.fetch(key.to_s, *args, &block)
334
221
  end
335
222
 
336
- private
223
+ def enabled?
224
+ true
225
+ end
337
226
 
227
+ def id_was
228
+ @id
229
+ end
230
+
231
+ private
338
232
  def load!
339
233
  @id
340
234
  end
341
235
  end
342
236
 
343
- # 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).
237
+ # # Action Controller Test Case
348
238
  #
349
- # == Basic example
239
+ # Superclass for ActionController functional tests. Functional tests allow you
240
+ # to test a single controller action per test method.
241
+ #
242
+ # ## Use integration style controller tests over functional style controller tests.
243
+ #
244
+ # Rails discourages the use of functional tests in favor of integration tests
245
+ # (use ActionDispatch::IntegrationTest).
246
+ #
247
+ # New Rails applications no longer generate functional style controller tests
248
+ # and they should only be used for backward compatibility. Integration style
249
+ # controller tests perform actual requests, whereas functional style controller
250
+ # tests merely simulate a request. Besides, integration tests are as fast as
251
+ # functional tests and provide lot of helpers such as `as`, `parsed_body` for
252
+ # effective testing of controller actions including even API endpoints.
253
+ #
254
+ # ## Basic example
350
255
  #
351
256
  # Functional tests are written as follows:
352
- # 1. First, one uses the +get+, +post+, +patch+, +put+, +delete+ or +head+ method to simulate
353
- # an HTTP request.
354
- # 2. Then, one asserts whether the current state is as expected. "State" can be anything:
355
- # the controller's HTTP response, the database contents, etc.
257
+ # 1. First, one uses the `get`, `post`, `patch`, `put`, `delete`, or `head`
258
+ # method to simulate an HTTP request.
259
+ # 2. Then, one asserts whether the current state is as expected. "State" can be
260
+ # anything: the controller's HTTP response, the database contents, etc.
261
+ #
356
262
  #
357
263
  # For example:
358
264
  #
359
- # class BooksControllerTest < ActionController::TestCase
360
- # def test_create
361
- # # Simulate a POST response with the given HTTP parameters.
362
- # post(:create, book: { title: "Love Hina" })
265
+ # class BooksControllerTest < ActionController::TestCase
266
+ # def test_create
267
+ # # Simulate a POST response with the given HTTP parameters.
268
+ # post(:create, params: { book: { title: "Love Hina" }})
363
269
  #
364
- # # Assert that the controller tried to redirect us to
365
- # # the created book's URI.
366
- # assert_response :found
270
+ # # Asserts that the controller tried to redirect us to
271
+ # # the created book's URI.
272
+ # assert_response :found
367
273
  #
368
- # # Assert that the controller really put the book in the database.
369
- # assert_not_nil Book.find_by(title: "Love Hina")
274
+ # # Asserts that the controller really put the book in the database.
275
+ # assert_not_nil Book.find_by(title: "Love Hina")
276
+ # end
370
277
  # end
371
- # end
372
278
  #
373
279
  # You can also send a real document in the simulated HTTP request.
374
280
  #
375
- # def test_create
376
- # json = {book: { title: "Love Hina" }}.to_json
377
- # post :create, json
378
- # end
281
+ # def test_create
282
+ # json = {book: { title: "Love Hina" }}.to_json
283
+ # post :create, body: json
284
+ # end
285
+ #
286
+ # ## Special instance variables
287
+ #
288
+ # ActionController::TestCase will also automatically provide the following
289
+ # instance variables for use in the tests:
379
290
  #
380
- # == Special instance variables
291
+ # @controller
292
+ # : The controller instance that will be tested.
381
293
  #
382
- # ActionController::TestCase will also automatically provide the following instance
383
- # variables for use in the tests:
294
+ # @request
295
+ # : An ActionController::TestRequest, representing the current HTTP request.
296
+ # You can modify this object before sending the HTTP request. For example,
297
+ # you might want to set some session properties before sending a GET
298
+ # request.
384
299
  #
385
- # <b>@controller</b>::
386
- # The controller instance that will be tested.
387
- # <b>@request</b>::
388
- # An ActionController::TestRequest, representing the current HTTP
389
- # request. You can modify this object before sending the HTTP request. For example,
390
- # you might want to set some session properties before sending a GET request.
391
- # <b>@response</b>::
392
- # An ActionController::TestResponse object, representing the response
393
- # of the last HTTP response. In the above example, <tt>@response</tt> becomes valid
394
- # after calling +post+. If the various assert methods are not sufficient, then you
395
- # may use this object to inspect the HTTP response in detail.
300
+ # @response
301
+ # : An ActionDispatch::TestResponse object, representing the response of the
302
+ # last HTTP response. In the above example, `@response` becomes valid after
303
+ # calling `post`. If the various assert methods are not sufficient, then you
304
+ # may use this object to inspect the HTTP response in detail.
396
305
  #
397
- # (Earlier versions of \Rails required each functional test to subclass
398
- # Test::Unit::TestCase and define @controller, @request, @response in +setup+.)
399
306
  #
400
- # == Controller is automatically inferred
307
+ # ## Controller is automatically inferred
401
308
  #
402
309
  # ActionController::TestCase will automatically infer the controller under test
403
310
  # from the test class name. If the controller cannot be inferred from the test
404
- # class name, you can explicitly set it with +tests+.
311
+ # class name, you can explicitly set it with `tests`.
405
312
  #
406
- # class SpecialEdgeCaseWidgetsControllerTest < ActionController::TestCase
407
- # tests WidgetController
408
- # end
313
+ # class SpecialEdgeCaseWidgetsControllerTest < ActionController::TestCase
314
+ # tests WidgetController
315
+ # end
409
316
  #
410
- # == \Testing controller internals
317
+ # ## Testing controller internals
411
318
  #
412
- # In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions
413
- # can be used against. These collections are:
319
+ # In addition to these specific assertions, you also have easy access to various
320
+ # collections that the regular test/unit assertions can be used against. These
321
+ # collections are:
414
322
  #
415
- # * assigns: Instance variables assigned in the action that are available for the view.
416
- # * session: Objects being saved in the session.
417
- # * flash: The flash objects currently in the session.
418
- # * cookies: \Cookies being sent to the user on this request.
323
+ # * session: Objects being saved in the session.
324
+ # * flash: The flash objects currently in the session.
325
+ # * cookies: Cookies being sent to the user on this request.
419
326
  #
420
- # These collections can be used just like any other hash:
421
327
  #
422
- # assert_not_nil assigns(:person) # makes sure that a @person instance variable was set
423
- # assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
424
- # assert flash.empty? # makes sure that there's nothing in the flash
328
+ # These collections can be used just like any other hash:
425
329
  #
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.
330
+ # assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
331
+ # assert flash.empty? # makes sure that there's nothing in the flash
429
332
  #
430
- # On top of the collections, you have the complete url that a given action redirected to available in <tt>redirect_to_url</tt>.
333
+ # On top of the collections, you have the complete URL that a given action
334
+ # redirected to available in `redirect_to_url`.
431
335
  #
432
- # For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
433
- # action call which can then be asserted against.
336
+ # For redirects within the same controller, you can even call follow_redirect
337
+ # and the redirect will be followed, triggering another action call which can
338
+ # then be asserted against.
434
339
  #
435
- # == Manipulating session and cookie variables
340
+ # ## Manipulating session and cookie variables
436
341
  #
437
- # Sometimes you need to set up the session and cookie variables for a test.
438
- # To do this just assign a value to the session or cookie collection:
342
+ # Sometimes you need to set up the session and cookie variables for a test. To
343
+ # do this just assign a value to the session or cookie collection:
439
344
  #
440
- # session[:key] = "value"
441
- # cookies[:key] = "value"
345
+ # session[:key] = "value"
346
+ # cookies[:key] = "value"
442
347
  #
443
348
  # To clear the cookies for a test just clear the cookie collection:
444
349
  #
445
- # cookies.clear
350
+ # cookies.clear
446
351
  #
447
- # == \Testing named routes
352
+ # ## Testing named routes
448
353
  #
449
- # If you're using named routes, they can be easily tested using the original named routes' methods straight in the test case.
354
+ # If you're using named routes, they can be easily tested using the original
355
+ # named routes' methods straight in the test case.
450
356
  #
451
- # assert_redirected_to page_url(title: 'foo')
357
+ # assert_redirected_to page_url(title: 'foo')
452
358
  class TestCase < ActiveSupport::TestCase
359
+ singleton_class.attr_accessor :executor_around_each_request
360
+
453
361
  module Behavior
454
362
  extend ActiveSupport::Concern
455
363
  include ActionDispatch::TestProcess
@@ -459,13 +367,12 @@ module ActionController
459
367
  attr_reader :response, :request
460
368
 
461
369
  module ClassMethods
462
-
463
- # Sets the controller class name. Useful if the name can't be inferred from test class.
464
- # Normalizes +controller_class+ before using.
370
+ # Sets the controller class name. Useful if the name can't be inferred from test
371
+ # class. Normalizes `controller_class` before using.
465
372
  #
466
- # tests WidgetController
467
- # tests :widget
468
- # tests 'widget'
373
+ # tests WidgetController
374
+ # tests :widget
375
+ # tests 'widget'
469
376
  def tests(controller_class)
470
377
  case controller_class
471
378
  when String, Symbol
@@ -482,7 +389,7 @@ module ActionController
482
389
  end
483
390
 
484
391
  def controller_class
485
- if current_controller_class = self._controller_class
392
+ if current_controller_class = _controller_class
486
393
  current_controller_class
487
394
  else
488
395
  self.controller_class = determine_default_controller_class(name)
@@ -498,170 +405,162 @@ module ActionController
498
405
 
499
406
  # Simulate a GET request with the given parameters.
500
407
  #
501
- # - +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
504
- # (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
505
- # - +session+: A hash of parameters to store in the session. This may be +nil+.
506
- # - +flash+: A hash of parameters to store in the flash. This may be +nil+.
408
+ # * `action`: The controller action to call.
409
+ # * `params`: The hash with HTTP parameters that you want to pass. This may be
410
+ # `nil`.
411
+ # * `body`: The request body with a string that is appropriately encoded
412
+ # (`application/x-www-form-urlencoded` or `multipart/form-data`).
413
+ # * `session`: A hash of parameters to store in the session. This may be
414
+ # `nil`.
415
+ # * `flash`: A hash of parameters to store in the flash. This may be `nil`.
416
+ #
417
+ #
418
+ # You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with `post`,
419
+ # `patch`, `put`, `delete`, and `head`. Example sending parameters, session, and
420
+ # setting a flash message:
507
421
  #
508
- # You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with
509
- # +post+, +patch+, +put+, +delete+, and +head+.
422
+ # get :show,
423
+ # params: { id: 7 },
424
+ # session: { user_id: 1 },
425
+ # flash: { notice: 'This is flash message' }
510
426
  #
511
427
  # Note that the request method is not verified. The different methods are
512
428
  # available to make the tests more expressive.
513
- def get(action, *args)
514
- process(action, "GET", *args)
429
+ def get(action, **args)
430
+ res = process(action, method: "GET", **args)
431
+ cookies.update res.cookies
432
+ res
515
433
  end
516
434
 
517
435
  # Simulate a POST request with the given parameters and set/volley the response.
518
- # See +get+ for more details.
519
- def post(action, *args)
520
- process(action, "POST", *args)
436
+ # See `get` for more details.
437
+ def post(action, **args)
438
+ process(action, method: "POST", **args)
521
439
  end
522
440
 
523
- # Simulate a PATCH request with the given parameters and set/volley the response.
524
- # See +get+ for more details.
525
- def patch(action, *args)
526
- process(action, "PATCH", *args)
441
+ # Simulate a PATCH request with the given parameters and set/volley the
442
+ # response. See `get` for more details.
443
+ def patch(action, **args)
444
+ process(action, method: "PATCH", **args)
527
445
  end
528
446
 
529
447
  # Simulate a PUT request with the given parameters and set/volley the response.
530
- # See +get+ for more details.
531
- def put(action, *args)
532
- process(action, "PUT", *args)
448
+ # See `get` for more details.
449
+ def put(action, **args)
450
+ process(action, method: "PUT", **args)
533
451
  end
534
452
 
535
- # Simulate a DELETE request with the given parameters and set/volley the response.
536
- # See +get+ for more details.
537
- def delete(action, *args)
538
- process(action, "DELETE", *args)
453
+ # Simulate a DELETE request with the given parameters and set/volley the
454
+ # response. See `get` for more details.
455
+ def delete(action, **args)
456
+ process(action, method: "DELETE", **args)
539
457
  end
540
458
 
541
459
  # Simulate a HEAD request with the given parameters and set/volley the response.
542
- # 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
460
+ # See `get` for more details.
461
+ def head(action, **args)
462
+ process(action, method: "HEAD", **args)
568
463
  end
569
464
 
570
- # Simulate a HTTP request to +action+ by specifying request method,
571
- # parameters and set/volley the response.
465
+ # Simulate an HTTP request to `action` by specifying request method, parameters
466
+ # and set/volley the response.
572
467
  #
573
- # - +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+).
579
- # - +session+: A hash of parameters to store in the session. This may be +nil+.
580
- # - +flash+: A hash of parameters to store in the flash. This may be +nil+.
468
+ # * `action`: The controller action to call.
469
+ # * `method`: Request method used to send the HTTP request. Possible values
470
+ # are `GET`, `POST`, `PATCH`, `PUT`, `DELETE`, `HEAD`. Defaults to `GET`.
471
+ # Can be a symbol.
472
+ # * `params`: The hash with HTTP parameters that you want to pass. This may be
473
+ # `nil`.
474
+ # * `body`: The request body with a string that is appropriately encoded
475
+ # (`application/x-www-form-urlencoded` or `multipart/form-data`).
476
+ # * `session`: A hash of parameters to store in the session. This may be
477
+ # `nil`.
478
+ # * `flash`: A hash of parameters to store in the flash. This may be `nil`.
479
+ # * `format`: Request format. Defaults to `nil`. Can be string or symbol.
480
+ # * `as`: Content type. Defaults to `nil`. Must be a symbol that corresponds
481
+ # to a mime type.
581
482
  #
582
- # Example calling +create+ action and sending two params:
583
483
  #
584
- # process :create, 'POST', user: { name: 'Gaurish Sharma', email: 'user@example.com' }
484
+ # Example calling `create` action and sending two params:
585
485
  #
586
- # Example sending parameters, +nil+ session and setting a flash message:
486
+ # process :create,
487
+ # method: 'POST',
488
+ # params: {
489
+ # user: { name: 'Gaurish Sharma', email: 'user@example.com' }
490
+ # },
491
+ # session: { user_id: 1 },
492
+ # flash: { notice: 'This is flash message' }
587
493
  #
588
- # process :view, 'GET', { id: 7 }, nil, { notice: 'This is flash message' }
494
+ # To simulate `GET`, `POST`, `PATCH`, `PUT`, `DELETE`, and `HEAD` requests
495
+ # prefer using #get, #post, #patch, #put, #delete and #head methods respectively
496
+ # which will make tests more expressive.
589
497
  #
590
- # To simulate +GET+, +POST+, +PATCH+, +PUT+, +DELETE+ and +HEAD+ requests
591
- # prefer using #get, #post, #patch, #put, #delete and #head methods
592
- # respectively which will make tests more expressive.
498
+ # It's not recommended to make more than one request in the same test. Instance
499
+ # variables that are set in one request will not persist to the next request,
500
+ # but it's not guaranteed that all Rails internal state will be reset. Prefer
501
+ # ActionDispatch::IntegrationTest for making multiple requests in the same test.
593
502
  #
594
503
  # Note that the request method is not verified.
595
- def process(action, http_method = 'GET', *args)
504
+ def process(action, method: "GET", params: nil, session: nil, body: nil, flash: {}, format: nil, xhr: false, as: nil)
596
505
  check_required_ivars
506
+ @controller.clear_instance_variables_between_requests
597
507
 
598
- if args.first.is_a?(String) && http_method != 'HEAD'
599
- @request.env['RAW_POST_DATA'] = args.shift
600
- end
601
-
602
- parameters, session, flash = args
603
- parameters ||= {}
604
-
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)
508
+ action = +action.to_s
509
+ http_method = method.to_s.upcase
608
510
 
609
511
  @html_document = nil
610
- @html_scanner_document = nil
611
512
 
612
- unless @controller.respond_to?(:recycle!)
613
- @controller.extend(Testing::Functional)
614
- end
513
+ cookies.update(@request.cookies)
514
+ cookies.update_cookies_from_jar
515
+ @request.set_header "HTTP_COOKIE", cookies.to_header
516
+ @request.delete_header "action_dispatch.cookies"
615
517
 
616
- @request.recycle!
617
- @response.recycle!
518
+ @request = TestRequest.new scrub_env!(@request.env), @request.session, @controller.class
519
+ @response = build_response @response_klass
520
+ @response.request = @request
618
521
  @controller.recycle!
619
522
 
620
- @request.env['REQUEST_METHOD'] = http_method
621
-
622
- controller_class_name = @controller.class.anonymous? ?
623
- "anonymous" :
624
- @controller.class.controller_path
625
-
626
- @request.assign_parameters(@routes, controller_class_name, action.to_s, parameters)
627
-
628
- @request.session.update(session) if session
629
- @request.flash.update(flash || {})
630
-
631
- @controller.request = @request
632
- @controller.response = @response
523
+ if body
524
+ @request.set_header "RAW_POST_DATA", body
525
+ end
633
526
 
634
- build_request_uri(action, parameters)
527
+ @request.set_header "REQUEST_METHOD", http_method
635
528
 
636
- name = @request.parameters[:action]
529
+ if as
530
+ @request.content_type = Mime[as].to_s
531
+ format ||= as
532
+ end
637
533
 
638
- @controller.recycle!
639
- @controller.process(name)
534
+ parameters = (params || {}).symbolize_keys
640
535
 
641
- if cookies = @request.env['action_dispatch.cookies']
642
- unless @response.committed?
643
- cookies.write(@response)
644
- end
536
+ if format
537
+ parameters[:format] = format
645
538
  end
646
- @response.prepare!
647
539
 
648
- @assigns = @controller.respond_to?(:view_assigns) ? @controller.view_assigns : {}
540
+ setup_request(controller_class_name, action, parameters, session, flash, xhr)
541
+ process_controller_response(action, cookies, xhr)
542
+ end
649
543
 
650
- if flash_value = @request.flash.to_session_value
651
- @request.session['flash'] = flash_value
652
- end
544
+ def controller_class_name
545
+ @controller.class.anonymous? ? "anonymous" : @controller.class.controller_path
546
+ end
547
+
548
+ def generated_path(generated_extras)
549
+ generated_extras[0]
550
+ end
653
551
 
654
- @response
552
+ def query_parameter_names(generated_extras)
553
+ generated_extras[1] + [:controller, :action]
655
554
  end
656
555
 
657
556
  def setup_controller_request_and_response
658
557
  @controller = nil unless defined? @controller
659
558
 
660
- response_klass = TestResponse
559
+ @response_klass = ActionDispatch::TestResponse
661
560
 
662
561
  if klass = self.class.controller_class
663
562
  if klass < ActionController::Live
664
- response_klass = LiveTestResponse
563
+ @response_klass = LiveTestResponse
665
564
  end
666
565
  unless @controller
667
566
  begin
@@ -672,8 +571,8 @@ module ActionController
672
571
  end
673
572
  end
674
573
 
675
- @request = build_request
676
- @response = build_response response_klass
574
+ @request = TestRequest.create(@controller.class)
575
+ @response = build_response @response_klass
677
576
  @response.request = @request
678
577
 
679
578
  if @controller
@@ -682,12 +581,8 @@ module ActionController
682
581
  end
683
582
  end
684
583
 
685
- def build_request
686
- TestRequest.new
687
- end
688
-
689
584
  def build_response(klass)
690
- klass.new
585
+ klass.create
691
586
  end
692
587
 
693
588
  included do
@@ -695,65 +590,98 @@ module ActionController
695
590
  include ActionDispatch::Assertions
696
591
  class_attribute :_controller_class
697
592
  setup :setup_controller_request_and_response
593
+ ActiveSupport.run_load_hooks(:action_controller_test_case, self)
698
594
  end
699
595
 
700
596
  private
597
+ def setup_request(controller_class_name, action, parameters, session, flash, xhr)
598
+ generated_extras = @routes.generate_extras(parameters.merge(controller: controller_class_name, action: action))
599
+ generated_path = generated_path(generated_extras)
600
+ query_string_keys = query_parameter_names(generated_extras)
701
601
 
702
- def document_root_element
703
- html_document.root
704
- end
602
+ @request.assign_parameters(@routes, controller_class_name, action, parameters, generated_path, query_string_keys)
603
+
604
+ @request.session.update(session) if session
605
+ @request.flash.update(flash || {})
606
+
607
+ if xhr
608
+ @request.set_header "HTTP_X_REQUESTED_WITH", "XMLHttpRequest"
609
+ @request.fetch_header("HTTP_ACCEPT") do |k|
610
+ @request.set_header k, [Mime[:js], Mime[:html], Mime[:xml], "text/xml", "*/*"].join(", ")
611
+ end
612
+ end
705
613
 
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."
614
+ @request.fetch_header("SCRIPT_NAME") do |k|
615
+ @request.set_header k, @controller.config.relative_url_root
712
616
  end
713
617
  end
714
- end
715
618
 
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
619
+ def wrap_execution(&block)
620
+ if ActionController::TestCase.executor_around_each_request && defined?(Rails.application) && Rails.application
621
+ Rails.application.executor.wrap(&block)
622
+ else
623
+ yield
624
+ end
625
+ end
626
+
627
+ def process_controller_response(action, cookies, xhr)
628
+ begin
629
+ @controller.recycle!
630
+
631
+ wrap_execution { @controller.dispatch(action, @request, @response) }
632
+ ensure
633
+ @request = @controller.request
634
+ @response = @controller.response
635
+
636
+ if @request.have_cookie_jar?
637
+ unless @request.cookie_jar.committed?
638
+ @request.cookie_jar.write(@response)
639
+ cookies.update(@request.cookie_jar.instance_variable_get(:@cookies))
640
+ end
641
+ end
642
+ @response.prepare!
643
+
644
+ if flash_value = @request.flash.to_session_value
645
+ @request.session["flash"] = flash_value
646
+ else
647
+ @request.session.delete("flash")
648
+ end
649
+
650
+ if xhr
651
+ @request.delete_header "HTTP_X_REQUESTED_WITH"
652
+ @request.delete_header "HTTP_ACCEPT"
653
+ end
654
+ @request.query_string = ""
655
+
656
+ @response.sent!
743
657
  end
744
658
 
745
- url, query_string = @routes.path_for(options, route_name).split("?", 2)
659
+ @response
660
+ end
746
661
 
747
- @request.env["SCRIPT_NAME"] = @controller.config.relative_url_root
748
- @request.env["PATH_INFO"] = url
749
- @request.env["QUERY_STRING"] = query_string || ""
662
+ def scrub_env!(env)
663
+ env.delete_if do |k, _|
664
+ k.start_with?("rack.request", "action_dispatch.request", "action_dispatch.rescue")
665
+ end
666
+ env["rack.input"] = StringIO.new
667
+ env.delete "CONTENT_LENGTH"
668
+ env.delete "RAW_POST_DATA"
669
+ env
750
670
  end
751
- end
752
671
 
753
- def html_format?(parameters)
754
- return true unless parameters.key?(:format)
755
- Mime.fetch(parameters[:format]) { Mime['html'] }.html?
756
- end
672
+ def document_root_element
673
+ html_document.root
674
+ end
675
+
676
+ def check_required_ivars
677
+ # Check for required instance variables so we can give an understandable error
678
+ # message.
679
+ [:@routes, :@controller, :@request, :@response].each do |iv_name|
680
+ if !instance_variable_defined?(iv_name) || instance_variable_get(iv_name).nil?
681
+ raise "#{iv_name} is nil: make sure you set it in your test's setup method."
682
+ end
683
+ end
684
+ end
757
685
  end
758
686
 
759
687
  include Behavior