actionpack 4.2.10 → 5.0.0

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 (131) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +553 -401
  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 +52 -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 +10 -13
  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 +11 -32
  26. data/lib/action_controller/metal/etag_with_template_digest.rb +1 -1
  27. data/lib/action_controller/metal/exceptions.rb +11 -6
  28. data/lib/action_controller/metal/force_ssl.rb +10 -10
  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 +66 -88
  35. data/lib/action_controller/metal/mime_responds.rb +27 -42
  36. data/lib/action_controller/metal/params_wrapper.rb +8 -8
  37. data/lib/action_controller/metal/redirecting.rb +32 -9
  38. data/lib/action_controller/metal/renderers.rb +85 -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 +293 -90
  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/renderer.rb +111 -0
  48. data/lib/action_controller/template_assertions.rb +9 -0
  49. data/lib/action_controller/test_case.rb +288 -368
  50. data/lib/action_controller.rb +12 -9
  51. data/lib/action_dispatch/http/cache.rb +73 -34
  52. data/lib/action_dispatch/http/filter_parameters.rb +15 -11
  53. data/lib/action_dispatch/http/filter_redirect.rb +7 -8
  54. data/lib/action_dispatch/http/headers.rb +44 -13
  55. data/lib/action_dispatch/http/mime_negotiation.rb +41 -23
  56. data/lib/action_dispatch/http/mime_type.rb +126 -90
  57. data/lib/action_dispatch/http/mime_types.rb +3 -4
  58. data/lib/action_dispatch/http/parameter_filter.rb +18 -8
  59. data/lib/action_dispatch/http/parameters.rb +54 -41
  60. data/lib/action_dispatch/http/request.rb +149 -82
  61. data/lib/action_dispatch/http/response.rb +206 -102
  62. data/lib/action_dispatch/http/url.rb +117 -8
  63. data/lib/action_dispatch/journey/formatter.rb +39 -28
  64. data/lib/action_dispatch/journey/gtg/transition_table.rb +1 -1
  65. data/lib/action_dispatch/journey/nfa/dot.rb +0 -2
  66. data/lib/action_dispatch/journey/nfa/transition_table.rb +1 -46
  67. data/lib/action_dispatch/journey/nodes/node.rb +14 -4
  68. data/lib/action_dispatch/journey/parser_extras.rb +4 -0
  69. data/lib/action_dispatch/journey/path/pattern.rb +38 -42
  70. data/lib/action_dispatch/journey/route.rb +74 -19
  71. data/lib/action_dispatch/journey/router/utils.rb +5 -5
  72. data/lib/action_dispatch/journey/router.rb +5 -9
  73. data/lib/action_dispatch/journey/routes.rb +14 -15
  74. data/lib/action_dispatch/journey/visitors.rb +86 -43
  75. data/lib/action_dispatch/middleware/callbacks.rb +10 -1
  76. data/lib/action_dispatch/middleware/cookies.rb +189 -135
  77. data/lib/action_dispatch/middleware/debug_exceptions.rb +124 -49
  78. data/lib/action_dispatch/middleware/exception_wrapper.rb +21 -21
  79. data/lib/action_dispatch/middleware/executor.rb +19 -0
  80. data/lib/action_dispatch/middleware/flash.rb +66 -45
  81. data/lib/action_dispatch/middleware/params_parser.rb +32 -46
  82. data/lib/action_dispatch/middleware/public_exceptions.rb +2 -2
  83. data/lib/action_dispatch/middleware/reloader.rb +14 -58
  84. data/lib/action_dispatch/middleware/remote_ip.rb +29 -19
  85. data/lib/action_dispatch/middleware/request_id.rb +11 -6
  86. data/lib/action_dispatch/middleware/session/abstract_store.rb +23 -11
  87. data/lib/action_dispatch/middleware/session/cache_store.rb +9 -6
  88. data/lib/action_dispatch/middleware/session/cookie_store.rb +30 -24
  89. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +4 -0
  90. data/lib/action_dispatch/middleware/show_exceptions.rb +11 -9
  91. data/lib/action_dispatch/middleware/ssl.rb +115 -36
  92. data/lib/action_dispatch/middleware/stack.rb +44 -40
  93. data/lib/action_dispatch/middleware/static.rb +51 -35
  94. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
  95. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  96. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -1
  97. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  98. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
  99. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +59 -63
  100. data/lib/action_dispatch/railtie.rb +2 -2
  101. data/lib/action_dispatch/request/session.rb +69 -33
  102. data/lib/action_dispatch/request/utils.rb +51 -19
  103. data/lib/action_dispatch/routing/inspector.rb +32 -43
  104. data/lib/action_dispatch/routing/mapper.rb +491 -338
  105. data/lib/action_dispatch/routing/polymorphic_routes.rb +8 -14
  106. data/lib/action_dispatch/routing/redirection.rb +3 -3
  107. data/lib/action_dispatch/routing/route_set.rb +145 -238
  108. data/lib/action_dispatch/routing/url_for.rb +27 -10
  109. data/lib/action_dispatch/routing.rb +17 -13
  110. data/lib/action_dispatch/testing/assertion_response.rb +45 -0
  111. data/lib/action_dispatch/testing/assertions/response.rb +38 -20
  112. data/lib/action_dispatch/testing/assertions/routing.rb +11 -10
  113. data/lib/action_dispatch/testing/assertions.rb +1 -1
  114. data/lib/action_dispatch/testing/integration.rb +368 -97
  115. data/lib/action_dispatch/testing/test_process.rb +5 -6
  116. data/lib/action_dispatch/testing/test_request.rb +22 -31
  117. data/lib/action_dispatch/testing/test_response.rb +7 -4
  118. data/lib/action_dispatch.rb +3 -1
  119. data/lib/action_pack/gem_version.rb +3 -3
  120. data/lib/action_pack.rb +1 -1
  121. metadata +30 -34
  122. data/lib/action_controller/metal/hide_actions.rb +0 -40
  123. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  124. data/lib/action_controller/middleware.rb +0 -39
  125. data/lib/action_controller/model_naming.rb +0 -12
  126. data/lib/action_dispatch/journey/backwards.rb +0 -5
  127. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  128. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  129. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  130. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
  131. /data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +0 -0
