actionpack 4.2.11.3 → 5.0.7.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 (136) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +890 -384
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -3
  5. data/lib/abstract_controller/base.rb +28 -38
  6. data/lib/{action_controller → abstract_controller}/caching/fragments.rb +51 -11
  7. data/lib/abstract_controller/caching.rb +62 -0
  8. data/lib/abstract_controller/callbacks.rb +54 -19
  9. data/lib/abstract_controller/collector.rb +4 -9
  10. data/lib/abstract_controller/error.rb +4 -0
  11. data/lib/abstract_controller/helpers.rb +4 -3
  12. data/lib/abstract_controller/railties/routes_helpers.rb +2 -2
  13. data/lib/abstract_controller/rendering.rb +28 -18
  14. data/lib/abstract_controller/translation.rb +8 -7
  15. data/lib/abstract_controller.rb +6 -2
  16. data/lib/action_controller/api/api_rendering.rb +14 -0
  17. data/lib/action_controller/api.rb +147 -0
  18. data/lib/action_controller/base.rb +14 -11
  19. data/lib/action_controller/caching.rb +13 -58
  20. data/lib/action_controller/form_builder.rb +48 -0
  21. data/lib/action_controller/log_subscriber.rb +3 -10
  22. data/lib/action_controller/metal/basic_implicit_render.rb +11 -0
  23. data/lib/action_controller/metal/conditional_get.rb +106 -34
  24. data/lib/action_controller/metal/cookies.rb +1 -3
  25. data/lib/action_controller/metal/data_streaming.rb +14 -34
  26. data/lib/action_controller/metal/etag_with_template_digest.rb +8 -2
  27. data/lib/action_controller/metal/exceptions.rb +11 -6
  28. data/lib/action_controller/metal/force_ssl.rb +11 -11
  29. data/lib/action_controller/metal/head.rb +14 -8
  30. data/lib/action_controller/metal/helpers.rb +15 -6
  31. data/lib/action_controller/metal/http_authentication.rb +44 -35
  32. data/lib/action_controller/metal/implicit_render.rb +61 -6
  33. data/lib/action_controller/metal/instrumentation.rb +5 -5
  34. data/lib/action_controller/metal/live.rb +71 -88
  35. data/lib/action_controller/metal/mime_responds.rb +27 -42
  36. data/lib/action_controller/metal/params_wrapper.rb +9 -9
  37. data/lib/action_controller/metal/redirecting.rb +32 -9
  38. data/lib/action_controller/metal/renderers.rb +83 -40
  39. data/lib/action_controller/metal/rendering.rb +38 -6
  40. data/lib/action_controller/metal/request_forgery_protection.rb +126 -48
  41. data/lib/action_controller/metal/rescue.rb +3 -12
  42. data/lib/action_controller/metal/streaming.rb +4 -4
  43. data/lib/action_controller/metal/strong_parameters.rb +527 -134
  44. data/lib/action_controller/metal/testing.rb +1 -12
  45. data/lib/action_controller/metal/url_for.rb +12 -5
  46. data/lib/action_controller/metal.rb +88 -63
  47. data/lib/action_controller/railtie.rb +11 -7
  48. data/lib/action_controller/renderer.rb +113 -0
  49. data/lib/action_controller/template_assertions.rb +9 -0
  50. data/lib/action_controller/test_case.rb +311 -374
  51. data/lib/action_controller.rb +12 -9
  52. data/lib/action_dispatch/http/cache.rb +73 -34
  53. data/lib/action_dispatch/http/filter_parameters.rb +16 -12
  54. data/lib/action_dispatch/http/filter_redirect.rb +7 -8
  55. data/lib/action_dispatch/http/headers.rb +45 -14
  56. data/lib/action_dispatch/http/mime_negotiation.rb +42 -23
  57. data/lib/action_dispatch/http/mime_type.rb +126 -90
  58. data/lib/action_dispatch/http/mime_types.rb +3 -4
  59. data/lib/action_dispatch/http/parameter_filter.rb +19 -9
  60. data/lib/action_dispatch/http/parameters.rb +70 -40
  61. data/lib/action_dispatch/http/request.rb +144 -89
  62. data/lib/action_dispatch/http/response.rb +215 -102
  63. data/lib/action_dispatch/http/upload.rb +6 -2
  64. data/lib/action_dispatch/http/url.rb +117 -8
  65. data/lib/action_dispatch/journey/formatter.rb +47 -30
  66. data/lib/action_dispatch/journey/gtg/transition_table.rb +1 -1
  67. data/lib/action_dispatch/journey/nfa/dot.rb +0 -2
  68. data/lib/action_dispatch/journey/nfa/transition_table.rb +1 -46
  69. data/lib/action_dispatch/journey/nodes/node.rb +14 -4
  70. data/lib/action_dispatch/journey/parser.rb +2 -0
  71. data/lib/action_dispatch/journey/parser_extras.rb +8 -2
  72. data/lib/action_dispatch/journey/path/pattern.rb +38 -42
  73. data/lib/action_dispatch/journey/route.rb +88 -26
  74. data/lib/action_dispatch/journey/router/utils.rb +5 -5
  75. data/lib/action_dispatch/journey/router.rb +8 -10
  76. data/lib/action_dispatch/journey/routes.rb +14 -15
  77. data/lib/action_dispatch/journey/visitors.rb +89 -44
  78. data/lib/action_dispatch/middleware/callbacks.rb +10 -1
  79. data/lib/action_dispatch/middleware/cookies.rb +188 -134
  80. data/lib/action_dispatch/middleware/debug_exceptions.rb +128 -49
  81. data/lib/action_dispatch/middleware/debug_locks.rb +122 -0
  82. data/lib/action_dispatch/middleware/exception_wrapper.rb +21 -21
  83. data/lib/action_dispatch/middleware/executor.rb +19 -0
  84. data/lib/action_dispatch/middleware/flash.rb +66 -45
  85. data/lib/action_dispatch/middleware/params_parser.rb +32 -46
  86. data/lib/action_dispatch/middleware/public_exceptions.rb +2 -2
  87. data/lib/action_dispatch/middleware/reloader.rb +14 -58
  88. data/lib/action_dispatch/middleware/remote_ip.rb +29 -19
  89. data/lib/action_dispatch/middleware/request_id.rb +11 -6
  90. data/lib/action_dispatch/middleware/session/abstract_store.rb +23 -11
  91. data/lib/action_dispatch/middleware/session/cache_store.rb +9 -6
  92. data/lib/action_dispatch/middleware/session/cookie_store.rb +30 -24
  93. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +4 -0
  94. data/lib/action_dispatch/middleware/show_exceptions.rb +11 -9
  95. data/lib/action_dispatch/middleware/ssl.rb +124 -36
  96. data/lib/action_dispatch/middleware/stack.rb +44 -40
  97. data/lib/action_dispatch/middleware/static.rb +51 -35
  98. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
  99. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  100. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -1
  101. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  102. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
  103. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +59 -63
  104. data/lib/action_dispatch/railtie.rb +2 -2
  105. data/lib/action_dispatch/request/session.rb +69 -33
  106. data/lib/action_dispatch/request/utils.rb +51 -19
  107. data/lib/action_dispatch/routing/inspector.rb +32 -43
  108. data/lib/action_dispatch/routing/mapper.rb +515 -348
  109. data/lib/action_dispatch/routing/polymorphic_routes.rb +8 -14
  110. data/lib/action_dispatch/routing/redirection.rb +5 -4
  111. data/lib/action_dispatch/routing/route_set.rb +148 -240
  112. data/lib/action_dispatch/routing/url_for.rb +27 -10
  113. data/lib/action_dispatch/routing.rb +17 -13
  114. data/lib/action_dispatch/testing/assertion_response.rb +45 -0
  115. data/lib/action_dispatch/testing/assertions/response.rb +38 -20
  116. data/lib/action_dispatch/testing/assertions/routing.rb +16 -12
  117. data/lib/action_dispatch/testing/assertions.rb +1 -1
  118. data/lib/action_dispatch/testing/integration.rb +377 -149
  119. data/lib/action_dispatch/testing/request_encoder.rb +53 -0
  120. data/lib/action_dispatch/testing/test_process.rb +24 -20
  121. data/lib/action_dispatch/testing/test_request.rb +22 -31
  122. data/lib/action_dispatch/testing/test_response.rb +12 -4
  123. data/lib/action_dispatch.rb +4 -1
  124. data/lib/action_pack/gem_version.rb +4 -4
  125. data/lib/action_pack.rb +1 -1
  126. metadata +32 -34
  127. data/lib/action_controller/metal/hide_actions.rb +0 -40
  128. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  129. data/lib/action_controller/middleware.rb +0 -39
  130. data/lib/action_controller/model_naming.rb +0 -12
  131. data/lib/action_dispatch/journey/backwards.rb +0 -5
  132. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  133. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  134. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  135. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
  136. /data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +0 -0
