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