@@ -1,224 +1,75 @@
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
10
+ # :stopdoc:
11
+ class Metal
12
+ include Testing::Functional
13
+ end
12
14
 
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
45
-
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
15
+ module Live
16
+ # Disable controller / rendering threads in tests. User tests can access
17
+ # the database on the main thread, so they could open a txn, then the
18
+ # controller thread will open a new connection and try to access data
19
+ # that's only visible to the main thread's txn. This is the problem in #23483
20
+ remove_method :new_controller_thread
21
+ def new_controller_thread # :nodoc:
22
+ yield
55
23
  end
24
+ end
56
25
 
57
- def teardown_subscriptions
58
- return unless defined?(@_subscribers)
26
+ # ActionController::TestCase will be deprecated and moved to a gem in Rails 5.1.
27
+ # Please use ActionDispatch::IntegrationTest going forward.
28
+ class TestRequest < ActionDispatch::TestRequest #:nodoc:
29
+ DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup
30
+ DEFAULT_ENV.delete 'PATH_INFO'
59
31
 
60
- @_subscribers.each do |subscriber|
61
- ActiveSupport::Notifications.unsubscribe(subscriber)
62
- end
32
+ def self.new_session
33
+ TestSession.new
63
34
  end
64
35
 
65
- def process(*args)
66
- reset_template_assertion
67
- super
36
+ # Create a new test request with default `env` values
37
+ def self.create
38
+ env = {}
39
+ env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
40
+ env["rack.request.cookie_hash"] = {}.with_indifferent_access
41
+ new(default_env.merge(env), new_session)
68
42
  end
69
43
 
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
44
+ def self.default_env
45
+ DEFAULT_ENV
77
46
  end
47
+ private_class_method :default_env
78
48
 
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
49
+ def initialize(env, session)
50
+ super(env)
155
51
 
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
52
+ self.session = session
53
+ self.session_options = TestSession::DEFAULT_OPTIONS
54
+ @custom_param_parsers = {
55
+ xml: lambda { |raw_post| Hash.from_xml(raw_post)['hash'] }
56
+ }
194
57
  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
58
 
201
- def initialize(env = {})
202
- super
59
+ def query_string=(string)
60
+ set_header Rack::QUERY_STRING, string
61
+ end
203
62
 
204
- self.session = TestSession.new
205
- self.session_options = TestSession::DEFAULT_OPTIONS.merge(:id => SecureRandom.hex(16))
63
+ def content_type=(type)
64
+ set_header 'CONTENT_TYPE', type
206
65
  end