@@ -1,224 +1,74 @@
1
1
  require 'rack/session/abstract/id'
2
+ require 'active_support/core_ext/hash/conversions'
2
3
  require 'active_support/core_ext/object/to_query'
3
4
  require 'active_support/core_ext/module/anonymous'
4
5
  require 'active_support/core_ext/hash/keys'
5
- require 'active_support/deprecation'
6
-
6
+ require 'action_controller/template_assertions'
7
7
  require 'rails-dom-testing'
8
8
 
9
9
  module ActionController
10
- module TemplateAssertions
11
- extend ActiveSupport::Concern
12
-
13
- included do
14
- setup :setup_subscriptions
15
- teardown :teardown_subscriptions
16
- end
17
-
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
10
+ class Metal
11
+ include Testing::Functional
12
+ end
45
13
 
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
14
+ module Live
15
+ # Disable controller / rendering threads in tests. User tests can access
16
+ # the database on the main thread, so they could open a txn, then the
17
+ # controller thread will open a new connection and try to access data
18
+ # that's only visible to the main thread's txn. This is the problem in #23483
19
+ remove_method :new_controller_thread
20
+ def new_controller_thread # :nodoc:
21
+ yield
55
22
  end
23
+ end
56
24
 
57
- def teardown_subscriptions
58
- return unless defined?(@_subscribers)
25
+ # ActionController::TestCase will be deprecated and moved to a gem in Rails 5.1.
26
+ # Please use ActionDispatch::IntegrationTest going forward.
27
+ class TestRequest < ActionDispatch::TestRequest #:nodoc:
28
+ DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup
29
+ DEFAULT_ENV.delete 'PATH_INFO'
59
30
 