207
66
 
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
67
+ def assign_parameters(routes, controller_path, action, parameters, generated_path, query_string_keys)
68
+ non_path_parameters = {}
69
+ path_parameters = {}
220
70
 
221
- if extra_keys.include?(key)
71
+ parameters.each do |key, value|
72
+ if query_string_keys.include?(key)
222
73
  non_path_parameters[key] = value
223
74
  else
224
75
  if value.is_a?(Array)
@@ -231,72 +82,88 @@ module ActionController
231
82
  end
232
83
  end
233
84
 
234
- # Clear the combined params hash in case it was already referenced.
235
- @env.delete("action_dispatch.request.parameters")
85
+ if get?
86
+ if self.query_string.blank?
87
+ self.query_string = non_path_parameters.to_query
88
+ end
89
+ else
90
+ if ENCODER.should_multipart?(non_path_parameters)
91
+ self.content_type = ENCODER.content_type
92
+ data = ENCODER.build_multipart non_path_parameters
93
+ else
94
+ fetch_header('CONTENT_TYPE') do |k|
95
+ set_header k, 'application/x-www-form-urlencoded'
96
+ end
236
97
 
237
- # Clear the filter cache variables so they're not stale
238
- @filtered_parameters = @filtered_env = @filtered_path = nil
98
+ case content_mime_type.to_sym
99
+ when nil
100
+ raise "Unknown Content-Type: #{content_type}"
101
+ when :json
102
+ data = ActiveSupport::JSON.encode(non_path_parameters)
103
+ when :xml
104
+ data = non_path_parameters.to_xml
105
+ when :url_encoded_form
106
+ data = non_path_parameters.to_query
107
+ else
108
+ @custom_param_parsers[content_mime_type.symbol] = ->(_) { non_path_parameters }
109
+ data = non_path_parameters.to_query
110
+ end
111
+ end
239
112
 
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)
113
+ set_header 'CONTENT_LENGTH', data.length.to_s
114
+ set_header 'rack.input', StringIO.new(data)
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)
@@ -359,13 +226,13 @@ module ActionController
359
226
  # class BooksControllerTest < ActionController::TestCase
360
227
  # def test_create
361
228
  # # Simulate a POST response with the given HTTP parameters.
362
- # post(:create, book: { title: "Love Hina" })
229
+ # post(:create, params: { book: { title: "Love Hina" }})
363
230
  #
364
- # # Assert that the controller tried to redirect us to
231
+ # # Asserts that the controller tried to redirect us to
365
232
  # # the created book's URI.
366
233
  # assert_response :found
367
234
  #
368
- # # Assert that the controller really put the book in the database.
235
+ # # Asserts that the controller really put the book in the database.
369
236
  # assert_not_nil Book.find_by(title: "Love Hina")
370
237
  # end
371
238
  # end
@@ -389,7 +256,7 @@ module ActionController
389
256
  # request. You can modify this object before sending the HTTP request. For example,
390
257
  # you might want to set some session properties before sending a GET request.
391
258
  # <b>@response</b>::
392
- # An ActionController::TestResponse object, representing the response
259
+ # An ActionDispatch::TestResponse object, representing the response
393
260
  # of the last HTTP response. In the above example, <tt>@response</tt> becomes valid
394
261
  # after calling +post+. If the various assert methods are not sufficient, then you
395
262
  # may use this object to inspect the HTTP response in detail.
@@ -412,21 +279,15 @@ module ActionController
412
279
  # In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions
413
280
  # can be used against. These collections are:
414
281
  #
415
- # * assigns: Instance variables assigned in the action that are available for the view.
416
282
  # * session: Objects being saved in the session.
417
283
  # * flash: The flash objects currently in the session.
418
284
  # * cookies: \Cookies being sent to the user on this request.
419
285
  #
420
286
  # These collections can be used just like any other hash:
421
287
  #
422
- # assert_not_nil assigns(:person) # makes sure that a @person instance variable was set
423
288
  # assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
424
289
  # assert flash.empty? # makes sure that there's nothing in the flash
425
290
  #
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
291
  # On top of the collections, you have the complete url that a given action redirected to available in <tt>redirect_to_url</tt>.
431
292
  #
432
293
  # For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
@@ -499,169 +360,228 @@ module ActionController
499
360
  # Simulate a GET request with the given parameters.
500
361
  #
501
362
  # - +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