60
- @_subscribers.each do |subscriber|
61
- ActiveSupport::Notifications.unsubscribe(subscriber)
62
- end
31
+ def self.new_session
32
+ TestSession.new
63
33
  end
64
34
 
65
- def process(*args)
66
- reset_template_assertion
67
- super
35
+ # Create a new test request with default `env` values
36
+ def self.create
37
+ env = {}
38
+ env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
39
+ env["rack.request.cookie_hash"] = {}.with_indifferent_access
40
+ new(default_env.merge(env), new_session)
68
41
  end
69
42
 
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
43
+ def self.default_env
44
+ DEFAULT_ENV
77
45
  end
46
+ private_class_method :default_env
78
47
 
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
155
-
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)/, '/')
48
+ def initialize(env, session)
49
+ super(env)
166
50
 
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
51
+ self.session = session
52
+ self.session_options = TestSession::DEFAULT_OPTIONS.dup
53
+ @custom_param_parsers = {
54
+ xml: lambda { |raw_post| Hash.from_xml(raw_post)['hash'] }
55
+ }
194
56
  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
57
 
201
- def initialize(env = {})
202
- super
58
+ def query_string=(string)
59
+ set_header Rack::QUERY_STRING, string
60
+ end
203
61
 
204
- self.session = TestSession.new
205
- self.session_options = TestSession::DEFAULT_OPTIONS.merge(:id => SecureRandom.hex(16))
62
+ def content_type=(type)
63
+ set_header 'CONTENT_TYPE', type
206
64
  end
207
65
 
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
66
+ def assign_parameters(routes, controller_path, action, parameters, generated_path, query_string_keys)
67
+ non_path_parameters = {}
68
+ path_parameters = {}
220
69
 
221
- if extra_keys.include?(key)
70
+ parameters.each do |key, value|
71
+ if query_string_keys.include?(key)
222
72
  non_path_parameters[key] = value
223
73
  else
224
74
  if value.is_a?(Array)
@@ -231,72 +81,89 @@ module ActionController
231
81
  end
232
82
  end
233
83
 