363
+ # - +params+: The hash with HTTP parameters that you want to pass. This may be +nil+.
364
+ # - +body+: The request body with a string that is appropriately encoded
504
365
  # (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
505
366
  # - +session+: A hash of parameters to store in the session. This may be +nil+.
506
367
  # - +flash+: A hash of parameters to store in the flash. This may be +nil+.
507
368
  #
508
369
  # You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with
509
370
  # +post+, +patch+, +put+, +delete+, and +head+.
371
+ # Example sending parameters, session and setting a flash message:
372
+ #
373
+ # get :show,
374
+ # params: { id: 7 },
375
+ # session: { user_id: 1 },
376
+ # flash: { notice: 'This is flash message' }
510
377
  #
511
378
  # Note that the request method is not verified. The different methods are
512
379
  # available to make the tests more expressive.
513
380
  def get(action, *args)
514
- process(action, "GET", *args)
381
+ res = process_with_kwargs("GET", action, *args)
382
+ cookies.update res.cookies
383
+ res
515
384
  end
516
385
 
517
386
  # Simulate a POST request with the given parameters and set/volley the response.
518
387
  # See +get+ for more details.
519
388
  def post(action, *args)
520
- process(action, "POST", *args)
389
+ process_with_kwargs("POST", action, *args)
521
390
  end
522
391
 
523
392
  # Simulate a PATCH request with the given parameters and set/volley the response.
524
393
  # See +get+ for more details.
525
394
  def patch(action, *args)
526
- process(action, "PATCH", *args)
395
+ process_with_kwargs("PATCH", action, *args)
527
396
  end
528
397
 
529
398
  # Simulate a PUT request with the given parameters and set/volley the response.
530
399
  # See +get+ for more details.
531
400
  def put(action, *args)
532
- process(action, "PUT", *args)
401
+ process_with_kwargs("PUT", action, *args)
533
402
  end
534
403
 
535
404
  # Simulate a DELETE request with the given parameters and set/volley the response.
536
405
  # See +get+ for more details.
537
406
  def delete(action, *args)
538
- process(action, "DELETE", *args)
407
+ process_with_kwargs("DELETE", action, *args)
539
408
  end
540
409
 
541
410
  # Simulate a HEAD request with the given parameters and set/volley the response.
542
411
  # See +get+ for more details.
543
412
  def head(action, *args)
544
- process(action, "HEAD", *args)
413
+ process_with_kwargs("HEAD", action, *args)
545
414
  end
546
415
 
547
- def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
416
+ def xml_http_request(*args)
417
+ ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc)
418
+ xhr and xml_http_request methods are deprecated in favor of
419
+ `get :index, xhr: true` and `post :create, xhr: true`
420
+ MSG
421
+
548
422
  @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
423
+ @request.env['HTTP_ACCEPT'] ||= [Mime[:js], Mime[:html], Mime[:xml], 'text/xml', '*/*'].join(', ')
424
+ __send__(*args).tap do
551
425
  @request.env.delete 'HTTP_X_REQUESTED_WITH'
552
426
  @request.env.delete 'HTTP_ACCEPT'
553
427
  end
554
428
  end
555
429
  alias xhr :xml_http_request
556
430
 
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,
431
+ # Simulate an HTTP request to +action+ by specifying request method,
571
432
  # parameters and set/volley the response.
572
433
  #
573
434
  # - +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+).
435
+ # - +method+: Request method used to send the HTTP request. Possible values
436
+ # are +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, +HEAD+. Defaults to +GET+. Can be a symbol.
437
+ # - +params+: The hash with HTTP parameters that you want to pass. This may be +nil+.
438
+ # - +body+: The request body with a string that is appropriately encoded
439
+ # (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
579
440
  # - +session+: A hash of parameters to store in the session. This may be +nil+.
580
441
  # - +flash+: A hash of parameters to store in the flash. This may be +nil+.
442
+ # - +format+: Request format. Defaults to +nil+. Can be string or symbol.
581
443
  #
582
444
  # Example calling +create+ action and sending two params:
583
445
  #
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' }
446
+ # process :create,
447
+ # method: 'POST',
448
+ # params: {
449
+ # user: { name: 'Gaurish Sharma', email: 'user@example.com' }
450
+ # },
451
+ # session: { user_id: 1 },
452
+ # flash: { notice: 'This is flash message' }
589
453
  #
590
454
  # To simulate +GET+, +POST+, +PATCH+, +PUT+, +DELETE+ and +HEAD+ requests
591
455
  # prefer using #get, #post, #patch, #put, #delete and #head methods
592
456
  # respectively which will make tests more expressive.
593
457
  #
594
458
  # Note that the request method is not verified.
595
- def process(action, http_method = 'GET', *args)
459
+ def process(action, *args)
596
460
  check_required_ivars
597
461
 
598
- if args.first.is_a?(String) && http_method != 'HEAD'
599
- @request.env['RAW_POST_DATA'] = args.shift
462
+ if kwarg_request?(args)
463
+ parameters, session, body, flash, http_method, format, xhr = args[0].values_at(:params, :session, :body, :flash, :method, :format, :xhr)
464
+ else
465
+ http_method, parameters, session, flash = args
466
+ format = nil
467
+
468
+ if parameters.is_a?(String) && http_method != 'HEAD'
469
+ body = parameters
470
+ parameters = nil
471
+ end
472
+
473
+ if parameters || session || flash
474
+ non_kwarg_request_warning
475
+ end
476
+ end
477
+
478
+ if body
479
+ @request.set_header 'RAW_POST_DATA', body
480
+ end
481
+
482
+ if http_method
483
+ http_method = http_method.to_s.upcase
484
+ else
485
+ http_method = "GET"
600
486
  end
601
487
 
602
- parameters, session, flash = args
603
488
  parameters ||= {}
604
489
 
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)
490
+ if format
491
+ parameters[:format] = format
492
+ end
608
493
 
609
494
  @html_document = nil
610
- @html_scanner_document = nil
611
495
 
612
- unless @controller.respond_to?(:recycle!)
613
- @controller.extend(Testing::Functional)
614
- end
496
+ self.cookies.update @request.cookies
497
+ self.cookies.update_cookies_from_jar
498
+ @request.set_header 'HTTP_COOKIE', cookies.to_header
499
+ @request.delete_header 'action_dispatch.cookies'
615
500
 
616
- @request.recycle!
617
- @response.recycle!
501
+ @request = TestRequest.new scrub_env!(@request.env), @request.session
502
+ @response = build_response @response_klass
503
+ @response.request = @request
618
504
  @controller.recycle!
619
505
 
620
- @request.env['REQUEST_METHOD'] = http_method
506
+ @request.set_header 'REQUEST_METHOD', http_method
621
507
 
622
- controller_class_name = @controller.class.anonymous? ?
623
- "anonymous" :
624
- @controller.class.controller_path
508
+ parameters = parameters.symbolize_keys
625
509
 
626
- @request.assign_parameters(@routes, controller_class_name, action.to_s, parameters)
510
+ generated_extras = @routes.generate_extras(parameters.merge(controller: controller_class_name, action: action.to_s))
511
+ generated_path = generated_path(generated_extras)
512
+ query_string_keys = query_parameter_names(generated_extras)
513
+
514
+ @request.assign_parameters(@routes, controller_class_name, action.to_s, parameters, generated_path, query_string_keys)
627
515
 
628
516
  @request.session.update(session) if session
629
517
  @request.flash.update(flash || {})
630
518
 
631
- @controller.request = @request
632
- @controller.response = @response
519
+ if xhr
520
+ @request.set_header 'HTTP_X_REQUESTED_WITH', 'XMLHttpRequest'
521
+ @request.fetch_header('HTTP_ACCEPT') do |k|
522
+ @request.set_header k, [Mime[:js], Mime[:html], Mime[:xml], 'text/xml', '*/*'].join(', ')
523
+ end
524
+ end
633
525
 
634
- build_request_uri(action, parameters)
526
+ @request.fetch_header("SCRIPT_NAME") do |k|
527
+ @request.set_header k, @controller.config.relative_url_root
528
+ end
635
529
 
636
- name = @request.parameters[:action]
530
+ begin
531
+ @controller.recycle!
532
+ @controller.dispatch(action, @request, @response)
533
+ ensure
534
+ @request = @controller.request
535
+ @response = @controller.response
637
536
 
638
- @controller.recycle!
639
- @controller.process(name)
537
+ @request.delete_header 'HTTP_COOKIE'
640
538
 
641
- if cookies = @request.env['action_dispatch.cookies']
642
- unless @response.committed?
643
- cookies.write(@response)
539
+ if @request.have_cookie_jar?
540
+ unless @request.cookie_jar.committed?
541
+ @request.cookie_jar.write(@response)
542
+ self.cookies.update(@request.cookie_jar.instance_variable_get(:@cookies))
543
+ end
644
544
  end