234
- # Clear the combined params hash in case it was already referenced.
235
- @env.delete("action_dispatch.request.parameters")
84
+ if get?
85
+ if self.query_string.blank?
86
+ self.query_string = non_path_parameters.to_query
87
+ end
88
+ else
89
+ if ENCODER.should_multipart?(non_path_parameters)
90
+ self.content_type = ENCODER.content_type
91
+ data = ENCODER.build_multipart non_path_parameters
92
+ else
93
+ fetch_header('CONTENT_TYPE') do |k|
94
+ set_header k, 'application/x-www-form-urlencoded'
95
+ end
236
96
 
237
- # Clear the filter cache variables so they're not stale
238
- @filtered_parameters = @filtered_env = @filtered_path = nil
97
+ case content_mime_type.to_sym
98
+ when nil
99
+ raise "Unknown Content-Type: #{content_type}"
100
+ when :json
101
+ data = ActiveSupport::JSON.encode(non_path_parameters)
102
+ when :xml
103
+ data = non_path_parameters.to_xml
104
+ when :url_encoded_form
105
+ data = non_path_parameters.to_query
106
+ else
107
+ @custom_param_parsers[content_mime_type.symbol] = ->(_) { non_path_parameters }
108
+ data = non_path_parameters.to_query
109
+ end
110
+ end
239
111
 
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)
112
+ data_stream = StringIO.new(data)
113
+ set_header "CONTENT_LENGTH", data_stream.length.to_s
114
+ set_header "rack.input", data_stream
244
115
  end
245
- data = params.to_query
246
116
 
247
- @env['CONTENT_LENGTH'] = data.length.to_s
248
- @env['rack.input'] = StringIO.new(data)
249
- end
117
+ fetch_header("PATH_INFO") do |k|
118
+ set_header k, generated_path
119
+ end
120
+ path_parameters[:controller] = controller_path
121
+ path_parameters[:action] = action
250
122
 
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!
123
+ self.path_parameters = path_parameters
266
124
  end
267
125
 
268
- private
126
+ ENCODER = Class.new do
127
+ include Rack::Test::Utils
128
+
129
+ def should_multipart?(params)
130
+ # FIXME: lifted from Rack-Test. We should push this separation upstream
131
+ multipart = false
132
+ query = lambda { |value|
133
+ case value
134
+ when Array
135
+ value.each(&query)
136
+ when Hash
137
+ value.values.each(&query)
138
+ when Rack::Test::UploadedFile
139
+ multipart = true
140
+ end
141
+ }
142
+ params.values.each(&query)
143
+ multipart
144
+ end
269
145
 
270
- def default_env
271
- DEFAULT_ENV
272
- end
273
- end
146
+ public :build_multipart
274
147
 
275
- class TestResponse < ActionDispatch::TestResponse
276
- def recycle!
277
- initialize
278
- end
279
- end
148
+ def content_type
149
+ "multipart/form-data; boundary=#{Rack::Test::MULTIPART_BOUNDARY}"
150
+ end
151
+ end.new
280
152
 
281
- class LiveTestResponse < Live::Response
282
- def recycle!
283
- @body = nil
284
- initialize
285
- end
153
+ private
286
154
 
287
- def body
288
- @body ||= super
155
+ def params_parsers
156
+ super.merge @custom_param_parsers
289
157
  end
158
+ end
290
159
 
160
+ class LiveTestResponse < Live::Response
291
161
  # Was the response successful?
292
162
  alias_method :success?, :successful?
293
163
 
294
164
  # Was the URL not found?
295
165
  alias_method :missing?, :not_found?
296
166
 
297
- # Were we redirected?
298
- alias_method :redirect?, :redirection?
299
-
300
167
  # Was there a server-side error?
301
168
  alias_method :error?, :server_error?
302
169
  end
@@ -304,7 +171,7 @@ module ActionController
304
171
  # Methods #destroy and #load! are overridden to avoid calling methods on the
305
172
  # @store object, which does not exist for the TestSession class.
306
173
  class TestSession < Rack::Session::Abstract::SessionHash #:nodoc:
307
- DEFAULT_OPTIONS = Rack::Session::Abstract::ID::DEFAULT_OPTIONS
174
+ DEFAULT_OPTIONS = Rack::Session::Abstract::Persisted::DEFAULT_OPTIONS
308
175
 
309
176
  def initialize(session = {})
310
177
  super(nil, nil)
@@ -341,10 +208,18 @@ module ActionController
341
208
  end
342
209
 
343
210
  # 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).
211
+ # test a single controller action per test method.
212
+ #
213
+ # == Use integration style controller tests over functional style controller tests.
214
+ #
215
+ # Rails discourages the use of functional tests in favor of integration tests
216
+ # (use ActionDispatch::IntegrationTest).
217
+ #
218
+ # New Rails applications no longer generate functional style controller tests and they should
219
+ # only be used for backward compatibility. Integration style controller tests perform actual
220
+ # requests, whereas functional style controller tests merely simulate a request. Besides,
221
+ # integration tests are as fast as functional tests and provide lot of helpers such as +as+,
222
+ # +parsed_body+ for effective testing of controller actions including even API endpoints.
348
223
  #
349
224
  # == Basic example
350
225
  #
@@ -359,13 +234,13 @@ module ActionController
359
234
  # class BooksControllerTest < ActionController::TestCase
360
235
  # def test_create
361
236
  # # Simulate a POST response with the given HTTP parameters.
362
- # post(:create, book: { title: "Love Hina" })
237
+ # post(:create, params: { book: { title: "Love Hina" }})
363
238
  #
364
- # # Assert that the controller tried to redirect us to
239
+ # # Asserts that the controller tried to redirect us to
365
240
  # # the created book's URI.
366
241
  # assert_response :found
367
242
  #
368
- # # Assert that the controller really put the book in the database.
243
+ # # Asserts that the controller really put the book in the database.
369
244
  # assert_not_nil Book.find_by(title: "Love Hina")
370
245
  # end
371
246
  # end
@@ -389,7 +264,7 @@ module ActionController
389
264
  # request. You can modify this object before sending the HTTP request. For example,
390
265
  # you might want to set some session properties before sending a GET request.
391
266
  # <b>@response</b>::
392
- # An ActionController::TestResponse object, representing the response
267
+ # An ActionDispatch::TestResponse object, representing the response
393
268
  # of the last HTTP response. In the above example, <tt>@response</tt> becomes valid
394
269
  # after calling +post+. If the various assert methods are not sufficient, then you
395
270
  # may use this object to inspect the HTTP response in detail.
@@ -412,21 +287,15 @@ module ActionController
412
287
  # In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions
413
288
  # can be used against. These collections are:
414
289
  #
415
- # * assigns: Instance variables assigned in the action that are available for the view.
416
290
  # * session: Objects being saved in the session.
417
291
  # * flash: The flash objects currently in the session.
418
292
  # * cookies: \Cookies being sent to the user on this request.
419
293
  #
420
294
  # These collections can be used just like any other hash:
421
295
  #
422
- # assert_not_nil assigns(:person) # makes sure that a @person instance variable was set
423
296
  # assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
424
297
  # assert flash.empty? # makes sure that there's nothing in the flash
425
298
  #
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
299
  # On top of the collections, you have the complete url that a given action redirected to available in <tt>redirect_to_url</tt>.
431
300
  #
432
301
  # For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
@@ -499,169 +368,233 @@ module ActionController
499
368
  # Simulate a GET request with the given parameters.
500
369
  #
501
370
  # - +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
371
+ # - +params+: The hash with HTTP parameters that you want to pass. This may be +nil+.
372
+ # - +body+: The request body with a string that is appropriately encoded
504
373
  # (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
505
374
  # - +session+: A hash of parameters to store in the session. This may be +nil+.
506
375
  # - +flash+: A hash of parameters to store in the flash. This may be +nil+.
507
376
  #
508
377
  # You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with
509
378
  # +post+, +patch+, +put+, +delete+, and +head+.
379
+ # Example sending parameters, session and setting a flash message:
380
+ #
381
+ # get :show,
382
+ # params: { id: 7 },
383
+ # session: { user_id: 1 },
384
+ # flash: { notice: 'This is flash message' }
510
385
  #
511
386
  # Note that the request method is not verified. The different methods are
512
387
  # available to make the tests more expressive.
513
388
  def get(action, *args)
514
- process(action, "GET", *args)
389
+ res = process_with_kwargs("GET", action, *args)
390
+ cookies.update res.cookies
391
+ res
515
392
  end
516
393
 
517
394
  # Simulate a POST request with the given parameters and set/volley the response.
518
395
  # See +get+ for more details.