645
- end
646
- @response.prepare!
545
+ @response.prepare!
647
546
 
648
- @assigns = @controller.respond_to?(:view_assigns) ? @controller.view_assigns : {}
547
+ if flash_value = @request.flash.to_session_value
548
+ @request.session['flash'] = flash_value
549
+ else
550
+ @request.session.delete('flash')
551
+ end
649
552
 
650
- if flash_value = @request.flash.to_session_value
651
- @request.session['flash'] = flash_value
553
+ if xhr
554
+ @request.delete_header 'HTTP_X_REQUESTED_WITH'
555
+ @request.delete_header 'HTTP_ACCEPT'
556
+ end
557
+ @request.query_string = ''
558
+
559
+ @response.sent!
652
560
  end
653
561
 
654
562
  @response
655
563
  end
656
564
 
565
+ def controller_class_name
566
+ @controller.class.anonymous? ? "anonymous" : @controller.class.controller_path
567
+ end
568
+
569
+ def generated_path(generated_extras)
570
+ generated_extras[0]
571
+ end
572
+
573
+ def query_parameter_names(generated_extras)
574
+ generated_extras[1] + [:controller, :action]
575
+ end
576
+
657
577
  def setup_controller_request_and_response
658
578
  @controller = nil unless defined? @controller
659
579
 
660
- response_klass = TestResponse
580
+ @response_klass = ActionDispatch::TestResponse
661
581
 
662
582
  if klass = self.class.controller_class
663
583
  if klass < ActionController::Live
664
- response_klass = LiveTestResponse
584
+ @response_klass = LiveTestResponse
665
585
  end
666
586
  unless @controller
667
587
  begin
@@ -672,8 +592,8 @@ module ActionController
672
592
  end
673
593
  end
674
594
 
675
- @request = build_request
676
- @response = build_response response_klass
595
+ @request = TestRequest.create
596
+ @response = build_response @response_klass
677
597
  @response.request = @request
678
598
 
679
599
  if @controller
@@ -682,12 +602,8 @@ module ActionController
682
602
  end
683
603
  end
684
604
 
685
- def build_request
686
- TestRequest.new
687
- end
688
-
689
605
  def build_response(klass)
690
- klass.new
606
+ klass.create
691
607
  end
692
608
 
693
609
  included do
@@ -699,6 +615,46 @@ module ActionController
699
615
 
700
616
  private
701
617
 
618
+ def scrub_env!(env)
619
+ env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
620
+ env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
621
+ env.delete 'action_dispatch.request.query_parameters'
622
+ env.delete 'action_dispatch.request.request_parameters'
623
+ env
624
+ end
625
+
626
+ def process_with_kwargs(http_method, action, *args)
627
+ if kwarg_request?(args)
628
+ args.first.merge!(method: http_method)
629
+ process(action, *args)
630
+ else
631
+ non_kwarg_request_warning if args.any?
632
+
633
+ args = args.unshift(http_method)
634
+ process(action, *args)
635
+ end
636
+ end
637
+
638
+ REQUEST_KWARGS = %i(params session flash method body xhr)
639
+ def kwarg_request?(args)
640
+ args[0].respond_to?(:keys) && (
641
+ (args[0].key?(:format) && args[0].keys.size == 1) ||
642
+ args[0].keys.any? { |k| REQUEST_KWARGS.include?(k) }
643
+ )
644
+ end
645
+
646
+ def non_kwarg_request_warning
647
+ ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc)
648
+ ActionController::TestCase HTTP request methods will accept only
649
+ keyword arguments in future Rails versions.
650
+
651
+ Examples:
652
+
653
+ get :show, params: { id: 1 }, session: { user_id: 1 }
654
+ process :update, method: :post, params: { id: 1 }
655
+ MSG
656
+ end
657
+
702
658
  def document_root_element
703
659
  html_document.root
704
660
  end
@@ -713,43 +669,6 @@ module ActionController
713
669
  end
714
670
  end
715
671
 
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
672
  def html_format?(parameters)
754
673
  return true unless parameters.key?(:format)
755
674
  Mime.fetch(parameters[:format]) { Mime['html'] }.html?
@@ -758,4 +677,5 @@ module ActionController
758
677
 
759
678
  include Behavior
760
679
  end
680
+ # :startdoc:
761
681
  end