519
396
  def post(action, *args)
520
- process(action, "POST", *args)
397
+ process_with_kwargs("POST", action, *args)
521
398
  end
522
399
 
523
400
  # Simulate a PATCH request with the given parameters and set/volley the response.
524
401
  # See +get+ for more details.
525
402
  def patch(action, *args)
526
- process(action, "PATCH", *args)
403
+ process_with_kwargs("PATCH", action, *args)
527
404
  end
528
405
 
529
406
  # Simulate a PUT request with the given parameters and set/volley the response.
530
407
  # See +get+ for more details.
531
408
  def put(action, *args)
532
- process(action, "PUT", *args)
409
+ process_with_kwargs("PUT", action, *args)
533
410
  end
534
411
 
535
412
  # Simulate a DELETE request with the given parameters and set/volley the response.
536
413
  # See +get+ for more details.
537
414
  def delete(action, *args)
538
- process(action, "DELETE", *args)
415
+ process_with_kwargs("DELETE", action, *args)
539
416
  end
540
417
 
541
418
  # Simulate a HEAD request with the given parameters and set/volley the response.
542
419
  # See +get+ for more details.
543
420
  def head(action, *args)
544
- process(action, "HEAD", *args)
421
+ process_with_kwargs("HEAD", action, *args)
545
422
  end
546
423
 
547
- def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
424
+ def xml_http_request(*args)
425
+ ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc)
426
+ `xhr` and `xml_http_request` are deprecated and will be removed in Rails 5.1.
427
+ Switch to e.g. `post :create, params: { comment: { body: 'Honey bunny' } }, xhr: true`.
428
+ MSG
429
+
548
430
  @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
431
+ @request.env['HTTP_ACCEPT'] ||= [Mime[:js], Mime[:html], Mime[:xml], 'text/xml', '*/*'].join(', ')
432
+ __send__(*args).tap do
551
433
  @request.env.delete 'HTTP_X_REQUESTED_WITH'
552
434
  @request.env.delete 'HTTP_ACCEPT'
553
435
  end
554
436
  end
555
437
  alias xhr :xml_http_request
556
438
 
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
568
- end
569
-
570
- # Simulate a HTTP request to +action+ by specifying request method,
439
+ # Simulate an HTTP request to +action+ by specifying request method,
571
440
  # parameters and set/volley the response.
572
441
  #
573
442
  # - +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+).
443
+ # - +method+: Request method used to send the HTTP request. Possible values
444
+ # are +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, +HEAD+. Defaults to +GET+. Can be a symbol.
445
+ # - +params+: The hash with HTTP parameters that you want to pass. This may be +nil+.
446
+ # - +body+: The request body with a string that is appropriately encoded
447
+ # (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
579
448
  # - +session+: A hash of parameters to store in the session. This may be +nil+.
580
449
  # - +flash+: A hash of parameters to store in the flash. This may be +nil+.
450
+ # - +format+: Request format. Defaults to +nil+. Can be string or symbol.
451
+ # - +as+: Content type. Defaults to +nil+. Must be a symbol that corresponds
452
+ # to a mime type.
581
453
  #
582
454
  # Example calling +create+ action and sending two params:
583
455
  #
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' }
456
+ # process :create,
457
+ # method: 'POST',
458
+ # params: {
459
+ # user: { name: 'Gaurish Sharma', email: 'user@example.com' }
460
+ # },
461
+ # session: { user_id: 1 },
462
+ # flash: { notice: 'This is flash message' }
589
463
  #
590
464
  # To simulate +GET+, +POST+, +PATCH+, +PUT+, +DELETE+ and +HEAD+ requests
591
465
  # prefer using #get, #post, #patch, #put, #delete and #head methods
592
466
  # respectively which will make tests more expressive.
593
467
  #
594
468
  # Note that the request method is not verified.
595
- def process(action, http_method = 'GET', *args)
469
+ def process(action, *args)
596
470
  check_required_ivars
597
471
 
598
- if args.first.is_a?(String) && http_method != 'HEAD'
599
- @request.env['RAW_POST_DATA'] = args.shift
472
+ if kwarg_request?(args)
473
+ parameters, session, body, flash, http_method, format, xhr, as = args[0].values_at(:params, :session, :body, :flash, :method, :format, :xhr, :as)
474
+ else
475
+ http_method, parameters, session, flash = args
476
+ format = nil
477
+
478
+ if parameters.is_a?(String) && http_method != 'HEAD'
479
+ body = parameters
480
+ parameters = nil
481
+ end
482
+
483
+ if parameters || session || flash
484
+ non_kwarg_request_warning
485
+ end
600
486
  end
601
487
 
602
- parameters, session, flash = args
603
- parameters ||= {}
488
+ if body
489
+ @request.set_header 'RAW_POST_DATA', body
490
+ end
491
+
492
+ if http_method
493
+ http_method = http_method.to_s.upcase
494
+ else
495
+ http_method = "GET"
496
+ end
604
497
 
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)
498
+ parameters ||= {}
608
499
 
609
500
  @html_document = nil
610
- @html_scanner_document = nil
611
501
 
612
- unless @controller.respond_to?(:recycle!)
613
- @controller.extend(Testing::Functional)
614
- end
502
+ self.cookies.update @request.cookies
503
+ self.cookies.update_cookies_from_jar
504
+ @request.set_header 'HTTP_COOKIE', cookies.to_header
505
+ @request.delete_header 'action_dispatch.cookies'
615
506
 
616
- @request.recycle!
617
- @response.recycle!
507
+ @request = TestRequest.new scrub_env!(@request.env), @request.session
508
+ @response = build_response @response_klass
509
+ @response.request = @request
618
510
  @controller.recycle!
619
511
 
620
- @request.env['REQUEST_METHOD'] = http_method
512
+ @request.set_header 'REQUEST_METHOD', http_method
621
513
 
622
- controller_class_name = @controller.class.anonymous? ?
623
- "anonymous" :
624
- @controller.class.controller_path
514
+ if as
515
+ @request.content_type = Mime[as].to_s
516
+ format ||= as
517
+ end
625
518
 
626
- @request.assign_parameters(@routes, controller_class_name, action.to_s, parameters)
519
+ if format
520
+ parameters[:format] = format
521
+ end
522
+
523
+ parameters = parameters.symbolize_keys
524
+
525
+ generated_extras = @routes.generate_extras(parameters.merge(controller: controller_class_name, action: action.to_s))
526
+ generated_path = generated_path(generated_extras)
527
+ query_string_keys = query_parameter_names(generated_extras)
528
+
529
+ @request.assign_parameters(@routes, controller_class_name, action.to_s, parameters, generated_path, query_string_keys)
627
530
 
628
531
  @request.session.update(session) if session
629
532
  @request.flash.update(flash || {})
630
533
 
631
- @controller.request = @request
632
- @controller.response = @response
633
-
634
- build_request_uri(action, parameters)
534
+ if xhr
535
+ @request.set_header 'HTTP_X_REQUESTED_WITH', 'XMLHttpRequest'
536
+ @request.fetch_header('HTTP_ACCEPT') do |k|
537
+ @request.set_header k, [Mime[:js], Mime[:html], Mime[:xml], 'text/xml', '*/*'].join(', ')
538
+ end
539
+ end
635
540
 
636
- name = @request.parameters[:action]
541
+ @request.fetch_header("SCRIPT_NAME") do |k|
542
+ @request.set_header k, @controller.config.relative_url_root
543
+ end
637
544
 
638
- @controller.recycle!
639
- @controller.process(name)
545
+ begin
546
+ @controller.recycle!
547
+ @controller.dispatch(action, @request, @response)
548
+ ensure
549
+ @request = @controller.request
550
+ @response = @controller.response
551
+
552
+ if @request.have_cookie_jar?
553
+ unless @request.cookie_jar.committed?
554
+ @request.cookie_jar.write(@response)
555
+ self.cookies.update(@request.cookie_jar.instance_variable_get(:@cookies))
556
+ end
557
+ end
558
+ @response.prepare!
640
559
 
641
- if cookies = @request.env['action_dispatch.cookies']
642
- unless @response.committed?
643
- cookies.write(@response)
560
+ if flash_value = @request.flash.to_session_value
561
+ @request.session['flash'] = flash_value
562
+ else
563
+ @request.session.delete('flash')
644
564
  end
645
- end
646
- @response.prepare!
647
565
 
648
- @assigns = @controller.respond_to?(:view_assigns) ? @controller.view_assigns : {}
566
+ if xhr
567
+ @request.delete_header 'HTTP_X_REQUESTED_WITH'
568
+ @request.delete_header 'HTTP_ACCEPT'
569
+ end
570
+ @request.query_string = ''
649
571
 
650
- if flash_value = @request.flash.to_session_value
651
- @request.session['flash'] = flash_value
572
+ @response.sent!
652
573
  end
653
574
 
654
575
  @response
655
576
  end
656
577
 
578
+ def controller_class_name
579
+ @controller.class.anonymous? ? "anonymous" : @controller.class.controller_path
580
+ end
581
+
582
+ def generated_path(generated_extras)
583
+ generated_extras[0]
584
+ end
585
+
586
+ def query_parameter_names(generated_extras)
587
+ generated_extras[1] + [:controller, :action]
588
+ end
589
+
657
590
  def setup_controller_request_and_response
658
591
  @controller = nil unless defined? @controller
659
592
 
660
- response_klass = TestResponse
593
+ @response_klass = ActionDispatch::TestResponse
661
594
 
662
595
  if klass = self.class.controller_class
663
596
  if klass < ActionController::Live
664
- response_klass = LiveTestResponse
597
+ @response_klass = LiveTestResponse
665
598
  end
666
599
  unless @controller
667
600
  begin
@@ -672,8 +605,8 @@ module ActionController
672
605
  end
673
606
  end
674
607
 
675
- @request = build_request
676
- @response = build_response response_klass
608
+ @request = TestRequest.create
609
+ @response = build_response @response_klass
677
610
  @response.request = @request
678
611
 
679
612
  if @controller
@@ -682,12 +615,8 @@ module ActionController
682
615
  end
683
616
  end
684
617
 
685
- def build_request
686
- TestRequest.new
687
- end
688
-
689
618
  def build_response(klass)
690
- klass.new
619
+ klass.create
691
620
  end
692
621
 
693
622
  included do
@@ -695,10 +624,55 @@ module ActionController
695
624
  include ActionDispatch::Assertions
696
625
  class_attribute :_controller_class
697
626
  setup :setup_controller_request_and_response
627
+ ActiveSupport.run_load_hooks(:action_controller_test_case, self)
698
628
  end
699
629
 
700
630
  private
701
631
 
632
+ def scrub_env!(env)
633
+ env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
634
+ env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
635
+ env.delete 'action_dispatch.request.query_parameters'
636
+ env.delete 'action_dispatch.request.request_parameters'
637
+ env['rack.input'] = StringIO.new
638
+ env
639
+ end
640
+
641
+ def process_with_kwargs(http_method, action, *args)
642
+ if kwarg_request?(args)
643
+ args.first.merge!(method: http_method)
644
+ process(action, *args)
645
+ else
646
+ non_kwarg_request_warning if args.any?
647
+
648
+ args = args.unshift(http_method)
649
+ process(action, *args)
650
+ end
651
+ end
652
+
653
+ REQUEST_KWARGS = %i(params session flash method body xhr)
654
+ FORMAT_KWARGS = %i(format as)
655
+ def kwarg_request?(args)
656
+ args.size == 1 && args[0].respond_to?(:keys) && (
657
+ args[0].keys.all? { |k| FORMAT_KWARGS.include?(k) } ||
658
+ args[0].keys.any? { |k| REQUEST_KWARGS.include?(k) }
659
+ )
660
+ end
661
+
662
+ def non_kwarg_request_warning
663
+ ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc)
664
+ Using positional arguments in functional tests has been deprecated,
665
+ in favor of keyword arguments, and will be removed in Rails 5.1.
666
+
667
+ Deprecated style:
668
+ get :show, { id: 1 }, nil, { notice: "This is a flash message" }
669
+
670
+ New keyword style:
671
+ get :show, params: { id: 1 }, flash: { notice: "This is a flash message" },
672
+ session: nil # Can safely be omitted.
673
+ MSG
674
+ end
675
+
702
676
  def document_root_element
703
677
  html_document.root
704
678
  end
@@ -713,43 +687,6 @@ module ActionController
713
687
  end
714
688
  end
715
689
 
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
743
- 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
- end
751
- end
752
-
753
690
  def html_format?(parameters)
754
691
  return true unless parameters.key?(:format)
755
692
  Mime.fetch(parameters[:format]) { Mime['html'] }.html?